Просмотр исходного кода

Merge topic 'COMPILE_FEATURES-genex'

0dfe395e Features: Add COMPILE_FEATURES generator expression.
aa8a6fce cmMakefile: Add methods for checking availability of a feature.
b6dedf03 cmMakefile: Extract CheckNeeded{C,Cxx}Language methods.
8dd129df cmMakefile: Extract CompileFeaturesAvailable method.
6b9b2fff cmMakefile: Extract CompileFeatureKnown method.
Brad King 11 лет назад
Родитель
Сommit
5ce40619db
26 измененных файлов с 539 добавлено и 48 удалено
  1. 6 0
      Help/manual/cmake-generator-expressions.7.rst
  2. 4 0
      Help/release/dev/compile-language-features.rst
  3. 17 0
      Source/cmGeneratorExpression.cxx
  4. 5 0
      Source/cmGeneratorExpression.h
  5. 89 0
      Source/cmGeneratorExpressionEvaluator.cxx
  6. 2 0
      Source/cmGeneratorExpressionEvaluator.h
  7. 25 0
      Source/cmLocalGenerator.cxx
  8. 231 48
      Source/cmMakefile.cxx
  9. 23 0
      Source/cmMakefile.h
  10. 1 0
      Source/cmTarget.cxx
  11. 7 0
      Source/cmTarget.h
  12. 14 0
      Tests/CompileFeatures/CMakeLists.txt
  13. 21 0
      Tests/CompileFeatures/genex_test.cpp
  14. 1 0
      Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle-result.txt
  15. 7 0
      Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle-stderr.txt
  16. 15 0
      Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle.cmake
  17. 1 0
      Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-result.txt
  18. 1 0
      Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-stderr.txt
  19. 14 0
      Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved.cmake
  20. 1 0
      Tests/RunCMake/CompileFeatures/NonValidTarget1-result.txt
  21. 9 0
      Tests/RunCMake/CompileFeatures/NonValidTarget1-stderr.txt
  22. 17 0
      Tests/RunCMake/CompileFeatures/NonValidTarget1.cmake
  23. 1 0
      Tests/RunCMake/CompileFeatures/NonValidTarget2-result.txt
  24. 9 0
      Tests/RunCMake/CompileFeatures/NonValidTarget2-stderr.txt
  25. 8 0
      Tests/RunCMake/CompileFeatures/NonValidTarget2.cmake
  26. 10 0
      Tests/RunCMake/CompileFeatures/RunCMakeTest.cmake

+ 6 - 0
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
 =========================

+ 4 - 0
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.

+ 17 - 0
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;
+    }
+}

+ 5 - 0
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;

+ 89 - 0
Source/cmGeneratorExpressionEvaluator.cxx

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

+ 2 - 0
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.

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

+ 231 - 48
Source/cmMakefile.cxx

@@ -5004,38 +5004,91 @@ AddRequiredTargetFeature(cmTarget *target, const std::string& feature,
     target->AppendProperty("COMPILE_FEATURES", feature.c_str());
     return true;
     }
+
+  std::string lang;
+  if (!this->CompileFeatureKnown(target, feature, lang, error))
+    {
+    return false;
+    }
+
+  const char* features = this->CompileFeaturesAvailable(lang, error);
+  if (!features)
+    {
+    return false;
+    }
+
+  std::vector<std::string> availableFeatures;
+  cmSystemTools::ExpandListArgument(features, availableFeatures);
+  if (std::find(availableFeatures.begin(),
+                availableFeatures.end(),
+                feature) == availableFeatures.end())
+    {
+    cmOStringStream 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") << ".";
+    this->IssueMessage(cmake::FATAL_ERROR, e.str());
+    return false;
+    }
+
+  target->AppendProperty("COMPILE_FEATURES", feature.c_str());
+
+  return lang == "C"
+      ? this->AddRequiredTargetCFeature(target, feature)
+      : this->AddRequiredTargetCxxFeature(target, feature);
+}
+
+//----------------------------------------------------------------------------
+bool cmMakefile::
+CompileFeatureKnown(cmTarget const* target, const std::string& feature,
+                    std::string& lang, std::string *error) const
+{
+  assert(cmGeneratorExpression::Find(feature) == std::string::npos);
+
   bool isCFeature = std::find_if(cmArrayBegin(C_FEATURES) + 1,
               cmArrayEnd(C_FEATURES), cmStrCmp(feature))
               != cmArrayEnd(C_FEATURES);
+  if (isCFeature)
+    {
+    lang = "C";
+    return true;
+    }
   bool isCxxFeature = std::find_if(cmArrayBegin(CXX_FEATURES) + 1,
               cmArrayEnd(CXX_FEATURES), cmStrCmp(feature))
               != cmArrayEnd(CXX_FEATURES);
-  if (!isCFeature && !isCxxFeature)
+  if (isCxxFeature)
     {
-    cmOStringStream e;
-    if (error)
-      {
-      e << "specified";
-      }
-    else
-      {
-      e << "Specified";
-      }
-    e << " unknown feature \"" << feature << "\" for "
-      "target \"" << target->GetName() << "\".";
-    if (error)
-      {
-      *error = e.str();
-      }
-    else
-      {
-      this->IssueMessage(cmake::FATAL_ERROR, e.str());
-      }
-    return false;
+    lang = "CXX";
+    return true;
     }
+  cmOStringStream e;
+  if (error)
+    {
+    e << "specified";
+    }
+  else
+    {
+    e << "Specified";
+    }
+  e << " unknown feature \"" << feature << "\" for "
+    "target \"" << target->GetName() << "\".";
+  if (error)
+    {
+    *error = e.str();
+    }
+  else
+    {
+    this->IssueMessage(cmake::FATAL_ERROR, e.str());
+    }
+  return false;
+}
 
-  std::string lang = isCFeature ? "C" : "CXX";
-
+//----------------------------------------------------------------------------
+const char* cmMakefile::
+CompileFeaturesAvailable(const std::string& lang, std::string *error) const
+{
   const char* featuresKnown =
     this->GetDefinition("CMAKE_" + lang + "_COMPILE_FEATURES");
 
@@ -5062,40 +5115,150 @@ AddRequiredTargetFeature(cmTarget *target, const std::string& feature,
       {
       this->IssueMessage(cmake::FATAL_ERROR, e.str());
       }
-    return false;
+    return 0;
     }
+  return featuresKnown;
+}
 
-  std::vector<std::string> availableFeatures;
-  cmSystemTools::ExpandListArgument(featuresKnown, availableFeatures);
-  if (std::find(availableFeatures.begin(),
-                availableFeatures.end(),
-                feature) == availableFeatures.end())
+//----------------------------------------------------------------------------
+bool cmMakefile::HaveFeatureAvailable(cmTarget const* target,
+                                      std::string const& lang,
+                                      const std::string& feature) const
+{
+  return lang == "C"
+      ? this->HaveCFeatureAvailable(target, feature)
+      : this->HaveCxxFeatureAvailable(target, feature);
+}
+
+//----------------------------------------------------------------------------
+bool cmMakefile::
+HaveCFeatureAvailable(cmTarget const* target, const std::string& feature) const
+{
+  bool needC90 = false;
+  bool needC99 = false;
+  bool needC11 = false;
+
+  this->CheckNeededCLanguage(feature, needC90, needC99, needC11);
+
+  const char *existingCStandard = target->GetProperty("C_STANDARD");
+  if (!existingCStandard)
+    {
+    existingCStandard = this->GetDefinition("CMAKE_C_STANDARD_DEFAULT");
+    }
+
+  if (std::find_if(cmArrayBegin(C_STANDARDS), cmArrayEnd(C_STANDARDS),
+                cmStrCmp(existingCStandard)) == cmArrayEnd(C_STANDARDS))
     {
     cmOStringStream 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") << ".";
-    this->IssueMessage(cmake::FATAL_ERROR, e.str());
+    e << "The C_STANDARD property on target \"" << target->GetName()
+      << "\" contained an invalid value: \"" << existingCStandard << "\".";
+    this->IssueMessage(cmake::FATAL_ERROR, e.str().c_str());
     return false;
     }
 
-  target->AppendProperty("COMPILE_FEATURES", feature.c_str());
+  const char * const *existingCIt = existingCStandard
+                                    ? std::find_if(cmArrayBegin(C_STANDARDS),
+                                      cmArrayEnd(C_STANDARDS),
+                                      cmStrCmp(existingCStandard))
+                                    : cmArrayEnd(C_STANDARDS);
 
-  return isCFeature
-      ? this->AddRequiredTargetCFeature(target, feature)
-      : this->AddRequiredTargetCxxFeature(target, feature);
+  if (needC11 && existingCStandard && existingCIt <
+                                    std::find_if(cmArrayBegin(C_STANDARDS),
+                                      cmArrayEnd(C_STANDARDS),
+                                      cmStrCmp("11")))
+    {
+    return false;
+    }
+  else if(needC99 && existingCStandard && existingCIt <
+                                    std::find_if(cmArrayBegin(C_STANDARDS),
+                                      cmArrayEnd(C_STANDARDS),
+                                      cmStrCmp("99")))
+    {
+    return false;
+    }
+  else if(needC90 && existingCStandard && existingCIt <
+                                    std::find_if(cmArrayBegin(C_STANDARDS),
+                                      cmArrayEnd(C_STANDARDS),
+                                      cmStrCmp("90")))
+    {
+    return false;
+    }
+  return true;
 }
 
 //----------------------------------------------------------------------------
-bool cmMakefile::
-AddRequiredTargetCxxFeature(cmTarget *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
 {
   bool needCxx98 = false;
   bool needCxx11 = false;
+  this->CheckNeededCxxLanguage(feature, needCxx98, needCxx11);
+
+  const char *existingCxxStandard = target->GetProperty("CXX_STANDARD");
+  if (!existingCxxStandard)
+    {
+    existingCxxStandard = this->GetDefinition("CMAKE_CXX_STANDARD_DEFAULT");
+    }
 
+  if (std::find_if(cmArrayBegin(CXX_STANDARDS), cmArrayEnd(CXX_STANDARDS),
+                cmStrCmp(existingCxxStandard)) == cmArrayEnd(CXX_STANDARDS))
+    {
+    cmOStringStream e;
+    e << "The CXX_STANDARD property on target \"" << target->GetName()
+      << "\" contained an invalid value: \"" << existingCxxStandard << "\".";
+    this->IssueMessage(cmake::FATAL_ERROR, e.str());
+    return false;
+    }
+
+  const char * const *existingCxxIt = existingCxxStandard
+                                    ? std::find_if(cmArrayBegin(CXX_STANDARDS),
+                                      cmArrayEnd(CXX_STANDARDS),
+                                      cmStrCmp(existingCxxStandard))
+                                    : cmArrayEnd(CXX_STANDARDS);
+
+  if (needCxx11 && existingCxxIt < std::find_if(cmArrayBegin(CXX_STANDARDS),
+                                      cmArrayEnd(CXX_STANDARDS),
+                                      cmStrCmp("11")))
+    {
+    return false;
+    }
+  else if(needCxx98 && existingCxxIt <
+                                    std::find_if(cmArrayBegin(CXX_STANDARDS),
+                                      cmArrayEnd(CXX_STANDARDS),
+                                      cmStrCmp("98")))
+    {
+    return false;
+    }
+  return true;
+}
+
+//----------------------------------------------------------------------------
+void cmMakefile::CheckNeededCxxLanguage(const std::string& feature,
+                                        bool& needCxx98,
+                                        bool& needCxx11) const
+{
   if (const char *propCxx98 =
           this->GetDefinition("CMAKE_CXX98_COMPILE_FEATURES"))
     {
@@ -5110,6 +5273,17 @@ AddRequiredTargetCxxFeature(cmTarget *target,
     cmSystemTools::ExpandListArgument(propCxx11, props);
     needCxx11 = std::find(props.begin(), props.end(), feature) != props.end();
     }
+}
+
+//----------------------------------------------------------------------------
+bool cmMakefile::
+AddRequiredTargetCxxFeature(cmTarget *target,
+                            const std::string& feature) const
+{
+  bool needCxx98 = false;
+  bool needCxx11 = false;
+
+  this->CheckNeededCxxLanguage(feature, needCxx98, needCxx11);
 
   const char *existingCxxStandard = target->GetProperty("CXX_STANDARD");
   if (existingCxxStandard)
@@ -5160,13 +5334,11 @@ AddRequiredTargetCxxFeature(cmTarget *target,
 }
 
 //----------------------------------------------------------------------------
-bool cmMakefile::
-AddRequiredTargetCFeature(cmTarget *target, const std::string& feature) const
+void cmMakefile::CheckNeededCLanguage(const std::string& feature,
+                                        bool& needC90,
+                                        bool& needC99,
+                                        bool& needC11) const
 {
-  bool needC90 = false;
-  bool needC99 = false;
-  bool needC11 = false;
-
   if (const char *propC90 =
           this->GetDefinition("CMAKE_C90_COMPILE_FEATURES"))
     {
@@ -5188,6 +5360,17 @@ AddRequiredTargetCFeature(cmTarget *target, const std::string& feature) const
     cmSystemTools::ExpandListArgument(propC11, props);
     needC11 = std::find(props.begin(), props.end(), feature) != props.end();
     }
+}
+
+//----------------------------------------------------------------------------
+bool cmMakefile::
+AddRequiredTargetCFeature(cmTarget *target, const std::string& feature) const
+{
+  bool needC90 = false;
+  bool needC99 = false;
+  bool needC11 = false;
+
+  this->CheckNeededCLanguage(feature, needC90, needC99, needC11);
 
   const char *existingCStandard = target->GetProperty("C_STANDARD");
   if (existingCStandard)

+ 23 - 0
Source/cmMakefile.h

@@ -889,6 +889,19 @@ public:
                                 const std::string& feature,
                                 std::string *error = 0) const;
 
+  bool CompileFeatureKnown(cmTarget const* target, const std::string& feature,
+                           std::string& lang, std::string *error) const;
+
+  const char* CompileFeaturesAvailable(const std::string& lang,
+                                       std::string *error) const;
+
+  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);
 
@@ -1104,6 +1117,16 @@ private:
 
   bool AddRequiredTargetCxxFeature(cmTarget *target,
                                    const std::string& feature) const;
+
+  void CheckNeededCLanguage(const std::string& feature, bool& needC90,
+                            bool& needC99, bool& needC11) const;
+  void CheckNeededCxxLanguage(const std::string& feature, bool& needCxx98,
+                              bool& needCxx11) const;
+
+  bool HaveCFeatureAvailable(cmTarget const* target,
+                             const std::string& feature) const;
+  bool HaveCxxFeatureAvailable(cmTarget const* target,
+                               const std::string& feature) const;
 };
 
 //----------------------------------------------------------------------------

+ 1 - 0
Source/cmTarget.cxx

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

+ 7 - 0
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.

+ 14 - 0
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>)

+ 21 - 0
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()
+{
+
+}

+ 1 - 0
Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycle-result.txt

@@ -0,0 +1 @@
+1

+ 7 - 0
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.

+ 15 - 0
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
+)

+ 1 - 0
Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-result.txt

@@ -0,0 +1 @@
+0

+ 1 - 0
Tests/RunCMake/CompileFeatures/LinkImplementationFeatureCycleSolved-stderr.txt

@@ -0,0 +1 @@
+^$

+ 14 - 0
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)

+ 1 - 0
Tests/RunCMake/CompileFeatures/NonValidTarget1-result.txt

@@ -0,0 +1 @@
+1

+ 9 - 0
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\)

+ 17 - 0
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()

+ 1 - 0
Tests/RunCMake/CompileFeatures/NonValidTarget2-result.txt

@@ -0,0 +1 @@
+1

+ 9 - 0
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\)

+ 8 - 0
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")

+ 10 - 0
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)