Imported Upstream version 3.25.0
[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 "cmListFileCache.h"
22 #include "cmMakefile.h"
23 #include "cmMessageType.h"
24 #include "cmPolicies.h"
25 #include "cmStringAlgorithms.h"
26 #include "cmTarget.h"
27 #include "cmValue.h"
28 #include "cmake.h"
29
30 namespace {
31
32 #define FEATURE_STRING(F) , #F
33 const char* const C_FEATURES[] = { nullptr FOR_EACH_C_FEATURE(
34   FEATURE_STRING) };
35
36 const char* const CXX_FEATURES[] = { nullptr FOR_EACH_CXX_FEATURE(
37   FEATURE_STRING) };
38
39 const char* const CUDA_FEATURES[] = { nullptr FOR_EACH_CUDA_FEATURE(
40   FEATURE_STRING) };
41
42 const char* const HIP_FEATURES[] = { nullptr FOR_EACH_HIP_FEATURE(
43   FEATURE_STRING) };
44 #undef FEATURE_STRING
45
46 struct StandardNeeded
47 {
48   int index;
49   int value;
50 };
51
52 int ParseStd(std::string const& level)
53 {
54   try {
55     return std::stoi(level);
56   } catch (std::invalid_argument&) {
57     // Fall through to use an invalid value.
58   }
59   return -1;
60 }
61
62 struct StandardLevelComputer
63 {
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))
69   {
70     assert(this->Levels.size() == this->LevelsAsStrings.size());
71   }
72
73   std::string GetCompileOptionDef(cmMakefile* makefile,
74                                   cmGeneratorTarget const* target,
75                                   std::string const& config) const
76   {
77
78     const auto& stds = this->Levels;
79     const auto& stdsStrings = this->LevelsAsStrings;
80
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
85       return std::string{};
86     }
87
88     cmPolicies::PolicyStatus const cmp0128{ makefile->GetPolicyStatus(
89       cmPolicies::CMP0128) };
90     bool const defaultExt{ cmIsOn(*makefile->GetDefinition(
91       cmStrCat("CMAKE_", this->Language, "_EXTENSIONS_DEFAULT"))) };
92     bool ext = true;
93
94     if (cmp0128 == cmPolicies::NEW) {
95       ext = defaultExt;
96     }
97
98     if (cmValue extPropValue = target->GetLanguageExtensions(this->Language)) {
99       ext = cmIsOn(*extPropValue);
100     }
101
102     std::string const type{ ext ? "EXTENSION" : "STANDARD" };
103
104     cmValue standardProp = target->GetLanguageStandard(this->Language, config);
105     if (!standardProp) {
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,
110                           "_COMPILE_OPTION");
111         }
112       } else {
113         if (cmp0128 == cmPolicies::WARN &&
114             makefile->PolicyOptionalWarningEnabled(
115               "CMAKE_POLICY_WARNING_CMP0128") &&
116             ext != defaultExt) {
117           const char* state{};
118           if (ext) {
119             if (!makefile->GetDefinition(cmStrCat(
120                   "CMAKE_", this->Language, "_EXTENSION_COMPILE_OPTION"))) {
121               state = "enabled";
122             }
123           } else {
124             state = "disabled";
125           }
126           if (state) {
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 ",
132                        state, "."));
133           }
134         }
135
136         if (ext) {
137           return cmStrCat("CMAKE_", this->Language,
138                           "_EXTENSION_COMPILE_OPTION");
139         }
140       }
141       return std::string{};
142     }
143
144     if (target->GetLanguageStandardRequired(this->Language)) {
145       std::string option_flag = cmStrCat(
146         "CMAKE_", this->Language, *standardProp, "_", type, "_COMPILE_OPTION");
147
148       cmValue opt = target->Target->GetMakefile()->GetDefinition(option_flag);
149       if (!opt) {
150         std::ostringstream e;
151         e << "Target \"" << target->GetName()
152           << "\" requires the language "
153              "dialect \""
154           << this->Language << *standardProp << "\" "
155           << (ext ? "(with compiler extensions)" : "")
156           << ". But the current compiler \""
157           << makefile->GetSafeDefinition("CMAKE_" + this->Language +
158                                          "_COMPILER_ID")
159           << "\" does not support this, or "
160              "CMake does not know the flags to enable it.";
161
162         makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
163       }
164       return option_flag;
165     }
166
167     // If the request matches the compiler's defaults we don't need to add
168     // anything.
169     if (*standardProp == *defaultStd && ext == defaultExt) {
170       if (cmp0128 == cmPolicies::NEW) {
171         return std::string{};
172       }
173
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."));
183       }
184     }
185
186     std::string standardStr(*standardProp);
187     if (this->Language == "CUDA" && standardStr == "98") {
188       standardStr = "03";
189     }
190
191     auto stdIt =
192       std::find(cm::cbegin(stds), cm::cend(stds), ParseStd(standardStr));
193     if (stdIt == cm::cend(stds)) {
194       std::string e =
195         cmStrCat(this->Language, "_STANDARD is set to invalid value '",
196                  standardStr, "'");
197       makefile->GetCMakeInstance()->IssueMessage(MessageType::FATAL_ERROR, e,
198                                                  target->GetBacktrace());
199       return std::string{};
200     }
201
202     auto defaultStdIt =
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 '",
207                                *defaultStd, "'");
208       makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
209       return std::string{};
210     }
211
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,
219                       "_COMPILE_OPTION");
220     }
221
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,
229                  "_COMPILE_OPTION");
230       if (target->Target->GetMakefile()->GetDefinition(option_flag)) {
231         return option_flag;
232       }
233     }
234
235     return std::string{};
236   }
237
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
244   {
245     if (currentLangStandardValue) {
246       newRequiredStandard = *currentLangStandardValue;
247     } else {
248       newRequiredStandard.clear();
249     }
250
251     auto needed = this->HighestStandardNeeded(makefile, feature);
252
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;
259       }
260     }
261
262     auto existingLevelIter = cm::cend(this->Levels);
263     if (existingStandard) {
264       existingLevelIter =
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, "\".");
272         if (error) {
273           *error = e;
274         } else {
275           makefile->IssueMessage(MessageType::FATAL_ERROR, e);
276         }
277         return false;
278       }
279     }
280
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];
287       }
288     }
289
290     return true;
291   }
292
293   bool HaveStandardAvailable(cmMakefile* makefile,
294                              cmGeneratorTarget const* target,
295                              std::string const& config,
296                              std::string const& feature) const
297   {
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.
307       return true;
308     }
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 "
314                                      "invalid value: \"",
315                                      *defaultStandard, "\".");
316       makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
317       return false;
318     }
319
320     cmValue existingStandard =
321       target->GetLanguageStandard(this->Language, config);
322     if (!existingStandard) {
323       existingStandard = defaultStandard;
324     }
325
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);
335       return false;
336     }
337
338     auto needed = this->HighestStandardNeeded(makefile, feature);
339
340     return (needed.index == -1) ||
341       (this->Levels.begin() + needed.index) <= existingLevelIter;
342   }
343
344   StandardNeeded HighestStandardNeeded(cmMakefile* makefile,
345                                        std::string const& feature) const
346   {
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] };
355         }
356       }
357     }
358     return maxLevel;
359   }
360
361   bool IsLaterStandard(int lhs, int rhs) const
362   {
363     auto rhsIt =
364       std::find(cm::cbegin(this->Levels), cm::cend(this->Levels), rhs);
365
366     return std::find(rhsIt, cm::cend(this->Levels), lhs) !=
367       cm::cend(this->Levels);
368   }
369
370   std::string Language;
371   std::vector<int> Levels;
372   std::vector<std::string> LevelsAsStrings;
373 };
374
375 std::unordered_map<std::string, StandardLevelComputer>
376   StandardComputerMapping = {
377     { "C",
378       StandardLevelComputer{
379         "C", std::vector<int>{ 90, 99, 11, 17, 23 },
380         std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
381     { "CXX",
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" } } },
386     { "CUDA",
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" } } },
391     { "OBJC",
392       StandardLevelComputer{
393         "OBJC", std::vector<int>{ 90, 99, 11, 17, 23 },
394         std::vector<std::string>{ "90", "99", "11", "17", "23" } } },
395     { "OBJCXX",
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" } } },
400     { "HIP",
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" } } }
405   };
406 }
407
408 std::string cmStandardLevelResolver::GetCompileOptionDef(
409   cmGeneratorTarget const* target, std::string const& lang,
410   std::string const& config) const
411 {
412   const auto& mapping = StandardComputerMapping.find(lang);
413   if (mapping == cm::cend(StandardComputerMapping)) {
414     return std::string{};
415   }
416
417   return mapping->second.GetCompileOptionDef(this->Makefile, target, config);
418 }
419
420 bool cmStandardLevelResolver::AddRequiredTargetFeature(
421   cmTarget* target, const std::string& feature, std::string* error) const
422 {
423   if (cmGeneratorExpression::Find(feature) != std::string::npos) {
424     target->AppendProperty("COMPILE_FEATURES", feature,
425                            this->Makefile->GetBacktrace());
426     return true;
427   }
428
429   std::string lang;
430   if (!this->CheckCompileFeaturesAvailable(target->GetName(), feature, lang,
431                                            error)) {
432     return false;
433   }
434
435   target->AppendProperty("COMPILE_FEATURES", feature,
436                          this->Makefile->GetBacktrace());
437
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,
447     error);
448   if (!newRequiredStandard.empty()) {
449     target->SetProperty(cmStrCat(lang, "_STANDARD"), newRequiredStandard);
450   }
451   return newRequired;
452 }
453
454 bool cmStandardLevelResolver::CheckCompileFeaturesAvailable(
455   const std::string& targetName, const std::string& feature, std::string& lang,
456   std::string* error) const
457 {
458   if (!this->CompileFeatureKnown(targetName, feature, lang, error)) {
459     return false;
460   }
461
462   if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
463     return true;
464   }
465
466   cmValue features = this->CompileFeaturesAvailable(lang, error);
467   if (!features) {
468     return false;
469   }
470
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
475       << " compiler\n\""
476       << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
477       << "\"\nversion "
478       << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
479                                            "_COMPILER_VERSION")
480       << ".";
481     if (error) {
482       *error = e.str();
483     } else {
484       this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
485     }
486     return false;
487   }
488
489   return true;
490 }
491
492 bool cmStandardLevelResolver::CompileFeatureKnown(
493   const std::string& targetName, const std::string& feature, std::string& lang,
494   std::string* error) const
495 {
496   assert(cmGeneratorExpression::Find(feature) == std::string::npos);
497
498   bool isCFeature =
499     std::find_if(cm::cbegin(C_FEATURES) + 1, cm::cend(C_FEATURES),
500                  cmStrCmp(feature)) != cm::cend(C_FEATURES);
501   if (isCFeature) {
502     lang = "C";
503     return true;
504   }
505   bool isCxxFeature =
506     std::find_if(cm::cbegin(CXX_FEATURES) + 1, cm::cend(CXX_FEATURES),
507                  cmStrCmp(feature)) != cm::cend(CXX_FEATURES);
508   if (isCxxFeature) {
509     lang = "CXX";
510     return true;
511   }
512   bool isCudaFeature =
513     std::find_if(cm::cbegin(CUDA_FEATURES) + 1, cm::cend(CUDA_FEATURES),
514                  cmStrCmp(feature)) != cm::cend(CUDA_FEATURES);
515   if (isCudaFeature) {
516     lang = "CUDA";
517     return true;
518   }
519   bool isHIPFeature =
520     std::find_if(cm::cbegin(HIP_FEATURES) + 1, cm::cend(HIP_FEATURES),
521                  cmStrCmp(feature)) != cm::cend(HIP_FEATURES);
522   if (isHIPFeature) {
523     lang = "HIP";
524     return true;
525   }
526   std::ostringstream e;
527   if (error) {
528     e << "specified";
529   } else {
530     e << "Specified";
531   }
532   e << " unknown feature \"" << feature
533     << "\" for "
534        "target \""
535     << targetName << "\".";
536   if (error) {
537     *error = e.str();
538   } else {
539     this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
540   }
541   return false;
542 }
543
544 cmValue cmStandardLevelResolver::CompileFeaturesAvailable(
545   const std::string& lang, std::string* error) const
546 {
547   if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
548     std::ostringstream e;
549     if (error) {
550       e << "cannot";
551     } else {
552       e << "Cannot";
553     }
554     e << " use features from non-enabled language " << lang;
555     if (error) {
556       *error = e.str();
557     } else {
558       this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
559     }
560     return nullptr;
561   }
562
563   cmValue featuresKnown =
564     this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILE_FEATURES");
565
566   if (!cmNonempty(featuresKnown)) {
567     std::ostringstream e;
568     if (error) {
569       e << "no";
570     } else {
571       e << "No";
572     }
573     e << " known features for " << lang << " compiler\n\""
574       << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
575       << "\"\nversion "
576       << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
577                                            "_COMPILER_VERSION")
578       << ".";
579     if (error) {
580       *error = e.str();
581     } else {
582       this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
583     }
584     return nullptr;
585   }
586   return featuresKnown;
587 }
588
589 bool cmStandardLevelResolver::GetNewRequiredStandard(
590   const std::string& targetName, const std::string& feature,
591   cmValue currentLangStandardValue, std::string& newRequiredStandard,
592   std::string* error) const
593 {
594   std::string lang;
595   if (!this->CheckCompileFeaturesAvailable(targetName, feature, lang, error)) {
596     return false;
597   }
598
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);
604   }
605   return false;
606 }
607
608 bool cmStandardLevelResolver::HaveStandardAvailable(
609   cmGeneratorTarget const* target, std::string const& lang,
610   std::string const& config, const std::string& feature) const
611 {
612   auto mapping = StandardComputerMapping.find(lang);
613   if (mapping != cm::cend(StandardComputerMapping)) {
614     return mapping->second.HaveStandardAvailable(this->Makefile, target,
615                                                  config, feature);
616   }
617   return false;
618 }
619
620 bool cmStandardLevelResolver::IsLaterStandard(std::string const& lang,
621                                               std::string const& lhs,
622                                               std::string const& rhs) const
623 {
624   auto mapping = StandardComputerMapping.find(lang);
625   if (mapping != cm::cend(StandardComputerMapping)) {
626     return mapping->second.IsLaterStandard(std::stoi(lhs), std::stoi(rhs));
627   }
628   return false;
629 }