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