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/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); } 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; +}