project: add COMPAT_VERSION keyword

Fixes: #26893
This commit is contained in:
Vito Gamberini
2025-04-22 07:00:06 -04:00
parent 468270d8ac
commit 0138df29dc
25 changed files with 189 additions and 15 deletions

View File

@@ -11,6 +11,7 @@ Synopsis
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[COMPAT_VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
@@ -86,6 +87,24 @@ The options are:
``CMakeLists.txt``, then the version is also stored in the variable
:variable:`CMAKE_PROJECT_VERSION`.
``COMPAT_VERSION <version>``
.. note::
Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
Optional; requires ``VERSION`` also be set.
Takes a ``<version>`` argument composed of non-negative integer components,
i.e. ``<major>[.<minor>[.<patch>[.<tweak>]]]``,
and sets the variables
* :variable:`PROJECT_COMPAT_VERSION`,
:variable:`<PROJECT-NAME>_COMPAT_VERSION`
When the ``project()`` command is called from the top-level
``CMakeLists.txt``, then the compatibility version is also stored in the
variable :variable:`CMAKE_PROJECT_COMPAT_VERSION`.
``DESCRIPTION <project-description-string>``
.. versionadded:: 3.9

View File

@@ -105,6 +105,7 @@ Variables that Provide Information
/variable/CMAKE_PROJECT_VERSION_MINOR
/variable/CMAKE_PROJECT_VERSION_PATCH
/variable/CMAKE_PROJECT_VERSION_TWEAK
/variable/CMAKE_PROJECT_COMPAT_VERSION
/variable/CMAKE_RANLIB
/variable/CMAKE_ROOT
/variable/CMAKE_RULE_MESSAGES
@@ -161,6 +162,7 @@ Variables that Provide Information
/variable/PROJECT-NAME_VERSION_MINOR
/variable/PROJECT-NAME_VERSION_PATCH
/variable/PROJECT-NAME_VERSION_TWEAK
/variable/PROJECT-NAME_COMPAT_VERSION
/variable/PROJECT_BINARY_DIR
/variable/PROJECT_DESCRIPTION
/variable/PROJECT_HOMEPAGE_URL
@@ -172,6 +174,7 @@ Variables that Provide Information
/variable/PROJECT_VERSION_MINOR
/variable/PROJECT_VERSION_PATCH
/variable/PROJECT_VERSION_TWEAK
/variable/PROJECT_COMPAT_VERSION
Variables that Change Behavior
==============================

View File

@@ -0,0 +1,6 @@
project-compat-version
----------------------
* The :command:`project` command now has experimental support for the
``COMPAT_VERSION`` keyword, gated by
``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.

View File

@@ -0,0 +1,39 @@
CMAKE_PROJECT_COMPAT_VERSION
----------------------------
.. note::
Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
The compatibility version of the top level project.
This variable holds the compatibility version of the project as specified in the
top level CMakeLists.txt file by a :command:`project` command. In the event
that the top level CMakeLists.txt contains multiple :command:`project` calls,
the most recently called one from that top level CMakeLists.txt will determine
the value that ``CMAKE_PROJECT_COMPAT_VERSION`` contains. For example, consider
the following top level CMakeLists.txt:
.. code-block:: cmake
cmake_minimum_required(VERSION 4.1)
project(First VERSION 9.0 COMPAT_VERSION 1.2.3)
project(Second VERSION 9.0 COMPAT_VERSION 3.4.5)
add_subdirectory(sub)
project(Third VERSION 9.0 COMPAT_VERSION 6.7.8)
And ``sub/CMakeLists.txt`` with the following contents:
.. code-block:: cmake
project(SubProj VERSION 2.0 COMPAT_VERSION 1.0)
message("CMAKE_PROJECT_VERSION = ${CMAKE_PROJECT_VERSION}")
The most recently seen :command:`project` command from the top level
CMakeLists.txt would be ``project(Second ...)``, so this will print::
CMAKE_PROJECT_COMPAT_VERSION = 3.4.5
To obtain the version from the most recent call to :command:`project` in
the current directory scope or above, see the :variable:`PROJECT_COMPAT_VERSION`
variable.

View File

@@ -0,0 +1,9 @@
<PROJECT-NAME>_COMPAT_VERSION
-----------------------------
.. note::
Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
Value given to the ``COMPAT_VERSION`` option of the most recent call to the
:command:`project` command with project name ``<PROJECT-NAME>``, if any.

View File

@@ -0,0 +1,10 @@
PROJECT_COMPAT_VERSION
----------------------
.. note::
Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``.
Value given to the ``COMPAT_VERSION`` option of the most recent call to the
:command:`project` command, if any. To obtain the compatibility version of the
top level project, see the :variable:`CMAKE_PROJECT_COMPAT_VERSION` variable.

View File

@@ -17,6 +17,7 @@
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmExecutionStatus.h"
#include "cmExperimental.h"
#include "cmList.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
@@ -36,6 +37,7 @@ struct ProjectArguments : ArgumentParser::ParseResult
{
cm::optional<std::string> ProjectName;
cm::optional<std::string> Version;
cm::optional<std::string> CompatVersion;
cm::optional<std::string> Description;
cm::optional<std::string> HomepageURL;
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Languages;
@@ -61,22 +63,30 @@ bool cmProjectCommand(std::vector<std::string> const& args,
std::vector<cm::string_view> missingValueKeywords;
std::vector<cm::string_view> parsedKeywords;
ProjectArguments prArgs;
ProjectArgumentParser{}
.BindKeywordMissingValue(missingValueKeywords)
ProjectArgumentParser parser;
parser.BindKeywordMissingValue(missingValueKeywords)
.BindParsedKeywords(parsedKeywords)
.Bind(0, prArgs.ProjectName)
.Bind("VERSION"_s, prArgs.Version)
.Bind("DESCRIPTION"_s, prArgs.Description)
.Bind("HOMEPAGE_URL"_s, prArgs.HomepageURL)
.Bind("LANGUAGES"_s, prArgs.Languages)
.Parse(args, &unparsedArgs, 0);
.Bind("LANGUAGES"_s, prArgs.Languages);
cmMakefile& mf = status.GetMakefile();
bool enableCompatVersion = cmExperimental::HasSupportEnabled(
mf, cmExperimental::Feature::ExportPackageInfo);
if (enableCompatVersion) {
parser.Bind("COMPAT_VERSION"_s, prArgs.CompatVersion);
}
parser.Parse(args, &unparsedArgs, 0);
if (!prArgs.ProjectName) {
status.SetError("PROJECT called with incorrect number of arguments");
return false;
}
cmMakefile& mf = status.GetMakefile();
if (mf.IsRootMakefile() &&
!mf.GetDefinition("CMAKE_MINIMUM_REQUIRED_VERSION")) {
mf.IssueMessage(
@@ -168,13 +178,21 @@ bool cmProjectCommand(std::vector<std::string> const& args,
prArgs.Languages->emplace_back("NONE");
}
if (prArgs.CompatVersion && !prArgs.Version) {
mf.IssueMessage(MessageType::FATAL_ERROR,
"project with COMPAT_VERSION must also provide VERSION.");
cmSystemTools::SetFatalErrorOccurred();
return true;
}
cmsys::RegularExpression vx(
R"(^([0-9]+(\.[0-9]+(\.[0-9]+(\.[0-9]+)?)?)?)?$)");
constexpr std::size_t MAX_VERSION_COMPONENTS = 4u;
std::string version_string;
std::array<std::string, MAX_VERSION_COMPONENTS> version_components;
if (prArgs.Version) {
cmsys::RegularExpression vx(
R"(^([0-9]+(\.[0-9]+(\.[0-9]+(\.[0-9]+)?)?)?)?$)");
if (!vx.find(*prArgs.Version)) {
std::string e =
R"(VERSION ")" + *prArgs.Version + R"(" format invalid.)";
@@ -215,19 +233,41 @@ bool cmProjectCommand(std::vector<std::string> const& args,
}
}
auto create_variables = [&](cm::string_view var, std::string const& val) {
if (prArgs.CompatVersion) {
if (!vx.find(*prArgs.CompatVersion)) {
std::string e =
R"(COMPAT_VERSION ")" + *prArgs.CompatVersion + R"(" format invalid.)";
mf.IssueMessage(MessageType::FATAL_ERROR, e);
cmSystemTools::SetFatalErrorOccurred();
return true;
}
if (cmSystemTools::VersionCompareGreater(*prArgs.CompatVersion,
*prArgs.Version)) {
mf.IssueMessage(MessageType::FATAL_ERROR,
"COMPAT_VERSION must be less than or equal to VERSION");
cmSystemTools::SetFatalErrorOccurred();
return true;
}
}
auto createVariables = [&](cm::string_view var, std::string const& val) {
mf.AddDefinition(cmStrCat("PROJECT_"_s, var), val);
mf.AddDefinition(cmStrCat(*prArgs.ProjectName, "_"_s, var), val);
TopLevelCMakeVarCondSet(mf, cmStrCat("CMAKE_PROJECT_"_s, var), val);
};
create_variables("VERSION"_s, version_string);
create_variables("VERSION_MAJOR"_s, version_components[0]);
create_variables("VERSION_MINOR"_s, version_components[1]);
create_variables("VERSION_PATCH"_s, version_components[2]);
create_variables("VERSION_TWEAK"_s, version_components[3]);
create_variables("DESCRIPTION"_s, prArgs.Description.value_or(""));
create_variables("HOMEPAGE_URL"_s, prArgs.HomepageURL.value_or(""));
createVariables("VERSION"_s, version_string);
createVariables("VERSION_MAJOR"_s, version_components[0]);
createVariables("VERSION_MINOR"_s, version_components[1]);
createVariables("VERSION_PATCH"_s, version_components[2]);
createVariables("VERSION_TWEAK"_s, version_components[3]);
createVariables("DESCRIPTION"_s, prArgs.Description.value_or(""));
createVariables("HOMEPAGE_URL"_s, prArgs.HomepageURL.value_or(""));
if (enableCompatVersion) {
createVariables("COMPAT_VERSION"_s, prArgs.CompatVersion.value_or(""));
}
if (unparsedArgs.empty() && !prArgs.Languages) {
// if no language is specified do c and c++

View File

@@ -0,0 +1,3 @@
-- PROJECT_COMPAT_VERSION=1.0.0
-- CMAKE_PROJECT_COMPAT_VERSION=1.0.0
-- ProjectCompatVersionTest_COMPAT_VERSION=1.0.0

View File

@@ -0,0 +1,13 @@
project(ProjectCompatVersionTest VERSION 1.5.0 COMPAT_VERSION 1.0.0 LANGUAGES)
if(NOT PROJECT_COMPAT_VERSION)
message(FATAL_ERROR "PROJECT_COMPAT_VERSION expected to be set")
endif()
if(NOT CMAKE_PROJECT_COMPAT_VERSION)
message(FATAL_ERROR "CMAKE_PROJECT_COMPAT_VERSION expected to be set")
endif()
if(NOT ProjectCompatVersionTest_COMPAT_VERSION)
message(FATAL_ERROR "ProjectCompatVersionTest_COMPAT_VERSION expected to be set")
endif()
message(STATUS "PROJECT_COMPAT_VERSION=${PROJECT_COMPAT_VERSION}")
message(STATUS "CMAKE_PROJECT_COMPAT_VERSION=${CMAKE_PROJECT_COMPAT_VERSION}")
message(STATUS "ProjectCompatVersionTest_COMPAT_VERSION=${ProjectCompatVersionTest_COMPAT_VERSION}")

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1 @@
COMPAT_VERSION may be specified at most once.

View File

@@ -0,0 +1 @@
project(ProjectCompatVersionTest VERSION 1.5.0 COMPAT_VERSION 1.0.0 COMPAT_VERSION 1.0.0 LANGUAGES)

View File

@@ -0,0 +1 @@
project(ProjectCompatVersionTest VERSION 1.0.0 COMPAT_VERSION 1.0.0 LANGUAGES)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,4 @@
CMake Error at ProjectCompatVersionInvalid.cmake:[0-9]+ \(project\):
COMPAT_VERSION "NONE" format invalid.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)$

View File

@@ -0,0 +1 @@
project(ProjectCompatVersionTest VERSION 1.5.0 COMPAT_VERSION NONE LANGUAGES)

View File

@@ -0,0 +1 @@
project with COMPAT_VERSION must also provide VERSION.

View File

@@ -0,0 +1 @@
project(ProjectCompatVersionTest COMPAT_VERSION 1.0.0 LANGUAGES)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1 @@
COMPAT_VERSION must be less than or equal to VERSION

View File

@@ -0,0 +1 @@
project(ProjectCompatVersionTest VERSION 1.0.0 COMPAT_VERSION 1.5.0 LANGUAGES)

View File

@@ -0,0 +1,2 @@
COMPAT_VERSION keyword not followed by a value or was followed by a value
that expanded to nothing.

View File

@@ -0,0 +1 @@
project(ProjectCompatVersionTest VERSION 1.5.0 COMPAT_VERSION LANGUAGES)

View File

@@ -53,6 +53,19 @@ run_cmake(VersionMissingValueOkay)
run_cmake(VersionTwice)
run_cmake(VersionMax)
set(opts
"-DCMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO=b80be207-778e-46ba-8080-b23bba22639e"
"-Wno-dev"
)
run_cmake_with_options(ProjectCompatVersion ${opts})
run_cmake_with_options(ProjectCompatVersion2 ${opts})
run_cmake_with_options(ProjectCompatVersionEqual ${opts})
run_cmake_with_options(ProjectCompatVersionInvalid ${opts})
run_cmake_with_options(ProjectCompatVersionMissingVersion ${opts})
run_cmake_with_options(ProjectCompatVersionNewer ${opts})
run_cmake_with_options(ProjectCompatVersionNoArg ${opts})
run_cmake(CMP0048-NEW)
run_cmake(CMP0096-WARN)