From 220fc8622d030f06e286ffe06abef9f16102eba0 Mon Sep 17 00:00:00 2001 From: Arha Gatram Date: Thu, 25 Jun 2026 16:36:02 -0700 Subject: [PATCH 1/2] block: Fix incorrect policy scope creation order Creates the policy scope after the variable scope instead of the other way round. This ensures that the cmStateSnapshot which the policy is registered to is the same as the one created with the variable scope so that reading a policy from the parent snapshot does not incorrectly read policies from the new snapshot. --- Source/cmBlockCommand.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/cmBlockCommand.cxx b/Source/cmBlockCommand.cxx index 575efe646c..d57c17051a 100644 --- a/Source/cmBlockCommand.cxx +++ b/Source/cmBlockCommand.cxx @@ -40,19 +40,19 @@ public: BlockScopePushPop& operator=(BlockScopePushPop const&) = delete; private: - std::unique_ptr PolicyScope; std::unique_ptr VariableScope; + std::unique_ptr PolicyScope; std::unique_ptr DiagnosticScope; }; BlockScopePushPop::BlockScopePushPop(cmMakefile* mf, ScopeSet const& scopes) { - if (scopes.contains(ScopeType::POLICIES)) { - this->PolicyScope = cm::make_unique(mf); - } if (scopes.contains(ScopeType::VARIABLES)) { this->VariableScope = cm::make_unique(mf); } + if (scopes.contains(ScopeType::POLICIES)) { + this->PolicyScope = cm::make_unique(mf); + } if (scopes.contains(ScopeType::DIAGNOSTICS)) { this->DiagnosticScope = cm::make_unique(mf); } From 86759dce817cf1b7788f64925b7d5dc5599998da Mon Sep 17 00:00:00 2001 From: Arha Gatram Date: Thu, 25 Jun 2026 16:29:11 -0700 Subject: [PATCH 2/2] enable_language: add support for usage in subdirectories Propagates variables set in enable_language() logic up variable scopes so that the language can be used by targets in the ancestor directories. Works through block() and function() scopes as well. The add_subdirectory() call can be thought of as the effective call site for enabling the language. The same holds for endblock() and the actual function call. Adds a policy CMP0220 to control this behavior. As the language is "enabled" at each ancestor scope, the policy is checked. If OLD, the language is not enabled at this scope and the ones above. Closes: #27881 #27508 #27389 Related: #26751 #27564 --- Help/command/enable_language.rst | 13 +-- Help/manual/cmake-policies.7.rst | 8 ++ Help/policy/CMP0220.rst | 64 +++++++++++++ .../dev/enable_language-subdirectory.rst | 9 ++ .../variable/CMAKE_POLICY_WARNING_CMPNNNN.rst | 2 + Source/cmGlobalGenerator.cxx | 37 ++++++++ Source/cmMakefile.cxx | 19 ++++ Source/cmMakefile.h | 2 + Source/cmPolicies.h | 6 +- Source/cmStateSnapshot.cxx | 92 +++++++++++++++++++ Source/cmStateSnapshot.h | 15 +++ Tests/RunCMake/CMakeLists.txt | 7 ++ .../CMP0220-MIXED-block.cmake | 13 +++ .../CMP0220-MIXED-implicit-RC.cmake | 29 ++++++ .../CMP0220-MIXED-nested-block.cmake | 20 ++++ .../CMP0220-NEW-function.cmake | 14 +++ .../CMP0220-NEW-multilevel.cmake | 11 +++ .../EnableLanguageSubdir/CMP0220-NEW.cmake | 10 ++ .../EnableLanguageSubdir/CMP0220-OLD.cmake | 10 ++ .../CMP0220-WARN-block-stderr.txt | 12 +++ .../CMP0220-WARN-block.cmake | 12 +++ .../CMP0220-WARN-default.cmake | 3 + .../CMP0220-WARN-stderr.txt | 10 ++ .../CMP0220-WARN-top-level.cmake | 6 ++ .../EnableLanguageSubdir/CMP0220-WARN.cmake | 5 + .../EnableLanguageSubdir/CMakeLists.txt | 3 + .../EnableLanguageSubdir/CUDAParent.cmake | 14 +++ .../EnableLanguageSubdir/CUDASibling.cmake | 9 ++ .../EnableLanguageSubdir/CXXImplicitRC.cmake | 7 ++ .../EnableLanguageSubdir/RunCMakeTest.cmake | 48 ++++++++++ .../EnableLanguageSubdir/cuda-main.cxx | 7 ++ .../EnableLanguageSubdir/cuda/CMakeLists.txt | 4 + .../RunCMake/EnableLanguageSubdir/cuda/src.cu | 14 +++ .../RunCMake/EnableLanguageSubdir/cuda/src.h | 2 + .../EnableLanguageSubdir/cxx/CMakeLists.txt | 1 + .../EnableLanguageSubdir/outer/CMakeLists.txt | 5 + .../outer/inner/CMakeLists.txt | 3 + .../RunCMake/EnableLanguageSubdir/rc-main.cxx | 4 + .../RunCMake/EnableLanguageSubdir/resource.rc | 4 + .../sibling/CMakeLists.txt | 6 ++ .../EnableLanguageSubdir/sibling/main.cxx | 7 ++ 41 files changed, 560 insertions(+), 7 deletions(-) create mode 100644 Help/policy/CMP0220.rst create mode 100644 Help/release/dev/enable_language-subdirectory.rst create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-block.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-implicit-RC.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-nested-block.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW-function.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW-multilevel.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-OLD.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-block-stderr.txt create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-block.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-default.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-stderr.txt create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-top-level.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CMakeLists.txt create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CUDAParent.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CUDASibling.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/CXXImplicitRC.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/RunCMakeTest.cmake create mode 100644 Tests/RunCMake/EnableLanguageSubdir/cuda-main.cxx create mode 100644 Tests/RunCMake/EnableLanguageSubdir/cuda/CMakeLists.txt create mode 100644 Tests/RunCMake/EnableLanguageSubdir/cuda/src.cu create mode 100644 Tests/RunCMake/EnableLanguageSubdir/cuda/src.h create mode 100644 Tests/RunCMake/EnableLanguageSubdir/cxx/CMakeLists.txt create mode 100644 Tests/RunCMake/EnableLanguageSubdir/outer/CMakeLists.txt create mode 100644 Tests/RunCMake/EnableLanguageSubdir/outer/inner/CMakeLists.txt create mode 100644 Tests/RunCMake/EnableLanguageSubdir/rc-main.cxx create mode 100644 Tests/RunCMake/EnableLanguageSubdir/resource.rc create mode 100644 Tests/RunCMake/EnableLanguageSubdir/sibling/CMakeLists.txt create mode 100644 Tests/RunCMake/EnableLanguageSubdir/sibling/main.cxx diff --git a/Help/command/enable_language.rst b/Help/command/enable_language.rst index 7584e3589a..21849eb11e 100644 --- a/Help/command/enable_language.rst +++ b/Help/command/enable_language.rst @@ -15,14 +15,15 @@ variables that are created by the :command:`project` command. The following restrictions apply to where ``enable_language()`` may be called: -* It must be called in file scope, not in a :command:`function` call - nor inside a :command:`block()`. * It must not be called before the first call to :command:`project`. See policy :policy:`CMP0165`. -* It must be called in the highest directory common to all targets - using the named language directly for compiling sources or - indirectly through link dependencies. It is simplest to enable all - needed languages in the top-level directory of a project. +* It must be called such that the command executes before any targets that + use the language directly for compiling sources or indirectly through link + dependencies. + +.. note:: + Further restrictions apply if policy :policy:`CMP0220` is not set + to ``NEW``. See the policy documentation for details. The ``OPTIONAL`` keyword is a placeholder for future implementation and does not currently work. Instead you can use the :module:`CheckLanguage` diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst index a535116192..505ecd1a00 100644 --- a/Help/manual/cmake-policies.7.rst +++ b/Help/manual/cmake-policies.7.rst @@ -94,6 +94,14 @@ Supported Policies The following policies are supported. +Policies Introduced by CMake 4.5 +-------------------------------- + +.. toctree:: + :maxdepth: 1 + + CMP0220: Languages enabled in subdirectories propagate to the top-level directory. + Policies Introduced by CMake 4.4 -------------------------------- diff --git a/Help/policy/CMP0220.rst b/Help/policy/CMP0220.rst new file mode 100644 index 0000000000..cfa0ceebec --- /dev/null +++ b/Help/policy/CMP0220.rst @@ -0,0 +1,64 @@ +CMP0220 +------- + +.. versionadded:: 4.5 + +Languages enabled in subdirectories propagate to the top-level directory. + +In CMake 4.4 and below, enabling a language with the :command:`project` +command or :command:`enable_language` command had some restrictions: + +* It had to be called in file scope, not in a :command:`function` call + nor inside a :command:`block`. +* It had be called in the highest directory common to all targets + using the named language directly for compiling sources or + indirectly through link dependencies. + +While these restrictions could be met by enabling all needed languages +in the top-level directory of a project, this was not always easy. + +CMake 4.5 and above prefer to relax these restrictions so that languages +enabled by calls to the :command:`project` and :command:`enable_language` +commands in subdirectories are available to targets created in ancestor +and sibling directories created afterward. This policy provides +compatibility with projects that have not been updated for the new behavior. + +One may think of an :command:`add_subdirectory` call as an effective call +site to :command:`enable_language` for languages enabled inside the +subdirectory's tree. Similarly, for languages enabled in :command:`function` +and :command:`block` scopes, the function or :command:`endblock` calls are +effectively call sites to :command:`enable_language`. + +This policy is evaluated independently at each "effective call site", +proceeding up the stack of variable scopes to determine if the language +should be enabled at that level. This propagation stops at the first +ancestor scope whose ``CMP0220`` is not ``NEW``. For example: + +.. code-block:: cmake + + cmake_policy(SET CMP0220 NEW) + + block() + cmake_policy(SET CMP0220 OLD) + block() + cmake_policy(SET CMP0220 NEW) + block() + cmake_policy(SET CMP0220 OLD) + enable_language(CXX) + # CXX is enabled in this scope + endblock() + # CXX is enabled in this scope + endblock() + # CXX is not enabled in this scope + endblock() + # CXX is not enabled in this scope + +.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.5 +.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn by default +.. include:: include/STANDARD_ADVICE.rst + +See documentation of the +:variable:`CMAKE_POLICY_WARNING_CMP0220 >` +variable to control the warning. + +.. include:: include/DEPRECATED.rst diff --git a/Help/release/dev/enable_language-subdirectory.rst b/Help/release/dev/enable_language-subdirectory.rst new file mode 100644 index 0000000000..6e5be9d3ce --- /dev/null +++ b/Help/release/dev/enable_language-subdirectory.rst @@ -0,0 +1,9 @@ +enable_language-subdirectory +---------------------------- + +* The :command:`enable_language` command, and the :command:`project` command + that calls it, may now be used in a subdirectory to enable a language for + targets in ancestor directory scopes. The language configuration is + propagated up to enclosing scopes. This also lifts the restriction for + calling inside of :command:`block` and :command:`function` commands. See + policy :policy:`CMP0220`. diff --git a/Help/variable/CMAKE_POLICY_WARNING_CMPNNNN.rst b/Help/variable/CMAKE_POLICY_WARNING_CMPNNNN.rst index 1197560fdf..f0b1ed6077 100644 --- a/Help/variable/CMAKE_POLICY_WARNING_CMPNNNN.rst +++ b/Help/variable/CMAKE_POLICY_WARNING_CMPNNNN.rst @@ -42,6 +42,8 @@ only for the policies that do not warn by default: policy :policy:`CMP0172`. * ``CMAKE_POLICY_WARNING_CMP0206`` controls the warning for policy :policy:`CMP0206`. +* ``CMAKE_POLICY_WARNING_CMP0220`` controls the warning for + policy :policy:`CMP0220`. This variable should not be set by a project in CMake code. Project developers running CMake may set this variable in their cache to diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 5b8c0d7537..4110460f96 100644 --- a/Source/cmGlobalGenerator.cxx +++ b/Source/cmGlobalGenerator.cxx @@ -626,6 +626,13 @@ void cmGlobalGenerator::EnableLanguage( return; } + bool propagate = true; + // If enable_language calls logic that calls enable_language, we don't + // need to propagate variables twice + if (!this->LanguagesInProgress.empty()) { + propagate = false; + } + std::set cur_languages(languages.begin(), languages.end()); for (std::string const& li : cur_languages) { if (!this->LanguagesInProgress.insert(li).second) { @@ -658,6 +665,12 @@ void cmGlobalGenerator::EnableLanguage( } } + // Variable scope to capture enable_language variables to be raised to root. + std::unique_ptr variableScope; + if (propagate) { + variableScope = cm::make_unique(mf); + } + bool fatalError = false; mf->AddDefinitionBool("RUN_CONFIGURE", true); @@ -1109,6 +1122,30 @@ void cmGlobalGenerator::EnableLanguage( for (std::string const& lang : cur_languages) { this->LanguagesInProgress.erase(lang); } + + // Propagate captured variables and set them at all scopes up to the root + if (propagate) { + cmStateSnapshot snapshot = mf->GetStateSnapshot(); + bool warnCMP0220 = false; + for (std::string const& key : snapshot.LocalKeys()) { + // Should never need to propagate unsets + if (!key.empty()) { + if (mf->RaiseToRoot(key, snapshot.GetDefinition(key).GetCStr()) == + cmStateSnapshot::WarnCMP0220::Yes) { + warnCMP0220 = true; + } + } + } + + if (warnCMP0220 && + mf->PolicyOptionalWarningEnabled("CMAKE_POLICY_WARNING_CMP0220")) { + mf->IssuePolicyWarning( + cmPolicies::CMP0220, {}, + "For compatibility with older versions of CMake, the language " + "configuration set by this call is not propagated to the enclosing " + "variable scopes."); + } + } } void cmGlobalGenerator::PrintCompilerAdvice(std::ostream& os, diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 4e8f9c80f5..f2c0348229 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -3992,6 +3992,25 @@ void cmMakefile::RaiseScope(std::vector const& variables) } } +cmStateSnapshot::WarnCMP0220 cmMakefile::RaiseToRoot(std::string const& var, + char const* varDef) +{ + if (var.empty()) { + return cmStateSnapshot::WarnCMP0220::No; + } + cmStateSnapshot::WarnCMP0220 const warnCMP0220 = + this->StateSnapshot.RaiseToRoot(var, varDef, + cmStateSnapshot::CheckCMP0220::No); +#ifndef CMAKE_BOOTSTRAP + cmVariableWatch* vv = this->GetVariableWatch(); + if (vv) { + vv->VariableAccessed(var, cmVariableWatch::VARIABLE_MODIFIED_ACCESS, + varDef, this); + } +#endif + return warnCMP0220; +} + cmTarget* cmMakefile::AddImportedTarget(std::string const& name, cm::TargetType type, cm::ImportedTargetScope scope) diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 662fccfda8..e7836957c3 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -972,6 +972,8 @@ public: this->RaiseScope(var, value.GetCStr()); } void RaiseScope(std::vector const& variables); + cmStateSnapshot::WarnCMP0220 RaiseToRoot(std::string const& var, + char const* varDef); // push and pop loop scopes void PushLoopBlockBarrier(); diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h index 5861fda9cb..4c0c5cc960 100644 --- a/Source/cmPolicies.h +++ b/Source/cmPolicies.h @@ -658,7 +658,11 @@ class cmMakefile; 4, 4, 0, WARN) \ SELECT(POLICY, CMP0219, \ "Macro invocations preserve backslashes in arguments.", 4, 4, 0, \ - WARN) + WARN) \ + SELECT(POLICY, CMP0220, \ + "Languages enabled in subdirectories propagate to the top-level " \ + "directory.", \ + 4, 5, 0, WARN) #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1) #define CM_FOR_EACH_POLICY_ID(POLICY) \ diff --git a/Source/cmStateSnapshot.cxx b/Source/cmStateSnapshot.cxx index e56817714a..1b673aa4fd 100644 --- a/Source/cmStateSnapshot.cxx +++ b/Source/cmStateSnapshot.cxx @@ -225,6 +225,43 @@ cmPolicies::PolicyStatus cmStateSnapshot::GetPolicy(cmPolicies::PolicyID id, return status; } +cmPolicies::PolicyStatus cmStateSnapshot::GetScopePolicy( + cmPolicies::PolicyID id) const +{ + if (cmPolicies::IsRemoved(id)) { + return cmPolicies::NEW; + } + + cmPolicies::PolicyStatus status = cmPolicies::WARN; + + cmLinkedTree::iterator dir = + this->Position->BuildSystemDirectory; + + // Logic mirrors GetPolicy(), except that the search starts at this + // snapshot's exact scope rather than the directory's current top scope. + cmLinkedTree::iterator leaf = + this->Position->Policies; + cmLinkedTree::iterator root = + this->Position->PolicyRoot; + while (true) { + assert(dir.IsValid()); + for (; leaf != root; ++leaf) { + if (leaf->IsDefined(id)) { + return leaf->Get(id); + } + } + cmStateDetail::PositionType e = dir->CurrentScope; + cmStateDetail::PositionType p = e->DirectoryParent; + if (p == this->State->SnapshotData.Root()) { + break; + } + dir = p->BuildSystemDirectory; + leaf = dir->CurrentScope->Policies; + root = dir->CurrentScope->PolicyRoot; + } + return status; +} + void cmStateSnapshot::PushDiagnostic(cmDiagnostics::DiagnosticMap entry, bool weak) { @@ -377,6 +414,27 @@ std::vector cmStateSnapshot::ClosureKeys() const this->Position->Root); } +std::vector cmStateSnapshot::LocalKeys() const +{ + std::vector keys = cmDefinitions::ClosureKeys( + this->Position->Vars, this->Position->ScopeParent->Vars); + + // Remove keys that are the same in the parent scope to avoid propagating + // logic meant to restore variables back to their original value. + keys.erase(std::remove_if( + keys.begin(), keys.end(), + [this](std::string const& key) { + return cmDefinitions::Get(key, this->Position->Vars, + this->Position->Root) == + cmDefinitions::Get(key, this->Position->ScopeParent->Vars, + this->Position->ScopeParent->Root); + }), + keys.end()); + std::sort(keys.begin(), keys.end()); + + return keys; +} + bool cmStateSnapshot::RaiseScope(std::string const& var, char const* varDef) { if (this->Position->ScopeParent == this->Position->DirectoryParent) { @@ -406,6 +464,40 @@ bool cmStateSnapshot::RaiseScope(std::string const& var, char const* varDef) return true; } +cmStateSnapshot::WarnCMP0220 cmStateSnapshot::RaiseToRoot( + std::string const& var, char const* varDef, CheckCMP0220 checkCMP0220) +{ + // If we are at the top of a directory, propagate to the parent directory (if + // any). + cmStateSnapshot parentScope = + this->Position->ScopeParent == this->Position->DirectoryParent + ? this->GetBuildsystemDirectoryParent() + : cmStateSnapshot(this->State, this->Position->ScopeParent); + + if (!parentScope.IsValid()) { + return WarnCMP0220::No; + } + + // First raise always occurs to propagate out of capturing variable scope. + if (checkCMP0220 == CheckCMP0220::Yes) { + switch (parentScope.GetScopePolicy(cmPolicies::CMP0220)) { + case cmPolicies::NEW: + break; + case cmPolicies::OLD: + return WarnCMP0220::No; + case cmPolicies::WARN: + return WarnCMP0220::Yes; + } + } + + if (varDef) { + parentScope.SetDefinition(var, varDef); + } else { + parentScope.RemoveDefinition(var); + } + return parentScope.RaiseToRoot(var, varDef, CheckCMP0220::Yes); +} + template void InitializeContentFromParent(T& parentContent, T& thisContent, U& contentEndPosition) diff --git a/Source/cmStateSnapshot.h b/Source/cmStateSnapshot.h index f72712346e..35d1e18959 100644 --- a/Source/cmStateSnapshot.h +++ b/Source/cmStateSnapshot.h @@ -30,8 +30,22 @@ public: void SetDefinition(std::string const& name, cm::string_view value); void RemoveDefinition(std::string const& name); std::vector ClosureKeys() const; + std::vector LocalKeys() const; bool RaiseScope(std::string const& var, char const* varDef); + enum class CheckCMP0220 + { + No, + Yes, + }; + enum class WarnCMP0220 + { + No, + Yes, + }; + WarnCMP0220 RaiseToRoot(std::string const& var, char const* varDef, + CheckCMP0220 checkCMP0220); + void SetListFile(std::string const& listfile); std::string const& GetExecutionListFile() const; @@ -54,6 +68,7 @@ public: void SetPolicy(cmPolicies::PolicyID id, cmPolicies::PolicyStatus status); cmPolicies::PolicyStatus GetPolicy(cmPolicies::PolicyID id, bool parent_scope = false) const; + cmPolicies::PolicyStatus GetScopePolicy(cmPolicies::PolicyID id) const; void PushPolicy(cmPolicies::PolicyMap const& entry, bool weak); bool PopPolicy(); bool CanPopPolicyScope() const; diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 1406ca291a..3fcc7f7c58 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -619,6 +619,13 @@ add_RunCMake_test(LanguageStandards ) set_property(TEST RunCMake.LanguageStandards APPEND PROPERTY LABELS "CUDA" "HIP") +add_RunCMake_test(EnableLanguageSubdir + -DCMake_TEST_CUDA=${CMake_TEST_CUDA} + -DCMake_TEST_RESOURCES=${CMake_TEST_RESOURCES}) +if(CMake_TEST_CUDA) + set_property(TEST RunCMake.EnableLanguageSubdir APPEND PROPERTY LABELS "CUDA") +endif() + add_RunCMake_test(LinkItemValidation) add_RunCMake_test(LinkStatic) add_RunCMake_test(ARCHIVER-prefix -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}) diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-block.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-block.cmake new file mode 100644 index 0000000000..e0aab8f764 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-block.cmake @@ -0,0 +1,13 @@ + +# enable_language() policy check should respect the policy inside of block() +cmake_policy(SET CMP0220 NEW) + +block() + cmake_policy(SET CMP0220 OLD) + add_subdirectory(cxx) + + if(CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was incorrectly propagated up a subdirectory with CMP0220 OLD inside a block()") + endif() +endblock() diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-implicit-RC.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-implicit-RC.cmake new file mode 100644 index 0000000000..a2148a60fb --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-implicit-RC.cmake @@ -0,0 +1,29 @@ + +# Policy is checked at every scope level for CXX and its implicit RC language. +cmake_policy(SET CMP0220 NEW) + +block() + cmake_policy(SET CMP0220 OLD) + block() + cmake_policy(SET CMP0220 NEW) + add_subdirectory(cxx) + + if(NOT CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was not propagated up a subdirectory with CMP0220 OLD inside a block()") + endif() + if(NOT CMAKE_RC_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): implicit RC language configuration was not propagated up a subdirectory with CMP0220 OLD inside a block()") + endif() + endblock() + + if(CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was incorrectly propagated up a subdirectory with CMP0220 OLD inside a block()") + endif() + if(CMAKE_RC_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): implicit RC language configuration was incorrectly propagated up a subdirectory with CMP0220 OLD inside a block()") + endif() +endblock() diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-nested-block.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-nested-block.cmake new file mode 100644 index 0000000000..088c4e9d6a --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-MIXED-nested-block.cmake @@ -0,0 +1,20 @@ + +# Policy is checked at every scope level inside a subdirectory +cmake_policy(SET CMP0220 NEW) + +block() + cmake_policy(SET CMP0220 OLD) + block() + cmake_policy(SET CMP0220 NEW) + add_subdirectory(cxx) + if(NOT CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was not propagated up a subdirectory with CMP0220 NEW inside a block()") + endif() + endblock() + + if(CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was incorrectly propagated up a subdirectory with CMP0220 OLD inside a block()") + endif() +endblock() diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW-function.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW-function.cmake new file mode 100644 index 0000000000..3766e05c96 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW-function.cmake @@ -0,0 +1,14 @@ + +# enable_language() propagates through a function call +cmake_policy(SET CMP0220 NEW) + +function(enable_cxx) + enable_language(CXX) +endfunction() + +enable_cxx() + +if(NOT CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was not propagated up a subdirectory with CMP0220 NEW inside a function()") +endif() diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW-multilevel.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW-multilevel.cmake new file mode 100644 index 0000000000..2f53a938cd --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW-multilevel.cmake @@ -0,0 +1,11 @@ + +# Ensure propagation through many subdirectories each with multiple scopes +cmake_policy(SET CMP0220 NEW) + +add_subdirectory(outer) + +if(NOT CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was not propagated up across " + "multiple levels of directories.") +endif() diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW.cmake new file mode 100644 index 0000000000..349cb70894 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-NEW.cmake @@ -0,0 +1,10 @@ + +# With CMP0220 NEW, a language enabled in a subdirectory is propagated up to this scope +cmake_policy(SET CMP0220 NEW) + +add_subdirectory(cxx) + +if(NOT CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was not propagated up a subdirectory with CMP0220 NEW") +endif() diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-OLD.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-OLD.cmake new file mode 100644 index 0000000000..ebf8819468 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-OLD.cmake @@ -0,0 +1,10 @@ + +# With CMP0220 OLD, a language enabled in a subdirectory is not propagated +cmake_policy(SET CMP0220 OLD) + +add_subdirectory(cxx) + +if(CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was propagated up a subdirectory with CMP0220 OLD") +endif() diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-block-stderr.txt b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-block-stderr.txt new file mode 100644 index 0000000000..b75561ae52 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-block-stderr.txt @@ -0,0 +1,12 @@ +CMake Warning \(policy\) at CMP0220-WARN-block\.cmake:[0-9]+ \(enable_language\): + Policy CMP0220 is not set: Languages enabled in subdirectories propagate to + the top-level directory\. Run "cmake --help-policy CMP0220" for policy + details\. Use the cmake_policy command to set the policy and suppress this + warning\. + + For compatibility with older versions of CMake, the language configuration + set by this call is not propagated to the enclosing variable scopes\. +Call Stack \(most recent call first\): + CMakeLists\.txt:[0-9]+ \(include\) +This warning is for project developers\. Use -Wno-author or -Wno-policy to +suppress it\. diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-block.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-block.cmake new file mode 100644 index 0000000000..34d5924763 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-block.cmake @@ -0,0 +1,12 @@ + +# A block() creates a variable scope, so enable_language() could be propagated with NEW behavior +set(CMAKE_POLICY_WARNING_CMP0220 ON) + +block() + enable_language(CXX) +endblock() + +if(CMAKE_CXX_COMPILER_LOADED) + message(FATAL_ERROR + "enable_language(): language configuration was propagated out of a block() scope with CMP0220 WARN") +endif() diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-default.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-default.cmake new file mode 100644 index 0000000000..85cb830c57 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-default.cmake @@ -0,0 +1,3 @@ + +# Without the optional warning variable set, no warning is issued +add_subdirectory(cxx) diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-stderr.txt b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-stderr.txt new file mode 100644 index 0000000000..60203d92fd --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-stderr.txt @@ -0,0 +1,10 @@ +CMake Warning \(policy\) at cxx/CMakeLists\.txt:[0-9]+ \(enable_language\): + Policy CMP0220 is not set: Languages enabled in subdirectories propagate to + the top-level directory\. Run "cmake --help-policy CMP0220" for policy + details\. Use the cmake_policy command to set the policy and suppress this + warning\. + + For compatibility with older versions of CMake, the language configuration + set by this call is not propagated to the enclosing variable scopes\. +This warning is for project developers\. Use -Wno-author or -Wno-policy to +suppress it\. diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-top-level.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-top-level.cmake new file mode 100644 index 0000000000..5d7c2a52d2 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN-top-level.cmake @@ -0,0 +1,6 @@ + +# A top-level enable_language() has no ancestor scope to propagate into, so it +# never warns for CMP0220 even when the optional warning is enabled. +set(CMAKE_POLICY_WARNING_CMP0220 ON) + +enable_language(CXX) diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN.cmake b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN.cmake new file mode 100644 index 0000000000..095aecf808 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMP0220-WARN.cmake @@ -0,0 +1,5 @@ + +# With the optional warning variable set, a warning is issued +set(CMAKE_POLICY_WARNING_CMP0220 ON) + +add_subdirectory(cxx) diff --git a/Tests/RunCMake/EnableLanguageSubdir/CMakeLists.txt b/Tests/RunCMake/EnableLanguageSubdir/CMakeLists.txt new file mode 100644 index 0000000000..0ceb19a24c --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 4.4) +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/EnableLanguageSubdir/CUDAParent.cmake b/Tests/RunCMake/EnableLanguageSubdir/CUDAParent.cmake new file mode 100644 index 0000000000..12731360be --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CUDAParent.cmake @@ -0,0 +1,14 @@ + +# With CMP0220 NEW, CUDA enabled in a subdirectory propagates up to this scope. +cmake_policy(SET CMP0220 NEW) + +enable_language(CXX) + +add_subdirectory(cuda) + +add_executable(main cuda-main.cxx) +target_link_libraries(main PRIVATE cuda_lib) + +if(APPLE) + set_property(TARGET main PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES}) +endif() diff --git a/Tests/RunCMake/EnableLanguageSubdir/CUDASibling.cmake b/Tests/RunCMake/EnableLanguageSubdir/CUDASibling.cmake new file mode 100644 index 0000000000..ae2395dc69 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CUDASibling.cmake @@ -0,0 +1,9 @@ + +# With CMP0220 NEW, CUDA enabled in a subdirectory propagates up to this scope. +cmake_policy(SET CMP0220 NEW) + +enable_language(CXX) + +add_subdirectory(cuda) + +add_subdirectory(sibling) diff --git a/Tests/RunCMake/EnableLanguageSubdir/CXXImplicitRC.cmake b/Tests/RunCMake/EnableLanguageSubdir/CXXImplicitRC.cmake new file mode 100644 index 0000000000..34484fd5d9 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/CXXImplicitRC.cmake @@ -0,0 +1,7 @@ + +# With CMP0220 NEW, CXX and its implicit RC language propagate up to this scope. +cmake_policy(SET CMP0220 NEW) + +add_subdirectory(cxx) + +add_executable(main rc-main.cxx resource.rc) diff --git a/Tests/RunCMake/EnableLanguageSubdir/RunCMakeTest.cmake b/Tests/RunCMake/EnableLanguageSubdir/RunCMakeTest.cmake new file mode 100644 index 0000000000..3099bdfe0e --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/RunCMakeTest.cmake @@ -0,0 +1,48 @@ +include(RunCMake) + +function(configure_and_build case) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build) + run_cmake(${case}) + set(RunCMake_TEST_NO_CLEAN 1) + set(RunCMake_TEST_OUTPUT_MERGE 1) + run_cmake_command(${case}-build ${CMAKE_COMMAND} --build . --target main) + unset(RunCMake_TEST_NO_CLEAN) + unset(RunCMake_TEST_OUTPUT_MERGE) + unset(RunCMake_TEST_BINARY_DIR) +endfunction() + +# CMP0220 policy behavior. These cases are language-agnostic (they use CXX) +# and configure-only, so they run on every platform regardless of CMake_TEST_CUDA. + +# NEW: language configuration propagates up out of the enabling scope. +run_cmake(CMP0220-NEW) +run_cmake(CMP0220-NEW-function) +run_cmake(CMP0220-NEW-multilevel) + +# OLD: language configuration stays in the scope that enabled it. +run_cmake(CMP0220-OLD) + +# MIXED: NEW and OLD set at different scope levels around the enabling scope. +run_cmake(CMP0220-MIXED-block) +run_cmake(CMP0220-MIXED-nested-block) + +# WARN: whether the optional CMP0220 warning is emitted. +run_cmake(CMP0220-WARN) +run_cmake(CMP0220-WARN-default) +run_cmake(CMP0220-WARN-block) +run_cmake(CMP0220-WARN-top-level) + +# These cases enable CXX in a subdirectory and verify that its implicit RC +# language state is usable from an ancestor scope. +if(CMake_TEST_RESOURCES) + configure_and_build(CXXImplicitRC) + run_cmake(CMP0220-MIXED-implicit-RC) +endif() + +# The following cases enable CUDA in a subdirectory and build a target in an +# ancestor scope, so they require a working CUDA toolchain. +if(CMake_TEST_CUDA) + foreach(case IN ITEMS CUDAParent CUDASibling) + configure_and_build(${case}) + endforeach() +endif() diff --git a/Tests/RunCMake/EnableLanguageSubdir/cuda-main.cxx b/Tests/RunCMake/EnableLanguageSubdir/cuda-main.cxx new file mode 100644 index 0000000000..687acee11c --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/cuda-main.cxx @@ -0,0 +1,7 @@ +#include "src.h" + +int main() +{ + hello_world(); + return 0; +} diff --git a/Tests/RunCMake/EnableLanguageSubdir/cuda/CMakeLists.txt b/Tests/RunCMake/EnableLanguageSubdir/cuda/CMakeLists.txt new file mode 100644 index 0000000000..95540e89b3 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/cuda/CMakeLists.txt @@ -0,0 +1,4 @@ +enable_language(CUDA) + +add_library(cuda_lib STATIC src.cu) +target_include_directories(cuda_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/Tests/RunCMake/EnableLanguageSubdir/cuda/src.cu b/Tests/RunCMake/EnableLanguageSubdir/cuda/src.cu new file mode 100644 index 0000000000..5cb7dd0949 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/cuda/src.cu @@ -0,0 +1,14 @@ +#include + +#include "src.h" + +__global__ void hello_world_kernel() +{ + printf("Hello from GPU thread %d\n", threadIdx.x); +} + +void hello_world() +{ + hello_world_kernel<<<1, 1>>>(); + cudaDeviceSynchronize(); +} diff --git a/Tests/RunCMake/EnableLanguageSubdir/cuda/src.h b/Tests/RunCMake/EnableLanguageSubdir/cuda/src.h new file mode 100644 index 0000000000..a20c23b0e9 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/cuda/src.h @@ -0,0 +1,2 @@ +#pragma once +void hello_world(); diff --git a/Tests/RunCMake/EnableLanguageSubdir/cxx/CMakeLists.txt b/Tests/RunCMake/EnableLanguageSubdir/cxx/CMakeLists.txt new file mode 100644 index 0000000000..fa2fc914f9 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/cxx/CMakeLists.txt @@ -0,0 +1 @@ +enable_language(CXX) diff --git a/Tests/RunCMake/EnableLanguageSubdir/outer/CMakeLists.txt b/Tests/RunCMake/EnableLanguageSubdir/outer/CMakeLists.txt new file mode 100644 index 0000000000..9aa0a87193 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/outer/CMakeLists.txt @@ -0,0 +1,5 @@ +block() + block() + add_subdirectory(inner) + endblock() +endblock() diff --git a/Tests/RunCMake/EnableLanguageSubdir/outer/inner/CMakeLists.txt b/Tests/RunCMake/EnableLanguageSubdir/outer/inner/CMakeLists.txt new file mode 100644 index 0000000000..e8e7327619 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/outer/inner/CMakeLists.txt @@ -0,0 +1,3 @@ +block() + enable_language(CXX) +endblock() diff --git a/Tests/RunCMake/EnableLanguageSubdir/rc-main.cxx b/Tests/RunCMake/EnableLanguageSubdir/rc-main.cxx new file mode 100644 index 0000000000..f8b643afbf --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/rc-main.cxx @@ -0,0 +1,4 @@ +int main() +{ + return 0; +} diff --git a/Tests/RunCMake/EnableLanguageSubdir/resource.rc b/Tests/RunCMake/EnableLanguageSubdir/resource.rc new file mode 100644 index 0000000000..c3d3d7e9a8 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/resource.rc @@ -0,0 +1,4 @@ +1 RCDATA +BEGIN + "EnableLanguageSubdir" +END diff --git a/Tests/RunCMake/EnableLanguageSubdir/sibling/CMakeLists.txt b/Tests/RunCMake/EnableLanguageSubdir/sibling/CMakeLists.txt new file mode 100644 index 0000000000..ef0500d191 --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/sibling/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(main main.cxx) +target_link_libraries(main PRIVATE cuda_lib) + +if(APPLE) + set_property(TARGET main PROPERTY BUILD_RPATH ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES}) +endif() diff --git a/Tests/RunCMake/EnableLanguageSubdir/sibling/main.cxx b/Tests/RunCMake/EnableLanguageSubdir/sibling/main.cxx new file mode 100644 index 0000000000..687acee11c --- /dev/null +++ b/Tests/RunCMake/EnableLanguageSubdir/sibling/main.cxx @@ -0,0 +1,7 @@ +#include "src.h" + +int main() +{ + hello_world(); + return 0; +}