Imported Upstream version 3.25.0
[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 PackagePreset = cmCMakePresetsGraph::PackagePreset;
47 using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
48 template <typename T>
49 using PresetPair = cmCMakePresetsGraph::PresetPair<T>;
50 using ExpandMacroResult = cmCMakePresetsGraphInternal::ExpandMacroResult;
51 using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander;
52
53 void InheritString(std::string& child, const std::string& parent)
54 {
55   if (child.empty()) {
56     child = parent;
57   }
58 }
59
60 template <typename T>
61 void InheritOptionalValue(cm::optional<T>& child,
62                           const cm::optional<T>& parent)
63 {
64   if (!child) {
65     child = parent;
66   }
67 }
68
69 template <typename T>
70 void InheritVector(std::vector<T>& child, const std::vector<T>& parent)
71 {
72   if (child.empty()) {
73     child = parent;
74   }
75 }
76
77 /**
78  * Check preset inheritance for cycles (using a DAG check algorithm) while
79  * also bubbling up fields through the inheritance hierarchy, then verify
80  * that each preset has the required fields, either directly or through
81  * inheritance.
82  */
83 template <class T>
84 ReadFileResult VisitPreset(
85   T& preset,
86   std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
87   std::map<std::string, CycleStatus> cycleStatus,
88   const cmCMakePresetsGraph& graph)
89 {
90   switch (cycleStatus[preset.Name]) {
91     case CycleStatus::InProgress:
92       return ReadFileResult::CYCLIC_PRESET_INHERITANCE;
93     case CycleStatus::Verified:
94       return ReadFileResult::READ_OK;
95     default:
96       break;
97   }
98
99   cycleStatus[preset.Name] = CycleStatus::InProgress;
100
101   if (preset.Environment.count("") != 0) {
102     return ReadFileResult::INVALID_PRESET;
103   }
104
105   CHECK_OK(preset.VisitPresetBeforeInherit());
106
107   for (auto const& i : preset.Inherits) {
108     auto parent = presets.find(i);
109     if (parent == presets.end()) {
110       return ReadFileResult::INVALID_PRESET;
111     }
112
113     auto& parentPreset = parent->second.Unexpanded;
114     if (!preset.OriginFile->ReachableFiles.count(parentPreset.OriginFile)) {
115       return ReadFileResult::INHERITED_PRESET_UNREACHABLE_FROM_FILE;
116     }
117
118     auto result = VisitPreset(parentPreset, presets, cycleStatus, graph);
119     if (result != ReadFileResult::READ_OK) {
120       return result;
121     }
122
123     CHECK_OK(preset.VisitPresetInherit(parentPreset));
124
125     for (auto const& v : parentPreset.Environment) {
126       preset.Environment.insert(v);
127     }
128
129     if (!preset.ConditionEvaluator) {
130       preset.ConditionEvaluator = parentPreset.ConditionEvaluator;
131     }
132   }
133
134   if (preset.ConditionEvaluator && preset.ConditionEvaluator->IsNull()) {
135     preset.ConditionEvaluator.reset();
136   }
137
138   CHECK_OK(preset.VisitPresetAfterInherit(graph.GetVersion(preset)));
139
140   cycleStatus[preset.Name] = CycleStatus::Verified;
141   return ReadFileResult::READ_OK;
142 }
143
144 template <class T>
145 ReadFileResult ComputePresetInheritance(
146   std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
147   const cmCMakePresetsGraph& graph)
148 {
149   std::map<std::string, CycleStatus> cycleStatus;
150   for (auto const& it : presets) {
151     cycleStatus[it.first] = CycleStatus::Unvisited;
152   }
153
154   for (auto& it : presets) {
155     auto& preset = it.second.Unexpanded;
156     auto result = VisitPreset<T>(preset, presets, cycleStatus, graph);
157     if (result != ReadFileResult::READ_OK) {
158       return result;
159     }
160   }
161
162   return ReadFileResult::READ_OK;
163 }
164
165 constexpr const char* ValidPrefixes[] = {
166   "",
167   "env",
168   "penv",
169   "vendor",
170 };
171
172 bool PrefixesValidMacroNamespace(const std::string& str)
173 {
174   return std::any_of(
175     std::begin(ValidPrefixes), std::end(ValidPrefixes),
176     [&str](const char* prefix) -> bool { return cmHasPrefix(prefix, str); });
177 }
178
179 bool IsValidMacroNamespace(const std::string& str)
180 {
181   return std::any_of(
182     std::begin(ValidPrefixes), std::end(ValidPrefixes),
183     [&str](const char* prefix) -> bool { return str == prefix; });
184 }
185
186 ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status,
187                            const std::vector<MacroExpander>& macroExpanders,
188                            int version);
189 ExpandMacroResult ExpandMacros(
190   std::string& out, const std::vector<MacroExpander>& macroExpanders,
191   int version);
192 ExpandMacroResult ExpandMacro(std::string& out,
193                               const std::string& macroNamespace,
194                               const std::string& macroName,
195                               const std::vector<MacroExpander>& macroExpanders,
196                               int version);
197
198 bool ExpandMacros(const cmCMakePresetsGraph& graph,
199                   const ConfigurePreset& preset,
200                   cm::optional<ConfigurePreset>& out,
201                   const std::vector<MacroExpander>& macroExpanders)
202 {
203   std::string binaryDir = preset.BinaryDir;
204   CHECK_EXPAND(out, binaryDir, macroExpanders, graph.GetVersion(preset));
205
206   if (!binaryDir.empty()) {
207     if (!cmSystemTools::FileIsFullPath(binaryDir)) {
208       binaryDir = cmStrCat(graph.SourceDir, '/', binaryDir);
209     }
210     out->BinaryDir = cmSystemTools::CollapseFullPath(binaryDir);
211     cmSystemTools::ConvertToUnixSlashes(out->BinaryDir);
212   }
213
214   if (!preset.InstallDir.empty()) {
215     std::string installDir = preset.InstallDir;
216     CHECK_EXPAND(out, installDir, macroExpanders, graph.GetVersion(preset));
217
218     if (!cmSystemTools::FileIsFullPath(installDir)) {
219       installDir = cmStrCat(graph.SourceDir, '/', installDir);
220     }
221     out->InstallDir = cmSystemTools::CollapseFullPath(installDir);
222     cmSystemTools::ConvertToUnixSlashes(out->InstallDir);
223   }
224
225   if (!preset.ToolchainFile.empty()) {
226     std::string toolchain = preset.ToolchainFile;
227     CHECK_EXPAND(out, toolchain, macroExpanders, graph.GetVersion(preset));
228     out->ToolchainFile = toolchain;
229   }
230
231   for (auto& variable : out->CacheVariables) {
232     if (variable.second) {
233       CHECK_EXPAND(out, variable.second->Value, macroExpanders,
234                    graph.GetVersion(preset));
235     }
236   }
237
238   return true;
239 }
240
241 bool ExpandMacros(const cmCMakePresetsGraph& graph, const BuildPreset& preset,
242                   cm::optional<BuildPreset>& out,
243                   const std::vector<MacroExpander>& macroExpanders)
244 {
245   for (auto& target : out->Targets) {
246     CHECK_EXPAND(out, target, macroExpanders, graph.GetVersion(preset));
247   }
248
249   for (auto& nativeToolOption : out->NativeToolOptions) {
250     CHECK_EXPAND(out, nativeToolOption, macroExpanders,
251                  graph.GetVersion(preset));
252   }
253
254   return true;
255 }
256
257 bool ExpandMacros(const cmCMakePresetsGraph& graph, const TestPreset& preset,
258                   cm::optional<TestPreset>& out,
259                   const std::vector<MacroExpander>& macroExpanders)
260 {
261   for (auto& overwrite : out->OverwriteConfigurationFile) {
262     CHECK_EXPAND(out, overwrite, macroExpanders, graph.GetVersion(preset));
263   }
264
265   if (out->Output) {
266     CHECK_EXPAND(out, out->Output->OutputLogFile, macroExpanders,
267                  graph.GetVersion(preset));
268     CHECK_EXPAND(out, out->Output->OutputJUnitFile, macroExpanders,
269                  graph.GetVersion(preset));
270   }
271
272   if (out->Filter) {
273     if (out->Filter->Include) {
274       CHECK_EXPAND(out, out->Filter->Include->Name, macroExpanders,
275                    graph.GetVersion(preset));
276       CHECK_EXPAND(out, out->Filter->Include->Label, macroExpanders,
277                    graph.GetVersion(preset));
278
279       if (out->Filter->Include->Index) {
280         CHECK_EXPAND(out, out->Filter->Include->Index->IndexFile,
281                      macroExpanders, graph.GetVersion(preset));
282       }
283     }
284
285     if (out->Filter->Exclude) {
286       CHECK_EXPAND(out, out->Filter->Exclude->Name, macroExpanders,
287                    graph.GetVersion(preset));
288       CHECK_EXPAND(out, out->Filter->Exclude->Label, macroExpanders,
289                    graph.GetVersion(preset));
290
291       if (out->Filter->Exclude->Fixtures) {
292         CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Any, macroExpanders,
293                      graph.GetVersion(preset));
294         CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Setup,
295                      macroExpanders, graph.GetVersion(preset));
296         CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Cleanup,
297                      macroExpanders, graph.GetVersion(preset));
298       }
299     }
300   }
301
302   if (out->Execution) {
303     CHECK_EXPAND(out, out->Execution->ResourceSpecFile, macroExpanders,
304                  graph.GetVersion(preset));
305   }
306
307   return true;
308 }
309
310 bool ExpandMacros(const cmCMakePresetsGraph& graph,
311                   const PackagePreset& preset,
312                   cm::optional<PackagePreset>& out,
313                   const std::vector<MacroExpander>& macroExpanders)
314 {
315   for (auto& variable : out->Variables) {
316     CHECK_EXPAND(out, variable.second, macroExpanders,
317                  graph.GetVersion(preset));
318   }
319
320   CHECK_EXPAND(out, out->ConfigFile, macroExpanders, graph.GetVersion(preset));
321   CHECK_EXPAND(out, out->PackageName, macroExpanders,
322                graph.GetVersion(preset));
323   CHECK_EXPAND(out, out->PackageVersion, macroExpanders,
324                graph.GetVersion(preset));
325   CHECK_EXPAND(out, out->PackageDirectory, macroExpanders,
326                graph.GetVersion(preset));
327   CHECK_EXPAND(out, out->VendorName, macroExpanders, graph.GetVersion(preset));
328
329   return true;
330 }
331
332 bool ExpandMacros(const cmCMakePresetsGraph& /*graph*/,
333                   const WorkflowPreset& /*preset*/,
334                   cm::optional<WorkflowPreset>& /*out*/,
335                   const std::vector<MacroExpander>& /*macroExpanders*/)
336 {
337   return true;
338 }
339
340 template <class T>
341 bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
342                   cm::optional<T>& out)
343 {
344   out.emplace(preset);
345
346   std::map<std::string, CycleStatus> envCycles;
347   for (auto const& v : out->Environment) {
348     envCycles[v.first] = CycleStatus::Unvisited;
349   }
350
351   std::vector<MacroExpander> macroExpanders;
352
353   MacroExpander defaultMacroExpander =
354     [&graph, &preset](const std::string& macroNamespace,
355                       const std::string& macroName, std::string& macroOut,
356                       int version) -> ExpandMacroResult {
357     if (macroNamespace.empty()) {
358       if (macroName == "sourceDir") {
359         macroOut += graph.SourceDir;
360         return ExpandMacroResult::Ok;
361       }
362       if (macroName == "sourceParentDir") {
363         macroOut += cmSystemTools::GetParentDirectory(graph.SourceDir);
364         return ExpandMacroResult::Ok;
365       }
366       if (macroName == "sourceDirName") {
367         macroOut += cmSystemTools::GetFilenameName(graph.SourceDir);
368         return ExpandMacroResult::Ok;
369       }
370       if (macroName == "presetName") {
371         macroOut += preset.Name;
372         return ExpandMacroResult::Ok;
373       }
374       if (macroName == "generator") {
375         // Generator only makes sense if preset is not hidden.
376         if (!preset.Hidden) {
377           macroOut += graph.GetGeneratorForPreset(preset.Name);
378         }
379         return ExpandMacroResult::Ok;
380       }
381       if (macroName == "dollar") {
382         macroOut += '$';
383         return ExpandMacroResult::Ok;
384       }
385       if (macroName == "hostSystemName") {
386         if (version < 3) {
387           return ExpandMacroResult::Error;
388         }
389         macroOut += cmSystemTools::GetSystemName();
390         return ExpandMacroResult::Ok;
391       }
392       if (macroName == "fileDir") {
393         if (version < 4) {
394           return ExpandMacroResult::Error;
395         }
396         macroOut +=
397           cmSystemTools::GetParentDirectory(preset.OriginFile->Filename);
398         return ExpandMacroResult::Ok;
399       }
400       if (macroName == "pathListSep") {
401         if (version < 5) {
402           return ExpandMacroResult::Error;
403         }
404         macroOut += cmSystemTools::GetSystemPathlistSeparator();
405         return ExpandMacroResult::Ok;
406       }
407     }
408
409     return ExpandMacroResult::Ignore;
410   };
411
412   MacroExpander environmentMacroExpander =
413     [&macroExpanders, &out, &envCycles](
414       const std::string& macroNamespace, const std::string& macroName,
415       std::string& result, int version) -> ExpandMacroResult {
416     if (macroNamespace == "env" && !macroName.empty() && out) {
417       auto v = out->Environment.find(macroName);
418       if (v != out->Environment.end() && v->second) {
419         auto e =
420           VisitEnv(*v->second, envCycles[macroName], macroExpanders, version);
421         if (e != ExpandMacroResult::Ok) {
422           return e;
423         }
424         result += *v->second;
425         return ExpandMacroResult::Ok;
426       }
427     }
428
429     if (macroNamespace == "env" || macroNamespace == "penv") {
430       if (macroName.empty()) {
431         return ExpandMacroResult::Error;
432       }
433       const char* value = std::getenv(macroName.c_str());
434       if (value) {
435         result += value;
436       }
437       return ExpandMacroResult::Ok;
438     }
439
440     return ExpandMacroResult::Ignore;
441   };
442
443   macroExpanders.push_back(defaultMacroExpander);
444   macroExpanders.push_back(environmentMacroExpander);
445
446   for (auto& v : out->Environment) {
447     if (v.second) {
448       switch (VisitEnv(*v.second, envCycles[v.first], macroExpanders,
449                        graph.GetVersion(preset))) {
450         case ExpandMacroResult::Error:
451           return false;
452         case ExpandMacroResult::Ignore:
453           out.reset();
454           return true;
455         case ExpandMacroResult::Ok:
456           break;
457       }
458     }
459   }
460
461   if (preset.ConditionEvaluator) {
462     cm::optional<bool> result;
463     if (!preset.ConditionEvaluator->Evaluate(
464           macroExpanders, graph.GetVersion(preset), result)) {
465       return false;
466     }
467     if (!result) {
468       out.reset();
469       return true;
470     }
471     out->ConditionResult = *result;
472   }
473
474   return ExpandMacros(graph, preset, out, macroExpanders);
475 }
476
477 ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status,
478                            const std::vector<MacroExpander>& macroExpanders,
479                            int version)
480 {
481   if (status == CycleStatus::Verified) {
482     return ExpandMacroResult::Ok;
483   }
484   if (status == CycleStatus::InProgress) {
485     return ExpandMacroResult::Error;
486   }
487
488   status = CycleStatus::InProgress;
489   auto e = ExpandMacros(value, macroExpanders, version);
490   if (e != ExpandMacroResult::Ok) {
491     return e;
492   }
493   status = CycleStatus::Verified;
494   return ExpandMacroResult::Ok;
495 }
496
497 ExpandMacroResult ExpandMacros(
498   std::string& out, const std::vector<MacroExpander>& macroExpanders,
499   int version)
500 {
501   std::string result;
502   std::string macroNamespace;
503   std::string macroName;
504
505   enum class State
506   {
507     Default,
508     MacroNamespace,
509     MacroName,
510   } state = State::Default;
511
512   for (auto c : out) {
513     switch (state) {
514       case State::Default:
515         if (c == '$') {
516           state = State::MacroNamespace;
517         } else {
518           result += c;
519         }
520         break;
521
522       case State::MacroNamespace:
523         if (c == '{') {
524           if (IsValidMacroNamespace(macroNamespace)) {
525             state = State::MacroName;
526           } else {
527             result += '$';
528             result += macroNamespace;
529             result += '{';
530             macroNamespace.clear();
531             state = State::Default;
532           }
533         } else {
534           macroNamespace += c;
535           if (!PrefixesValidMacroNamespace(macroNamespace)) {
536             result += '$';
537             result += macroNamespace;
538             macroNamespace.clear();
539             state = State::Default;
540           }
541         }
542         break;
543
544       case State::MacroName:
545         if (c == '}') {
546           auto e = ExpandMacro(result, macroNamespace, macroName,
547                                macroExpanders, version);
548           if (e != ExpandMacroResult::Ok) {
549             return e;
550           }
551           macroNamespace.clear();
552           macroName.clear();
553           state = State::Default;
554         } else {
555           macroName += c;
556         }
557         break;
558     }
559   }
560
561   switch (state) {
562     case State::Default:
563       break;
564     case State::MacroNamespace:
565       result += '$';
566       result += macroNamespace;
567       break;
568     case State::MacroName:
569       return ExpandMacroResult::Error;
570   }
571
572   out = std::move(result);
573   return ExpandMacroResult::Ok;
574 }
575
576 ExpandMacroResult ExpandMacro(std::string& out,
577                               const std::string& macroNamespace,
578                               const std::string& macroName,
579                               const std::vector<MacroExpander>& macroExpanders,
580                               int version)
581 {
582   for (auto const& macroExpander : macroExpanders) {
583     auto result = macroExpander(macroNamespace, macroName, out, version);
584     if (result != ExpandMacroResult::Ignore) {
585       return result;
586     }
587   }
588
589   if (macroNamespace == "vendor") {
590     return ExpandMacroResult::Ignore;
591   }
592
593   return ExpandMacroResult::Error;
594 }
595
596 template <typename T>
597 ReadFileResult SetupWorkflowConfigurePreset(
598   const T& preset, const ConfigurePreset*& configurePreset)
599 {
600   if (preset.ConfigurePreset != configurePreset->Name) {
601     return ReadFileResult::INVALID_WORKFLOW_STEPS;
602   }
603   return ReadFileResult::READ_OK;
604 }
605
606 template <>
607 ReadFileResult SetupWorkflowConfigurePreset<ConfigurePreset>(
608   const ConfigurePreset& preset, const ConfigurePreset*& configurePreset)
609 {
610   configurePreset = &preset;
611   return ReadFileResult::READ_OK;
612 }
613
614 template <typename T>
615 ReadFileResult TryReachPresetFromWorkflow(
616   const WorkflowPreset& origin,
617   const std::map<std::string, PresetPair<T>>& presets, const std::string& name,
618   const ConfigurePreset*& configurePreset)
619 {
620   auto it = presets.find(name);
621   if (it == presets.end()) {
622     return ReadFileResult::INVALID_WORKFLOW_STEPS;
623   }
624   if (!origin.OriginFile->ReachableFiles.count(
625         it->second.Unexpanded.OriginFile)) {
626     return ReadFileResult::WORKFLOW_STEP_UNREACHABLE_FROM_FILE;
627   }
628   return SetupWorkflowConfigurePreset<T>(it->second.Unexpanded,
629                                          configurePreset);
630 }
631 }
632
633 bool cmCMakePresetsGraphInternal::EqualsCondition::Evaluate(
634   const std::vector<MacroExpander>& expanders, int version,
635   cm::optional<bool>& out) const
636 {
637   std::string lhs = this->Lhs;
638   CHECK_EXPAND(out, lhs, expanders, version);
639
640   std::string rhs = this->Rhs;
641   CHECK_EXPAND(out, rhs, expanders, version);
642
643   out = (lhs == rhs);
644   return true;
645 }
646
647 bool cmCMakePresetsGraphInternal::InListCondition::Evaluate(
648   const std::vector<MacroExpander>& expanders, int version,
649   cm::optional<bool>& out) const
650 {
651   std::string str = this->String;
652   CHECK_EXPAND(out, str, expanders, version);
653
654   for (auto item : this->List) {
655     CHECK_EXPAND(out, item, expanders, version);
656     if (str == item) {
657       out = true;
658       return true;
659     }
660   }
661
662   out = false;
663   return true;
664 }
665
666 bool cmCMakePresetsGraphInternal::MatchesCondition::Evaluate(
667   const std::vector<MacroExpander>& expanders, int version,
668   cm::optional<bool>& out) const
669 {
670   std::string str = this->String;
671   CHECK_EXPAND(out, str, expanders, version);
672   std::string regexStr = this->Regex;
673   CHECK_EXPAND(out, regexStr, expanders, version);
674
675   cmsys::RegularExpression regex;
676   if (!regex.compile(regexStr)) {
677     return false;
678   }
679
680   out = regex.find(str);
681   return true;
682 }
683
684 bool cmCMakePresetsGraphInternal::AnyAllOfCondition::Evaluate(
685   const std::vector<MacroExpander>& expanders, int version,
686   cm::optional<bool>& out) const
687 {
688   for (auto const& condition : this->Conditions) {
689     cm::optional<bool> result;
690     if (!condition->Evaluate(expanders, version, result)) {
691       out.reset();
692       return false;
693     }
694
695     if (!result) {
696       out.reset();
697       return true;
698     }
699
700     if (result == this->StopValue) {
701       out = result;
702       return true;
703     }
704   }
705
706   out = !this->StopValue;
707   return true;
708 }
709
710 bool cmCMakePresetsGraphInternal::NotCondition::Evaluate(
711   const std::vector<MacroExpander>& expanders, int version,
712   cm::optional<bool>& out) const
713 {
714   out.reset();
715   if (!this->SubCondition->Evaluate(expanders, version, out)) {
716     out.reset();
717     return false;
718   }
719   if (out) {
720     *out = !*out;
721   }
722   return true;
723 }
724
725 cmCMakePresetsGraph::ReadFileResult
726 cmCMakePresetsGraph::ConfigurePreset::VisitPresetInherit(
727   const cmCMakePresetsGraph::Preset& parentPreset)
728 {
729   auto& preset = *this;
730   const ConfigurePreset& parent =
731     static_cast<const ConfigurePreset&>(parentPreset);
732   InheritString(preset.Generator, parent.Generator);
733   InheritString(preset.Architecture, parent.Architecture);
734   InheritString(preset.Toolset, parent.Toolset);
735   if (!preset.ArchitectureStrategy) {
736     preset.ArchitectureStrategy = parent.ArchitectureStrategy;
737   }
738   if (!preset.ToolsetStrategy) {
739     preset.ToolsetStrategy = parent.ToolsetStrategy;
740   }
741   InheritString(preset.BinaryDir, parent.BinaryDir);
742   InheritString(preset.InstallDir, parent.InstallDir);
743   InheritString(preset.ToolchainFile, parent.ToolchainFile);
744   InheritOptionalValue(preset.WarnDev, parent.WarnDev);
745   InheritOptionalValue(preset.ErrorDev, parent.ErrorDev);
746   InheritOptionalValue(preset.WarnDeprecated, parent.WarnDeprecated);
747   InheritOptionalValue(preset.ErrorDeprecated, parent.ErrorDeprecated);
748   InheritOptionalValue(preset.WarnUninitialized, parent.WarnUninitialized);
749   InheritOptionalValue(preset.WarnUnusedCli, parent.WarnUnusedCli);
750   InheritOptionalValue(preset.WarnSystemVars, parent.WarnSystemVars);
751
752   for (auto const& v : parent.CacheVariables) {
753     preset.CacheVariables.insert(v);
754   }
755
756   return ReadFileResult::READ_OK;
757 }
758
759 cmCMakePresetsGraph::ReadFileResult
760 cmCMakePresetsGraph::ConfigurePreset::VisitPresetBeforeInherit()
761 {
762   auto& preset = *this;
763   if (preset.Environment.count("") != 0) {
764     return ReadFileResult::INVALID_PRESET;
765   }
766
767   return ReadFileResult::READ_OK;
768 }
769
770 cmCMakePresetsGraph::ReadFileResult
771 cmCMakePresetsGraph::ConfigurePreset::VisitPresetAfterInherit(int version)
772 {
773   auto& preset = *this;
774   if (!preset.Hidden) {
775     if (version < 3) {
776       if (preset.Generator.empty()) {
777         return ReadFileResult::INVALID_PRESET;
778       }
779       if (preset.BinaryDir.empty()) {
780         return ReadFileResult::INVALID_PRESET;
781       }
782     }
783
784     if (preset.WarnDev == false && preset.ErrorDev == true) {
785       return ReadFileResult::INVALID_PRESET;
786     }
787     if (preset.WarnDeprecated == false && preset.ErrorDeprecated == true) {
788       return ReadFileResult::INVALID_PRESET;
789     }
790     if (preset.CacheVariables.count("") != 0) {
791       return ReadFileResult::INVALID_PRESET;
792     }
793   }
794
795   return ReadFileResult::READ_OK;
796 }
797
798 cmCMakePresetsGraph::ReadFileResult
799 cmCMakePresetsGraph::BuildPreset::VisitPresetInherit(
800   const cmCMakePresetsGraph::Preset& parentPreset)
801 {
802   auto& preset = *this;
803   const BuildPreset& parent = static_cast<const BuildPreset&>(parentPreset);
804
805   InheritString(preset.ConfigurePreset, parent.ConfigurePreset);
806   InheritOptionalValue(preset.InheritConfigureEnvironment,
807                        parent.InheritConfigureEnvironment);
808   InheritOptionalValue(preset.Jobs, parent.Jobs);
809   InheritVector(preset.Targets, parent.Targets);
810   InheritString(preset.Configuration, parent.Configuration);
811   InheritOptionalValue(preset.CleanFirst, parent.CleanFirst);
812   InheritOptionalValue(preset.Verbose, parent.Verbose);
813   InheritVector(preset.NativeToolOptions, parent.NativeToolOptions);
814   if (!preset.ResolvePackageReferences) {
815     preset.ResolvePackageReferences = parent.ResolvePackageReferences;
816   }
817
818   return ReadFileResult::READ_OK;
819 }
820
821 cmCMakePresetsGraph::ReadFileResult
822 cmCMakePresetsGraph::BuildPreset::VisitPresetAfterInherit(int /* version */)
823 {
824   auto& preset = *this;
825   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
826     return ReadFileResult::INVALID_PRESET;
827   }
828   return ReadFileResult::READ_OK;
829 }
830
831 cmCMakePresetsGraph::ReadFileResult
832 cmCMakePresetsGraph::TestPreset::VisitPresetInherit(
833   const cmCMakePresetsGraph::Preset& parentPreset)
834 {
835   auto& preset = *this;
836   const TestPreset& parent = static_cast<const TestPreset&>(parentPreset);
837
838   InheritString(preset.ConfigurePreset, parent.ConfigurePreset);
839   InheritOptionalValue(preset.InheritConfigureEnvironment,
840                        parent.InheritConfigureEnvironment);
841   InheritString(preset.Configuration, parent.Configuration);
842   InheritVector(preset.OverwriteConfigurationFile,
843                 parent.OverwriteConfigurationFile);
844
845   if (parent.Output) {
846     if (preset.Output) {
847       auto& output = preset.Output.value();
848       const auto& parentOutput = parent.Output.value();
849       InheritOptionalValue(output.ShortProgress, parentOutput.ShortProgress);
850       InheritOptionalValue(output.Verbosity, parentOutput.Verbosity);
851       InheritOptionalValue(output.Debug, parentOutput.Debug);
852       InheritOptionalValue(output.OutputOnFailure,
853                            parentOutput.OutputOnFailure);
854       InheritOptionalValue(output.Quiet, parentOutput.Quiet);
855       InheritString(output.OutputLogFile, parentOutput.OutputLogFile);
856       InheritString(output.OutputJUnitFile, parentOutput.OutputJUnitFile);
857       InheritOptionalValue(output.LabelSummary, parentOutput.LabelSummary);
858       InheritOptionalValue(output.SubprojectSummary,
859                            parentOutput.SubprojectSummary);
860       InheritOptionalValue(output.MaxPassedTestOutputSize,
861                            parentOutput.MaxPassedTestOutputSize);
862       InheritOptionalValue(output.MaxFailedTestOutputSize,
863                            parentOutput.MaxFailedTestOutputSize);
864       InheritOptionalValue(output.TestOutputTruncation,
865                            parentOutput.TestOutputTruncation);
866       InheritOptionalValue(output.MaxTestNameWidth,
867                            parentOutput.MaxTestNameWidth);
868     } else {
869       preset.Output = parent.Output;
870     }
871   }
872
873   if (parent.Filter) {
874     if (parent.Filter->Include) {
875       if (preset.Filter && preset.Filter->Include) {
876         auto& include = *preset.Filter->Include;
877         const auto& parentInclude = *parent.Filter->Include;
878         InheritString(include.Name, parentInclude.Name);
879         InheritString(include.Label, parentInclude.Label);
880         InheritOptionalValue(include.Index, parentInclude.Index);
881       } else {
882         if (!preset.Filter) {
883           preset.Filter.emplace();
884         }
885         preset.Filter->Include = parent.Filter->Include;
886       }
887     }
888
889     if (parent.Filter->Exclude) {
890       if (preset.Filter && preset.Filter->Exclude) {
891         auto& exclude = *preset.Filter->Exclude;
892         const auto& parentExclude = *parent.Filter->Exclude;
893         InheritString(exclude.Name, parentExclude.Name);
894         InheritString(exclude.Label, parentExclude.Label);
895         InheritOptionalValue(exclude.Fixtures, parentExclude.Fixtures);
896       } else {
897         if (!preset.Filter) {
898           preset.Filter.emplace();
899         }
900         preset.Filter->Exclude = parent.Filter->Exclude;
901       }
902     }
903   }
904
905   if (parent.Execution) {
906     if (preset.Execution) {
907       auto& execution = *preset.Execution;
908       const auto& parentExecution = *parent.Execution;
909       InheritOptionalValue(execution.StopOnFailure,
910                            parentExecution.StopOnFailure);
911       InheritOptionalValue(execution.EnableFailover,
912                            parentExecution.EnableFailover);
913       InheritOptionalValue(execution.Jobs, parentExecution.Jobs);
914       InheritString(execution.ResourceSpecFile,
915                     parentExecution.ResourceSpecFile);
916       InheritOptionalValue(execution.TestLoad, parentExecution.TestLoad);
917       InheritOptionalValue(execution.ShowOnly, parentExecution.ShowOnly);
918       InheritOptionalValue(execution.Repeat, parentExecution.Repeat);
919       InheritOptionalValue(execution.InteractiveDebugging,
920                            parentExecution.InteractiveDebugging);
921       InheritOptionalValue(execution.ScheduleRandom,
922                            parentExecution.ScheduleRandom);
923       InheritOptionalValue(execution.Timeout, parentExecution.Timeout);
924       InheritOptionalValue(execution.NoTestsAction,
925                            parentExecution.NoTestsAction);
926     } else {
927       preset.Execution = parent.Execution;
928     }
929   }
930
931   return ReadFileResult::READ_OK;
932 }
933
934 cmCMakePresetsGraph::ReadFileResult
935 cmCMakePresetsGraph::TestPreset::VisitPresetAfterInherit(int /* version */)
936 {
937   auto& preset = *this;
938   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
939     return ReadFileResult::INVALID_PRESET;
940   }
941   return ReadFileResult::READ_OK;
942 }
943
944 cmCMakePresetsGraph::ReadFileResult
945 cmCMakePresetsGraph::PackagePreset::VisitPresetInherit(
946   const cmCMakePresetsGraph::Preset& parentPreset)
947 {
948   auto& preset = *this;
949   const PackagePreset& parent =
950     static_cast<const PackagePreset&>(parentPreset);
951
952   InheritString(preset.ConfigurePreset, parent.ConfigurePreset);
953   InheritOptionalValue(preset.InheritConfigureEnvironment,
954                        parent.InheritConfigureEnvironment);
955   InheritVector(preset.Generators, parent.Generators);
956   InheritVector(preset.Configurations, parent.Configurations);
957
958   for (auto const& v : parent.Variables) {
959     preset.Variables.insert(v);
960   }
961
962   InheritOptionalValue(preset.DebugOutput, parent.DebugOutput);
963   InheritOptionalValue(preset.VerboseOutput, parent.VerboseOutput);
964   InheritString(preset.PackageName, parent.PackageName);
965   InheritString(preset.PackageVersion, parent.PackageVersion);
966   InheritString(preset.PackageDirectory, parent.PackageDirectory);
967   InheritString(preset.VendorName, parent.VendorName);
968
969   return ReadFileResult::READ_OK;
970 }
971
972 cmCMakePresetsGraph::ReadFileResult
973 cmCMakePresetsGraph::PackagePreset::VisitPresetAfterInherit(int /* version */)
974 {
975   auto& preset = *this;
976   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
977     return ReadFileResult::INVALID_PRESET;
978   }
979   return ReadFileResult::READ_OK;
980 }
981
982 cmCMakePresetsGraph::ReadFileResult
983 cmCMakePresetsGraph::WorkflowPreset::VisitPresetInherit(
984   const cmCMakePresetsGraph::Preset& /*parentPreset*/)
985 {
986   return ReadFileResult::READ_OK;
987 }
988
989 cmCMakePresetsGraph::ReadFileResult
990 cmCMakePresetsGraph::WorkflowPreset::VisitPresetAfterInherit(int /* version */)
991 {
992   return ReadFileResult::READ_OK;
993 }
994
995 std::string cmCMakePresetsGraph::GetFilename(const std::string& sourceDir)
996 {
997   return cmStrCat(sourceDir, "/CMakePresets.json");
998 }
999
1000 std::string cmCMakePresetsGraph::GetUserFilename(const std::string& sourceDir)
1001 {
1002   return cmStrCat(sourceDir, "/CMakeUserPresets.json");
1003 }
1004
1005 cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadProjectPresets(
1006   const std::string& sourceDir, bool allowNoFiles)
1007 {
1008   this->SourceDir = sourceDir;
1009   this->ClearPresets();
1010
1011   auto result = this->ReadProjectPresetsInternal(allowNoFiles);
1012   if (result != ReadFileResult::READ_OK) {
1013     this->ClearPresets();
1014   }
1015
1016   return result;
1017 }
1018
1019 cmCMakePresetsGraph::ReadFileResult
1020 cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
1021 {
1022   bool haveOneFile = false;
1023
1024   File* file;
1025   std::string filename = GetUserFilename(this->SourceDir);
1026   std::vector<File*> inProgressFiles;
1027   if (cmSystemTools::FileExists(filename)) {
1028     auto result =
1029       this->ReadJSONFile(filename, RootType::User, ReadReason::Root,
1030                          inProgressFiles, file, this->errors);
1031     if (result != ReadFileResult::READ_OK) {
1032       return result;
1033     }
1034     haveOneFile = true;
1035   } else {
1036     filename = GetFilename(this->SourceDir);
1037     if (cmSystemTools::FileExists(filename)) {
1038       auto result =
1039         this->ReadJSONFile(filename, RootType::Project, ReadReason::Root,
1040                            inProgressFiles, file, this->errors);
1041       if (result != ReadFileResult::READ_OK) {
1042         return result;
1043       }
1044       haveOneFile = true;
1045     }
1046   }
1047   assert(inProgressFiles.empty());
1048
1049   if (!haveOneFile) {
1050     return allowNoFiles ? ReadFileResult::READ_OK
1051                         : ReadFileResult::FILE_NOT_FOUND;
1052   }
1053
1054   CHECK_OK(ComputePresetInheritance(this->ConfigurePresets, *this));
1055   CHECK_OK(ComputePresetInheritance(this->BuildPresets, *this));
1056   CHECK_OK(ComputePresetInheritance(this->TestPresets, *this));
1057   CHECK_OK(ComputePresetInheritance(this->PackagePresets, *this));
1058   CHECK_OK(ComputePresetInheritance(this->WorkflowPresets, *this));
1059
1060   for (auto& it : this->ConfigurePresets) {
1061     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
1062       return ReadFileResult::INVALID_MACRO_EXPANSION;
1063     }
1064   }
1065
1066   for (auto& it : this->BuildPresets) {
1067     if (!it.second.Unexpanded.Hidden) {
1068       const auto configurePreset =
1069         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
1070       if (configurePreset == this->ConfigurePresets.end()) {
1071         return ReadFileResult::INVALID_CONFIGURE_PRESET;
1072       }
1073       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
1074             configurePreset->second.Unexpanded.OriginFile)) {
1075         return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
1076       }
1077
1078       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
1079         it.second.Unexpanded.Environment.insert(
1080           configurePreset->second.Unexpanded.Environment.begin(),
1081           configurePreset->second.Unexpanded.Environment.end());
1082       }
1083     }
1084
1085     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
1086       return ReadFileResult::INVALID_MACRO_EXPANSION;
1087     }
1088   }
1089
1090   for (auto& it : this->TestPresets) {
1091     if (!it.second.Unexpanded.Hidden) {
1092       const auto configurePreset =
1093         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
1094       if (configurePreset == this->ConfigurePresets.end()) {
1095         return ReadFileResult::INVALID_CONFIGURE_PRESET;
1096       }
1097       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
1098             configurePreset->second.Unexpanded.OriginFile)) {
1099         return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
1100       }
1101
1102       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
1103         it.second.Unexpanded.Environment.insert(
1104           configurePreset->second.Unexpanded.Environment.begin(),
1105           configurePreset->second.Unexpanded.Environment.end());
1106       }
1107     }
1108
1109     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
1110       return ReadFileResult::INVALID_MACRO_EXPANSION;
1111     }
1112   }
1113
1114   for (auto& it : this->PackagePresets) {
1115     if (!it.second.Unexpanded.Hidden) {
1116       const auto configurePreset =
1117         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
1118       if (configurePreset == this->ConfigurePresets.end()) {
1119         return ReadFileResult::INVALID_CONFIGURE_PRESET;
1120       }
1121       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
1122             configurePreset->second.Unexpanded.OriginFile)) {
1123         return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
1124       }
1125
1126       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
1127         it.second.Unexpanded.Environment.insert(
1128           configurePreset->second.Unexpanded.Environment.begin(),
1129           configurePreset->second.Unexpanded.Environment.end());
1130       }
1131     }
1132
1133     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
1134       return ReadFileResult::INVALID_MACRO_EXPANSION;
1135     }
1136   }
1137
1138   for (auto& it : this->WorkflowPresets) {
1139     using Type = WorkflowPreset::WorkflowStep::Type;
1140
1141     const ConfigurePreset* configurePreset = nullptr;
1142     for (auto const& step : it.second.Unexpanded.Steps) {
1143       if (configurePreset == nullptr && step.PresetType != Type::Configure) {
1144         return ReadFileResult::INVALID_WORKFLOW_STEPS;
1145       }
1146       if (configurePreset != nullptr && step.PresetType == Type::Configure) {
1147         return ReadFileResult::INVALID_WORKFLOW_STEPS;
1148       }
1149
1150       ReadFileResult result;
1151       switch (step.PresetType) {
1152         case Type::Configure:
1153           result = TryReachPresetFromWorkflow(
1154             it.second.Unexpanded, this->ConfigurePresets, step.PresetName,
1155             configurePreset);
1156           break;
1157         case Type::Build:
1158           result = TryReachPresetFromWorkflow(
1159             it.second.Unexpanded, this->BuildPresets, step.PresetName,
1160             configurePreset);
1161           break;
1162         case Type::Test:
1163           result =
1164             TryReachPresetFromWorkflow(it.second.Unexpanded, this->TestPresets,
1165                                        step.PresetName, configurePreset);
1166           break;
1167         case Type::Package:
1168           result = TryReachPresetFromWorkflow(
1169             it.second.Unexpanded, this->PackagePresets, step.PresetName,
1170             configurePreset);
1171           break;
1172       }
1173       if (result != ReadFileResult::READ_OK) {
1174         return result;
1175       }
1176     }
1177
1178     if (configurePreset == nullptr) {
1179       return ReadFileResult::INVALID_WORKFLOW_STEPS;
1180     }
1181
1182     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
1183       return ReadFileResult::INVALID_MACRO_EXPANSION;
1184     }
1185   }
1186
1187   return ReadFileResult::READ_OK;
1188 }
1189
1190 const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
1191 {
1192   switch (result) {
1193     case ReadFileResult::READ_OK:
1194       return "OK";
1195     case ReadFileResult::FILE_NOT_FOUND:
1196       return "File not found";
1197     case ReadFileResult::JSON_PARSE_ERROR:
1198       return "JSON parse error";
1199     case ReadFileResult::INVALID_ROOT:
1200       return "Invalid root object";
1201     case ReadFileResult::NO_VERSION:
1202       return "No \"version\" field";
1203     case ReadFileResult::INVALID_VERSION:
1204       return "Invalid \"version\" field";
1205     case ReadFileResult::UNRECOGNIZED_VERSION:
1206       return "Unrecognized \"version\" field";
1207     case ReadFileResult::INVALID_CMAKE_VERSION:
1208       return "Invalid \"cmakeMinimumRequired\" field";
1209     case ReadFileResult::UNRECOGNIZED_CMAKE_VERSION:
1210       return "\"cmakeMinimumRequired\" version too new";
1211     case ReadFileResult::INVALID_PRESETS:
1212       return "Invalid \"configurePresets\" field";
1213     case ReadFileResult::INVALID_PRESET:
1214       return "Invalid preset";
1215     case ReadFileResult::INVALID_VARIABLE:
1216       return "Invalid CMake variable definition";
1217     case ReadFileResult::DUPLICATE_PRESETS:
1218       return "Duplicate presets";
1219     case ReadFileResult::CYCLIC_PRESET_INHERITANCE:
1220       return "Cyclic preset inheritance";
1221     case ReadFileResult::INHERITED_PRESET_UNREACHABLE_FROM_FILE:
1222       return "Inherited preset is unreachable from preset's file";
1223     case ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE:
1224       return "Configure preset is unreachable from preset's file";
1225     case ReadFileResult::INVALID_MACRO_EXPANSION:
1226       return "Invalid macro expansion";
1227     case ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED:
1228       return "File version must be 2 or higher for build and test preset "
1229              "support.";
1230     case ReadFileResult::PACKAGE_PRESETS_UNSUPPORTED:
1231       return "File version must be 6 or higher for package preset support";
1232     case ReadFileResult::WORKFLOW_PRESETS_UNSUPPORTED:
1233       return "File version must be 6 or higher for workflow preset support";
1234     case ReadFileResult::INCLUDE_UNSUPPORTED:
1235       return "File version must be 4 or higher for include support";
1236     case ReadFileResult::INVALID_INCLUDE:
1237       return "Invalid \"include\" field";
1238     case ReadFileResult::INVALID_CONFIGURE_PRESET:
1239       return "Invalid \"configurePreset\" field";
1240     case ReadFileResult::INSTALL_PREFIX_UNSUPPORTED:
1241       return "File version must be 3 or higher for installDir preset "
1242              "support.";
1243     case ReadFileResult::INVALID_CONDITION:
1244       return "Invalid preset condition";
1245     case ReadFileResult::CONDITION_UNSUPPORTED:
1246       return "File version must be 3 or higher for condition support";
1247     case ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED:
1248       return "File version must be 3 or higher for toolchainFile preset "
1249              "support.";
1250     case ReadFileResult::CYCLIC_INCLUDE:
1251       return "Cyclic include among preset files";
1252     case ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED:
1253       return "File version must be 5 or higher for testOutputTruncation "
1254              "preset support.";
1255     case ReadFileResult::INVALID_WORKFLOW_STEPS:
1256       return "Invalid workflow steps";
1257     case ReadFileResult::WORKFLOW_STEP_UNREACHABLE_FROM_FILE:
1258       return "Workflow step is unreachable from preset's file";
1259     case ReadFileResult::CTEST_JUNIT_UNSUPPORTED:
1260       return "File version must be 6 or higher for CTest JUnit output support";
1261   }
1262
1263   return "Unknown error";
1264 }
1265
1266 void cmCMakePresetsGraph::ClearPresets()
1267 {
1268   this->ConfigurePresets.clear();
1269   this->BuildPresets.clear();
1270   this->TestPresets.clear();
1271   this->PackagePresets.clear();
1272   this->WorkflowPresets.clear();
1273
1274   this->ConfigurePresetOrder.clear();
1275   this->BuildPresetOrder.clear();
1276   this->TestPresetOrder.clear();
1277   this->PackagePresetOrder.clear();
1278   this->WorkflowPresetOrder.clear();
1279
1280   this->Files.clear();
1281 }
1282
1283 void cmCMakePresetsGraph::printPrecedingNewline(PrintPrecedingNewline* newline)
1284 {
1285   if (newline) {
1286     if (*newline == PrintPrecedingNewline::True) {
1287       std::cout << std::endl;
1288     }
1289     *newline = PrintPrecedingNewline::True;
1290   }
1291 }
1292
1293 void cmCMakePresetsGraph::PrintPresets(
1294   const std::vector<const cmCMakePresetsGraph::Preset*>& presets)
1295 {
1296   if (presets.empty()) {
1297     return;
1298   }
1299
1300   auto longestPresetName =
1301     std::max_element(presets.begin(), presets.end(),
1302                      [](const cmCMakePresetsGraph::Preset* a,
1303                         const cmCMakePresetsGraph::Preset* b) {
1304                        return a->Name.length() < b->Name.length();
1305                      });
1306   auto longestLength = (*longestPresetName)->Name.length();
1307
1308   for (const auto* preset : presets) {
1309     std::cout << "  \"" << preset->Name << '"';
1310     const auto& description = preset->DisplayName;
1311     if (!description.empty()) {
1312       for (std::size_t i = 0; i < longestLength - preset->Name.length(); ++i) {
1313         std::cout << ' ';
1314       }
1315       std::cout << " - " << description;
1316     }
1317     std::cout << '\n';
1318   }
1319 }
1320
1321 void cmCMakePresetsGraph::PrintConfigurePresetList(
1322   PrintPrecedingNewline* newline) const
1323 {
1324   PrintConfigurePresetList([](const ConfigurePreset&) { return true; },
1325                            newline);
1326 }
1327
1328 void cmCMakePresetsGraph::PrintConfigurePresetList(
1329   const std::function<bool(const ConfigurePreset&)>& filter,
1330   PrintPrecedingNewline* newline) const
1331 {
1332   std::vector<const cmCMakePresetsGraph::Preset*> presets;
1333   for (auto const& p : this->ConfigurePresetOrder) {
1334     auto const& preset = this->ConfigurePresets.at(p);
1335     if (!preset.Unexpanded.Hidden && preset.Expanded &&
1336         preset.Expanded->ConditionResult && filter(preset.Unexpanded)) {
1337       presets.push_back(
1338         static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
1339     }
1340   }
1341
1342   if (!presets.empty()) {
1343     printPrecedingNewline(newline);
1344     std::cout << "Available configure presets:\n\n";
1345     cmCMakePresetsGraph::PrintPresets(presets);
1346   }
1347 }
1348
1349 void cmCMakePresetsGraph::PrintBuildPresetList(
1350   PrintPrecedingNewline* newline) const
1351 {
1352   std::vector<const cmCMakePresetsGraph::Preset*> presets;
1353   for (auto const& p : this->BuildPresetOrder) {
1354     auto const& preset = this->BuildPresets.at(p);
1355     if (!preset.Unexpanded.Hidden && preset.Expanded &&
1356         preset.Expanded->ConditionResult) {
1357       presets.push_back(
1358         static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
1359     }
1360   }
1361
1362   if (!presets.empty()) {
1363     printPrecedingNewline(newline);
1364     std::cout << "Available build presets:\n\n";
1365     cmCMakePresetsGraph::PrintPresets(presets);
1366   }
1367 }
1368
1369 void cmCMakePresetsGraph::PrintTestPresetList(
1370   PrintPrecedingNewline* newline) const
1371 {
1372   std::vector<const cmCMakePresetsGraph::Preset*> presets;
1373   for (auto const& p : this->TestPresetOrder) {
1374     auto const& preset = this->TestPresets.at(p);
1375     if (!preset.Unexpanded.Hidden && preset.Expanded &&
1376         preset.Expanded->ConditionResult) {
1377       presets.push_back(
1378         static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
1379     }
1380   }
1381
1382   if (!presets.empty()) {
1383     printPrecedingNewline(newline);
1384     std::cout << "Available test presets:\n\n";
1385     cmCMakePresetsGraph::PrintPresets(presets);
1386   }
1387 }
1388
1389 void cmCMakePresetsGraph::PrintPackagePresetList(
1390   PrintPrecedingNewline* newline) const
1391 {
1392   this->PrintPackagePresetList([](const PackagePreset&) { return true; },
1393                                newline);
1394 }
1395
1396 void cmCMakePresetsGraph::PrintPackagePresetList(
1397   const std::function<bool(const PackagePreset&)>& filter,
1398   PrintPrecedingNewline* newline) const
1399 {
1400   std::vector<const cmCMakePresetsGraph::Preset*> presets;
1401   for (auto const& p : this->PackagePresetOrder) {
1402     auto const& preset = this->PackagePresets.at(p);
1403     if (!preset.Unexpanded.Hidden && preset.Expanded &&
1404         preset.Expanded->ConditionResult && filter(preset.Unexpanded)) {
1405       presets.push_back(
1406         static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
1407     }
1408   }
1409
1410   if (!presets.empty()) {
1411     printPrecedingNewline(newline);
1412     std::cout << "Available package presets:\n\n";
1413     cmCMakePresetsGraph::PrintPresets(presets);
1414   }
1415 }
1416
1417 void cmCMakePresetsGraph::PrintWorkflowPresetList(
1418   PrintPrecedingNewline* newline) const
1419 {
1420   std::vector<const cmCMakePresetsGraph::Preset*> presets;
1421   for (auto const& p : this->WorkflowPresetOrder) {
1422     auto const& preset = this->WorkflowPresets.at(p);
1423     if (!preset.Unexpanded.Hidden && preset.Expanded &&
1424         preset.Expanded->ConditionResult) {
1425       presets.push_back(
1426         static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
1427     }
1428   }
1429
1430   if (!presets.empty()) {
1431     printPrecedingNewline(newline);
1432     std::cout << "Available workflow presets:\n\n";
1433     cmCMakePresetsGraph::PrintPresets(presets);
1434   }
1435 }
1436
1437 void cmCMakePresetsGraph::PrintAllPresets() const
1438 {
1439   PrintPrecedingNewline newline = PrintPrecedingNewline::False;
1440   this->PrintConfigurePresetList(&newline);
1441   this->PrintBuildPresetList(&newline);
1442   this->PrintTestPresetList(&newline);
1443   this->PrintPackagePresetList(&newline);
1444   this->PrintWorkflowPresetList(&newline);
1445 }