From aa16b8eb9a6c817e25bf9b2a939a7f5d00e4f7b8 Mon Sep 17 00:00:00 2001 From: Vito Gamberini Date: Mon, 1 Sep 2025 18:08:06 -0400 Subject: [PATCH] project: Revert changes to VERSION handling In c4d12d7d we changed how the project command handles various variables and made the behavior consistent across all variables controlled by the project command. The conditions under which project() modifies the various VERSION variables are non-intuitive, but can be observed as side-effects. Because this behavior is observable, it requires a policy to be changed. This commit reverts to the previous behavior until a policy is added and adds a test for that behavior. Fixes: #27142 --- Source/cmProjectCommand.cxx | 40 ++++++++++++++++--- Tests/RunCMake/project/Omissions.cmake | 14 ++++--- Tests/RunCMake/project/RunCMakeTest.cmake | 1 + Tests/RunCMake/project/VersionAbsent.cmake | 14 +++++++ .../project/VersionSubdir/CMakeLists.txt | 24 +++++++++++ 5 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 Tests/RunCMake/project/VersionAbsent.cmake create mode 100644 Tests/RunCMake/project/VersionSubdir/CMakeLists.txt diff --git a/Source/cmProjectCommand.cxx b/Source/cmProjectCommand.cxx index 1a94eb9f5d..8d362f8b05 100644 --- a/Source/cmProjectCommand.cxx +++ b/Source/cmProjectCommand.cxx @@ -202,6 +202,7 @@ bool cmProjectCommand(std::vector const& args, std::string version_string; std::array version_components; + bool has_version = prArgs.Version.has_value(); if (prArgs.Version) { if (!vx.find(*prArgs.Version)) { std::string e = @@ -267,11 +268,40 @@ bool cmProjectCommand(std::vector const& args, TopLevelCMakeVarCondSet(mf, cmStrCat("CMAKE_PROJECT_"_s, var), val); }; - 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]); + // Note, this intentionally doesn't touch cache variables as the legacy + // behavior did not modify cache + auto checkAndClearVariables = [&](cm::string_view var) { + std::vector vv = { "PROJECT_", cmStrCat(projectName, "_") }; + if (mf.IsRootMakefile()) { + vv.push_back("CMAKE_PROJECT_"); + } + for (std::string const& prefix : vv) { + std::string def = cmStrCat(prefix, var); + if (!mf.GetDefinition(def).IsEmpty()) { + mf.AddDefinition(def, ""); + } + } + }; + + // TODO: We should treat VERSION the same as all other project variables, but + // setting it to empty string unconditionally causes various behavior + // changes. It needs a policy. For now, maintain the old behavior and add a + // policy in a future release. + + if (has_version) { + 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]); + } else { + checkAndClearVariables("VERSION"_s); + checkAndClearVariables("VERSION_MAJOR"_s); + checkAndClearVariables("VERSION_MINOR"_s); + checkAndClearVariables("VERSION_PATCH"_s); + checkAndClearVariables("VERSION_TWEAK"_s); + } + createVariables("COMPAT_VERSION"_s, prArgs.CompatVersion.value_or("")); createVariables("DESCRIPTION"_s, prArgs.Description.value_or("")); createVariables("HOMEPAGE_URL"_s, prArgs.HomepageURL.value_or("")); diff --git a/Tests/RunCMake/project/Omissions.cmake b/Tests/RunCMake/project/Omissions.cmake index 79f695f604..d0649e7a14 100644 --- a/Tests/RunCMake/project/Omissions.cmake +++ b/Tests/RunCMake/project/Omissions.cmake @@ -14,11 +14,15 @@ function(expect_empty SUFFIX) endfunction() project(Omissions LANGUAGES) -expect_empty(VERSION) -expect_empty(VERSION_MAJOR) -expect_empty(VERSION_MINOR) -expect_empty(VERSION_PATCH) -expect_empty(VERSION_TWEAK) + +# TODO: Test these when a policy is in place to handle projects expecting +# legacy handling of the version variables +# expect_empty(VERSION) +# expect_empty(VERSION_MAJOR) +# expect_empty(VERSION_MINOR) +# expect_empty(VERSION_PATCH) +# expect_empty(VERSION_TWEAK) + expect_empty(COMPAT_VERSION) expect_empty(DESCRIPTION) expect_empty(HOMEPAGE_URL) diff --git a/Tests/RunCMake/project/RunCMakeTest.cmake b/Tests/RunCMake/project/RunCMakeTest.cmake index 92b6de593c..7376a11d8c 100644 --- a/Tests/RunCMake/project/RunCMakeTest.cmake +++ b/Tests/RunCMake/project/RunCMakeTest.cmake @@ -48,6 +48,7 @@ run_cmake(ProjectIsTopLevelMultiple) run_cmake(ProjectIsTopLevelSubdirectory) run_cmake(ProjectTwice) run_cmake(Omissions) +run_cmake(VersionAbsent) run_cmake(VersionAndLanguagesEmpty) run_cmake(VersionEmpty) run_cmake(VersionInvalid) diff --git a/Tests/RunCMake/project/VersionAbsent.cmake b/Tests/RunCMake/project/VersionAbsent.cmake new file mode 100644 index 0000000000..0091036ce7 --- /dev/null +++ b/Tests/RunCMake/project/VersionAbsent.cmake @@ -0,0 +1,14 @@ +# TODO: In the future this should be a policy test, but for now verify version +# variables remain undefined for various configurations. + +project(NoVersion LANGUAGES NONE) + +foreach(pre "NoVersion_" "PROJECT_" "CMAKE_PROJECT_") + foreach(post "" "_MAJOR" "_MINOR" "_PATCH" "_TWEAK") + if(DEFINED ${pre}VERSION${post}) + message(SEND_ERROR "${pre}VERSION${post} is defined when no project version was provided") + endif() + endforeach() +endforeach() + +add_subdirectory(VersionSubdir) diff --git a/Tests/RunCMake/project/VersionSubdir/CMakeLists.txt b/Tests/RunCMake/project/VersionSubdir/CMakeLists.txt new file mode 100644 index 0000000000..f1aeca68a3 --- /dev/null +++ b/Tests/RunCMake/project/VersionSubdir/CMakeLists.txt @@ -0,0 +1,24 @@ +project(WithVersion LANGUAGES NONE VERSION 1.1.1.1) + +project(SubdirNoVersion LANGUAGES NONE) + +foreach(post "" "_MAJOR" "_MINOR" "_PATCH" "_TWEAK") + if(NOT DEFINED PROJECT_VERSION${post}) + message(SEND_ERROR "PROJECT_VERSION${post} is not defined when previous project version was provided") + continue() + endif() + + if(NOT PROJECT_VERSION${post} STREQUAL "") + message(SEND_ERROR "PROJECT_VERSION${post} has value '${PROJECT_VERSION${post}}' when empty string is expected") + endif() +endforeach() + +if(NOT CMAKE_PROJECT_VERSION STREQUAL "1.1.1.1") + message(SEND_ERROR "CMAKE_PROJECT_VERSION has value '${CMAKE_PROJECT_VERION}' when 1.1.1.1 is expected") +endif() + +foreach(post "_MAJOR" "_MINOR" "_PATCH" "_TWEAK") + if(NOT CMAKE_PROJECT_VERSION${post} STREQUAL "1") + message(SEND_ERROR "CMAKE_PROJECT_VERSION${post} has value '${CMAKE_PROJECT_VERSION${post}}' when 1 is expected") + endif() +endforeach()