From dc94b0249e1f4057c153bbd6deeea13ad85987e6 Mon Sep 17 00:00:00 2001 From: Robert Maynard Date: Mon, 22 Jun 2020 17:11:56 -0400 Subject: cmStandardLevelResolver: Added to handle standard level queries Refactored out of cmMakefile --- Source/CMakeLists.txt | 2 + Source/cmGeneratorExpressionNode.cxx | 11 +- Source/cmGeneratorTarget.cxx | 8 +- Source/cmLocalGenerator.cxx | 4 +- Source/cmMakefile.cxx | 781 ------------------------------ Source/cmMakefile.h | 86 ---- Source/cmStandardLevelResolver.cxx | 779 +++++++++++++++++++++++++++++ Source/cmStandardLevelResolver.h | 111 +++++ Source/cmTargetCompileFeaturesCommand.cxx | 4 +- bootstrap | 1 + 10 files changed, 910 insertions(+), 877 deletions(-) create mode 100644 Source/cmStandardLevelResolver.cxx create mode 100644 Source/cmStandardLevelResolver.h diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 1b6bb00..ee8767f 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -412,6 +412,8 @@ set(SRCS cmSourceFileLocationKind.h cmSourceGroup.cxx cmSourceGroup.h + cmStandardLevelResolver.cxx + cmStandardLevelResolver.h cmState.cxx cmState.h cmStateDirectory.cxx diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index b712b71..4adc6a2 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -37,6 +37,7 @@ #include "cmPolicies.h" #include "cmProperty.h" #include "cmRange.h" +#include "cmStandardLevelResolver.h" #include "cmState.h" #include "cmStateSnapshot.h" #include "cmStateTypes.h" @@ -1707,11 +1708,11 @@ static const struct CompileFeaturesNode : public cmGeneratorExpressionNode static LangMap availableFeatures; LangMap testedFeatures; - + cmStandardLevelResolver standardResolver(context->LG->GetMakefile()); for (std::string const& p : parameters) { std::string error; std::string lang; - if (!context->LG->GetMakefile()->CompileFeatureKnown( + if (!standardResolver.CompileFeatureKnown( context->HeadTarget->Target->GetName(), p, lang, &error)) { reportError(context, content->GetOriginalExpression(), error); return std::string(); @@ -1720,7 +1721,7 @@ static const struct CompileFeaturesNode : public cmGeneratorExpressionNode if (availableFeatures.find(lang) == availableFeatures.end()) { const char* featuresKnown = - context->LG->GetMakefile()->CompileFeaturesAvailable(lang, &error); + standardResolver.CompileFeaturesAvailable(lang, &error); if (!featuresKnown) { reportError(context, content->GetOriginalExpression(), error); return std::string(); @@ -1745,8 +1746,8 @@ static const struct CompileFeaturesNode : public cmGeneratorExpressionNode // All features known for the language are always available. continue; } - if (!context->LG->GetMakefile()->HaveStandardAvailable( - target, lit.first, context->Config, it)) { + if (!standardResolver.HaveStandardAvailable(target, lit.first, + context->Config, it)) { if (evalLL) { cmProp l = target->GetLanguageStandard(lit.first, context->Config); if (!l) { diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index 992682f..b290919 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -43,6 +43,7 @@ #include "cmSourceFile.h" #include "cmSourceFileLocation.h" #include "cmSourceFileLocationKind.h" +#include "cmStandardLevelResolver.h" #include "cmState.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" @@ -4464,11 +4465,12 @@ void cmGeneratorTarget::ComputeTargetManifest(const std::string& config) const bool cmGeneratorTarget::ComputeCompileFeatures(std::string const& config) const { // Compute the language standard based on the compile features. + cmStandardLevelResolver standardResolver(this->Makefile); std::vector> features = this->GetCompileFeatures(config); for (BT const& f : features) { std::string lang; - if (!this->Makefile->CompileFeatureKnown(this->Target->GetName(), f.Value, - lang, nullptr)) { + if (!standardResolver.CompileFeatureKnown(this->Target->GetName(), f.Value, + lang, nullptr)) { return false; } @@ -4476,7 +4478,7 @@ bool cmGeneratorTarget::ComputeCompileFeatures(std::string const& config) const cmProp currentLanguageStandard = this->GetLanguageStandard(lang, config); std::string newRequiredStandard; - if (!this->Makefile->GetNewRequiredStandard( + if (!standardResolver.GetNewRequiredStandard( this->Target->GetName(), f.Value, currentLanguageStandard, newRequiredStandard)) { return false; diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 42a5780..27c87f6 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -39,6 +39,7 @@ #include "cmSourceFile.h" #include "cmSourceFileLocation.h" #include "cmSourceFileLocationKind.h" +#include "cmStandardLevelResolver.h" #include "cmState.h" #include "cmStateDirectory.h" #include "cmStateTypes.h" @@ -994,12 +995,13 @@ void cmLocalGenerator::AddCompileOptions(std::vector>& flags, this->AppendCompileOptions(flags, targetCompileOpts); } + cmStandardLevelResolver standardResolver(this->Makefile); for (auto const& it : target->GetMaxLanguageStandards()) { cmProp standard = target->GetLanguageStandard(it.first, config); if (!standard) { continue; } - if (this->Makefile->IsLaterStandard(it.first, *standard, it.second)) { + if (standardResolver.IsLaterStandard(it.first, *standard, it.second)) { std::ostringstream e; e << "The COMPILE_FEATURES property of target \"" << target->GetName() << "\" was evaluated when computing the link " diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 70861ce..2866975 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -40,7 +40,6 @@ #include "cmGeneratedFileStream.h" #include "cmGeneratorExpression.h" #include "cmGeneratorExpressionEvaluationFile.h" -#include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmInstallGenerator.h" // IWYU pragma: keep #include "cmInstallSubdirectoryGenerator.h" @@ -4634,786 +4633,6 @@ bool cmMakefile::IgnoreErrorsCMP0061() const return ignoreErrors; } -#define FEATURE_STRING(F) , #F -static const char* const C_FEATURES[] = { nullptr FOR_EACH_C_FEATURE( - FEATURE_STRING) }; - -static const char* const CXX_FEATURES[] = { nullptr FOR_EACH_CXX_FEATURE( - FEATURE_STRING) }; - -static const char* const CUDA_FEATURES[] = { nullptr FOR_EACH_CUDA_FEATURE( - FEATURE_STRING) }; -#undef FEATURE_STRING - -static const char* const C_STANDARDS[] = { "90", "99", "11" }; -static const char* const CXX_STANDARDS[] = { "98", "11", "14", "17", "20" }; -static const char* const CUDA_STANDARDS[] = { "03", "11", "14", "17", "20" }; - -bool cmMakefile::AddRequiredTargetFeature(cmTarget* target, - const std::string& feature, - std::string* error) const -{ - if (cmGeneratorExpression::Find(feature) != std::string::npos) { - target->AppendProperty("COMPILE_FEATURES", feature); - return true; - } - - std::string lang; - if (!this->CheckCompileFeaturesAvailable(target->GetName(), feature, lang, - error)) { - return false; - } - - target->AppendProperty("COMPILE_FEATURES", feature); - - // FIXME: Add a policy to avoid updating the _STANDARD target - // property due to COMPILE_FEATURES. The language standard selection - // should be done purely at generate time based on whatever the project - // code put in these properties explicitly. That is mostly true now, - // but for compatibility we need to continue updating the property here. - if (lang == "C" || lang == "OBJC") { - return this->AddRequiredTargetCFeature(target, feature, lang, error); - } - if (lang == "CUDA") { - return this->AddRequiredTargetCudaFeature(target, feature, lang, error); - } - return this->AddRequiredTargetCxxFeature(target, feature, lang, error); -} - -bool cmMakefile::CheckCompileFeaturesAvailable(const std::string& targetName, - const std::string& feature, - std::string& lang, - std::string* error) const -{ - if (!this->CompileFeatureKnown(targetName, feature, lang, error)) { - return false; - } - - const char* features = this->CompileFeaturesAvailable(lang, error); - if (!features) { - return false; - } - - std::vector availableFeatures = cmExpandedList(features); - if (!cm::contains(availableFeatures, feature)) { - std::ostringstream e; - e << "The compiler feature \"" << feature << "\" is not known to " << lang - << " compiler\n\"" - << this->GetDefinition("CMAKE_" + lang + "_COMPILER_ID") - << "\"\nversion " - << this->GetDefinition("CMAKE_" + lang + "_COMPILER_VERSION") << "."; - if (error) { - *error = e.str(); - } else { - this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str(), - this->Backtrace); - } - return false; - } - - return true; -} - -bool cmMakefile::CompileFeatureKnown(const std::string& targetName, - const std::string& feature, - std::string& lang, - std::string* error) const -{ - assert(cmGeneratorExpression::Find(feature) == std::string::npos); - - bool isCFeature = - std::find_if(cm::cbegin(C_FEATURES) + 1, cm::cend(C_FEATURES), - cmStrCmp(feature)) != cm::cend(C_FEATURES); - if (isCFeature) { - lang = "C"; - return true; - } - bool isCxxFeature = - std::find_if(cm::cbegin(CXX_FEATURES) + 1, cm::cend(CXX_FEATURES), - cmStrCmp(feature)) != cm::cend(CXX_FEATURES); - if (isCxxFeature) { - lang = "CXX"; - return true; - } - bool isCudaFeature = - std::find_if(cm::cbegin(CUDA_FEATURES) + 1, cm::cend(CUDA_FEATURES), - cmStrCmp(feature)) != cm::cend(CUDA_FEATURES); - if (isCudaFeature) { - lang = "CUDA"; - return true; - } - std::ostringstream e; - if (error) { - e << "specified"; - } else { - e << "Specified"; - } - e << " unknown feature \"" << feature - << "\" for " - "target \"" - << targetName << "\"."; - if (error) { - *error = e.str(); - } else { - this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str(), - this->Backtrace); - } - return false; -} - -const char* cmMakefile::CompileFeaturesAvailable(const std::string& lang, - std::string* error) const -{ - if (!this->GlobalGenerator->GetLanguageEnabled(lang)) { - std::ostringstream e; - if (error) { - e << "cannot"; - } else { - e << "Cannot"; - } - e << " use features from non-enabled language " << lang; - if (error) { - *error = e.str(); - } else { - this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str(), - this->Backtrace); - } - return nullptr; - } - - const char* featuresKnown = - this->GetDefinition("CMAKE_" + lang + "_COMPILE_FEATURES"); - - if (!featuresKnown || !*featuresKnown) { - std::ostringstream e; - if (error) { - e << "no"; - } else { - e << "No"; - } - e << " known features for " << lang << " compiler\n\"" - << this->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID") - << "\"\nversion " - << this->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_VERSION") << "."; - if (error) { - *error = e.str(); - } else { - this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e.str(), - this->Backtrace); - } - return nullptr; - } - return featuresKnown; -} - -bool cmMakefile::GetNewRequiredStandard(const std::string& targetName, - const std::string& feature, - cmProp currentLangStandardValue, - std::string& newRequiredStandard, - std::string* error) const -{ - std::string lang; - if (!this->CheckCompileFeaturesAvailable(targetName, feature, lang, error)) { - return false; - } - - if (lang == "C" || lang == "OBJC") { - return this->GetNewRequiredCStandard(targetName, feature, lang, - currentLangStandardValue, - newRequiredStandard, error); - } - if (lang == "CUDA") { - return this->GetNewRequiredCudaStandard(targetName, feature, lang, - currentLangStandardValue, - newRequiredStandard, error); - } - return this->GetNewRequiredCxxStandard(targetName, feature, lang, - currentLangStandardValue, - newRequiredStandard, error); -} - -bool cmMakefile::HaveStandardAvailable(cmGeneratorTarget const* target, - std::string const& lang, - std::string const& config, - const std::string& feature) const -{ - if (lang == "C" || lang == "OBJC") { - return this->HaveCStandardAvailable(target, lang, config, feature); - } - if (lang == "CUDA") { - return this->HaveCudaStandardAvailable(target, lang, config, feature); - } - return this->HaveCxxStandardAvailable(target, lang, config, feature); -} - -bool cmMakefile::HaveCStandardAvailable(cmGeneratorTarget const* target, - std::string const& lang, - std::string const& config, - const std::string& feature) const -{ - cmProp defaultCStandard = - this->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); - if (!defaultCStandard) { - this->IssueMessage( - MessageType::INTERNAL_ERROR, - cmStrCat("CMAKE_", lang, - "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support " - "not fully configured for this compiler.")); - // Return true so the caller does not try to lookup the default standard. - return true; - } - if (std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), - cmStrCmp(*defaultCStandard)) == cm::cend(C_STANDARDS)) { - const std::string e = cmStrCat("The CMAKE_", lang, - "_STANDARD_DEFAULT variable contains an " - "invalid value: \"", - *defaultCStandard, "\"."); - this->IssueMessage(MessageType::INTERNAL_ERROR, e); - return false; - } - - bool needC90 = false; - bool needC99 = false; - bool needC11 = false; - - this->CheckNeededCLanguage(feature, lang, needC90, needC99, needC11); - - cmProp existingCStandard = target->GetLanguageStandard(lang, config); - if (!existingCStandard) { - existingCStandard = defaultCStandard; - } - - if (std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), - cmStrCmp(*existingCStandard)) == cm::cend(C_STANDARDS)) { - const std::string e = cmStrCat( - "The ", lang, "_STANDARD property on target \"", target->GetName(), - "\" contained an invalid value: \"", *existingCStandard, "\"."); - this->IssueMessage(MessageType::FATAL_ERROR, e); - return false; - } - - const char* const* existingCIt = existingCStandard - ? std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), - cmStrCmp(*existingCStandard)) - : cm::cend(C_STANDARDS); - - if (needC11 && existingCStandard && - existingCIt < std::find_if(cm::cbegin(C_STANDARDS), - cm::cend(C_STANDARDS), cmStrCmp("11"))) { - return false; - } - if (needC99 && existingCStandard && - existingCIt < std::find_if(cm::cbegin(C_STANDARDS), - cm::cend(C_STANDARDS), cmStrCmp("99"))) { - return false; - } - if (needC90 && existingCStandard && - existingCIt < std::find_if(cm::cbegin(C_STANDARDS), - cm::cend(C_STANDARDS), cmStrCmp("90"))) { - return false; - } - return true; -} - -bool cmMakefile::IsLaterStandard(std::string const& lang, - std::string const& lhs, - std::string const& rhs) -{ - if (lang == "C" || lang == "OBJC") { - const char* const* rhsIt = std::find_if( - cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), cmStrCmp(rhs)); - - return std::find_if(rhsIt, cm::cend(C_STANDARDS), cmStrCmp(lhs)) != - cm::cend(C_STANDARDS); - } - if (lang == "CUDA") { - const char* const* rhsIt = std::find_if( - cm::cbegin(CUDA_STANDARDS), cm::cend(CUDA_STANDARDS), cmStrCmp(rhs)); - - return std::find_if(rhsIt, cm::cend(CUDA_STANDARDS), cmStrCmp(lhs)) != - cm::cend(CUDA_STANDARDS); - } - - const char* const* rhsIt = std::find_if( - cm::cbegin(CXX_STANDARDS), cm::cend(CXX_STANDARDS), cmStrCmp(rhs)); - - return std::find_if(rhsIt, cm::cend(CXX_STANDARDS), cmStrCmp(lhs)) != - cm::cend(CXX_STANDARDS); -} - -bool cmMakefile::HaveCxxStandardAvailable(cmGeneratorTarget const* target, - std::string const& lang, - std::string const& config, - const std::string& feature) const -{ - cmProp defaultCxxStandard = - this->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); - if (!defaultCxxStandard) { - this->IssueMessage( - MessageType::INTERNAL_ERROR, - cmStrCat("CMAKE_", lang, - "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support " - "not fully configured for this compiler.")); - // Return true so the caller does not try to lookup the default standard. - return true; - } - if (std::find_if(cm::cbegin(CXX_STANDARDS), cm::cend(CXX_STANDARDS), - cmStrCmp(*defaultCxxStandard)) == cm::cend(CXX_STANDARDS)) { - const std::string e = - cmStrCat("The CMAKE_", lang, "_STANDARD_DEFAULT variable contains an ", - "invalid value: \"", *defaultCxxStandard, "\"."); - this->IssueMessage(MessageType::INTERNAL_ERROR, e); - return false; - } - - bool needCxx98 = false; - bool needCxx11 = false; - bool needCxx14 = false; - bool needCxx17 = false; - bool needCxx20 = false; - this->CheckNeededCxxLanguage(feature, lang, needCxx98, needCxx11, needCxx14, - needCxx17, needCxx20); - - cmProp existingCxxStandard = target->GetLanguageStandard(lang, config); - if (!existingCxxStandard) { - existingCxxStandard = defaultCxxStandard; - } - - const char* const* existingCxxLevel = - std::find_if(cm::cbegin(CXX_STANDARDS), cm::cend(CXX_STANDARDS), - cmStrCmp(*existingCxxStandard)); - if (existingCxxLevel == cm::cend(CXX_STANDARDS)) { - const std::string e = cmStrCat( - "The ", lang, "_STANDARD property on target \"", target->GetName(), - "\" contained an invalid value: \"", *existingCxxStandard, "\"."); - this->IssueMessage(MessageType::FATAL_ERROR, e); - return false; - } - - /* clang-format off */ - const char* const* needCxxLevel = - needCxx20 ? &CXX_STANDARDS[4] - : needCxx17 ? &CXX_STANDARDS[3] - : needCxx14 ? &CXX_STANDARDS[2] - : needCxx11 ? &CXX_STANDARDS[1] - : needCxx98 ? &CXX_STANDARDS[0] - : nullptr; - /* clang-format on */ - - return !needCxxLevel || needCxxLevel <= existingCxxLevel; -} - -void cmMakefile::CheckNeededCxxLanguage(const std::string& feature, - std::string const& lang, - bool& needCxx98, bool& needCxx11, - bool& needCxx14, bool& needCxx17, - bool& needCxx20) const -{ - if (const char* propCxx98 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "98_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCxx98); - needCxx98 = cm::contains(props, feature); - } - if (const char* propCxx11 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "11_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCxx11); - needCxx11 = cm::contains(props, feature); - } - if (const char* propCxx14 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "14_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCxx14); - needCxx14 = cm::contains(props, feature); - } - if (const char* propCxx17 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "17_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCxx17); - needCxx17 = cm::contains(props, feature); - } - if (const char* propCxx20 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "20_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCxx20); - needCxx20 = cm::contains(props, feature); - } -} - -bool cmMakefile::AddRequiredTargetCxxFeature(cmTarget* target, - const std::string& feature, - std::string const& lang, - std::string* error) const -{ - std::string newRequiredStandard; - if (this->GetNewRequiredCxxStandard( - target->GetName(), feature, lang, - target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard, - error)) { - if (!newRequiredStandard.empty()) { - target->SetLanguageStandardProperty(lang, newRequiredStandard, feature); - } - return true; - } - - return false; -} - -bool cmMakefile::GetNewRequiredCxxStandard(const std::string& targetName, - const std::string& feature, - std::string const& lang, - cmProp currentLangStandardValue, - std::string& newRequiredStandard, - std::string* error) const -{ - newRequiredStandard.clear(); - - bool needCxx98 = false; - bool needCxx11 = false; - bool needCxx14 = false; - bool needCxx17 = false; - bool needCxx20 = false; - - this->CheckNeededCxxLanguage(feature, lang, needCxx98, needCxx11, needCxx14, - needCxx17, needCxx20); - - cmProp existingCxxStandard = currentLangStandardValue; - if (existingCxxStandard == nullptr) { - cmProp defaultCxxStandard = - this->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); - if (defaultCxxStandard && !defaultCxxStandard->empty()) { - existingCxxStandard = defaultCxxStandard; - } - } - const char* const* existingCxxLevel = nullptr; - if (existingCxxStandard) { - existingCxxLevel = - std::find_if(cm::cbegin(CXX_STANDARDS), cm::cend(CXX_STANDARDS), - cmStrCmp(*existingCxxStandard)); - if (existingCxxLevel == cm::cend(CXX_STANDARDS)) { - const std::string e = cmStrCat( - "The ", lang, "_STANDARD property on target \"", targetName, - "\" contained an invalid value: \"", *existingCxxStandard, "\"."); - if (error) { - *error = e; - } else { - this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e, - this->Backtrace); - } - return false; - } - } - - /* clang-format off */ - const char* const* needCxxLevel = - needCxx20 ? &CXX_STANDARDS[4] - : needCxx17 ? &CXX_STANDARDS[3] - : needCxx14 ? &CXX_STANDARDS[2] - : needCxx11 ? &CXX_STANDARDS[1] - : needCxx98 ? &CXX_STANDARDS[0] - : nullptr; - /* clang-format on */ - - if (needCxxLevel) { - // Ensure the C++ language level is high enough to support - // the needed C++ features. - if (!existingCxxLevel || existingCxxLevel < needCxxLevel) { - newRequiredStandard = *needCxxLevel; - } - } - - return true; -} - -bool cmMakefile::HaveCudaStandardAvailable(cmGeneratorTarget const* target, - std::string const& lang, - std::string const& config, - const std::string& feature) const -{ - cmProp defaultCudaStandard = - this->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); - if (!defaultCudaStandard) { - this->IssueMessage( - MessageType::INTERNAL_ERROR, - cmStrCat("CMAKE_", lang, - "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support " - "not fully configured for this compiler.")); - // Return true so the caller does not try to lookup the default standard. - return true; - } - if (std::find_if(cm::cbegin(CUDA_STANDARDS), cm::cend(CUDA_STANDARDS), - cmStrCmp(*defaultCudaStandard)) == - cm::cend(CUDA_STANDARDS)) { - const std::string e = - cmStrCat("The CMAKE_", lang, "_STANDARD_DEFAULT variable contains an ", - "invalid value: \"", *defaultCudaStandard, "\"."); - this->IssueMessage(MessageType::INTERNAL_ERROR, e); - return false; - } - - bool needCuda03 = false; - bool needCuda11 = false; - bool needCuda14 = false; - bool needCuda17 = false; - bool needCuda20 = false; - this->CheckNeededCudaLanguage(feature, lang, needCuda03, needCuda11, - needCuda14, needCuda17, needCuda20); - - cmProp existingCudaStandard = target->GetLanguageStandard(lang, config); - if (!existingCudaStandard) { - existingCudaStandard = defaultCudaStandard; - } - - const char* const* existingCudaLevel = - std::find_if(cm::cbegin(CUDA_STANDARDS), cm::cend(CUDA_STANDARDS), - cmStrCmp(*existingCudaStandard)); - if (existingCudaLevel == cm::cend(CUDA_STANDARDS)) { - const std::string e = cmStrCat( - "The ", lang, "_STANDARD property on target \"", target->GetName(), - "\" contained an invalid value: \"", *existingCudaStandard, "\"."); - this->IssueMessage(MessageType::FATAL_ERROR, e); - return false; - } - - /* clang-format off */ - const char* const* needCudaLevel = - needCuda20 ? &CUDA_STANDARDS[4] - : needCuda17 ? &CUDA_STANDARDS[3] - : needCuda14 ? &CUDA_STANDARDS[2] - : needCuda11 ? &CUDA_STANDARDS[1] - : needCuda03 ? &CUDA_STANDARDS[0] - : nullptr; - /* clang-format on */ - - return !needCudaLevel || needCudaLevel <= existingCudaLevel; -} - -void cmMakefile::CheckNeededCudaLanguage(const std::string& feature, - std::string const& lang, - bool& needCuda03, bool& needCuda11, - bool& needCuda14, bool& needCuda17, - bool& needCuda20) const -{ - if (const char* propCuda03 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "03_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCuda03); - needCuda03 = cm::contains(props, feature); - } - if (const char* propCuda11 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "11_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCuda11); - needCuda11 = cm::contains(props, feature); - } - if (const char* propCuda14 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "14_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCuda14); - needCuda14 = cm::contains(props, feature); - } - if (const char* propCuda17 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "17_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCuda17); - needCuda17 = cm::contains(props, feature); - } - if (const char* propCuda20 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "20_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propCuda20); - needCuda20 = cm::contains(props, feature); - } -} - -bool cmMakefile::AddRequiredTargetCudaFeature(cmTarget* target, - const std::string& feature, - std::string const& lang, - std::string* error) const -{ - std::string newRequiredStandard; - if (this->GetNewRequiredCudaStandard( - target->GetName(), feature, lang, - target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard, - error)) { - if (!newRequiredStandard.empty()) { - target->SetLanguageStandardProperty(lang, newRequiredStandard, feature); - } - return true; - } - return false; -} - -bool cmMakefile::GetNewRequiredCudaStandard(const std::string& targetName, - const std::string& feature, - std::string const& lang, - cmProp currentLangStandardValue, - std::string& newRequiredStandard, - std::string* error) const -{ - newRequiredStandard.clear(); - - bool needCuda03 = false; - bool needCuda11 = false; - bool needCuda14 = false; - bool needCuda17 = false; - bool needCuda20 = false; - - this->CheckNeededCudaLanguage(feature, lang, needCuda03, needCuda11, - needCuda14, needCuda17, needCuda20); - - cmProp existingCudaStandard = currentLangStandardValue; - if (existingCudaStandard == nullptr) { - cmProp defaultCudaStandard = - this->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); - if (defaultCudaStandard && !defaultCudaStandard->empty()) { - existingCudaStandard = defaultCudaStandard; - } - } - const char* const* existingCudaLevel = nullptr; - if (existingCudaStandard) { - existingCudaLevel = - std::find_if(cm::cbegin(CUDA_STANDARDS), cm::cend(CUDA_STANDARDS), - cmStrCmp(*existingCudaStandard)); - if (existingCudaLevel == cm::cend(CUDA_STANDARDS)) { - const std::string e = cmStrCat( - "The ", lang, "_STANDARD property on target \"", targetName, - "\" contained an invalid value: \"", *existingCudaStandard, "\"."); - if (error) { - *error = e; - } else { - this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e, - this->Backtrace); - } - return false; - } - } - - /* clang-format off */ - const char* const* needCudaLevel = - needCuda20 ? &CUDA_STANDARDS[4] - : needCuda17 ? &CUDA_STANDARDS[3] - : needCuda14 ? &CUDA_STANDARDS[2] - : needCuda11 ? &CUDA_STANDARDS[1] - : needCuda03 ? &CUDA_STANDARDS[0] - : nullptr; - /* clang-format on */ - - if (needCudaLevel) { - // Ensure the CUDA language level is high enough to support - // the needed CUDA features. - if (!existingCudaLevel || existingCudaLevel < needCudaLevel) { - newRequiredStandard = *needCudaLevel; - } - } - - return true; -} - -void cmMakefile::CheckNeededCLanguage(const std::string& feature, - std::string const& lang, bool& needC90, - bool& needC99, bool& needC11) const -{ - if (const char* propC90 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "90_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propC90); - needC90 = cm::contains(props, feature); - } - if (const char* propC99 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "99_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propC99); - needC99 = cm::contains(props, feature); - } - if (const char* propC11 = - this->GetDefinition(cmStrCat("CMAKE_", lang, "11_COMPILE_FEATURES"))) { - std::vector props = cmExpandedList(propC11); - needC11 = cm::contains(props, feature); - } -} - -bool cmMakefile::AddRequiredTargetCFeature(cmTarget* target, - const std::string& feature, - std::string const& lang, - std::string* error) const -{ - std::string newRequiredStandard; - if (this->GetNewRequiredCStandard( - target->GetName(), feature, lang, - target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard, - error)) { - if (!newRequiredStandard.empty()) { - target->SetLanguageStandardProperty(lang, newRequiredStandard, feature); - } - return true; - } - - return false; -} - -bool cmMakefile::GetNewRequiredCStandard(const std::string& targetName, - const std::string& feature, - std::string const& lang, - cmProp currentLangStandardValue, - std::string& newRequiredStandard, - std::string* error) const -{ - newRequiredStandard.clear(); - - bool needC90 = false; - bool needC99 = false; - bool needC11 = false; - - this->CheckNeededCLanguage(feature, lang, needC90, needC99, needC11); - - cmProp existingCStandard = currentLangStandardValue; - if (existingCStandard == nullptr) { - cmProp defaultCStandard = - this->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); - if (defaultCStandard && !defaultCStandard->empty()) { - existingCStandard = defaultCStandard; - } - } - if (existingCStandard) { - if (std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), - cmStrCmp(*existingCStandard)) == cm::cend(C_STANDARDS)) { - const std::string e = cmStrCat( - "The ", lang, "_STANDARD property on target \"", targetName, - "\" contained an invalid value: \"", *existingCStandard, "\"."); - if (error) { - *error = e; - } else { - this->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e, - this->Backtrace); - } - return false; - } - } - const char* const* existingCIt = existingCStandard - ? std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), - cmStrCmp(*existingCStandard)) - : cm::cend(C_STANDARDS); - - bool setC90 = needC90 && !existingCStandard; - bool setC99 = needC99 && !existingCStandard; - bool setC11 = needC11 && !existingCStandard; - - if (needC11 && existingCStandard && - existingCIt < std::find_if(cm::cbegin(C_STANDARDS), - cm::cend(C_STANDARDS), cmStrCmp("11"))) { - setC11 = true; - } else if (needC99 && existingCStandard && - existingCIt < std::find_if(cm::cbegin(C_STANDARDS), - cm::cend(C_STANDARDS), - cmStrCmp("99"))) { - setC99 = true; - } else if (needC90 && existingCStandard && - existingCIt < std::find_if(cm::cbegin(C_STANDARDS), - cm::cend(C_STANDARDS), - cmStrCmp("90"))) { - setC90 = true; - } - - if (setC11) { - newRequiredStandard = "11"; - } else if (setC99) { - newRequiredStandard = "99"; - } else if (setC90) { - newRequiredStandard = "90"; - } - return true; -} - cmMakefile::FunctionPushPop::FunctionPushPop(cmMakefile* mf, const std::string& fileName, cmPolicies::PolicyMap const& pm) diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 323ab5a..36ea834 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -47,7 +47,6 @@ class cmExpandedCommandArgument; class cmExportBuildFileGenerator; class cmFunctionBlocker; class cmGeneratorExpressionEvaluationFile; -class cmGeneratorTarget; class cmGlobalGenerator; class cmImplicitDependsList; class cmInstallGenerator; @@ -927,30 +926,6 @@ public: bool PolicyOptionalWarningEnabled(std::string const& var); - bool AddRequiredTargetFeature(cmTarget* target, const std::string& feature, - std::string* error = nullptr) const; - - bool CompileFeatureKnown(const std::string& targetName, - const std::string& feature, std::string& lang, - std::string* error) const; - - const char* CompileFeaturesAvailable(const std::string& lang, - std::string* error) const; - - bool GetNewRequiredStandard(const std::string& targetName, - const std::string& feature, - cmProp currentLangStandardValue, - std::string& newRequiredStandard, - std::string* error = nullptr) const; - - bool HaveStandardAvailable(cmGeneratorTarget const* target, - std::string const& lang, - std::string const& config, - const std::string& feature) const; - - bool IsLaterStandard(std::string const& lang, std::string const& lhs, - std::string const& rhs); - void PushLoopBlock(); void PopLoopBlock(); bool IsLoopBlock() const; @@ -1170,67 +1145,6 @@ private: */ bool MightHaveCustomCommand(const std::string& name) const; - bool AddRequiredTargetCFeature(cmTarget* target, const std::string& feature, - std::string const& lang, - std::string* error = nullptr) const; - bool AddRequiredTargetCxxFeature(cmTarget* target, - const std::string& feature, - std::string const& lang, - std::string* error = nullptr) const; - bool AddRequiredTargetCudaFeature(cmTarget* target, - const std::string& feature, - std::string const& lang, - std::string* error = nullptr) const; - - bool CheckCompileFeaturesAvailable(const std::string& targetName, - const std::string& feature, - std::string& lang, - std::string* error) const; - - void CheckNeededCLanguage(const std::string& feature, - std::string const& lang, bool& needC90, - bool& needC99, bool& needC11) const; - void CheckNeededCxxLanguage(const std::string& feature, - std::string const& lang, bool& needCxx98, - bool& needCxx11, bool& needCxx14, - bool& needCxx17, bool& needCxx20) const; - void CheckNeededCudaLanguage(const std::string& feature, - std::string const& lang, bool& needCuda03, - bool& needCuda11, bool& needCuda14, - bool& needCuda17, bool& needCuda20) const; - - bool GetNewRequiredCStandard(const std::string& targetName, - const std::string& feature, - std::string const& lang, - cmProp currentLangStandardValue, - std::string& newRequiredStandard, - std::string* error = nullptr) const; - bool GetNewRequiredCxxStandard(const std::string& targetName, - const std::string& feature, - std::string const& lang, - cmProp currentLangStandardValue, - std::string& newRequiredStandard, - std::string* error = nullptr) const; - bool GetNewRequiredCudaStandard(const std::string& targetName, - const std::string& feature, - std::string const& lang, - cmProp currentLangStandardValue, - std::string& newRequiredStandard, - std::string* error = nullptr) const; - - bool HaveCStandardAvailable(cmGeneratorTarget const* target, - std::string const& lang, - std::string const& config, - const std::string& feature) const; - bool HaveCxxStandardAvailable(cmGeneratorTarget const* target, - std::string const& lang, - std::string const& config, - const std::string& feature) const; - bool HaveCudaStandardAvailable(cmGeneratorTarget const* target, - std::string const& lang, - std::string const& config, - const std::string& feature) const; - bool CheckSystemVars; bool CheckCMP0000; std::set WarnedCMP0074; diff --git a/Source/cmStandardLevelResolver.cxx b/Source/cmStandardLevelResolver.cxx new file mode 100644 index 0000000..9d01368 --- /dev/null +++ b/Source/cmStandardLevelResolver.cxx @@ -0,0 +1,779 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#include "cmStandardLevelResolver.h" + +#include +#include +#include +#include + +#include +#include + +#include "cmGeneratorExpression.h" +#include "cmGeneratorTarget.h" +#include "cmGlobalGenerator.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmProperty.h" +#include "cmStringAlgorithms.h" +#include "cmTarget.h" +#include "cmake.h" + +#define FEATURE_STRING(F) , #F +static const char* const C_FEATURES[] = { nullptr FOR_EACH_C_FEATURE( + FEATURE_STRING) }; + +static const char* const CXX_FEATURES[] = { nullptr FOR_EACH_CXX_FEATURE( + FEATURE_STRING) }; + +static const char* const CUDA_FEATURES[] = { nullptr FOR_EACH_CUDA_FEATURE( + FEATURE_STRING) }; +#undef FEATURE_STRING + +static const char* const C_STANDARDS[] = { "90", "99", "11" }; +static const char* const CXX_STANDARDS[] = { "98", "11", "14", "17", "20" }; +static const char* const CUDA_STANDARDS[] = { "03", "11", "14", "17", "20" }; + +bool cmStandardLevelResolver::AddRequiredTargetFeature( + cmTarget* target, const std::string& feature, std::string* error) const +{ + if (cmGeneratorExpression::Find(feature) != std::string::npos) { + target->AppendProperty("COMPILE_FEATURES", feature); + return true; + } + + std::string lang; + if (!this->CheckCompileFeaturesAvailable(target->GetName(), feature, lang, + error)) { + return false; + } + + target->AppendProperty("COMPILE_FEATURES", feature); + + // FIXME: Add a policy to avoid updating the _STANDARD target + // property due to COMPILE_FEATURES. The language standard selection + // should be done purely at generate time based on whatever the project + // code put in these properties explicitly. That is mostly true now, + // but for compatibility we need to continue updating the property here. + if (lang == "C" || lang == "OBJC") { + return this->AddRequiredTargetCFeature(target, feature, lang, error); + } + if (lang == "CUDA") { + return this->AddRequiredTargetCudaFeature(target, feature, lang, error); + } + return this->AddRequiredTargetCxxFeature(target, feature, lang, error); +} + +bool cmStandardLevelResolver::CheckCompileFeaturesAvailable( + const std::string& targetName, const std::string& feature, std::string& lang, + std::string* error) const +{ + if (!this->CompileFeatureKnown(targetName, feature, lang, error)) { + return false; + } + + const char* features = this->CompileFeaturesAvailable(lang, error); + if (!features) { + return false; + } + + std::vector availableFeatures = cmExpandedList(features); + if (!cm::contains(availableFeatures, feature)) { + std::ostringstream e; + e << "The compiler feature \"" << feature << "\" is not known to " << lang + << " compiler\n\"" + << this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILER_ID") + << "\"\nversion " + << this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILER_VERSION") + << "."; + if (error) { + *error = e.str(); + } else { + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + } + return false; + } + + return true; +} + +bool cmStandardLevelResolver::CompileFeatureKnown( + const std::string& targetName, const std::string& feature, std::string& lang, + std::string* error) const +{ + assert(cmGeneratorExpression::Find(feature) == std::string::npos); + + bool isCFeature = + std::find_if(cm::cbegin(C_FEATURES) + 1, cm::cend(C_FEATURES), + cmStrCmp(feature)) != cm::cend(C_FEATURES); + if (isCFeature) { + lang = "C"; + return true; + } + bool isCxxFeature = + std::find_if(cm::cbegin(CXX_FEATURES) + 1, cm::cend(CXX_FEATURES), + cmStrCmp(feature)) != cm::cend(CXX_FEATURES); + if (isCxxFeature) { + lang = "CXX"; + return true; + } + bool isCudaFeature = + std::find_if(cm::cbegin(CUDA_FEATURES) + 1, cm::cend(CUDA_FEATURES), + cmStrCmp(feature)) != cm::cend(CUDA_FEATURES); + if (isCudaFeature) { + lang = "CUDA"; + return true; + } + std::ostringstream e; + if (error) { + e << "specified"; + } else { + e << "Specified"; + } + e << " unknown feature \"" << feature + << "\" for " + "target \"" + << targetName << "\"."; + if (error) { + *error = e.str(); + } else { + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + } + return false; +} + +const char* cmStandardLevelResolver::CompileFeaturesAvailable( + const std::string& lang, std::string* error) const +{ + if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) { + std::ostringstream e; + if (error) { + e << "cannot"; + } else { + e << "Cannot"; + } + e << " use features from non-enabled language " << lang; + if (error) { + *error = e.str(); + } else { + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + } + return nullptr; + } + + const char* featuresKnown = + this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILE_FEATURES"); + + if (!featuresKnown || !*featuresKnown) { + std::ostringstream e; + if (error) { + e << "no"; + } else { + e << "No"; + } + e << " known features for " << lang << " compiler\n\"" + << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID") + << "\"\nversion " + << this->Makefile->GetSafeDefinition("CMAKE_" + lang + + "_COMPILER_VERSION") + << "."; + if (error) { + *error = e.str(); + } else { + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str()); + } + return nullptr; + } + return featuresKnown; +} + +bool cmStandardLevelResolver::GetNewRequiredStandard( + const std::string& targetName, const std::string& feature, + cmProp currentLangStandardValue, std::string& newRequiredStandard, + std::string* error) const +{ + std::string lang; + if (!this->CheckCompileFeaturesAvailable(targetName, feature, lang, error)) { + return false; + } + + if (lang == "C" || lang == "OBJC") { + return this->GetNewRequiredCStandard(targetName, feature, lang, + currentLangStandardValue, + newRequiredStandard, error); + } + if (lang == "CUDA") { + return this->GetNewRequiredCudaStandard(targetName, feature, lang, + currentLangStandardValue, + newRequiredStandard, error); + } + return this->GetNewRequiredCxxStandard(targetName, feature, lang, + currentLangStandardValue, + newRequiredStandard, error); +} + +bool cmStandardLevelResolver::HaveStandardAvailable( + cmGeneratorTarget const* target, std::string const& lang, + std::string const& config, const std::string& feature) const +{ + if (lang == "C" || lang == "OBJC") { + return this->HaveCStandardAvailable(target, lang, config, feature); + } + if (lang == "CUDA") { + return this->HaveCudaStandardAvailable(target, lang, config, feature); + } + return this->HaveCxxStandardAvailable(target, lang, config, feature); +} + +bool cmStandardLevelResolver::HaveCStandardAvailable( + cmGeneratorTarget const* target, std::string const& lang, + std::string const& config, const std::string& feature) const +{ + cmProp defaultCStandard = + this->Makefile->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); + if (!defaultCStandard) { + this->Makefile->IssueMessage( + MessageType::INTERNAL_ERROR, + cmStrCat("CMAKE_", lang, + "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support " + "not fully configured for this compiler.")); + // Return true so the caller does not try to lookup the default standard. + return true; + } + if (std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), + cmStrCmp(*defaultCStandard)) == cm::cend(C_STANDARDS)) { + const std::string e = cmStrCat("The CMAKE_", lang, + "_STANDARD_DEFAULT variable contains an " + "invalid value: \"", + *defaultCStandard, "\"."); + this->Makefile->IssueMessage(MessageType::INTERNAL_ERROR, e); + return false; + } + + bool needC90 = false; + bool needC99 = false; + bool needC11 = false; + + this->CheckNeededCLanguage(feature, lang, needC90, needC99, needC11); + + cmProp existingCStandard = target->GetLanguageStandard(lang, config); + if (!existingCStandard) { + existingCStandard = defaultCStandard; + } + + if (std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), + cmStrCmp(*existingCStandard)) == cm::cend(C_STANDARDS)) { + const std::string e = cmStrCat( + "The ", lang, "_STANDARD property on target \"", target->GetName(), + "\" contained an invalid value: \"", *existingCStandard, "\"."); + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e); + return false; + } + + const char* const* existingCIt = existingCStandard + ? std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), + cmStrCmp(*existingCStandard)) + : cm::cend(C_STANDARDS); + + if (needC11 && existingCStandard && + existingCIt < std::find_if(cm::cbegin(C_STANDARDS), + cm::cend(C_STANDARDS), cmStrCmp("11"))) { + return false; + } + if (needC99 && existingCStandard && + existingCIt < std::find_if(cm::cbegin(C_STANDARDS), + cm::cend(C_STANDARDS), cmStrCmp("99"))) { + return false; + } + if (needC90 && existingCStandard && + existingCIt < std::find_if(cm::cbegin(C_STANDARDS), + cm::cend(C_STANDARDS), cmStrCmp("90"))) { + return false; + } + return true; +} + +bool cmStandardLevelResolver::IsLaterStandard(std::string const& lang, + std::string const& lhs, + std::string const& rhs) const +{ + if (lang == "C" || lang == "OBJC") { + const char* const* rhsIt = std::find_if( + cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), cmStrCmp(rhs)); + + return std::find_if(rhsIt, cm::cend(C_STANDARDS), cmStrCmp(lhs)) != + cm::cend(C_STANDARDS); + } + if (lang == "CUDA") { + const char* const* rhsIt = std::find_if( + cm::cbegin(CUDA_STANDARDS), cm::cend(CUDA_STANDARDS), cmStrCmp(rhs)); + + return std::find_if(rhsIt, cm::cend(CUDA_STANDARDS), cmStrCmp(lhs)) != + cm::cend(CUDA_STANDARDS); + } + + const char* const* rhsIt = std::find_if( + cm::cbegin(CXX_STANDARDS), cm::cend(CXX_STANDARDS), cmStrCmp(rhs)); + + return std::find_if(rhsIt, cm::cend(CXX_STANDARDS), cmStrCmp(lhs)) != + cm::cend(CXX_STANDARDS); +} + +bool cmStandardLevelResolver::HaveCxxStandardAvailable( + cmGeneratorTarget const* target, std::string const& lang, + std::string const& config, const std::string& feature) const +{ + cmProp defaultCxxStandard = + this->Makefile->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); + if (!defaultCxxStandard) { + this->Makefile->IssueMessage( + MessageType::INTERNAL_ERROR, + cmStrCat("CMAKE_", lang, + "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support " + "not fully configured for this compiler.")); + // Return true so the caller does not try to lookup the default standard. + return true; + } + if (std::find_if(cm::cbegin(CXX_STANDARDS), cm::cend(CXX_STANDARDS), + cmStrCmp(*defaultCxxStandard)) == cm::cend(CXX_STANDARDS)) { + const std::string e = + cmStrCat("The CMAKE_", lang, "_STANDARD_DEFAULT variable contains an ", + "invalid value: \"", *defaultCxxStandard, "\"."); + this->Makefile->IssueMessage(MessageType::INTERNAL_ERROR, e); + return false; + } + + bool needCxx98 = false; + bool needCxx11 = false; + bool needCxx14 = false; + bool needCxx17 = false; + bool needCxx20 = false; + this->CheckNeededCxxLanguage(feature, lang, needCxx98, needCxx11, needCxx14, + needCxx17, needCxx20); + + cmProp existingCxxStandard = target->GetLanguageStandard(lang, config); + if (!existingCxxStandard) { + existingCxxStandard = defaultCxxStandard; + } + + const char* const* existingCxxLevel = + std::find_if(cm::cbegin(CXX_STANDARDS), cm::cend(CXX_STANDARDS), + cmStrCmp(*existingCxxStandard)); + if (existingCxxLevel == cm::cend(CXX_STANDARDS)) { + const std::string e = cmStrCat( + "The ", lang, "_STANDARD property on target \"", target->GetName(), + "\" contained an invalid value: \"", *existingCxxStandard, "\"."); + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e); + return false; + } + + /* clang-format off */ + const char* const* needCxxLevel = + needCxx20 ? &CXX_STANDARDS[4] + : needCxx17 ? &CXX_STANDARDS[3] + : needCxx14 ? &CXX_STANDARDS[2] + : needCxx11 ? &CXX_STANDARDS[1] + : needCxx98 ? &CXX_STANDARDS[0] + : nullptr; + /* clang-format on */ + + return !needCxxLevel || needCxxLevel <= existingCxxLevel; +} + +void cmStandardLevelResolver::CheckNeededCxxLanguage( + const std::string& feature, std::string const& lang, bool& needCxx98, + bool& needCxx11, bool& needCxx14, bool& needCxx17, bool& needCxx20) const +{ + if (const char* propCxx98 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "98_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCxx98); + needCxx98 = cm::contains(props, feature); + } + if (const char* propCxx11 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "11_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCxx11); + needCxx11 = cm::contains(props, feature); + } + if (const char* propCxx14 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "14_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCxx14); + needCxx14 = cm::contains(props, feature); + } + if (const char* propCxx17 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "17_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCxx17); + needCxx17 = cm::contains(props, feature); + } + if (const char* propCxx20 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "20_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCxx20); + needCxx20 = cm::contains(props, feature); + } +} + +bool cmStandardLevelResolver::AddRequiredTargetCxxFeature( + cmTarget* target, const std::string& feature, std::string const& lang, + std::string* error) const +{ + std::string newRequiredStandard; + if (this->GetNewRequiredCxxStandard( + target->GetName(), feature, lang, + target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard, + error)) { + if (!newRequiredStandard.empty()) { + target->SetLanguageStandardProperty(lang, newRequiredStandard, feature); + } + return true; + } + + return false; +} + +bool cmStandardLevelResolver::GetNewRequiredCxxStandard( + const std::string& targetName, const std::string& feature, + std::string const& lang, cmProp currentLangStandardValue, + std::string& newRequiredStandard, std::string* error) const +{ + newRequiredStandard.clear(); + + bool needCxx98 = false; + bool needCxx11 = false; + bool needCxx14 = false; + bool needCxx17 = false; + bool needCxx20 = false; + + this->CheckNeededCxxLanguage(feature, lang, needCxx98, needCxx11, needCxx14, + needCxx17, needCxx20); + + cmProp existingCxxStandard = currentLangStandardValue; + if (existingCxxStandard == nullptr) { + cmProp defaultCxxStandard = + this->Makefile->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); + if (defaultCxxStandard && !defaultCxxStandard->empty()) { + existingCxxStandard = defaultCxxStandard; + } + } + const char* const* existingCxxLevel = nullptr; + if (existingCxxStandard) { + existingCxxLevel = + std::find_if(cm::cbegin(CXX_STANDARDS), cm::cend(CXX_STANDARDS), + cmStrCmp(*existingCxxStandard)); + if (existingCxxLevel == cm::cend(CXX_STANDARDS)) { + const std::string e = cmStrCat( + "The ", lang, "_STANDARD property on target \"", targetName, + "\" contained an invalid value: \"", *existingCxxStandard, "\"."); + if (error) { + *error = e; + } else { + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e); + } + return false; + } + } + + /* clang-format off */ + const char* const* needCxxLevel = + needCxx20 ? &CXX_STANDARDS[4] + : needCxx17 ? &CXX_STANDARDS[3] + : needCxx14 ? &CXX_STANDARDS[2] + : needCxx11 ? &CXX_STANDARDS[1] + : needCxx98 ? &CXX_STANDARDS[0] + : nullptr; + /* clang-format on */ + + if (needCxxLevel) { + // Ensure the C++ language level is high enough to support + // the needed C++ features. + if (!existingCxxLevel || existingCxxLevel < needCxxLevel) { + newRequiredStandard = *needCxxLevel; + } + } + + return true; +} + +bool cmStandardLevelResolver::HaveCudaStandardAvailable( + cmGeneratorTarget const* target, std::string const& lang, + std::string const& config, const std::string& feature) const +{ + cmProp defaultCudaStandard = + this->Makefile->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); + if (!defaultCudaStandard) { + this->Makefile->IssueMessage( + MessageType::INTERNAL_ERROR, + cmStrCat("CMAKE_", lang, + "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support " + "not fully configured for this compiler.")); + // Return true so the caller does not try to lookup the default standard. + return true; + } + if (std::find_if(cm::cbegin(CUDA_STANDARDS), cm::cend(CUDA_STANDARDS), + cmStrCmp(*defaultCudaStandard)) == + cm::cend(CUDA_STANDARDS)) { + const std::string e = + cmStrCat("The CMAKE_", lang, "_STANDARD_DEFAULT variable contains an ", + "invalid value: \"", *defaultCudaStandard, "\"."); + this->Makefile->IssueMessage(MessageType::INTERNAL_ERROR, e); + return false; + } + + bool needCuda03 = false; + bool needCuda11 = false; + bool needCuda14 = false; + bool needCuda17 = false; + bool needCuda20 = false; + this->CheckNeededCudaLanguage(feature, lang, needCuda03, needCuda11, + needCuda14, needCuda17, needCuda20); + + cmProp existingCudaStandard = target->GetLanguageStandard(lang, config); + if (!existingCudaStandard) { + existingCudaStandard = defaultCudaStandard; + } + + const char* const* existingCudaLevel = + std::find_if(cm::cbegin(CUDA_STANDARDS), cm::cend(CUDA_STANDARDS), + cmStrCmp(*existingCudaStandard)); + if (existingCudaLevel == cm::cend(CUDA_STANDARDS)) { + const std::string e = cmStrCat( + "The ", lang, "_STANDARD property on target \"", target->GetName(), + "\" contained an invalid value: \"", *existingCudaStandard, "\"."); + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e); + return false; + } + + /* clang-format off */ + const char* const* needCudaLevel = + needCuda20 ? &CUDA_STANDARDS[4] + : needCuda17 ? &CUDA_STANDARDS[3] + : needCuda14 ? &CUDA_STANDARDS[2] + : needCuda11 ? &CUDA_STANDARDS[1] + : needCuda03 ? &CUDA_STANDARDS[0] + : nullptr; + /* clang-format on */ + + return !needCudaLevel || needCudaLevel <= existingCudaLevel; +} + +void cmStandardLevelResolver::CheckNeededCudaLanguage( + const std::string& feature, std::string const& lang, bool& needCuda03, + bool& needCuda11, bool& needCuda14, bool& needCuda17, bool& needCuda20) const +{ + if (const char* propCuda03 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "03_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCuda03); + needCuda03 = cm::contains(props, feature); + } + if (const char* propCuda11 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "11_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCuda11); + needCuda11 = cm::contains(props, feature); + } + if (const char* propCuda14 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "14_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCuda14); + needCuda14 = cm::contains(props, feature); + } + if (const char* propCuda17 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "17_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCuda17); + needCuda17 = cm::contains(props, feature); + } + if (const char* propCuda20 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "20_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propCuda20); + needCuda20 = cm::contains(props, feature); + } +} + +bool cmStandardLevelResolver::AddRequiredTargetCudaFeature( + cmTarget* target, const std::string& feature, std::string const& lang, + std::string* error) const +{ + std::string newRequiredStandard; + if (this->GetNewRequiredCudaStandard( + target->GetName(), feature, lang, + target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard, + error)) { + if (!newRequiredStandard.empty()) { + target->SetLanguageStandardProperty(lang, newRequiredStandard, feature); + } + return true; + } + return false; +} + +bool cmStandardLevelResolver::GetNewRequiredCudaStandard( + const std::string& targetName, const std::string& feature, + std::string const& lang, cmProp currentLangStandardValue, + std::string& newRequiredStandard, std::string* error) const +{ + newRequiredStandard.clear(); + + bool needCuda03 = false; + bool needCuda11 = false; + bool needCuda14 = false; + bool needCuda17 = false; + bool needCuda20 = false; + + this->CheckNeededCudaLanguage(feature, lang, needCuda03, needCuda11, + needCuda14, needCuda17, needCuda20); + + cmProp existingCudaStandard = currentLangStandardValue; + if (existingCudaStandard == nullptr) { + cmProp defaultCudaStandard = + this->Makefile->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); + if (defaultCudaStandard && !defaultCudaStandard->empty()) { + existingCudaStandard = defaultCudaStandard; + } + } + const char* const* existingCudaLevel = nullptr; + if (existingCudaStandard) { + existingCudaLevel = + std::find_if(cm::cbegin(CUDA_STANDARDS), cm::cend(CUDA_STANDARDS), + cmStrCmp(*existingCudaStandard)); + if (existingCudaLevel == cm::cend(CUDA_STANDARDS)) { + const std::string e = cmStrCat( + "The ", lang, "_STANDARD property on target \"", targetName, + "\" contained an invalid value: \"", *existingCudaStandard, "\"."); + if (error) { + *error = e; + } else { + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e); + } + return false; + } + } + + /* clang-format off */ + const char* const* needCudaLevel = + needCuda20 ? &CUDA_STANDARDS[4] + : needCuda17 ? &CUDA_STANDARDS[3] + : needCuda14 ? &CUDA_STANDARDS[2] + : needCuda11 ? &CUDA_STANDARDS[1] + : needCuda03 ? &CUDA_STANDARDS[0] + : nullptr; + /* clang-format on */ + + if (needCudaLevel) { + // Ensure the CUDA language level is high enough to support + // the needed CUDA features. + if (!existingCudaLevel || existingCudaLevel < needCudaLevel) { + newRequiredStandard = *needCudaLevel; + } + } + + return true; +} + +void cmStandardLevelResolver::CheckNeededCLanguage(const std::string& feature, + std::string const& lang, + bool& needC90, + bool& needC99, + bool& needC11) const +{ + if (const char* propC90 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "90_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propC90); + needC90 = cm::contains(props, feature); + } + if (const char* propC99 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "99_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propC99); + needC99 = cm::contains(props, feature); + } + if (const char* propC11 = this->Makefile->GetDefinition( + cmStrCat("CMAKE_", lang, "11_COMPILE_FEATURES"))) { + std::vector props = cmExpandedList(propC11); + needC11 = cm::contains(props, feature); + } +} + +bool cmStandardLevelResolver::AddRequiredTargetCFeature( + cmTarget* target, const std::string& feature, std::string const& lang, + std::string* error) const +{ + std::string newRequiredStandard; + if (this->GetNewRequiredCStandard( + target->GetName(), feature, lang, + target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard, + error)) { + if (!newRequiredStandard.empty()) { + target->SetLanguageStandardProperty(lang, newRequiredStandard, feature); + } + return true; + } + + return false; +} + +bool cmStandardLevelResolver::GetNewRequiredCStandard( + const std::string& targetName, const std::string& feature, + std::string const& lang, cmProp currentLangStandardValue, + std::string& newRequiredStandard, std::string* error) const +{ + newRequiredStandard.clear(); + + bool needC90 = false; + bool needC99 = false; + bool needC11 = false; + + this->CheckNeededCLanguage(feature, lang, needC90, needC99, needC11); + + cmProp existingCStandard = currentLangStandardValue; + if (existingCStandard == nullptr) { + cmProp defaultCStandard = + this->Makefile->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT")); + if (defaultCStandard && !defaultCStandard->empty()) { + existingCStandard = defaultCStandard; + } + } + if (existingCStandard) { + if (std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), + cmStrCmp(*existingCStandard)) == cm::cend(C_STANDARDS)) { + const std::string e = cmStrCat( + "The ", lang, "_STANDARD property on target \"", targetName, + "\" contained an invalid value: \"", *existingCStandard, "\"."); + if (error) { + *error = e; + } else { + this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e); + } + return false; + } + } + const char* const* existingCIt = existingCStandard + ? std::find_if(cm::cbegin(C_STANDARDS), cm::cend(C_STANDARDS), + cmStrCmp(*existingCStandard)) + : cm::cend(C_STANDARDS); + + bool setC90 = needC90 && !existingCStandard; + bool setC99 = needC99 && !existingCStandard; + bool setC11 = needC11 && !existingCStandard; + + if (needC11 && existingCStandard && + existingCIt < std::find_if(cm::cbegin(C_STANDARDS), + cm::cend(C_STANDARDS), cmStrCmp("11"))) { + setC11 = true; + } else if (needC99 && existingCStandard && + existingCIt < std::find_if(cm::cbegin(C_STANDARDS), + cm::cend(C_STANDARDS), + cmStrCmp("99"))) { + setC99 = true; + } else if (needC90 && existingCStandard && + existingCIt < std::find_if(cm::cbegin(C_STANDARDS), + cm::cend(C_STANDARDS), + cmStrCmp("90"))) { + setC90 = true; + } + + if (setC11) { + newRequiredStandard = "11"; + } else if (setC99) { + newRequiredStandard = "99"; + } else if (setC90) { + newRequiredStandard = "90"; + } + return true; +} diff --git a/Source/cmStandardLevelResolver.h b/Source/cmStandardLevelResolver.h new file mode 100644 index 0000000..666d6d3 --- /dev/null +++ b/Source/cmStandardLevelResolver.h @@ -0,0 +1,111 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#ifndef cmStandardLevelResolver_h +#define cmStandardLevelResolver_h + +#include + +#include "cmProperty.h" + +class cmMakefile; +class cmGeneratorTarget; +class cmTarget; + +class cmStandardLevelResolver +{ + +public: + explicit cmStandardLevelResolver(cmMakefile* makefile) + : Makefile(makefile) + { + } + + bool AddRequiredTargetFeature(cmTarget* target, const std::string& feature, + std::string* error = nullptr) const; + + bool CompileFeatureKnown(const std::string& targetName, + const std::string& feature, std::string& lang, + std::string* error) const; + + const char* CompileFeaturesAvailable(const std::string& lang, + std::string* error) const; + + bool GetNewRequiredStandard(const std::string& targetName, + const std::string& feature, + cmProp currentLangStandardValue, + std::string& newRequiredStandard, + std::string* error = nullptr) const; + + bool HaveStandardAvailable(cmGeneratorTarget const* target, + std::string const& lang, + std::string const& config, + const std::string& feature) const; + + bool IsLaterStandard(std::string const& lang, std::string const& lhs, + std::string const& rhs) const; + +private: + bool AddRequiredTargetCFeature(cmTarget* target, const std::string& feature, + std::string const& lang, + std::string* error = nullptr) const; + bool AddRequiredTargetCxxFeature(cmTarget* target, + const std::string& feature, + std::string const& lang, + std::string* error = nullptr) const; + bool AddRequiredTargetCudaFeature(cmTarget* target, + const std::string& feature, + std::string const& lang, + std::string* error = nullptr) const; + + bool CheckCompileFeaturesAvailable(const std::string& targetName, + const std::string& feature, + std::string& lang, + std::string* error) const; + + void CheckNeededCLanguage(const std::string& feature, + std::string const& lang, bool& needC90, + bool& needC99, bool& needC11) const; + void CheckNeededCxxLanguage(const std::string& feature, + std::string const& lang, bool& needCxx98, + bool& needCxx11, bool& needCxx14, + bool& needCxx17, bool& needCxx20) const; + void CheckNeededCudaLanguage(const std::string& feature, + std::string const& lang, bool& needCuda03, + bool& needCuda11, bool& needCuda14, + bool& needCuda17, bool& needCuda20) const; + + bool GetNewRequiredCStandard(const std::string& targetName, + const std::string& feature, + std::string const& lang, + cmProp currentLangStandardValue, + std::string& newRequiredStandard, + std::string* error = nullptr) const; + bool GetNewRequiredCxxStandard(const std::string& targetName, + const std::string& feature, + std::string const& lang, + cmProp currentLangStandardValue, + std::string& newRequiredStandard, + std::string* error = nullptr) const; + bool GetNewRequiredCudaStandard(const std::string& targetName, + const std::string& feature, + std::string const& lang, + cmProp currentLangStandardValue, + std::string& newRequiredStandard, + std::string* error = nullptr) const; + + bool HaveCStandardAvailable(cmGeneratorTarget const* target, + std::string const& lang, + std::string const& config, + const std::string& feature) const; + bool HaveCxxStandardAvailable(cmGeneratorTarget const* target, + std::string const& lang, + std::string const& config, + const std::string& feature) const; + bool HaveCudaStandardAvailable(cmGeneratorTarget const* target, + std::string const& lang, + std::string const& config, + const std::string& feature) const; + + cmMakefile* Makefile; +}; +#endif diff --git a/Source/cmTargetCompileFeaturesCommand.cxx b/Source/cmTargetCompileFeaturesCommand.cxx index 06be4f0..aa1abdd 100644 --- a/Source/cmTargetCompileFeaturesCommand.cxx +++ b/Source/cmTargetCompileFeaturesCommand.cxx @@ -4,6 +4,7 @@ #include "cmMakefile.h" #include "cmMessageType.h" +#include "cmStandardLevelResolver.h" #include "cmStringAlgorithms.h" #include "cmTargetPropCommandBase.h" @@ -29,9 +30,10 @@ private: const std::vector& content, bool /*prepend*/, bool /*system*/) override { + cmStandardLevelResolver standardResolver(this->Makefile); for (std::string const& it : content) { std::string error; - if (!this->Makefile->AddRequiredTargetFeature(tgt, it, &error)) { + if (!standardResolver.AddRequiredTargetFeature(tgt, it, &error)) { this->SetError(error); return false; // Not (successfully) handled. } diff --git a/bootstrap b/bootstrap index fe7b2ad..1273a46 100755 --- a/bootstrap +++ b/bootstrap @@ -432,6 +432,7 @@ CMAKE_CXX_SOURCES="\ cmSiteNameCommand \ cmSourceFile \ cmSourceFileLocation \ + cmStandardLevelResolver \ cmState \ cmStateDirectory \ cmStateSnapshot \ -- cgit v0.12