1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
4 #include "cmStandardLevelResolver.h"
11 #include <unordered_map>
15 #include <cm/iterator>
16 #include <cmext/algorithm>
18 #include "cmGeneratorExpression.h"
19 #include "cmGeneratorTarget.h"
20 #include "cmGlobalGenerator.h"
21 #include "cmListFileCache.h"
22 #include "cmMakefile.h"
23 #include "cmMessageType.h"
24 #include "cmPolicies.h"
25 #include "cmStringAlgorithms.h"
32 #define FEATURE_STRING(F) , #F
33 const char* const C_FEATURES[] = { nullptr FOR_EACH_C_FEATURE(
36 const char* const CXX_FEATURES[] = { nullptr FOR_EACH_CXX_FEATURE(
39 const char* const CUDA_FEATURES[] = { nullptr FOR_EACH_CUDA_FEATURE(
42 const char* const HIP_FEATURES[] = { nullptr FOR_EACH_HIP_FEATURE(
52 int ParseStd(std::string const& level)
55 return std::stoi(level);
56 } catch (std::invalid_argument&) {
57 // Fall through to use an invalid value.
62 struct StandardLevelComputer
64 explicit StandardLevelComputer(std::string lang, std::vector<int> levels,
65 std::vector<std::string> levelsStr)
66 : Language(std::move(lang))
67 , Levels(std::move(levels))
68 , LevelsAsStrings(std::move(levelsStr))
70 assert(this->Levels.size() == this->LevelsAsStrings.size());
73 std::string GetCompileOptionDef(cmMakefile* makefile,
74 cmGeneratorTarget const* target,
75 std::string const& config) const
78 const auto& stds = this->Levels;
79 const auto& stdsStrings = this->LevelsAsStrings;
81 cmValue defaultStd = makefile->GetDefinition(
82 cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
83 if (!cmNonempty(defaultStd)) {
84 // this compiler has no notion of language standard levels
88 cmPolicies::PolicyStatus const cmp0128{ makefile->GetPolicyStatus(
89 cmPolicies::CMP0128) };
90 bool const defaultExt{ cmIsOn(*makefile->GetDefinition(
91 cmStrCat("CMAKE_", this->Language, "_EXTENSIONS_DEFAULT"))) };
94 if (cmp0128 == cmPolicies::NEW) {
98 if (cmValue extPropValue = target->GetLanguageExtensions(this->Language)) {
99 ext = cmIsOn(*extPropValue);
102 std::string const type{ ext ? "EXTENSION" : "STANDARD" };
104 cmValue standardProp = target->GetLanguageStandard(this->Language, config);
106 if (cmp0128 == cmPolicies::NEW) {
107 // Add extension flag if compiler's default doesn't match.
108 if (ext != defaultExt) {
109 return cmStrCat("CMAKE_", this->Language, *defaultStd, "_", type,
113 if (cmp0128 == cmPolicies::WARN &&
114 makefile->PolicyOptionalWarningEnabled(
115 "CMAKE_POLICY_WARNING_CMP0128") &&
119 if (!makefile->GetDefinition(cmStrCat(
120 "CMAKE_", this->Language, "_EXTENSION_COMPILE_OPTION"))) {
127 makefile->IssueMessage(
128 MessageType::AUTHOR_WARNING,
129 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0128),
130 "\nFor compatibility with older versions of CMake, "
131 "compiler extensions won't be ",
137 return cmStrCat("CMAKE_", this->Language,
138 "_EXTENSION_COMPILE_OPTION");
141 return std::string{};
144 if (target->GetLanguageStandardRequired(this->Language)) {
145 std::string option_flag = cmStrCat(
146 "CMAKE_", this->Language, *standardProp, "_", type, "_COMPILE_OPTION");
148 cmValue opt = target->Target->GetMakefile()->GetDefinition(option_flag);
150 std::ostringstream e;
151 e << "Target \"" << target->GetName()
152 << "\" requires the language "
154 << this->Language << *standardProp << "\" "
155 << (ext ? "(with compiler extensions)" : "")
156 << ". But the current compiler \""
157 << makefile->GetSafeDefinition("CMAKE_" + this->Language +
159 << "\" does not support this, or "
160 "CMake does not know the flags to enable it.";
162 makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
167 // If the request matches the compiler's defaults we don't need to add
169 if (*standardProp == *defaultStd && ext == defaultExt) {
170 if (cmp0128 == cmPolicies::NEW) {
171 return std::string{};
174 if (cmp0128 == cmPolicies::WARN &&
175 makefile->PolicyOptionalWarningEnabled(
176 "CMAKE_POLICY_WARNING_CMP0128")) {
177 makefile->IssueMessage(
178 MessageType::AUTHOR_WARNING,
179 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0128),
180 "\nFor compatibility with older versions of CMake, "
181 "unnecessary flags for language standard or compiler "
182 "extensions may be added."));
186 std::string standardStr(*standardProp);
187 if (this->Language == "CUDA" && standardStr == "98") {
192 std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(standardStr));
193 if (stdIt == cm::cend(stds)) {
195 cmStrCat(this->Language, "_STANDARD is set to invalid value '",
197 makefile->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e,
198 target->GetBacktrace());
199 return std::string{};
203 std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(*defaultStd));
204 if (defaultStdIt == cm::cend(stds)) {
205 std::string e = cmStrCat("CMAKE_", this->Language,
206 "_STANDARD_DEFAULT is set to invalid value '",
208 makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
209 return std::string{};
212 // If the standard requested is older than the compiler's default or the
213 // extension mode doesn't match then we need to use a flag.
214 if ((cmp0128 != cmPolicies::NEW && stdIt <= defaultStdIt) ||
215 (cmp0128 == cmPolicies::NEW &&
216 (stdIt < defaultStdIt || ext != defaultExt))) {
217 auto offset = std::distance(cm::cbegin(stds), stdIt);
218 return cmStrCat("CMAKE_", this->Language, stdsStrings[offset], "_", type,
222 // The compiler's default is at least as new as the requested standard,
223 // and the requested standard is not required. Decay to the newest
224 // standard for which a flag is defined.
225 for (; defaultStdIt < stdIt; --stdIt) {
226 auto offset = std::distance(cm::cbegin(stds), stdIt);
227 std::string option_flag =
228 cmStrCat("CMAKE_", this->Language, stdsStrings[offset], "_", type,
230 if (target->Target->GetMakefile()->GetDefinition(option_flag)) {
235 return std::string{};
238 bool GetNewRequiredStandard(cmMakefile* makefile,
239 std::string const& targetName,
240 const std::string& feature,
241 cmValue currentLangStandardValue,
242 std::string& newRequiredStandard,
243 std::string* error) const
245 if (currentLangStandardValue) {
246 newRequiredStandard = *currentLangStandardValue;
248 newRequiredStandard.clear();
251 auto needed = this->HighestStandardNeeded(makefile, feature);
253 cmValue existingStandard = currentLangStandardValue;
254 if (!existingStandard) {
255 cmValue defaultStandard = makefile->GetDefinition(
256 cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
257 if (cmNonempty(defaultStandard)) {
258 existingStandard = defaultStandard;
262 auto existingLevelIter = cm::cend(this->Levels);
263 if (existingStandard) {
265 std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
266 ParseStd(*existingStandard));
267 if (existingLevelIter == cm::cend(this->Levels)) {
268 const std::string e =
269 cmStrCat("The ", this->Language, "_STANDARD property on target \"",
270 targetName, "\" contained an invalid value: \"",
271 *existingStandard, "\".");
275 makefile->IssueMessage(MessageType::FATAL_ERROR, e);
281 if (needed.index != -1) {
282 // Ensure the C++ language level is high enough to support
283 // the needed C++ features.
284 if (existingLevelIter == cm::cend(this->Levels) ||
285 existingLevelIter < this->Levels.begin() + needed.index) {
286 newRequiredStandard = this->LevelsAsStrings[needed.index];
293 bool HaveStandardAvailable(cmMakefile* makefile,
294 cmGeneratorTarget const* target,
295 std::string const& config,
296 std::string const& feature) const
298 cmValue defaultStandard = makefile->GetDefinition(
299 cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
300 if (!defaultStandard) {
301 makefile->IssueMessage(
302 MessageType::INTERNAL_ERROR,
303 cmStrCat("CMAKE_", this->Language,
304 "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support "
305 "not fully configured for this compiler."));
306 // Return true so the caller does not try to lookup the default standard.
309 // convert defaultStandard to an integer
310 if (std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
311 ParseStd(*defaultStandard)) == cm::cend(this->Levels)) {
312 const std::string e = cmStrCat("The CMAKE_", this->Language,
313 "_STANDARD_DEFAULT variable contains an "
315 *defaultStandard, "\".");
316 makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
320 cmValue existingStandard =
321 target->GetLanguageStandard(this->Language, config);
322 if (!existingStandard) {
323 existingStandard = defaultStandard;
326 auto existingLevelIter =
327 std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
328 ParseStd(*existingStandard));
329 if (existingLevelIter == cm::cend(this->Levels)) {
330 const std::string e =
331 cmStrCat("The ", this->Language, "_STANDARD property on target \"",
332 target->GetName(), "\" contained an invalid value: \"",
333 *existingStandard, "\".");
334 makefile->IssueMessage(MessageType::FATAL_ERROR, e);
338 auto needed = this->HighestStandardNeeded(makefile, feature);
340 return (needed.index == -1) ||
341 (this->Levels.begin() + needed.index) <= existingLevelIter;
344 StandardNeeded HighestStandardNeeded(cmMakefile* makefile,
345 std::string const& feature) const
347 std::string prefix = cmStrCat("CMAKE_", this->Language);
348 StandardNeeded maxLevel = { -1, -1 };
349 for (size_t i = 0; i < this->Levels.size(); ++i) {
350 if (cmValue prop = makefile->GetDefinition(
351 cmStrCat(prefix, this->LevelsAsStrings[i], "_COMPILE_FEATURES"))) {
352 std::vector<std::string> props = cmExpandedList(*prop);
353 if (cm::contains(props, feature)) {
354 maxLevel = { static_cast<int>(i), this->Levels[i] };
361 bool IsLaterStandard(int lhs, int rhs) const
364 std::find(cm::cbegin(this->Levels), cm::cend(this->Levels), rhs);
366 return std::find(rhsIt, cm::cend(this->Levels), lhs) !=
367 cm::cend(this->Levels);
370 std::string Language;
371 std::vector<int> Levels;
372 std::vector<std::string> LevelsAsStrings;
375 std::unordered_map<std::string, StandardLevelComputer>
376 StandardComputerMapping = {
378 StandardLevelComputer{
379 "C", std::vector<int>{ 90, 99, 11, 17, 23 },
380 std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
382 StandardLevelComputer{ "CXX",
383 std::vector<int>{ 98, 11, 14, 17, 20, 23, 26 },
384 std::vector<std::string>{ "98", "11", "14", "17",
385 "20", "23", "26" } } },
387 StandardLevelComputer{ "CUDA",
388 std::vector<int>{ 03, 11, 14, 17, 20, 23, 26 },
389 std::vector<std::string>{ "03", "11", "14", "17",
390 "20", "23", "26" } } },
392 StandardLevelComputer{
393 "OBJC", std::vector<int>{ 90, 99, 11, 17, 23 },
394 std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
396 StandardLevelComputer{ "OBJCXX",
397 std::vector<int>{ 98, 11, 14, 17, 20, 23, 26 },
398 std::vector<std::string>{ "98", "11", "14", "17",
399 "20", "23", "26" } } },
401 StandardLevelComputer{ "HIP",
402 std::vector<int>{ 98, 11, 14, 17, 20, 23, 26 },
403 std::vector<std::string>{ "98", "11", "14", "17",
404 "20", "23", "26" } } }
408 std::string cmStandardLevelResolver::GetCompileOptionDef(
409 cmGeneratorTarget const* target, std::string const& lang,
410 std::string const& config) const
412 const auto& mapping = StandardComputerMapping.find(lang);
413 if (mapping == cm::cend(StandardComputerMapping)) {
414 return std::string{};
417 return mapping->second.GetCompileOptionDef(this->Makefile, target, config);
420 bool cmStandardLevelResolver::AddRequiredTargetFeature(
421 cmTarget* target, const std::string& feature, std::string* error) const
423 if (cmGeneratorExpression::Find(feature) != std::string::npos) {
424 target->AppendProperty("COMPILE_FEATURES", feature,
425 this->Makefile->GetBacktrace());
430 if (!this->CheckCompileFeaturesAvailable(target->GetName(), feature, lang,
435 target->AppendProperty("COMPILE_FEATURES", feature,
436 this->Makefile->GetBacktrace());
438 // FIXME: Add a policy to avoid updating the <LANG>_STANDARD target
439 // property due to COMPILE_FEATURES. The language standard selection
440 // should be done purely at generate time based on whatever the project
441 // code put in these properties explicitly. That is mostly true now,
442 // but for compatibility we need to continue updating the property here.
443 std::string newRequiredStandard;
444 bool newRequired = this->GetNewRequiredStandard(
445 target->GetName(), feature,
446 target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard,
448 if (!newRequiredStandard.empty()) {
449 target->SetProperty(cmStrCat(lang, "_STANDARD"), newRequiredStandard);
454 bool cmStandardLevelResolver::CheckCompileFeaturesAvailable(
455 const std::string& targetName, const std::string& feature, std::string& lang,
456 std::string* error) const
458 if (!this->CompileFeatureKnown(targetName, feature, lang, error)) {
462 if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
466 cmValue features = this->CompileFeaturesAvailable(lang, error);
471 std::vector<std::string> availableFeatures = cmExpandedList(features);
472 if (!cm::contains(availableFeatures, feature)) {
473 std::ostringstream e;
474 e << "The compiler feature \"" << feature << "\" is not known to " << lang
476 << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
478 << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
484 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
492 bool cmStandardLevelResolver::CompileFeatureKnown(
493 const std::string& targetName, const std::string& feature, std::string& lang,
494 std::string* error) const
496 assert(cmGeneratorExpression::Find(feature) == std::string::npos);
499 std::find_if(cm::cbegin(C_FEATURES) + 1, cm::cend(C_FEATURES),
500 cmStrCmp(feature)) != cm::cend(C_FEATURES);
506 std::find_if(cm::cbegin(CXX_FEATURES) + 1, cm::cend(CXX_FEATURES),
507 cmStrCmp(feature)) != cm::cend(CXX_FEATURES);
513 std::find_if(cm::cbegin(CUDA_FEATURES) + 1, cm::cend(CUDA_FEATURES),
514 cmStrCmp(feature)) != cm::cend(CUDA_FEATURES);
520 std::find_if(cm::cbegin(HIP_FEATURES) + 1, cm::cend(HIP_FEATURES),
521 cmStrCmp(feature)) != cm::cend(HIP_FEATURES);
526 std::ostringstream e;
532 e << " unknown feature \"" << feature
535 << targetName << "\".";
539 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
544 cmValue cmStandardLevelResolver::CompileFeaturesAvailable(
545 const std::string& lang, std::string* error) const
547 if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
548 std::ostringstream e;
554 e << " use features from non-enabled language " << lang;
558 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
563 cmValue featuresKnown =
564 this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILE_FEATURES");
566 if (!cmNonempty(featuresKnown)) {
567 std::ostringstream e;
573 e << " known features for " << lang << " compiler\n\""
574 << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
576 << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
582 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
586 return featuresKnown;
589 bool cmStandardLevelResolver::GetNewRequiredStandard(
590 const std::string& targetName, const std::string& feature,
591 cmValue currentLangStandardValue, std::string& newRequiredStandard,
592 std::string* error) const
595 if (!this->CheckCompileFeaturesAvailable(targetName, feature, lang, error)) {
599 auto mapping = StandardComputerMapping.find(lang);
600 if (mapping != cm::cend(StandardComputerMapping)) {
601 return mapping->second.GetNewRequiredStandard(
602 this->Makefile, targetName, feature, currentLangStandardValue,
603 newRequiredStandard, error);
608 bool cmStandardLevelResolver::HaveStandardAvailable(
609 cmGeneratorTarget const* target, std::string const& lang,
610 std::string const& config, const std::string& feature) const
612 auto mapping = StandardComputerMapping.find(lang);
613 if (mapping != cm::cend(StandardComputerMapping)) {
614 return mapping->second.HaveStandardAvailable(this->Makefile, target,
620 bool cmStandardLevelResolver::IsLaterStandard(std::string const& lang,
621 std::string const& lhs,
622 std::string const& rhs) const
624 auto mapping = StandardComputerMapping.find(lang);
625 if (mapping != cm::cend(StandardComputerMapping)) {
626 return mapping->second.IsLaterStandard(std::stoi(lhs), std::stoi(rhs));