b737c1f29efbe229b5394ca9cea45c088b120021
[platform/upstream/cmake.git] / Source / cmCMakePresetsGraph.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 #include "cmCMakePresetsGraph.h"
4
5 #include <algorithm>
6 #include <cassert>
7 #include <cstdlib>
8 #include <functional>
9 #include <iostream>
10 #include <iterator>
11 #include <utility>
12
13 #include <cm/string_view>
14
15 #include "cmsys/RegularExpression.hxx"
16
17 #include "cmCMakePresetsGraphInternal.h"
18 #include "cmStringAlgorithms.h"
19 #include "cmSystemTools.h"
20
21 #define CHECK_EXPAND(out, field, expanders, version)                          \
22   do {                                                                        \
23     switch (ExpandMacros(field, expanders, version)) {                        \
24       case ExpandMacroResult::Error:                                          \
25         return false;                                                         \
26       case ExpandMacroResult::Ignore:                                         \
27         out.reset();                                                          \
28         return true;                                                          \
29       case ExpandMacroResult::Ok:                                             \
30         break;                                                                \
31     }                                                                         \
32   } while (false)
33
34 namespace {
35 enum class CycleStatus
36 {
37   Unvisited,
38   InProgress,
39   Verified,
40 };
41
42 using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
43 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
44 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
45 using TestPreset = cmCMakePresetsGraph::TestPreset;
46 using ExpandMacroResult = cmCMakePresetsGraphInternal::ExpandMacroResult;
47 using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander;
48
49 void InheritString(std::string& child, const std::string& parent)
50 {
51   if (child.empty()) {
52     child = parent;
53   }
54 }
55
56 template <typename T>
57 void InheritOptionalValue(cm::optional<T>& child,
58                           const cm::optional<T>& parent)
59 {
60   if (!child) {
61     child = parent;
62   }
63 }
64
65 template <typename T>
66 void InheritVector(std::vector<T>& child, const std::vector<T>& parent)
67 {
68   if (child.empty()) {
69     child = parent;
70   }
71 }
72
73 /**
74  * Check preset inheritance for cycles (using a DAG check algorithm) while
75  * also bubbling up fields through the inheritance hierarchy, then verify
76  * that each preset has the required fields, either directly or through
77  * inheritance.
78  */
79 template <class T>
80 ReadFileResult VisitPreset(
81   T& preset,
82   std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
83   std::map<std::string, CycleStatus> cycleStatus,
84   const cmCMakePresetsGraph& graph)
85 {
86   switch (cycleStatus[preset.Name]) {
87     case CycleStatus::InProgress:
88       return ReadFileResult::CYCLIC_PRESET_INHERITANCE;
89     case CycleStatus::Verified:
90       return ReadFileResult::READ_OK;
91     default:
92       break;
93   }
94
95   cycleStatus[preset.Name] = CycleStatus::InProgress;
96
97   if (preset.Environment.count("") != 0) {
98     return ReadFileResult::INVALID_PRESET;
99   }
100
101   CHECK_OK(preset.VisitPresetBeforeInherit());
102
103   for (auto const& i : preset.Inherits) {
104     auto parent = presets.find(i);
105     if (parent == presets.end()) {
106       return ReadFileResult::INVALID_PRESET;
107     }
108
109     auto& parentPreset = parent->second.Unexpanded;
110     if (!preset.OriginFile->ReachableFiles.count(parentPreset.OriginFile)) {
111       return ReadFileResult::INHERITED_PRESET_UNREACHABLE_FROM_FILE;
112     }
113
114     auto result = VisitPreset(parentPreset, presets, cycleStatus, graph);
115     if (result != ReadFileResult::READ_OK) {
116       return result;
117     }
118
119     CHECK_OK(preset.VisitPresetInherit(parentPreset));
120
121     for (auto const& v : parentPreset.Environment) {
122       preset.Environment.insert(v);
123     }
124
125     if (!preset.ConditionEvaluator) {
126       preset.ConditionEvaluator = parentPreset.ConditionEvaluator;
127     }
128   }
129
130   if (preset.ConditionEvaluator && preset.ConditionEvaluator->IsNull()) {
131     preset.ConditionEvaluator.reset();
132   }
133
134   CHECK_OK(preset.VisitPresetAfterInherit(graph.GetVersion(preset)));
135
136   cycleStatus[preset.Name] = CycleStatus::Verified;
137   return ReadFileResult::READ_OK;
138 }
139
140 template <class T>
141 ReadFileResult ComputePresetInheritance(
142   std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
143   const cmCMakePresetsGraph& graph)
144 {
145   std::map<std::string, CycleStatus> cycleStatus;
146   for (auto const& it : presets) {
147     cycleStatus[it.first] = CycleStatus::Unvisited;
148   }
149
150   for (auto& it : presets) {
151     auto& preset = it.second.Unexpanded;
152     auto result = VisitPreset<T>(preset, presets, cycleStatus, graph);
153     if (result != ReadFileResult::READ_OK) {
154       return result;
155     }
156   }
157
158   return ReadFileResult::READ_OK;
159 }
160
161 constexpr const char* ValidPrefixes[] = {
162   "",
163   "env",
164   "penv",
165   "vendor",
166 };
167
168 bool PrefixesValidMacroNamespace(const std::string& str)
169 {
170   return std::any_of(
171     std::begin(ValidPrefixes), std::end(ValidPrefixes),
172     [&str](const char* prefix) -> bool { return cmHasPrefix(prefix, str); });
173 }
174
175 bool IsValidMacroNamespace(const std::string& str)
176 {
177   return std::any_of(
178     std::begin(ValidPrefixes), std::end(ValidPrefixes),
179     [&str](const char* prefix) -> bool { return str == prefix; });
180 }
181
182 ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status,
183                            const std::vector<MacroExpander>& macroExpanders,
184                            int version);
185 ExpandMacroResult ExpandMacros(
186   std::string& out, const std::vector<MacroExpander>& macroExpanders,
187   int version);
188 ExpandMacroResult ExpandMacro(std::string& out,
189                               const std::string& macroNamespace,
190                               const std::string& macroName,
191                               const std::vector<MacroExpander>& macroExpanders,
192                               int version);
193
194 bool ExpandMacros(const cmCMakePresetsGraph& graph,
195                   const ConfigurePreset& preset,
196                   cm::optional<ConfigurePreset>& out,
197                   const std::vector<MacroExpander>& macroExpanders)
198 {
199   std::string binaryDir = preset.BinaryDir;
200   CHECK_EXPAND(out, binaryDir, macroExpanders, graph.GetVersion(preset));
201
202   if (!binaryDir.empty()) {
203     if (!cmSystemTools::FileIsFullPath(binaryDir)) {
204       binaryDir = cmStrCat(graph.SourceDir, '/', binaryDir);
205     }
206     out->BinaryDir = cmSystemTools::CollapseFullPath(binaryDir);
207     cmSystemTools::ConvertToUnixSlashes(out->BinaryDir);
208   }
209
210   if (!preset.InstallDir.empty()) {
211     std::string installDir = preset.InstallDir;
212     CHECK_EXPAND(out, installDir, macroExpanders, graph.GetVersion(preset));
213
214     if (!cmSystemTools::FileIsFullPath(installDir)) {
215       installDir = cmStrCat(graph.SourceDir, '/', installDir);
216     }
217     out->InstallDir = cmSystemTools::CollapseFullPath(installDir);
218     cmSystemTools::ConvertToUnixSlashes(out->InstallDir);
219   }
220
221   if (!preset.ToolchainFile.empty()) {
222     std::string toolchain = preset.ToolchainFile;
223     CHECK_EXPAND(out, toolchain, macroExpanders, graph.GetVersion(preset));
224     out->ToolchainFile = toolchain;
225   }
226
227   for (auto& variable : out->CacheVariables) {
228     if (variable.second) {
229       CHECK_EXPAND(out, variable.second->Value, macroExpanders,
230                    graph.GetVersion(preset));
231     }
232   }
233
234   return true;
235 }
236
237 bool ExpandMacros(const cmCMakePresetsGraph& graph, const BuildPreset& preset,
238                   cm::optional<BuildPreset>& out,
239                   const std::vector<MacroExpander>& macroExpanders)
240 {
241   for (auto& target : out->Targets) {
242     CHECK_EXPAND(out, target, macroExpanders, graph.GetVersion(preset));
243   }
244
245   for (auto& nativeToolOption : out->NativeToolOptions) {
246     CHECK_EXPAND(out, nativeToolOption, macroExpanders,
247                  graph.GetVersion(preset));
248   }
249
250   return true;
251 }
252
253 bool ExpandMacros(const cmCMakePresetsGraph& graph, const TestPreset& preset,
254                   cm::optional<TestPreset>& out,
255                   const std::vector<MacroExpander>& macroExpanders)
256 {
257   for (auto& overwrite : out->OverwriteConfigurationFile) {
258     CHECK_EXPAND(out, overwrite, macroExpanders, graph.GetVersion(preset));
259   }
260
261   if (out->Output) {
262     CHECK_EXPAND(out, out->Output->OutputLogFile, macroExpanders,
263                  graph.GetVersion(preset));
264   }
265
266   if (out->Filter) {
267     if (out->Filter->Include) {
268       CHECK_EXPAND(out, out->Filter->Include->Name, macroExpanders,
269                    graph.GetVersion(preset));
270       CHECK_EXPAND(out, out->Filter->Include->Label, macroExpanders,
271                    graph.GetVersion(preset));
272
273       if (out->Filter->Include->Index) {
274         CHECK_EXPAND(out, out->Filter->Include->Index->IndexFile,
275                      macroExpanders, graph.GetVersion(preset));
276       }
277     }
278
279     if (out->Filter->Exclude) {
280       CHECK_EXPAND(out, out->Filter->Exclude->Name, macroExpanders,
281                    graph.GetVersion(preset));
282       CHECK_EXPAND(out, out->Filter->Exclude->Label, macroExpanders,
283                    graph.GetVersion(preset));
284
285       if (out->Filter->Exclude->Fixtures) {
286         CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Any, macroExpanders,
287                      graph.GetVersion(preset));
288         CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Setup,
289                      macroExpanders, graph.GetVersion(preset));
290         CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Cleanup,
291                      macroExpanders, graph.GetVersion(preset));
292       }
293     }
294   }
295
296   if (out->Execution) {
297     CHECK_EXPAND(out, out->Execution->ResourceSpecFile, macroExpanders,
298                  graph.GetVersion(preset));
299   }
300
301   return true;
302 }
303
304 template <class T>
305 bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
306                   cm::optional<T>& out)
307 {
308   out.emplace(preset);
309
310   std::map<std::string, CycleStatus> envCycles;
311   for (auto const& v : out->Environment) {
312     envCycles[v.first] = CycleStatus::Unvisited;
313   }
314
315   std::vector<MacroExpander> macroExpanders;
316
317   MacroExpander defaultMacroExpander =
318     [&graph, &preset](const std::string& macroNamespace,
319                       const std::string& macroName, std::string& macroOut,
320                       int version) -> ExpandMacroResult {
321     if (macroNamespace.empty()) {
322       if (macroName == "sourceDir") {
323         macroOut += graph.SourceDir;
324         return ExpandMacroResult::Ok;
325       }
326       if (macroName == "sourceParentDir") {
327         macroOut += cmSystemTools::GetParentDirectory(graph.SourceDir);
328         return ExpandMacroResult::Ok;
329       }
330       if (macroName == "sourceDirName") {
331         macroOut += cmSystemTools::GetFilenameName(graph.SourceDir);
332         return ExpandMacroResult::Ok;
333       }
334       if (macroName == "presetName") {
335         macroOut += preset.Name;
336         return ExpandMacroResult::Ok;
337       }
338       if (macroName == "generator") {
339         // Generator only makes sense if preset is not hidden.
340         if (!preset.Hidden) {
341           macroOut += graph.GetGeneratorForPreset(preset.Name);
342         }
343         return ExpandMacroResult::Ok;
344       }
345       if (macroName == "dollar") {
346         macroOut += '$';
347         return ExpandMacroResult::Ok;
348       }
349       if (macroName == "hostSystemName") {
350         if (version < 3) {
351           return ExpandMacroResult::Error;
352         }
353         macroOut += cmSystemTools::GetSystemName();
354         return ExpandMacroResult::Ok;
355       }
356       if (macroName == "fileDir") {
357         if (version < 4) {
358           return ExpandMacroResult::Error;
359         }
360         macroOut +=
361           cmSystemTools::GetParentDirectory(preset.OriginFile->Filename);
362         return ExpandMacroResult::Ok;
363       }
364       if (macroName == "pathListSep") {
365         if (version < 5) {
366           return ExpandMacroResult::Error;
367         }
368         macroOut += cmSystemTools::GetSystemPathlistSeparator();
369         return ExpandMacroResult::Ok;
370       }
371     }
372
373     return ExpandMacroResult::Ignore;
374   };
375
376   MacroExpander environmentMacroExpander =
377     [&macroExpanders, &out, &envCycles](
378       const std::string& macroNamespace, const std::string& macroName,
379       std::string& result, int version) -> ExpandMacroResult {
380     if (macroNamespace == "env" && !macroName.empty() && out) {
381       auto v = out->Environment.find(macroName);
382       if (v != out->Environment.end() && v->second) {
383         auto e =
384           VisitEnv(*v->second, envCycles[macroName], macroExpanders, version);
385         if (e != ExpandMacroResult::Ok) {
386           return e;
387         }
388         result += *v->second;
389         return ExpandMacroResult::Ok;
390       }
391     }
392
393     if (macroNamespace == "env" || macroNamespace == "penv") {
394       if (macroName.empty()) {
395         return ExpandMacroResult::Error;
396       }
397       const char* value = std::getenv(macroName.c_str());
398       if (value) {
399         result += value;
400       }
401       return ExpandMacroResult::Ok;
402     }
403
404     return ExpandMacroResult::Ignore;
405   };
406
407   macroExpanders.push_back(defaultMacroExpander);
408   macroExpanders.push_back(environmentMacroExpander);
409
410   for (auto& v : out->Environment) {
411     if (v.second) {
412       switch (VisitEnv(*v.second, envCycles[v.first], macroExpanders,
413                        graph.GetVersion(preset))) {
414         case ExpandMacroResult::Error:
415           return false;
416         case ExpandMacroResult::Ignore:
417           out.reset();
418           return true;
419         case ExpandMacroResult::Ok:
420           break;
421       }
422     }
423   }
424
425   if (preset.ConditionEvaluator) {
426     cm::optional<bool> result;
427     if (!preset.ConditionEvaluator->Evaluate(
428           macroExpanders, graph.GetVersion(preset), result)) {
429       return false;
430     }
431     if (!result) {
432       out.reset();
433       return true;
434     }
435     out->ConditionResult = *result;
436   }
437
438   return ExpandMacros(graph, preset, out, macroExpanders);
439 }
440
441 ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status,
442                            const std::vector<MacroExpander>& macroExpanders,
443                            int version)
444 {
445   if (status == CycleStatus::Verified) {
446     return ExpandMacroResult::Ok;
447   }
448   if (status == CycleStatus::InProgress) {
449     return ExpandMacroResult::Error;
450   }
451
452   status = CycleStatus::InProgress;
453   auto e = ExpandMacros(value, macroExpanders, version);
454   if (e != ExpandMacroResult::Ok) {
455     return e;
456   }
457   status = CycleStatus::Verified;
458   return ExpandMacroResult::Ok;
459 }
460
461 ExpandMacroResult ExpandMacros(
462   std::string& out, const std::vector<MacroExpander>& macroExpanders,
463   int version)
464 {
465   std::string result;
466   std::string macroNamespace;
467   std::string macroName;
468
469   enum class State
470   {
471     Default,
472     MacroNamespace,
473     MacroName,
474   } state = State::Default;
475
476   for (auto c : out) {
477     switch (state) {
478       case State::Default:
479         if (c == '$') {
480           state = State::MacroNamespace;
481         } else {
482           result += c;
483         }
484         break;
485
486       case State::MacroNamespace:
487         if (c == '{') {
488           if (IsValidMacroNamespace(macroNamespace)) {
489             state = State::MacroName;
490           } else {
491             result += '$';
492             result += macroNamespace;
493             result += '{';
494             macroNamespace.clear();
495             state = State::Default;
496           }
497         } else {
498           macroNamespace += c;
499           if (!PrefixesValidMacroNamespace(macroNamespace)) {
500             result += '$';
501             result += macroNamespace;
502             macroNamespace.clear();
503             state = State::Default;
504           }
505         }
506         break;
507
508       case State::MacroName:
509         if (c == '}') {
510           auto e = ExpandMacro(result, macroNamespace, macroName,
511                                macroExpanders, version);
512           if (e != ExpandMacroResult::Ok) {
513             return e;
514           }
515           macroNamespace.clear();
516           macroName.clear();
517           state = State::Default;
518         } else {
519           macroName += c;
520         }
521         break;
522     }
523   }
524
525   switch (state) {
526     case State::Default:
527       break;
528     case State::MacroNamespace:
529       result += '$';
530       result += macroNamespace;
531       break;
532     case State::MacroName:
533       return ExpandMacroResult::Error;
534   }
535
536   out = std::move(result);
537   return ExpandMacroResult::Ok;
538 }
539
540 ExpandMacroResult ExpandMacro(std::string& out,
541                               const std::string& macroNamespace,
542                               const std::string& macroName,
543                               const std::vector<MacroExpander>& macroExpanders,
544                               int version)
545 {
546   for (auto const& macroExpander : macroExpanders) {
547     auto result = macroExpander(macroNamespace, macroName, out, version);
548     if (result != ExpandMacroResult::Ignore) {
549       return result;
550     }
551   }
552
553   if (macroNamespace == "vendor") {
554     return ExpandMacroResult::Ignore;
555   }
556
557   return ExpandMacroResult::Error;
558 }
559 }
560
561 bool cmCMakePresetsGraphInternal::EqualsCondition::Evaluate(
562   const std::vector<MacroExpander>& expanders, int version,
563   cm::optional<bool>& out) const
564 {
565   std::string lhs = this->Lhs;
566   CHECK_EXPAND(out, lhs, expanders, version);
567
568   std::string rhs = this->Rhs;
569   CHECK_EXPAND(out, rhs, expanders, version);
570
571   out = (lhs == rhs);
572   return true;
573 }
574
575 bool cmCMakePresetsGraphInternal::InListCondition::Evaluate(
576   const std::vector<MacroExpander>& expanders, int version,
577   cm::optional<bool>& out) const
578 {
579   std::string str = this->String;
580   CHECK_EXPAND(out, str, expanders, version);
581
582   for (auto item : this->List) {
583     CHECK_EXPAND(out, item, expanders, version);
584     if (str == item) {
585       out = true;
586       return true;
587     }
588   }
589
590   out = false;
591   return true;
592 }
593
594 bool cmCMakePresetsGraphInternal::MatchesCondition::Evaluate(
595   const std::vector<MacroExpander>& expanders, int version,
596   cm::optional<bool>& out) const
597 {
598   std::string str = this->String;
599   CHECK_EXPAND(out, str, expanders, version);
600   std::string regexStr = this->Regex;
601   CHECK_EXPAND(out, regexStr, expanders, version);
602
603   cmsys::RegularExpression regex;
604   if (!regex.compile(regexStr)) {
605     return false;
606   }
607
608   out = regex.find(str);
609   return true;
610 }
611
612 bool cmCMakePresetsGraphInternal::AnyAllOfCondition::Evaluate(
613   const std::vector<MacroExpander>& expanders, int version,
614   cm::optional<bool>& out) const
615 {
616   for (auto const& condition : this->Conditions) {
617     cm::optional<bool> result;
618     if (!condition->Evaluate(expanders, version, result)) {
619       out.reset();
620       return false;
621     }
622
623     if (!result) {
624       out.reset();
625       return true;
626     }
627
628     if (result == this->StopValue) {
629       out = result;
630       return true;
631     }
632   }
633
634   out = !this->StopValue;
635   return true;
636 }
637
638 bool cmCMakePresetsGraphInternal::NotCondition::Evaluate(
639   const std::vector<MacroExpander>& expanders, int version,
640   cm::optional<bool>& out) const
641 {
642   out.reset();
643   if (!this->SubCondition->Evaluate(expanders, version, out)) {
644     out.reset();
645     return false;
646   }
647   if (out) {
648     *out = !*out;
649   }
650   return true;
651 }
652
653 cmCMakePresetsGraph::ReadFileResult
654 cmCMakePresetsGraph::ConfigurePreset::VisitPresetInherit(
655   const cmCMakePresetsGraph::Preset& parentPreset)
656 {
657   auto& preset = *this;
658   const ConfigurePreset& parent =
659     static_cast<const ConfigurePreset&>(parentPreset);
660   InheritString(preset.Generator, parent.Generator);
661   InheritString(preset.Architecture, parent.Architecture);
662   InheritString(preset.Toolset, parent.Toolset);
663   if (!preset.ArchitectureStrategy) {
664     preset.ArchitectureStrategy = parent.ArchitectureStrategy;
665   }
666   if (!preset.ToolsetStrategy) {
667     preset.ToolsetStrategy = parent.ToolsetStrategy;
668   }
669   InheritString(preset.BinaryDir, parent.BinaryDir);
670   InheritString(preset.InstallDir, parent.InstallDir);
671   InheritString(preset.ToolchainFile, parent.ToolchainFile);
672   InheritOptionalValue(preset.WarnDev, parent.WarnDev);
673   InheritOptionalValue(preset.ErrorDev, parent.ErrorDev);
674   InheritOptionalValue(preset.WarnDeprecated, parent.WarnDeprecated);
675   InheritOptionalValue(preset.ErrorDeprecated, parent.ErrorDeprecated);
676   InheritOptionalValue(preset.WarnUninitialized, parent.WarnUninitialized);
677   InheritOptionalValue(preset.WarnUnusedCli, parent.WarnUnusedCli);
678   InheritOptionalValue(preset.WarnSystemVars, parent.WarnSystemVars);
679
680   for (auto const& v : parent.CacheVariables) {
681     preset.CacheVariables.insert(v);
682   }
683
684   return ReadFileResult::READ_OK;
685 }
686
687 cmCMakePresetsGraph::ReadFileResult
688 cmCMakePresetsGraph::ConfigurePreset::VisitPresetBeforeInherit()
689 {
690   auto& preset = *this;
691   if (preset.Environment.count("") != 0) {
692     return ReadFileResult::INVALID_PRESET;
693   }
694
695   return ReadFileResult::READ_OK;
696 }
697
698 cmCMakePresetsGraph::ReadFileResult
699 cmCMakePresetsGraph::ConfigurePreset::VisitPresetAfterInherit(int version)
700 {
701   auto& preset = *this;
702   if (!preset.Hidden) {
703     if (version < 3) {
704       if (preset.Generator.empty()) {
705         return ReadFileResult::INVALID_PRESET;
706       }
707       if (preset.BinaryDir.empty()) {
708         return ReadFileResult::INVALID_PRESET;
709       }
710     }
711
712     if (preset.WarnDev == false && preset.ErrorDev == true) {
713       return ReadFileResult::INVALID_PRESET;
714     }
715     if (preset.WarnDeprecated == false && preset.ErrorDeprecated == true) {
716       return ReadFileResult::INVALID_PRESET;
717     }
718     if (preset.CacheVariables.count("") != 0) {
719       return ReadFileResult::INVALID_PRESET;
720     }
721   }
722
723   return ReadFileResult::READ_OK;
724 }
725
726 cmCMakePresetsGraph::ReadFileResult
727 cmCMakePresetsGraph::BuildPreset::VisitPresetInherit(
728   const cmCMakePresetsGraph::Preset& parentPreset)
729 {
730   auto& preset = *this;
731   const BuildPreset& parent = static_cast<const BuildPreset&>(parentPreset);
732
733   InheritString(preset.ConfigurePreset, parent.ConfigurePreset);
734   InheritOptionalValue(preset.InheritConfigureEnvironment,
735                        parent.InheritConfigureEnvironment);
736   InheritOptionalValue(preset.Jobs, parent.Jobs);
737   InheritVector(preset.Targets, parent.Targets);
738   InheritString(preset.Configuration, parent.Configuration);
739   InheritOptionalValue(preset.CleanFirst, parent.CleanFirst);
740   InheritOptionalValue(preset.Verbose, parent.Verbose);
741   InheritVector(preset.NativeToolOptions, parent.NativeToolOptions);
742   if (!preset.ResolvePackageReferences) {
743     preset.ResolvePackageReferences = parent.ResolvePackageReferences;
744   }
745
746   return ReadFileResult::READ_OK;
747 }
748
749 cmCMakePresetsGraph::ReadFileResult
750 cmCMakePresetsGraph::BuildPreset::VisitPresetAfterInherit(int /* version */)
751 {
752   auto& preset = *this;
753   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
754     return ReadFileResult::INVALID_PRESET;
755   }
756   return ReadFileResult::READ_OK;
757 }
758
759 cmCMakePresetsGraph::ReadFileResult
760 cmCMakePresetsGraph::TestPreset::VisitPresetInherit(
761   const cmCMakePresetsGraph::Preset& parentPreset)
762 {
763   auto& preset = *this;
764   const TestPreset& parent = static_cast<const TestPreset&>(parentPreset);
765
766   InheritString(preset.ConfigurePreset, parent.ConfigurePreset);
767   InheritOptionalValue(preset.InheritConfigureEnvironment,
768                        parent.InheritConfigureEnvironment);
769   InheritString(preset.Configuration, parent.Configuration);
770   InheritVector(preset.OverwriteConfigurationFile,
771                 parent.OverwriteConfigurationFile);
772
773   if (parent.Output) {
774     if (preset.Output) {
775       auto& output = preset.Output.value();
776       const auto& parentOutput = parent.Output.value();
777       InheritOptionalValue(output.ShortProgress, parentOutput.ShortProgress);
778       InheritOptionalValue(output.Verbosity, parentOutput.Verbosity);
779       InheritOptionalValue(output.Debug, parentOutput.Debug);
780       InheritOptionalValue(output.OutputOnFailure,
781                            parentOutput.OutputOnFailure);
782       InheritOptionalValue(output.Quiet, parentOutput.Quiet);
783       InheritString(output.OutputLogFile, parentOutput.OutputLogFile);
784       InheritOptionalValue(output.LabelSummary, parentOutput.LabelSummary);
785       InheritOptionalValue(output.SubprojectSummary,
786                            parentOutput.SubprojectSummary);
787       InheritOptionalValue(output.MaxPassedTestOutputSize,
788                            parentOutput.MaxPassedTestOutputSize);
789       InheritOptionalValue(output.MaxFailedTestOutputSize,
790                            parentOutput.MaxFailedTestOutputSize);
791       InheritOptionalValue(output.TestOutputTruncation,
792                            parentOutput.TestOutputTruncation);
793       InheritOptionalValue(output.MaxTestNameWidth,
794                            parentOutput.MaxTestNameWidth);
795     } else {
796       preset.Output = parent.Output;
797     }
798   }
799
800   if (parent.Filter) {
801     if (parent.Filter->Include) {
802       if (preset.Filter && preset.Filter->Include) {
803         auto& include = *preset.Filter->Include;
804         const auto& parentInclude = *parent.Filter->Include;
805         InheritString(include.Name, parentInclude.Name);
806         InheritString(include.Label, parentInclude.Label);
807         InheritOptionalValue(include.Index, parentInclude.Index);
808       } else {
809         if (!preset.Filter) {
810           preset.Filter.emplace();
811         }
812         preset.Filter->Include = parent.Filter->Include;
813       }
814     }
815
816     if (parent.Filter->Exclude) {
817       if (preset.Filter && preset.Filter->Exclude) {
818         auto& exclude = *preset.Filter->Exclude;
819         const auto& parentExclude = *parent.Filter->Exclude;
820         InheritString(exclude.Name, parentExclude.Name);
821         InheritString(exclude.Label, parentExclude.Label);
822         InheritOptionalValue(exclude.Fixtures, parentExclude.Fixtures);
823       } else {
824         if (!preset.Filter) {
825           preset.Filter.emplace();
826         }
827         preset.Filter->Exclude = parent.Filter->Exclude;
828       }
829     }
830   }
831
832   if (parent.Execution) {
833     if (preset.Execution) {
834       auto& execution = *preset.Execution;
835       const auto& parentExecution = *parent.Execution;
836       InheritOptionalValue(execution.StopOnFailure,
837                            parentExecution.StopOnFailure);
838       InheritOptionalValue(execution.EnableFailover,
839                            parentExecution.EnableFailover);
840       InheritOptionalValue(execution.Jobs, parentExecution.Jobs);
841       InheritString(execution.ResourceSpecFile,
842                     parentExecution.ResourceSpecFile);
843       InheritOptionalValue(execution.TestLoad, parentExecution.TestLoad);
844       InheritOptionalValue(execution.ShowOnly, parentExecution.ShowOnly);
845       InheritOptionalValue(execution.Repeat, parentExecution.Repeat);
846       InheritOptionalValue(execution.InteractiveDebugging,
847                            parentExecution.InteractiveDebugging);
848       InheritOptionalValue(execution.ScheduleRandom,
849                            parentExecution.ScheduleRandom);
850       InheritOptionalValue(execution.Timeout, parentExecution.Timeout);
851       InheritOptionalValue(execution.NoTestsAction,
852                            parentExecution.NoTestsAction);
853     } else {
854       preset.Execution = parent.Execution;
855     }
856   }
857
858   return ReadFileResult::READ_OK;
859 }
860
861 cmCMakePresetsGraph::ReadFileResult
862 cmCMakePresetsGraph::TestPreset::VisitPresetAfterInherit(int /* version */)
863 {
864   auto& preset = *this;
865   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
866     return ReadFileResult::INVALID_PRESET;
867   }
868   return ReadFileResult::READ_OK;
869 }
870
871 std::string cmCMakePresetsGraph::GetFilename(const std::string& sourceDir)
872 {
873   return cmStrCat(sourceDir, "/CMakePresets.json");
874 }
875
876 std::string cmCMakePresetsGraph::GetUserFilename(const std::string& sourceDir)
877 {
878   return cmStrCat(sourceDir, "/CMakeUserPresets.json");
879 }
880
881 cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadProjectPresets(
882   const std::string& sourceDir, bool allowNoFiles)
883 {
884   this->SourceDir = sourceDir;
885   this->ClearPresets();
886
887   auto result = this->ReadProjectPresetsInternal(allowNoFiles);
888   if (result != ReadFileResult::READ_OK) {
889     this->ClearPresets();
890   }
891
892   return result;
893 }
894
895 cmCMakePresetsGraph::ReadFileResult
896 cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
897 {
898   bool haveOneFile = false;
899
900   File* file;
901   std::string filename = GetUserFilename(this->SourceDir);
902   std::vector<File*> inProgressFiles;
903   if (cmSystemTools::FileExists(filename)) {
904     auto result = this->ReadJSONFile(filename, RootType::User,
905                                      ReadReason::Root, inProgressFiles, file);
906     if (result != ReadFileResult::READ_OK) {
907       return result;
908     }
909     haveOneFile = true;
910   } else {
911     filename = GetFilename(this->SourceDir);
912     if (cmSystemTools::FileExists(filename)) {
913       auto result = this->ReadJSONFile(
914         filename, RootType::Project, ReadReason::Root, inProgressFiles, file);
915       if (result != ReadFileResult::READ_OK) {
916         return result;
917       }
918       haveOneFile = true;
919     }
920   }
921   assert(inProgressFiles.empty());
922
923   if (!haveOneFile) {
924     return allowNoFiles ? ReadFileResult::READ_OK
925                         : ReadFileResult::FILE_NOT_FOUND;
926   }
927
928   CHECK_OK(ComputePresetInheritance(this->ConfigurePresets, *this));
929   CHECK_OK(ComputePresetInheritance(this->BuildPresets, *this));
930   CHECK_OK(ComputePresetInheritance(this->TestPresets, *this));
931
932   for (auto& it : this->ConfigurePresets) {
933     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
934       return ReadFileResult::INVALID_MACRO_EXPANSION;
935     }
936   }
937
938   for (auto& it : this->BuildPresets) {
939     if (!it.second.Unexpanded.Hidden) {
940       const auto configurePreset =
941         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
942       if (configurePreset == this->ConfigurePresets.end()) {
943         return ReadFileResult::INVALID_CONFIGURE_PRESET;
944       }
945       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
946             configurePreset->second.Unexpanded.OriginFile)) {
947         return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
948       }
949
950       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
951         it.second.Unexpanded.Environment.insert(
952           configurePreset->second.Unexpanded.Environment.begin(),
953           configurePreset->second.Unexpanded.Environment.end());
954       }
955     }
956
957     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
958       return ReadFileResult::INVALID_MACRO_EXPANSION;
959     }
960   }
961
962   for (auto& it : this->TestPresets) {
963     if (!it.second.Unexpanded.Hidden) {
964       const auto configurePreset =
965         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
966       if (configurePreset == this->ConfigurePresets.end()) {
967         return ReadFileResult::INVALID_CONFIGURE_PRESET;
968       }
969       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
970             configurePreset->second.Unexpanded.OriginFile)) {
971         return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
972       }
973
974       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
975         it.second.Unexpanded.Environment.insert(
976           configurePreset->second.Unexpanded.Environment.begin(),
977           configurePreset->second.Unexpanded.Environment.end());
978       }
979     }
980
981     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
982       return ReadFileResult::INVALID_MACRO_EXPANSION;
983     }
984   }
985
986   return ReadFileResult::READ_OK;
987 }
988
989 const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
990 {
991   switch (result) {
992     case ReadFileResult::READ_OK:
993       return "OK";
994     case ReadFileResult::FILE_NOT_FOUND:
995       return "File not found";
996     case ReadFileResult::JSON_PARSE_ERROR:
997       return "JSON parse error";
998     case ReadFileResult::INVALID_ROOT:
999       return "Invalid root object";
1000     case ReadFileResult::NO_VERSION:
1001       return "No \"version\" field";
1002     case ReadFileResult::INVALID_VERSION:
1003       return "Invalid \"version\" field";
1004     case ReadFileResult::UNRECOGNIZED_VERSION:
1005       return "Unrecognized \"version\" field";
1006     case ReadFileResult::INVALID_CMAKE_VERSION:
1007       return "Invalid \"cmakeMinimumRequired\" field";
1008     case ReadFileResult::UNRECOGNIZED_CMAKE_VERSION:
1009       return "\"cmakeMinimumRequired\" version too new";
1010     case ReadFileResult::INVALID_PRESETS:
1011       return "Invalid \"configurePresets\" field";
1012     case ReadFileResult::INVALID_PRESET:
1013       return "Invalid preset";
1014     case ReadFileResult::INVALID_VARIABLE:
1015       return "Invalid CMake variable definition";
1016     case ReadFileResult::DUPLICATE_PRESETS:
1017       return "Duplicate presets";
1018     case ReadFileResult::CYCLIC_PRESET_INHERITANCE:
1019       return "Cyclic preset inheritance";
1020     case ReadFileResult::INHERITED_PRESET_UNREACHABLE_FROM_FILE:
1021       return "Inherited preset is unreachable from preset's file";
1022     case ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE:
1023       return "Configure preset is unreachable from preset's file";
1024     case ReadFileResult::INVALID_MACRO_EXPANSION:
1025       return "Invalid macro expansion";
1026     case ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED:
1027       return "File version must be 2 or higher for build and test preset "
1028              "support.";
1029     case ReadFileResult::INCLUDE_UNSUPPORTED:
1030       return "File version must be 4 or higher for include support";
1031     case ReadFileResult::INVALID_INCLUDE:
1032       return "Invalid \"include\" field";
1033     case ReadFileResult::INVALID_CONFIGURE_PRESET:
1034       return "Invalid \"configurePreset\" field";
1035     case ReadFileResult::INSTALL_PREFIX_UNSUPPORTED:
1036       return "File version must be 3 or higher for installDir preset "
1037              "support.";
1038     case ReadFileResult::INVALID_CONDITION:
1039       return "Invalid preset condition";
1040     case ReadFileResult::CONDITION_UNSUPPORTED:
1041       return "File version must be 3 or higher for condition support";
1042     case ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED:
1043       return "File version must be 3 or higher for toolchainFile preset "
1044              "support.";
1045     case ReadFileResult::CYCLIC_INCLUDE:
1046       return "Cyclic include among preset files";
1047     case ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED:
1048       return "File version must be 5 or higher for testOutputTruncation "
1049              "preset support.";
1050   }
1051
1052   return "Unknown error";
1053 }
1054
1055 void cmCMakePresetsGraph::ClearPresets()
1056 {
1057   this->ConfigurePresets.clear();
1058   this->BuildPresets.clear();
1059   this->TestPresets.clear();
1060
1061   this->ConfigurePresetOrder.clear();
1062   this->BuildPresetOrder.clear();
1063   this->TestPresetOrder.clear();
1064
1065   this->Files.clear();
1066 }
1067
1068 void cmCMakePresetsGraph::PrintPresets(
1069   const std::vector<const cmCMakePresetsGraph::Preset*>& presets)
1070 {
1071   if (presets.empty()) {
1072     return;
1073   }
1074
1075   auto longestPresetName =
1076     std::max_element(presets.begin(), presets.end(),
1077                      [](const cmCMakePresetsGraph::Preset* a,
1078                         const cmCMakePresetsGraph::Preset* b) {
1079                        return a->Name.length() < b->Name.length();
1080                      });
1081   auto longestLength = (*longestPresetName)->Name.length();
1082
1083   for (const auto* preset : presets) {
1084     std::cout << "  \"" << preset->Name << '"';
1085     const auto& description = preset->DisplayName;
1086     if (!description.empty()) {
1087       for (std::size_t i = 0; i < longestLength - preset->Name.length(); ++i) {
1088         std::cout << ' ';
1089       }
1090       std::cout << " - " << description;
1091     }
1092     std::cout << '\n';
1093   }
1094 }
1095
1096 void cmCMakePresetsGraph::PrintConfigurePresetList() const
1097 {
1098   PrintConfigurePresetList([](const ConfigurePreset&) { return true; });
1099 }
1100
1101 void cmCMakePresetsGraph::PrintConfigurePresetList(
1102   const std::function<bool(const ConfigurePreset&)>& filter) const
1103 {
1104   std::vector<const cmCMakePresetsGraph::Preset*> presets;
1105   for (auto const& p : this->ConfigurePresetOrder) {
1106     auto const& preset = this->ConfigurePresets.at(p);
1107     if (!preset.Unexpanded.Hidden && preset.Expanded &&
1108         preset.Expanded->ConditionResult && filter(preset.Unexpanded)) {
1109       presets.push_back(
1110         static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
1111     }
1112   }
1113
1114   if (!presets.empty()) {
1115     std::cout << "Available configure presets:\n\n";
1116     cmCMakePresetsGraph::PrintPresets(presets);
1117   }
1118 }
1119
1120 void cmCMakePresetsGraph::PrintBuildPresetList() const
1121 {
1122   std::vector<const cmCMakePresetsGraph::Preset*> presets;
1123   for (auto const& p : this->BuildPresetOrder) {
1124     auto const& preset = this->BuildPresets.at(p);
1125     if (!preset.Unexpanded.Hidden && preset.Expanded &&
1126         preset.Expanded->ConditionResult) {
1127       presets.push_back(
1128         static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
1129     }
1130   }
1131
1132   if (!presets.empty()) {
1133     std::cout << "Available build presets:\n\n";
1134     cmCMakePresetsGraph::PrintPresets(presets);
1135   }
1136 }
1137
1138 void cmCMakePresetsGraph::PrintTestPresetList() const
1139 {
1140   std::vector<const cmCMakePresetsGraph::Preset*> presets;
1141   for (auto const& p : this->TestPresetOrder) {
1142     auto const& preset = this->TestPresets.at(p);
1143     if (!preset.Unexpanded.Hidden && preset.Expanded &&
1144         preset.Expanded->ConditionResult) {
1145       presets.push_back(
1146         static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
1147     }
1148   }
1149
1150   if (!presets.empty()) {
1151     std::cout << "Available test presets:\n\n";
1152     cmCMakePresetsGraph::PrintPresets(presets);
1153   }
1154 }
1155
1156 void cmCMakePresetsGraph::PrintAllPresets() const
1157 {
1158   this->PrintConfigurePresetList();
1159   std::cout << std::endl;
1160   this->PrintBuildPresetList();
1161   std::cout << std::endl;
1162   this->PrintTestPresetList();
1163 }