785f3566798bf4f208552d0461ae814e76b1e2ed
[platform/upstream/cmake.git] / Source / cmStandardLevelResolver.cxx
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
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 {
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))
68   {
69     assert(this->Levels.size() == this->LevelsAsStrings.size());
70   }
71
72   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 the current compiler \""
156           << makefile->GetSafeDefinition("CMAKE_" + this->Language +
157                                          "_COMPILER_ID")
158           << "\" does not support this, or "
159              "CMake does not know the flags to enable it.";
160
161         makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
162       }
163       return option_flag;
164     }
165
166     // If the request matches the compiler's defaults we don't need to add
167     // anything.
168     if (*standardProp == *defaultStd && ext == defaultExt) {
169       if (cmp0128 == cmPolicies::NEW) {
170         return std::string{};
171       }
172
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."));
182       }
183     }
184
185     std::string standardStr(*standardProp);
186     if (this->Language == "CUDA" && standardStr == "98") {
187       standardStr = "03";
188     }
189
190     auto stdIt =
191       std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(standardStr));
192     if (stdIt == cm::cend(stds)) {
193       std::string e =
194         cmStrCat(this->Language, "_STANDARD is set to invalid value '",
195                  standardStr, "'");
196       makefile->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e,
197                                                  target->GetBacktrace());
198       return std::string{};
199     }
200
201     auto defaultStdIt =
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 '",
206                                *defaultStd, "'");
207       makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
208       return std::string{};
209     }
210
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,
218                       "_COMPILE_OPTION");
219     }
220
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,
228                  "_COMPILE_OPTION");
229       if (target->Target->GetMakefile()->GetDefinition(option_flag)) {
230         return option_flag;
231       }
232     }
233
234     return std::string{};
235   }
236
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
243   {
244     if (currentLangStandardValue) {
245       newRequiredStandard = *currentLangStandardValue;
246     } else {
247       newRequiredStandard.clear();
248     }
249
250     auto needed = this->HighestStandardNeeded(makefile, feature);
251
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;
258       }
259     }
260
261     auto existingLevelIter = cm::cend(this->Levels);
262     if (existingStandard) {
263       existingLevelIter =
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, "\".");
271         if (error) {
272           *error = e;
273         } else {
274           makefile->IssueMessage(MessageType::FATAL_ERROR, e);
275         }
276         return false;
277       }
278     }
279
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];
286       }
287     }
288
289     return true;
290   }
291
292   bool HaveStandardAvailable(cmMakefile* makefile,
293                              cmGeneratorTarget const* target,
294                              std::string const& config,
295                              std::string const& feature) const
296   {
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.
306       return true;
307     }
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 "
313                                      "invalid value: \"",
314                                      *defaultStandard, "\".");
315       makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
316       return false;
317     }
318
319     cmValue existingStandard =
320       target->GetLanguageStandard(this->Language, config);
321     if (!existingStandard) {
322       existingStandard = defaultStandard;
323     }
324
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);
334       return false;
335     }
336
337     auto needed = this->HighestStandardNeeded(makefile, feature);
338
339     return (needed.index == -1) ||
340       (this->Levels.begin() + needed.index) <= existingLevelIter;
341   }
342
343   StandardNeeded HighestStandardNeeded(cmMakefile* makefile,
344                                        std::string const& feature) const
345   {
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] };
354         }
355       }
356     }
357     return maxLevel;
358   }
359
360   bool IsLaterStandard(int lhs, int rhs) const
361   {
362     auto rhsIt =
363       std::find(cm::cbegin(this->Levels), cm::cend(this->Levels), rhs);
364
365     return std::find(rhsIt, cm::cend(this->Levels), lhs) !=
366       cm::cend(this->Levels);
367   }
368
369   std::string Language;
370   std::vector<int> Levels;
371   std::vector<std::string> LevelsAsStrings;
372 };
373
374 std::unordered_map<std::string, StandardLevelComputer>
375   StandardComputerMapping = {
376     { "C",
377       StandardLevelComputer{
378         "C", std::vector<int>{ 90, 99, 11, 17, 23 },
379         std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
380     { "CXX",
381       StandardLevelComputer{
382         "CXX", std::vector<int>{ 98, 11, 14, 17, 20, 23 },
383         std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } },
384     { "CUDA",
385       StandardLevelComputer{
386         "CUDA", std::vector<int>{ 03, 11, 14, 17, 20, 23 },
387         std::vector<std::string>{ "03", "11", "14", "17", "20", "23" } } },
388     { "OBJC",
389       StandardLevelComputer{
390         "OBJC", std::vector<int>{ 90, 99, 11, 17, 23 },
391         std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
392     { "OBJCXX",
393       StandardLevelComputer{
394         "OBJCXX", std::vector<int>{ 98, 11, 14, 17, 20, 23 },
395         std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } },
396     { "HIP",
397       StandardLevelComputer{
398         "HIP", std::vector<int>{ 98, 11, 14, 17, 20, 23 },
399         std::vector<std::string>{ "98", "11", "14", "17", "20", "23" } } }
400   };
401 }
402
403 std::string cmStandardLevelResolver::GetCompileOptionDef(
404   cmGeneratorTarget const* target, std::string const& lang,
405   std::string const& config) const
406 {
407   const auto& mapping = StandardComputerMapping.find(lang);
408   if (mapping == cm::cend(StandardComputerMapping)) {
409     return std::string{};
410   }
411
412   return mapping->second.GetCompileOptionDef(this->Makefile, target, config);
413 }
414
415 bool cmStandardLevelResolver::AddRequiredTargetFeature(
416   cmTarget* target, const std::string& feature, std::string* error) const
417 {
418   if (cmGeneratorExpression::Find(feature) != std::string::npos) {
419     target->AppendProperty("COMPILE_FEATURES", feature);
420     return true;
421   }
422
423   std::string lang;
424   if (!this->CheckCompileFeaturesAvailable(target->GetName(), feature, lang,
425                                            error)) {
426     return false;
427   }
428
429   target->AppendProperty("COMPILE_FEATURES", feature);
430
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,
440     error);
441   if (!newRequiredStandard.empty()) {
442     target->SetProperty(cmStrCat(lang, "_STANDARD"), newRequiredStandard);
443   }
444   return newRequired;
445 }
446
447 bool cmStandardLevelResolver::CheckCompileFeaturesAvailable(
448   const std::string& targetName, const std::string& feature, std::string& lang,
449   std::string* error) const
450 {
451   if (!this->CompileFeatureKnown(targetName, feature, lang, error)) {
452     return false;
453   }
454
455   if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
456     return true;
457   }
458
459   cmValue features = this->CompileFeaturesAvailable(lang, error);
460   if (!features) {
461     return false;
462   }
463
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
468       << " compiler\n\""
469       << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
470       << "\"\nversion "
471       << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
472                                            "_COMPILER_VERSION")
473       << ".";
474     if (error) {
475       *error = e.str();
476     } else {
477       this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
478     }
479     return false;
480   }
481
482   return true;
483 }
484
485 bool cmStandardLevelResolver::CompileFeatureKnown(
486   const std::string& targetName, const std::string& feature, std::string& lang,
487   std::string* error) const
488 {
489   assert(cmGeneratorExpression::Find(feature) == std::string::npos);
490
491   bool isCFeature =
492     std::find_if(cm::cbegin(C_FEATURES) + 1, cm::cend(C_FEATURES),
493                  cmStrCmp(feature)) != cm::cend(C_FEATURES);
494   if (isCFeature) {
495     lang = "C";
496     return true;
497   }
498   bool isCxxFeature =
499     std::find_if(cm::cbegin(CXX_FEATURES) + 1, cm::cend(CXX_FEATURES),
500                  cmStrCmp(feature)) != cm::cend(CXX_FEATURES);
501   if (isCxxFeature) {
502     lang = "CXX";
503     return true;
504   }
505   bool isCudaFeature =
506     std::find_if(cm::cbegin(CUDA_FEATURES) + 1, cm::cend(CUDA_FEATURES),
507                  cmStrCmp(feature)) != cm::cend(CUDA_FEATURES);
508   if (isCudaFeature) {
509     lang = "CUDA";
510     return true;
511   }
512   bool isHIPFeature =
513     std::find_if(cm::cbegin(HIP_FEATURES) + 1, cm::cend(HIP_FEATURES),
514                  cmStrCmp(feature)) != cm::cend(HIP_FEATURES);
515   if (isHIPFeature) {
516     lang = "HIP";
517     return true;
518   }
519   std::ostringstream e;
520   if (error) {
521     e << "specified";
522   } else {
523     e << "Specified";
524   }
525   e << " unknown feature \"" << feature
526     << "\" for "
527        "target \""
528     << targetName << "\".";
529   if (error) {
530     *error = e.str();
531   } else {
532     this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
533   }
534   return false;
535 }
536
537 cmValue cmStandardLevelResolver::CompileFeaturesAvailable(
538   const std::string& lang, std::string* error) const
539 {
540   if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
541     std::ostringstream e;
542     if (error) {
543       e << "cannot";
544     } else {
545       e << "Cannot";
546     }
547     e << " use features from non-enabled language " << lang;
548     if (error) {
549       *error = e.str();
550     } else {
551       this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
552     }
553     return nullptr;
554   }
555
556   cmValue featuresKnown =
557     this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILE_FEATURES");
558
559   if (!cmNonempty(featuresKnown)) {
560     std::ostringstream e;
561     if (error) {
562       e << "no";
563     } else {
564       e << "No";
565     }
566     e << " known features for " << lang << " compiler\n\""
567       << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
568       << "\"\nversion "
569       << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
570                                            "_COMPILER_VERSION")
571       << ".";
572     if (error) {
573       *error = e.str();
574     } else {
575       this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
576     }
577     return nullptr;
578   }
579   return featuresKnown;
580 }
581
582 bool cmStandardLevelResolver::GetNewRequiredStandard(
583   const std::string& targetName, const std::string& feature,
584   cmValue currentLangStandardValue, std::string& newRequiredStandard,
585   std::string* error) const
586 {
587   std::string lang;
588   if (!this->CheckCompileFeaturesAvailable(targetName, feature, lang, error)) {
589     return false;
590   }
591
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);
597   }
598   return false;
599 }
600
601 bool cmStandardLevelResolver::HaveStandardAvailable(
602   cmGeneratorTarget const* target, std::string const& lang,
603   std::string const& config, const std::string& feature) const
604 {
605   auto mapping = StandardComputerMapping.find(lang);
606   if (mapping != cm::cend(StandardComputerMapping)) {
607     return mapping->second.HaveStandardAvailable(this->Makefile, target,
608                                                  config, feature);
609   }
610   return false;
611 }
612
613 bool cmStandardLevelResolver::IsLaterStandard(std::string const& lang,
614                                               std::string const& lhs,
615                                               std::string const& rhs) const
616 {
617   auto mapping = StandardComputerMapping.find(lang);
618   if (mapping != cm::cend(StandardComputerMapping)) {
619     return mapping->second.IsLaterStandard(std::stoi(lhs), std::stoi(rhs));
620   }
621   return false;
622 }