diff options
author | Stephen Kelly <steveire@gmail.com> | 2014-05-15 09:32:30 (GMT) |
---|---|---|
committer | Stephen Kelly <steveire@gmail.com> | 2014-05-21 15:22:32 (GMT) |
commit | 0dfe395e3cb1a720c4853087db554a6827feaadb (patch) | |
tree | 83d9cbe0ee9843bc4aa0012d9a7064a3522ba018 | |
parent | aa8a6fcee8c67b0516efcd745fb1d7a66d249096 (diff) | |
download | CMake-0dfe395e3cb1a720c4853087db554a6827feaadb.zip CMake-0dfe395e3cb1a720c4853087db554a6827feaadb.tar.gz CMake-0dfe395e3cb1a720c4853087db554a6827feaadb.tar.bz2 |
Features: Add COMPILE_FEATURES generator expression.
Allow setting build properties based on the features available
for a target. The availability of features is determined at
generate-time by evaluating the link implementation.
Ensure that the <LANG>_STANDARD determined while evaluating
COMPILE_FEATURES in the link implementation is not lower than that
provided by the INTERFACE of the link implementation. This is
similar to handling of transitive properties such as
POSITION_INDEPENDENT_CODE.
26 files changed, 311 insertions, 0 deletions
diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst index dfda8dc..9184580 100644 --- a/Help/manual/cmake-generator-expressions.7.rst +++ b/Help/manual/cmake-generator-expressions.7.rst @@ -83,6 +83,12 @@ otherwise expands to nothing. else ``0``. If the policy was not set, the warning message for the policy will be emitted. This generator expression only works for a subset of policies. +``$<COMPILE_FEATURES:feature[,feature]...>`` + ``1`` if all of the ``feature`` features are available for the 'head' + target, and ``0`` otherwise. If this expression is used while evaluating + the link implementation of a target and if any dependency transitively + increases the required :prop_tgt:`C_STANDARD` or :prop_tgt:`CXX_STANDARD` + for the 'head' target, an error is reported. Informational Expressions ========================= diff --git a/Help/release/dev/compile-language-features.rst b/Help/release/dev/compile-language-features.rst index fe72e39..d10e22b 100644 --- a/Help/release/dev/compile-language-features.rst +++ b/Help/release/dev/compile-language-features.rst @@ -22,3 +22,7 @@ target-language-features * New :command:`target_compile_features` command allows populating the :prop_tgt:`COMPILE_FEATURES` target property, just like any other build variable. + +* New ``COMPILE_FEATURES`` + :manual:`generator expression <cmake-generator-expressions(7)>` allows + setting build properties based on available compiler features. diff --git a/Source/cmGeneratorExpression.cxx b/Source/cmGeneratorExpression.cxx index d09e950..d53bdd7 100644 --- a/Source/cmGeneratorExpression.cxx +++ b/Source/cmGeneratorExpression.cxx @@ -110,6 +110,9 @@ const char *cmCompiledGeneratorExpression::Evaluate( break; } } + + this->MaxLanguageStandard = context.MaxLanguageStandard; + if (!context.HadError) { this->HadContextSensitiveCondition = context.HadContextSensitiveCondition; @@ -465,3 +468,17 @@ bool cmGeneratorExpression::IsValidTargetName(const std::string &input) return targetNameValidator.find(input.c_str()); } + +//---------------------------------------------------------------------------- +void +cmCompiledGeneratorExpression::GetMaxLanguageStandard(cmTarget const* tgt, + std::map<std::string, std::string>& mapping) +{ + typedef std::map<cmTarget const*, + std::map<std::string, std::string> > MapType; + MapType::const_iterator it = this->MaxLanguageStandard.find(tgt); + if (it != this->MaxLanguageStandard.end()) + { + mapping = it->second; + } +} diff --git a/Source/cmGeneratorExpression.h b/Source/cmGeneratorExpression.h index da64515..ef5360e 100644 --- a/Source/cmGeneratorExpression.h +++ b/Source/cmGeneratorExpression.h @@ -117,6 +117,9 @@ public: this->EvaluateForBuildsystem = eval; } + void GetMaxLanguageStandard(cmTarget const* tgt, + std::map<std::string, std::string>& mapping); + private: cmCompiledGeneratorExpression(cmListFileBacktrace const& backtrace, const std::string& input); @@ -134,6 +137,8 @@ private: mutable std::set<cmTarget*> DependTargets; mutable std::set<cmTarget const*> AllTargetsSeen; mutable std::set<std::string> SeenTargetProperties; + mutable std::map<cmTarget const*, std::map<std::string, std::string> > + MaxLanguageStandard; mutable std::string Output; mutable bool HadContextSensitiveCondition; bool EvaluateForBuildsystem; diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx index a513921..0b357f6 100644 --- a/Source/cmGeneratorExpressionEvaluator.cxx +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -1314,6 +1314,94 @@ static const struct TargetObjectsNode : public cmGeneratorExpressionNode } targetObjectsNode; //---------------------------------------------------------------------------- +static const struct CompileFeaturesNode : public cmGeneratorExpressionNode +{ + CompileFeaturesNode() {} + + virtual int NumExpectedParameters() const { return OneOrMoreParameters; } + + std::string Evaluate(const std::vector<std::string> ¶meters, + cmGeneratorExpressionContext *context, + const GeneratorExpressionContent *content, + cmGeneratorExpressionDAGChecker *dagChecker) const + { + cmTarget const* target = context->HeadTarget; + if (!target) + { + reportError(context, content->GetOriginalExpression(), + "$<COMPILE_FEATURE> may only be used with binary targets. It may " + "not be used with add_custom_command or add_custom_target."); + return std::string(); + } + + typedef std::map<std::string, std::vector<std::string> > LangMap; + static LangMap availableFeatures; + + LangMap testedFeatures; + + for (std::vector<std::string>::const_iterator it = parameters.begin(); + it != parameters.end(); ++it) + { + std::string error; + std::string lang; + if (!context->Makefile->CompileFeatureKnown(context->HeadTarget, + *it, lang, &error)) + { + reportError(context, content->GetOriginalExpression(), error); + return std::string(); + } + testedFeatures[lang].push_back(*it); + + if (availableFeatures.find(lang) == availableFeatures.end()) + { + const char* featuresKnown + = context->Makefile->CompileFeaturesAvailable(lang, &error); + if (!featuresKnown) + { + reportError(context, content->GetOriginalExpression(), error); + return std::string(); + } + cmSystemTools::ExpandListArgument(featuresKnown, + availableFeatures[lang]); + } + } + + bool evalLL = dagChecker && dagChecker->EvaluatingLinkLibraries(); + + std::string result; + + for (LangMap::const_iterator lit = testedFeatures.begin(); + lit != testedFeatures.end(); ++lit) + { + for (std::vector<std::string>::const_iterator it = lit->second.begin(); + it != lit->second.end(); ++it) + { + if (!context->Makefile->HaveFeatureAvailable(target, + lit->first, *it)) + { + if (evalLL) + { + const char* l = target->GetProperty(lit->first + "_STANDARD"); + if (!l) + { + l = context->Makefile + ->GetDefinition("CMAKE_" + lit->first + "_STANDARD_DEFAULT"); + } + assert(l); + context->MaxLanguageStandard[target][lit->first] = l; + } + else + { + return "0"; + } + } + } + } + return "1"; + } +} compileFeaturesNode; + +//---------------------------------------------------------------------------- static const char* targetPolicyWhitelist[] = { 0 #define TARGET_POLICY_STRING(POLICY) \ @@ -1647,6 +1735,7 @@ cmGeneratorExpressionNode* GetNode(const std::string &identifier) nodeMap["C_COMPILER_VERSION"] = &cCompilerVersionNode; nodeMap["CXX_COMPILER_VERSION"] = &cxxCompilerVersionNode; nodeMap["PLATFORM_ID"] = &platformIdNode; + nodeMap["COMPILE_FEATURES"] = &compileFeaturesNode; nodeMap["CONFIGURATION"] = &configurationNode; nodeMap["CONFIG"] = &configurationTestNode; nodeMap["TARGET_FILE"] = &targetFileNode; diff --git a/Source/cmGeneratorExpressionEvaluator.h b/Source/cmGeneratorExpressionEvaluator.h index 54a2548..eb76d7f 100644 --- a/Source/cmGeneratorExpressionEvaluator.h +++ b/Source/cmGeneratorExpressionEvaluator.h @@ -26,6 +26,8 @@ struct cmGeneratorExpressionContext std::set<cmTarget*> DependTargets; std::set<cmTarget const*> AllTargets; std::set<std::string> SeenTargetProperties; + std::map<cmTarget const*, std::map<std::string, std::string> > + MaxLanguageStandard; cmMakefile *Makefile; std::string Config; cmTarget const* HeadTarget; // The target whose property is being evaluated. diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index a6ad714..5d58265 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -1484,6 +1484,31 @@ void cmLocalGenerator::AddCompileOptions( return; } } + + for(std::map<std::string, std::string>::const_iterator it + = target->GetMaxLanguageStandards().begin(); + it != target->GetMaxLanguageStandards().end(); ++it) + { + const char* standard = target->GetProperty(it->first + "_STANDARD"); + if(!standard) + { + continue; + } + if (this->Makefile->IsLaterStandard(it->first, standard, it->second)) + { + cmOStringStream e; + e << "The COMPILE_FEATURES property of target \"" + << target->GetName() << "\" was evaluated when computing the link " + "implementation, and the \"" << it->first << "_STANDARD\" was \"" + << it->second << "\" for that computation. Computing the " + "COMPILE_FEATURES based on the link implementation resulted in a " + "higher \"" << it->first << "_STANDARD\" \"" << standard << "\". " + "This is not permitted. The COMPILE_FEATURES may not both depend on " + "and be depended on by the link implementation." << std::endl; + this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str()); + return; + } + } this->AddCompilerRequirementFlag(flags, target, lang); } diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 9d7b3c6..9f33b92 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -5187,6 +5187,28 @@ HaveCFeatureAvailable(cmTarget const* target, const std::string& feature) const } //---------------------------------------------------------------------------- +bool cmMakefile::IsLaterStandard(std::string const& lang, + std::string const& lhs, + std::string const& rhs) +{ + if (lang == "C") + { + const char * const *rhsIt = std::find_if(cmArrayBegin(C_STANDARDS), + cmArrayEnd(C_STANDARDS), + cmStrCmp(rhs)); + + return std::find_if(rhsIt, cmArrayEnd(C_STANDARDS), + cmStrCmp(lhs)) != cmArrayEnd(C_STANDARDS); + } + const char * const *rhsIt = std::find_if(cmArrayBegin(CXX_STANDARDS), + cmArrayEnd(CXX_STANDARDS), + cmStrCmp(rhs)); + + return std::find_if(rhsIt, cmArrayEnd(CXX_STANDARDS), + cmStrCmp(lhs)) != cmArrayEnd(CXX_STANDARDS); +} + +//---------------------------------------------------------------------------- bool cmMakefile::HaveCxxFeatureAvailable(cmTarget const* target, const std::string& feature) const { diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index b2c3c4d..11904a6 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -898,6 +898,10 @@ public: bool HaveFeatureAvailable(cmTarget const* target, std::string const& lang, const std::string& feature) const; + bool IsLaterStandard(std::string const& lang, + std::string const& lhs, + std::string const& rhs); + void ClearMatches(); void StoreMatches(cmsys::RegularExpression& re); diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 86842a4..63db20f 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -1229,6 +1229,7 @@ void cmTarget::GetDirectLinkLibraries(const std::string& config, this->LinkImplicitNullProperties.insert(*it); } } + cge->GetMaxLanguageStandard(this, this->MaxLanguageStandards); } } diff --git a/Source/cmTarget.h b/Source/cmTarget.h index bee6b34..45d1bd6 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -587,6 +587,12 @@ public: const std::string &report, const std::string &compatibilityType) const; + std::map<std::string, std::string> const& + GetMaxLanguageStandards() const + { + return this->MaxLanguageStandards; + } + private: bool HandleLocationPropertyPolicy(cmMakefile* context) const; @@ -718,6 +724,7 @@ private: mutable bool DebugSourcesDone; mutable bool DebugCompileFeaturesDone; mutable std::set<std::string> LinkImplicitNullProperties; + mutable std::map<std::string, std::string> MaxLanguageStandards; bool BuildInterfaceIncludesAppended; // Cache target output paths for each configuration. diff --git a/Tests/CompileFeatures/CMakeLists.txt b/Tests/CompileFeatures/CMakeLists.txt index 0e1e6c9..7a8a975 100644 --- a/Tests/CompileFeatures/CMakeLists.txt +++ b/Tests/CompileFeatures/CMakeLists.txt @@ -83,3 +83,17 @@ set_property(TARGET iface ) add_executable(IfaceCompileFeatures main.cpp) target_link_libraries(IfaceCompileFeatures iface) + +add_executable(CompileFeaturesGenex genex_test.cpp) +set_property(TARGET CompileFeaturesGenex PROPERTY CXX_STANDARD 11) +target_compile_definitions(CompileFeaturesGenex PRIVATE HAVE_OVERRIDE_CONTROL=$<COMPILE_FEATURES:cxx_final,cxx_override>) + +add_executable(CompileFeaturesGenex2 genex_test.cpp) +target_compile_features(CompileFeaturesGenex2 PRIVATE cxx_constexpr) +target_compile_definitions(CompileFeaturesGenex2 PRIVATE HAVE_OVERRIDE_CONTROL=$<COMPILE_FEATURES:cxx_final,cxx_override>) + +add_library(noexcept_iface INTERFACE) +target_compile_features(noexcept_iface INTERFACE cxx_noexcept) +add_executable(CompileFeaturesGenex3 genex_test.cpp) +target_link_libraries(CompileFeaturesGenex3 PRIVATE noexcept_iface) +target_compile_definitions(CompileFeaturesGenex3 PRIVATE HAVE_OVERRIDE_CONTROL=$<COMPILE_FEATURES:cxx_final,cxx_override>) diff --git a/Tests/CompileFeatures/genex_test.cpp b/Tests/CompileFeatures/genex_test.cpp new file mode 100644 index 0000000..ca38883 --- /dev/null +++ b/Tests/CompileFeatures/genex_test.cpp @@ -0,0 +1,21 @@ + +#if !HAVE_OVERRIDE_CONTROL +#error "Expect override control feature" +#else + +struct A +{ + virtual int getA() { return 7; } +}; + +struct B final : A +{ + int getA() override { return 42; } +}; + +#endif + +int main() +{ + +} diff --git a/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle-result.txt b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle-stderr.txt b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle-stderr.txt new file mode 100644 index 0000000..a584d7d --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle-stderr.txt @@ -0,0 +1,7 @@ +CMake Error in CMakeLists.txt: + The COMPILE_FEATURES property of target "empty1" was evaluated when + computing the link implementation, and the "CXX_STANDARD" was "98" for that + computation. Computing the COMPILE_FEATURES based on the link + implementation resulted in a higher "CXX_STANDARD" "11". This is not + permitted. The COMPILE_FEATURES may not both depend on and be depended on + by the link implementation. diff --git a/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle.cmake b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle.cmake new file mode 100644 index 0000000..9d56bc0 --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle.cmake @@ -0,0 +1,15 @@ + +add_library(empty1 empty.cpp) + +add_library(empty2 INTERFACE) +add_library(empty3 INTERFACE) +target_compile_features(empty3 INTERFACE cxx_constexpr) + +target_link_libraries(empty1 + # When starting, $<COMPILE_FEATURES:cxx_final> is '0', so 'freeze' the + # CXX_STANDARD at 98 during computation. + $<$<COMPILE_FEATURES:cxx_final>:empty2> + # This would add cxx_constexpr, but that would require CXX_STANDARD = 11, + # which is not allowed after freeze. Report an error. + empty3 +) diff --git a/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-result.txt b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-result.txt new file mode 100644 index 0000000..573541a --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-result.txt @@ -0,0 +1 @@ +0 diff --git a/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-stderr.txt b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-stderr.txt new file mode 100644 index 0000000..10f3293 --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-stderr.txt @@ -0,0 +1 @@ +^$ diff --git a/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved.cmake b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved.cmake new file mode 100644 index 0000000..0df548b --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved.cmake @@ -0,0 +1,14 @@ + +add_library(empty1 empty.cpp) + +add_library(empty2 INTERFACE) +add_library(empty3 INTERFACE) +target_compile_features(empty3 INTERFACE cxx_constexpr) + +target_link_libraries(empty1 + $<$<COMPILE_FEATURES:cxx_final>:empty2> + empty3 +) +# This, or populating the COMPILE_FEATURES property with a feature in the +# same standard as cxx_final, solves the cycle above. +set_property(TARGET empty1 PROPERTY CXX_STANDARD 11) diff --git a/Tests/RunCMake/CompileFeatures/NonValidTarget1-result.txt b/Tests/RunCMake/CompileFeatures/NonValidTarget1-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/NonValidTarget1-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CompileFeatures/NonValidTarget1-stderr.txt b/Tests/RunCMake/CompileFeatures/NonValidTarget1-stderr.txt new file mode 100644 index 0000000..7f3b43b --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/NonValidTarget1-stderr.txt @@ -0,0 +1,9 @@ +CMake Error at NonValidTarget1.cmake:[0-9]+ \(add_custom_command\): + Error evaluating generator expression: + + \$<COMPILE_FEATURES:cxx_final> + + \$<COMPILE_FEATURE> may only be used with binary targets. It may not be + used with add_custom_command or add_custom_target. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/CompileFeatures/NonValidTarget1.cmake b/Tests/RunCMake/CompileFeatures/NonValidTarget1.cmake new file mode 100644 index 0000000..c6707c1 --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/NonValidTarget1.cmake @@ -0,0 +1,17 @@ + +set(genexvar $<COMPILE_FEATURES:cxx_final>) + +if (HAVE_FINAL) + set(expected_result 1) +else() + set(expected_result 0) +endif() + +add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/copied_file${HAVE_FINAL}.cpp" + COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/empty.cpp" "${CMAKE_CURRENT_BINARY_DIR}/copied_file${genexvar}.cpp" +) + +add_library(empty "${CMAKE_CURRENT_BINARY_DIR}/copied_file${genexvar}.cpp") +if (HAVE_FINAL) + target_compile_features(empty PRIVATE cxx_final) +endif() diff --git a/Tests/RunCMake/CompileFeatures/NonValidTarget2-result.txt b/Tests/RunCMake/CompileFeatures/NonValidTarget2-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/NonValidTarget2-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CompileFeatures/NonValidTarget2-stderr.txt b/Tests/RunCMake/CompileFeatures/NonValidTarget2-stderr.txt new file mode 100644 index 0000000..635150c --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/NonValidTarget2-stderr.txt @@ -0,0 +1,9 @@ +CMake Error at NonValidTarget2.cmake:4 \(add_custom_target\): + Error evaluating generator expression: + + \$<COMPILE_FEATURES:cxx_final> + + \$<COMPILE_FEATURE> may only be used with binary targets. It may not be + used with add_custom_command or add_custom_target. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/CompileFeatures/NonValidTarget2.cmake b/Tests/RunCMake/CompileFeatures/NonValidTarget2.cmake new file mode 100644 index 0000000..eb84692 --- /dev/null +++ b/Tests/RunCMake/CompileFeatures/NonValidTarget2.cmake @@ -0,0 +1,8 @@ + +set(genexvar $<COMPILE_FEATURES:cxx_final>) + +add_custom_target(copy_target + COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/empty.cpp" "${CMAKE_CURRENT_BINARY_DIR}/copied_file${genexvar}.txt" +) + +add_library(empty "${CMAKE_CURRENT_BINARY_DIR}/copied_file${genexvar}.cpp") diff --git a/Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake b/Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake index a23d44f..1892a5c 100644 --- a/Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake +++ b/Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake @@ -26,6 +26,16 @@ endif() if (NOT CXX_FEATURES) run_cmake(NoSupportedCxxFeatures) run_cmake(NoSupportedCxxFeaturesGenex) +else() + run_cmake(LinkImplementationFeatureCycle) + run_cmake(LinkImplementationFeatureCycleSolved) + + if (";${CXX_FEATURES};" MATCHES ";cxx_final;") + set(RunCMake_TEST_OPTIONS "-DHAVE_FINAL=1") + endif() + run_cmake(NonValidTarget1) + run_cmake(NonValidTarget2) + unset(RunCMake_TEST_OPTIONS) endif() foreach(standard 98 11) |