Merge topic 'languageStandardRefactor'

742ff97f80 Refactor language standard computation
0892c798f7 cmMakefile: Change CompileFeatureKnown to take target name instead of target

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !4803
diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx
index e4fb67e..3e0c21c 100644
--- a/Source/cmGeneratorExpressionNode.cxx
+++ b/Source/cmGeneratorExpressionNode.cxx
@@ -1708,7 +1708,7 @@
       std::string error;
       std::string lang;
       if (!context->LG->GetMakefile()->CompileFeatureKnown(
-            context->HeadTarget->Target, p, lang, &error)) {
+            context->HeadTarget->Target->GetName(), p, lang, &error)) {
         reportError(context, content->GetOriginalExpression(), error);
         return std::string();
       }
@@ -1742,9 +1742,9 @@
           continue;
         }
         if (!context->LG->GetMakefile()->HaveStandardAvailable(
-              target->Target, lit.first, it)) {
+              target, lit.first, context->Config, it)) {
           if (evalLL) {
-            cmProp l = target->GetProperty(lit.first + "_STANDARD");
+            cmProp l = target->GetLanguageStandard(lit.first, context->Config);
             if (!l) {
               l = standardDefault;
             }
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index f2011ee..1f66a9f 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -947,6 +947,45 @@
   return it != this->ExplicitObjectName.end();
 }
 
+cmProp cmGeneratorTarget::GetLanguageStandard(std::string const& lang,
+                                              std::string const& config) const
+{
+  std::string key = cmStrCat(cmSystemTools::UpperCase(config), '-', lang);
+  auto langStandardIter = this->LanguageStandardMap.find(key);
+  if (langStandardIter != this->LanguageStandardMap.end()) {
+    return &langStandardIter->second;
+  }
+
+  return this->Target->GetProperty(cmStrCat(lang, "_STANDARD"));
+}
+
+cmProp cmGeneratorTarget::GetLanguageStandardProperty(std::string const& lang,
+                                                      const char* suffix) const
+{
+  cmProp propertyValue = this->Target->GetProperty(cmStrCat(lang, suffix));
+  if (propertyValue == nullptr) {
+    // Check if we should use the value set by another language.
+    if (lang == "OBJC") {
+      propertyValue = this->GetLanguageStandardProperty("C", suffix);
+    } else if (lang == "OBJCXX" || lang == "CUDA") {
+      propertyValue = this->GetLanguageStandardProperty("CXX", suffix);
+    }
+  }
+  return propertyValue;
+}
+
+cmProp cmGeneratorTarget::GetLanguageExtensions(std::string const& lang) const
+{
+  return this->GetLanguageStandardProperty(lang, "_EXTENSIONS");
+}
+
+bool cmGeneratorTarget::GetLanguageStandardRequired(
+  std::string const& lang) const
+{
+  cmProp p = this->GetLanguageStandardProperty(lang, "_STANDARD_REQUIRED");
+  return p && cmIsOn(*p);
+}
+
 void cmGeneratorTarget::GetModuleDefinitionSources(
   std::vector<cmSourceFile const*>& data, const std::string& config) const
 {
@@ -4408,12 +4447,68 @@
 
 bool cmGeneratorTarget::ComputeCompileFeatures(std::string const& config) const
 {
+  // Compute the language standard based on the compile features.
   std::vector<BT<std::string>> features = this->GetCompileFeatures(config);
   for (BT<std::string> const& f : features) {
-    if (!this->Makefile->AddRequiredTargetFeature(this->Target, f.Value)) {
+    std::string lang;
+    if (!this->Makefile->CompileFeatureKnown(this->Target->GetName(), f.Value,
+                                             lang, nullptr)) {
       return false;
     }
+
+    std::string key = cmStrCat(cmSystemTools::UpperCase(config), '-', lang);
+    cmProp currentLanguageStandard = this->GetLanguageStandard(lang, config);
+
+    std::string newRequiredStandard;
+    if (!this->Makefile->GetNewRequiredStandard(
+          this->Target->GetName(), f.Value, currentLanguageStandard,
+          newRequiredStandard)) {
+      return false;
+    }
+
+    if (!newRequiredStandard.empty()) {
+      this->LanguageStandardMap[key] = newRequiredStandard;
+    }
   }
+
+  return true;
+}
+
+bool cmGeneratorTarget::ComputeCompileFeatures(
+  std::string const& config, std::set<LanguagePair> const& languagePairs) const
+{
+  for (const auto& language : languagePairs) {
+    cmProp generatorTargetLanguageStandard =
+      this->GetLanguageStandard(language.first, config);
+    if (!generatorTargetLanguageStandard) {
+      // If the standard isn't explicitly set we copy it over from the
+      // specified paired language.
+      std::string key =
+        cmStrCat(cmSystemTools::UpperCase(config), '-', language.first);
+      cmProp standardToCopy =
+        this->GetLanguageStandard(language.second, config);
+      if (standardToCopy != nullptr) {
+        this->LanguageStandardMap[key] = *standardToCopy;
+        generatorTargetLanguageStandard = &this->LanguageStandardMap[key];
+      } else {
+        cmProp defaultStandard = this->Makefile->GetDef(
+          cmStrCat("CMAKE_", language.second, "_STANDARD_DEFAULT"));
+        if (defaultStandard != nullptr) {
+          this->LanguageStandardMap[key] = *defaultStandard;
+          generatorTargetLanguageStandard = &this->LanguageStandardMap[key];
+        }
+      }
+
+      // Custom updates for the CUDA standard.
+      if (generatorTargetLanguageStandard != nullptr &&
+          language.first == "CUDA") {
+        if (*generatorTargetLanguageStandard == "98") {
+          this->LanguageStandardMap[key] = "03";
+        }
+      }
+    }
+  }
+
   return true;
 }
 
diff --git a/Source/cmGeneratorTarget.h b/Source/cmGeneratorTarget.h
index 3aedbf5..a71e64c 100644
--- a/Source/cmGeneratorTarget.h
+++ b/Source/cmGeneratorTarget.h
@@ -148,6 +148,13 @@
   bool HasExplicitObjectName(cmSourceFile const* file) const;
   void AddExplicitObjectName(cmSourceFile const* sf);
 
+  cmProp GetLanguageStandard(std::string const& lang,
+                             std::string const& config) const;
+
+  cmProp GetLanguageExtensions(std::string const& lang) const;
+
+  bool GetLanguageStandardRequired(std::string const& lang) const;
+
   void GetModuleDefinitionSources(std::vector<cmSourceFile const*>&,
                                   const std::string& config) const;
   void GetExternalObjects(std::vector<cmSourceFile const*>&,
@@ -515,6 +522,11 @@
 
   bool ComputeCompileFeatures(std::string const& config) const;
 
+  using LanguagePair = std::pair<std::string, std::string>;
+  bool ComputeCompileFeatures(
+    std::string const& config,
+    std::set<LanguagePair> const& languagePairs) const;
+
   /**
    * Trace through the source files in this target and add al source files
    * that they depend on, used by all generators
@@ -1038,6 +1050,11 @@
   bool GetRPATH(const std::string& config, const std::string& prop,
                 std::string& rpath) const;
 
+  mutable std::map<std::string, std::string> LanguageStandardMap;
+
+  cmProp GetLanguageStandardProperty(std::string const& lang,
+                                     const char* suffix) const;
+
 public:
   const std::vector<const cmGeneratorTarget*>& GetLinkImplementationClosure(
     const std::string& config) const;
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index 1ed5e8b..4b4ffda 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -1442,12 +1442,10 @@
     localGen->AddHelperCommands();
   }
 
-  // Finalize the set of compile features for each target.
-  // FIXME: This turns into calls to cmMakefile::AddRequiredTargetFeature
-  // which actually modifies the <lang>_STANDARD target property
-  // on the original cmTarget instance.  It accumulates features
-  // across all configurations.  Some refactoring is needed to
-  // compute a per-config resulta purely during generation.
+  // Perform up-front computation in order to handle errors (such as unknown
+  // features) at this point. While processing the compile features we also
+  // calculate and cache the language standard required by the compile
+  // features.
   for (const auto& localGen : this->LocalGenerators) {
     if (!localGen->ComputeTargetCompileFeatures()) {
       return false;
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 3b3f110..95caa30 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -803,39 +803,8 @@
     // Now that C/C++ _STANDARD values have been computed
     // set the values to ObjC/ObjCXX _STANDARD variables
     if (target->GetType() != cmStateEnums::INTERFACE_LIBRARY) {
-      auto copyStandardToObjLang = [&](LanguagePair const& lang) -> bool {
-        if (!target->GetProperty(cmStrCat(lang.first, "_STANDARD"))) {
-          cmProp standard =
-            target->GetProperty(cmStrCat(lang.second, "_STANDARD"));
-          if (!standard) {
-            standard = this->Makefile->GetDef(
-              cmStrCat("CMAKE_", lang.second, "_STANDARD_DEFAULT"));
-          }
-          target->Target->SetProperty(cmStrCat(lang.first, "_STANDARD"),
-                                      standard ? standard->c_str() : nullptr);
-          return true;
-        }
-        return false;
-      };
-      auto copyPropertyToObjLang = [&](LanguagePair const& lang,
-                                       const char* property) {
-        if (!target->GetProperty(cmStrCat(lang.first, property)) &&
-            target->GetProperty(cmStrCat(lang.second, property))) {
-          cmProp p = target->GetProperty(cmStrCat(lang.second, property));
-          target->Target->SetProperty(cmStrCat(lang.first, property),
-                                      p ? p->c_str() : nullptr);
-        }
-      };
-      for (auto const& lang : pairedLanguages) {
-        if (copyStandardToObjLang(lang)) {
-          copyPropertyToObjLang(lang, "_STANDARD_REQUIRED");
-          copyPropertyToObjLang(lang, "_EXTENSIONS");
-        }
-      }
-      if (cmProp standard = target->GetProperty("CUDA_STANDARD")) {
-        if (*standard == "98") {
-          target->Target->SetProperty("CUDA_STANDARD", "03");
-        }
+      for (std::string const& c : configNames) {
+        target->ComputeCompileFeatures(c, inferredEnabledLanguages);
       }
     }
   }
@@ -1026,7 +995,7 @@
   }
 
   for (auto const& it : target->GetMaxLanguageStandards()) {
-    cmProp standard = target->GetProperty(it.first + "_STANDARD");
+    cmProp standard = target->GetLanguageStandard(it.first, config);
     if (!standard) {
       continue;
     }
@@ -1050,7 +1019,7 @@
   }
 
   std::string compReqFlag;
-  this->AddCompilerRequirementFlag(compReqFlag, target, lang);
+  this->AddCompilerRequirementFlag(compReqFlag, target, lang, config);
   if (!compReqFlag.empty()) {
     flags.emplace_back(std::move(compReqFlag));
   }
@@ -2046,7 +2015,7 @@
     // when linking in order to use the matching standard library.
     // FIXME: If CMake gains an abstraction for standard library
     // selection, this will have to be reconciled with it.
-    this->AddCompilerRequirementFlag(flags, target, lang);
+    this->AddCompilerRequirementFlag(flags, target, lang, config);
   }
 
   this->AddLanguageFlags(flags, target, lang, config);
@@ -2189,7 +2158,8 @@
 }
 
 void cmLocalGenerator::AddCompilerRequirementFlag(
-  std::string& flags, cmGeneratorTarget const* target, const std::string& lang)
+  std::string& flags, cmGeneratorTarget const* target, const std::string& lang,
+  const std::string& config)
 {
   if (lang.empty()) {
     return;
@@ -2200,15 +2170,13 @@
     // This compiler has no notion of language standard levels.
     return;
   }
-  std::string extProp = lang + "_EXTENSIONS";
   bool ext = true;
-  if (cmProp extPropValue = target->GetProperty(extProp)) {
+  if (cmProp extPropValue = target->GetLanguageExtensions(lang)) {
     if (cmIsOff(*extPropValue)) {
       ext = false;
     }
   }
-  std::string stdProp = lang + "_STANDARD";
-  cmProp standardProp = target->GetProperty(stdProp);
+  cmProp standardProp = target->GetLanguageStandard(lang, config);
   if (!standardProp) {
     if (ext) {
       // No language standard is specified and extensions are not disabled.
@@ -2228,7 +2196,7 @@
 
   std::string const type = ext ? "EXTENSION" : "STANDARD";
 
-  if (target->GetPropertyAsBool(lang + "_STANDARD_REQUIRED")) {
+  if (target->GetLanguageStandardRequired(lang)) {
     std::string option_flag =
       "CMAKE_" + lang + *standardProp + "_" + type + "_COMPILE_OPTION";
 
diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h
index f2d9145..f4781d6 100644
--- a/Source/cmLocalGenerator.h
+++ b/Source/cmLocalGenerator.h
@@ -123,7 +123,8 @@
                               const std::string& config);
   void AddCompilerRequirementFlag(std::string& flags,
                                   cmGeneratorTarget const* target,
-                                  const std::string& lang);
+                                  const std::string& lang,
+                                  const std::string& config);
   //! Append flags to a string.
   virtual void AppendFlags(std::string& flags,
                            const std::string& newFlags) const;
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index fa91059..1f1c06a 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -39,6 +39,7 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorExpressionEvaluationFile.h"
+#include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmInstallGenerator.h" // IWYU pragma: keep
 #include "cmInstallSubdirectoryGenerator.h"
@@ -4681,7 +4682,33 @@
   }
 
   std::string lang;
-  if (!this->CompileFeatureKnown(target, feature, lang, error)) {
+  if (!this->CheckCompileFeaturesAvailable(target->GetName(), feature, lang,
+                                           error)) {
+    return false;
+  }
+
+  target->AppendProperty("COMPILE_FEATURES", feature);
+
+  // FIXME: Add a policy to avoid updating the <LANG>_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;
   }
 
@@ -4707,18 +4734,10 @@
     return false;
   }
 
-  target->AppendProperty("COMPILE_FEATURES", feature);
-
-  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);
+  return true;
 }
 
-bool cmMakefile::CompileFeatureKnown(cmTarget const* target,
+bool cmMakefile::CompileFeatureKnown(const std::string& targetName,
                                      const std::string& feature,
                                      std::string& lang,
                                      std::string* error) const
@@ -4755,7 +4774,7 @@
   e << " unknown feature \"" << feature
     << "\" for "
        "target \""
-    << target->GetName() << "\".";
+    << targetName << "\".";
   if (error) {
     *error = e.str();
   } else {
@@ -4810,22 +4829,50 @@
   return featuresKnown;
 }
 
-bool cmMakefile::HaveStandardAvailable(cmTarget const* target,
+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, feature, lang);
+    return this->HaveCStandardAvailable(target, lang, config, feature);
   }
   if (lang == "CUDA") {
-    return this->HaveCudaStandardAvailable(target, feature, lang);
+    return this->HaveCudaStandardAvailable(target, lang, config, feature);
   }
-  return this->HaveCxxStandardAvailable(target, feature, lang);
+  return this->HaveCxxStandardAvailable(target, lang, config, feature);
 }
 
-bool cmMakefile::HaveCStandardAvailable(cmTarget const* target,
-                                        const std::string& feature,
-                                        std::string const& lang) const
+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"));
@@ -4854,7 +4901,7 @@
 
   this->CheckNeededCLanguage(feature, lang, needC90, needC99, needC11);
 
-  cmProp existingCStandard = target->GetProperty(cmStrCat(lang, "_STANDARD"));
+  cmProp existingCStandard = target->GetLanguageStandard(lang, config);
   if (!existingCStandard) {
     existingCStandard = defaultCStandard;
   }
@@ -4917,9 +4964,10 @@
     cm::cend(CXX_STANDARDS);
 }
 
-bool cmMakefile::HaveCxxStandardAvailable(cmTarget const* target,
-                                          const std::string& feature,
-                                          std::string const& lang) const
+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"));
@@ -4949,8 +4997,7 @@
   this->CheckNeededCxxLanguage(feature, lang, needCxx98, needCxx11, needCxx14,
                                needCxx17, needCxx20);
 
-  cmProp existingCxxStandard =
-    target->GetProperty(cmStrCat(lang, "_STANDARD"));
+  cmProp existingCxxStandard = target->GetLanguageStandard(lang, config);
   if (!existingCxxStandard) {
     existingCxxStandard = defaultCxxStandard;
   }
@@ -5017,6 +5064,29 @@
                                              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->SetProperty(cmStrCat(lang, "_STANDARD"), newRequiredStandard);
+    }
+    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;
@@ -5026,8 +5096,7 @@
   this->CheckNeededCxxLanguage(feature, lang, needCxx98, needCxx11, needCxx14,
                                needCxx17, needCxx20);
 
-  cmProp existingCxxStandard =
-    target->GetProperty(cmStrCat(lang, "_STANDARD"));
+  cmProp existingCxxStandard = currentLangStandardValue;
   if (existingCxxStandard == nullptr) {
     cmProp defaultCxxStandard =
       this->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT"));
@@ -5042,7 +5111,7 @@
                    cmStrCmp(*existingCxxStandard));
     if (existingCxxLevel == cm::cend(CXX_STANDARDS)) {
       const std::string e = cmStrCat(
-        "The ", lang, "_STANDARD property on target \"", target->GetName(),
+        "The ", lang, "_STANDARD property on target \"", targetName,
         "\" contained an invalid value: \"", *existingCxxStandard, "\".");
       if (error) {
         *error = e;
@@ -5068,16 +5137,17 @@
     // Ensure the C++ language level is high enough to support
     // the needed C++ features.
     if (!existingCxxLevel || existingCxxLevel < needCxxLevel) {
-      target->SetProperty(cmStrCat(lang, "_STANDARD"), *needCxxLevel);
+      newRequiredStandard = *needCxxLevel;
     }
   }
 
   return true;
 }
 
-bool cmMakefile::HaveCudaStandardAvailable(cmTarget const* target,
-                                           const std::string& feature,
-                                           std::string const& lang) const
+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"));
@@ -5108,8 +5178,7 @@
   this->CheckNeededCudaLanguage(feature, lang, needCuda03, needCuda11,
                                 needCuda14, needCuda17, needCuda20);
 
-  cmProp existingCudaStandard =
-    target->GetProperty(cmStrCat(lang, "_STANDARD"));
+  cmProp existingCudaStandard = target->GetLanguageStandard(lang, config);
   if (!existingCudaStandard) {
     existingCudaStandard = defaultCudaStandard;
   }
@@ -5176,6 +5245,28 @@
                                               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->SetProperty(cmStrCat(lang, "_STANDARD"), newRequiredStandard);
+    }
+    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;
@@ -5185,8 +5276,7 @@
   this->CheckNeededCudaLanguage(feature, lang, needCuda03, needCuda11,
                                 needCuda14, needCuda17, needCuda20);
 
-  cmProp existingCudaStandard =
-    target->GetProperty(cmStrCat(lang, "_STANDARD"));
+  cmProp existingCudaStandard = currentLangStandardValue;
   if (existingCudaStandard == nullptr) {
     cmProp defaultCudaStandard =
       this->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT"));
@@ -5201,7 +5291,7 @@
                    cmStrCmp(*existingCudaStandard));
     if (existingCudaLevel == cm::cend(CUDA_STANDARDS)) {
       const std::string e = cmStrCat(
-        "The ", lang, "_STANDARD property on target \"", target->GetName(),
+        "The ", lang, "_STANDARD property on target \"", targetName,
         "\" contained an invalid value: \"", *existingCudaStandard, "\".");
       if (error) {
         *error = e;
@@ -5227,7 +5317,7 @@
     // Ensure the CUDA language level is high enough to support
     // the needed CUDA features.
     if (!existingCudaLevel || existingCudaLevel < needCudaLevel) {
-      target->SetProperty("CUDA_STANDARD", *needCudaLevel);
+      newRequiredStandard = *needCudaLevel;
     }
   }
 
@@ -5260,13 +5350,36 @@
                                            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->SetProperty(cmStrCat(lang, "_STANDARD"), newRequiredStandard);
+    }
+    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 = target->GetProperty(cmStrCat(lang, "_STANDARD"));
+  cmProp existingCStandard = currentLangStandardValue;
   if (existingCStandard == nullptr) {
     cmProp defaultCStandard =
       this->GetDef(cmStrCat("CMAKE_", lang, "_STANDARD_DEFAULT"));
@@ -5278,7 +5391,7 @@
     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(),
+        "The ", lang, "_STANDARD property on target \"", targetName,
         "\" contained an invalid value: \"", *existingCStandard, "\".");
       if (error) {
         *error = e;
@@ -5315,11 +5428,11 @@
   }
 
   if (setC11) {
-    target->SetProperty(cmStrCat(lang, "_STANDARD"), "11");
+    newRequiredStandard = "11";
   } else if (setC99) {
-    target->SetProperty(cmStrCat(lang, "_STANDARD"), "99");
+    newRequiredStandard = "99";
   } else if (setC90) {
-    target->SetProperty(cmStrCat(lang, "_STANDARD"), "90");
+    newRequiredStandard = "90";
   }
   return true;
 }
diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h
index 45d7109..332554e 100644
--- a/Source/cmMakefile.h
+++ b/Source/cmMakefile.h
@@ -47,6 +47,7 @@
 class cmExportBuildFileGenerator;
 class cmFunctionBlocker;
 class cmGeneratorExpressionEvaluationFile;
+class cmGeneratorTarget;
 class cmGlobalGenerator;
 class cmImplicitDependsList;
 class cmInstallGenerator;
@@ -928,13 +929,22 @@
   bool AddRequiredTargetFeature(cmTarget* target, const std::string& feature,
                                 std::string* error = nullptr) const;
 
-  bool CompileFeatureKnown(cmTarget const* target, const std::string& feature,
-                           std::string& lang, std::string* error) 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 HaveStandardAvailable(cmTarget const* target, std::string const& lang,
+  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,
@@ -1174,6 +1184,11 @@
                                     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;
@@ -1186,15 +1201,37 @@
                                bool& needCuda11, bool& needCuda14,
                                bool& needCuda17, bool& needCuda20) const;
 
-  bool HaveCStandardAvailable(cmTarget const* target,
-                              const std::string& feature,
-                              std::string const& lang) const;
-  bool HaveCxxStandardAvailable(cmTarget const* target,
-                                const std::string& feature,
-                                std::string const& lang) const;
-  bool HaveCudaStandardAvailable(cmTarget const* target,
+  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) const;
+                                 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;
 
   void CheckForUnusedVariables() const;