Просмотр исходного кода

Merge topic 'cmake-presets-include'

26a5512c0f CMakePresets: Add include field
a239f23a98 Refactor: Generalize file graph in CMakePresets
84d440caac Refactor: Split JSON processing into configure, build, and test presets
fd6ea2f67f Refactor: Rename cmCMakePresetsFile to cmCMakePresetsGraph

Acked-by: Kitware Robot <[email protected]>
Tested-by: buildbot <[email protected]>
Merge-request: !6829
Brad King 4 лет назад
Родитель
Сommit
39eabc17c4
58 измененных файлов с 1983 добавлено и 1331 удалено
  1. 25 3
      Help/manual/cmake-presets.7.rst
  2. 1 1
      Help/manual/presets/example.json
  3. 22 0
      Help/manual/presets/schema.json
  4. 6 0
      Help/release/dev/cmake-presets-include.rst
  5. 7 4
      Source/CMakeLists.txt
  6. 2 2
      Source/QtDialog/CMakeSetupDialog.cxx
  7. 1 1
      Source/QtDialog/CMakeSetupDialog.h
  8. 11 11
      Source/QtDialog/QCMake.cxx
  9. 6 6
      Source/QtDialog/QCMake.h
  10. 1 1
      Source/QtDialog/QCMakePreset.h
  11. 0 112
      Source/cmCMakePresetsFileInternal.h
  12. 0 1032
      Source/cmCMakePresetsFileReadJSON.cxx
  13. 125 108
      Source/cmCMakePresetsGraph.cxx
  14. 36 8
      Source/cmCMakePresetsGraph.h
  15. 163 0
      Source/cmCMakePresetsGraphInternal.h
  16. 641 0
      Source/cmCMakePresetsGraphReadJSON.cxx
  17. 75 0
      Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx
  18. 228 0
      Source/cmCMakePresetsGraphReadJSONConfigurePresets.cxx
  19. 360 0
      Source/cmCMakePresetsGraphReadJSONTestPresets.cxx
  20. 17 17
      Source/cmCTest.cxx
  21. 21 21
      Source/cmake.cxx
  22. 3 3
      Source/cmake.h
  23. 8 0
      Tests/RunCMake/CMakePresets/Include-stdout.txt
  24. 16 0
      Tests/RunCMake/CMakePresets/Include.json.in
  25. 8 0
      Tests/RunCMake/CMakePresets/IncludeCommon.json.in
  26. 1 0
      Tests/RunCMake/CMakePresets/IncludeCycle-result.txt
  27. 2 0
      Tests/RunCMake/CMakePresets/IncludeCycle-stderr.txt
  28. 11 0
      Tests/RunCMake/CMakePresets/IncludeCycle.json.in
  29. 1 0
      Tests/RunCMake/CMakePresets/IncludeCycle3Files-result.txt
  30. 2 0
      Tests/RunCMake/CMakePresets/IncludeCycle3Files-stderr.txt
  31. 6 0
      Tests/RunCMake/CMakePresets/IncludeCycle3Files.json.in
  32. 6 0
      Tests/RunCMake/CMakePresets/IncludeCycle3Files2.json.in
  33. 6 0
      Tests/RunCMake/CMakePresets/IncludeCycle3Files3.json.in
  34. 3 0
      Tests/RunCMake/CMakePresets/IncludeCycleUser.json.in
  35. 1 0
      Tests/RunCMake/CMakePresets/IncludeNotFound-result.txt
  36. 2 0
      Tests/RunCMake/CMakePresets/IncludeNotFound-stderr.txt
  37. 11 0
      Tests/RunCMake/CMakePresets/IncludeNotFound.json.in
  38. 1 0
      Tests/RunCMake/CMakePresets/IncludeOutsideProject-result.txt
  39. 2 0
      Tests/RunCMake/CMakePresets/IncludeOutsideProject-stderr.txt
  40. 11 0
      Tests/RunCMake/CMakePresets/IncludeOutsideProject.json.in
  41. 3 0
      Tests/RunCMake/CMakePresets/IncludeOutsideProjectInclude.json
  42. 6 0
      Tests/RunCMake/CMakePresets/IncludeOutsideProjectIntermediate.json.in
  43. 6 0
      Tests/RunCMake/CMakePresets/IncludeOutsideProjectUser.json.in
  44. 15 0
      Tests/RunCMake/CMakePresets/IncludeUser.json.in
  45. 8 0
      Tests/RunCMake/CMakePresets/IncludeUserCommon.json.in
  46. 0 0
      Tests/RunCMake/CMakePresets/IncludeUserOutsideProject.cmake
  47. 11 0
      Tests/RunCMake/CMakePresets/IncludeUserOutsideProjectUser.json.in
  48. 1 0
      Tests/RunCMake/CMakePresets/IncludeV3-result.txt
  49. 2 0
      Tests/RunCMake/CMakePresets/IncludeV3-stderr.txt
  50. 4 0
      Tests/RunCMake/CMakePresets/IncludeV3.json.in
  51. 1 0
      Tests/RunCMake/CMakePresets/IncludeV4V3-result.txt
  52. 2 0
      Tests/RunCMake/CMakePresets/IncludeV4V3-stderr.txt
  53. 6 0
      Tests/RunCMake/CMakePresets/IncludeV4V3.json.in
  54. 4 0
      Tests/RunCMake/CMakePresets/IncludeV4V3Extra.json.in
  55. 45 0
      Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
  56. 1 1
      Tests/RunCMake/CMakePresets/UserInheritance-stderr.txt
  57. 7 0
      Tests/RunCMake/CMakePresets/check.cmake
  58. 12 0
      Tests/RunCMake/CMakePresets/subdir/CMakePresets.json.in

+ 25 - 3
Help/manual/cmake-presets.7.rst

@@ -12,9 +12,10 @@ Introduction
 
 One problem that CMake users often face is sharing settings with other people
 for common ways to configure a project. This may be done to support CI builds,
-or for users who frequently use the same build. CMake supports two files,
+or for users who frequently use the same build. CMake supports two main files,
 ``CMakePresets.json`` and ``CMakeUserPresets.json``, that allow users to
-specify common configure options and share them with others.
+specify common configure options and share them with others. CMake also
+supports files included with the ``include`` field.
 
 ``CMakePresets.json`` and ``CMakeUserPresets.json`` live in the project's root
 directory. They both have exactly the same format, and both are optional
@@ -26,6 +27,21 @@ builds. ``CMakePresets.json`` may be checked into a version control system, and
 is using Git, ``CMakePresets.json`` may be tracked, and
 ``CMakeUserPresets.json`` should be added to the ``.gitignore``.
 
+``CMakePresets.json`` and ``CMakeUserPresets.json`` can include other files
+with the ``include`` field in file version ``4`` and later. Files included by
+these files can also include other files. If a preset file contains presets
+that inherit from presets in another file, the file must include the other file
+either directly or indirectly. Include cycles are not allowed among files (if
+``a.json`` includes ``b.json``, ``b.json`` cannot include ``a.json``). However,
+a file may be included multiple times from the same file or from different
+files. If ``CMakePresets.json`` and ``CMakeUserPresets.json`` are both present,
+``CMakeUserPresets.json`` implicitly includes ``CMakePresets.json``, even with
+no ``include`` field, in all versions of the format. Files directly or
+indirectly included from ``CMakePresets.json`` must be inside the project
+directory. This restriction does not apply to ``CMakeUserPresets.json`` and
+files that it includes, unless those files are also included by
+``CMakePresets.json``.
+
 Format
 ======
 
@@ -39,7 +55,7 @@ The root object recognizes the following fields:
 ``version``
 
   A required integer representing the version of the JSON schema.
-  The supported versions are ``1``, ``2``, and ``3``.
+  The supported versions are ``1``, ``2``, ``3``, and ``4``.
 
 ``cmakeMinimumRequired``
 
@@ -82,6 +98,12 @@ The root object recognizes the following fields:
   An optional array of `Test Preset`_ objects.
   This is allowed in preset files specifying version ``2`` or above.
 
+``include``
+
+  An optional array of strings representing files to include. If the filenames
+  are not absolute, they are considered relative to the current file.
+  This is allowed in preset files specifying version ``4`` or above.
+
 Configure Preset
 ^^^^^^^^^^^^^^^^
 

+ 1 - 1
Help/manual/presets/example.json

@@ -1,5 +1,5 @@
 {
-  "version": 3,
+  "version": 4,
   "cmakeMinimumRequired": {
     "major": 3,
     "minor": 21,

+ 22 - 0
Help/manual/presets/schema.json

@@ -42,6 +42,21 @@
         "testPresets": { "$ref": "#/definitions/testPresetsV3"}
       },
       "additionalProperties": false
+    },
+    {
+      "properties": {
+        "version": {
+          "const": 4,
+          "description": "A required integer representing the version of the JSON schema."
+        },
+        "cmakeMinimumRequired": { "$ref": "#/definitions/cmakeMinimumRequired"},
+        "vendor": { "$ref": "#/definitions/vendor" },
+        "configurePresets": { "$ref": "#/definitions/configurePresetsV3"},
+        "buildPresets": { "$ref": "#/definitions/buildPresetsV3"},
+        "testPresets": { "$ref": "#/definitions/testPresetsV3"},
+        "include": { "$ref": "#/definitions/include"}
+      },
+      "additionalProperties": false
     }
   ],
   "required": [
@@ -1235,6 +1250,13 @@
           "description": "Null indicates that the condition always evaluates to true and is not inherited."
         }
       ]
+    },
+    "include": {
+      "type": "array",
+      "description": "An optional array of strings representing files to include. If the filenames are not absolute, they are considered relative to the current file.",
+      "items": {
+        "type": "string"
+      }
     }
   }
 }

+ 6 - 0
Help/release/dev/cmake-presets-include.rst

@@ -0,0 +1,6 @@
+cmake-presets-include
+---------------------
+
+* :manual:`cmake-presets(7)` files now support schema version ``4``.
+* :manual:`cmake-presets(7)` files now have an optional ``include`` field,
+  which allows the files to include other files.

+ 7 - 4
Source/CMakeLists.txt

@@ -161,10 +161,13 @@ set(SRCS
   cmCLocaleEnvironmentScope.cxx
   cmCMakePath.h
   cmCMakePath.cxx
-  cmCMakePresetsFile.cxx
-  cmCMakePresetsFile.h
-  cmCMakePresetsFileInternal.h
-  cmCMakePresetsFileReadJSON.cxx
+  cmCMakePresetsGraph.cxx
+  cmCMakePresetsGraph.h
+  cmCMakePresetsGraphInternal.h
+  cmCMakePresetsGraphReadJSON.cxx
+  cmCMakePresetsGraphReadJSONBuildPresets.cxx
+  cmCMakePresetsGraphReadJSONConfigurePresets.cxx
+  cmCMakePresetsGraphReadJSONTestPresets.cxx
   cmCommandArgumentParserHelper.cxx
   cmCommonTargetGenerator.cxx
   cmCommonTargetGenerator.h

+ 2 - 2
Source/QtDialog/CMakeSetupDialog.cxx

@@ -730,12 +730,12 @@ void CMakeSetupDialog::updatePreset(const QString& name)
 }
 
 void CMakeSetupDialog::showPresetLoadError(
-  const QString& dir, cmCMakePresetsFile::ReadFileResult result)
+  const QString& dir, cmCMakePresetsGraph::ReadFileResult result)
 {
   QMessageBox::warning(
     this, "Error Reading CMake Presets",
     QString::fromLocal8Bit("Could not read presets from %1: %2")
-      .arg(dir, cmCMakePresetsFile::ResultToString(result)));
+      .arg(dir, cmCMakePresetsGraph::ResultToString(result)));
 }
 
 void CMakeSetupDialog::doBinaryBrowse()

+ 1 - 1
Source/QtDialog/CMakeSetupDialog.h

@@ -60,7 +60,7 @@ protected slots:
   void updatePresets(const QVector<QCMakePreset>& presets);
   void updatePreset(const QString& name);
   void showPresetLoadError(const QString& dir,
-                           cmCMakePresetsFile::ReadFileResult result);
+                           cmCMakePresetsGraph::ReadFileResult result);
   void showProgress(const QString& msg, float percent);
   void setEnabledState(bool);
   bool setupFirstConfigure();

+ 11 - 11
Source/QtDialog/QCMake.cxx

@@ -32,7 +32,7 @@ QCMake::QCMake(QObject* p)
   qRegisterMetaType<QCMakePropertyList>();
   qRegisterMetaType<QProcessEnvironment>();
   qRegisterMetaType<QVector<QCMakePreset>>();
-  qRegisterMetaType<cmCMakePresetsFile::ReadFileResult>();
+  qRegisterMetaType<cmCMakePresetsGraph::ReadFileResult>();
 
   cmSystemTools::DisableRunCommandOutput();
   cmSystemTools::SetRunCommandHideConsole(true);
@@ -69,9 +69,9 @@ QCMake::QCMake(QObject* p)
   connect(&this->LoadPresetsTimer, &QTimer::timeout, this, [this]() {
     this->loadPresets();
     if (!this->PresetName.isEmpty() &&
-        this->CMakePresetsFile.ConfigurePresets.find(
+        this->CMakePresetsGraph.ConfigurePresets.find(
           std::string(this->PresetName.toLocal8Bit())) ==
-          this->CMakePresetsFile.ConfigurePresets.end()) {
+          this->CMakePresetsGraph.ConfigurePresets.end()) {
       this->setPreset(QString{});
     }
   });
@@ -159,7 +159,7 @@ void QCMake::setPreset(const QString& name, bool setBinary)
     if (!name.isNull()) {
       std::string presetName(name.toLocal8Bit());
       auto const& expandedPreset =
-        this->CMakePresetsFile.ConfigurePresets[presetName].Expanded;
+        this->CMakePresetsGraph.ConfigurePresets[presetName].Expanded;
       if (expandedPreset) {
         if (setBinary && !expandedPreset->BinaryDir.empty()) {
           QString binaryDir =
@@ -427,7 +427,7 @@ QCMakePropertyList QCMake::properties() const
   if (!this->PresetName.isNull()) {
     std::string presetName(this->PresetName.toLocal8Bit());
     auto const& p =
-      this->CMakePresetsFile.ConfigurePresets.at(presetName).Expanded;
+      this->CMakePresetsGraph.ConfigurePresets.at(presetName).Expanded;
     if (p) {
       for (auto const& v : p->CacheVariables) {
         if (!v.second) {
@@ -533,17 +533,17 @@ void QCMake::setUpEnvironment() const
 
 void QCMake::loadPresets()
 {
-  auto result = this->CMakePresetsFile.ReadProjectPresets(
+  auto result = this->CMakePresetsGraph.ReadProjectPresets(
     this->SourceDirectory.toLocal8Bit().data(), true);
   if (result != this->LastLoadPresetsResult &&
-      result != cmCMakePresetsFile::ReadFileResult::READ_OK) {
+      result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
     emit this->presetLoadError(this->SourceDirectory, result);
   }
   this->LastLoadPresetsResult = result;
 
   QVector<QCMakePreset> presets;
-  for (auto const& name : this->CMakePresetsFile.ConfigurePresetOrder) {
-    auto const& it = this->CMakePresetsFile.ConfigurePresets[name];
+  for (auto const& name : this->CMakePresetsGraph.ConfigurePresetOrder) {
+    auto const& it = this->CMakePresetsGraph.ConfigurePresets[name];
     auto const& p = it.Unexpanded;
     if (p.Hidden) {
       continue;
@@ -556,10 +556,10 @@ void QCMake::loadPresets()
     preset.generator = QString::fromLocal8Bit(p.Generator.data());
     preset.architecture = QString::fromLocal8Bit(p.Architecture.data());
     preset.setArchitecture = !p.ArchitectureStrategy ||
-      p.ArchitectureStrategy == cmCMakePresetsFile::ArchToolsetStrategy::Set;
+      p.ArchitectureStrategy == cmCMakePresetsGraph::ArchToolsetStrategy::Set;
     preset.toolset = QString::fromLocal8Bit(p.Toolset.data());
     preset.setToolset = !p.ToolsetStrategy ||
-      p.ToolsetStrategy == cmCMakePresetsFile::ArchToolsetStrategy::Set;
+      p.ToolsetStrategy == cmCMakePresetsGraph::ArchToolsetStrategy::Set;
     preset.enabled = it.Expanded && it.Expanded->ConditionResult &&
       std::find_if(this->AvailableGenerators.begin(),
                    this->AvailableGenerators.end(),

+ 6 - 6
Source/QtDialog/QCMake.h

@@ -4,7 +4,7 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
-#include "cmCMakePresetsFile.h"
+#include "cmCMakePresetsGraph.h"
 #include "cmake.h"
 
 #ifdef _MSC_VER
@@ -60,7 +60,7 @@ using QCMakePropertyList = QList<QCMakeProperty>;
 Q_DECLARE_METATYPE(QCMakeProperty)
 Q_DECLARE_METATYPE(QCMakePropertyList)
 Q_DECLARE_METATYPE(QProcessEnvironment)
-Q_DECLARE_METATYPE(cmCMakePresetsFile::ReadFileResult)
+Q_DECLARE_METATYPE(cmCMakePresetsGraph::ReadFileResult)
 
 /// Qt API for CMake library.
 /// Wrapper like class allows for easier integration with
@@ -159,7 +159,7 @@ signals:
   void presetChanged(const QString& name);
   /// signal when there's an error reading the presets files
   void presetLoadError(const QString& dir,
-                       cmCMakePresetsFile::ReadFileResult error);
+                       cmCMakePresetsGraph::ReadFileResult error);
   /// signal when uninitialized warning changes
   void warnUninitializedModeChanged(bool value);
   /// signal for progress events
@@ -202,9 +202,9 @@ protected:
   QString Platform;
   QString Toolset;
   std::vector<cmake::GeneratorInfo> AvailableGenerators;
-  cmCMakePresetsFile CMakePresetsFile;
-  cmCMakePresetsFile::ReadFileResult LastLoadPresetsResult =
-    cmCMakePresetsFile::ReadFileResult::READ_OK;
+  cmCMakePresetsGraph CMakePresetsGraph;
+  cmCMakePresetsGraph::ReadFileResult LastLoadPresetsResult =
+    cmCMakePresetsGraph::ReadFileResult::READ_OK;
   QString PresetName;
   QString CMakeExecutable;
   QAtomicInt InterruptFlag;

+ 1 - 1
Source/QtDialog/QCMakePreset.h

@@ -5,7 +5,7 @@
 #include <QString>
 #include <QVariant>
 
-#include "cmCMakePresetsFile.h"
+#include "cmCMakePresetsGraph.h"
 
 class QCMakePreset
 {

+ 0 - 112
Source/cmCMakePresetsFileInternal.h

@@ -1,112 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#include <memory>
-
-#include "cmCMakePresetsFile.h"
-
-#define CHECK_OK(expr)                                                        \
-  do {                                                                        \
-    auto _result = expr;                                                      \
-    if (_result != ReadFileResult::READ_OK)                                   \
-      return _result;                                                         \
-  } while (false)
-
-namespace cmCMakePresetsFileInternal {
-enum class ExpandMacroResult
-{
-  Ok,
-  Ignore,
-  Error,
-};
-
-using MacroExpander = std::function<ExpandMacroResult(
-  const std::string&, const std::string&, std::string&, int version)>;
-}
-
-class cmCMakePresetsFile::Condition
-{
-public:
-  virtual ~Condition() = default;
-
-  virtual bool Evaluate(
-    const std::vector<cmCMakePresetsFileInternal::MacroExpander>& expanders,
-    int version, cm::optional<bool>& out) const = 0;
-  virtual bool IsNull() const { return false; }
-};
-
-namespace cmCMakePresetsFileInternal {
-
-class NullCondition : public cmCMakePresetsFile::Condition
-{
-  bool Evaluate(const std::vector<MacroExpander>& /*expanders*/,
-                int /*version*/, cm::optional<bool>& out) const override
-  {
-    out = true;
-    return true;
-  }
-
-  bool IsNull() const override { return true; }
-};
-
-class ConstCondition : public cmCMakePresetsFile::Condition
-{
-public:
-  bool Evaluate(const std::vector<MacroExpander>& /*expanders*/,
-                int /*version*/, cm::optional<bool>& out) const override
-  {
-    out = this->Value;
-    return true;
-  }
-
-  bool Value;
-};
-
-class EqualsCondition : public cmCMakePresetsFile::Condition
-{
-public:
-  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
-                cm::optional<bool>& out) const override;
-
-  std::string Lhs;
-  std::string Rhs;
-};
-
-class InListCondition : public cmCMakePresetsFile::Condition
-{
-public:
-  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
-                cm::optional<bool>& out) const override;
-
-  std::string String;
-  std::vector<std::string> List;
-};
-
-class MatchesCondition : public cmCMakePresetsFile::Condition
-{
-public:
-  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
-                cm::optional<bool>& out) const override;
-
-  std::string String;
-  std::string Regex;
-};
-
-class AnyAllOfCondition : public cmCMakePresetsFile::Condition
-{
-public:
-  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
-                cm::optional<bool>& out) const override;
-
-  std::vector<std::unique_ptr<Condition>> Conditions;
-  bool StopValue;
-};
-
-class NotCondition : public cmCMakePresetsFile::Condition
-{
-public:
-  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
-                cm::optional<bool>& out) const override;
-
-  std::unique_ptr<Condition> SubCondition;
-};
-}

+ 0 - 1032
Source/cmCMakePresetsFileReadJSON.cxx

@@ -1,1032 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#include <functional>
-#include <map>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <cm/memory>
-#include <cm/optional>
-#include <cmext/string_view>
-
-#include <cm3p/json/reader.h>
-#include <cm3p/json/value.h>
-
-#include "cmsys/FStream.hxx"
-
-#include "cmCMakePresetsFile.h"
-#include "cmCMakePresetsFileInternal.h"
-#include "cmJSONHelpers.h"
-#include "cmVersion.h"
-
-namespace {
-using ReadFileResult = cmCMakePresetsFile::ReadFileResult;
-using CacheVariable = cmCMakePresetsFile::CacheVariable;
-using ConfigurePreset = cmCMakePresetsFile::ConfigurePreset;
-using BuildPreset = cmCMakePresetsFile::BuildPreset;
-using TestPreset = cmCMakePresetsFile::TestPreset;
-using ArchToolsetStrategy = cmCMakePresetsFile::ArchToolsetStrategy;
-
-constexpr int MIN_VERSION = 1;
-constexpr int MAX_VERSION = 3;
-
-struct CMakeVersion
-{
-  unsigned int Major = 0;
-  unsigned int Minor = 0;
-  unsigned int Patch = 0;
-};
-
-struct RootPresets
-{
-  CMakeVersion CMakeMinimumRequired;
-  std::vector<cmCMakePresetsFile::ConfigurePreset> ConfigurePresets;
-  std::vector<cmCMakePresetsFile::BuildPreset> BuildPresets;
-  std::vector<cmCMakePresetsFile::TestPreset> TestPresets;
-};
-
-std::unique_ptr<cmCMakePresetsFileInternal::NotCondition> InvertCondition(
-  std::unique_ptr<cmCMakePresetsFile::Condition> condition)
-{
-  auto retval = cm::make_unique<cmCMakePresetsFileInternal::NotCondition>();
-  retval->SubCondition = std::move(condition);
-  return retval;
-}
-
-auto const ConditionStringHelper = cmJSONStringHelper<ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
-
-auto const ConditionBoolHelper = cmJSONBoolHelper<ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
-
-auto const ConditionStringListHelper =
-  cmJSONVectorHelper<std::string, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION,
-    ConditionStringHelper);
-
-auto const ConstConditionHelper =
-  cmJSONObjectHelper<cmCMakePresetsFileInternal::ConstCondition,
-                     ReadFileResult>(ReadFileResult::READ_OK,
-                                     ReadFileResult::INVALID_CONDITION, false)
-    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
-    .Bind("value"_s, &cmCMakePresetsFileInternal::ConstCondition::Value,
-          ConditionBoolHelper, true);
-
-auto const EqualsConditionHelper =
-  cmJSONObjectHelper<cmCMakePresetsFileInternal::EqualsCondition,
-                     ReadFileResult>(ReadFileResult::READ_OK,
-                                     ReadFileResult::INVALID_CONDITION, false)
-    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
-    .Bind("lhs"_s, &cmCMakePresetsFileInternal::EqualsCondition::Lhs,
-          ConditionStringHelper, true)
-    .Bind("rhs"_s, &cmCMakePresetsFileInternal::EqualsCondition::Rhs,
-          ConditionStringHelper, true);
-
-auto const InListConditionHelper =
-  cmJSONObjectHelper<cmCMakePresetsFileInternal::InListCondition,
-                     ReadFileResult>(ReadFileResult::READ_OK,
-                                     ReadFileResult::INVALID_CONDITION, false)
-    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
-    .Bind("string"_s, &cmCMakePresetsFileInternal::InListCondition::String,
-          ConditionStringHelper, true)
-    .Bind("list"_s, &cmCMakePresetsFileInternal::InListCondition::List,
-          ConditionStringListHelper, true);
-
-auto const MatchesConditionHelper =
-  cmJSONObjectHelper<cmCMakePresetsFileInternal::MatchesCondition,
-                     ReadFileResult>(ReadFileResult::READ_OK,
-                                     ReadFileResult::INVALID_CONDITION, false)
-    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
-    .Bind("string"_s, &cmCMakePresetsFileInternal::MatchesCondition::String,
-          ConditionStringHelper, true)
-    .Bind("regex"_s, &cmCMakePresetsFileInternal::MatchesCondition::Regex,
-          ConditionStringHelper, true);
-
-ReadFileResult SubConditionHelper(
-  std::unique_ptr<cmCMakePresetsFile::Condition>& out,
-  const Json::Value* value);
-
-auto const ListConditionVectorHelper =
-  cmJSONVectorHelper<std::unique_ptr<cmCMakePresetsFile::Condition>,
-                     ReadFileResult>(ReadFileResult::READ_OK,
-                                     ReadFileResult::INVALID_CONDITION,
-                                     SubConditionHelper);
-auto const AnyAllOfConditionHelper =
-  cmJSONObjectHelper<cmCMakePresetsFileInternal::AnyAllOfCondition,
-                     ReadFileResult>(ReadFileResult::READ_OK,
-                                     ReadFileResult::INVALID_CONDITION, false)
-    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
-    .Bind("conditions"_s,
-          &cmCMakePresetsFileInternal::AnyAllOfCondition::Conditions,
-          ListConditionVectorHelper);
-
-auto const NotConditionHelper =
-  cmJSONObjectHelper<cmCMakePresetsFileInternal::NotCondition, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
-    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
-    .Bind("condition"_s,
-          &cmCMakePresetsFileInternal::NotCondition::SubCondition,
-          SubConditionHelper);
-
-ReadFileResult ConditionHelper(
-  std::unique_ptr<cmCMakePresetsFile::Condition>& out,
-  const Json::Value* value)
-{
-  if (!value) {
-    out.reset();
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->isBool()) {
-    auto c = cm::make_unique<cmCMakePresetsFileInternal::ConstCondition>();
-    c->Value = value->asBool();
-    out = std::move(c);
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->isNull()) {
-    out = cm::make_unique<cmCMakePresetsFileInternal::NullCondition>();
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->isObject()) {
-    if (!value->isMember("type")) {
-      return ReadFileResult::INVALID_CONDITION;
-    }
-
-    if (!(*value)["type"].isString()) {
-      return ReadFileResult::INVALID_CONDITION;
-    }
-    auto type = (*value)["type"].asString();
-
-    if (type == "const") {
-      auto c = cm::make_unique<cmCMakePresetsFileInternal::ConstCondition>();
-      CHECK_OK(ConstConditionHelper(*c, value));
-      out = std::move(c);
-      return ReadFileResult::READ_OK;
-    }
-
-    if (type == "equals" || type == "notEquals") {
-      auto c = cm::make_unique<cmCMakePresetsFileInternal::EqualsCondition>();
-      CHECK_OK(EqualsConditionHelper(*c, value));
-      out = std::move(c);
-      if (type == "notEquals") {
-        out = InvertCondition(std::move(out));
-      }
-      return ReadFileResult::READ_OK;
-    }
-
-    if (type == "inList" || type == "notInList") {
-      auto c = cm::make_unique<cmCMakePresetsFileInternal::InListCondition>();
-      CHECK_OK(InListConditionHelper(*c, value));
-      out = std::move(c);
-      if (type == "notInList") {
-        out = InvertCondition(std::move(out));
-      }
-      return ReadFileResult::READ_OK;
-    }
-
-    if (type == "matches" || type == "notMatches") {
-      auto c = cm::make_unique<cmCMakePresetsFileInternal::MatchesCondition>();
-      CHECK_OK(MatchesConditionHelper(*c, value));
-      out = std::move(c);
-      if (type == "notMatches") {
-        out = InvertCondition(std::move(out));
-      }
-      return ReadFileResult::READ_OK;
-    }
-
-    if (type == "anyOf" || type == "allOf") {
-      auto c =
-        cm::make_unique<cmCMakePresetsFileInternal::AnyAllOfCondition>();
-      c->StopValue = (type == "anyOf");
-      CHECK_OK(AnyAllOfConditionHelper(*c, value));
-      out = std::move(c);
-      return ReadFileResult::READ_OK;
-    }
-
-    if (type == "not") {
-      auto c = cm::make_unique<cmCMakePresetsFileInternal::NotCondition>();
-      CHECK_OK(NotConditionHelper(*c, value));
-      out = std::move(c);
-      return ReadFileResult::READ_OK;
-    }
-  }
-
-  return ReadFileResult::INVALID_CONDITION;
-}
-
-ReadFileResult PresetConditionHelper(
-  std::shared_ptr<cmCMakePresetsFile::Condition>& out,
-  const Json::Value* value)
-{
-  std::unique_ptr<cmCMakePresetsFile::Condition> ptr;
-  auto result = ConditionHelper(ptr, value);
-  out = std::move(ptr);
-  return result;
-}
-
-ReadFileResult SubConditionHelper(
-  std::unique_ptr<cmCMakePresetsFile::Condition>& out,
-  const Json::Value* value)
-{
-  std::unique_ptr<cmCMakePresetsFile::Condition> ptr;
-  auto result = ConditionHelper(ptr, value);
-  if (ptr && ptr->IsNull()) {
-    return ReadFileResult::INVALID_CONDITION;
-  }
-  out = std::move(ptr);
-  return result;
-}
-
-cmJSONHelper<std::nullptr_t, ReadFileResult> VendorHelper(ReadFileResult error)
-{
-  return [error](std::nullptr_t& /*out*/,
-                 const Json::Value* value) -> ReadFileResult {
-    if (!value) {
-      return ReadFileResult::READ_OK;
-    }
-
-    if (!value->isObject()) {
-      return error;
-    }
-
-    return ReadFileResult::READ_OK;
-  };
-}
-
-auto const VersionIntHelper = cmJSONIntHelper<ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
-
-auto const VersionHelper = cmJSONRequiredHelper<int, ReadFileResult>(
-  ReadFileResult::NO_VERSION, VersionIntHelper);
-
-auto const RootVersionHelper =
-  cmJSONObjectHelper<int, ReadFileResult>(ReadFileResult::READ_OK,
-                                          ReadFileResult::INVALID_ROOT)
-    .Bind("version"_s, VersionHelper, false);
-
-auto const VariableStringHelper = cmJSONStringHelper<ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE);
-
-ReadFileResult VariableValueHelper(std::string& out, const Json::Value* value)
-{
-  if (!value) {
-    out.clear();
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->isBool()) {
-    out = value->asBool() ? "TRUE" : "FALSE";
-    return ReadFileResult::READ_OK;
-  }
-
-  return VariableStringHelper(out, value);
-}
-
-auto const VariableObjectHelper =
-  cmJSONObjectHelper<CacheVariable, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE, false)
-    .Bind("type"_s, &CacheVariable::Type, VariableStringHelper, false)
-    .Bind("value"_s, &CacheVariable::Value, VariableValueHelper);
-
-ReadFileResult VariableHelper(cm::optional<CacheVariable>& out,
-                              const Json::Value* value)
-{
-  if (value->isBool()) {
-    out = CacheVariable{
-      /*Type=*/"BOOL",
-      /*Value=*/value->asBool() ? "TRUE" : "FALSE",
-    };
-    return ReadFileResult::READ_OK;
-  }
-  if (value->isString()) {
-    out = CacheVariable{
-      /*Type=*/"",
-      /*Value=*/value->asString(),
-    };
-    return ReadFileResult::READ_OK;
-  }
-  if (value->isObject()) {
-    out.emplace();
-    return VariableObjectHelper(*out, value);
-  }
-  if (value->isNull()) {
-    out = cm::nullopt;
-    return ReadFileResult::READ_OK;
-  }
-  return ReadFileResult::INVALID_VARIABLE;
-}
-
-auto const VariablesHelper =
-  cmJSONMapHelper<cm::optional<CacheVariable>, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, VariableHelper);
-
-auto const PresetStringHelper = cmJSONStringHelper<ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
-
-ReadFileResult EnvironmentHelper(cm::optional<std::string>& out,
-                                 const Json::Value* value)
-{
-  if (!value || value->isNull()) {
-    out = cm::nullopt;
-    return ReadFileResult::READ_OK;
-  }
-  if (value->isString()) {
-    out = value->asString();
-    return ReadFileResult::READ_OK;
-  }
-  return ReadFileResult::INVALID_PRESET;
-}
-
-auto const EnvironmentMapHelper =
-  cmJSONMapHelper<cm::optional<std::string>, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
-    EnvironmentHelper);
-
-auto const PresetVectorStringHelper =
-  cmJSONVectorHelper<std::string, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
-    PresetStringHelper);
-
-ReadFileResult PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
-                                                 const Json::Value* value)
-{
-  out.clear();
-  if (!value) {
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->isString()) {
-    out.push_back(value->asString());
-    return ReadFileResult::READ_OK;
-  }
-
-  return PresetVectorStringHelper(out, value);
-}
-
-auto const PresetBoolHelper = cmJSONBoolHelper<ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
-
-auto const PresetOptionalBoolHelper =
-  cmJSONOptionalHelper<bool, ReadFileResult>(ReadFileResult::READ_OK,
-                                             PresetBoolHelper);
-
-auto const PresetIntHelper = cmJSONIntHelper<ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
-
-auto const PresetOptionalIntHelper = cmJSONOptionalHelper<int, ReadFileResult>(
-  ReadFileResult::READ_OK, PresetIntHelper);
-
-auto const PresetVectorIntHelper = cmJSONVectorHelper<int, ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, PresetIntHelper);
-
-auto const PresetWarningsHelper =
-  cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
-    .Bind("dev"_s, &ConfigurePreset::WarnDev, PresetOptionalBoolHelper, false)
-    .Bind("deprecated"_s, &ConfigurePreset::WarnDeprecated,
-          PresetOptionalBoolHelper, false)
-    .Bind("uninitialized"_s, &ConfigurePreset::WarnUninitialized,
-          PresetOptionalBoolHelper, false)
-    .Bind("unusedCli"_s, &ConfigurePreset::WarnUnusedCli,
-          PresetOptionalBoolHelper, false)
-    .Bind("systemVars"_s, &ConfigurePreset::WarnSystemVars,
-          PresetOptionalBoolHelper, false);
-
-auto const PresetErrorsHelper =
-  cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
-    .Bind("dev"_s, &ConfigurePreset::ErrorDev, PresetOptionalBoolHelper, false)
-    .Bind("deprecated"_s, &ConfigurePreset::ErrorDeprecated,
-          PresetOptionalBoolHelper, false);
-
-auto const PresetDebugHelper =
-  cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
-    .Bind("output"_s, &ConfigurePreset::DebugOutput, PresetOptionalBoolHelper,
-          false)
-    .Bind("tryCompile"_s, &ConfigurePreset::DebugTryCompile,
-          PresetOptionalBoolHelper, false)
-    .Bind("find"_s, &ConfigurePreset::DebugFind, PresetOptionalBoolHelper,
-          false);
-
-ReadFileResult ArchToolsetStrategyHelper(
-  cm::optional<ArchToolsetStrategy>& out, const Json::Value* value)
-{
-  if (!value) {
-    out = cm::nullopt;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
-  }
-
-  if (value->asString() == "set") {
-    out = ArchToolsetStrategy::Set;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->asString() == "external") {
-    out = ArchToolsetStrategy::External;
-    return ReadFileResult::READ_OK;
-  }
-
-  return ReadFileResult::INVALID_PRESET;
-}
-
-std::function<ReadFileResult(ConfigurePreset&, const Json::Value*)>
-ArchToolsetHelper(
-  std::string ConfigurePreset::*valueField,
-  cm::optional<ArchToolsetStrategy> ConfigurePreset::*strategyField)
-{
-  auto const objectHelper =
-    cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
-      .Bind("value", valueField, PresetStringHelper, false)
-      .Bind("strategy", strategyField, ArchToolsetStrategyHelper, false);
-  return [valueField, strategyField, objectHelper](
-           ConfigurePreset& out, const Json::Value* value) -> ReadFileResult {
-    if (!value) {
-      (out.*valueField).clear();
-      out.*strategyField = cm::nullopt;
-      return ReadFileResult::READ_OK;
-    }
-
-    if (value->isString()) {
-      out.*valueField = value->asString();
-      out.*strategyField = cm::nullopt;
-      return ReadFileResult::READ_OK;
-    }
-
-    if (value->isObject()) {
-      return objectHelper(out, value);
-    }
-
-    return ReadFileResult::INVALID_PRESET;
-  };
-}
-
-auto const ArchitectureHelper = ArchToolsetHelper(
-  &ConfigurePreset::Architecture, &ConfigurePreset::ArchitectureStrategy);
-auto const ToolsetHelper = ArchToolsetHelper(
-  &ConfigurePreset::Toolset, &ConfigurePreset::ToolsetStrategy);
-
-auto const ConfigurePresetHelper =
-  cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
-    .Bind("name"_s, &ConfigurePreset::Name, PresetStringHelper)
-    .Bind("inherits"_s, &ConfigurePreset::Inherits,
-          PresetVectorOneOrMoreStringHelper, false)
-    .Bind("hidden"_s, &ConfigurePreset::Hidden, PresetBoolHelper, false)
-    .Bind<std::nullptr_t>("vendor"_s, nullptr,
-                          VendorHelper(ReadFileResult::INVALID_PRESET), false)
-    .Bind("displayName"_s, &ConfigurePreset::DisplayName, PresetStringHelper,
-          false)
-    .Bind("description"_s, &ConfigurePreset::Description, PresetStringHelper,
-          false)
-    .Bind("generator"_s, &ConfigurePreset::Generator, PresetStringHelper,
-          false)
-    .Bind("architecture"_s, ArchitectureHelper, false)
-    .Bind("toolset"_s, ToolsetHelper, false)
-    .Bind("toolchainFile"_s, &ConfigurePreset::ToolchainFile,
-          PresetStringHelper, false)
-    .Bind("binaryDir"_s, &ConfigurePreset::BinaryDir, PresetStringHelper,
-          false)
-    .Bind("installDir"_s, &ConfigurePreset::InstallDir, PresetStringHelper,
-          false)
-    .Bind<std::string>("cmakeExecutable"_s, nullptr, PresetStringHelper, false)
-    .Bind("cacheVariables"_s, &ConfigurePreset::CacheVariables,
-          VariablesHelper, false)
-    .Bind("environment"_s, &ConfigurePreset::Environment, EnvironmentMapHelper,
-          false)
-    .Bind("warnings"_s, PresetWarningsHelper, false)
-    .Bind("errors"_s, PresetErrorsHelper, false)
-    .Bind("debug"_s, PresetDebugHelper, false)
-    .Bind("condition"_s, &ConfigurePreset::ConditionEvaluator,
-          PresetConditionHelper, false);
-
-auto const BuildPresetHelper =
-  cmJSONObjectHelper<BuildPreset, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
-    .Bind("name"_s, &BuildPreset::Name, PresetStringHelper)
-    .Bind("inherits"_s, &BuildPreset::Inherits,
-          PresetVectorOneOrMoreStringHelper, false)
-    .Bind("hidden"_s, &BuildPreset::Hidden, PresetBoolHelper, false)
-    .Bind<std::nullptr_t>("vendor"_s, nullptr,
-                          VendorHelper(ReadFileResult::INVALID_PRESET), false)
-    .Bind("displayName"_s, &BuildPreset::DisplayName, PresetStringHelper,
-          false)
-    .Bind("description"_s, &BuildPreset::Description, PresetStringHelper,
-          false)
-    .Bind("environment"_s, &BuildPreset::Environment, EnvironmentMapHelper,
-          false)
-    .Bind("configurePreset"_s, &BuildPreset::ConfigurePreset,
-          PresetStringHelper, false)
-    .Bind("inheritConfigureEnvironment"_s,
-          &BuildPreset::InheritConfigureEnvironment, PresetOptionalBoolHelper,
-          false)
-    .Bind("jobs"_s, &BuildPreset::Jobs, PresetOptionalIntHelper, false)
-    .Bind("targets"_s, &BuildPreset::Targets,
-          PresetVectorOneOrMoreStringHelper, false)
-    .Bind("configuration"_s, &BuildPreset::Configuration, PresetStringHelper,
-          false)
-    .Bind("cleanFirst"_s, &BuildPreset::CleanFirst, PresetOptionalBoolHelper,
-          false)
-    .Bind("verbose"_s, &BuildPreset::Verbose, PresetOptionalBoolHelper, false)
-    .Bind("nativeToolOptions"_s, &BuildPreset::NativeToolOptions,
-          PresetVectorStringHelper, false)
-    .Bind("condition"_s, &BuildPreset::ConditionEvaluator,
-          PresetConditionHelper, false);
-
-ReadFileResult TestPresetOutputVerbosityHelper(
-  TestPreset::OutputOptions::VerbosityEnum& out, const Json::Value* value)
-{
-  if (!value) {
-    out = TestPreset::OutputOptions::VerbosityEnum::Default;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
-  }
-
-  if (value->asString() == "default") {
-    out = TestPreset::OutputOptions::VerbosityEnum::Default;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->asString() == "verbose") {
-    out = TestPreset::OutputOptions::VerbosityEnum::Verbose;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->asString() == "extra") {
-    out = TestPreset::OutputOptions::VerbosityEnum::Extra;
-    return ReadFileResult::READ_OK;
-  }
-
-  return ReadFileResult::INVALID_PRESET;
-}
-
-auto const TestPresetOptionalOutputVerbosityHelper =
-  cmJSONOptionalHelper<TestPreset::OutputOptions::VerbosityEnum,
-                       ReadFileResult>(ReadFileResult::READ_OK,
-                                       TestPresetOutputVerbosityHelper);
-
-auto const TestPresetOptionalOutputHelper =
-  cmJSONOptionalHelper<TestPreset::OutputOptions, ReadFileResult>(
-    ReadFileResult::READ_OK,
-    cmJSONObjectHelper<TestPreset::OutputOptions, ReadFileResult>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
-      .Bind("shortProgress"_s, &TestPreset::OutputOptions::ShortProgress,
-            PresetOptionalBoolHelper, false)
-      .Bind("verbosity"_s, &TestPreset::OutputOptions::Verbosity,
-            TestPresetOptionalOutputVerbosityHelper, false)
-      .Bind("debug"_s, &TestPreset::OutputOptions::Debug,
-            PresetOptionalBoolHelper, false)
-      .Bind("outputOnFailure"_s, &TestPreset::OutputOptions::OutputOnFailure,
-            PresetOptionalBoolHelper, false)
-      .Bind("quiet"_s, &TestPreset::OutputOptions::Quiet,
-            PresetOptionalBoolHelper, false)
-      .Bind("outputLogFile"_s, &TestPreset::OutputOptions::OutputLogFile,
-            PresetStringHelper, false)
-      .Bind("labelSummary"_s, &TestPreset::OutputOptions::LabelSummary,
-            PresetOptionalBoolHelper, false)
-      .Bind("subprojectSummary"_s,
-            &TestPreset::OutputOptions::SubprojectSummary,
-            PresetOptionalBoolHelper, false)
-      .Bind("maxPassedTestOutputSize"_s,
-            &TestPreset::OutputOptions::MaxPassedTestOutputSize,
-            PresetOptionalIntHelper, false)
-      .Bind("maxFailedTestOutputSize"_s,
-            &TestPreset::OutputOptions::MaxFailedTestOutputSize,
-            PresetOptionalIntHelper, false)
-      .Bind("maxTestNameWidth"_s, &TestPreset::OutputOptions::MaxTestNameWidth,
-            PresetOptionalIntHelper, false));
-
-auto const TestPresetOptionalFilterIncludeIndexObjectHelper =
-  cmJSONOptionalHelper<TestPreset::IncludeOptions::IndexOptions,
-                       ReadFileResult>(
-    ReadFileResult::READ_OK,
-    cmJSONObjectHelper<TestPreset::IncludeOptions::IndexOptions,
-                       ReadFileResult>(ReadFileResult::READ_OK,
-                                       ReadFileResult::INVALID_PRESET)
-      .Bind("start"_s, &TestPreset::IncludeOptions::IndexOptions::Start,
-            PresetOptionalIntHelper, false)
-      .Bind("end"_s, &TestPreset::IncludeOptions::IndexOptions::End,
-            PresetOptionalIntHelper, false)
-      .Bind("stride"_s, &TestPreset::IncludeOptions::IndexOptions::Stride,
-            PresetOptionalIntHelper, false)
-      .Bind("specificTests"_s,
-            &TestPreset::IncludeOptions::IndexOptions::SpecificTests,
-            PresetVectorIntHelper, false));
-
-ReadFileResult TestPresetOptionalFilterIncludeIndexHelper(
-  cm::optional<TestPreset::IncludeOptions::IndexOptions>& out,
-  const Json::Value* value)
-{
-  if (!value) {
-    out = cm::nullopt;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->isString()) {
-    out.emplace();
-    out->IndexFile = value->asString();
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->isObject()) {
-    return TestPresetOptionalFilterIncludeIndexObjectHelper(out, value);
-  }
-
-  return ReadFileResult::INVALID_PRESET;
-}
-
-auto const TestPresetOptionalFilterIncludeHelper =
-  cmJSONOptionalHelper<TestPreset::IncludeOptions, ReadFileResult>(
-    ReadFileResult::READ_OK,
-    cmJSONObjectHelper<TestPreset::IncludeOptions, ReadFileResult>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
-      .Bind("name"_s, &TestPreset::IncludeOptions::Name, PresetStringHelper,
-            false)
-      .Bind("label"_s, &TestPreset::IncludeOptions::Label, PresetStringHelper,
-            false)
-      .Bind("index"_s, &TestPreset::IncludeOptions::Index,
-            TestPresetOptionalFilterIncludeIndexHelper, false)
-      .Bind("useUnion"_s, &TestPreset::IncludeOptions::UseUnion,
-            PresetOptionalBoolHelper, false));
-
-auto const TestPresetOptionalFilterExcludeFixturesHelper =
-  cmJSONOptionalHelper<TestPreset::ExcludeOptions::FixturesOptions,
-                       ReadFileResult>(
-    ReadFileResult::READ_OK,
-    cmJSONObjectHelper<TestPreset::ExcludeOptions::FixturesOptions,
-                       ReadFileResult>(ReadFileResult::READ_OK,
-                                       ReadFileResult::INVALID_PRESET)
-      .Bind("any"_s, &TestPreset::ExcludeOptions::FixturesOptions::Any,
-            PresetStringHelper, false)
-      .Bind("setup"_s, &TestPreset::ExcludeOptions::FixturesOptions::Setup,
-            PresetStringHelper, false)
-      .Bind("cleanup"_s, &TestPreset::ExcludeOptions::FixturesOptions::Cleanup,
-            PresetStringHelper, false));
-
-auto const TestPresetOptionalFilterExcludeHelper =
-  cmJSONOptionalHelper<TestPreset::ExcludeOptions, ReadFileResult>(
-    ReadFileResult::READ_OK,
-    cmJSONObjectHelper<TestPreset::ExcludeOptions, ReadFileResult>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
-      .Bind("name"_s, &TestPreset::ExcludeOptions::Name, PresetStringHelper,
-            false)
-      .Bind("label"_s, &TestPreset::ExcludeOptions::Label, PresetStringHelper,
-            false)
-      .Bind("fixtures"_s, &TestPreset::ExcludeOptions::Fixtures,
-            TestPresetOptionalFilterExcludeFixturesHelper, false));
-
-ReadFileResult TestPresetExecutionShowOnlyHelper(
-  TestPreset::ExecutionOptions::ShowOnlyEnum& out, const Json::Value* value)
-{
-  if (!value || !value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
-  }
-
-  if (value->asString() == "human") {
-    out = TestPreset::ExecutionOptions::ShowOnlyEnum::Human;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->asString() == "json-v1") {
-    out = TestPreset::ExecutionOptions::ShowOnlyEnum::JsonV1;
-    return ReadFileResult::READ_OK;
-  }
-
-  return ReadFileResult::INVALID_PRESET;
-}
-
-auto const TestPresetOptionalExecutionShowOnlyHelper =
-  cmJSONOptionalHelper<TestPreset::ExecutionOptions::ShowOnlyEnum,
-                       ReadFileResult>(ReadFileResult::READ_OK,
-                                       TestPresetExecutionShowOnlyHelper);
-
-ReadFileResult TestPresetExecutionModeHelper(
-  TestPreset::ExecutionOptions::RepeatOptions::ModeEnum& out,
-  const Json::Value* value)
-{
-  if (!value) {
-    return ReadFileResult::READ_OK;
-  }
-
-  if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
-  }
-
-  if (value->asString() == "until-fail") {
-    out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilFail;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->asString() == "until-pass") {
-    out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilPass;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->asString() == "after-timeout") {
-    out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::AfterTimeout;
-    return ReadFileResult::READ_OK;
-  }
-
-  return ReadFileResult::INVALID_PRESET;
-}
-
-auto const TestPresetOptionalExecutionRepeatHelper =
-  cmJSONOptionalHelper<TestPreset::ExecutionOptions::RepeatOptions,
-                       ReadFileResult>(
-    ReadFileResult::READ_OK,
-    cmJSONObjectHelper<TestPreset::ExecutionOptions::RepeatOptions,
-                       ReadFileResult>(ReadFileResult::READ_OK,
-                                       ReadFileResult::INVALID_PRESET)
-      .Bind("mode"_s, &TestPreset::ExecutionOptions::RepeatOptions::Mode,
-            TestPresetExecutionModeHelper, true)
-      .Bind("count"_s, &TestPreset::ExecutionOptions::RepeatOptions::Count,
-            PresetIntHelper, true));
-
-ReadFileResult TestPresetExecutionNoTestsActionHelper(
-  TestPreset::ExecutionOptions::NoTestsActionEnum& out,
-  const Json::Value* value)
-{
-  if (!value) {
-    out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
-  }
-
-  if (value->asString() == "default") {
-    out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->asString() == "error") {
-    out = TestPreset::ExecutionOptions::NoTestsActionEnum::Error;
-    return ReadFileResult::READ_OK;
-  }
-
-  if (value->asString() == "ignore") {
-    out = TestPreset::ExecutionOptions::NoTestsActionEnum::Ignore;
-    return ReadFileResult::READ_OK;
-  }
-
-  return ReadFileResult::INVALID_PRESET;
-}
-
-auto const TestPresetOptionalExecutionNoTestsActionHelper =
-  cmJSONOptionalHelper<TestPreset::ExecutionOptions::NoTestsActionEnum,
-                       ReadFileResult>(ReadFileResult::READ_OK,
-                                       TestPresetExecutionNoTestsActionHelper);
-
-auto const TestPresetExecutionHelper =
-  cmJSONOptionalHelper<TestPreset::ExecutionOptions, ReadFileResult>(
-    ReadFileResult::READ_OK,
-    cmJSONObjectHelper<TestPreset::ExecutionOptions, ReadFileResult>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
-      .Bind("stopOnFailure"_s, &TestPreset::ExecutionOptions::StopOnFailure,
-            PresetOptionalBoolHelper, false)
-      .Bind("enableFailover"_s, &TestPreset::ExecutionOptions::EnableFailover,
-            PresetOptionalBoolHelper, false)
-      .Bind("jobs"_s, &TestPreset::ExecutionOptions::Jobs,
-            PresetOptionalIntHelper, false)
-      .Bind("resourceSpecFile"_s,
-            &TestPreset::ExecutionOptions::ResourceSpecFile,
-            PresetStringHelper, false)
-      .Bind("testLoad"_s, &TestPreset::ExecutionOptions::TestLoad,
-            PresetOptionalIntHelper, false)
-      .Bind("showOnly"_s, &TestPreset::ExecutionOptions::ShowOnly,
-            TestPresetOptionalExecutionShowOnlyHelper, false)
-      .Bind("repeat"_s, &TestPreset::ExecutionOptions::Repeat,
-            TestPresetOptionalExecutionRepeatHelper, false)
-      .Bind("interactiveDebugging"_s,
-            &TestPreset::ExecutionOptions::InteractiveDebugging,
-            PresetOptionalBoolHelper, false)
-      .Bind("scheduleRandom"_s, &TestPreset::ExecutionOptions::ScheduleRandom,
-            PresetOptionalBoolHelper, false)
-      .Bind("timeout"_s, &TestPreset::ExecutionOptions::Timeout,
-            PresetOptionalIntHelper, false)
-      .Bind("noTestsAction"_s, &TestPreset::ExecutionOptions::NoTestsAction,
-            TestPresetOptionalExecutionNoTestsActionHelper, false));
-
-auto const TestPresetFilterHelper =
-  cmJSONOptionalHelper<TestPreset::FilterOptions, ReadFileResult>(
-    ReadFileResult::READ_OK,
-    cmJSONObjectHelper<TestPreset::FilterOptions, ReadFileResult>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
-      .Bind("include"_s, &TestPreset::FilterOptions::Include,
-            TestPresetOptionalFilterIncludeHelper, false)
-      .Bind("exclude"_s, &TestPreset::FilterOptions::Exclude,
-            TestPresetOptionalFilterExcludeHelper, false));
-
-auto const TestPresetHelper =
-  cmJSONObjectHelper<TestPreset, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
-    .Bind("name"_s, &TestPreset::Name, PresetStringHelper)
-    .Bind("inherits"_s, &TestPreset::Inherits,
-          PresetVectorOneOrMoreStringHelper, false)
-    .Bind("hidden"_s, &TestPreset::Hidden, PresetBoolHelper, false)
-    .Bind<std::nullptr_t>("vendor"_s, nullptr,
-                          VendorHelper(ReadFileResult::INVALID_PRESET), false)
-    .Bind("displayName"_s, &TestPreset::DisplayName, PresetStringHelper, false)
-    .Bind("description"_s, &TestPreset::Description, PresetStringHelper, false)
-    .Bind("environment"_s, &TestPreset::Environment, EnvironmentMapHelper,
-          false)
-    .Bind("configurePreset"_s, &TestPreset::ConfigurePreset,
-          PresetStringHelper, false)
-    .Bind("inheritConfigureEnvironment"_s,
-          &TestPreset::InheritConfigureEnvironment, PresetOptionalBoolHelper,
-          false)
-    .Bind("configuration"_s, &TestPreset::Configuration, PresetStringHelper,
-          false)
-    .Bind("overwriteConfigurationFile"_s,
-          &TestPreset::OverwriteConfigurationFile, PresetVectorStringHelper,
-          false)
-    .Bind("output"_s, &TestPreset::Output, TestPresetOptionalOutputHelper,
-          false)
-    .Bind("filter"_s, &TestPreset::Filter, TestPresetFilterHelper, false)
-    .Bind("execution"_s, &TestPreset::Execution, TestPresetExecutionHelper,
-          false)
-    .Bind("condition"_s, &TestPreset::ConditionEvaluator,
-          PresetConditionHelper, false);
-
-auto const ConfigurePresetsHelper =
-  cmJSONVectorHelper<ConfigurePreset, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-    ConfigurePresetHelper);
-
-auto const BuildPresetsHelper =
-  cmJSONVectorHelper<BuildPreset, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-    BuildPresetHelper);
-
-auto const TestPresetsHelper = cmJSONVectorHelper<TestPreset, ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS, TestPresetHelper);
-
-auto const CMakeVersionUIntHelper = cmJSONUIntHelper<ReadFileResult>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
-
-auto const CMakeVersionHelper =
-  cmJSONObjectHelper<CMakeVersion, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CMAKE_VERSION, false)
-    .Bind("major"_s, &CMakeVersion::Major, CMakeVersionUIntHelper, false)
-    .Bind("minor"_s, &CMakeVersion::Minor, CMakeVersionUIntHelper, false)
-    .Bind("patch"_s, &CMakeVersion::Patch, CMakeVersionUIntHelper, false);
-
-auto const RootPresetsHelper =
-  cmJSONObjectHelper<RootPresets, ReadFileResult>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_ROOT, false)
-    .Bind<int>("version"_s, nullptr, VersionHelper)
-    .Bind("configurePresets"_s, &RootPresets::ConfigurePresets,
-          ConfigurePresetsHelper, false)
-    .Bind("buildPresets"_s, &RootPresets::BuildPresets, BuildPresetsHelper,
-          false)
-    .Bind("testPresets"_s, &RootPresets::TestPresets, TestPresetsHelper, false)
-    .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
-          CMakeVersionHelper, false)
-    .Bind<std::nullptr_t>("vendor"_s, nullptr,
-                          VendorHelper(ReadFileResult::INVALID_ROOT), false);
-}
-
-cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadJSONFile(
-  const std::string& filename, bool user)
-{
-  cmsys::ifstream fin(filename.c_str());
-  if (!fin) {
-    return ReadFileResult::FILE_NOT_FOUND;
-  }
-  // If there's a BOM, toss it.
-  cmsys::FStream::ReadBOM(fin);
-
-  Json::Value root;
-  Json::CharReaderBuilder builder;
-  Json::CharReaderBuilder::strictMode(&builder.settings_);
-  if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
-    return ReadFileResult::JSON_PARSE_ERROR;
-  }
-
-  int v = 0;
-  auto result = RootVersionHelper(v, &root);
-  if (result != ReadFileResult::READ_OK) {
-    return result;
-  }
-  if (v < MIN_VERSION || v > MAX_VERSION) {
-    return ReadFileResult::UNRECOGNIZED_VERSION;
-  }
-  if (user) {
-    this->UserVersion = v;
-  } else {
-    this->Version = v;
-  }
-
-  // Support for build and test presets added in version 2.
-  if (v < 2 &&
-      (root.isMember("buildPresets") || root.isMember("testPresets"))) {
-    return ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED;
-  }
-
-  RootPresets presets;
-  if ((result = RootPresetsHelper(presets, &root)) !=
-      ReadFileResult::READ_OK) {
-    return result;
-  }
-
-  unsigned int currentMajor = cmVersion::GetMajorVersion();
-  unsigned int currentMinor = cmVersion::GetMinorVersion();
-  unsigned int currentPatch = cmVersion::GetPatchVersion();
-  auto const& required = presets.CMakeMinimumRequired;
-  if (required.Major > currentMajor ||
-      (required.Major == currentMajor &&
-       (required.Minor > currentMinor ||
-        (required.Minor == currentMinor &&
-         (required.Patch > currentPatch))))) {
-    return ReadFileResult::UNRECOGNIZED_CMAKE_VERSION;
-  }
-
-  for (auto& preset : presets.ConfigurePresets) {
-    preset.User = user;
-    if (preset.Name.empty()) {
-      return ReadFileResult::INVALID_PRESET;
-    }
-
-    PresetPair<ConfigurePreset> presetPair;
-    presetPair.Unexpanded = preset;
-    presetPair.Expanded = cm::nullopt;
-    if (!this->ConfigurePresets
-           .emplace(std::make_pair(preset.Name, presetPair))
-           .second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
-    }
-
-    // Support for installDir presets added in version 3.
-    if (v < 3 && !preset.InstallDir.empty()) {
-      return ReadFileResult::INSTALL_PREFIX_UNSUPPORTED;
-    }
-
-    // Support for conditions added in version 3.
-    if (v < 3 && preset.ConditionEvaluator) {
-      return ReadFileResult::CONDITION_UNSUPPORTED;
-    }
-
-    // Support for toolchainFile presets added in version 3.
-    if (v < 3 && !preset.ToolchainFile.empty()) {
-      return ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED;
-    }
-
-    this->ConfigurePresetOrder.push_back(preset.Name);
-  }
-
-  for (auto& preset : presets.BuildPresets) {
-    preset.User = user;
-    if (preset.Name.empty()) {
-      return ReadFileResult::INVALID_PRESET;
-    }
-
-    PresetPair<BuildPreset> presetPair;
-    presetPair.Unexpanded = preset;
-    presetPair.Expanded = cm::nullopt;
-    if (!this->BuildPresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
-    }
-
-    // Support for conditions added in version 3.
-    if (v < 3 && preset.ConditionEvaluator) {
-      return ReadFileResult::CONDITION_UNSUPPORTED;
-    }
-
-    this->BuildPresetOrder.push_back(preset.Name);
-  }
-
-  for (auto& preset : presets.TestPresets) {
-    preset.User = user;
-    if (preset.Name.empty()) {
-      return ReadFileResult::INVALID_PRESET;
-    }
-
-    PresetPair<TestPreset> presetPair;
-    presetPair.Unexpanded = preset;
-    presetPair.Expanded = cm::nullopt;
-    if (!this->TestPresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
-    }
-
-    // Support for conditions added in version 3.
-    if (v < 3 && preset.ConditionEvaluator) {
-      return ReadFileResult::CONDITION_UNSUPPORTED;
-    }
-
-    this->TestPresetOrder.push_back(preset.Name);
-  }
-
-  return ReadFileResult::READ_OK;
-}

+ 125 - 108
Source/cmCMakePresetsFile.cxx → Source/cmCMakePresetsGraph.cxx

@@ -1,8 +1,9 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file Copyright.txt or https://cmake.org/licensing for details.  */
-#include "cmCMakePresetsFile.h"
+#include "cmCMakePresetsGraph.h"
 
 #include <algorithm>
+#include <cassert>
 #include <cstdlib>
 #include <functional>
 #include <iostream>
@@ -13,7 +14,7 @@
 
 #include "cmsys/RegularExpression.hxx"
 
-#include "cmCMakePresetsFileInternal.h"
+#include "cmCMakePresetsGraphInternal.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
@@ -38,12 +39,12 @@ enum class CycleStatus
   Verified,
 };
 
-using ReadFileResult = cmCMakePresetsFile::ReadFileResult;
-using ConfigurePreset = cmCMakePresetsFile::ConfigurePreset;
-using BuildPreset = cmCMakePresetsFile::BuildPreset;
-using TestPreset = cmCMakePresetsFile::TestPreset;
-using ExpandMacroResult = cmCMakePresetsFileInternal::ExpandMacroResult;
-using MacroExpander = cmCMakePresetsFileInternal::MacroExpander;
+using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
+using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
+using BuildPreset = cmCMakePresetsGraph::BuildPreset;
+using TestPreset = cmCMakePresetsGraph::TestPreset;
+using ExpandMacroResult = cmCMakePresetsGraphInternal::ExpandMacroResult;
+using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander;
 
 void InheritString(std::string& child, const std::string& parent)
 {
@@ -77,9 +78,10 @@ void InheritVector(std::vector<T>& child, const std::vector<T>& parent)
  */
 template <class T>
 ReadFileResult VisitPreset(
-  T& preset, std::map<std::string, cmCMakePresetsFile::PresetPair<T>>& presets,
+  T& preset,
+  std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
   std::map<std::string, CycleStatus> cycleStatus,
-  const cmCMakePresetsFile& file)
+  const cmCMakePresetsGraph& graph)
 {
   switch (cycleStatus[preset.Name]) {
     case CycleStatus::InProgress:
@@ -105,11 +107,11 @@ ReadFileResult VisitPreset(
     }
 
     auto& parentPreset = parent->second.Unexpanded;
-    if (!preset.User && parentPreset.User) {
-      return ReadFileResult::USER_PRESET_INHERITANCE;
+    if (!preset.OriginFile->ReachableFiles.count(parentPreset.OriginFile)) {
+      return ReadFileResult::PRESET_UNREACHABLE_FROM_FILE;
     }
 
-    auto result = VisitPreset(parentPreset, presets, cycleStatus, file);
+    auto result = VisitPreset(parentPreset, presets, cycleStatus, graph);
     if (result != ReadFileResult::READ_OK) {
       return result;
     }
@@ -129,7 +131,7 @@ ReadFileResult VisitPreset(
     preset.ConditionEvaluator.reset();
   }
 
-  CHECK_OK(preset.VisitPresetAfterInherit(file.GetVersion(preset)));
+  CHECK_OK(preset.VisitPresetAfterInherit(graph.GetVersion(preset)));
 
   cycleStatus[preset.Name] = CycleStatus::Verified;
   return ReadFileResult::READ_OK;
@@ -137,8 +139,8 @@ ReadFileResult VisitPreset(
 
 template <class T>
 ReadFileResult ComputePresetInheritance(
-  std::map<std::string, cmCMakePresetsFile::PresetPair<T>>& presets,
-  const cmCMakePresetsFile& file)
+  std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
+  const cmCMakePresetsGraph& graph)
 {
   std::map<std::string, CycleStatus> cycleStatus;
   for (auto const& it : presets) {
@@ -147,7 +149,7 @@ ReadFileResult ComputePresetInheritance(
 
   for (auto& it : presets) {
     auto& preset = it.second.Unexpanded;
-    auto result = VisitPreset<T>(preset, presets, cycleStatus, file);
+    auto result = VisitPreset<T>(preset, presets, cycleStatus, graph);
     if (result != ReadFileResult::READ_OK) {
       return result;
     }
@@ -189,17 +191,17 @@ ExpandMacroResult ExpandMacro(std::string& out,
                               const std::vector<MacroExpander>& macroExpanders,
                               int version);
 
-bool ExpandMacros(const cmCMakePresetsFile& file,
+bool ExpandMacros(const cmCMakePresetsGraph& graph,
                   const ConfigurePreset& preset,
                   cm::optional<ConfigurePreset>& out,
                   const std::vector<MacroExpander>& macroExpanders)
 {
   std::string binaryDir = preset.BinaryDir;
-  CHECK_EXPAND(out, binaryDir, macroExpanders, file.GetVersion(preset));
+  CHECK_EXPAND(out, binaryDir, macroExpanders, graph.GetVersion(preset));
 
   if (!binaryDir.empty()) {
     if (!cmSystemTools::FileIsFullPath(binaryDir)) {
-      binaryDir = cmStrCat(file.SourceDir, '/', binaryDir);
+      binaryDir = cmStrCat(graph.SourceDir, '/', binaryDir);
     }
     out->BinaryDir = cmSystemTools::CollapseFullPath(binaryDir);
     cmSystemTools::ConvertToUnixSlashes(out->BinaryDir);
@@ -207,10 +209,10 @@ bool ExpandMacros(const cmCMakePresetsFile& file,
 
   if (!preset.InstallDir.empty()) {
     std::string installDir = preset.InstallDir;
-    CHECK_EXPAND(out, installDir, macroExpanders, file.GetVersion(preset));
+    CHECK_EXPAND(out, installDir, macroExpanders, graph.GetVersion(preset));
 
     if (!cmSystemTools::FileIsFullPath(installDir)) {
-      installDir = cmStrCat(file.SourceDir, '/', installDir);
+      installDir = cmStrCat(graph.SourceDir, '/', installDir);
     }
     out->InstallDir = cmSystemTools::CollapseFullPath(installDir);
     cmSystemTools::ConvertToUnixSlashes(out->InstallDir);
@@ -218,89 +220,89 @@ bool ExpandMacros(const cmCMakePresetsFile& file,
 
   if (!preset.ToolchainFile.empty()) {
     std::string toolchain = preset.ToolchainFile;
-    CHECK_EXPAND(out, toolchain, macroExpanders, file.GetVersion(preset));
+    CHECK_EXPAND(out, toolchain, macroExpanders, graph.GetVersion(preset));
     out->ToolchainFile = toolchain;
   }
 
   for (auto& variable : out->CacheVariables) {
     if (variable.second) {
       CHECK_EXPAND(out, variable.second->Value, macroExpanders,
-                   file.GetVersion(preset));
+                   graph.GetVersion(preset));
     }
   }
 
   return true;
 }
 
-bool ExpandMacros(const cmCMakePresetsFile& file, const BuildPreset& preset,
+bool ExpandMacros(const cmCMakePresetsGraph& graph, const BuildPreset& preset,
                   cm::optional<BuildPreset>& out,
                   const std::vector<MacroExpander>& macroExpanders)
 {
   for (auto& target : out->Targets) {
-    CHECK_EXPAND(out, target, macroExpanders, file.GetVersion(preset));
+    CHECK_EXPAND(out, target, macroExpanders, graph.GetVersion(preset));
   }
 
   for (auto& nativeToolOption : out->NativeToolOptions) {
     CHECK_EXPAND(out, nativeToolOption, macroExpanders,
-                 file.GetVersion(preset));
+                 graph.GetVersion(preset));
   }
 
   return true;
 }
 
-bool ExpandMacros(const cmCMakePresetsFile& file, const TestPreset& preset,
+bool ExpandMacros(const cmCMakePresetsGraph& graph, const TestPreset& preset,
                   cm::optional<TestPreset>& out,
                   const std::vector<MacroExpander>& macroExpanders)
 {
   for (auto& overwrite : out->OverwriteConfigurationFile) {
-    CHECK_EXPAND(out, overwrite, macroExpanders, file.GetVersion(preset));
+    CHECK_EXPAND(out, overwrite, macroExpanders, graph.GetVersion(preset));
   }
 
   if (out->Output) {
     CHECK_EXPAND(out, out->Output->OutputLogFile, macroExpanders,
-                 file.GetVersion(preset));
+                 graph.GetVersion(preset));
   }
 
   if (out->Filter) {
     if (out->Filter->Include) {
       CHECK_EXPAND(out, out->Filter->Include->Name, macroExpanders,
-                   file.GetVersion(preset));
+                   graph.GetVersion(preset));
       CHECK_EXPAND(out, out->Filter->Include->Label, macroExpanders,
-                   file.GetVersion(preset));
+                   graph.GetVersion(preset));
 
       if (out->Filter->Include->Index) {
         CHECK_EXPAND(out, out->Filter->Include->Index->IndexFile,
-                     macroExpanders, file.GetVersion(preset));
+                     macroExpanders, graph.GetVersion(preset));
       }
     }
 
     if (out->Filter->Exclude) {
       CHECK_EXPAND(out, out->Filter->Exclude->Name, macroExpanders,
-                   file.GetVersion(preset));
+                   graph.GetVersion(preset));
       CHECK_EXPAND(out, out->Filter->Exclude->Label, macroExpanders,
-                   file.GetVersion(preset));
+                   graph.GetVersion(preset));
 
       if (out->Filter->Exclude->Fixtures) {
         CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Any, macroExpanders,
-                     file.GetVersion(preset));
+                     graph.GetVersion(preset));
         CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Setup,
-                     macroExpanders, file.GetVersion(preset));
+                     macroExpanders, graph.GetVersion(preset));
         CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Cleanup,
-                     macroExpanders, file.GetVersion(preset));
+                     macroExpanders, graph.GetVersion(preset));
       }
     }
   }
 
   if (out->Execution) {
     CHECK_EXPAND(out, out->Execution->ResourceSpecFile, macroExpanders,
-                 file.GetVersion(preset));
+                 graph.GetVersion(preset));
   }
 
   return true;
 }
 
 template <class T>
-bool ExpandMacros(const cmCMakePresetsFile& file, const T& preset,
+bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
                   cm::optional<T>& out)
 {
   out.emplace(preset);
@@ -313,20 +315,20 @@ bool ExpandMacros(const cmCMakePresetsFile& file, const T& preset,
   std::vector<MacroExpander> macroExpanders;
 
   MacroExpander defaultMacroExpander =
-    [&file, &preset](const std::string& macroNamespace,
-                     const std::string& macroName, std::string& macroOut,
-                     int version) -> ExpandMacroResult {
+    [&graph, &preset](const std::string& macroNamespace,
+                      const std::string& macroName, std::string& macroOut,
+                      int version) -> ExpandMacroResult {
     if (macroNamespace.empty()) {
       if (macroName == "sourceDir") {
-        macroOut += file.SourceDir;
+        macroOut += graph.SourceDir;
         return ExpandMacroResult::Ok;
       }
       if (macroName == "sourceParentDir") {
-        macroOut += cmSystemTools::GetParentDirectory(file.SourceDir);
+        macroOut += cmSystemTools::GetParentDirectory(graph.SourceDir);
         return ExpandMacroResult::Ok;
       }
       if (macroName == "sourceDirName") {
-        macroOut += cmSystemTools::GetFilenameName(file.SourceDir);
+        macroOut += cmSystemTools::GetFilenameName(graph.SourceDir);
         return ExpandMacroResult::Ok;
       }
       if (macroName == "presetName") {
@@ -336,7 +338,7 @@ bool ExpandMacros(const cmCMakePresetsFile& file, const T& preset,
       if (macroName == "generator") {
         // Generator only makes sense if preset is not hidden.
         if (!preset.Hidden) {
-          macroOut += file.GetGeneratorForPreset(preset.Name);
+          macroOut += graph.GetGeneratorForPreset(preset.Name);
         }
         return ExpandMacroResult::Ok;
       }
@@ -393,7 +395,7 @@ bool ExpandMacros(const cmCMakePresetsFile& file, const T& preset,
   for (auto& v : out->Environment) {
     if (v.second) {
       switch (VisitEnv(*v.second, envCycles[v.first], macroExpanders,
-                       file.GetVersion(preset))) {
+                       graph.GetVersion(preset))) {
         case ExpandMacroResult::Error:
           return false;
         case ExpandMacroResult::Ignore:
@@ -408,7 +410,7 @@ bool ExpandMacros(const cmCMakePresetsFile& file, const T& preset,
   if (preset.ConditionEvaluator) {
     cm::optional<bool> result;
     if (!preset.ConditionEvaluator->Evaluate(
-          macroExpanders, file.GetVersion(preset), result)) {
+          macroExpanders, graph.GetVersion(preset), result)) {
       return false;
     }
     if (!result) {
@@ -418,7 +420,7 @@ bool ExpandMacros(const cmCMakePresetsFile& file, const T& preset,
     out->ConditionResult = *result;
   }
 
-  return ExpandMacros(file, preset, out, macroExpanders);
+  return ExpandMacros(graph, preset, out, macroExpanders);
 }
 
 ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status,
@@ -541,7 +543,7 @@ ExpandMacroResult ExpandMacro(std::string& out,
 }
 }
 
-bool cmCMakePresetsFileInternal::EqualsCondition::Evaluate(
+bool cmCMakePresetsGraphInternal::EqualsCondition::Evaluate(
   const std::vector<MacroExpander>& expanders, int version,
   cm::optional<bool>& out) const
 {
@@ -555,7 +557,7 @@ bool cmCMakePresetsFileInternal::EqualsCondition::Evaluate(
   return true;
 }
 
-bool cmCMakePresetsFileInternal::InListCondition::Evaluate(
+bool cmCMakePresetsGraphInternal::InListCondition::Evaluate(
   const std::vector<MacroExpander>& expanders, int version,
   cm::optional<bool>& out) const
 {
@@ -574,7 +576,7 @@ bool cmCMakePresetsFileInternal::InListCondition::Evaluate(
   return true;
 }
 
-bool cmCMakePresetsFileInternal::MatchesCondition::Evaluate(
+bool cmCMakePresetsGraphInternal::MatchesCondition::Evaluate(
   const std::vector<MacroExpander>& expanders, int version,
   cm::optional<bool>& out) const
 {
@@ -592,7 +594,7 @@ bool cmCMakePresetsFileInternal::MatchesCondition::Evaluate(
   return true;
 }
 
-bool cmCMakePresetsFileInternal::AnyAllOfCondition::Evaluate(
+bool cmCMakePresetsGraphInternal::AnyAllOfCondition::Evaluate(
   const std::vector<MacroExpander>& expanders, int version,
   cm::optional<bool>& out) const
 {
@@ -618,7 +620,7 @@ bool cmCMakePresetsFileInternal::AnyAllOfCondition::Evaluate(
   return true;
 }
 
-bool cmCMakePresetsFileInternal::NotCondition::Evaluate(
+bool cmCMakePresetsGraphInternal::NotCondition::Evaluate(
   const std::vector<MacroExpander>& expanders, int version,
   cm::optional<bool>& out) const
 {
@@ -633,9 +635,9 @@ bool cmCMakePresetsFileInternal::NotCondition::Evaluate(
   return true;
 }
 
-cmCMakePresetsFile::ReadFileResult
-cmCMakePresetsFile::ConfigurePreset::VisitPresetInherit(
-  const cmCMakePresetsFile::Preset& parentPreset)
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::ConfigurePreset::VisitPresetInherit(
+  const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
   const ConfigurePreset& parent =
@@ -667,8 +669,8 @@ cmCMakePresetsFile::ConfigurePreset::VisitPresetInherit(
   return ReadFileResult::READ_OK;
 }
 
-cmCMakePresetsFile::ReadFileResult
-cmCMakePresetsFile::ConfigurePreset::VisitPresetBeforeInherit()
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::ConfigurePreset::VisitPresetBeforeInherit()
 {
   auto& preset = *this;
   if (preset.Environment.count("") != 0) {
@@ -678,8 +680,8 @@ cmCMakePresetsFile::ConfigurePreset::VisitPresetBeforeInherit()
   return ReadFileResult::READ_OK;
 }
 
-cmCMakePresetsFile::ReadFileResult
-cmCMakePresetsFile::ConfigurePreset::VisitPresetAfterInherit(int version)
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::ConfigurePreset::VisitPresetAfterInherit(int version)
 {
   auto& preset = *this;
   if (!preset.Hidden) {
@@ -706,9 +708,9 @@ cmCMakePresetsFile::ConfigurePreset::VisitPresetAfterInherit(int version)
   return ReadFileResult::READ_OK;
 }
 
-cmCMakePresetsFile::ReadFileResult
-cmCMakePresetsFile::BuildPreset::VisitPresetInherit(
-  const cmCMakePresetsFile::Preset& parentPreset)
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::BuildPreset::VisitPresetInherit(
+  const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
   const BuildPreset& parent = static_cast<const BuildPreset&>(parentPreset);
@@ -726,8 +728,8 @@ cmCMakePresetsFile::BuildPreset::VisitPresetInherit(
   return ReadFileResult::READ_OK;
 }
 
-cmCMakePresetsFile::ReadFileResult
-cmCMakePresetsFile::BuildPreset::VisitPresetAfterInherit(int /* version */)
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::BuildPreset::VisitPresetAfterInherit(int /* version */)
 {
   auto& preset = *this;
   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
@@ -736,9 +738,9 @@ cmCMakePresetsFile::BuildPreset::VisitPresetAfterInherit(int /* version */)
   return ReadFileResult::READ_OK;
 }
 
-cmCMakePresetsFile::ReadFileResult
-cmCMakePresetsFile::TestPreset::VisitPresetInherit(
-  const cmCMakePresetsFile::Preset& parentPreset)
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::TestPreset::VisitPresetInherit(
+  const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
   const TestPreset& parent = static_cast<const TestPreset&>(parentPreset);
@@ -836,8 +838,8 @@ cmCMakePresetsFile::TestPreset::VisitPresetInherit(
   return ReadFileResult::READ_OK;
 }
 
-cmCMakePresetsFile::ReadFileResult
-cmCMakePresetsFile::TestPreset::VisitPresetAfterInherit(int /* version */)
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::TestPreset::VisitPresetAfterInherit(int /* version */)
 {
   auto& preset = *this;
   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
@@ -846,17 +848,17 @@ cmCMakePresetsFile::TestPreset::VisitPresetAfterInherit(int /* version */)
   return ReadFileResult::READ_OK;
 }
 
-std::string cmCMakePresetsFile::GetFilename(const std::string& sourceDir)
+std::string cmCMakePresetsGraph::GetFilename(const std::string& sourceDir)
 {
   return cmStrCat(sourceDir, "/CMakePresets.json");
 }
 
-std::string cmCMakePresetsFile::GetUserFilename(const std::string& sourceDir)
+std::string cmCMakePresetsGraph::GetUserFilename(const std::string& sourceDir)
 {
   return cmStrCat(sourceDir, "/CMakeUserPresets.json");
 }
 
-cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadProjectPresets(
+cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadProjectPresets(
   const std::string& sourceDir, bool allowNoFiles)
 {
   this->SourceDir = sourceDir;
@@ -870,28 +872,33 @@ cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadProjectPresets(
   return result;
 }
 
-cmCMakePresetsFile::ReadFileResult
-cmCMakePresetsFile::ReadProjectPresetsInternal(bool allowNoFiles)
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
 {
   bool haveOneFile = false;
 
+  File* file;
   std::string filename = GetUserFilename(this->SourceDir);
+  std::vector<File*> inProgressFiles;
   if (cmSystemTools::FileExists(filename)) {
-    auto result = this->ReadJSONFile(filename, true);
+    auto result = this->ReadJSONFile(filename, RootType::User,
+                                     ReadReason::Root, inProgressFiles, file);
     if (result != ReadFileResult::READ_OK) {
       return result;
     }
     haveOneFile = true;
-  }
-
-  filename = GetFilename(this->SourceDir);
-  if (cmSystemTools::FileExists(filename)) {
-    auto result = this->ReadJSONFile(filename, false);
-    if (result != ReadFileResult::READ_OK) {
-      return result;
+  } else {
+    filename = GetFilename(this->SourceDir);
+    if (cmSystemTools::FileExists(filename)) {
+      auto result = this->ReadJSONFile(
+        filename, RootType::Project, ReadReason::Root, inProgressFiles, file);
+      if (result != ReadFileResult::READ_OK) {
+        return result;
+      }
+      haveOneFile = true;
     }
-    haveOneFile = true;
   }
+  assert(inProgressFiles.empty());
 
   if (!haveOneFile) {
     return allowNoFiles ? ReadFileResult::READ_OK
@@ -951,7 +958,7 @@ cmCMakePresetsFile::ReadProjectPresetsInternal(bool allowNoFiles)
   return ReadFileResult::READ_OK;
 }
 
-const char* cmCMakePresetsFile::ResultToString(ReadFileResult result)
+const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
 {
   switch (result) {
     case ReadFileResult::READ_OK:
@@ -982,13 +989,17 @@ const char* cmCMakePresetsFile::ResultToString(ReadFileResult result)
       return "Duplicate presets";
     case ReadFileResult::CYCLIC_PRESET_INHERITANCE:
       return "Cyclic preset inheritance";
-    case ReadFileResult::USER_PRESET_INHERITANCE:
-      return "Project preset inherits from user preset";
+    case ReadFileResult::PRESET_UNREACHABLE_FROM_FILE:
+      return "Inherited preset is unreachable from preset's file";
     case ReadFileResult::INVALID_MACRO_EXPANSION:
       return "Invalid macro expansion";
     case ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED:
       return "File version must be 2 or higher for build and test preset "
              "support.";
+    case ReadFileResult::INCLUDE_UNSUPPORTED:
+      return "File version must be 4 or higher for include support";
+    case ReadFileResult::INVALID_INCLUDE:
+      return "Invalid \"include\" field";
     case ReadFileResult::INVALID_CONFIGURE_PRESET:
       return "Invalid \"configurePreset\" field";
     case ReadFileResult::INSTALL_PREFIX_UNSUPPORTED:
@@ -1001,12 +1012,16 @@ const char* cmCMakePresetsFile::ResultToString(ReadFileResult result)
     case ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED:
       return "File version must be 3 or higher for toolchainFile preset "
              "support.";
+    case ReadFileResult::CYCLIC_INCLUDE:
+      return "Cyclic include among preset files";
+    case ReadFileResult::INCLUDE_OUTSIDE_PROJECT:
+      return "File included from outside project directory";
   }
 
   return "Unknown error";
 }
 
-void cmCMakePresetsFile::ClearPresets()
+void cmCMakePresetsGraph::ClearPresets()
 {
   this->ConfigurePresets.clear();
   this->BuildPresets.clear();
@@ -1015,10 +1030,12 @@ void cmCMakePresetsFile::ClearPresets()
   this->ConfigurePresetOrder.clear();
   this->BuildPresetOrder.clear();
   this->TestPresetOrder.clear();
+
+  this->Files.clear();
 }
 
-void cmCMakePresetsFile::PrintPresets(
-  const std::vector<const cmCMakePresetsFile::Preset*>& presets)
+void cmCMakePresetsGraph::PrintPresets(
+  const std::vector<const cmCMakePresetsGraph::Preset*>& presets)
 {
   if (presets.empty()) {
     return;
@@ -1026,8 +1043,8 @@ void cmCMakePresetsFile::PrintPresets(
 
   auto longestPresetName =
     std::max_element(presets.begin(), presets.end(),
-                     [](const cmCMakePresetsFile::Preset* a,
-                        const cmCMakePresetsFile::Preset* b) {
+                     [](const cmCMakePresetsGraph::Preset* a,
+                        const cmCMakePresetsGraph::Preset* b) {
                        return a->Name.length() < b->Name.length();
                      });
   auto longestLength = (*longestPresetName)->Name.length();
@@ -1045,67 +1062,67 @@ void cmCMakePresetsFile::PrintPresets(
   }
 }
 
-void cmCMakePresetsFile::PrintConfigurePresetList() const
+void cmCMakePresetsGraph::PrintConfigurePresetList() const
 {
   PrintConfigurePresetList([](const ConfigurePreset&) { return true; });
 }
 
-void cmCMakePresetsFile::PrintConfigurePresetList(
+void cmCMakePresetsGraph::PrintConfigurePresetList(
   const std::function<bool(const ConfigurePreset&)>& filter) const
 {
-  std::vector<const cmCMakePresetsFile::Preset*> presets;
+  std::vector<const cmCMakePresetsGraph::Preset*> presets;
   for (auto const& p : this->ConfigurePresetOrder) {
     auto const& preset = this->ConfigurePresets.at(p);
     if (!preset.Unexpanded.Hidden && preset.Expanded &&
         preset.Expanded->ConditionResult && filter(preset.Unexpanded)) {
       presets.push_back(
-        static_cast<const cmCMakePresetsFile::Preset*>(&preset.Unexpanded));
+        static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
     }
   }
 
   if (!presets.empty()) {
     std::cout << "Available configure presets:\n\n";
-    cmCMakePresetsFile::PrintPresets(presets);
+    cmCMakePresetsGraph::PrintPresets(presets);
   }
 }
 
-void cmCMakePresetsFile::PrintBuildPresetList() const
+void cmCMakePresetsGraph::PrintBuildPresetList() const
 {
-  std::vector<const cmCMakePresetsFile::Preset*> presets;
+  std::vector<const cmCMakePresetsGraph::Preset*> presets;
   for (auto const& p : this->BuildPresetOrder) {
     auto const& preset = this->BuildPresets.at(p);
     if (!preset.Unexpanded.Hidden && preset.Expanded &&
         preset.Expanded->ConditionResult) {
       presets.push_back(
-        static_cast<const cmCMakePresetsFile::Preset*>(&preset.Unexpanded));
+        static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
     }
   }
 
   if (!presets.empty()) {
     std::cout << "Available build presets:\n\n";
-    cmCMakePresetsFile::PrintPresets(presets);
+    cmCMakePresetsGraph::PrintPresets(presets);
   }
 }
 
-void cmCMakePresetsFile::PrintTestPresetList() const
+void cmCMakePresetsGraph::PrintTestPresetList() const
 {
-  std::vector<const cmCMakePresetsFile::Preset*> presets;
+  std::vector<const cmCMakePresetsGraph::Preset*> presets;
   for (auto const& p : this->TestPresetOrder) {
     auto const& preset = this->TestPresets.at(p);
     if (!preset.Unexpanded.Hidden && preset.Expanded &&
         preset.Expanded->ConditionResult) {
       presets.push_back(
-        static_cast<const cmCMakePresetsFile::Preset*>(&preset.Unexpanded));
+        static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
     }
   }
 
   if (!presets.empty()) {
     std::cout << "Available test presets:\n\n";
-    cmCMakePresetsFile::PrintPresets(presets);
+    cmCMakePresetsGraph::PrintPresets(presets);
   }
 }
 
-void cmCMakePresetsFile::PrintAllPresets() const
+void cmCMakePresetsGraph::PrintAllPresets() const
 {
   this->PrintConfigurePresetList();
   std::cout << std::endl;

+ 36 - 8
Source/cmCMakePresetsFile.h → Source/cmCMakePresetsGraph.h

@@ -8,12 +8,13 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
 #include <cm/optional>
 
-class cmCMakePresetsFile
+class cmCMakePresetsGraph
 {
 public:
   enum class ReadFileResult
@@ -32,14 +33,18 @@ public:
     INVALID_VARIABLE,
     DUPLICATE_PRESETS,
     CYCLIC_PRESET_INHERITANCE,
-    USER_PRESET_INHERITANCE,
+    PRESET_UNREACHABLE_FROM_FILE,
     INVALID_MACRO_EXPANSION,
     BUILD_TEST_PRESETS_UNSUPPORTED,
+    INCLUDE_UNSUPPORTED,
+    INVALID_INCLUDE,
     INVALID_CONFIGURE_PRESET,
     INSTALL_PREFIX_UNSUPPORTED,
     INVALID_CONDITION,
     CONDITION_UNSUPPORTED,
     TOOLCHAIN_FILE_UNSUPPORTED,
+    CYCLIC_INCLUDE,
+    INCLUDE_OUTSIDE_PROJECT,
   };
 
   enum class ArchToolsetStrategy
@@ -57,6 +62,15 @@ public:
 
   class Condition;
 
+  class File
+  {
+  public:
+    std::string Filename;
+    int Version;
+
+    std::unordered_set<File*> ReachableFiles;
+  };
+
   class Preset
   {
   public:
@@ -77,7 +91,7 @@ public:
     std::string Name;
     std::vector<std::string> Inherits;
     bool Hidden;
-    bool User;
+    File* OriginFile;
     std::string DisplayName;
     std::string Description;
 
@@ -321,12 +335,11 @@ public:
   std::vector<std::string> TestPresetOrder;
 
   std::string SourceDir;
-  int Version;
-  int UserVersion;
+  std::vector<std::unique_ptr<File>> Files;
 
   int GetVersion(const Preset& preset) const
   {
-    return preset.User ? this->UserVersion : this->Version;
+    return preset.OriginFile->Version;
   }
 
   static std::string GetFilename(const std::string& sourceDir);
@@ -363,7 +376,7 @@ public:
   }
 
   static void PrintPresets(
-    const std::vector<const cmCMakePresetsFile::Preset*>& presets);
+    const std::vector<const cmCMakePresetsGraph::Preset*>& presets);
   void PrintConfigurePresetList() const;
   void PrintConfigurePresetList(
     const std::function<bool(const ConfigurePreset&)>& filter) const;
@@ -372,7 +385,22 @@ public:
   void PrintAllPresets() const;
 
 private:
+  enum class RootType
+  {
+    Project,
+    User,
+  };
+
+  enum class ReadReason
+  {
+    Root,
+    Included,
+  };
+
   ReadFileResult ReadProjectPresetsInternal(bool allowNoFiles);
-  ReadFileResult ReadJSONFile(const std::string& filename, bool user);
+  ReadFileResult ReadJSONFile(const std::string& filename, RootType rootType,
+                              ReadReason readReason,
+                              std::vector<File*>& inProgressFiles,
+                              File*& file);
   void ClearPresets();
 };

+ 163 - 0
Source/cmCMakePresetsGraphInternal.h

@@ -0,0 +1,163 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm3p/json/value.h>
+
+#include "cmCMakePresetsGraph.h"
+#include "cmJSONHelpers.h"
+
+#define CHECK_OK(expr)                                                        \
+  do {                                                                        \
+    auto _result = expr;                                                      \
+    if (_result != ReadFileResult::READ_OK)                                   \
+      return _result;                                                         \
+  } while (false)
+
+namespace cmCMakePresetsGraphInternal {
+enum class ExpandMacroResult
+{
+  Ok,
+  Ignore,
+  Error,
+};
+
+using MacroExpander = std::function<ExpandMacroResult(
+  const std::string&, const std::string&, std::string&, int version)>;
+}
+
+class cmCMakePresetsGraph::Condition
+{
+public:
+  virtual ~Condition() = default;
+
+  virtual bool Evaluate(
+    const std::vector<cmCMakePresetsGraphInternal::MacroExpander>& expanders,
+    int version, cm::optional<bool>& out) const = 0;
+  virtual bool IsNull() const { return false; }
+};
+
+namespace cmCMakePresetsGraphInternal {
+
+class NullCondition : public cmCMakePresetsGraph::Condition
+{
+  bool Evaluate(const std::vector<MacroExpander>& /*expanders*/,
+                int /*version*/, cm::optional<bool>& out) const override
+  {
+    out = true;
+    return true;
+  }
+
+  bool IsNull() const override { return true; }
+};
+
+class ConstCondition : public cmCMakePresetsGraph::Condition
+{
+public:
+  bool Evaluate(const std::vector<MacroExpander>& /*expanders*/,
+                int /*version*/, cm::optional<bool>& out) const override
+  {
+    out = this->Value;
+    return true;
+  }
+
+  bool Value;
+};
+
+class EqualsCondition : public cmCMakePresetsGraph::Condition
+{
+public:
+  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
+                cm::optional<bool>& out) const override;
+
+  std::string Lhs;
+  std::string Rhs;
+};
+
+class InListCondition : public cmCMakePresetsGraph::Condition
+{
+public:
+  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
+                cm::optional<bool>& out) const override;
+
+  std::string String;
+  std::vector<std::string> List;
+};
+
+class MatchesCondition : public cmCMakePresetsGraph::Condition
+{
+public:
+  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
+                cm::optional<bool>& out) const override;
+
+  std::string String;
+  std::string Regex;
+};
+
+class AnyAllOfCondition : public cmCMakePresetsGraph::Condition
+{
+public:
+  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
+                cm::optional<bool>& out) const override;
+
+  std::vector<std::unique_ptr<Condition>> Conditions;
+  bool StopValue;
+};
+
+class NotCondition : public cmCMakePresetsGraph::Condition
+{
+public:
+  bool Evaluate(const std::vector<MacroExpander>& expanders, int version,
+                cm::optional<bool>& out) const override;
+
+  std::unique_ptr<Condition> SubCondition;
+};
+
+cmCMakePresetsGraph::ReadFileResult PresetStringHelper(
+  std::string& out, const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult PresetVectorStringHelper(
+  std::vector<std::string>& out, const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult PresetBoolHelper(bool& out,
+                                                     const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult PresetOptionalBoolHelper(
+  cm::optional<bool>& out, const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult PresetIntHelper(int& out,
+                                                    const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult PresetOptionalIntHelper(
+  cm::optional<int>& out, const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult PresetVectorIntHelper(
+  std::vector<int>& out, const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult ConfigurePresetsHelper(
+  std::vector<cmCMakePresetsGraph::ConfigurePreset>& out,
+  const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult BuildPresetsHelper(
+  std::vector<cmCMakePresetsGraph::BuildPreset>& out,
+  const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult TestPresetsHelper(
+  std::vector<cmCMakePresetsGraph::TestPreset>& out, const Json::Value* value);
+
+cmJSONHelper<std::nullptr_t, cmCMakePresetsGraph::ReadFileResult> VendorHelper(
+  cmCMakePresetsGraph::ReadFileResult error);
+
+cmCMakePresetsGraph::ReadFileResult PresetConditionHelper(
+  std::shared_ptr<cmCMakePresetsGraph::Condition>& out,
+  const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult PresetVectorOneOrMoreStringHelper(
+  std::vector<std::string>& out, const Json::Value* value);
+
+cmCMakePresetsGraph::ReadFileResult EnvironmentMapHelper(
+  std::map<std::string, cm::optional<std::string>>& out,
+  const Json::Value* value);
+}

+ 641 - 0
Source/cmCMakePresetsGraphReadJSON.cxx

@@ -0,0 +1,641 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include <algorithm>
+#include <functional>
+#include <map>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <cm/memory>
+#include <cm/optional>
+#include <cmext/string_view>
+
+#include <cm3p/json/reader.h>
+#include <cm3p/json/value.h>
+
+#include "cmsys/FStream.hxx"
+
+#include "cmCMakePresetsGraph.h"
+#include "cmCMakePresetsGraphInternal.h"
+#include "cmJSONHelpers.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmVersion.h"
+
+namespace {
+using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
+using CacheVariable = cmCMakePresetsGraph::CacheVariable;
+using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
+using BuildPreset = cmCMakePresetsGraph::BuildPreset;
+using TestPreset = cmCMakePresetsGraph::TestPreset;
+using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
+
+constexpr int MIN_VERSION = 1;
+constexpr int MAX_VERSION = 4;
+
+struct CMakeVersion
+{
+  unsigned int Major = 0;
+  unsigned int Minor = 0;
+  unsigned int Patch = 0;
+};
+
+struct RootPresets
+{
+  CMakeVersion CMakeMinimumRequired;
+  std::vector<cmCMakePresetsGraph::ConfigurePreset> ConfigurePresets;
+  std::vector<cmCMakePresetsGraph::BuildPreset> BuildPresets;
+  std::vector<cmCMakePresetsGraph::TestPreset> TestPresets;
+  std::vector<std::string> Include;
+};
+
+std::unique_ptr<cmCMakePresetsGraphInternal::NotCondition> InvertCondition(
+  std::unique_ptr<cmCMakePresetsGraph::Condition> condition)
+{
+  auto retval = cm::make_unique<cmCMakePresetsGraphInternal::NotCondition>();
+  retval->SubCondition = std::move(condition);
+  return retval;
+}
+
+auto const ConditionStringHelper = cmJSONStringHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
+
+auto const ConditionBoolHelper = cmJSONBoolHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
+
+auto const ConditionStringListHelper =
+  cmJSONVectorHelper<std::string, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION,
+    ConditionStringHelper);
+
+auto const ConstConditionHelper =
+  cmJSONObjectHelper<cmCMakePresetsGraphInternal::ConstCondition,
+                     ReadFileResult>(ReadFileResult::READ_OK,
+                                     ReadFileResult::INVALID_CONDITION, false)
+    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
+    .Bind("value"_s, &cmCMakePresetsGraphInternal::ConstCondition::Value,
+          ConditionBoolHelper, true);
+
+auto const EqualsConditionHelper =
+  cmJSONObjectHelper<cmCMakePresetsGraphInternal::EqualsCondition,
+                     ReadFileResult>(ReadFileResult::READ_OK,
+                                     ReadFileResult::INVALID_CONDITION, false)
+    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
+    .Bind("lhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Lhs,
+          ConditionStringHelper, true)
+    .Bind("rhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Rhs,
+          ConditionStringHelper, true);
+
+auto const InListConditionHelper =
+  cmJSONObjectHelper<cmCMakePresetsGraphInternal::InListCondition,
+                     ReadFileResult>(ReadFileResult::READ_OK,
+                                     ReadFileResult::INVALID_CONDITION, false)
+    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
+    .Bind("string"_s, &cmCMakePresetsGraphInternal::InListCondition::String,
+          ConditionStringHelper, true)
+    .Bind("list"_s, &cmCMakePresetsGraphInternal::InListCondition::List,
+          ConditionStringListHelper, true);
+
+auto const MatchesConditionHelper =
+  cmJSONObjectHelper<cmCMakePresetsGraphInternal::MatchesCondition,
+                     ReadFileResult>(ReadFileResult::READ_OK,
+                                     ReadFileResult::INVALID_CONDITION, false)
+    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
+    .Bind("string"_s, &cmCMakePresetsGraphInternal::MatchesCondition::String,
+          ConditionStringHelper, true)
+    .Bind("regex"_s, &cmCMakePresetsGraphInternal::MatchesCondition::Regex,
+          ConditionStringHelper, true);
+
+ReadFileResult SubConditionHelper(
+  std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
+  const Json::Value* value);
+
+auto const ListConditionVectorHelper =
+  cmJSONVectorHelper<std::unique_ptr<cmCMakePresetsGraph::Condition>,
+                     ReadFileResult>(ReadFileResult::READ_OK,
+                                     ReadFileResult::INVALID_CONDITION,
+                                     SubConditionHelper);
+auto const AnyAllOfConditionHelper =
+  cmJSONObjectHelper<cmCMakePresetsGraphInternal::AnyAllOfCondition,
+                     ReadFileResult>(ReadFileResult::READ_OK,
+                                     ReadFileResult::INVALID_CONDITION, false)
+    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
+    .Bind("conditions"_s,
+          &cmCMakePresetsGraphInternal::AnyAllOfCondition::Conditions,
+          ListConditionVectorHelper);
+
+auto const NotConditionHelper =
+  cmJSONObjectHelper<cmCMakePresetsGraphInternal::NotCondition,
+                     ReadFileResult>(ReadFileResult::READ_OK,
+                                     ReadFileResult::INVALID_CONDITION, false)
+    .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
+    .Bind("condition"_s,
+          &cmCMakePresetsGraphInternal::NotCondition::SubCondition,
+          SubConditionHelper);
+
+ReadFileResult ConditionHelper(
+  std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
+  const Json::Value* value)
+{
+  if (!value) {
+    out.reset();
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->isBool()) {
+    auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
+    c->Value = value->asBool();
+    out = std::move(c);
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->isNull()) {
+    out = cm::make_unique<cmCMakePresetsGraphInternal::NullCondition>();
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->isObject()) {
+    if (!value->isMember("type")) {
+      return ReadFileResult::INVALID_CONDITION;
+    }
+
+    if (!(*value)["type"].isString()) {
+      return ReadFileResult::INVALID_CONDITION;
+    }
+    auto type = (*value)["type"].asString();
+
+    if (type == "const") {
+      auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
+      CHECK_OK(ConstConditionHelper(*c, value));
+      out = std::move(c);
+      return ReadFileResult::READ_OK;
+    }
+
+    if (type == "equals" || type == "notEquals") {
+      auto c = cm::make_unique<cmCMakePresetsGraphInternal::EqualsCondition>();
+      CHECK_OK(EqualsConditionHelper(*c, value));
+      out = std::move(c);
+      if (type == "notEquals") {
+        out = InvertCondition(std::move(out));
+      }
+      return ReadFileResult::READ_OK;
+    }
+
+    if (type == "inList" || type == "notInList") {
+      auto c = cm::make_unique<cmCMakePresetsGraphInternal::InListCondition>();
+      CHECK_OK(InListConditionHelper(*c, value));
+      out = std::move(c);
+      if (type == "notInList") {
+        out = InvertCondition(std::move(out));
+      }
+      return ReadFileResult::READ_OK;
+    }
+
+    if (type == "matches" || type == "notMatches") {
+      auto c =
+        cm::make_unique<cmCMakePresetsGraphInternal::MatchesCondition>();
+      CHECK_OK(MatchesConditionHelper(*c, value));
+      out = std::move(c);
+      if (type == "notMatches") {
+        out = InvertCondition(std::move(out));
+      }
+      return ReadFileResult::READ_OK;
+    }
+
+    if (type == "anyOf" || type == "allOf") {
+      auto c =
+        cm::make_unique<cmCMakePresetsGraphInternal::AnyAllOfCondition>();
+      c->StopValue = (type == "anyOf");
+      CHECK_OK(AnyAllOfConditionHelper(*c, value));
+      out = std::move(c);
+      return ReadFileResult::READ_OK;
+    }
+
+    if (type == "not") {
+      auto c = cm::make_unique<cmCMakePresetsGraphInternal::NotCondition>();
+      CHECK_OK(NotConditionHelper(*c, value));
+      out = std::move(c);
+      return ReadFileResult::READ_OK;
+    }
+  }
+
+  return ReadFileResult::INVALID_CONDITION;
+}
+
+ReadFileResult SubConditionHelper(
+  std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
+  const Json::Value* value)
+{
+  std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
+  auto result = ConditionHelper(ptr, value);
+  if (ptr && ptr->IsNull()) {
+    return ReadFileResult::INVALID_CONDITION;
+  }
+  out = std::move(ptr);
+  return result;
+}
+
+ReadFileResult EnvironmentHelper(cm::optional<std::string>& out,
+                                 const Json::Value* value)
+{
+  if (!value || value->isNull()) {
+    out = cm::nullopt;
+    return ReadFileResult::READ_OK;
+  }
+  if (value->isString()) {
+    out = value->asString();
+    return ReadFileResult::READ_OK;
+  }
+  return ReadFileResult::INVALID_PRESET;
+}
+
+auto const VersionIntHelper = cmJSONIntHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
+
+auto const VersionHelper = cmJSONRequiredHelper<int, ReadFileResult>(
+  ReadFileResult::NO_VERSION, VersionIntHelper);
+
+auto const RootVersionHelper =
+  cmJSONObjectHelper<int, ReadFileResult>(ReadFileResult::READ_OK,
+                                          ReadFileResult::INVALID_ROOT)
+    .Bind("version"_s, VersionHelper, false);
+
+auto const CMakeVersionUIntHelper = cmJSONUIntHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
+
+auto const CMakeVersionHelper =
+  cmJSONObjectHelper<CMakeVersion, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_CMAKE_VERSION, false)
+    .Bind("major"_s, &CMakeVersion::Major, CMakeVersionUIntHelper, false)
+    .Bind("minor"_s, &CMakeVersion::Minor, CMakeVersionUIntHelper, false)
+    .Bind("patch"_s, &CMakeVersion::Patch, CMakeVersionUIntHelper, false);
+
+auto const IncludeHelper = cmJSONStringHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE);
+
+auto const IncludeVectorHelper =
+  cmJSONVectorHelper<std::string, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE, IncludeHelper);
+
+auto const RootPresetsHelper =
+  cmJSONObjectHelper<RootPresets, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_ROOT, false)
+    .Bind<int>("version"_s, nullptr, VersionHelper)
+    .Bind("configurePresets"_s, &RootPresets::ConfigurePresets,
+          cmCMakePresetsGraphInternal::ConfigurePresetsHelper, false)
+    .Bind("buildPresets"_s, &RootPresets::BuildPresets,
+          cmCMakePresetsGraphInternal::BuildPresetsHelper, false)
+    .Bind("testPresets"_s, &RootPresets::TestPresets,
+          cmCMakePresetsGraphInternal::TestPresetsHelper, false)
+    .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
+          CMakeVersionHelper, false)
+    .Bind("include"_s, &RootPresets::Include, IncludeVectorHelper, false)
+    .Bind<std::nullptr_t>(
+      "vendor"_s, nullptr,
+      cmCMakePresetsGraphInternal::VendorHelper(ReadFileResult::INVALID_ROOT),
+      false);
+}
+
+namespace cmCMakePresetsGraphInternal {
+cmCMakePresetsGraph::ReadFileResult PresetStringHelper(
+  std::string& out, const Json::Value* value)
+{
+  static auto const helper = cmJSONStringHelper<ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
+
+  return helper(out, value);
+}
+
+cmCMakePresetsGraph::ReadFileResult PresetVectorStringHelper(
+  std::vector<std::string>& out, const Json::Value* value)
+{
+  static auto const helper = cmJSONVectorHelper<std::string, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
+    cmCMakePresetsGraphInternal::PresetStringHelper);
+
+  return helper(out, value);
+}
+
+cmCMakePresetsGraph::ReadFileResult PresetBoolHelper(bool& out,
+                                                     const Json::Value* value)
+{
+  static auto const helper = cmJSONBoolHelper<ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
+
+  return helper(out, value);
+}
+
+cmCMakePresetsGraph::ReadFileResult PresetOptionalBoolHelper(
+  cm::optional<bool>& out, const Json::Value* value)
+{
+  static auto const helper = cmJSONOptionalHelper<bool, ReadFileResult>(
+    ReadFileResult::READ_OK, PresetBoolHelper);
+
+  return helper(out, value);
+}
+
+cmCMakePresetsGraph::ReadFileResult PresetIntHelper(int& out,
+                                                    const Json::Value* value)
+{
+  static auto const helper = cmJSONIntHelper<ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
+
+  return helper(out, value);
+}
+
+cmCMakePresetsGraph::ReadFileResult PresetOptionalIntHelper(
+  cm::optional<int>& out, const Json::Value* value)
+{
+  static auto const helper = cmJSONOptionalHelper<int, ReadFileResult>(
+    ReadFileResult::READ_OK, PresetIntHelper);
+
+  return helper(out, value);
+}
+
+cmCMakePresetsGraph::ReadFileResult PresetVectorIntHelper(
+  std::vector<int>& out, const Json::Value* value)
+{
+  static auto const helper = cmJSONVectorHelper<int, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, PresetIntHelper);
+
+  return helper(out, value);
+}
+
+cmJSONHelper<std::nullptr_t, ReadFileResult> VendorHelper(ReadFileResult error)
+{
+  return [error](std::nullptr_t& /*out*/,
+                 const Json::Value* value) -> ReadFileResult {
+    if (!value) {
+      return ReadFileResult::READ_OK;
+    }
+
+    if (!value->isObject()) {
+      return error;
+    }
+
+    return ReadFileResult::READ_OK;
+  };
+}
+
+ReadFileResult PresetConditionHelper(
+  std::shared_ptr<cmCMakePresetsGraph::Condition>& out,
+  const Json::Value* value)
+{
+  std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
+  auto result = ConditionHelper(ptr, value);
+  out = std::move(ptr);
+  return result;
+}
+
+ReadFileResult PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
+                                                 const Json::Value* value)
+{
+  out.clear();
+  if (!value) {
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->isString()) {
+    out.push_back(value->asString());
+    return ReadFileResult::READ_OK;
+  }
+
+  return PresetVectorStringHelper(out, value);
+}
+
+cmCMakePresetsGraph::ReadFileResult EnvironmentMapHelper(
+  std::map<std::string, cm::optional<std::string>>& out,
+  const Json::Value* value)
+{
+  static auto const helper =
+    cmJSONMapHelper<cm::optional<std::string>, ReadFileResult>(
+      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
+      EnvironmentHelper);
+
+  return helper(out, value);
+}
+}
+
+cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
+  const std::string& filename, RootType rootType, ReadReason readReason,
+  std::vector<File*>& inProgressFiles, File*& file)
+{
+  ReadFileResult result;
+
+  if (rootType == RootType::Project) {
+    auto normalizedFilename = cmSystemTools::CollapseFullPath(filename);
+
+    auto normalizedProjectDir =
+      cmSystemTools::CollapseFullPath(this->SourceDir);
+    if (!cmSystemTools::IsSubDirectory(normalizedFilename,
+                                       normalizedProjectDir)) {
+      return ReadFileResult::INCLUDE_OUTSIDE_PROJECT;
+    }
+  }
+
+  for (auto const& f : this->Files) {
+    if (cmSystemTools::SameFile(filename, f->Filename)) {
+      file = f.get();
+      auto fileIt =
+        std::find(inProgressFiles.begin(), inProgressFiles.end(), file);
+      if (fileIt != inProgressFiles.end()) {
+        return cmCMakePresetsGraph::ReadFileResult::CYCLIC_INCLUDE;
+      }
+
+      // Check files included by this file again to make sure they're in the
+      // project directory.
+      if (rootType == RootType::Project) {
+        for (auto* f2 : file->ReachableFiles) {
+          if (!cmSystemTools::SameFile(filename, f2->Filename)) {
+            File* file2;
+            if ((result = this->ReadJSONFile(
+                   f2->Filename, rootType, ReadReason::Included,
+                   inProgressFiles, file2)) != ReadFileResult::READ_OK) {
+              return result;
+            }
+          }
+        }
+      }
+
+      return cmCMakePresetsGraph::ReadFileResult::READ_OK;
+    }
+  }
+
+  cmsys::ifstream fin(filename.c_str());
+  if (!fin) {
+    return ReadFileResult::FILE_NOT_FOUND;
+  }
+  // If there's a BOM, toss it.
+  cmsys::FStream::ReadBOM(fin);
+
+  Json::Value root;
+  Json::CharReaderBuilder builder;
+  Json::CharReaderBuilder::strictMode(&builder.settings_);
+  if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
+    return ReadFileResult::JSON_PARSE_ERROR;
+  }
+
+  int v = 0;
+  if ((result = RootVersionHelper(v, &root)) != ReadFileResult::READ_OK) {
+    return result;
+  }
+  if (v < MIN_VERSION || v > MAX_VERSION) {
+    return ReadFileResult::UNRECOGNIZED_VERSION;
+  }
+
+  // Support for build and test presets added in version 2.
+  if (v < 2 &&
+      (root.isMember("buildPresets") || root.isMember("testPresets"))) {
+    return ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED;
+  }
+
+  // Support for include added in version 4.
+  if (v < 4 && root.isMember("include")) {
+    return ReadFileResult::INCLUDE_UNSUPPORTED;
+  }
+
+  RootPresets presets;
+  if ((result = RootPresetsHelper(presets, &root)) !=
+      ReadFileResult::READ_OK) {
+    return result;
+  }
+
+  unsigned int currentMajor = cmVersion::GetMajorVersion();
+  unsigned int currentMinor = cmVersion::GetMinorVersion();
+  unsigned int currentPatch = cmVersion::GetPatchVersion();
+  auto const& required = presets.CMakeMinimumRequired;
+  if (required.Major > currentMajor ||
+      (required.Major == currentMajor &&
+       (required.Minor > currentMinor ||
+        (required.Minor == currentMinor &&
+         (required.Patch > currentPatch))))) {
+    return ReadFileResult::UNRECOGNIZED_CMAKE_VERSION;
+  }
+
+  auto filePtr = cm::make_unique<File>();
+  file = filePtr.get();
+  this->Files.emplace_back(std::move(filePtr));
+  inProgressFiles.emplace_back(file);
+  file->Filename = filename;
+  file->Version = v;
+  file->ReachableFiles.insert(file);
+
+  for (auto& preset : presets.ConfigurePresets) {
+    preset.OriginFile = file;
+    if (preset.Name.empty()) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+
+    PresetPair<ConfigurePreset> presetPair;
+    presetPair.Unexpanded = preset;
+    presetPair.Expanded = cm::nullopt;
+    if (!this->ConfigurePresets
+           .emplace(std::make_pair(preset.Name, presetPair))
+           .second) {
+      return ReadFileResult::DUPLICATE_PRESETS;
+    }
+
+    // Support for installDir presets added in version 3.
+    if (v < 3 && !preset.InstallDir.empty()) {
+      return ReadFileResult::INSTALL_PREFIX_UNSUPPORTED;
+    }
+
+    // Support for conditions added in version 3.
+    if (v < 3 && preset.ConditionEvaluator) {
+      return ReadFileResult::CONDITION_UNSUPPORTED;
+    }
+
+    // Support for toolchainFile presets added in version 3.
+    if (v < 3 && !preset.ToolchainFile.empty()) {
+      return ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED;
+    }
+
+    this->ConfigurePresetOrder.push_back(preset.Name);
+  }
+
+  for (auto& preset : presets.BuildPresets) {
+    preset.OriginFile = file;
+    if (preset.Name.empty()) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+
+    PresetPair<BuildPreset> presetPair;
+    presetPair.Unexpanded = preset;
+    presetPair.Expanded = cm::nullopt;
+    if (!this->BuildPresets.emplace(preset.Name, presetPair).second) {
+      return ReadFileResult::DUPLICATE_PRESETS;
+    }
+
+    // Support for conditions added in version 3.
+    if (v < 3 && preset.ConditionEvaluator) {
+      return ReadFileResult::CONDITION_UNSUPPORTED;
+    }
+
+    this->BuildPresetOrder.push_back(preset.Name);
+  }
+
+  for (auto& preset : presets.TestPresets) {
+    preset.OriginFile = file;
+    if (preset.Name.empty()) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+
+    PresetPair<TestPreset> presetPair;
+    presetPair.Unexpanded = preset;
+    presetPair.Expanded = cm::nullopt;
+    if (!this->TestPresets.emplace(preset.Name, presetPair).second) {
+      return ReadFileResult::DUPLICATE_PRESETS;
+    }
+
+    // Support for conditions added in version 3.
+    if (v < 3 && preset.ConditionEvaluator) {
+      return ReadFileResult::CONDITION_UNSUPPORTED;
+    }
+
+    this->TestPresetOrder.push_back(preset.Name);
+  }
+
+  auto const includeFile = [this, &inProgressFiles, file](
+                             const std::string& include, RootType rootType2,
+                             ReadReason readReason2) -> ReadFileResult {
+    ReadFileResult r;
+    File* includedFile;
+    if ((r = this->ReadJSONFile(include, rootType2, readReason2,
+                                inProgressFiles, includedFile)) !=
+        ReadFileResult::READ_OK) {
+      return r;
+    }
+
+    file->ReachableFiles.insert(includedFile->ReachableFiles.begin(),
+                                includedFile->ReachableFiles.end());
+    return ReadFileResult::READ_OK;
+  };
+
+  for (auto include : presets.Include) {
+    if (!cmSystemTools::FileIsFullPath(include)) {
+      auto directory = cmSystemTools::GetFilenamePath(filename);
+      include = cmStrCat(directory, '/', include);
+    }
+
+    if ((result = includeFile(include, rootType, ReadReason::Included)) !=
+        ReadFileResult::READ_OK) {
+      return result;
+    }
+  }
+
+  if (rootType == RootType::User && readReason == ReadReason::Root) {
+    auto cmakePresetsFilename = GetFilename(this->SourceDir);
+    if (cmSystemTools::FileExists(cmakePresetsFilename)) {
+      if ((result = includeFile(cmakePresetsFilename, RootType::Project,
+                                ReadReason::Root)) !=
+          ReadFileResult::READ_OK) {
+        return result;
+      }
+    }
+  }
+
+  inProgressFiles.pop_back();
+  return ReadFileResult::READ_OK;
+}

+ 75 - 0
Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx

@@ -0,0 +1,75 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include <cstddef>
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm/optional>
+#include <cmext/string_view>
+
+#include <cm3p/json/value.h>
+
+#include "cmCMakePresetsGraph.h"
+#include "cmCMakePresetsGraphInternal.h"
+#include "cmJSONHelpers.h"
+
+namespace {
+using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
+using BuildPreset = cmCMakePresetsGraph::BuildPreset;
+
+auto const BuildPresetHelper =
+  cmJSONObjectHelper<BuildPreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("name"_s, &BuildPreset::Name,
+          cmCMakePresetsGraphInternal::PresetStringHelper)
+    .Bind("inherits"_s, &BuildPreset::Inherits,
+          cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
+          false)
+    .Bind("hidden"_s, &BuildPreset::Hidden,
+          cmCMakePresetsGraphInternal::PresetBoolHelper, false)
+    .Bind<std::nullptr_t>("vendor"_s, nullptr,
+                          cmCMakePresetsGraphInternal::VendorHelper(
+                            ReadFileResult::INVALID_PRESET),
+                          false)
+    .Bind("displayName"_s, &BuildPreset::DisplayName,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("description"_s, &BuildPreset::Description,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("environment"_s, &BuildPreset::Environment,
+          cmCMakePresetsGraphInternal::EnvironmentMapHelper, false)
+    .Bind("configurePreset"_s, &BuildPreset::ConfigurePreset,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("inheritConfigureEnvironment"_s,
+          &BuildPreset::InheritConfigureEnvironment,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("jobs"_s, &BuildPreset::Jobs,
+          cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
+    .Bind("targets"_s, &BuildPreset::Targets,
+          cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
+          false)
+    .Bind("configuration"_s, &BuildPreset::Configuration,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("cleanFirst"_s, &BuildPreset::CleanFirst,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("verbose"_s, &BuildPreset::Verbose,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("nativeToolOptions"_s, &BuildPreset::NativeToolOptions,
+          cmCMakePresetsGraphInternal::PresetVectorStringHelper, false)
+    .Bind("condition"_s, &BuildPreset::ConditionEvaluator,
+          cmCMakePresetsGraphInternal::PresetConditionHelper, false);
+}
+
+namespace cmCMakePresetsGraphInternal {
+ReadFileResult BuildPresetsHelper(std::vector<BuildPreset>& out,
+                                  const Json::Value* value)
+{
+  static auto const helper = cmJSONVectorHelper<BuildPreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
+    BuildPresetHelper);
+
+  return helper(out, value);
+}
+}

+ 228 - 0
Source/cmCMakePresetsGraphReadJSONConfigurePresets.cxx

@@ -0,0 +1,228 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include <cstddef>
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm/optional>
+#include <cmext/string_view>
+
+#include <cm3p/json/value.h>
+
+#include "cmCMakePresetsGraph.h"
+#include "cmCMakePresetsGraphInternal.h"
+#include "cmJSONHelpers.h"
+
+namespace {
+using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
+using CacheVariable = cmCMakePresetsGraph::CacheVariable;
+using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
+using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
+
+ReadFileResult ArchToolsetStrategyHelper(
+  cm::optional<ArchToolsetStrategy>& out, const Json::Value* value)
+{
+  if (!value) {
+    out = cm::nullopt;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (!value->isString()) {
+    return ReadFileResult::INVALID_PRESET;
+  }
+
+  if (value->asString() == "set") {
+    out = ArchToolsetStrategy::Set;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "external") {
+    out = ArchToolsetStrategy::External;
+    return ReadFileResult::READ_OK;
+  }
+
+  return ReadFileResult::INVALID_PRESET;
+}
+
+std::function<ReadFileResult(ConfigurePreset&, const Json::Value*)>
+ArchToolsetHelper(
+  std::string ConfigurePreset::*valueField,
+  cm::optional<ArchToolsetStrategy> ConfigurePreset::*strategyField)
+{
+  auto const objectHelper =
+    cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
+      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+      .Bind("value", valueField,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false)
+      .Bind("strategy", strategyField, ArchToolsetStrategyHelper, false);
+  return [valueField, strategyField, objectHelper](
+           ConfigurePreset& out, const Json::Value* value) -> ReadFileResult {
+    if (!value) {
+      (out.*valueField).clear();
+      out.*strategyField = cm::nullopt;
+      return ReadFileResult::READ_OK;
+    }
+
+    if (value->isString()) {
+      out.*valueField = value->asString();
+      out.*strategyField = cm::nullopt;
+      return ReadFileResult::READ_OK;
+    }
+
+    if (value->isObject()) {
+      return objectHelper(out, value);
+    }
+
+    return ReadFileResult::INVALID_PRESET;
+  };
+}
+
+auto const ArchitectureHelper = ArchToolsetHelper(
+  &ConfigurePreset::Architecture, &ConfigurePreset::ArchitectureStrategy);
+auto const ToolsetHelper = ArchToolsetHelper(
+  &ConfigurePreset::Toolset, &ConfigurePreset::ToolsetStrategy);
+
+auto const VariableStringHelper = cmJSONStringHelper<ReadFileResult>(
+  ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE);
+
+ReadFileResult VariableValueHelper(std::string& out, const Json::Value* value)
+{
+  if (!value) {
+    out.clear();
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->isBool()) {
+    out = value->asBool() ? "TRUE" : "FALSE";
+    return ReadFileResult::READ_OK;
+  }
+
+  return VariableStringHelper(out, value);
+}
+
+auto const VariableObjectHelper =
+  cmJSONObjectHelper<CacheVariable, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE, false)
+    .Bind("type"_s, &CacheVariable::Type, VariableStringHelper, false)
+    .Bind("value"_s, &CacheVariable::Value, VariableValueHelper);
+
+ReadFileResult VariableHelper(cm::optional<CacheVariable>& out,
+                              const Json::Value* value)
+{
+  if (value->isBool()) {
+    out = CacheVariable{
+      /*Type=*/"BOOL",
+      /*Value=*/value->asBool() ? "TRUE" : "FALSE",
+    };
+    return ReadFileResult::READ_OK;
+  }
+  if (value->isString()) {
+    out = CacheVariable{
+      /*Type=*/"",
+      /*Value=*/value->asString(),
+    };
+    return ReadFileResult::READ_OK;
+  }
+  if (value->isObject()) {
+    out.emplace();
+    return VariableObjectHelper(*out, value);
+  }
+  if (value->isNull()) {
+    out = cm::nullopt;
+    return ReadFileResult::READ_OK;
+  }
+  return ReadFileResult::INVALID_VARIABLE;
+}
+
+auto const VariablesHelper =
+  cmJSONMapHelper<cm::optional<CacheVariable>, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, VariableHelper);
+
+auto const PresetWarningsHelper =
+  cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("dev"_s, &ConfigurePreset::WarnDev,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("deprecated"_s, &ConfigurePreset::WarnDeprecated,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("uninitialized"_s, &ConfigurePreset::WarnUninitialized,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("unusedCli"_s, &ConfigurePreset::WarnUnusedCli,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("systemVars"_s, &ConfigurePreset::WarnSystemVars,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false);
+
+auto const PresetErrorsHelper =
+  cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("dev"_s, &ConfigurePreset::ErrorDev,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("deprecated"_s, &ConfigurePreset::ErrorDeprecated,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false);
+
+auto const PresetDebugHelper =
+  cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("output"_s, &ConfigurePreset::DebugOutput,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("tryCompile"_s, &ConfigurePreset::DebugTryCompile,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("find"_s, &ConfigurePreset::DebugFind,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false);
+
+auto const ConfigurePresetHelper =
+  cmJSONObjectHelper<ConfigurePreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("name"_s, &ConfigurePreset::Name,
+          cmCMakePresetsGraphInternal::PresetStringHelper)
+    .Bind("inherits"_s, &ConfigurePreset::Inherits,
+          cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
+          false)
+    .Bind("hidden"_s, &ConfigurePreset::Hidden,
+          cmCMakePresetsGraphInternal::PresetBoolHelper, false)
+    .Bind<std::nullptr_t>("vendor"_s, nullptr,
+                          cmCMakePresetsGraphInternal::VendorHelper(
+                            ReadFileResult::INVALID_PRESET),
+                          false)
+    .Bind("displayName"_s, &ConfigurePreset::DisplayName,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("description"_s, &ConfigurePreset::Description,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("generator"_s, &ConfigurePreset::Generator,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("architecture"_s, ArchitectureHelper, false)
+    .Bind("toolset"_s, ToolsetHelper, false)
+    .Bind("toolchainFile"_s, &ConfigurePreset::ToolchainFile,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("binaryDir"_s, &ConfigurePreset::BinaryDir,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("installDir"_s, &ConfigurePreset::InstallDir,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind<std::string>("cmakeExecutable"_s, nullptr,
+                       cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("cacheVariables"_s, &ConfigurePreset::CacheVariables,
+          VariablesHelper, false)
+    .Bind("environment"_s, &ConfigurePreset::Environment,
+          cmCMakePresetsGraphInternal::EnvironmentMapHelper, false)
+    .Bind("warnings"_s, PresetWarningsHelper, false)
+    .Bind("errors"_s, PresetErrorsHelper, false)
+    .Bind("debug"_s, PresetDebugHelper, false)
+    .Bind("condition"_s, &ConfigurePreset::ConditionEvaluator,
+          cmCMakePresetsGraphInternal::PresetConditionHelper, false);
+}
+
+namespace cmCMakePresetsGraphInternal {
+ReadFileResult ConfigurePresetsHelper(std::vector<ConfigurePreset>& out,
+                                      const Json::Value* value)
+{
+  static auto const helper =
+    cmJSONVectorHelper<ConfigurePreset, ReadFileResult>(
+      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
+      ConfigurePresetHelper);
+
+  return helper(out, value);
+}
+}

+ 360 - 0
Source/cmCMakePresetsGraphReadJSONTestPresets.cxx

@@ -0,0 +1,360 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include <cstddef>
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm/optional>
+#include <cmext/string_view>
+
+#include <cm3p/json/value.h>
+
+#include "cmCMakePresetsGraph.h"
+#include "cmCMakePresetsGraphInternal.h"
+#include "cmJSONHelpers.h"
+
+namespace {
+using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
+using TestPreset = cmCMakePresetsGraph::TestPreset;
+
+ReadFileResult TestPresetOutputVerbosityHelper(
+  TestPreset::OutputOptions::VerbosityEnum& out, const Json::Value* value)
+{
+  if (!value) {
+    out = TestPreset::OutputOptions::VerbosityEnum::Default;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (!value->isString()) {
+    return ReadFileResult::INVALID_PRESET;
+  }
+
+  if (value->asString() == "default") {
+    out = TestPreset::OutputOptions::VerbosityEnum::Default;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "verbose") {
+    out = TestPreset::OutputOptions::VerbosityEnum::Verbose;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "extra") {
+    out = TestPreset::OutputOptions::VerbosityEnum::Extra;
+    return ReadFileResult::READ_OK;
+  }
+
+  return ReadFileResult::INVALID_PRESET;
+}
+
+auto const TestPresetOptionalOutputVerbosityHelper =
+  cmJSONOptionalHelper<TestPreset::OutputOptions::VerbosityEnum,
+                       ReadFileResult>(ReadFileResult::READ_OK,
+                                       TestPresetOutputVerbosityHelper);
+
+auto const TestPresetOptionalOutputHelper =
+  cmJSONOptionalHelper<TestPreset::OutputOptions, ReadFileResult>(
+    ReadFileResult::READ_OK,
+    cmJSONObjectHelper<TestPreset::OutputOptions, ReadFileResult>(
+      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+      .Bind("shortProgress"_s, &TestPreset::OutputOptions::ShortProgress,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("verbosity"_s, &TestPreset::OutputOptions::Verbosity,
+            TestPresetOptionalOutputVerbosityHelper, false)
+      .Bind("debug"_s, &TestPreset::OutputOptions::Debug,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("outputOnFailure"_s, &TestPreset::OutputOptions::OutputOnFailure,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("quiet"_s, &TestPreset::OutputOptions::Quiet,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("outputLogFile"_s, &TestPreset::OutputOptions::OutputLogFile,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false)
+      .Bind("labelSummary"_s, &TestPreset::OutputOptions::LabelSummary,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("subprojectSummary"_s,
+            &TestPreset::OutputOptions::SubprojectSummary,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("maxPassedTestOutputSize"_s,
+            &TestPreset::OutputOptions::MaxPassedTestOutputSize,
+            cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
+      .Bind("maxFailedTestOutputSize"_s,
+            &TestPreset::OutputOptions::MaxFailedTestOutputSize,
+            cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
+      .Bind("maxTestNameWidth"_s, &TestPreset::OutputOptions::MaxTestNameWidth,
+            cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false));
+
+auto const TestPresetOptionalFilterIncludeIndexObjectHelper =
+  cmJSONOptionalHelper<TestPreset::IncludeOptions::IndexOptions,
+                       ReadFileResult>(
+    ReadFileResult::READ_OK,
+    cmJSONObjectHelper<TestPreset::IncludeOptions::IndexOptions,
+                       ReadFileResult>(ReadFileResult::READ_OK,
+                                       ReadFileResult::INVALID_PRESET)
+      .Bind("start"_s, &TestPreset::IncludeOptions::IndexOptions::Start,
+            cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
+      .Bind("end"_s, &TestPreset::IncludeOptions::IndexOptions::End,
+            cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
+      .Bind("stride"_s, &TestPreset::IncludeOptions::IndexOptions::Stride,
+            cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
+      .Bind("specificTests"_s,
+            &TestPreset::IncludeOptions::IndexOptions::SpecificTests,
+            cmCMakePresetsGraphInternal::PresetVectorIntHelper, false));
+
+ReadFileResult TestPresetOptionalFilterIncludeIndexHelper(
+  cm::optional<TestPreset::IncludeOptions::IndexOptions>& out,
+  const Json::Value* value)
+{
+  if (!value) {
+    out = cm::nullopt;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->isString()) {
+    out.emplace();
+    out->IndexFile = value->asString();
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->isObject()) {
+    return TestPresetOptionalFilterIncludeIndexObjectHelper(out, value);
+  }
+
+  return ReadFileResult::INVALID_PRESET;
+}
+
+auto const TestPresetOptionalFilterIncludeHelper =
+  cmJSONOptionalHelper<TestPreset::IncludeOptions, ReadFileResult>(
+    ReadFileResult::READ_OK,
+    cmJSONObjectHelper<TestPreset::IncludeOptions, ReadFileResult>(
+      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+      .Bind("name"_s, &TestPreset::IncludeOptions::Name,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false)
+      .Bind("label"_s, &TestPreset::IncludeOptions::Label,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false)
+      .Bind("index"_s, &TestPreset::IncludeOptions::Index,
+            TestPresetOptionalFilterIncludeIndexHelper, false)
+      .Bind("useUnion"_s, &TestPreset::IncludeOptions::UseUnion,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false));
+
+auto const TestPresetOptionalFilterExcludeFixturesHelper =
+  cmJSONOptionalHelper<TestPreset::ExcludeOptions::FixturesOptions,
+                       ReadFileResult>(
+    ReadFileResult::READ_OK,
+    cmJSONObjectHelper<TestPreset::ExcludeOptions::FixturesOptions,
+                       ReadFileResult>(ReadFileResult::READ_OK,
+                                       ReadFileResult::INVALID_PRESET)
+      .Bind("any"_s, &TestPreset::ExcludeOptions::FixturesOptions::Any,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false)
+      .Bind("setup"_s, &TestPreset::ExcludeOptions::FixturesOptions::Setup,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false)
+      .Bind("cleanup"_s, &TestPreset::ExcludeOptions::FixturesOptions::Cleanup,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false));
+
+auto const TestPresetOptionalFilterExcludeHelper =
+  cmJSONOptionalHelper<TestPreset::ExcludeOptions, ReadFileResult>(
+    ReadFileResult::READ_OK,
+    cmJSONObjectHelper<TestPreset::ExcludeOptions, ReadFileResult>(
+      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+      .Bind("name"_s, &TestPreset::ExcludeOptions::Name,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false)
+      .Bind("label"_s, &TestPreset::ExcludeOptions::Label,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false)
+      .Bind("fixtures"_s, &TestPreset::ExcludeOptions::Fixtures,
+            TestPresetOptionalFilterExcludeFixturesHelper, false));
+
+ReadFileResult TestPresetExecutionShowOnlyHelper(
+  TestPreset::ExecutionOptions::ShowOnlyEnum& out, const Json::Value* value)
+{
+  if (!value || !value->isString()) {
+    return ReadFileResult::INVALID_PRESET;
+  }
+
+  if (value->asString() == "human") {
+    out = TestPreset::ExecutionOptions::ShowOnlyEnum::Human;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "json-v1") {
+    out = TestPreset::ExecutionOptions::ShowOnlyEnum::JsonV1;
+    return ReadFileResult::READ_OK;
+  }
+
+  return ReadFileResult::INVALID_PRESET;
+}
+
+auto const TestPresetOptionalExecutionShowOnlyHelper =
+  cmJSONOptionalHelper<TestPreset::ExecutionOptions::ShowOnlyEnum,
+                       ReadFileResult>(ReadFileResult::READ_OK,
+                                       TestPresetExecutionShowOnlyHelper);
+
+ReadFileResult TestPresetExecutionModeHelper(
+  TestPreset::ExecutionOptions::RepeatOptions::ModeEnum& out,
+  const Json::Value* value)
+{
+  if (!value) {
+    return ReadFileResult::READ_OK;
+  }
+
+  if (!value->isString()) {
+    return ReadFileResult::INVALID_PRESET;
+  }
+
+  if (value->asString() == "until-fail") {
+    out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilFail;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "until-pass") {
+    out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilPass;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "after-timeout") {
+    out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::AfterTimeout;
+    return ReadFileResult::READ_OK;
+  }
+
+  return ReadFileResult::INVALID_PRESET;
+}
+
+auto const TestPresetOptionalExecutionRepeatHelper =
+  cmJSONOptionalHelper<TestPreset::ExecutionOptions::RepeatOptions,
+                       ReadFileResult>(
+    ReadFileResult::READ_OK,
+    cmJSONObjectHelper<TestPreset::ExecutionOptions::RepeatOptions,
+                       ReadFileResult>(ReadFileResult::READ_OK,
+                                       ReadFileResult::INVALID_PRESET)
+      .Bind("mode"_s, &TestPreset::ExecutionOptions::RepeatOptions::Mode,
+            TestPresetExecutionModeHelper, true)
+      .Bind("count"_s, &TestPreset::ExecutionOptions::RepeatOptions::Count,
+            cmCMakePresetsGraphInternal::PresetIntHelper, true));
+
+ReadFileResult TestPresetExecutionNoTestsActionHelper(
+  TestPreset::ExecutionOptions::NoTestsActionEnum& out,
+  const Json::Value* value)
+{
+  if (!value) {
+    out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (!value->isString()) {
+    return ReadFileResult::INVALID_PRESET;
+  }
+
+  if (value->asString() == "default") {
+    out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "error") {
+    out = TestPreset::ExecutionOptions::NoTestsActionEnum::Error;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "ignore") {
+    out = TestPreset::ExecutionOptions::NoTestsActionEnum::Ignore;
+    return ReadFileResult::READ_OK;
+  }
+
+  return ReadFileResult::INVALID_PRESET;
+}
+
+auto const TestPresetOptionalExecutionNoTestsActionHelper =
+  cmJSONOptionalHelper<TestPreset::ExecutionOptions::NoTestsActionEnum,
+                       ReadFileResult>(ReadFileResult::READ_OK,
+                                       TestPresetExecutionNoTestsActionHelper);
+
+auto const TestPresetExecutionHelper =
+  cmJSONOptionalHelper<TestPreset::ExecutionOptions, ReadFileResult>(
+    ReadFileResult::READ_OK,
+    cmJSONObjectHelper<TestPreset::ExecutionOptions, ReadFileResult>(
+      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+      .Bind("stopOnFailure"_s, &TestPreset::ExecutionOptions::StopOnFailure,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("enableFailover"_s, &TestPreset::ExecutionOptions::EnableFailover,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("jobs"_s, &TestPreset::ExecutionOptions::Jobs,
+            cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
+      .Bind("resourceSpecFile"_s,
+            &TestPreset::ExecutionOptions::ResourceSpecFile,
+            cmCMakePresetsGraphInternal::PresetStringHelper, false)
+      .Bind("testLoad"_s, &TestPreset::ExecutionOptions::TestLoad,
+            cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
+      .Bind("showOnly"_s, &TestPreset::ExecutionOptions::ShowOnly,
+            TestPresetOptionalExecutionShowOnlyHelper, false)
+      .Bind("repeat"_s, &TestPreset::ExecutionOptions::Repeat,
+            TestPresetOptionalExecutionRepeatHelper, false)
+      .Bind("interactiveDebugging"_s,
+            &TestPreset::ExecutionOptions::InteractiveDebugging,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("scheduleRandom"_s, &TestPreset::ExecutionOptions::ScheduleRandom,
+            cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+      .Bind("timeout"_s, &TestPreset::ExecutionOptions::Timeout,
+            cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
+      .Bind("noTestsAction"_s, &TestPreset::ExecutionOptions::NoTestsAction,
+            TestPresetOptionalExecutionNoTestsActionHelper, false));
+
+auto const TestPresetFilterHelper =
+  cmJSONOptionalHelper<TestPreset::FilterOptions, ReadFileResult>(
+    ReadFileResult::READ_OK,
+    cmJSONObjectHelper<TestPreset::FilterOptions, ReadFileResult>(
+      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+      .Bind("include"_s, &TestPreset::FilterOptions::Include,
+            TestPresetOptionalFilterIncludeHelper, false)
+      .Bind("exclude"_s, &TestPreset::FilterOptions::Exclude,
+            TestPresetOptionalFilterExcludeHelper, false));
+
+auto const TestPresetHelper =
+  cmJSONObjectHelper<TestPreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("name"_s, &TestPreset::Name,
+          cmCMakePresetsGraphInternal::PresetStringHelper)
+    .Bind("inherits"_s, &TestPreset::Inherits,
+          cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
+          false)
+    .Bind("hidden"_s, &TestPreset::Hidden,
+          cmCMakePresetsGraphInternal::PresetBoolHelper, false)
+    .Bind<std::nullptr_t>("vendor"_s, nullptr,
+                          cmCMakePresetsGraphInternal::VendorHelper(
+                            ReadFileResult::INVALID_PRESET),
+                          false)
+    .Bind("displayName"_s, &TestPreset::DisplayName,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("description"_s, &TestPreset::Description,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("environment"_s, &TestPreset::Environment,
+          cmCMakePresetsGraphInternal::EnvironmentMapHelper, false)
+    .Bind("configurePreset"_s, &TestPreset::ConfigurePreset,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("inheritConfigureEnvironment"_s,
+          &TestPreset::InheritConfigureEnvironment,
+          cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
+    .Bind("configuration"_s, &TestPreset::Configuration,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("overwriteConfigurationFile"_s,
+          &TestPreset::OverwriteConfigurationFile,
+          cmCMakePresetsGraphInternal::PresetVectorStringHelper, false)
+    .Bind("output"_s, &TestPreset::Output, TestPresetOptionalOutputHelper,
+          false)
+    .Bind("filter"_s, &TestPreset::Filter, TestPresetFilterHelper, false)
+    .Bind("execution"_s, &TestPreset::Execution, TestPresetExecutionHelper,
+          false)
+    .Bind("condition"_s, &TestPreset::ConditionEvaluator,
+          cmCMakePresetsGraphInternal::PresetConditionHelper, false);
+}
+
+namespace cmCMakePresetsGraphInternal {
+cmCMakePresetsGraph::ReadFileResult TestPresetsHelper(
+  std::vector<cmCMakePresetsGraph::TestPreset>& out, const Json::Value* value)
+{
+  static auto const helper = cmJSONVectorHelper<TestPreset, ReadFileResult>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
+    TestPresetHelper);
+
+  return helper(out, value);
+}
+}

+ 17 - 17
Source/cmCTest.cxx

@@ -38,7 +38,7 @@
 #  include <unistd.h> // IWYU pragma: keep
 #endif
 
-#include "cmCMakePresetsFile.h"
+#include "cmCMakePresetsGraph.h"
 #include "cmCTestBuildAndTestHandler.h"
 #include "cmCTestBuildHandler.h"
 #include "cmCTestConfigureHandler.h"
@@ -2327,12 +2327,12 @@ bool cmCTest::SetArgsFromPreset(const std::string& presetName,
 {
   const auto workingDirectory = cmSystemTools::GetCurrentWorkingDirectory();
 
-  cmCMakePresetsFile settingsFile;
+  cmCMakePresetsGraph settingsFile;
   auto result = settingsFile.ReadProjectPresets(workingDirectory);
-  if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) {
-    cmSystemTools::Error(cmStrCat("Could not read presets from ",
-                                  workingDirectory, ": ",
-                                  cmCMakePresetsFile::ResultToString(result)));
+  if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
+    cmSystemTools::Error(
+      cmStrCat("Could not read presets from ", workingDirectory, ": ",
+               cmCMakePresetsGraph::ResultToString(result)));
     return false;
   }
 
@@ -2422,15 +2422,15 @@ bool cmCTest::SetArgsFromPreset(const std::string& presetName,
     if (expandedPreset->Output->Verbosity) {
       const auto& verbosity = *expandedPreset->Output->Verbosity;
       switch (verbosity) {
-        case cmCMakePresetsFile::TestPreset::OutputOptions::VerbosityEnum::
+        case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
           Extra:
           this->Impl->ExtraVerbose = true;
           CM_FALLTHROUGH;
-        case cmCMakePresetsFile::TestPreset::OutputOptions::VerbosityEnum::
+        case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
           Verbose:
           this->Impl->Verbose = true;
           break;
-        case cmCMakePresetsFile::TestPreset::OutputOptions::VerbosityEnum::
+        case cmCMakePresetsGraph::TestPreset::OutputOptions::VerbosityEnum::
           Default:
         default:
           // leave default settings
@@ -2548,13 +2548,13 @@ bool cmCTest::SetArgsFromPreset(const std::string& presetName,
       this->Impl->ShowOnly = true;
 
       switch (*expandedPreset->Execution->ShowOnly) {
-        case cmCMakePresetsFile::TestPreset::ExecutionOptions::ShowOnlyEnum::
+        case cmCMakePresetsGraph::TestPreset::ExecutionOptions::ShowOnlyEnum::
           JsonV1:
           this->Impl->Quiet = true;
           this->Impl->OutputAsJson = true;
           this->Impl->OutputAsJsonVersion = 1;
           break;
-        case cmCMakePresetsFile::TestPreset::ExecutionOptions::ShowOnlyEnum::
+        case cmCMakePresetsGraph::TestPreset::ExecutionOptions::ShowOnlyEnum::
           Human:
           // intentional fallthrough (human is the default)
         default:
@@ -2565,15 +2565,15 @@ bool cmCTest::SetArgsFromPreset(const std::string& presetName,
     if (expandedPreset->Execution->Repeat) {
       this->Impl->RepeatCount = expandedPreset->Execution->Repeat->Count;
       switch (expandedPreset->Execution->Repeat->Mode) {
-        case cmCMakePresetsFile::TestPreset::ExecutionOptions::RepeatOptions::
+        case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
           ModeEnum::UntilFail:
           this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
           break;
-        case cmCMakePresetsFile::TestPreset::ExecutionOptions::RepeatOptions::
+        case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
           ModeEnum::UntilPass:
           this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
           break;
-        case cmCMakePresetsFile::TestPreset::ExecutionOptions::RepeatOptions::
+        case cmCMakePresetsGraph::TestPreset::ExecutionOptions::RepeatOptions::
           ModeEnum::AfterTimeout:
           this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
           break;
@@ -2599,15 +2599,15 @@ bool cmCTest::SetArgsFromPreset(const std::string& presetName,
 
     if (expandedPreset->Execution->NoTestsAction) {
       switch (*expandedPreset->Execution->NoTestsAction) {
-        case cmCMakePresetsFile::TestPreset::ExecutionOptions::
+        case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
           NoTestsActionEnum::Error:
           this->Impl->NoTestsMode = cmCTest::NoTests::Error;
           break;
-        case cmCMakePresetsFile::TestPreset::ExecutionOptions::
+        case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
           NoTestsActionEnum::Ignore:
           this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
           break;
-        case cmCMakePresetsFile::TestPreset::ExecutionOptions::
+        case cmCMakePresetsGraph::TestPreset::ExecutionOptions::
           NoTestsActionEnum::Default:
           break;
         default:

+ 21 - 21
Source/cmake.cxx

@@ -29,7 +29,7 @@
 #include "cm_sys_stat.h"
 
 #include "cmCMakePath.h"
-#include "cmCMakePresetsFile.h"
+#include "cmCMakePresetsGraph.h"
 #include "cmCommandLineArgument.h"
 #include "cmCommands.h"
 #include "cmDocumentation.h"
@@ -1239,43 +1239,43 @@ void cmake::SetArgs(const std::vector<std::string>& args)
 
 #if !defined(CMAKE_BOOTSTRAP)
   if (listPresets != ListPresets::None || !presetName.empty()) {
-    cmCMakePresetsFile settingsFile;
-    auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
-    if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) {
+    cmCMakePresetsGraph presetsGraph;
+    auto result = presetsGraph.ReadProjectPresets(this->GetHomeDirectory());
+    if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
       cmSystemTools::Error(
         cmStrCat("Could not read presets from ", this->GetHomeDirectory(),
-                 ": ", cmCMakePresetsFile::ResultToString(result)));
+                 ": ", cmCMakePresetsGraph::ResultToString(result)));
       return;
     }
 
     if (listPresets != ListPresets::None) {
       if (listPresets == ListPresets::Configure) {
-        this->PrintPresetList(settingsFile);
+        this->PrintPresetList(presetsGraph);
       } else if (listPresets == ListPresets::Build) {
-        settingsFile.PrintBuildPresetList();
+        presetsGraph.PrintBuildPresetList();
       } else if (listPresets == ListPresets::Test) {
-        settingsFile.PrintTestPresetList();
+        presetsGraph.PrintTestPresetList();
       } else if (listPresets == ListPresets::All) {
-        settingsFile.PrintAllPresets();
+        presetsGraph.PrintAllPresets();
       }
 
       this->SetWorkingMode(WorkingMode::HELP_MODE);
       return;
     }
 
-    auto preset = settingsFile.ConfigurePresets.find(presetName);
-    if (preset == settingsFile.ConfigurePresets.end()) {
+    auto preset = presetsGraph.ConfigurePresets.find(presetName);
+    if (preset == presetsGraph.ConfigurePresets.end()) {
       cmSystemTools::Error(cmStrCat("No such preset in ",
                                     this->GetHomeDirectory(), ": \"",
                                     presetName, '"'));
-      this->PrintPresetList(settingsFile);
+      this->PrintPresetList(presetsGraph);
       return;
     }
     if (preset->second.Unexpanded.Hidden) {
       cmSystemTools::Error(cmStrCat("Cannot use hidden preset in ",
                                     this->GetHomeDirectory(), ": \"",
                                     presetName, '"'));
-      this->PrintPresetList(settingsFile);
+      this->PrintPresetList(presetsGraph);
       return;
     }
     auto const& expandedPreset = preset->second.Expanded;
@@ -1319,14 +1319,14 @@ void cmake::SetArgs(const std::vector<std::string>& args)
 
     if (!expandedPreset->ArchitectureStrategy ||
         expandedPreset->ArchitectureStrategy ==
-          cmCMakePresetsFile::ArchToolsetStrategy::Set) {
+          cmCMakePresetsGraph::ArchToolsetStrategy::Set) {
       if (!this->GeneratorPlatformSet) {
         this->SetGeneratorPlatform(expandedPreset->Architecture);
       }
     }
     if (!expandedPreset->ToolsetStrategy ||
         expandedPreset->ToolsetStrategy ==
-          cmCMakePresetsFile::ArchToolsetStrategy::Set) {
+          cmCMakePresetsGraph::ArchToolsetStrategy::Set) {
       if (!this->GeneratorToolsetSet) {
         this->SetGeneratorToolset(expandedPreset->Toolset);
       }
@@ -1707,12 +1707,12 @@ bool cmake::CreateAndSetGlobalGenerator(const std::string& name,
 }
 
 #ifndef CMAKE_BOOTSTRAP
-void cmake::PrintPresetList(const cmCMakePresetsFile& file) const
+void cmake::PrintPresetList(const cmCMakePresetsGraph& graph) const
 {
   std::vector<GeneratorInfo> generators;
   this->GetRegisteredGenerators(generators, false);
   auto filter =
-    [&generators](const cmCMakePresetsFile::ConfigurePreset& preset) -> bool {
+    [&generators](const cmCMakePresetsGraph::ConfigurePreset& preset) -> bool {
     if (preset.Generator.empty()) {
       return true;
     }
@@ -1723,7 +1723,7 @@ void cmake::PrintPresetList(const cmCMakePresetsFile& file) const
     return it != generators.end();
   };
 
-  file.PrintConfigurePresetList(filter);
+  graph.PrintConfigurePresetList(filter);
 }
 #endif
 
@@ -3230,12 +3230,12 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
     this->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory());
     this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory());
 
-    cmCMakePresetsFile settingsFile;
+    cmCMakePresetsGraph settingsFile;
     auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
-    if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) {
+    if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
       cmSystemTools::Error(
         cmStrCat("Could not read presets from ", this->GetHomeDirectory(),
-                 ": ", cmCMakePresetsFile::ResultToString(result)));
+                 ": ", cmCMakePresetsGraph::ResultToString(result)));
       return 1;
     }
 

+ 3 - 3
Source/cmake.h

@@ -31,7 +31,7 @@
 
 #  include <cm3p/json/value.h>
 
-#  include "cmCMakePresetsFile.h"
+#  include "cmCMakePresetsGraph.h"
 #endif
 
 class cmExternalMakefileProjectGeneratorFactory;
@@ -248,7 +248,7 @@ public:
 
 #ifndef CMAKE_BOOTSTRAP
   //! Print list of configure presets
-  void PrintPresetList(const cmCMakePresetsFile& file) const;
+  void PrintPresetList(const cmCMakePresetsGraph& graph) const;
 #endif
 
   //! Return the global generator assigned to this instance of cmake
@@ -691,7 +691,7 @@ private:
   std::string GraphVizFile;
   InstalledFilesMap InstalledFiles;
 #ifndef CMAKE_BOOTSTRAP
-  std::map<std::string, cm::optional<cmCMakePresetsFile::CacheVariable>>
+  std::map<std::string, cm::optional<cmCMakePresetsGraph::CacheVariable>>
     UnprocessedPresetVariables;
   std::map<std::string, cm::optional<std::string>>
     UnprocessedPresetEnvironment;

+ 8 - 0
Tests/RunCMake/CMakePresets/Include-stdout.txt

@@ -0,0 +1,8 @@
+^Not searching for unused variables given on the command line\.
+Available configure presets:
+
+  "IncludeUser"
+  "IncludeUserCommon"
+  "Include"
+  "Subdir"
+  "IncludeCommon"$

+ 16 - 0
Tests/RunCMake/CMakePresets/Include.json.in

@@ -0,0 +1,16 @@
+{
+  "version": 4,
+  "include": [
+    "subdir/CMakePresets.json",
+    "@RunCMake_TEST_SOURCE_DIR@/IncludeCommon.json"
+  ],
+  "configurePresets": [
+    {
+      "name": "Include",
+      "inherits": [
+        "IncludeCommon",
+        "Subdir"
+      ]
+    }
+  ]
+}

+ 8 - 0
Tests/RunCMake/CMakePresets/IncludeCommon.json.in

@@ -0,0 +1,8 @@
+{
+  "version": 3,
+  "configurePresets": [
+    {
+      "name": "IncludeCommon"
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/IncludeCycle-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresets/IncludeCycle-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/IncludeCycle: Cyclic include among preset files$

+ 11 - 0
Tests/RunCMake/CMakePresets/IncludeCycle.json.in

@@ -0,0 +1,11 @@
+{
+  "version": 4,
+  "include": [
+    "CMakeUserPresets.json"
+  ],
+  "configurePresets": [
+    {
+      "name": "IncludeCycle"
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/IncludeCycle3Files-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresets/IncludeCycle3Files-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/IncludeCycle3Files: Cyclic include among preset files$

+ 6 - 0
Tests/RunCMake/CMakePresets/IncludeCycle3Files.json.in

@@ -0,0 +1,6 @@
+{
+  "version": 4,
+  "include": [
+    "IncludeCycle3Files2.json"
+  ]
+}

+ 6 - 0
Tests/RunCMake/CMakePresets/IncludeCycle3Files2.json.in

@@ -0,0 +1,6 @@
+{
+  "version": 4,
+  "include": [
+    "IncludeCycle3Files3.json"
+  ]
+}

+ 6 - 0
Tests/RunCMake/CMakePresets/IncludeCycle3Files3.json.in

@@ -0,0 +1,6 @@
+{
+  "version": 4,
+  "include": [
+    "CMakePresets.json"
+  ]
+}

+ 3 - 0
Tests/RunCMake/CMakePresets/IncludeCycleUser.json.in

@@ -0,0 +1,3 @@
+{
+  "version": 3
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/IncludeNotFound-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresets/IncludeNotFound-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/IncludeNotFound: File not found$

+ 11 - 0
Tests/RunCMake/CMakePresets/IncludeNotFound.json.in

@@ -0,0 +1,11 @@
+{
+  "version": 4,
+  "include": [
+    "NotFound.json"
+  ],
+  "configurePresets": [
+    {
+      "name": "IncludeNotFound"
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/IncludeOutsideProject-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresets/IncludeOutsideProject-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/IncludeOutsideProject: File included from outside project directory$

+ 11 - 0
Tests/RunCMake/CMakePresets/IncludeOutsideProject.json.in

@@ -0,0 +1,11 @@
+{
+  "version": 4,
+  "include": [
+    "IncludeOutsideProjectIntermediate.json"
+  ],
+  "configurePresets": [
+    {
+      "name": "IncludeOutsideProject"
+    }
+  ]
+}

+ 3 - 0
Tests/RunCMake/CMakePresets/IncludeOutsideProjectInclude.json

@@ -0,0 +1,3 @@
+{
+  "version": 4
+}

+ 6 - 0
Tests/RunCMake/CMakePresets/IncludeOutsideProjectIntermediate.json.in

@@ -0,0 +1,6 @@
+{
+  "version": 4,
+  "include": [
+    "@RunCMake_SOURCE_DIR@/IncludeOutsideProjectInclude.json"
+  ]
+}

+ 6 - 0
Tests/RunCMake/CMakePresets/IncludeOutsideProjectUser.json.in

@@ -0,0 +1,6 @@
+{
+  "version": 4,
+  "include": [
+    "IncludeOutsideProjectIntermediate.json"
+  ]
+}

+ 15 - 0
Tests/RunCMake/CMakePresets/IncludeUser.json.in

@@ -0,0 +1,15 @@
+{
+  "version": 4,
+  "include": [
+    "IncludeUserCommon.json"
+  ],
+  "configurePresets": [
+    {
+      "name": "IncludeUser",
+      "inherits": [
+        "Include",
+        "IncludeUserCommon"
+      ]
+    }
+  ]
+}

+ 8 - 0
Tests/RunCMake/CMakePresets/IncludeUserCommon.json.in

@@ -0,0 +1,8 @@
+{
+  "version": 3,
+  "configurePresets": [
+    {
+      "name": "IncludeUserCommon"
+    }
+  ]
+}

+ 0 - 0
Tests/RunCMake/CMakePresets/IncludeUserOutsideProject.cmake


+ 11 - 0
Tests/RunCMake/CMakePresets/IncludeUserOutsideProjectUser.json.in

@@ -0,0 +1,11 @@
+{
+  "version": 4,
+  "include": [
+    "@RunCMake_SOURCE_DIR@/IncludeOutsideProjectInclude.json"
+  ],
+  "configurePresets": [
+    {
+      "name": "IncludeUserOutsideProject"
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/IncludeV3-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresets/IncludeV3-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/IncludeV3: File version must be 4 or higher for include support$

+ 4 - 0
Tests/RunCMake/CMakePresets/IncludeV3.json.in

@@ -0,0 +1,4 @@
+{
+  "version": 3,
+  "include": []
+}

+ 1 - 0
Tests/RunCMake/CMakePresets/IncludeV4V3-result.txt

@@ -0,0 +1 @@
+1

+ 2 - 0
Tests/RunCMake/CMakePresets/IncludeV4V3-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresets/IncludeV4V3: File version must be 4 or higher for include support$

+ 6 - 0
Tests/RunCMake/CMakePresets/IncludeV4V3.json.in

@@ -0,0 +1,6 @@
+{
+  "version": 4,
+  "include": [
+    "IncludeV4V3Extra.json"
+  ]
+}

+ 4 - 0
Tests/RunCMake/CMakePresets/IncludeV4V3Extra.json.in

@@ -0,0 +1,4 @@
+{
+  "version": 3,
+  "include": []
+}

+ 45 - 0
Tests/RunCMake/CMakePresets/RunCMakeTest.cmake

@@ -44,6 +44,20 @@ function(run_cmake_presets name)
     configure_file("${CMakeUserPresets_FILE}" "${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json" @ONLY)
   endif()
 
+  set(_CMakePresets_EXTRA_FILES_OUT)
+  set(_CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
+  foreach(_extra_file IN LISTS CMakePresets_EXTRA_FILES)
+    cmake_path(RELATIVE_PATH _extra_file
+      BASE_DIRECTORY "${RunCMake_SOURCE_DIR}"
+      OUTPUT_VARIABLE _extra_file_relative
+      )
+    string(REGEX REPLACE "\\.in$" "" _extra_file_out_relative "${_extra_file_relative}")
+    set(_extra_file_out "${RunCMake_TEST_SOURCE_DIR}/${_extra_file_out_relative}")
+    configure_file("${_extra_file}" "${_extra_file_out}")
+    list(APPEND _CMakePresets_EXTRA_FILES_OUT "${_extra_file_out}")
+    list(APPEND _CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS 0)
+  endforeach()
+
   set(_s_arg -S)
   if(CMakePresets_NO_S_ARG)
     set(_s_arg)
@@ -319,6 +333,37 @@ run_cmake_presets(OptionalBinaryDirFieldNoS)
 unset(CMakePresets_SOURCE_ARG)
 unset(CMakePresets_NO_S_ARG)
 
+# Test include field
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 1)
+run_cmake_presets(IncludeV3)
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
+set(CMakePresets_EXTRA_FILES
+  "${RunCMake_SOURCE_DIR}/IncludeV4V3Extra.json.in"
+  )
+set(CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS 1)
+run_cmake_presets(IncludeV4V3)
+unset(CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
+set(CMakePresets_EXTRA_FILES
+  "${RunCMake_SOURCE_DIR}/IncludeCommon.json.in"
+  "${RunCMake_SOURCE_DIR}/IncludeUserCommon.json.in"
+  "${RunCMake_SOURCE_DIR}/subdir/CMakePresets.json.in"
+  )
+run_cmake_presets(Include --list-presets)
+unset(CMakePresets_EXTRA_FILES)
+run_cmake_presets(IncludeNotFound)
+run_cmake_presets(IncludeCycle)
+set(CMakePresets_EXTRA_FILES
+  "${RunCMake_SOURCE_DIR}/IncludeCycle3Files2.json.in"
+  "${RunCMake_SOURCE_DIR}/IncludeCycle3Files3.json.in"
+  )
+run_cmake_presets(IncludeCycle3Files)
+set(CMakePresets_EXTRA_FILES
+  "${RunCMake_SOURCE_DIR}/IncludeOutsideProjectIntermediate.json.in"
+  )
+run_cmake_presets(IncludeOutsideProject)
+unset(CMakePresets_EXTRA_FILES)
+run_cmake_presets(IncludeUserOutsideProject)
+
 # Test the example from the documentation
 file(READ "${RunCMake_SOURCE_DIR}/../../../Help/manual/presets/example.json" _example)
 string(REPLACE "\"generator\": \"Ninja\"" "\"generator\": \"@RunCMake_GENERATOR@\"" _example "${_example}")

+ 1 - 1
Tests/RunCMake/CMakePresets/UserInheritance-stderr.txt

@@ -1,2 +1,2 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UserInheritance: Project preset inherits from user preset$
+]*/Tests/RunCMake/CMakePresets/UserInheritance: Inherited preset is unreachable from preset's file$

+ 7 - 0
Tests/RunCMake/CMakePresets/check.cmake

@@ -12,4 +12,11 @@ if(PYTHON_EXECUTABLE AND CMake_TEST_JSON_SCHEMA)
   if(EXISTS "${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json")
     validate_schema("${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json" "${CMakeUserPresets_SCHEMA_EXPECTED_RESULT}")
   endif()
+
+  if(NOT CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
+    set(CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS "${_CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS}")
+  endif()
+  foreach(_f _r IN ZIP_LISTS _CMakePresets_EXTRA_FILES_OUT CMakePresets_EXTRA_FILES_SCHEMA_EXPECTED_RESULTS)
+    validate_schema("${_f}" "${_r}")
+  endforeach()
 endif()

+ 12 - 0
Tests/RunCMake/CMakePresets/subdir/CMakePresets.json.in

@@ -0,0 +1,12 @@
+{
+  "version": 4,
+  "include": [
+    "../IncludeCommon.json"
+  ],
+  "configurePresets": [
+    {
+      "name": "Subdir",
+      "inherits": "IncludeCommon"
+    }
+  ]
+}