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.
This commit is contained in:
Stephen Kelly 2014-05-15 11:32:30 +02:00
parent aa8a6fcee8
commit 0dfe395e3c
26 changed files with 311 additions and 0 deletions

View File

@ -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
=========================

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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;

View File

@ -1313,6 +1313,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> &parameters,
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
@ -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;

View File

@ -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.

View File

@ -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);
}

View File

@ -5186,6 +5186,28 @@ HaveCFeatureAvailable(cmTarget const* target, const std::string& feature) const
return true;
}
//----------------------------------------------------------------------------
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

View File

@ -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);

View File

@ -1229,6 +1229,7 @@ void cmTarget::GetDirectLinkLibraries(const std::string& config,
this->LinkImplicitNullProperties.insert(*it);
}
}
cge->GetMaxLanguageStandard(this, this->MaxLanguageStandards);
}
}

View File

@ -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.

View File

@ -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>)

View File

@ -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()
{
}

View File

@ -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.

View File

@ -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
)

View File

@ -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)

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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()

View File

@ -0,0 +1 @@
1

View File

@ -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\)

View File

@ -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")

View File

@ -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)