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 "cmMakefile.h"
22 #include "cmMessageType.h"
23 #include "cmPolicies.h"
24 #include "cmStringAlgorithms.h"
31 #define FEATURE_STRING(F) , #F
32 const char* const C_FEATURES[] = { nullptr FOR_EACH_C_FEATURE(
35 const char* const CXX_FEATURES[] = { nullptr FOR_EACH_CXX_FEATURE(
38 const char* const CUDA_FEATURES[] = { nullptr FOR_EACH_CUDA_FEATURE(
41 const char* const HIP_FEATURES[] = { nullptr FOR_EACH_HIP_FEATURE(
51 int ParseStd(std::string const& level)
54 return std::stoi(level);
55 } catch (std::invalid_argument&) {
56 // Fall through to use an invalid value.
61 struct StandardLevelComputer
63 explicit StandardLevelComputer(std::string lang, std::vector<int> levels,
64 std::vector<std::string> levelsStr)
65 : Language(std::move(lang))
66 , Levels(std::move(levels))
67 , LevelsAsStrings(std::move(levelsStr))
69 assert(this->Levels.size() == this->LevelsAsStrings.size());
72 std::string GetCompileOptionDef(cmMakefile* makefile,
73 cmGeneratorTarget const* target,
74 std::string const& config) const
77 const auto& stds = this->Levels;
78 const auto& stdsStrings = this->LevelsAsStrings;
80 cmValue defaultStd = makefile->GetDefinition(
81 cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
82 if (!cmNonempty(defaultStd)) {
83 // this compiler has no notion of language standard levels
87 cmPolicies::PolicyStatus const cmp0128{ makefile->GetPolicyStatus(
88 cmPolicies::CMP0128) };
89 bool const defaultExt{ cmIsOn(*makefile->GetDefinition(
90 cmStrCat("CMAKE_", this->Language, "_EXTENSIONS_DEFAULT"))) };
93 if (cmp0128 == cmPolicies::NEW) {
97 if (cmValue extPropValue = target->GetLanguageExtensions(this->Language)) {
98 ext = cmIsOn(*extPropValue);
101 std::string const type{ ext ? "EXTENSION" : "STANDARD" };
103 cmValue standardProp = target->GetLanguageStandard(this->Language, config);
105 if (cmp0128 == cmPolicies::NEW) {
106 // Add extension flag if compiler's default doesn't match.
107 if (ext != defaultExt) {
108 return cmStrCat("CMAKE_", this->Language, *defaultStd, "_", type,
112 if (cmp0128 == cmPolicies::WARN &&
113 makefile->PolicyOptionalWarningEnabled(
114 "CMAKE_POLICY_WARNING_CMP0128") &&
118 if (!makefile->GetDefinition(cmStrCat(
119 "CMAKE_", this->Language, "_EXTENSION_COMPILE_OPTION"))) {
126 makefile->IssueMessage(
127 MessageType::AUTHOR_WARNING,
128 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0128),
129 "\nFor compatibility with older versions of CMake, "
130 "compiler extensions won't be ",
136 return cmStrCat("CMAKE_", this->Language,
137 "_EXTENSION_COMPILE_OPTION");
140 return std::string{};
143 if (target->GetLanguageStandardRequired(this->Language)) {
144 std::string option_flag = cmStrCat(
145 "CMAKE_", this->Language, *standardProp, "_", type, "_COMPILE_OPTION");
147 cmValue opt = target->Target->GetMakefile()->GetDefinition(option_flag);
149 std::ostringstream e;
150 e << "Target \"" << target->GetName()
151 << "\" requires the language "
153 << this->Language << *standardProp << "\" "
154 << (ext ? "(with compiler extensions)" : "")
155 << ". But the current compiler \""
156 << makefile->GetSafeDefinition("CMAKE_" + this->Language +
158 << "\" does not support this, or "
159 "CMake does not know the flags to enable it.";
161 makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
166 // If the request matches the compiler's defaults we don't need to add
168 if (*standardProp == *defaultStd && ext == defaultExt) {
169 if (cmp0128 == cmPolicies::NEW) {
170 return std::string{};
173 if (cmp0128 == cmPolicies::WARN &&
174 makefile->PolicyOptionalWarningEnabled(
175 "CMAKE_POLICY_WARNING_CMP0128")) {
176 makefile->IssueMessage(
177 MessageType::AUTHOR_WARNING,
178 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0128),
179 "\nFor compatibility with older versions of CMake, "
180 "unnecessary flags for language standard or compiler "
181 "extensions may be added."));
185 std::string standardStr(*standardProp);
186 if (this->Language == "CUDA" && standardStr == "98") {
191 std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(standardStr));
192 if (stdIt == cm::cend(stds)) {
194 cmStrCat(this->Language, "_STANDARD is set to invalid value '",
196 makefile->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e,
197 target->GetBacktrace());
198 return std::string{};
202 std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(*defaultStd));
203 if (defaultStdIt == cm::cend(stds)) {
204 std::string e = cmStrCat("CMAKE_", this->Language,
205 "_STANDARD_DEFAULT is set to invalid value '",
207 makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
208 return std::string{};
211 // If the standard requested is older than the compiler's default or the
212 // extension mode doesn't match then we need to use a flag.
213 if ((cmp0128 != cmPolicies::NEW && stdIt <= defaultStdIt) ||
214 (cmp0128 == cmPolicies::NEW &&
215 (stdIt < defaultStdIt || ext != defaultExt))) {
216 auto offset = std::distance(cm::cbegin(stds), stdIt);
217 return cmStrCat("CMAKE_", this->Language, stdsStrings[offset], "_", type,
221 // The compiler's default is at least as new as the requested standard,
222 // and the requested standard is not required. Decay to the newest
223 // standard for which a flag is defined.
224 for (; defaultStdIt < stdIt; --stdIt) {
225 auto offset = std::distance(cm::cbegin(stds), stdIt);
226 std::string option_flag =
227 cmStrCat("CMAKE_", this->Language, stdsStrings[offset], "_", type,
229 if (target->Target->GetMakefile()->GetDefinition(option_flag)) {
234 return std::string{};
237 bool GetNewRequiredStandard(cmMakefile* makefile,
238 std::string const& targetName,
239 const std::string& feature,
240 cmValue currentLangStandardValue,
241 std::string& newRequiredStandard,
242 std::string* error) const
244 if (currentLangStandardValue) {
245 newRequiredStandard = *currentLangStandardValue;
247 newRequiredStandard.clear();
250 auto needed = this->HighestStandardNeeded(makefile, feature);
252 cmValue existingStandard = currentLangStandardValue;
253 if (!existingStandard) {
254 cmValue defaultStandard = makefile->GetDefinition(
255 cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
256 if (cmNonempty(defaultStandard)) {
257 existingStandard = defaultStandard;
261 auto existingLevelIter = cm::cend(this->Levels);
262 if (existingStandard) {
264 std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
265 ParseStd(*existingStandard));
266 if (existingLevelIter == cm::cend(this->Levels)) {
267 const std::string e =
268 cmStrCat("The ", this->Language, "_STANDARD property on target \"",
269 targetName, "\" contained an invalid value: \"",
270 *existingStandard, "\".");
274 makefile->IssueMessage(MessageType::FATAL_ERROR, e);
280 if (needed.index != -1) {
281 // Ensure the C++ language level is high enough to support
282 // the needed C++ features.
283 if (existingLevelIter == cm::cend(this->Levels) ||
284 existingLevelIter < this->Levels.begin() + needed.index) {
285 newRequiredStandard = this->LevelsAsStrings[needed.index];
292 bool HaveStandardAvailable(cmMakefile* makefile,
293 cmGeneratorTarget const* target,
294 std::string const& config,
295 std::string const& feature) const
297 cmValue defaultStandard = makefile->GetDefinition(
298 cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
299 if (!defaultStandard) {
300 makefile->IssueMessage(
301 MessageType::INTERNAL_ERROR,
302 cmStrCat("CMAKE_", this->Language,
303 "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support "
304 "not fully configured for this compiler."));
305 // Return true so the caller does not try to lookup the default standard.
308 // convert defaultStandard to an integer
309 if (std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
310 ParseStd(*defaultStandard)) == cm::cend(this->Levels)) {
311 const std::string e = cmStrCat("The CMAKE_", this->Language,
312 "_STANDARD_DEFAULT variable contains an "
314 *defaultStandard, "\".");
315 makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
319 cmValue existingStandard =
320 target->GetLanguageStandard(this->Language, config);
321 if (!existingStandard) {
322 existingStandard = defaultStandard;
325 auto existingLevelIter =
326 std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
327 ParseStd(*existingStandard));
328 if (existingLevelIter == cm::cend(this->Levels)) {
329 const std::string e =
330 cmStrCat("The ", this->Language, "_STANDARD property on target \"",
331 target->GetName(), "\" contained an invalid value: \"",
332 *existingStandard, "\".");
333 makefile->IssueMessage(MessageType::FATAL_ERROR, e);
337 auto needed = this->HighestStandardNeeded(makefile, feature);
339 return (needed.index == -1) ||
340 (this->Levels.begin() + needed.index) <= existingLevelIter;
343 StandardNeeded HighestStandardNeeded(cmMakefile* makefile,
344 std::string const& feature) const
346 std::string prefix = cmStrCat("CMAKE_", this->Language);
347 StandardNeeded maxLevel = { -1, -1 };
348 for (size_t i = 0; i < this->Levels.size(); ++i) {
349 if (cmValue prop = makefile->GetDefinition(
350 cmStrCat(prefix, this->LevelsAsStrings[i], "_COMPILE_FEATURES"))) {
351 std::vector<std::string> props = cmExpandedList(*prop);
352 if (cm::contains(props, feature)) {
353 maxLevel = { static_cast<int>(i), this->Levels[i] };
360 bool IsLaterStandard(int lhs, int rhs) const
363 std::find(cm::cbegin(this->Levels), cm::cend(this->Levels), rhs);
365 return std::find(rhsIt, cm::cend(this->Levels), lhs) !=
366 cm::cend(this->Levels);
369 std::string Language;
370 std::vector<int> Levels;
371 std::vector<std::string> LevelsAsStrings;
374 std::unordered_map<std::string, StandardLevelComputer>
375 StandardComputerMapping = {
377 StandardLevelComputer{
378 "C", std::vector<int>{ 90, 99, 11, 17, 23 },
379 std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
381 StandardLevelComputer{
382 "CXX", std::vector<int>{ 98, 11, 14, 17, 20, 23 },
383 std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } },
385 StandardLevelComputer{
386 "CUDA", std::vector<int>{ 03, 11, 14, 17, 20, 23 },
387 std::vector<std::string>{ "03", "11", "14", "17", "20", "23" } } },
389 StandardLevelComputer{
390 "OBJC", std::vector<int>{ 90, 99, 11, 17, 23 },
391 std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
393 StandardLevelComputer{
394 "OBJCXX", std::vector<int>{ 98, 11, 14, 17, 20, 23 },
395 std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } },
397 StandardLevelComputer{
398 "HIP", std::vector<int>{ 98, 11, 14, 17, 20, 23 },
399 std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } }
403 std::string cmStandardLevelResolver::GetCompileOptionDef(
404 cmGeneratorTarget const* target, std::string const& lang,
405 std::string const& config) const
407 const auto& mapping = StandardComputerMapping.find(lang);
408 if (mapping == cm::cend(StandardComputerMapping)) {
409 return std::string{};
412 return mapping->second.GetCompileOptionDef(this->Makefile, target, config);
415 bool cmStandardLevelResolver::AddRequiredTargetFeature(
416 cmTarget* target, const std::string& feature, std::string* error) const
418 if (cmGeneratorExpression::Find(feature) != std::string::npos) {
419 target->AppendProperty("COMPILE_FEATURES", feature);
424 if (!this->CheckCompileFeaturesAvailable(target->GetName(), feature, lang,
429 target->AppendProperty("COMPILE_FEATURES", feature);
431 // FIXME: Add a policy to avoid updating the <LANG>_STANDARD target
432 // property due to COMPILE_FEATURES. The language standard selection
433 // should be done purely at generate time based on whatever the project
434 // code put in these properties explicitly. That is mostly true now,
435 // but for compatibility we need to continue updating the property here.
436 std::string newRequiredStandard;
437 bool newRequired = this->GetNewRequiredStandard(
438 target->GetName(), feature,
439 target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard,
441 if (!newRequiredStandard.empty()) {
442 target->SetProperty(cmStrCat(lang, "_STANDARD"), newRequiredStandard);
447 bool cmStandardLevelResolver::CheckCompileFeaturesAvailable(
448 const std::string& targetName, const std::string& feature, std::string& lang,
449 std::string* error) const
451 if (!this->CompileFeatureKnown(targetName, feature, lang, error)) {
455 if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
459 cmValue features = this->CompileFeaturesAvailable(lang, error);
464 std::vector<std::string> availableFeatures = cmExpandedList(features);
465 if (!cm::contains(availableFeatures, feature)) {
466 std::ostringstream e;
467 e << "The compiler feature \"" << feature << "\" is not known to " << lang
469 << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
471 << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
477 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
485 bool cmStandardLevelResolver::CompileFeatureKnown(
486 const std::string& targetName, const std::string& feature, std::string& lang,
487 std::string* error) const
489 assert(cmGeneratorExpression::Find(feature) == std::string::npos);
492 std::find_if(cm::cbegin(C_FEATURES) + 1, cm::cend(C_FEATURES),
493 cmStrCmp(feature)) != cm::cend(C_FEATURES);
499 std::find_if(cm::cbegin(CXX_FEATURES) + 1, cm::cend(CXX_FEATURES),
500 cmStrCmp(feature)) != cm::cend(CXX_FEATURES);
506 std::find_if(cm::cbegin(CUDA_FEATURES) + 1, cm::cend(CUDA_FEATURES),
507 cmStrCmp(feature)) != cm::cend(CUDA_FEATURES);
513 std::find_if(cm::cbegin(HIP_FEATURES) + 1, cm::cend(HIP_FEATURES),
514 cmStrCmp(feature)) != cm::cend(HIP_FEATURES);
519 std::ostringstream e;
525 e << " unknown feature \"" << feature
528 << targetName << "\".";
532 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
537 cmValue cmStandardLevelResolver::CompileFeaturesAvailable(
538 const std::string& lang, std::string* error) const
540 if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
541 std::ostringstream e;
547 e << " use features from non-enabled language " << lang;
551 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
556 cmValue featuresKnown =
557 this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILE_FEATURES");
559 if (!cmNonempty(featuresKnown)) {
560 std::ostringstream e;
566 e << " known features for " << lang << " compiler\n\""
567 << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
569 << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
575 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
579 return featuresKnown;
582 bool cmStandardLevelResolver::GetNewRequiredStandard(
583 const std::string& targetName, const std::string& feature,
584 cmValue currentLangStandardValue, std::string& newRequiredStandard,
585 std::string* error) const
588 if (!this->CheckCompileFeaturesAvailable(targetName, feature, lang, error)) {
592 auto mapping = StandardComputerMapping.find(lang);
593 if (mapping != cm::cend(StandardComputerMapping)) {
594 return mapping->second.GetNewRequiredStandard(
595 this->Makefile, targetName, feature, currentLangStandardValue,
596 newRequiredStandard, error);
601 bool cmStandardLevelResolver::HaveStandardAvailable(
602 cmGeneratorTarget const* target, std::string const& lang,
603 std::string const& config, const std::string& feature) const
605 auto mapping = StandardComputerMapping.find(lang);
606 if (mapping != cm::cend(StandardComputerMapping)) {
607 return mapping->second.HaveStandardAvailable(this->Makefile, target,
613 bool cmStandardLevelResolver::IsLaterStandard(std::string const& lang,
614 std::string const& lhs,
615 std::string const& rhs) const
617 auto mapping = StandardComputerMapping.find(lang);
618 if (mapping != cm::cend(StandardComputerMapping)) {
619 return mapping->second.IsLaterStandard(std::stoi(lhs), std::stoi(rhs));