1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3
4 #include "cmStandardLevelResolver.h"
5
6 #include <algorithm>
7 #include <cassert>
8 #include <cstddef>
9 #include <sstream>
10 #include <stdexcept>
11 #include <unordered_map>
12 #include <utility>
13 #include <vector>
14
15 #include <cm/iterator>
16 #include <cmext/algorithm>
17
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"
25 #include "cmTarget.h"
26 #include "cmValue.h"
27 #include "cmake.h"
28
29 namespace {
30
31 #define FEATURE_STRING(F) , #F
32 const char* const C_FEATURES[] = { nullptr FOR_EACH_C_FEATURE(
33 FEATURE_STRING) };
34
35 const char* const CXX_FEATURES[] = { nullptr FOR_EACH_CXX_FEATURE(
36 FEATURE_STRING) };
37
38 const char* const CUDA_FEATURES[] = { nullptr FOR_EACH_CUDA_FEATURE(
39 FEATURE_STRING) };
40
41 const char* const HIP_FEATURES[] = { nullptr FOR_EACH_HIP_FEATURE(
42 FEATURE_STRING) };
43 #undef FEATURE_STRING
44
45 struct StandardNeeded
46 {
47 int index;
48 int value;
49 };
50
ParseStd(std::string const & level)51 int ParseStd(std::string const& level)
52 {
53 try {
54 return std::stoi(level);
55 } catch (std::invalid_argument&) {
56 // Fall through to use an invalid value.
57 }
58 return -1;
59 }
60
61 struct StandardLevelComputer
62 {
StandardLevelComputer__anond88e6be60111::StandardLevelComputer63 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))
68 {
69 assert(this->Levels.size() == this->LevelsAsStrings.size());
70 }
71
GetCompileOptionDef__anond88e6be60111::StandardLevelComputer72 std::string GetCompileOptionDef(cmMakefile* makefile,
73 cmGeneratorTarget const* target,
74 std::string const& config) const
75 {
76
77 const auto& stds = this->Levels;
78 const auto& stdsStrings = this->LevelsAsStrings;
79
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
84 return std::string{};
85 }
86
87 cmPolicies::PolicyStatus const cmp0128{ makefile->GetPolicyStatus(
88 cmPolicies::CMP0128) };
89 bool const defaultExt{ cmIsOn(*makefile->GetDefinition(
90 cmStrCat("CMAKE_", this->Language, "_EXTENSIONS_DEFAULT"))) };
91 bool ext = true;
92
93 if (cmp0128 == cmPolicies::NEW) {
94 ext = defaultExt;
95 }
96
97 if (cmValue extPropValue = target->GetLanguageExtensions(this->Language)) {
98 ext = cmIsOn(*extPropValue);
99 }
100
101 std::string const type{ ext ? "EXTENSION" : "STANDARD" };
102
103 cmValue standardProp = target->GetLanguageStandard(this->Language, config);
104 if (!standardProp) {
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,
109 "_COMPILE_OPTION");
110 }
111 } else {
112 if (cmp0128 == cmPolicies::WARN &&
113 makefile->PolicyOptionalWarningEnabled(
114 "CMAKE_POLICY_WARNING_CMP0128") &&
115 ext != defaultExt) {
116 const char* state{};
117 if (ext) {
118 if (!makefile->GetDefinition(cmStrCat(
119 "CMAKE_", this->Language, "_EXTENSION_COMPILE_OPTION"))) {
120 state = "enabled";
121 }
122 } else {
123 state = "disabled";
124 }
125 if (state) {
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 ",
131 state, "."));
132 }
133 }
134
135 if (ext) {
136 return cmStrCat("CMAKE_", this->Language,
137 "_EXTENSION_COMPILE_OPTION");
138 }
139 }
140 return std::string{};
141 }
142
143 if (target->GetLanguageStandardRequired(this->Language)) {
144 std::string option_flag = cmStrCat(
145 "CMAKE_", this->Language, *standardProp, "_", type, "_COMPILE_OPTION");
146
147 cmValue opt = target->Target->GetMakefile()->GetDefinition(option_flag);
148 if (!opt) {
149 std::ostringstream e;
150 e << "Target \"" << target->GetName()
151 << "\" requires the language "
152 "dialect \""
153 << this->Language << *standardProp << "\" "
154 << (ext ? "(with compiler extensions)" : "")
155 << ", but CMake "
156 "does not know the compile flags to use to enable it.";
157 makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
158 }
159 return option_flag;
160 }
161
162 // If the request matches the compiler's defaults we don't need to add
163 // anything.
164 if (*standardProp == *defaultStd && ext == defaultExt) {
165 if (cmp0128 == cmPolicies::NEW) {
166 return std::string{};
167 }
168
169 if (cmp0128 == cmPolicies::WARN &&
170 makefile->PolicyOptionalWarningEnabled(
171 "CMAKE_POLICY_WARNING_CMP0128")) {
172 makefile->IssueMessage(
173 MessageType::AUTHOR_WARNING,
174 cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0128),
175 "\nFor compatibility with older versions of CMake, "
176 "unnecessary flags for language standard or compiler "
177 "extensions may be added."));
178 }
179 }
180
181 std::string standardStr(*standardProp);
182 if (this->Language == "CUDA" && standardStr == "98") {
183 standardStr = "03";
184 }
185
186 auto stdIt =
187 std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(standardStr));
188 if (stdIt == cm::cend(stds)) {
189 std::string e =
190 cmStrCat(this->Language, "_STANDARD is set to invalid value '",
191 standardStr, "'");
192 makefile->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e,
193 target->GetBacktrace());
194 return std::string{};
195 }
196
197 auto defaultStdIt =
198 std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(*defaultStd));
199 if (defaultStdIt == cm::cend(stds)) {
200 std::string e = cmStrCat("CMAKE_", this->Language,
201 "_STANDARD_DEFAULT is set to invalid value '",
202 *defaultStd, "'");
203 makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
204 return std::string{};
205 }
206
207 // If the standard requested is older than the compiler's default or the
208 // extension mode doesn't match then we need to use a flag.
209 if (stdIt < defaultStdIt ||
210 (cmp0128 == cmPolicies::NEW && ext != defaultExt)) {
211 auto offset = std::distance(cm::cbegin(stds), stdIt);
212 return cmStrCat("CMAKE_", this->Language, stdsStrings[offset], "_", type,
213 "_COMPILE_OPTION");
214 }
215
216 // The compiler's default is at least as new as the requested standard,
217 // and the requested standard is not required. Decay to the newest
218 // standard for which a flag is defined.
219 for (; defaultStdIt < stdIt; --stdIt) {
220 auto offset = std::distance(cm::cbegin(stds), stdIt);
221 std::string option_flag =
222 cmStrCat("CMAKE_", this->Language, stdsStrings[offset], "_", type,
223 "_COMPILE_OPTION");
224 if (target->Target->GetMakefile()->GetDefinition(option_flag)) {
225 return option_flag;
226 }
227 }
228
229 return std::string{};
230 }
231
GetNewRequiredStandard__anond88e6be60111::StandardLevelComputer232 bool GetNewRequiredStandard(cmMakefile* makefile,
233 std::string const& targetName,
234 const std::string& feature,
235 cmValue currentLangStandardValue,
236 std::string& newRequiredStandard,
237 std::string* error) const
238 {
239 if (currentLangStandardValue) {
240 newRequiredStandard = *currentLangStandardValue;
241 } else {
242 newRequiredStandard.clear();
243 }
244
245 auto needed = this->HighestStandardNeeded(makefile, feature);
246
247 cmValue existingStandard = currentLangStandardValue;
248 if (!existingStandard) {
249 cmValue defaultStandard = makefile->GetDefinition(
250 cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
251 if (cmNonempty(defaultStandard)) {
252 existingStandard = defaultStandard;
253 }
254 }
255
256 auto existingLevelIter = cm::cend(this->Levels);
257 if (existingStandard) {
258 existingLevelIter =
259 std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
260 ParseStd(*existingStandard));
261 if (existingLevelIter == cm::cend(this->Levels)) {
262 const std::string e =
263 cmStrCat("The ", this->Language, "_STANDARD property on target \"",
264 targetName, "\" contained an invalid value: \"",
265 *existingStandard, "\".");
266 if (error) {
267 *error = e;
268 } else {
269 makefile->IssueMessage(MessageType::FATAL_ERROR, e);
270 }
271 return false;
272 }
273 }
274
275 if (needed.index != -1) {
276 // Ensure the C++ language level is high enough to support
277 // the needed C++ features.
278 if (existingLevelIter == cm::cend(this->Levels) ||
279 existingLevelIter < this->Levels.begin() + needed.index) {
280 newRequiredStandard = this->LevelsAsStrings[needed.index];
281 }
282 }
283
284 return true;
285 }
286
HaveStandardAvailable__anond88e6be60111::StandardLevelComputer287 bool HaveStandardAvailable(cmMakefile* makefile,
288 cmGeneratorTarget const* target,
289 std::string const& config,
290 std::string const& feature) const
291 {
292 cmValue defaultStandard = makefile->GetDefinition(
293 cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
294 if (!defaultStandard) {
295 makefile->IssueMessage(
296 MessageType::INTERNAL_ERROR,
297 cmStrCat("CMAKE_", this->Language,
298 "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support "
299 "not fully configured for this compiler."));
300 // Return true so the caller does not try to lookup the default standard.
301 return true;
302 }
303 // convert defaultStandard to an integer
304 if (std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
305 ParseStd(*defaultStandard)) == cm::cend(this->Levels)) {
306 const std::string e = cmStrCat("The CMAKE_", this->Language,
307 "_STANDARD_DEFAULT variable contains an "
308 "invalid value: \"",
309 *defaultStandard, "\".");
310 makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
311 return false;
312 }
313
314 cmValue existingStandard =
315 target->GetLanguageStandard(this->Language, config);
316 if (!existingStandard) {
317 existingStandard = defaultStandard;
318 }
319
320 auto existingLevelIter =
321 std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
322 ParseStd(*existingStandard));
323 if (existingLevelIter == cm::cend(this->Levels)) {
324 const std::string e =
325 cmStrCat("The ", this->Language, "_STANDARD property on target \"",
326 target->GetName(), "\" contained an invalid value: \"",
327 *existingStandard, "\".");
328 makefile->IssueMessage(MessageType::FATAL_ERROR, e);
329 return false;
330 }
331
332 auto needed = this->HighestStandardNeeded(makefile, feature);
333
334 return (needed.index == -1) ||
335 (this->Levels.begin() + needed.index) <= existingLevelIter;
336 }
337
HighestStandardNeeded__anond88e6be60111::StandardLevelComputer338 StandardNeeded HighestStandardNeeded(cmMakefile* makefile,
339 std::string const& feature) const
340 {
341 std::string prefix = cmStrCat("CMAKE_", this->Language);
342 StandardNeeded maxLevel = { -1, -1 };
343 for (size_t i = 0; i < this->Levels.size(); ++i) {
344 if (cmValue prop = makefile->GetDefinition(
345 cmStrCat(prefix, this->LevelsAsStrings[i], "_COMPILE_FEATURES"))) {
346 std::vector<std::string> props = cmExpandedList(*prop);
347 if (cm::contains(props, feature)) {
348 maxLevel = { static_cast<int>(i), this->Levels[i] };
349 }
350 }
351 }
352 return maxLevel;
353 }
354
IsLaterStandard__anond88e6be60111::StandardLevelComputer355 bool IsLaterStandard(int lhs, int rhs) const
356 {
357 auto rhsIt =
358 std::find(cm::cbegin(this->Levels), cm::cend(this->Levels), rhs);
359
360 return std::find(rhsIt, cm::cend(this->Levels), lhs) !=
361 cm::cend(this->Levels);
362 }
363
364 std::string Language;
365 std::vector<int> Levels;
366 std::vector<std::string> LevelsAsStrings;
367 };
368
369 std::unordered_map<std::string, StandardLevelComputer>
370 StandardComputerMapping = {
371 { "C",
372 StandardLevelComputer{
373 "C", std::vector<int>{ 90, 99, 11, 17, 23 },
374 std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
375 { "CXX",
376 StandardLevelComputer{
377 "CXX", std::vector<int>{ 98, 11, 14, 17, 20, 23 },
378 std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } },
379 { "CUDA",
380 StandardLevelComputer{
381 "CUDA", std::vector<int>{ 03, 11, 14, 17, 20, 23 },
382 std::vector<std::string>{ "03", "11", "14", "17", "20", "23" } } },
383 { "OBJC",
384 StandardLevelComputer{
385 "OBJC", std::vector<int>{ 90, 99, 11, 17, 23 },
386 std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
387 { "OBJCXX",
388 StandardLevelComputer{
389 "OBJCXX", std::vector<int>{ 98, 11, 14, 17, 20, 23 },
390 std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } },
391 { "HIP",
392 StandardLevelComputer{
393 "HIP", std::vector<int>{ 98, 11, 14, 17, 20, 23 },
394 std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } }
395 };
396 }
397
GetCompileOptionDef(cmGeneratorTarget const * target,std::string const & lang,std::string const & config) const398 std::string cmStandardLevelResolver::GetCompileOptionDef(
399 cmGeneratorTarget const* target, std::string const& lang,
400 std::string const& config) const
401 {
402 const auto& mapping = StandardComputerMapping.find(lang);
403 if (mapping == cm::cend(StandardComputerMapping)) {
404 return std::string{};
405 }
406
407 return mapping->second.GetCompileOptionDef(this->Makefile, target, config);
408 }
409
AddRequiredTargetFeature(cmTarget * target,const std::string & feature,std::string * error) const410 bool cmStandardLevelResolver::AddRequiredTargetFeature(
411 cmTarget* target, const std::string& feature, std::string* error) const
412 {
413 if (cmGeneratorExpression::Find(feature) != std::string::npos) {
414 target->AppendProperty("COMPILE_FEATURES", feature);
415 return true;
416 }
417
418 std::string lang;
419 if (!this->CheckCompileFeaturesAvailable(target->GetName(), feature, lang,
420 error)) {
421 return false;
422 }
423
424 target->AppendProperty("COMPILE_FEATURES", feature);
425
426 // FIXME: Add a policy to avoid updating the <LANG>_STANDARD target
427 // property due to COMPILE_FEATURES. The language standard selection
428 // should be done purely at generate time based on whatever the project
429 // code put in these properties explicitly. That is mostly true now,
430 // but for compatibility we need to continue updating the property here.
431 std::string newRequiredStandard;
432 bool newRequired = this->GetNewRequiredStandard(
433 target->GetName(), feature,
434 target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard,
435 error);
436 if (!newRequiredStandard.empty()) {
437 target->SetProperty(cmStrCat(lang, "_STANDARD"), newRequiredStandard);
438 }
439 return newRequired;
440 }
441
CheckCompileFeaturesAvailable(const std::string & targetName,const std::string & feature,std::string & lang,std::string * error) const442 bool cmStandardLevelResolver::CheckCompileFeaturesAvailable(
443 const std::string& targetName, const std::string& feature, std::string& lang,
444 std::string* error) const
445 {
446 if (!this->CompileFeatureKnown(targetName, feature, lang, error)) {
447 return false;
448 }
449
450 if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
451 return true;
452 }
453
454 cmValue features = this->CompileFeaturesAvailable(lang, error);
455 if (!features) {
456 return false;
457 }
458
459 std::vector<std::string> availableFeatures = cmExpandedList(features);
460 if (!cm::contains(availableFeatures, feature)) {
461 std::ostringstream e;
462 e << "The compiler feature \"" << feature << "\" is not known to " << lang
463 << " compiler\n\""
464 << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
465 << "\"\nversion "
466 << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
467 "_COMPILER_VERSION")
468 << ".";
469 if (error) {
470 *error = e.str();
471 } else {
472 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
473 }
474 return false;
475 }
476
477 return true;
478 }
479
CompileFeatureKnown(const std::string & targetName,const std::string & feature,std::string & lang,std::string * error) const480 bool cmStandardLevelResolver::CompileFeatureKnown(
481 const std::string& targetName, const std::string& feature, std::string& lang,
482 std::string* error) const
483 {
484 assert(cmGeneratorExpression::Find(feature) == std::string::npos);
485
486 bool isCFeature =
487 std::find_if(cm::cbegin(C_FEATURES) + 1, cm::cend(C_FEATURES),
488 cmStrCmp(feature)) != cm::cend(C_FEATURES);
489 if (isCFeature) {
490 lang = "C";
491 return true;
492 }
493 bool isCxxFeature =
494 std::find_if(cm::cbegin(CXX_FEATURES) + 1, cm::cend(CXX_FEATURES),
495 cmStrCmp(feature)) != cm::cend(CXX_FEATURES);
496 if (isCxxFeature) {
497 lang = "CXX";
498 return true;
499 }
500 bool isCudaFeature =
501 std::find_if(cm::cbegin(CUDA_FEATURES) + 1, cm::cend(CUDA_FEATURES),
502 cmStrCmp(feature)) != cm::cend(CUDA_FEATURES);
503 if (isCudaFeature) {
504 lang = "CUDA";
505 return true;
506 }
507 bool isHIPFeature =
508 std::find_if(cm::cbegin(HIP_FEATURES) + 1, cm::cend(HIP_FEATURES),
509 cmStrCmp(feature)) != cm::cend(HIP_FEATURES);
510 if (isHIPFeature) {
511 lang = "HIP";
512 return true;
513 }
514 std::ostringstream e;
515 if (error) {
516 e << "specified";
517 } else {
518 e << "Specified";
519 }
520 e << " unknown feature \"" << feature
521 << "\" for "
522 "target \""
523 << targetName << "\".";
524 if (error) {
525 *error = e.str();
526 } else {
527 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
528 }
529 return false;
530 }
531
CompileFeaturesAvailable(const std::string & lang,std::string * error) const532 cmValue cmStandardLevelResolver::CompileFeaturesAvailable(
533 const std::string& lang, std::string* error) const
534 {
535 if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
536 std::ostringstream e;
537 if (error) {
538 e << "cannot";
539 } else {
540 e << "Cannot";
541 }
542 e << " use features from non-enabled language " << lang;
543 if (error) {
544 *error = e.str();
545 } else {
546 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
547 }
548 return nullptr;
549 }
550
551 cmValue featuresKnown =
552 this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILE_FEATURES");
553
554 if (!cmNonempty(featuresKnown)) {
555 std::ostringstream e;
556 if (error) {
557 e << "no";
558 } else {
559 e << "No";
560 }
561 e << " known features for " << lang << " compiler\n\""
562 << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
563 << "\"\nversion "
564 << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
565 "_COMPILER_VERSION")
566 << ".";
567 if (error) {
568 *error = e.str();
569 } else {
570 this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
571 }
572 return nullptr;
573 }
574 return featuresKnown;
575 }
576
GetNewRequiredStandard(const std::string & targetName,const std::string & feature,cmValue currentLangStandardValue,std::string & newRequiredStandard,std::string * error) const577 bool cmStandardLevelResolver::GetNewRequiredStandard(
578 const std::string& targetName, const std::string& feature,
579 cmValue currentLangStandardValue, std::string& newRequiredStandard,
580 std::string* error) const
581 {
582 std::string lang;
583 if (!this->CheckCompileFeaturesAvailable(targetName, feature, lang, error)) {
584 return false;
585 }
586
587 auto mapping = StandardComputerMapping.find(lang);
588 if (mapping != cm::cend(StandardComputerMapping)) {
589 return mapping->second.GetNewRequiredStandard(
590 this->Makefile, targetName, feature, currentLangStandardValue,
591 newRequiredStandard, error);
592 }
593 return false;
594 }
595
HaveStandardAvailable(cmGeneratorTarget const * target,std::string const & lang,std::string const & config,const std::string & feature) const596 bool cmStandardLevelResolver::HaveStandardAvailable(
597 cmGeneratorTarget const* target, std::string const& lang,
598 std::string const& config, const std::string& feature) const
599 {
600 auto mapping = StandardComputerMapping.find(lang);
601 if (mapping != cm::cend(StandardComputerMapping)) {
602 return mapping->second.HaveStandardAvailable(this->Makefile, target,
603 config, feature);
604 }
605 return false;
606 }
607
IsLaterStandard(std::string const & lang,std::string const & lhs,std::string const & rhs) const608 bool cmStandardLevelResolver::IsLaterStandard(std::string const& lang,
609 std::string const& lhs,
610 std::string const& rhs) const
611 {
612 auto mapping = StandardComputerMapping.find(lang);
613 if (mapping != cm::cend(StandardComputerMapping)) {
614 return mapping->second.IsLaterStandard(std::stoi(lhs), std::stoi(rhs));
615 }
616 return false;
617 }
618