Browse Source

Merge topic 'presets-json-errors'

19305afd8a presets: Improve JSON parser and error messages

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Acked-by: scivision <[email protected]>
Merge-request: !8290
Brad King 2 years ago
parent
commit
4901fdb201
100 changed files with 1903 additions and 1198 deletions
  1. 3 0
      Source/CMakeLists.txt
  2. 4 3
      Source/CPack/cpack.cxx
  3. 42 106
      Source/CTest/cmCTestResourceSpec.cxx
  4. 43 16
      Source/CTest/cmCTestResourceSpec.h
  5. 3 3
      Source/CTest/cmCTestTestHandler.cxx
  6. 3 4
      Source/QtDialog/CMakeSetupDialog.cxx
  7. 1 2
      Source/QtDialog/CMakeSetupDialog.h
  8. 5 4
      Source/QtDialog/QCMake.cxx
  9. 2 5
      Source/QtDialog/QCMake.h
  10. 242 0
      Source/cmCMakePresetErrors.h
  11. 178 201
      Source/cmCMakePresetsGraph.cxx
  12. 30 62
      Source/cmCMakePresetsGraph.h
  13. 34 35
      Source/cmCMakePresetsGraphInternal.h
  14. 228 198
      Source/cmCMakePresetsGraphReadJSON.cxx
  15. 24 21
      Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx
  16. 49 44
      Source/cmCMakePresetsGraphReadJSONConfigurePresets.cxx
  17. 17 20
      Source/cmCMakePresetsGraphReadJSONPackagePresets.cxx
  18. 80 81
      Source/cmCMakePresetsGraphReadJSONTestPresets.cxx
  19. 27 26
      Source/cmCMakePresetsGraphReadJSONWorkflowPresets.cxx
  20. 5 4
      Source/cmCTest.cxx
  21. 258 94
      Source/cmJSONHelpers.h
  22. 163 0
      Source/cmJSONState.cxx
  23. 73 0
      Source/cmJSONState.h
  24. 11 13
      Source/cmake.cxx
  25. 10 6
      Tests/CMakeLib/testCTestResourceAllocator.cxx
  26. 49 66
      Tests/CMakeLib/testCTestResourceSpec.cxx
  27. 108 95
      Tests/CMakeLib/testJSONHelpers.cxx
  28. 2 3
      Tests/RunCMake/CMakePresets/Comment-stderr.txt
  29. 2 1
      Tests/RunCMake/CMakePresets/ConditionFuture-stderr.txt
  30. 2 1
      Tests/RunCMake/CMakePresets/CyclicInheritance0-stderr.txt
  31. 2 1
      Tests/RunCMake/CMakePresets/CyclicInheritance1-stderr.txt
  32. 2 1
      Tests/RunCMake/CMakePresets/CyclicInheritance2-stderr.txt
  33. 2 1
      Tests/RunCMake/CMakePresets/DuplicatePresets-stderr.txt
  34. 2 1
      Tests/RunCMake/CMakePresets/EmptyCacheKey-stderr.txt
  35. 2 1
      Tests/RunCMake/CMakePresets/EmptyEnv-stderr.txt
  36. 2 1
      Tests/RunCMake/CMakePresets/EmptyEnvKey-stderr.txt
  37. 2 1
      Tests/RunCMake/CMakePresets/EmptyPenv-stderr.txt
  38. 4 4
      Tests/RunCMake/CMakePresets/EmptyPresetName-stderr.txt
  39. 3 1
      Tests/RunCMake/CMakePresets/EnvCycle-stderr.txt
  40. 2 1
      Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-stderr.txt
  41. 2 1
      Tests/RunCMake/CMakePresets/ErrorNoWarningDev-stderr.txt
  42. 4 1
      Tests/RunCMake/CMakePresets/ExtraPresetField-stderr.txt
  43. 4 1
      Tests/RunCMake/CMakePresets/ExtraRootField-stderr.txt
  44. 4 1
      Tests/RunCMake/CMakePresets/ExtraVariableField-stderr.txt
  45. 2 1
      Tests/RunCMake/CMakePresets/FileDirFuture-stderr.txt
  46. 2 1
      Tests/RunCMake/CMakePresets/FuturePresetInstallDirField-stderr.txt
  47. 2 1
      Tests/RunCMake/CMakePresets/FuturePresetToolchainField-stderr.txt
  48. 4 1
      Tests/RunCMake/CMakePresets/HighVersion-stderr.txt
  49. 2 1
      Tests/RunCMake/CMakePresets/HostSystemNameFuture-stderr.txt
  50. 3 1
      Tests/RunCMake/CMakePresets/IncludeCycle-stderr.txt
  51. 4 2
      Tests/RunCMake/CMakePresets/IncludeCycle3Files-stderr.txt
  52. 3 4
      Tests/RunCMake/CMakePresets/IncludeNotFound-stderr.txt
  53. 2 1
      Tests/RunCMake/CMakePresets/IncludeV3-stderr.txt
  54. 2 1
      Tests/RunCMake/CMakePresets/IncludeV4V3-stderr.txt
  55. 4 1
      Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy-stderr.txt
  56. 2 1
      Tests/RunCMake/CMakePresets/InvalidInheritance-stderr.txt
  57. 4 1
      Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir-stderr.txt
  58. 4 1
      Tests/RunCMake/CMakePresets/InvalidPresetGenerator-stderr.txt
  59. 4 1
      Tests/RunCMake/CMakePresets/InvalidPresetName-stderr.txt
  60. 4 1
      Tests/RunCMake/CMakePresets/InvalidPresetVendor-stderr.txt
  61. 4 1
      Tests/RunCMake/CMakePresets/InvalidPresets-stderr.txt
  62. 3 1
      Tests/RunCMake/CMakePresets/InvalidRegex-stderr.txt
  63. 4 1
      Tests/RunCMake/CMakePresets/InvalidRoot-stderr.txt
  64. 4 1
      Tests/RunCMake/CMakePresets/InvalidToolsetStrategy-stderr.txt
  65. 4 1
      Tests/RunCMake/CMakePresets/InvalidVariableValue-stderr.txt
  66. 4 1
      Tests/RunCMake/CMakePresets/InvalidVariables-stderr.txt
  67. 4 1
      Tests/RunCMake/CMakePresets/InvalidVendor-stderr.txt
  68. 4 1
      Tests/RunCMake/CMakePresets/InvalidVersion-stderr.txt
  69. 2 8
      Tests/RunCMake/CMakePresets/JSONParseError-stderr.txt
  70. 4 1
      Tests/RunCMake/CMakePresets/LowVersion-stderr.txt
  71. 4 1
      Tests/RunCMake/CMakePresets/MinimumRequiredInvalid-stderr.txt
  72. 4 1
      Tests/RunCMake/CMakePresets/MinimumRequiredMajor-stderr.txt
  73. 4 1
      Tests/RunCMake/CMakePresets/MinimumRequiredMinor-stderr.txt
  74. 4 1
      Tests/RunCMake/CMakePresets/MinimumRequiredPatch-stderr.txt
  75. 3 1
      Tests/RunCMake/CMakePresets/NoCMakePresets-stderr.txt
  76. 3 1
      Tests/RunCMake/CMakePresets/NoPresetBinaryDir-stderr.txt
  77. 3 1
      Tests/RunCMake/CMakePresets/NoPresetGenerator-stderr.txt
  78. 4 1
      Tests/RunCMake/CMakePresets/NoPresetName-stderr.txt
  79. 2 1
      Tests/RunCMake/CMakePresets/NoSuchMacro-stderr.txt
  80. 4 1
      Tests/RunCMake/CMakePresets/NoVariableValue-stderr.txt
  81. 2 1
      Tests/RunCMake/CMakePresets/NoVersion-stderr.txt
  82. 2 1
      Tests/RunCMake/CMakePresets/PathListSepFuture-stderr.txt
  83. 4 1
      Tests/RunCMake/CMakePresets/PresetNotObject-stderr.txt
  84. 2 1
      Tests/RunCMake/CMakePresets/SubConditionNull-stderr.txt
  85. 2 1
      Tests/RunCMake/CMakePresets/UnclosedMacro-stderr.txt
  86. 4 1
      Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy-stderr.txt
  87. 4 1
      Tests/RunCMake/CMakePresets/UnknownToolsetStrategy-stderr.txt
  88. 2 1
      Tests/RunCMake/CMakePresets/UserDuplicateCross-stderr.txt
  89. 2 1
      Tests/RunCMake/CMakePresets/UserDuplicateInUser-stderr.txt
  90. 2 1
      Tests/RunCMake/CMakePresets/UserInheritance-stderr.txt
  91. 4 1
      Tests/RunCMake/CMakePresets/VariableNotObject-stderr.txt
  92. 2 1
      Tests/RunCMake/CMakePresetsBuild/ConditionFuture-build-conditionFuture-stderr.txt
  93. 2 1
      Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable-build-x-stderr.txt
  94. 2 1
      Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-build-badConfigurePreset-stderr.txt
  95. 2 1
      Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-configure-default-stderr.txt
  96. 2 1
      Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-stderr.txt
  97. 2 1
      Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-stderr.txt
  98. 2 1
      Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion-configure-x-stderr.txt
  99. 2 1
      Tests/RunCMake/CMakePresetsTest/ConditionFuture-test-x-stderr.txt
  100. 2 1
      Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable-test-x-stderr.txt

+ 3 - 0
Source/CMakeLists.txt

@@ -128,6 +128,7 @@ add_library(
   cmCLocaleEnvironmentScope.cxx
   cmCMakePath.h
   cmCMakePath.cxx
+  cmCMakePresetErrors.h
   cmCMakePresetsGraph.cxx
   cmCMakePresetsGraph.h
   cmCMakePresetsGraphInternal.h
@@ -319,6 +320,8 @@ add_library(
   cmInstallDirectoryGenerator.h
   cmInstallDirectoryGenerator.cxx
   cmJSONHelpers.h
+  cmJSONState.cxx
+  cmJSONState.h
   cmLDConfigLDConfigTool.cxx
   cmLDConfigLDConfigTool.h
   cmLDConfigTool.cxx

+ 4 - 3
Source/CPack/cpack.cxx

@@ -28,6 +28,7 @@
 #include "cmDocumentation.h"
 #include "cmDocumentationEntry.h"
 #include "cmGlobalGenerator.h"
+#include "cmJSONState.h"
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStateSnapshot.h"
@@ -265,11 +266,11 @@ int main(int argc, char const* const* argv)
 
     cmCMakePresetsGraph presetsGraph;
     auto result = presetsGraph.ReadProjectPresets(workingDirectory);
-    if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
+    if (result != true) {
       cmCPack_Log(&log, cmCPackLog::LOG_ERROR,
                   "Could not read presets from "
-                    << workingDirectory << ": "
-                    << cmCMakePresetsGraph::ResultToString(result) << '\n');
+                    << workingDirectory << ":"
+                    << presetsGraph.parseState.GetErrorMessage() << '\n');
       return 1;
     }
 

+ 42 - 106
Source/CTest/cmCTestResourceSpec.cxx

@@ -10,17 +10,14 @@
 
 #include <cmext/string_view>
 
-#include <cm3p/json/reader.h>
 #include <cm3p/json/value.h>
 
-#include "cmsys/FStream.hxx"
 #include "cmsys/RegularExpression.hxx"
 
 #include "cmJSONHelpers.h"
 
 namespace {
-using JSONHelperBuilder =
-  cmJSONHelperBuilder<cmCTestResourceSpec::ReadFileResult>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
 const cmsys::RegularExpression IdentifierRegex{ "^[a-z_][a-z0-9_]*$" };
 const cmsys::RegularExpression IdRegex{ "^[a-z0-9_]+$" };
 
@@ -36,165 +33,104 @@ struct TopVersion
 };
 
 auto const VersionFieldHelper =
-  JSONHelperBuilder::Int(cmCTestResourceSpec::ReadFileResult::READ_OK,
-                         cmCTestResourceSpec::ReadFileResult::INVALID_VERSION);
+  JSONHelperBuilder::Int(cmCTestResourceSpecErrors::INVALID_VERSION);
 
 auto const VersionHelper = JSONHelperBuilder::Required<Version>(
-  cmCTestResourceSpec::ReadFileResult::NO_VERSION,
-  JSONHelperBuilder::Object<Version>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_VERSION)
+  cmCTestResourceSpecErrors::NO_VERSION,
+  JSONHelperBuilder::Object<Version>()
     .Bind("major"_s, &Version::Major, VersionFieldHelper)
     .Bind("minor"_s, &Version::Minor, VersionFieldHelper));
 
-auto const RootVersionHelper =
-  JSONHelperBuilder::Object<TopVersion>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_ROOT)
-    .Bind("version"_s, &TopVersion::Version, VersionHelper, false);
+auto const RootVersionHelper = JSONHelperBuilder::Object<TopVersion>().Bind(
+  "version"_s, &TopVersion::Version, VersionHelper, false);
 
-cmCTestResourceSpec::ReadFileResult ResourceIdHelper(std::string& out,
-                                                     const Json::Value* value)
+bool ResourceIdHelper(std::string& out, const Json::Value* value,
+                      cmJSONState* state)
 {
-  auto result = JSONHelperBuilder::String(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE)(out, value);
-  if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
-    return result;
+  if (!JSONHelperBuilder::String(cmCTestResourceSpecErrors::INVALID_RESOURCE)(
+        out, value, state)) {
+    return false;
   }
   cmsys::RegularExpressionMatch match;
   if (!IdRegex.find(out.c_str(), match)) {
-    return cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE;
+    cmCTestResourceSpecErrors::INVALID_RESOURCE(value, state);
+    return false;
   }
-  return cmCTestResourceSpec::ReadFileResult::READ_OK;
+  return true;
 }
 
 auto const ResourceHelper =
-  JSONHelperBuilder::Object<cmCTestResourceSpec::Resource>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE)
+  JSONHelperBuilder::Object<cmCTestResourceSpec::Resource>()
     .Bind("id"_s, &cmCTestResourceSpec::Resource::Id, ResourceIdHelper)
-    .Bind("slots"_s, &cmCTestResourceSpec::Resource::Capacity,
-          JSONHelperBuilder::UInt(
-            cmCTestResourceSpec::ReadFileResult::READ_OK,
-            cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, 1),
-          false);
+    .Bind(
+      "slots"_s, &cmCTestResourceSpec::Resource::Capacity,
+      JSONHelperBuilder::UInt(cmCTestResourceSpecErrors::INVALID_RESOURCE, 1),
+      false);
 
 auto const ResourceListHelper =
   JSONHelperBuilder::Vector<cmCTestResourceSpec::Resource>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE_TYPE,
-    ResourceHelper);
+    cmCTestResourceSpecErrors::INVALID_RESOURCE_TYPE, ResourceHelper);
 
 auto const ResourceMapHelper =
   JSONHelperBuilder::MapFilter<std::vector<cmCTestResourceSpec::Resource>>(
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    ResourceListHelper, [](const std::string& key) -> bool {
+    cmCTestResourceSpecErrors::INVALID_SOCKET_SPEC, ResourceListHelper,
+    [](const std::string& key) -> bool {
       cmsys::RegularExpressionMatch match;
       return IdentifierRegex.find(key.c_str(), match);
     });
 
 auto const SocketSetHelper = JSONHelperBuilder::Vector<
   std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>>(
-  cmCTestResourceSpec::ReadFileResult::READ_OK,
-  cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, ResourceMapHelper);
+  cmCTestResourceSpecErrors::INVALID_SOCKET_SPEC, ResourceMapHelper);
 
-cmCTestResourceSpec::ReadFileResult SocketHelper(
-  cmCTestResourceSpec::Socket& out, const Json::Value* value)
+bool SocketHelper(cmCTestResourceSpec::Socket& out, const Json::Value* value,
+                  cmJSONState* state)
 {
   std::vector<
     std::map<std::string, std::vector<cmCTestResourceSpec::Resource>>>
     sockets;
-  cmCTestResourceSpec::ReadFileResult result = SocketSetHelper(sockets, value);
-  if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
-    return result;
+  if (!SocketSetHelper(sockets, value, state)) {
+    return false;
   }
   if (sockets.size() > 1) {
-    return cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC;
+    cmCTestResourceSpecErrors::INVALID_SOCKET_SPEC(value, state);
+    return false;
   }
   if (sockets.empty()) {
     out.Resources.clear();
   } else {
     out.Resources = std::move(sockets[0]);
   }
-  return cmCTestResourceSpec::ReadFileResult::READ_OK;
+  return true;
 }
 
 auto const LocalRequiredHelper =
   JSONHelperBuilder::Required<cmCTestResourceSpec::Socket>(
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC, SocketHelper);
+    cmCTestResourceSpecErrors::INVALID_SOCKET_SPEC, SocketHelper);
 
-auto const RootHelper = JSONHelperBuilder::Object<cmCTestResourceSpec>(
-                          cmCTestResourceSpec::ReadFileResult::READ_OK,
-                          cmCTestResourceSpec::ReadFileResult::INVALID_ROOT)
-                          .Bind("local", &cmCTestResourceSpec::LocalSocket,
-                                LocalRequiredHelper, false);
+auto const RootHelper = JSONHelperBuilder::Object<cmCTestResourceSpec>().Bind(
+  "local", &cmCTestResourceSpec::LocalSocket, LocalRequiredHelper, false);
 }
 
-cmCTestResourceSpec::ReadFileResult cmCTestResourceSpec::ReadFromJSONFile(
-  const std::string& filename)
+bool cmCTestResourceSpec::ReadFromJSONFile(const std::string& filename)
 {
-  cmsys::ifstream fin(filename.c_str());
-  if (!fin) {
-    return ReadFileResult::FILE_NOT_FOUND;
-  }
-
   Json::Value root;
-  Json::CharReaderBuilder builder;
-  if (!Json::parseFromStream(builder, fin, &root, nullptr)) {
-    return ReadFileResult::JSON_PARSE_ERROR;
+
+  this->parseState = cmJSONState(filename, &root);
+  if (!this->parseState.errors.empty()) {
+    return false;
   }
 
   TopVersion version;
-  ReadFileResult result;
-  if ((result = RootVersionHelper(version, &root)) !=
-      ReadFileResult::READ_OK) {
+  bool result;
+  if ((result = RootVersionHelper(version, &root, &parseState)) != true) {
     return result;
   }
   if (version.Version.Major != 1 || version.Version.Minor != 0) {
-    return ReadFileResult::UNSUPPORTED_VERSION;
+    return false;
   }
 
-  return RootHelper(*this, &root);
-}
-
-const char* cmCTestResourceSpec::ResultToString(ReadFileResult result)
-{
-  switch (result) {
-    case ReadFileResult::READ_OK:
-      return "OK";
-
-    case ReadFileResult::FILE_NOT_FOUND:
-      return "File not found";
-
-    case ReadFileResult::JSON_PARSE_ERROR:
-      return "JSON parse error";
-
-    case ReadFileResult::INVALID_ROOT:
-      return "Invalid root object";
-
-    case ReadFileResult::NO_VERSION:
-      return "No version specified";
-
-    case ReadFileResult::INVALID_VERSION:
-      return "Invalid version object";
-
-    case ReadFileResult::UNSUPPORTED_VERSION:
-      return "Unsupported version";
-
-    case ReadFileResult::INVALID_SOCKET_SPEC:
-      return "Invalid socket object";
-
-    case ReadFileResult::INVALID_RESOURCE_TYPE:
-      return "Invalid resource type object";
-
-    case ReadFileResult::INVALID_RESOURCE:
-      return "Invalid resource object";
-
-    default:
-      return "Unknown";
-  }
+  return RootHelper(*this, &root, &parseState);
 }
 
 bool cmCTestResourceSpec::operator==(const cmCTestResourceSpec& other) const

+ 43 - 16
Source/CTest/cmCTestResourceSpec.h

@@ -8,6 +8,12 @@
 #include <string>
 #include <vector>
 
+#include "cmJSONState.h"
+
+namespace Json {
+class Value;
+}
+
 class cmCTestResourceSpec
 {
 public:
@@ -31,24 +37,45 @@ public:
   };
 
   Socket LocalSocket;
+  cmJSONState parseState;
 
-  enum class ReadFileResult
-  {
-    READ_OK,
-    FILE_NOT_FOUND,
-    JSON_PARSE_ERROR,
-    INVALID_ROOT,
-    NO_VERSION,
-    INVALID_VERSION,
-    UNSUPPORTED_VERSION,
-    INVALID_SOCKET_SPEC, // Can't be INVALID_SOCKET due to a Windows macro
-    INVALID_RESOURCE_TYPE,
-    INVALID_RESOURCE,
-  };
-
-  ReadFileResult ReadFromJSONFile(const std::string& filename);
-  static const char* ResultToString(ReadFileResult result);
+  bool ReadFromJSONFile(const std::string& filename);
 
   bool operator==(const cmCTestResourceSpec& other) const;
   bool operator!=(const cmCTestResourceSpec& other) const;
 };
+
+namespace cmCTestResourceSpecErrors {
+const auto FILE_NOT_FOUND = [](const Json::Value*, cmJSONState* state) {
+  state->AddError("File not found");
+};
+const auto JSON_PARSE_ERROR = [](const Json::Value* value,
+                                 cmJSONState* state) {
+  state->AddErrorAtValue("JSON parse error", value);
+};
+const auto INVALID_ROOT = [](const Json::Value* value, cmJSONState* state) {
+  state->AddErrorAtValue("Invalid root object", value);
+};
+const auto NO_VERSION = [](const Json::Value* value, cmJSONState* state) {
+  state->AddErrorAtValue("No version specified", value);
+};
+const auto INVALID_VERSION = [](const Json::Value* value, cmJSONState* state) {
+  state->AddErrorAtValue("Invalid version object", value);
+};
+const auto UNSUPPORTED_VERSION = [](const Json::Value* value,
+                                    cmJSONState* state) {
+  state->AddErrorAtValue("Unsupported version", value);
+};
+const auto INVALID_SOCKET_SPEC = [](const Json::Value* value,
+                                    cmJSONState* state) {
+  state->AddErrorAtValue("Invalid socket object", value);
+};
+const auto INVALID_RESOURCE_TYPE = [](const Json::Value* value,
+                                      cmJSONState* state) {
+  state->AddErrorAtValue("Invalid resource type object", value);
+};
+const auto INVALID_RESOURCE = [](const Json::Value* value,
+                                 cmJSONState* state) {
+  state->AddErrorAtValue("Invalid resource object", value);
+};
+}

+ 3 - 3
Source/CTest/cmCTestTestHandler.cxx

@@ -37,6 +37,7 @@
 #include "cmExecutionStatus.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
+#include "cmJSONState.h"
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStateSnapshot.h"
@@ -1346,12 +1347,11 @@ bool cmCTestTestHandler::ProcessDirectory(std::vector<std::string>& passed,
   }
   if (!this->ResourceSpecFile.empty()) {
     this->UseResourceSpec = true;
-    auto result = this->ResourceSpec.ReadFromJSONFile(this->ResourceSpecFile);
-    if (result != cmCTestResourceSpec::ReadFileResult::READ_OK) {
+    if (!this->ResourceSpec.ReadFromJSONFile(this->ResourceSpecFile)) {
       cmCTestLog(this->CTest, ERROR_MESSAGE,
                  "Could not read/parse resource spec file "
                    << this->ResourceSpecFile << ": "
-                   << cmCTestResourceSpec::ResultToString(result)
+                   << this->ResourceSpec.parseState.GetErrorMessage()
                    << std::endl);
       return false;
     }

+ 3 - 4
Source/QtDialog/CMakeSetupDialog.cxx

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

+ 1 - 2
Source/QtDialog/CMakeSetupDialog.h

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

+ 5 - 4
Source/QtDialog/QCMake.cxx

@@ -33,7 +33,6 @@ QCMake::QCMake(QObject* p)
   qRegisterMetaType<QCMakePropertyList>();
   qRegisterMetaType<QProcessEnvironment>();
   qRegisterMetaType<QVector<QCMakePreset>>();
-  qRegisterMetaType<cmCMakePresetsGraph::ReadFileResult>();
 
   cmSystemTools::DisableRunCommandOutput();
   cmSystemTools::SetRunCommandHideConsole(true);
@@ -530,9 +529,11 @@ void QCMake::loadPresets()
 {
   auto result = this->CMakePresetsGraph.ReadProjectPresets(
     this->SourceDirectory.toStdString(), true);
-  if (result != this->LastLoadPresetsResult &&
-      result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
-    emit this->presetLoadError(this->SourceDirectory, result);
+  if (result != this->LastLoadPresetsResult && !result) {
+    emit this->presetLoadError(
+      this->SourceDirectory,
+      QString::fromStdString(
+        this->CMakePresetsGraph.parseState.GetErrorMessage(false)));
   }
   this->LastLoadPresetsResult = result;
 

+ 2 - 5
Source/QtDialog/QCMake.h

@@ -60,7 +60,6 @@ using QCMakePropertyList = QList<QCMakeProperty>;
 Q_DECLARE_METATYPE(QCMakeProperty)
 Q_DECLARE_METATYPE(QCMakePropertyList)
 Q_DECLARE_METATYPE(QProcessEnvironment)
-Q_DECLARE_METATYPE(cmCMakePresetsGraph::ReadFileResult)
 
 /// Qt API for CMake library.
 /// Wrapper like class allows for easier integration with
@@ -158,8 +157,7 @@ signals:
   /// signal when the selected preset changes
   void presetChanged(const QString& name);
   /// signal when there's an error reading the presets files
-  void presetLoadError(const QString& dir,
-                       cmCMakePresetsGraph::ReadFileResult error);
+  void presetLoadError(const QString& dir, const QString& error);
   /// signal when uninitialized warning changes
   void warnUninitializedModeChanged(bool value);
   /// signal for progress events
@@ -203,8 +201,7 @@ protected:
   QString Toolset;
   std::vector<cmake::GeneratorInfo> AvailableGenerators;
   cmCMakePresetsGraph CMakePresetsGraph;
-  cmCMakePresetsGraph::ReadFileResult LastLoadPresetsResult =
-    cmCMakePresetsGraph::ReadFileResult::READ_OK;
+  bool LastLoadPresetsResult = true;
   QString PresetName;
   QString CMakeExecutable;
   QAtomicInt InterruptFlag;

+ 242 - 0
Source/cmCMakePresetErrors.h

@@ -0,0 +1,242 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cm3p/json/value.h>
+
+#include "cmJSONHelpers.h"
+#include "cmJSONState.h"
+#include "cmStringAlgorithms.h"
+
+namespace cmCMakePresetErrors {
+const auto getPreset = [](cmJSONState* state) -> const Json::Value* {
+  if (state->parseStack.size() < 2) {
+    return nullptr;
+  }
+  std::string firstKey = state->parseStack[0].first;
+  if (firstKey == "configurePresets" || firstKey == "packagePresets" ||
+      firstKey == "buildPresets" || firstKey == "testPresets") {
+    return state->parseStack[1].second;
+  }
+  return nullptr;
+};
+const auto getPresetName = [](cmJSONState* state) -> std::string {
+#if !defined(CMAKE_BOOTSTRAP)
+  const Json::Value* preset = getPreset(state);
+  if (preset != nullptr && preset->isMember("name")) {
+    return preset->operator[]("name").asString();
+  }
+#endif
+  return "";
+};
+const auto getVariableName = [](cmJSONState* state) -> std::string {
+  std::string var = state->key_after("cacheVariables");
+  std::string errMsg = cmStrCat("variable \"", var, "\"");
+  errMsg = cmStrCat(errMsg, " for preset \"", getPresetName(state), "\"");
+  return errMsg;
+};
+const auto FILE_NOT_FOUND = [](const std::string& filename,
+                               cmJSONState* state) -> void {
+  state->AddError(cmStrCat("File not found: ", filename));
+};
+const auto INVALID_ROOT = [](const Json::Value* value,
+                             cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid root object", value);
+};
+const auto NO_VERSION = [](const Json::Value* value,
+                           cmJSONState* state) -> void {
+  state->AddErrorAtValue("No \"version\" field", value);
+};
+const auto INVALID_VERSION = [](const Json::Value* value,
+                                cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid \"version\" field", value);
+};
+const auto UNRECOGNIZED_VERSION = [](const Json::Value* value,
+                                     cmJSONState* state) -> void {
+  state->AddErrorAtValue("Unrecognized \"version\" field", value);
+};
+const auto INVALID_PRESETS = [](const Json::Value* value,
+                                cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid \"configurePresets\" field", value);
+};
+const auto INVALID_PRESET = [](const Json::Value* value,
+                               cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid preset", value);
+};
+const auto INVALID_PRESET_NAMED = [](const std::string& presetName,
+                                     cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Invalid preset: \"", presetName, "\""));
+};
+const auto INVALID_VARIABLE = [](const Json::Value* value,
+                                 cmJSONState* state) -> void {
+  std::string var = cmCMakePresetErrors::getVariableName(state);
+  state->AddErrorAtValue(cmStrCat("Invalid CMake ", var), value);
+};
+const auto DUPLICATE_PRESETS = [](const std::string& presetName,
+                                  cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Duplicate preset: \"", presetName, "\""));
+};
+const auto CYCLIC_PRESET_INHERITANCE = [](const std::string& presetName,
+                                          cmJSONState* state) -> void {
+  state->AddError(
+    cmStrCat("Cyclic preset inheritance for preset \"", presetName, "\""));
+};
+const auto INHERITED_PRESET_UNREACHABLE_FROM_FILE =
+  [](const std::string& presetName, cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Inherited preset \"", presetName,
+                           "\" is unreachable from preset's file"));
+};
+const auto CONFIGURE_PRESET_UNREACHABLE_FROM_FILE =
+  [](const std::string& presetName, cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Configure preset \"", presetName,
+                           "\" is unreachable from preset's file"));
+};
+const auto INVALID_MACRO_EXPANSION = [](const std::string& presetName,
+                                        cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Invalid macro expansion in \"", presetName, "\""));
+};
+const auto BUILD_TEST_PRESETS_UNSUPPORTED = [](const Json::Value*,
+                                               cmJSONState* state) -> void {
+  state->AddError("File version must be 2 or higher for build and test preset "
+                  "support");
+};
+const auto PACKAGE_PRESETS_UNSUPPORTED = [](const Json::Value*,
+                                            cmJSONState* state) -> void {
+  state->AddError(
+    "File version must be 6 or higher for package preset support");
+};
+const auto WORKFLOW_PRESETS_UNSUPPORTED = [](const Json::Value*,
+                                             cmJSONState* state) -> void {
+  state->AddError(
+    "File version must be 6 or higher for workflow preset support");
+};
+const auto INCLUDE_UNSUPPORTED = [](const Json::Value*,
+                                    cmJSONState* state) -> void {
+  state->AddError("File version must be 4 or higher for include support");
+};
+const auto INVALID_INCLUDE = [](const Json::Value* value,
+                                cmJSONState* state) -> void {
+  state->AddErrorAtValue("Invalid \"include\" field", value);
+};
+const auto INVALID_CONFIGURE_PRESET = [](const std::string& presetName,
+                                         cmJSONState* state) -> void {
+  state->AddError(
+    cmStrCat(R"(Invalid "configurePreset": ")", presetName, "\""));
+};
+const auto INSTALL_PREFIX_UNSUPPORTED = [](const Json::Value* value,
+                                           cmJSONState* state) -> void {
+  state->AddErrorAtValue(
+    "File version must be 3 or higher for installDir preset "
+    "support",
+    value);
+};
+const auto CONDITION_UNSUPPORTED = [](cmJSONState* state) -> void {
+  state->AddError("File version must be 3 or higher for condition support");
+};
+const auto TOOLCHAIN_FILE_UNSUPPORTED = [](cmJSONState* state) -> void {
+  state->AddError("File version must be 3 or higher for toolchainFile preset "
+                  "support");
+};
+const auto CYCLIC_INCLUDE = [](const std::string& file,
+                               cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Cyclic include among preset files: ", file));
+};
+const auto TEST_OUTPUT_TRUNCATION_UNSUPPORTED =
+  [](cmJSONState* state) -> void {
+  state->AddError("File version must be 5 or higher for testOutputTruncation "
+                  "preset support");
+};
+const auto INVALID_WORKFLOW_STEPS = [](const std::string& workflowStep,
+                                       cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Invalid workflow step \"", workflowStep, "\""));
+};
+const auto NO_WORKFLOW_STEPS = [](const std::string& presetName,
+                                  cmJSONState* state) -> void {
+  state->AddError(
+    cmStrCat("No workflow steps specified for \"", presetName, "\""));
+};
+const auto FIRST_WORKFLOW_STEP_NOT_CONFIGURE = [](const std::string& stepName,
+                                                  cmJSONState* state) -> void {
+  state->AddError(cmStrCat("First workflow step \"", stepName,
+                           "\" must be a configure step"));
+};
+const auto CONFIGURE_WORKFLOW_STEP_NOT_FIRST = [](const std::string& stepName,
+                                                  cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Configure workflow step \"", stepName,
+                           "\" must be the first step"));
+};
+const auto WORKFLOW_STEP_UNREACHABLE_FROM_FILE =
+  [](const std::string& workflowStep, cmJSONState* state) -> void {
+  state->AddError(cmStrCat("Workflow step \"", workflowStep,
+                           "\" is unreachable from preset's file"));
+};
+const auto CTEST_JUNIT_UNSUPPORTED = [](cmJSONState* state) -> void {
+  state->AddError(
+    "File version must be 6 or higher for CTest JUnit output support");
+};
+const auto UNRECOGNIZED_CMAKE_VERSION = [](const std::string& version,
+                                           int current, int required) {
+  return [version, current, required](const Json::Value* value,
+                                      cmJSONState* state) -> void {
+    state->AddErrorAtValue(cmStrCat("\"cmakeMinimumRequired\" ", version,
+                                    " version ", required,
+                                    " must be less than ", current),
+                           value);
+  };
+};
+const auto INVALID_PRESET_NAME = [](const Json::Value* value,
+                                    cmJSONState* state) -> void {
+  std::string errMsg = "Invalid Preset Name";
+  if (value && value->isConvertibleTo(Json::ValueType::stringValue) &&
+      !value->asString().empty()) {
+    errMsg = cmStrCat(errMsg, ": ", value->asString());
+  }
+  state->AddErrorAtValue(errMsg, value);
+};
+const auto INVALID_CONDITION = [](const Json::Value* value,
+                                  cmJSONState* state) -> void {
+  state->AddErrorAtValue(
+    cmStrCat("Invalid condition for preset \"", getPresetName(state), "\""),
+    value);
+};
+const auto INVALID_CONDITION_OBJECT =
+  [](JsonErrors::ObjectError errorType,
+     const Json::Value::Members& extraFields) {
+    return JsonErrors::INVALID_NAMED_OBJECT(
+      [](const Json::Value*, cmJSONState* state) -> std::string {
+        return cmStrCat(" condition for preset \"", getPresetName(state),
+                        "\"");
+      })(errorType, extraFields);
+  };
+const auto INVALID_VARIABLE_OBJECT =
+  [](JsonErrors::ObjectError errorType,
+     const Json::Value::Members& extraFields) {
+    return JsonErrors::INVALID_NAMED_OBJECT(
+      [](const Json::Value*, cmJSONState* state) -> std::string {
+        return getVariableName(state);
+      })(errorType, extraFields);
+  };
+const auto INVALID_PRESET_OBJECT =
+  [](JsonErrors::ObjectError errorType,
+     const Json::Value::Members& extraFields) {
+    return JsonErrors::INVALID_NAMED_OBJECT(
+      [](const Json::Value*, cmJSONState*) -> std::string {
+        return "Preset";
+      })(errorType, extraFields);
+  };
+const auto INVALID_ROOT_OBJECT = [](JsonErrors::ObjectError errorType,
+                                    const Json::Value::Members& extraFields) {
+  return JsonErrors::INVALID_NAMED_OBJECT(
+    [](const Json::Value*, cmJSONState*) -> std::string {
+      return "root object";
+    })(errorType, extraFields);
+};
+const auto PRESET_MISSING_FIELD = [](const std::string& presetName,
+                                     const std::string& missingField,
+                                     cmJSONState* state) {
+  state->AddError(cmStrCat("Preset \"", presetName, "\" missing field \"",
+                           missingField, "\""));
+};
+}

+ 178 - 201
Source/cmCMakePresetsGraph.cxx

@@ -14,6 +14,7 @@
 
 #include "cmsys/RegularExpression.hxx"
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -39,7 +40,6 @@ enum class CycleStatus
   Verified,
 };
 
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
 using TestPreset = cmCMakePresetsGraph::TestPreset;
@@ -81,17 +81,18 @@ void InheritVector(std::vector<T>& child, const std::vector<T>& parent)
  * inheritance.
  */
 template <class T>
-ReadFileResult VisitPreset(
+bool VisitPreset(
   T& preset,
   std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
-  std::map<std::string, CycleStatus> cycleStatus,
-  const cmCMakePresetsGraph& graph)
+  std::map<std::string, CycleStatus> cycleStatus, cmCMakePresetsGraph& graph)
 {
   switch (cycleStatus[preset.Name]) {
     case CycleStatus::InProgress:
-      return ReadFileResult::CYCLIC_PRESET_INHERITANCE;
+      cmCMakePresetErrors::CYCLIC_PRESET_INHERITANCE(preset.Name,
+                                                     &graph.parseState);
+      return false;
     case CycleStatus::Verified:
-      return ReadFileResult::READ_OK;
+      return true;
     default:
       break;
   }
@@ -99,28 +100,41 @@ ReadFileResult VisitPreset(
   cycleStatus[preset.Name] = CycleStatus::InProgress;
 
   if (preset.Environment.count("") != 0) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name, &graph.parseState);
+    return false;
   }
 
-  CHECK_OK(preset.VisitPresetBeforeInherit());
+  bool result = preset.VisitPresetBeforeInherit();
+  if (!result) {
+    cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name, &graph.parseState);
+    return false;
+  }
 
   for (auto const& i : preset.Inherits) {
     auto parent = presets.find(i);
     if (parent == presets.end()) {
-      return ReadFileResult::INVALID_PRESET;
+      cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name,
+                                                &graph.parseState);
+      return false;
     }
 
     auto& parentPreset = parent->second.Unexpanded;
     if (!preset.OriginFile->ReachableFiles.count(parentPreset.OriginFile)) {
-      return ReadFileResult::INHERITED_PRESET_UNREACHABLE_FROM_FILE;
+      cmCMakePresetErrors::INHERITED_PRESET_UNREACHABLE_FROM_FILE(
+        preset.Name, &graph.parseState);
+      return false;
     }
 
-    auto result = VisitPreset(parentPreset, presets, cycleStatus, graph);
-    if (result != ReadFileResult::READ_OK) {
-      return result;
+    if (!VisitPreset(parentPreset, presets, cycleStatus, graph)) {
+      return false;
     }
 
-    CHECK_OK(preset.VisitPresetInherit(parentPreset));
+    result = preset.VisitPresetInherit(parentPreset);
+    if (!result) {
+      cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name,
+                                                &graph.parseState);
+      return false;
+    }
 
     for (auto const& v : parentPreset.Environment) {
       preset.Environment.insert(v);
@@ -135,16 +149,21 @@ ReadFileResult VisitPreset(
     preset.ConditionEvaluator.reset();
   }
 
-  CHECK_OK(preset.VisitPresetAfterInherit(graph.GetVersion(preset)));
+  result = preset.VisitPresetAfterInherit(graph.GetVersion(preset),
+                                          &graph.parseState);
+  if (!result) {
+    cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name, &graph.parseState);
+    return false;
+  }
 
   cycleStatus[preset.Name] = CycleStatus::Verified;
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 template <class T>
-ReadFileResult ComputePresetInheritance(
+bool ComputePresetInheritance(
   std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
-  const cmCMakePresetsGraph& graph)
+  cmCMakePresetsGraph& graph)
 {
   std::map<std::string, CycleStatus> cycleStatus;
   for (auto const& it : presets) {
@@ -153,13 +172,12 @@ ReadFileResult ComputePresetInheritance(
 
   for (auto& it : presets) {
     auto& preset = it.second.Unexpanded;
-    auto result = VisitPreset<T>(preset, presets, cycleStatus, graph);
-    if (result != ReadFileResult::READ_OK) {
-      return result;
+    if (!VisitPreset<T>(preset, presets, cycleStatus, graph)) {
+      return false;
     }
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 constexpr const char* ValidPrefixes[] = {
@@ -338,7 +356,7 @@ bool ExpandMacros(const cmCMakePresetsGraph& /*graph*/,
 }
 
 template <class T>
-bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
+bool ExpandMacros(cmCMakePresetsGraph& graph, const T& preset,
                   cm::optional<T>& out)
 {
   out.emplace(preset);
@@ -448,6 +466,8 @@ bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
       switch (VisitEnv(*v.second, envCycles[v.first], macroExpanders,
                        graph.GetVersion(preset))) {
         case ExpandMacroResult::Error:
+          cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name,
+                                                    &graph.parseState);
           return false;
         case ExpandMacroResult::Ignore:
           out.reset();
@@ -462,6 +482,8 @@ bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
     cm::optional<bool> result;
     if (!preset.ConditionEvaluator->Evaluate(
           macroExpanders, graph.GetVersion(preset), result)) {
+      cmCMakePresetErrors::INVALID_PRESET_NAMED(preset.Name,
+                                                &graph.parseState);
       return false;
     }
     if (!result) {
@@ -594,39 +616,44 @@ ExpandMacroResult ExpandMacro(std::string& out,
 }
 
 template <typename T>
-ReadFileResult SetupWorkflowConfigurePreset(
-  const T& preset, const ConfigurePreset*& configurePreset)
+bool SetupWorkflowConfigurePreset(const T& preset,
+                                  const ConfigurePreset*& configurePreset,
+                                  cmJSONState* state)
 {
   if (preset.ConfigurePreset != configurePreset->Name) {
-    return ReadFileResult::INVALID_WORKFLOW_STEPS;
+    cmCMakePresetErrors::INVALID_WORKFLOW_STEPS(configurePreset->Name, state);
+    return false;
   }
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 template <>
-ReadFileResult SetupWorkflowConfigurePreset<ConfigurePreset>(
-  const ConfigurePreset& preset, const ConfigurePreset*& configurePreset)
+bool SetupWorkflowConfigurePreset<ConfigurePreset>(
+  const ConfigurePreset& preset, const ConfigurePreset*& configurePreset,
+  cmJSONState*)
 {
   configurePreset = &preset;
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 template <typename T>
-ReadFileResult TryReachPresetFromWorkflow(
+bool TryReachPresetFromWorkflow(
   const WorkflowPreset& origin,
   const std::map<std::string, PresetPair<T>>& presets, const std::string& name,
-  const ConfigurePreset*& configurePreset)
+  const ConfigurePreset*& configurePreset, cmJSONState* state)
 {
   auto it = presets.find(name);
   if (it == presets.end()) {
-    return ReadFileResult::INVALID_WORKFLOW_STEPS;
+    cmCMakePresetErrors::INVALID_WORKFLOW_STEPS(name, state);
+    return false;
   }
   if (!origin.OriginFile->ReachableFiles.count(
         it->second.Unexpanded.OriginFile)) {
-    return ReadFileResult::WORKFLOW_STEP_UNREACHABLE_FROM_FILE;
+    cmCMakePresetErrors::WORKFLOW_STEP_UNREACHABLE_FROM_FILE(name, state);
+    return false;
   }
   return SetupWorkflowConfigurePreset<T>(it->second.Unexpanded,
-                                         configurePreset);
+                                         configurePreset, state);
 }
 }
 
@@ -722,8 +749,7 @@ bool cmCMakePresetsGraphInternal::NotCondition::Evaluate(
   return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::ConfigurePreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::ConfigurePreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
@@ -753,50 +779,52 @@ cmCMakePresetsGraph::ConfigurePreset::VisitPresetInherit(
     preset.CacheVariables.insert(v);
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::ConfigurePreset::VisitPresetBeforeInherit()
+bool cmCMakePresetsGraph::ConfigurePreset::VisitPresetBeforeInherit()
 {
   auto& preset = *this;
   if (preset.Environment.count("") != 0) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::ConfigurePreset::VisitPresetAfterInherit(int version)
+bool cmCMakePresetsGraph::ConfigurePreset::VisitPresetAfterInherit(
+  int version, cmJSONState* state)
 {
   auto& preset = *this;
   if (!preset.Hidden) {
     if (version < 3) {
       if (preset.Generator.empty()) {
-        return ReadFileResult::INVALID_PRESET;
+        cmCMakePresetErrors::PRESET_MISSING_FIELD(preset.Name, "generator",
+                                                  state);
+        return false;
       }
       if (preset.BinaryDir.empty()) {
-        return ReadFileResult::INVALID_PRESET;
+        cmCMakePresetErrors::PRESET_MISSING_FIELD(preset.Name, "binaryDir",
+                                                  state);
+        return false;
       }
     }
 
     if (preset.WarnDev == false && preset.ErrorDev == true) {
-      return ReadFileResult::INVALID_PRESET;
+      return false;
     }
     if (preset.WarnDeprecated == false && preset.ErrorDeprecated == true) {
-      return ReadFileResult::INVALID_PRESET;
+      return false;
     }
     if (preset.CacheVariables.count("") != 0) {
-      return ReadFileResult::INVALID_PRESET;
+      return false;
     }
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::BuildPreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::BuildPreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
@@ -815,21 +843,20 @@ cmCMakePresetsGraph::BuildPreset::VisitPresetInherit(
     preset.ResolvePackageReferences = parent.ResolvePackageReferences;
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::BuildPreset::VisitPresetAfterInherit(int /* version */)
+bool cmCMakePresetsGraph::BuildPreset::VisitPresetAfterInherit(
+  int /* version */, cmJSONState* /*stat*/)
 {
   auto& preset = *this;
   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::TestPreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::TestPreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
@@ -928,21 +955,20 @@ cmCMakePresetsGraph::TestPreset::VisitPresetInherit(
     }
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::TestPreset::VisitPresetAfterInherit(int /* version */)
+bool cmCMakePresetsGraph::TestPreset::VisitPresetAfterInherit(
+  int /* version */, cmJSONState* /*state*/)
 {
   auto& preset = *this;
   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::PackagePreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::PackagePreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& parentPreset)
 {
   auto& preset = *this;
@@ -966,30 +992,29 @@ cmCMakePresetsGraph::PackagePreset::VisitPresetInherit(
   InheritString(preset.PackageDirectory, parent.PackageDirectory);
   InheritString(preset.VendorName, parent.VendorName);
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::PackagePreset::VisitPresetAfterInherit(int /* version */)
+bool cmCMakePresetsGraph::PackagePreset::VisitPresetAfterInherit(
+  int /* version */, cmJSONState* /*state*/)
 {
   auto& preset = *this;
   if (!preset.Hidden && preset.ConfigurePreset.empty()) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::WorkflowPreset::VisitPresetInherit(
+bool cmCMakePresetsGraph::WorkflowPreset::VisitPresetInherit(
   const cmCMakePresetsGraph::Preset& /*parentPreset*/)
 {
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::WorkflowPreset::VisitPresetAfterInherit(int /* version */)
+bool cmCMakePresetsGraph::WorkflowPreset::VisitPresetAfterInherit(
+  int /* version */, cmJSONState* /*state*/)
 {
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
 std::string cmCMakePresetsGraph::GetFilename(const std::string& sourceDir)
@@ -1002,22 +1027,21 @@ std::string cmCMakePresetsGraph::GetUserFilename(const std::string& sourceDir)
   return cmStrCat(sourceDir, "/CMakeUserPresets.json");
 }
 
-cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadProjectPresets(
-  const std::string& sourceDir, bool allowNoFiles)
+bool cmCMakePresetsGraph::ReadProjectPresets(const std::string& sourceDir,
+                                             bool allowNoFiles)
 {
   this->SourceDir = sourceDir;
   this->ClearPresets();
 
-  auto result = this->ReadProjectPresetsInternal(allowNoFiles);
-  if (result != ReadFileResult::READ_OK) {
+  if (!this->ReadProjectPresetsInternal(allowNoFiles)) {
     this->ClearPresets();
+    return false;
   }
 
-  return result;
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult
-cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
+bool cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
 {
   bool haveOneFile = false;
 
@@ -1025,21 +1049,17 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
   std::string filename = GetUserFilename(this->SourceDir);
   std::vector<File*> inProgressFiles;
   if (cmSystemTools::FileExists(filename)) {
-    auto result =
-      this->ReadJSONFile(filename, RootType::User, ReadReason::Root,
-                         inProgressFiles, file, this->errors);
-    if (result != ReadFileResult::READ_OK) {
-      return result;
+    if (!this->ReadJSONFile(filename, RootType::User, ReadReason::Root,
+                            inProgressFiles, file, this->errors)) {
+      return false;
     }
     haveOneFile = true;
   } else {
     filename = GetFilename(this->SourceDir);
     if (cmSystemTools::FileExists(filename)) {
-      auto result =
-        this->ReadJSONFile(filename, RootType::Project, ReadReason::Root,
-                           inProgressFiles, file, this->errors);
-      if (result != ReadFileResult::READ_OK) {
-        return result;
+      if (!this->ReadJSONFile(filename, RootType::Project, ReadReason::Root,
+                              inProgressFiles, file, this->errors)) {
+        return false;
       }
       haveOneFile = true;
     }
@@ -1047,19 +1067,28 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
   assert(inProgressFiles.empty());
 
   if (!haveOneFile) {
-    return allowNoFiles ? ReadFileResult::READ_OK
-                        : ReadFileResult::FILE_NOT_FOUND;
+    if (allowNoFiles) {
+      return true;
+    }
+    cmCMakePresetErrors::FILE_NOT_FOUND(filename, &this->parseState);
+    return false;
   }
 
-  CHECK_OK(ComputePresetInheritance(this->ConfigurePresets, *this));
-  CHECK_OK(ComputePresetInheritance(this->BuildPresets, *this));
-  CHECK_OK(ComputePresetInheritance(this->TestPresets, *this));
-  CHECK_OK(ComputePresetInheritance(this->PackagePresets, *this));
-  CHECK_OK(ComputePresetInheritance(this->WorkflowPresets, *this));
+  bool result = ComputePresetInheritance(this->ConfigurePresets, *this) &&
+    ComputePresetInheritance(this->ConfigurePresets, *this) &&
+    ComputePresetInheritance(this->BuildPresets, *this) &&
+    ComputePresetInheritance(this->TestPresets, *this) &&
+    ComputePresetInheritance(this->PackagePresets, *this) &&
+    ComputePresetInheritance(this->WorkflowPresets, *this);
+  if (!result) {
+    return false;
+  }
 
   for (auto& it : this->ConfigurePresets) {
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
@@ -1068,11 +1097,15 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
       const auto configurePreset =
         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
       if (configurePreset == this->ConfigurePresets.end()) {
-        return ReadFileResult::INVALID_CONFIGURE_PRESET;
+        cmCMakePresetErrors::INVALID_CONFIGURE_PRESET(it.first,
+                                                      &this->parseState);
+        return false;
       }
       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
             configurePreset->second.Unexpanded.OriginFile)) {
-        return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
+        cmCMakePresetErrors::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE(
+          it.first, &this->parseState);
+        return false;
       }
 
       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
@@ -1083,7 +1116,9 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
     }
 
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
@@ -1092,11 +1127,15 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
       const auto configurePreset =
         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
       if (configurePreset == this->ConfigurePresets.end()) {
-        return ReadFileResult::INVALID_CONFIGURE_PRESET;
+        cmCMakePresetErrors::INVALID_CONFIGURE_PRESET(it.first,
+                                                      &this->parseState);
+        return false;
       }
       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
             configurePreset->second.Unexpanded.OriginFile)) {
-        return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
+        cmCMakePresetErrors::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE(
+          it.first, &this->parseState);
+        return false;
       }
 
       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
@@ -1107,7 +1146,9 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
     }
 
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
@@ -1116,11 +1157,15 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
       const auto configurePreset =
         this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
       if (configurePreset == this->ConfigurePresets.end()) {
-        return ReadFileResult::INVALID_CONFIGURE_PRESET;
+        cmCMakePresetErrors::INVALID_CONFIGURE_PRESET(it.first,
+                                                      &this->parseState);
+        return false;
       }
       if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
             configurePreset->second.Unexpanded.OriginFile)) {
-        return ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE;
+        cmCMakePresetErrors::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE(
+          it.first, &this->parseState);
+        return false;
       }
 
       if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
@@ -1131,7 +1176,9 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
     }
 
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
@@ -1141,126 +1188,56 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
     const ConfigurePreset* configurePreset = nullptr;
     for (auto const& step : it.second.Unexpanded.Steps) {
       if (configurePreset == nullptr && step.PresetType != Type::Configure) {
-        return ReadFileResult::INVALID_WORKFLOW_STEPS;
+        cmCMakePresetErrors::FIRST_WORKFLOW_STEP_NOT_CONFIGURE(
+          step.PresetName, &this->parseState);
+        return false;
       }
       if (configurePreset != nullptr && step.PresetType == Type::Configure) {
-        return ReadFileResult::INVALID_WORKFLOW_STEPS;
+        cmCMakePresetErrors::CONFIGURE_WORKFLOW_STEP_NOT_FIRST(
+          step.PresetName, &this->parseState);
+        return false;
       }
 
-      ReadFileResult result;
       switch (step.PresetType) {
         case Type::Configure:
           result = TryReachPresetFromWorkflow(
             it.second.Unexpanded, this->ConfigurePresets, step.PresetName,
-            configurePreset);
+            configurePreset, &this->parseState);
           break;
         case Type::Build:
           result = TryReachPresetFromWorkflow(
             it.second.Unexpanded, this->BuildPresets, step.PresetName,
-            configurePreset);
+            configurePreset, &this->parseState);
           break;
         case Type::Test:
-          result =
-            TryReachPresetFromWorkflow(it.second.Unexpanded, this->TestPresets,
-                                       step.PresetName, configurePreset);
+          result = TryReachPresetFromWorkflow(
+            it.second.Unexpanded, this->TestPresets, step.PresetName,
+            configurePreset, &this->parseState);
           break;
         case Type::Package:
           result = TryReachPresetFromWorkflow(
             it.second.Unexpanded, this->PackagePresets, step.PresetName,
-            configurePreset);
+            configurePreset, &this->parseState);
           break;
       }
-      if (result != ReadFileResult::READ_OK) {
-        return result;
+      if (!result) {
+        return false;
       }
     }
 
     if (configurePreset == nullptr) {
-      return ReadFileResult::INVALID_WORKFLOW_STEPS;
+      cmCMakePresetErrors::NO_WORKFLOW_STEPS(it.first, &this->parseState);
+      return false;
     }
 
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
-      return ReadFileResult::INVALID_MACRO_EXPANSION;
+      cmCMakePresetErrors::INVALID_MACRO_EXPANSION(it.first,
+                                                   &this->parseState);
+      return false;
     }
   }
 
-  return ReadFileResult::READ_OK;
-}
-
-const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
-{
-  switch (result) {
-    case ReadFileResult::READ_OK:
-      return "OK";
-    case ReadFileResult::FILE_NOT_FOUND:
-      return "File not found";
-    case ReadFileResult::JSON_PARSE_ERROR:
-      return "JSON parse error";
-    case ReadFileResult::INVALID_ROOT:
-      return "Invalid root object";
-    case ReadFileResult::NO_VERSION:
-      return "No \"version\" field";
-    case ReadFileResult::INVALID_VERSION:
-      return "Invalid \"version\" field";
-    case ReadFileResult::UNRECOGNIZED_VERSION:
-      return "Unrecognized \"version\" field";
-    case ReadFileResult::INVALID_CMAKE_VERSION:
-      return "Invalid \"cmakeMinimumRequired\" field";
-    case ReadFileResult::UNRECOGNIZED_CMAKE_VERSION:
-      return "\"cmakeMinimumRequired\" version too new";
-    case ReadFileResult::INVALID_PRESETS:
-      return "Invalid \"configurePresets\" field";
-    case ReadFileResult::INVALID_PRESET:
-      return "Invalid preset";
-    case ReadFileResult::INVALID_VARIABLE:
-      return "Invalid CMake variable definition";
-    case ReadFileResult::DUPLICATE_PRESETS:
-      return "Duplicate presets";
-    case ReadFileResult::CYCLIC_PRESET_INHERITANCE:
-      return "Cyclic preset inheritance";
-    case ReadFileResult::INHERITED_PRESET_UNREACHABLE_FROM_FILE:
-      return "Inherited preset is unreachable from preset's file";
-    case ReadFileResult::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE:
-      return "Configure 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::PACKAGE_PRESETS_UNSUPPORTED:
-      return "File version must be 6 or higher for package preset support";
-    case ReadFileResult::WORKFLOW_PRESETS_UNSUPPORTED:
-      return "File version must be 6 or higher for workflow 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:
-      return "File version must be 3 or higher for installDir preset "
-             "support.";
-    case ReadFileResult::INVALID_CONDITION:
-      return "Invalid preset condition";
-    case ReadFileResult::CONDITION_UNSUPPORTED:
-      return "File version must be 3 or higher for condition support";
-    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::TEST_OUTPUT_TRUNCATION_UNSUPPORTED:
-      return "File version must be 5 or higher for testOutputTruncation "
-             "preset support.";
-    case ReadFileResult::INVALID_WORKFLOW_STEPS:
-      return "Invalid workflow steps";
-    case ReadFileResult::WORKFLOW_STEP_UNREACHABLE_FROM_FILE:
-      return "Workflow step is unreachable from preset's file";
-    case ReadFileResult::CTEST_JUNIT_UNSUPPORTED:
-      return "File version must be 6 or higher for CTest JUnit output support";
-  }
-
-  return "Unknown error";
+  return true;
 }
 
 void cmCMakePresetsGraph::ClearPresets()

+ 30 - 62
Source/cmCMakePresetsGraph.h

@@ -14,6 +14,8 @@
 
 #include <cm/optional>
 
+#include "cmJSONState.h"
+
 #include "CTest/cmCTestTypes.h"
 
 enum class PackageResolveMode;
@@ -21,43 +23,9 @@ enum class PackageResolveMode;
 class cmCMakePresetsGraph
 {
 public:
-  enum class ReadFileResult
-  {
-    READ_OK,
-    FILE_NOT_FOUND,
-    JSON_PARSE_ERROR,
-    INVALID_ROOT,
-    NO_VERSION,
-    INVALID_VERSION,
-    UNRECOGNIZED_VERSION,
-    INVALID_CMAKE_VERSION,
-    UNRECOGNIZED_CMAKE_VERSION,
-    INVALID_PRESETS,
-    INVALID_PRESET,
-    INVALID_VARIABLE,
-    DUPLICATE_PRESETS,
-    CYCLIC_PRESET_INHERITANCE,
-    INHERITED_PRESET_UNREACHABLE_FROM_FILE,
-    CONFIGURE_PRESET_UNREACHABLE_FROM_FILE,
-    INVALID_MACRO_EXPANSION,
-    BUILD_TEST_PRESETS_UNSUPPORTED,
-    PACKAGE_PRESETS_UNSUPPORTED,
-    WORKFLOW_PRESETS_UNSUPPORTED,
-    INCLUDE_UNSUPPORTED,
-    INVALID_INCLUDE,
-    INVALID_CONFIGURE_PRESET,
-    INSTALL_PREFIX_UNSUPPORTED,
-    INVALID_CONDITION,
-    CONDITION_UNSUPPORTED,
-    TOOLCHAIN_FILE_UNSUPPORTED,
-    CYCLIC_INCLUDE,
-    TEST_OUTPUT_TRUNCATION_UNSUPPORTED,
-    INVALID_WORKFLOW_STEPS,
-    WORKFLOW_STEP_UNREACHABLE_FROM_FILE,
-    CTEST_JUNIT_UNSUPPORTED,
-  };
-
   std::string errors;
+  cmJSONState parseState;
+
   enum class ArchToolsetStrategy
   {
     Set,
@@ -111,15 +79,13 @@ public:
 
     std::map<std::string, cm::optional<std::string>> Environment;
 
-    virtual ReadFileResult VisitPresetInherit(const Preset& parent) = 0;
-    virtual ReadFileResult VisitPresetBeforeInherit()
-    {
-      return ReadFileResult::READ_OK;
-    }
+    virtual bool VisitPresetInherit(const Preset& parent) = 0;
+    virtual bool VisitPresetBeforeInherit() { return true; }
 
-    virtual ReadFileResult VisitPresetAfterInherit(int /* version */)
+    virtual bool VisitPresetAfterInherit(int /* version */,
+                                         cmJSONState* /*state*/)
     {
-      return ReadFileResult::READ_OK;
+      return true;
     }
   };
 
@@ -163,9 +129,9 @@ public:
     cm::optional<bool> DebugTryCompile;
     cm::optional<bool> DebugFind;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetBeforeInherit() override;
-    ReadFileResult VisitPresetAfterInherit(int version) override;
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetBeforeInherit() override;
+    bool VisitPresetAfterInherit(int version, cmJSONState* state) override;
   };
 
   class BuildPreset : public Preset
@@ -195,8 +161,9 @@ public:
     std::vector<std::string> NativeToolOptions;
     cm::optional<PackageResolveMode> ResolvePackageReferences;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetAfterInherit(int /* version */) override;
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetAfterInherit(int /* version */,
+                                 cmJSONState* /*state*/) override;
   };
 
   class TestPreset : public Preset
@@ -328,8 +295,9 @@ public:
     cm::optional<FilterOptions> Filter;
     cm::optional<ExecutionOptions> Execution;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetAfterInherit(int /* version */) override;
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetAfterInherit(int /* version */,
+                                 cmJSONState* /*state*/) override;
   };
 
   class PackagePreset : public Preset
@@ -364,8 +332,9 @@ public:
     std::string PackageDirectory;
     std::string VendorName;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetAfterInherit(int /* version */) override;
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetAfterInherit(int /* version */,
+                                 cmJSONState* /*state*/) override;
   };
 
   class WorkflowPreset : public Preset
@@ -401,8 +370,9 @@ public:
 
     std::vector<WorkflowStep> Steps;
 
-    ReadFileResult VisitPresetInherit(const Preset& parent) override;
-    ReadFileResult VisitPresetAfterInherit(int /* version */) override;
+    bool VisitPresetInherit(const Preset& parent) override;
+    bool VisitPresetAfterInherit(int /* version */,
+                                 cmJSONState* /* state */) override;
   };
 
   template <class T>
@@ -435,9 +405,8 @@ public:
 
   static std::string GetFilename(const std::string& sourceDir);
   static std::string GetUserFilename(const std::string& sourceDir);
-  ReadFileResult ReadProjectPresets(const std::string& sourceDir,
-                                    bool allowNoFiles = false);
-  static const char* ResultToString(ReadFileResult result);
+  bool ReadProjectPresets(const std::string& sourceDir,
+                          bool allowNoFiles = false);
 
   std::string GetGeneratorForPreset(const std::string& presetName) const
   {
@@ -502,10 +471,9 @@ private:
     Included,
   };
 
-  ReadFileResult ReadProjectPresetsInternal(bool allowNoFiles);
-  ReadFileResult ReadJSONFile(const std::string& filename, RootType rootType,
-                              ReadReason readReason,
-                              std::vector<File*>& inProgressFiles, File*& file,
-                              std::string& errMsg);
+  bool ReadProjectPresetsInternal(bool allowNoFiles);
+  bool ReadJSONFile(const std::string& filename, RootType rootType,
+                    ReadReason readReason, std::vector<File*>& inProgressFiles,
+                    File*& file, std::string& errMsg);
   void ClearPresets();
 };

+ 34 - 35
Source/cmCMakePresetsGraphInternal.h

@@ -14,7 +14,7 @@
 #define CHECK_OK(expr)                                                        \
   do {                                                                        \
     auto _result = expr;                                                      \
-    if (_result != ReadFileResult::READ_OK)                                   \
+    if (_result != true)                                                      \
       return _result;                                                         \
   } while (false)
 
@@ -117,57 +117,56 @@ public:
   std::unique_ptr<Condition> SubCondition;
 };
 
-cmCMakePresetsGraph::ReadFileResult PresetStringHelper(
-  std::string& out, const Json::Value* value);
+bool PresetStringHelper(std::string& out, const Json::Value* value,
+                        cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorStringHelper(
-  std::vector<std::string>& out, const Json::Value* value);
+bool PresetNameHelper(std::string& out, const Json::Value* value,
+                      cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetBoolHelper(bool& out,
-                                                     const Json::Value* value);
+bool PresetVectorStringHelper(std::vector<std::string>& out,
+                              const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetOptionalBoolHelper(
-  cm::optional<bool>& out, const Json::Value* value);
+bool PresetBoolHelper(bool& out, const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetIntHelper(int& out,
-                                                    const Json::Value* value);
+bool PresetOptionalBoolHelper(cm::optional<bool>& out,
+                              const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetOptionalIntHelper(
-  cm::optional<int>& out, const Json::Value* value);
+bool PresetIntHelper(int& out, const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorIntHelper(
-  std::vector<int>& out, const Json::Value* value);
+bool PresetOptionalIntHelper(cm::optional<int>& out, const Json::Value* value,
+                             cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult ConfigurePresetsHelper(
+bool PresetVectorIntHelper(std::vector<int>& out, const Json::Value* value,
+                           cmJSONState* state);
+
+bool ConfigurePresetsHelper(
   std::vector<cmCMakePresetsGraph::ConfigurePreset>& out,
-  const Json::Value* value);
+  const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult BuildPresetsHelper(
-  std::vector<cmCMakePresetsGraph::BuildPreset>& out,
-  const Json::Value* value);
+bool BuildPresetsHelper(std::vector<cmCMakePresetsGraph::BuildPreset>& out,
+                        const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult TestPresetsHelper(
-  std::vector<cmCMakePresetsGraph::TestPreset>& out, const Json::Value* value);
+bool TestPresetsHelper(std::vector<cmCMakePresetsGraph::TestPreset>& out,
+                       const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PackagePresetsHelper(
-  std::vector<cmCMakePresetsGraph::PackagePreset>& out,
-  const Json::Value* value);
+bool PackagePresetsHelper(std::vector<cmCMakePresetsGraph::PackagePreset>& out,
+                          const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult WorkflowPresetsHelper(
+bool WorkflowPresetsHelper(
   std::vector<cmCMakePresetsGraph::WorkflowPreset>& out,
-  const Json::Value* value);
+  const Json::Value* value, cmJSONState* state);
 
-cmJSONHelper<std::nullptr_t, cmCMakePresetsGraph::ReadFileResult> VendorHelper(
-  cmCMakePresetsGraph::ReadFileResult error);
+cmJSONHelper<std::nullptr_t> VendorHelper(const ErrorGenerator& error);
 
-cmCMakePresetsGraph::ReadFileResult PresetConditionHelper(
+bool PresetConditionHelper(
   std::shared_ptr<cmCMakePresetsGraph::Condition>& out,
-  const Json::Value* value);
+  const Json::Value* value, cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorOneOrMoreStringHelper(
-  std::vector<std::string>& out, const Json::Value* value);
+bool PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
+                                       const Json::Value* value,
+                                       cmJSONState* state);
 
-cmCMakePresetsGraph::ReadFileResult EnvironmentMapHelper(
+bool EnvironmentMapHelper(
   std::map<std::string, cm::optional<std::string>>& out,
-  const Json::Value* value);
+  const Json::Value* value, cmJSONState* state);
 }

+ 228 - 198
Source/cmCMakePresetsGraphReadJSON.cxx

@@ -1,6 +1,7 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include <algorithm>
+#include <fstream>
 #include <functional>
 #include <map>
 #include <string>
@@ -12,20 +13,18 @@
 #include <cm/optional>
 #include <cmext/string_view>
 
-#include <cm3p/json/reader.h>
 #include <cm3p/json/value.h>
 
-#include "cmsys/FStream.hxx"
-
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
+#include "cmJSONState.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;
@@ -33,7 +32,7 @@ using TestPreset = cmCMakePresetsGraph::TestPreset;
 using PackagePreset = cmCMakePresetsGraph::PackagePreset;
 using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
 using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
-using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
 
 constexpr int MIN_VERSION = 1;
 constexpr int MAX_VERSION = 6;
@@ -64,26 +63,23 @@ std::unique_ptr<cmCMakePresetsGraphInternal::NotCondition> InvertCondition(
   return retval;
 }
 
-auto const ConditionStringHelper = JSONHelperBuilder::String(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
+auto const ConditionStringHelper = JSONHelperBuilder::String();
 
-auto const ConditionBoolHelper = JSONHelperBuilder::Bool(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION);
+auto const ConditionBoolHelper = JSONHelperBuilder::Bool();
 
 auto const ConditionStringListHelper = JSONHelperBuilder::Vector<std::string>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION,
-  ConditionStringHelper);
+  cmCMakePresetErrors::INVALID_CONDITION, ConditionStringHelper);
 
 auto const ConstConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::ConstCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("value"_s, &cmCMakePresetsGraphInternal::ConstCondition::Value,
           ConditionBoolHelper, true);
 
 auto const EqualsConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::EqualsCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("lhs"_s, &cmCMakePresetsGraphInternal::EqualsCondition::Lhs,
           ConditionStringHelper, true)
@@ -92,7 +88,7 @@ auto const EqualsConditionHelper =
 
 auto const InListConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::InListCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("string"_s, &cmCMakePresetsGraphInternal::InListCondition::String,
           ConditionStringHelper, true)
@@ -101,24 +97,22 @@ auto const InListConditionHelper =
 
 auto const MatchesConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::MatchesCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, 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);
+bool SubConditionHelper(std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
+                        const Json::Value* value, cmJSONState* state);
 
 auto const ListConditionVectorHelper =
   JSONHelperBuilder::Vector<std::unique_ptr<cmCMakePresetsGraph::Condition>>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION,
-    SubConditionHelper);
+    cmCMakePresetErrors::INVALID_CONDITION, SubConditionHelper);
 auto const AnyAllOfConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::AnyAllOfCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, false)
     .Bind<std::string>("type"_s, nullptr, ConditionStringHelper, true)
     .Bind("conditions"_s,
           &cmCMakePresetsGraphInternal::AnyAllOfCondition::Conditions,
@@ -126,158 +120,160 @@ auto const AnyAllOfConditionHelper =
 
 auto const NotConditionHelper =
   JSONHelperBuilder::Object<cmCMakePresetsGraphInternal::NotCondition>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CONDITION, false)
+    cmCMakePresetErrors::INVALID_CONDITION_OBJECT, 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)
+bool ConditionHelper(std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
+                     const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out.reset();
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isBool()) {
     auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
     c->Value = value->asBool();
     out = std::move(c);
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isNull()) {
     out = cm::make_unique<cmCMakePresetsGraphInternal::NullCondition>();
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isObject()) {
     if (!value->isMember("type")) {
-      return ReadFileResult::INVALID_CONDITION;
+      cmCMakePresetErrors::INVALID_CONDITION(value, state);
+      return false;
     }
 
     if (!(*value)["type"].isString()) {
-      return ReadFileResult::INVALID_CONDITION;
+      cmCMakePresetErrors::INVALID_CONDITION(value, state);
+      return false;
     }
     auto type = (*value)["type"].asString();
 
     if (type == "const") {
       auto c = cm::make_unique<cmCMakePresetsGraphInternal::ConstCondition>();
-      CHECK_OK(ConstConditionHelper(*c, value));
+      CHECK_OK(ConstConditionHelper(*c, value, state));
       out = std::move(c);
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "equals" || type == "notEquals") {
       auto c = cm::make_unique<cmCMakePresetsGraphInternal::EqualsCondition>();
-      CHECK_OK(EqualsConditionHelper(*c, value));
+      CHECK_OK(EqualsConditionHelper(*c, value, state));
       out = std::move(c);
       if (type == "notEquals") {
         out = InvertCondition(std::move(out));
       }
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "inList" || type == "notInList") {
       auto c = cm::make_unique<cmCMakePresetsGraphInternal::InListCondition>();
-      CHECK_OK(InListConditionHelper(*c, value));
+      CHECK_OK(InListConditionHelper(*c, value, state));
       out = std::move(c);
       if (type == "notInList") {
         out = InvertCondition(std::move(out));
       }
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "matches" || type == "notMatches") {
       auto c =
         cm::make_unique<cmCMakePresetsGraphInternal::MatchesCondition>();
-      CHECK_OK(MatchesConditionHelper(*c, value));
+      CHECK_OK(MatchesConditionHelper(*c, value, state));
       out = std::move(c);
       if (type == "notMatches") {
         out = InvertCondition(std::move(out));
       }
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "anyOf" || type == "allOf") {
       auto c =
         cm::make_unique<cmCMakePresetsGraphInternal::AnyAllOfCondition>();
       c->StopValue = (type == "anyOf");
-      CHECK_OK(AnyAllOfConditionHelper(*c, value));
+      CHECK_OK(AnyAllOfConditionHelper(*c, value, state));
       out = std::move(c);
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (type == "not") {
       auto c = cm::make_unique<cmCMakePresetsGraphInternal::NotCondition>();
-      CHECK_OK(NotConditionHelper(*c, value));
+      CHECK_OK(NotConditionHelper(*c, value, state));
       out = std::move(c);
-      return ReadFileResult::READ_OK;
+      return true;
     }
   }
 
-  return ReadFileResult::INVALID_CONDITION;
+  cmCMakePresetErrors::INVALID_CONDITION(value, state);
+  return false;
 }
 
-ReadFileResult SubConditionHelper(
-  std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
-  const Json::Value* value)
+bool SubConditionHelper(std::unique_ptr<cmCMakePresetsGraph::Condition>& out,
+                        const Json::Value* value, cmJSONState* state)
 {
   std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
-  auto result = ConditionHelper(ptr, value);
+  auto result = ConditionHelper(ptr, value, state);
   if (ptr && ptr->IsNull()) {
-    return ReadFileResult::INVALID_CONDITION;
+    cmCMakePresetErrors::INVALID_CONDITION(value, state);
+    return false;
   }
   out = std::move(ptr);
   return result;
 }
 
-ReadFileResult EnvironmentHelper(cm::optional<std::string>& out,
-                                 const Json::Value* value)
+bool EnvironmentHelper(cm::optional<std::string>& out,
+                       const Json::Value* value, cmJSONState* state)
 {
   if (!value || value->isNull()) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
   if (value->isString()) {
     out = value->asString();
-    return ReadFileResult::READ_OK;
+    return true;
   }
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
-auto const VersionIntHelper = JSONHelperBuilder::Int(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
+auto const VersionIntHelper =
+  JSONHelperBuilder::Int(cmCMakePresetErrors::INVALID_VERSION);
 
 auto const VersionHelper = JSONHelperBuilder::Required<int>(
-  ReadFileResult::NO_VERSION, VersionIntHelper);
+  cmCMakePresetErrors::NO_VERSION, VersionIntHelper);
 
 auto const RootVersionHelper =
-  JSONHelperBuilder::Object<int>(ReadFileResult::READ_OK,
-                                 ReadFileResult::INVALID_ROOT)
+  JSONHelperBuilder::Object<int>(cmCMakePresetErrors::INVALID_ROOT_OBJECT)
     .Bind("version"_s, VersionHelper, false);
 
-auto const CMakeVersionUIntHelper = JSONHelperBuilder::UInt(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION);
+auto const CMakeVersionUIntHelper =
+  JSONHelperBuilder::UInt(cmCMakePresetErrors::INVALID_VERSION);
 
 auto const CMakeVersionHelper =
-  JSONHelperBuilder::Object<CMakeVersion>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_CMAKE_VERSION, false)
+  JSONHelperBuilder::Object<CMakeVersion>(JsonErrors::INVALID_NAMED_OBJECT_KEY,
+                                          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 = JSONHelperBuilder::String(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE);
+auto const IncludeHelper =
+  JSONHelperBuilder::String(cmCMakePresetErrors::INVALID_INCLUDE);
 
 auto const IncludeVectorHelper = JSONHelperBuilder::Vector<std::string>(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_INCLUDE, IncludeHelper);
+  cmCMakePresetErrors::INVALID_INCLUDE, IncludeHelper);
 
 auto const RootPresetsHelper =
-  JSONHelperBuilder::Object<RootPresets>(ReadFileResult::READ_OK,
-                                         ReadFileResult::INVALID_ROOT, false)
+  JSONHelperBuilder::Object<RootPresets>(
+    cmCMakePresetErrors::INVALID_ROOT_OBJECT, false)
     .Bind<int>("version"_s, nullptr, VersionHelper)
     .Bind("configurePresets"_s, &RootPresets::ConfigurePresets,
           cmCMakePresetsGraphInternal::ConfigurePresetsHelper, false)
@@ -292,136 +288,137 @@ auto const RootPresetsHelper =
     .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);
+    .Bind<std::nullptr_t>("vendor"_s, nullptr,
+                          cmCMakePresetsGraphInternal::VendorHelper(
+                            cmCMakePresetErrors::INVALID_ROOT),
+                          false);
 }
 
 namespace cmCMakePresetsGraphInternal {
-cmCMakePresetsGraph::ReadFileResult PresetStringHelper(
-  std::string& out, const Json::Value* value)
+bool PresetStringHelper(std::string& out, const Json::Value* value,
+                        cmJSONState* state)
 {
-  static auto const helper = JSONHelperBuilder::String(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
+  static auto const helper = JSONHelperBuilder::String();
+  return helper(out, value, state);
+}
 
-  return helper(out, value);
+bool PresetNameHelper(std::string& out, const Json::Value* value,
+                      cmJSONState* state)
+{
+  if (!value || !value->isString() || value->asString().empty()) {
+    cmCMakePresetErrors::INVALID_PRESET_NAME(value, state);
+    return false;
+  }
+  out = value->asString();
+  return true;
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorStringHelper(
-  std::vector<std::string>& out, const Json::Value* value)
+bool PresetVectorStringHelper(std::vector<std::string>& out,
+                              const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<std::string>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
+    cmCMakePresetErrors::INVALID_PRESET,
     cmCMakePresetsGraphInternal::PresetStringHelper);
-
-  return helper(out, value);
+  return helper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetBoolHelper(bool& out,
-                                                     const Json::Value* value)
+bool PresetBoolHelper(bool& out, const Json::Value* value, cmJSONState* state)
 {
-  static auto const helper = JSONHelperBuilder::Bool(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
-
-  return helper(out, value);
+  static auto const helper = JSONHelperBuilder::Bool();
+  return helper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetOptionalBoolHelper(
-  cm::optional<bool>& out, const Json::Value* value)
+bool PresetOptionalBoolHelper(cm::optional<bool>& out,
+                              const Json::Value* value, cmJSONState* state)
 {
-  static auto const helper = JSONHelperBuilder::Optional<bool>(
-    ReadFileResult::READ_OK, PresetBoolHelper);
-
-  return helper(out, value);
+  static auto const helper =
+    JSONHelperBuilder::Optional<bool>(PresetBoolHelper);
+  return helper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetIntHelper(int& out,
-                                                    const Json::Value* value)
+bool PresetIntHelper(int& out, const Json::Value* value, cmJSONState* state)
 {
-  static auto const helper = JSONHelperBuilder::Int(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET);
-
-  return helper(out, value);
+  static auto const helper = JSONHelperBuilder::Int();
+  return helper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetOptionalIntHelper(
-  cm::optional<int>& out, const Json::Value* value)
+bool PresetOptionalIntHelper(cm::optional<int>& out, const Json::Value* value,
+                             cmJSONState* state)
 {
-  static auto const helper =
-    JSONHelperBuilder::Optional<int>(ReadFileResult::READ_OK, PresetIntHelper);
-
-  return helper(out, value);
+  static auto const helper = JSONHelperBuilder::Optional<int>(PresetIntHelper);
+  return helper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult PresetVectorIntHelper(
-  std::vector<int>& out, const Json::Value* value)
+bool PresetVectorIntHelper(std::vector<int>& out, const Json::Value* value,
+                           cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<int>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, PresetIntHelper);
-
-  return helper(out, value);
+    cmCMakePresetErrors::INVALID_PRESET, PresetIntHelper);
+  return helper(out, value, state);
 }
 
-cmJSONHelper<std::nullptr_t, ReadFileResult> VendorHelper(ReadFileResult error)
+cmJSONHelper<std::nullptr_t> VendorHelper(const ErrorGenerator& error)
 {
-  return [error](std::nullptr_t& /*out*/,
-                 const Json::Value* value) -> ReadFileResult {
+  return [error](std::nullptr_t& /*out*/, const Json::Value* value,
+                 cmJSONState* state) -> bool {
     if (!value) {
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (!value->isObject()) {
-      return error;
+      error(value, state);
+      return false;
     }
 
-    return ReadFileResult::READ_OK;
+    return true;
   };
 }
 
-ReadFileResult PresetConditionHelper(
+bool PresetConditionHelper(
   std::shared_ptr<cmCMakePresetsGraph::Condition>& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   std::unique_ptr<cmCMakePresetsGraph::Condition> ptr;
-  auto result = ConditionHelper(ptr, value);
+  auto result = ConditionHelper(ptr, value, state);
   out = std::move(ptr);
   return result;
 }
 
-ReadFileResult PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
-                                                 const Json::Value* value)
+bool PresetVectorOneOrMoreStringHelper(std::vector<std::string>& out,
+                                       const Json::Value* value,
+                                       cmJSONState* state)
 {
   out.clear();
   if (!value) {
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isString()) {
     out.push_back(value->asString());
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return PresetVectorStringHelper(out, value);
+  return PresetVectorStringHelper(out, value, state);
 }
 
-cmCMakePresetsGraph::ReadFileResult EnvironmentMapHelper(
+bool EnvironmentMapHelper(
   std::map<std::string, cm::optional<std::string>>& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Map<cm::optional<std::string>>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
-    EnvironmentHelper);
+    cmCMakePresetErrors::INVALID_PRESET, EnvironmentHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }
 
-cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
-  const std::string& filename, RootType rootType, ReadReason readReason,
-  std::vector<File*>& inProgressFiles, File*& file, std::string& errMsg)
+bool cmCMakePresetsGraph::ReadJSONFile(const std::string& filename,
+                                       RootType rootType,
+                                       ReadReason readReason,
+                                       std::vector<File*>& inProgressFiles,
+                                       File*& file, std::string& errMsg)
 {
-  ReadFileResult result;
+  bool result;
 
   for (auto const& f : this->Files) {
     if (cmSystemTools::SameFile(filename, f->Filename)) {
@@ -429,61 +426,67 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
       auto fileIt =
         std::find(inProgressFiles.begin(), inProgressFiles.end(), file);
       if (fileIt != inProgressFiles.end()) {
-        return cmCMakePresetsGraph::ReadFileResult::CYCLIC_INCLUDE;
+        cmCMakePresetErrors::CYCLIC_INCLUDE(filename, &this->parseState);
+        return false;
       }
 
-      return cmCMakePresetsGraph::ReadFileResult::READ_OK;
+      return true;
     }
   }
 
-  cmsys::ifstream fin(filename.c_str());
-  if (!fin) {
-    errMsg = cmStrCat(filename, ": Failed to read file\n", errMsg);
-    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, &errMsg)) {
-    errMsg = cmStrCat(filename, ":\n", errMsg);
-    return ReadFileResult::JSON_PARSE_ERROR;
+  this->parseState = cmJSONState(filename, &root);
+  if (!this->parseState.errors.empty()) {
+    return false;
   }
 
   int v = 0;
-  if ((result = RootVersionHelper(v, &root)) != ReadFileResult::READ_OK) {
+  if ((result = RootVersionHelper(v, &root, &parseState)) != true) {
     return result;
   }
   if (v < MIN_VERSION || v > MAX_VERSION) {
-    return ReadFileResult::UNRECOGNIZED_VERSION;
+    cmCMakePresetErrors::UNRECOGNIZED_VERSION(&root["version"],
+                                              &this->parseState);
+    return false;
   }
 
   // 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;
+  if (v < 2) {
+    if (root.isMember("buildPresets")) {
+      cmCMakePresetErrors::BUILD_TEST_PRESETS_UNSUPPORTED(
+        &root["buildPresets"], &this->parseState);
+      return false;
+    }
+    if (root.isMember("testPresets")) {
+      cmCMakePresetErrors::BUILD_TEST_PRESETS_UNSUPPORTED(&root["testPresets"],
+                                                          &this->parseState);
+      return false;
+    }
   }
 
   // Support for package presets added in version 6.
   if (v < 6 && root.isMember("packagePresets")) {
-    return ReadFileResult::PACKAGE_PRESETS_UNSUPPORTED;
+    cmCMakePresetErrors::PACKAGE_PRESETS_UNSUPPORTED(&root["packagePresets"],
+                                                     &this->parseState);
+    return false;
   }
 
   // Support for workflow presets added in version 6.
   if (v < 6 && root.isMember("workflowPresets")) {
-    return ReadFileResult::WORKFLOW_PRESETS_UNSUPPORTED;
+    cmCMakePresetErrors::WORKFLOW_PRESETS_UNSUPPORTED(&root["workflowPresets"],
+                                                      &this->parseState);
+    return false;
   }
 
   // Support for include added in version 4.
   if (v < 4 && root.isMember("include")) {
-    return ReadFileResult::INCLUDE_UNSUPPORTED;
+    cmCMakePresetErrors::INCLUDE_UNSUPPORTED(&root["include"],
+                                             &this->parseState);
+    return false;
   }
 
   RootPresets presets;
-  if ((result = RootPresetsHelper(presets, &root)) !=
-      ReadFileResult::READ_OK) {
+  if ((result = RootPresetsHelper(presets, &root, &parseState)) != true) {
     return result;
   }
 
@@ -491,12 +494,25 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
   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;
+  if (required.Major > currentMajor) {
+    ErrorGenerator error = cmCMakePresetErrors::UNRECOGNIZED_CMAKE_VERSION(
+      "major", currentMajor, required.Major);
+    error(&root["cmakeMinimumRequired"]["major"], &this->parseState);
+    return false;
+  }
+  if (required.Major == currentMajor) {
+    if (required.Minor > currentMinor) {
+      ErrorGenerator error = cmCMakePresetErrors::UNRECOGNIZED_CMAKE_VERSION(
+        "minor", currentMinor, required.Minor);
+      error(&root["cmakeMinimumRequired"]["minor"], &this->parseState);
+      return false;
+    }
+    if (required.Minor == currentMinor && required.Patch > currentPatch) {
+      ErrorGenerator error = cmCMakePresetErrors::UNRECOGNIZED_CMAKE_VERSION(
+        "patch", currentPatch, required.Patch);
+      error(&root["cmakeMinimumRequired"]["patch"], &this->parseState);
+      return false;
+    }
   }
 
   auto filePtr = cm::make_unique<File>();
@@ -510,31 +526,35 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
   for (auto& preset : presets.ConfigurePresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      errMsg += R"(\n\t)";
-      errMsg += filename;
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<ConfigurePreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->ConfigurePresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for installDir presets added in version 3.
     if (v < 3 && !preset.InstallDir.empty()) {
-      return ReadFileResult::INSTALL_PREFIX_UNSUPPORTED;
+      cmCMakePresetErrors::INSTALL_PREFIX_UNSUPPORTED(&root["installDir"],
+                                                      &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3.
     if (v < 3 && preset.ConditionEvaluator) {
-      return ReadFileResult::CONDITION_UNSUPPORTED;
+      cmCMakePresetErrors::CONDITION_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     // Support for toolchainFile presets added in version 3.
     if (v < 3 && !preset.ToolchainFile.empty()) {
-      return ReadFileResult::TOOLCHAIN_FILE_UNSUPPORTED;
+      cmCMakePresetErrors::TOOLCHAIN_FILE_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     this->ConfigurePresetOrder.push_back(preset.Name);
@@ -543,21 +563,22 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
   for (auto& preset : presets.BuildPresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      errMsg += R"(\n\t)";
-      errMsg += filename;
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<BuildPreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->BuildPresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3.
     if (v < 3 && preset.ConditionEvaluator) {
-      return ReadFileResult::CONDITION_UNSUPPORTED;
+      cmCMakePresetErrors::CONDITION_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     this->BuildPresetOrder.push_back(preset.Name);
@@ -566,29 +587,35 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
   for (auto& preset : presets.TestPresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<TestPreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->TestPresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3.
     if (v < 3 && preset.ConditionEvaluator) {
-      return ReadFileResult::CONDITION_UNSUPPORTED;
+      cmCMakePresetErrors::CONDITION_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     // Support for TestOutputTruncation added in version 5.
     if (v < 5 && preset.Output && preset.Output->TestOutputTruncation) {
-      return ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED;
+      cmCMakePresetErrors::TEST_OUTPUT_TRUNCATION_UNSUPPORTED(
+        &this->parseState);
+      return false;
     }
 
     // Support for outputJUnitFile added in version 6.
     if (v < 6 && preset.Output && !preset.Output->OutputJUnitFile.empty()) {
-      return ReadFileResult::CTEST_JUNIT_UNSUPPORTED;
+      cmCMakePresetErrors::CTEST_JUNIT_UNSUPPORTED(&this->parseState);
+      return false;
     }
 
     this->TestPresetOrder.push_back(preset.Name);
@@ -597,14 +624,16 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
   for (auto& preset : presets.PackagePresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<PackagePreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->PackagePresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3, but this requires version 5
@@ -616,14 +645,16 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
   for (auto& preset : presets.WorkflowPresets) {
     preset.OriginFile = file;
     if (preset.Name.empty()) {
-      return ReadFileResult::INVALID_PRESET;
+      // No error, already handled by PresetNameHelper
+      return false;
     }
 
     PresetPair<WorkflowPreset> presetPair;
     presetPair.Unexpanded = preset;
     presetPair.Expanded = cm::nullopt;
     if (!this->WorkflowPresets.emplace(preset.Name, presetPair).second) {
-      return ReadFileResult::DUPLICATE_PRESETS;
+      cmCMakePresetErrors::DUPLICATE_PRESETS(preset.Name, &this->parseState);
+      return false;
     }
 
     // Support for conditions added in version 3, but this requires version 6
@@ -632,21 +663,21 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
     this->WorkflowPresetOrder.push_back(preset.Name);
   }
 
-  auto const includeFile = [this, &inProgressFiles, file](
-                             const std::string& include, RootType rootType2,
-                             ReadReason readReason2,
-                             std::string& FailureMessage) -> ReadFileResult {
-    ReadFileResult r;
+  auto const includeFile = [this, &inProgressFiles,
+                            file](const std::string& include,
+                                  RootType rootType2, ReadReason readReason2,
+                                  std::string& FailureMessage) -> bool {
+    bool r;
     File* includedFile;
-    if ((r = this->ReadJSONFile(include, rootType2, readReason2,
-                                inProgressFiles, includedFile,
-                                FailureMessage)) != ReadFileResult::READ_OK) {
+    if ((r =
+           this->ReadJSONFile(include, rootType2, readReason2, inProgressFiles,
+                              includedFile, FailureMessage)) != true) {
       return r;
     }
 
     file->ReachableFiles.insert(includedFile->ReachableFiles.begin(),
                                 includedFile->ReachableFiles.end());
-    return ReadFileResult::READ_OK;
+    return true;
   };
 
   for (auto include : presets.Include) {
@@ -656,7 +687,7 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
     }
 
     if ((result = includeFile(include, rootType, ReadReason::Included,
-                              errMsg)) != ReadFileResult::READ_OK) {
+                              errMsg)) != true) {
       return result;
     }
   }
@@ -665,13 +696,12 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
     auto cmakePresetsFilename = GetFilename(this->SourceDir);
     if (cmSystemTools::FileExists(cmakePresetsFilename)) {
       if ((result = includeFile(cmakePresetsFilename, RootType::Project,
-                                ReadReason::Root, errMsg)) !=
-          ReadFileResult::READ_OK) {
+                                ReadReason::Root, errMsg)) != true) {
         return result;
       }
     }
   }
 
   inProgressFiles.pop_back();
-  return ReadFileResult::READ_OK;
+  return true;
 }

+ 24 - 21
Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx

@@ -13,25 +13,27 @@
 #include <cm3p/json/value.h>
 
 #include "cmBuildOptions.h"
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
 
+class cmJSONState;
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
-using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
 
-ReadFileResult PackageResolveModeHelper(cm::optional<PackageResolveMode>& out,
-                                        const Json::Value* value)
+bool PackageResolveModeHelper(cm::optional<PackageResolveMode>& out,
+                              const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "on") {
@@ -41,23 +43,25 @@ ReadFileResult PackageResolveModeHelper(cm::optional<PackageResolveMode>& out,
   } else if (value->asString() == "only") {
     out = PackageResolveMode::OnlyResolve;
   } else {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
-  return ReadFileResult::READ_OK;
+  return true;
 }
 
-std::function<ReadFileResult(BuildPreset&, const Json::Value*)> const
-  ResolvePackageReferencesHelper =
-    [](BuildPreset& out, const Json::Value* value) -> ReadFileResult {
-  return PackageResolveModeHelper(out.ResolvePackageReferences, value);
+std::function<bool(BuildPreset&, const Json::Value*, cmJSONState*)> const
+  ResolvePackageReferencesHelper = [](BuildPreset& out,
+                                      const Json::Value* value,
+                                      cmJSONState* state) -> bool {
+  return PackageResolveModeHelper(out.ResolvePackageReferences, value, state);
 };
 
 auto const BuildPresetHelper =
-  JSONHelperBuilder::Object<BuildPreset>(ReadFileResult::READ_OK,
-                                         ReadFileResult::INVALID_PRESET, false)
+  JSONHelperBuilder::Object<BuildPreset>(
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &BuildPreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind("inherits"_s, &BuildPreset::Inherits,
           cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
           false)
@@ -65,7 +69,7 @@ auto const BuildPresetHelper =
           cmCMakePresetsGraphInternal::PresetBoolHelper, false)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &BuildPreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -97,13 +101,12 @@ auto const BuildPresetHelper =
 }
 
 namespace cmCMakePresetsGraphInternal {
-ReadFileResult BuildPresetsHelper(std::vector<BuildPreset>& out,
-                                  const Json::Value* value)
+bool BuildPresetsHelper(std::vector<BuildPreset>& out,
+                        const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<BuildPreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-    BuildPresetHelper);
+    cmCMakePresetErrors::INVALID_PRESETS, BuildPresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }

+ 49 - 44
Source/cmCMakePresetsGraphReadJSONConfigurePresets.cxx

@@ -12,72 +12,77 @@
 
 #include <cm3p/json/value.h>
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
+#include "cmJSONState.h"
 
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using CacheVariable = cmCMakePresetsGraph::CacheVariable;
 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
-using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
 
-ReadFileResult ArchToolsetStrategyHelper(
-  cm::optional<ArchToolsetStrategy>& out, const Json::Value* value)
+bool ArchToolsetStrategyHelper(cm::optional<ArchToolsetStrategy>& out,
+                               const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "set") {
     out = ArchToolsetStrategy::Set;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "external") {
     out = ArchToolsetStrategy::External;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
-std::function<ReadFileResult(ConfigurePreset&, const Json::Value*)>
+std::function<bool(ConfigurePreset&, const Json::Value*, cmJSONState*)>
 ArchToolsetHelper(
   std::string ConfigurePreset::*valueField,
   cm::optional<ArchToolsetStrategy> ConfigurePreset::*strategyField)
 {
   auto const objectHelper =
-    JSONHelperBuilder::Object<ConfigurePreset>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    JSONHelperBuilder::Object<ConfigurePreset>(JsonErrors::INVALID_OBJECT,
+                                               false)
       .Bind("value", valueField,
             cmCMakePresetsGraphInternal::PresetStringHelper, false)
       .Bind("strategy", strategyField, ArchToolsetStrategyHelper, false);
-  return [valueField, strategyField, objectHelper](
-           ConfigurePreset& out, const Json::Value* value) -> ReadFileResult {
+  return [valueField, strategyField,
+          objectHelper](ConfigurePreset& out, const Json::Value* value,
+                        cmJSONState* state) -> bool {
     if (!value) {
       (out.*valueField).clear();
       out.*strategyField = cm::nullopt;
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (value->isString()) {
       out.*valueField = value->asString();
       out.*strategyField = cm::nullopt;
-      return ReadFileResult::READ_OK;
+      return true;
     }
 
     if (value->isObject()) {
-      return objectHelper(out, value);
+      return objectHelper(out, value, state);
     }
 
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   };
 }
 
@@ -86,65 +91,66 @@ auto const ArchitectureHelper = ArchToolsetHelper(
 auto const ToolsetHelper = ArchToolsetHelper(
   &ConfigurePreset::Toolset, &ConfigurePreset::ToolsetStrategy);
 
-auto const VariableStringHelper = JSONHelperBuilder::String(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE);
+auto const VariableStringHelper = JSONHelperBuilder::String();
 
-ReadFileResult VariableValueHelper(std::string& out, const Json::Value* value)
+bool VariableValueHelper(std::string& out, const Json::Value* value,
+                         cmJSONState* state)
 {
   if (!value) {
     out.clear();
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isBool()) {
     out = value->asBool() ? "TRUE" : "FALSE";
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return VariableStringHelper(out, value);
+  return VariableStringHelper(out, value, state);
 }
 
 auto const VariableObjectHelper =
   JSONHelperBuilder::Object<CacheVariable>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE, false)
+    cmCMakePresetErrors::INVALID_VARIABLE_OBJECT, false)
     .Bind("type"_s, &CacheVariable::Type, VariableStringHelper, false)
     .Bind("value"_s, &CacheVariable::Value, VariableValueHelper);
 
-ReadFileResult VariableHelper(cm::optional<CacheVariable>& out,
-                              const Json::Value* value)
+bool VariableHelper(cm::optional<CacheVariable>& out, const Json::Value* value,
+                    cmJSONState* state)
 {
   if (value->isBool()) {
     out = CacheVariable{
       /*Type=*/"BOOL",
       /*Value=*/value->asBool() ? "TRUE" : "FALSE",
     };
-    return ReadFileResult::READ_OK;
+    return true;
   }
   if (value->isString()) {
     out = CacheVariable{
       /*Type=*/"",
       /*Value=*/value->asString(),
     };
-    return ReadFileResult::READ_OK;
+    return true;
   }
   if (value->isObject()) {
     out.emplace();
-    return VariableObjectHelper(*out, value);
+    return VariableObjectHelper(*out, value, state);
   }
   if (value->isNull()) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
-  return ReadFileResult::INVALID_VARIABLE;
+  cmCMakePresetErrors::INVALID_VARIABLE(value, state);
+  return false;
 }
 
 auto const VariablesHelper =
   JSONHelperBuilder::Map<cm::optional<CacheVariable>>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, VariableHelper);
+    cmCMakePresetErrors::INVALID_PRESET, VariableHelper);
 
 auto const PresetWarningsHelper =
   JSONHelperBuilder::Object<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    JsonErrors::INVALID_NAMED_OBJECT_KEY, false)
     .Bind("dev"_s, &ConfigurePreset::WarnDev,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
     .Bind("deprecated"_s, &ConfigurePreset::WarnDeprecated,
@@ -158,7 +164,7 @@ auto const PresetWarningsHelper =
 
 auto const PresetErrorsHelper =
   JSONHelperBuilder::Object<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    JsonErrors::INVALID_NAMED_OBJECT_KEY, false)
     .Bind("dev"_s, &ConfigurePreset::ErrorDev,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
     .Bind("deprecated"_s, &ConfigurePreset::ErrorDeprecated,
@@ -166,7 +172,7 @@ auto const PresetErrorsHelper =
 
 auto const PresetDebugHelper =
   JSONHelperBuilder::Object<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    JsonErrors::INVALID_NAMED_OBJECT_KEY, false)
     .Bind("output"_s, &ConfigurePreset::DebugOutput,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
     .Bind("tryCompile"_s, &ConfigurePreset::DebugTryCompile,
@@ -176,9 +182,9 @@ auto const PresetDebugHelper =
 
 auto const ConfigurePresetHelper =
   JSONHelperBuilder::Object<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &ConfigurePreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind("inherits"_s, &ConfigurePreset::Inherits,
           cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
           false)
@@ -186,7 +192,7 @@ auto const ConfigurePresetHelper =
           cmCMakePresetsGraphInternal::PresetBoolHelper, false)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &ConfigurePreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -216,13 +222,12 @@ auto const ConfigurePresetHelper =
 }
 
 namespace cmCMakePresetsGraphInternal {
-ReadFileResult ConfigurePresetsHelper(std::vector<ConfigurePreset>& out,
-                                      const Json::Value* value)
+bool ConfigurePresetsHelper(std::vector<ConfigurePreset>& out,
+                            const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<ConfigurePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-    ConfigurePresetHelper);
+    cmCMakePresetErrors::INVALID_PRESETS, ConfigurePresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }

+ 17 - 20
Source/cmCMakePresetsGraphReadJSONPackagePresets.cxx

@@ -12,34 +12,34 @@
 
 #include <cm3p/json/value.h>
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
+class cmJSONState;
 
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using PackagePreset = cmCMakePresetsGraph::PackagePreset;
 
 auto const OutputHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Object<PackagePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+  cmJSONHelperBuilder::Object<PackagePreset>(
+    JsonErrors::INVALID_NAMED_OBJECT_KEY, false)
     .Bind("debug"_s, &PackagePreset::DebugOutput,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
     .Bind("verbose"_s, &PackagePreset::VerboseOutput,
           cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false);
 
-auto const VariableHelper = cmJSONHelperBuilder<ReadFileResult>::String(
-  ReadFileResult::READ_OK, ReadFileResult::INVALID_VARIABLE);
+auto const VariableHelper =
+  cmJSONHelperBuilder::String(cmCMakePresetErrors::INVALID_VARIABLE);
 
-auto const VariablesHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Map<std::string>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, VariableHelper);
+auto const VariablesHelper = cmJSONHelperBuilder::Map<std::string>(
+  cmCMakePresetErrors::INVALID_VARIABLE, VariableHelper);
 
 auto const PackagePresetHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Object<PackagePreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+  cmJSONHelperBuilder::Object<PackagePreset>(
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &PackagePreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind("inherits"_s, &PackagePreset::Inherits,
           cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
           false)
@@ -47,7 +47,7 @@ auto const PackagePresetHelper =
           cmCMakePresetsGraphInternal::PresetBoolHelper, false)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &PackagePreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -81,15 +81,12 @@ auto const PackagePresetHelper =
 }
 
 namespace cmCMakePresetsGraphInternal {
-cmCMakePresetsGraph::ReadFileResult PackagePresetsHelper(
-  std::vector<cmCMakePresetsGraph::PackagePreset>& out,
-  const Json::Value* value)
+bool PackagePresetsHelper(std::vector<cmCMakePresetsGraph::PackagePreset>& out,
+                          const Json::Value* value, cmJSONState* state)
 {
-  static auto const helper =
-    cmJSONHelperBuilder<ReadFileResult>::Vector<PackagePreset>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-      PackagePresetHelper);
+  static auto const helper = cmJSONHelperBuilder::Vector<PackagePreset>(
+    cmCMakePresetErrors::INVALID_PRESETS, PackagePresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }

+ 80 - 81
Source/cmCMakePresetsGraphReadJSONTestPresets.cxx

@@ -12,86 +12,93 @@
 
 #include <cm3p/json/value.h>
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
 
 #include "CTest/cmCTestTypes.h"
 
+class cmJSONState;
+
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using TestPreset = cmCMakePresetsGraph::TestPreset;
-using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
 
-ReadFileResult TestPresetOutputVerbosityHelper(
-  TestPreset::OutputOptions::VerbosityEnum& out, const Json::Value* value)
+bool TestPresetOutputVerbosityHelper(
+  TestPreset::OutputOptions::VerbosityEnum& out, const Json::Value* value,
+  cmJSONState* state)
 {
   if (!value) {
     out = TestPreset::OutputOptions::VerbosityEnum::Default;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "default") {
     out = TestPreset::OutputOptions::VerbosityEnum::Default;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "verbose") {
     out = TestPreset::OutputOptions::VerbosityEnum::Verbose;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "extra") {
     out = TestPreset::OutputOptions::VerbosityEnum::Extra;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalOutputVerbosityHelper =
   JSONHelperBuilder::Optional<TestPreset::OutputOptions::VerbosityEnum>(
-    ReadFileResult::READ_OK, TestPresetOutputVerbosityHelper);
+    TestPresetOutputVerbosityHelper);
 
-ReadFileResult TestPresetOutputTruncationHelper(
-  cm::optional<cmCTestTypes::TruncationMode>& out, const Json::Value* value)
+bool TestPresetOutputTruncationHelper(
+  cm::optional<cmCTestTypes::TruncationMode>& out, const Json::Value* value,
+  cmJSONState* state)
 {
   if (!value) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "tail") {
     out = cmCTestTypes::TruncationMode::Tail;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "middle") {
     out = cmCTestTypes::TruncationMode::Middle;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "head") {
     out = cmCTestTypes::TruncationMode::Head;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalOutputHelper =
   JSONHelperBuilder::Optional<TestPreset::OutputOptions>(
-    ReadFileResult::READ_OK,
     JSONHelperBuilder::Object<TestPreset::OutputOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+      JsonErrors::INVALID_OBJECT, false)
       .Bind("shortProgress"_s, &TestPreset::OutputOptions::ShortProgress,
             cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
       .Bind("verbosity"_s, &TestPreset::OutputOptions::Verbosity,
@@ -125,9 +132,7 @@ auto const TestPresetOptionalOutputHelper =
 
 auto const TestPresetOptionalFilterIncludeIndexObjectHelper =
   JSONHelperBuilder::Optional<TestPreset::IncludeOptions::IndexOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::IncludeOptions::IndexOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::IncludeOptions::IndexOptions>()
       .Bind("start"_s, &TestPreset::IncludeOptions::IndexOptions::Start,
             cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
       .Bind("end"_s, &TestPreset::IncludeOptions::IndexOptions::End,
@@ -138,33 +143,31 @@ auto const TestPresetOptionalFilterIncludeIndexObjectHelper =
             &TestPreset::IncludeOptions::IndexOptions::SpecificTests,
             cmCMakePresetsGraphInternal::PresetVectorIntHelper, false));
 
-ReadFileResult TestPresetOptionalFilterIncludeIndexHelper(
+bool TestPresetOptionalFilterIncludeIndexHelper(
   cm::optional<TestPreset::IncludeOptions::IndexOptions>& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out = cm::nullopt;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isString()) {
     out.emplace();
     out->IndexFile = value->asString();
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->isObject()) {
-    return TestPresetOptionalFilterIncludeIndexObjectHelper(out, value);
+    return TestPresetOptionalFilterIncludeIndexObjectHelper(out, value, state);
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  return false;
 }
 
 auto const TestPresetOptionalFilterIncludeHelper =
   JSONHelperBuilder::Optional<TestPreset::IncludeOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::IncludeOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::IncludeOptions>()
       .Bind("name"_s, &TestPreset::IncludeOptions::Name,
             cmCMakePresetsGraphInternal::PresetStringHelper, false)
       .Bind("label"_s, &TestPreset::IncludeOptions::Label,
@@ -176,9 +179,7 @@ auto const TestPresetOptionalFilterIncludeHelper =
 
 auto const TestPresetOptionalFilterExcludeFixturesHelper =
   JSONHelperBuilder::Optional<TestPreset::ExcludeOptions::FixturesOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::ExcludeOptions::FixturesOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::ExcludeOptions::FixturesOptions>()
       .Bind("any"_s, &TestPreset::ExcludeOptions::FixturesOptions::Any,
             cmCMakePresetsGraphInternal::PresetStringHelper, false)
       .Bind("setup"_s, &TestPreset::ExcludeOptions::FixturesOptions::Setup,
@@ -188,9 +189,7 @@ auto const TestPresetOptionalFilterExcludeFixturesHelper =
 
 auto const TestPresetOptionalFilterExcludeHelper =
   JSONHelperBuilder::Optional<TestPreset::ExcludeOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::ExcludeOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::ExcludeOptions>()
       .Bind("name"_s, &TestPreset::ExcludeOptions::Name,
             cmCMakePresetsGraphInternal::PresetStringHelper, false)
       .Bind("label"_s, &TestPreset::ExcludeOptions::Label,
@@ -198,110 +197,113 @@ auto const TestPresetOptionalFilterExcludeHelper =
       .Bind("fixtures"_s, &TestPreset::ExcludeOptions::Fixtures,
             TestPresetOptionalFilterExcludeFixturesHelper, false));
 
-ReadFileResult TestPresetExecutionShowOnlyHelper(
-  TestPreset::ExecutionOptions::ShowOnlyEnum& out, const Json::Value* value)
+bool TestPresetExecutionShowOnlyHelper(
+  TestPreset::ExecutionOptions::ShowOnlyEnum& out, const Json::Value* value,
+  cmJSONState* state)
 {
   if (!value || !value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "human") {
     out = TestPreset::ExecutionOptions::ShowOnlyEnum::Human;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "json-v1") {
     out = TestPreset::ExecutionOptions::ShowOnlyEnum::JsonV1;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalExecutionShowOnlyHelper =
   JSONHelperBuilder::Optional<TestPreset::ExecutionOptions::ShowOnlyEnum>(
-    ReadFileResult::READ_OK, TestPresetExecutionShowOnlyHelper);
+    TestPresetExecutionShowOnlyHelper);
 
-ReadFileResult TestPresetExecutionModeHelper(
+bool TestPresetExecutionModeHelper(
   TestPreset::ExecutionOptions::RepeatOptions::ModeEnum& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "until-fail") {
     out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilFail;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "until-pass") {
     out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilPass;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "after-timeout") {
     out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::AfterTimeout;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalExecutionRepeatHelper =
   JSONHelperBuilder::Optional<TestPreset::ExecutionOptions::RepeatOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::ExecutionOptions::RepeatOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::ExecutionOptions::RepeatOptions>()
       .Bind("mode"_s, &TestPreset::ExecutionOptions::RepeatOptions::Mode,
             TestPresetExecutionModeHelper, true)
       .Bind("count"_s, &TestPreset::ExecutionOptions::RepeatOptions::Count,
             cmCMakePresetsGraphInternal::PresetIntHelper, true));
 
-ReadFileResult TestPresetExecutionNoTestsActionHelper(
+bool TestPresetExecutionNoTestsActionHelper(
   TestPreset::ExecutionOptions::NoTestsActionEnum& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
     out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (value->asString() == "default") {
     out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "error") {
     out = TestPreset::ExecutionOptions::NoTestsActionEnum::Error;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "ignore") {
     out = TestPreset::ExecutionOptions::NoTestsActionEnum::Ignore;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const TestPresetOptionalExecutionNoTestsActionHelper =
   JSONHelperBuilder::Optional<TestPreset::ExecutionOptions::NoTestsActionEnum>(
-    ReadFileResult::READ_OK, TestPresetExecutionNoTestsActionHelper);
+    TestPresetExecutionNoTestsActionHelper);
 
 auto const TestPresetExecutionHelper =
   JSONHelperBuilder::Optional<TestPreset::ExecutionOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::ExecutionOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::ExecutionOptions>()
       .Bind("stopOnFailure"_s, &TestPreset::ExecutionOptions::StopOnFailure,
             cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
       .Bind("enableFailover"_s, &TestPreset::ExecutionOptions::EnableFailover,
@@ -329,19 +331,17 @@ auto const TestPresetExecutionHelper =
 
 auto const TestPresetFilterHelper =
   JSONHelperBuilder::Optional<TestPreset::FilterOptions>(
-    ReadFileResult::READ_OK,
-    JSONHelperBuilder::Object<TestPreset::FilterOptions>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET)
+    JSONHelperBuilder::Object<TestPreset::FilterOptions>()
       .Bind("include"_s, &TestPreset::FilterOptions::Include,
             TestPresetOptionalFilterIncludeHelper, false)
       .Bind("exclude"_s, &TestPreset::FilterOptions::Exclude,
             TestPresetOptionalFilterExcludeHelper, false));
 
 auto const TestPresetHelper =
-  JSONHelperBuilder::Object<TestPreset>(ReadFileResult::READ_OK,
-                                        ReadFileResult::INVALID_PRESET, false)
+  JSONHelperBuilder::Object<TestPreset>(
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &TestPreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind("inherits"_s, &TestPreset::Inherits,
           cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
           false)
@@ -349,7 +349,7 @@ auto const TestPresetHelper =
           cmCMakePresetsGraphInternal::PresetBoolHelper, false)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &TestPreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -377,13 +377,12 @@ auto const TestPresetHelper =
 }
 
 namespace cmCMakePresetsGraphInternal {
-cmCMakePresetsGraph::ReadFileResult TestPresetsHelper(
-  std::vector<cmCMakePresetsGraph::TestPreset>& out, const Json::Value* value)
+bool TestPresetsHelper(std::vector<cmCMakePresetsGraph::TestPreset>& out,
+                       const Json::Value* value, cmJSONState* state)
 {
   static auto const helper = JSONHelperBuilder::Vector<TestPreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-    TestPresetHelper);
+    cmCMakePresetErrors::INVALID_PRESETS, TestPresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }

+ 27 - 26
Source/cmCMakePresetsGraphReadJSONWorkflowPresets.cxx

@@ -9,69 +9,72 @@
 
 #include <cm3p/json/value.h>
 
+#include "cmCMakePresetErrors.h"
 #include "cmCMakePresetsGraph.h"
 #include "cmCMakePresetsGraphInternal.h"
 #include "cmJSONHelpers.h"
 
+class cmJSONState;
+
 namespace {
-using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
 using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
 
-ReadFileResult WorkflowStepTypeHelper(WorkflowPreset::WorkflowStep::Type& out,
-                                      const Json::Value* value)
+bool WorkflowStepTypeHelper(WorkflowPreset::WorkflowStep::Type& out,
+                            const Json::Value* value, cmJSONState* state)
 {
   if (!value) {
-    return ReadFileResult::INVALID_PRESET;
+    cmCMakePresetErrors::INVALID_PRESET(value, state);
+    return false;
   }
 
   if (!value->isString()) {
-    return ReadFileResult::INVALID_PRESET;
+    return false;
   }
 
   if (value->asString() == "configure") {
     out = WorkflowPreset::WorkflowStep::Type::Configure;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "build") {
     out = WorkflowPreset::WorkflowStep::Type::Build;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "test") {
     out = WorkflowPreset::WorkflowStep::Type::Test;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
   if (value->asString() == "package") {
     out = WorkflowPreset::WorkflowStep::Type::Package;
-    return ReadFileResult::READ_OK;
+    return true;
   }
 
-  return ReadFileResult::INVALID_PRESET;
+  cmCMakePresetErrors::INVALID_PRESET(value, state);
+  return false;
 }
 
 auto const WorkflowStepHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Object<WorkflowPreset::WorkflowStep>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+  cmJSONHelperBuilder::Object<WorkflowPreset::WorkflowStep>(
+    JsonErrors::INVALID_OBJECT, false)
     .Bind("type"_s, &WorkflowPreset::WorkflowStep::PresetType,
           WorkflowStepTypeHelper)
     .Bind("name"_s, &WorkflowPreset::WorkflowStep::PresetName,
           cmCMakePresetsGraphInternal::PresetStringHelper);
 
 auto const WorkflowStepsHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Vector<WorkflowPreset::WorkflowStep>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET,
-    WorkflowStepHelper);
+  cmJSONHelperBuilder::Vector<WorkflowPreset::WorkflowStep>(
+    cmCMakePresetErrors::INVALID_PRESET, WorkflowStepHelper);
 
 auto const WorkflowPresetHelper =
-  cmJSONHelperBuilder<ReadFileResult>::Object<WorkflowPreset>(
-    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+  cmJSONHelperBuilder::Object<WorkflowPreset>(
+    cmCMakePresetErrors::INVALID_PRESET_OBJECT, false)
     .Bind("name"_s, &WorkflowPreset::Name,
-          cmCMakePresetsGraphInternal::PresetStringHelper)
+          cmCMakePresetsGraphInternal::PresetNameHelper)
     .Bind<std::nullptr_t>("vendor"_s, nullptr,
                           cmCMakePresetsGraphInternal::VendorHelper(
-                            ReadFileResult::INVALID_PRESET),
+                            cmCMakePresetErrors::INVALID_PRESET),
                           false)
     .Bind("displayName"_s, &WorkflowPreset::DisplayName,
           cmCMakePresetsGraphInternal::PresetStringHelper, false)
@@ -81,15 +84,13 @@ auto const WorkflowPresetHelper =
 }
 
 namespace cmCMakePresetsGraphInternal {
-cmCMakePresetsGraph::ReadFileResult WorkflowPresetsHelper(
+bool WorkflowPresetsHelper(
   std::vector<cmCMakePresetsGraph::WorkflowPreset>& out,
-  const Json::Value* value)
+  const Json::Value* value, cmJSONState* state)
 {
-  static auto const helper =
-    cmJSONHelperBuilder<ReadFileResult>::Vector<WorkflowPreset>(
-      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
-      WorkflowPresetHelper);
+  static auto const helper = cmJSONHelperBuilder::Vector<WorkflowPreset>(
+    cmCMakePresetErrors::INVALID_PRESETS, WorkflowPresetHelper);
 
-  return helper(out, value);
+  return helper(out, value, state);
 }
 }

+ 5 - 4
Source/cmCTest.cxx

@@ -54,6 +54,7 @@
 #include "cmDynamicLoader.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
+#include "cmJSONState.h"
 #include "cmMakefile.h"
 #include "cmProcessOutput.h"
 #include "cmState.h"
@@ -2336,10 +2337,10 @@ bool cmCTest::SetArgsFromPreset(const std::string& presetName,
 
   cmCMakePresetsGraph settingsFile;
   auto result = settingsFile.ReadProjectPresets(workingDirectory);
-  if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
-    cmSystemTools::Error(
-      cmStrCat("Could not read presets from ", workingDirectory, ": ",
-               cmCMakePresetsGraph::ResultToString(result)));
+  if (result != true) {
+    cmSystemTools::Error(cmStrCat("Could not read presets from ",
+                                  workingDirectory, ":",
+                                  settingsFile.parseState.GetErrorMessage()));
     return false;
   }
 

+ 258 - 94
Source/cmJSONHelpers.h

@@ -2,9 +2,12 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #pragma once
 
+#include "cmConfigure.h" // IWYU pragma: keep
+
 #include <algorithm>
 #include <cstddef>
 #include <functional>
+#include <iostream>
 #include <map>
 #include <string>
 #include <vector>
@@ -14,20 +17,129 @@
 
 #include <cm3p/json/value.h>
 
-template <typename T, typename E, typename... CallState>
+#include "cmJSONState.h"
+
+template <typename T>
 using cmJSONHelper =
-  std::function<E(T& out, const Json::Value* value, CallState&&... state)>;
+  std::function<bool(T& out, const Json::Value* value, cmJSONState* state)>;
+
+using ErrorGenerator = std::function<void(const Json::Value*, cmJSONState*)>;
+
+namespace JsonErrors {
+enum ObjectError
+{
+  RequiredMissing,
+  InvalidObject,
+  ExtraField,
+  MissingRequired
+};
+using ErrorGenerator = std::function<void(const Json::Value*, cmJSONState*)>;
+using ObjectErrorGenerator =
+  std::function<ErrorGenerator(ObjectError, const Json::Value::Members&)>;
+const auto EXPECTED_TYPE = [](const std::string& type) {
+  return [type](const Json::Value* value, cmJSONState* state) -> void {
+#if !defined(CMAKE_BOOTSTRAP)
+    if (state->key().empty()) {
+      state->AddErrorAtValue(cmStrCat("Expected ", type), value);
+      return;
+    }
+    std::string errMsg = cmStrCat("\"", state->key(), "\" expected ", type);
+    if (value && value->isConvertibleTo(Json::ValueType::stringValue)) {
+      errMsg = cmStrCat(errMsg, ", got: ", value->asString());
+    }
+    state->AddErrorAtValue(errMsg, value);
+#endif
+  };
+};
+const auto INVALID_STRING = [](const Json::Value* value,
+                               cmJSONState* state) -> void {
+  JsonErrors::EXPECTED_TYPE("a string")(value, state);
+};
+const auto INVALID_BOOL = [](const Json::Value* value,
+                             cmJSONState* state) -> void {
+  JsonErrors::EXPECTED_TYPE("a bool")(value, state);
+};
+const auto INVALID_INT = [](const Json::Value* value,
+                            cmJSONState* state) -> void {
+  JsonErrors::EXPECTED_TYPE("an integer")(value, state);
+};
+const auto INVALID_UINT = [](const Json::Value* value,
+                             cmJSONState* state) -> void {
+  JsonErrors::EXPECTED_TYPE("an unsigned integer")(value, state);
+};
+const auto INVALID_NAMED_OBJECT =
+  [](const std::function<std::string(const Json::Value*, cmJSONState*)>&
+       nameGenerator) -> ObjectErrorGenerator {
+  return [nameGenerator](
+           ObjectError errorType,
+           const Json::Value::Members& extraFields) -> ErrorGenerator {
+    return [nameGenerator, errorType, extraFields](
+             const Json::Value* value, cmJSONState* state) -> void {
+#if !defined(CMAKE_BOOTSTRAP)
+      std::string name = nameGenerator(value, state);
+      switch (errorType) {
+        case ObjectError::RequiredMissing:
+          state->AddErrorAtValue(cmStrCat("Invalid Required ", name), value);
+          break;
+        case ObjectError::InvalidObject:
+          state->AddErrorAtValue(cmStrCat("Invalid ", name), value);
+          break;
+        case ObjectError::ExtraField: {
+          for (auto const& member : extraFields) {
+            if (value) {
+              state->AddErrorAtValue(
+                cmStrCat("Invalid extra field \"", member, "\" in ", name),
+                &(*value)[member]);
+            } else {
+              state->AddError(
+                cmStrCat("Invalid extra field \"", member, "\" in ", name));
+            }
+          }
+        } break;
+        case ObjectError::MissingRequired:
+          state->AddErrorAtValue(cmStrCat("Missing required field \"",
+                                          state->key(), "\" in ", name),
+                                 value);
+          break;
+      }
+#endif
+    };
+  };
+};
+const auto INVALID_OBJECT =
+  [](ObjectError errorType,
+     const Json::Value::Members& extraFields) -> ErrorGenerator {
+  return INVALID_NAMED_OBJECT(
+    [](const Json::Value*, cmJSONState*) -> std::string { return "Object"; })(
+    errorType, extraFields);
+};
+const auto INVALID_NAMED_OBJECT_KEY =
+  [](ObjectError errorType,
+     const Json::Value::Members& extraFields) -> ErrorGenerator {
+  return INVALID_NAMED_OBJECT(
+    [](const Json::Value*, cmJSONState* state) -> std::string {
+      for (auto it = state->parseStack.rbegin();
+           it != state->parseStack.rend(); ++it) {
+        if (it->first.rfind("$vector_item_", 0) == 0) {
+          continue;
+        }
+        return cmStrCat("\"", it->first, "\"");
+      }
+      return "root";
+    })(errorType, extraFields);
+};
+}
 
-template <typename E, typename... CallState>
 struct cmJSONHelperBuilder
 {
+
   template <typename T>
   class Object
   {
   public:
-    Object(E&& success, E&& fail, bool allowExtra = true)
-      : Success(std::move(success))
-      , Fail(std::move(fail))
+    Object(JsonErrors::ObjectErrorGenerator error = JsonErrors::INVALID_OBJECT,
+           bool allowExtra = true)
+      : Error(std::move(error))
       , AllowExtra(allowExtra)
     {
     }
@@ -38,8 +150,8 @@ struct cmJSONHelperBuilder
     {
       return this->BindPrivate(
         name,
-        [func, member](T& out, const Json::Value* value, CallState&&... state)
-          -> E { return func(out.*member, value, std::forward(state)...); },
+        [func, member](T& out, const Json::Value* value, cmJSONState* state)
+          -> bool { return func(out.*member, value, state); },
         required);
     }
     template <typename M, typename F>
@@ -49,9 +161,9 @@ struct cmJSONHelperBuilder
       return this->BindPrivate(
         name,
         [func](T& /*out*/, const Json::Value* value,
-               CallState&&... state) -> E {
+               cmJSONState* state) -> bool {
           M dummy;
-          return func(dummy, value, std::forward(state)...);
+          return func(dummy, value, state);
         },
         required);
     }
@@ -61,46 +173,56 @@ struct cmJSONHelperBuilder
       return this->BindPrivate(name, MemberFunction(func), required);
     }
 
-    E operator()(T& out, const Json::Value* value, CallState&&... state) const
+    bool operator()(T& out, const Json::Value* value, cmJSONState* state) const
     {
+      Json::Value::Members extraFields;
+      bool success = true;
       if (!value && this->AnyRequired) {
-        return this->Fail;
+        Error(JsonErrors::ObjectError::RequiredMissing, extraFields)(value,
+                                                                     state);
+        return false;
       }
       if (value && !value->isObject()) {
-        return this->Fail;
+        Error(JsonErrors::ObjectError::InvalidObject, extraFields)(value,
+                                                                   state);
+        return false;
       }
-      Json::Value::Members extraFields;
       if (value) {
         extraFields = value->getMemberNames();
       }
 
       for (auto const& m : this->Members) {
         std::string name(m.Name.data(), m.Name.size());
+        state->push_stack(name, value);
         if (value && value->isMember(name)) {
-          E result = m.Function(out, &(*value)[name], std::forward(state)...);
-          if (result != this->Success) {
-            return result;
+          if (!m.Function(out, &(*value)[name], state)) {
+            success = false;
           }
           extraFields.erase(
             std::find(extraFields.begin(), extraFields.end(), name));
         } else if (!m.Required) {
-          E result = m.Function(out, nullptr, std::forward(state)...);
-          if (result != this->Success) {
-            return result;
+          if (!m.Function(out, nullptr, state)) {
+            success = false;
           }
         } else {
-          return this->Fail;
+          Error(JsonErrors::ObjectError::MissingRequired, extraFields)(value,
+                                                                       state);
+          success = false;
         }
+        state->pop_stack();
       }
 
-      return this->AllowExtra || extraFields.empty() ? this->Success
-                                                     : this->Fail;
+      if (!this->AllowExtra && !extraFields.empty()) {
+        Error(JsonErrors::ObjectError::ExtraField, extraFields)(value, state);
+        success = false;
+      }
+      return success;
     }
 
   private:
     // Not a true cmJSONHelper, it just happens to match the signature
-    using MemberFunction =
-      std::function<E(T& out, const Json::Value* value, CallState&&... state)>;
+    using MemberFunction = std::function<bool(T& out, const Json::Value* value,
+                                              cmJSONState* state)>;
     struct Member
     {
       cm::string_view Name;
@@ -109,8 +231,7 @@ struct cmJSONHelperBuilder
     };
     std::vector<Member> Members;
     bool AnyRequired = false;
-    E Success;
-    E Fail;
+    JsonErrors::ObjectErrorGenerator Error;
     bool AllowExtra;
 
     Object& BindPrivate(const cm::string_view& name, MemberFunction&& func,
@@ -127,175 +248,218 @@ struct cmJSONHelperBuilder
       return *this;
     }
   };
-  static cmJSONHelper<std::string, E, CallState...> String(
-    E success, E fail, const std::string& defval = "")
+
+  static cmJSONHelper<std::string> String(
+    const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_STRING,
+    const std::string& defval = "")
   {
-    return [success, fail, defval](std::string& out, const Json::Value* value,
-                                   CallState&&... /*state*/) -> E {
+    return [error, defval](std::string& out, const Json::Value* value,
+                           cmJSONState* state) -> bool {
       if (!value) {
         out = defval;
-        return success;
+        return true;
       }
       if (!value->isString()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out = value->asString();
-      return success;
+      return true;
     };
-  }
+  };
+
+  static cmJSONHelper<std::string> String(const std::string& defval)
+  {
+    return String(JsonErrors::INVALID_STRING, defval);
+  };
 
-  static cmJSONHelper<int, E, CallState...> Int(E success, E fail,
-                                                int defval = 0)
+  static cmJSONHelper<int> Int(
+    const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_INT,
+    int defval = 0)
   {
-    return [success, fail, defval](int& out, const Json::Value* value,
-                                   CallState&&... /*state*/) -> E {
+    return [error, defval](int& out, const Json::Value* value,
+                           cmJSONState* state) -> bool {
       if (!value) {
         out = defval;
-        return success;
+        return true;
       }
       if (!value->isInt()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out = value->asInt();
-      return success;
+      return true;
     };
   }
 
-  static cmJSONHelper<unsigned int, E, CallState...> UInt(
-    E success, E fail, unsigned int defval = 0)
+  static cmJSONHelper<int> Int(int defval)
+  {
+    return Int(JsonErrors::INVALID_INT, defval);
+  };
+
+  static cmJSONHelper<unsigned int> UInt(
+    const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_UINT,
+    unsigned int defval = 0)
   {
-    return [success, fail, defval](unsigned int& out, const Json::Value* value,
-                                   CallState&&... /*state*/) -> E {
+    return [error, defval](unsigned int& out, const Json::Value* value,
+                           cmJSONState* state) -> bool {
       if (!value) {
         out = defval;
-        return success;
+        return true;
       }
       if (!value->isUInt()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out = value->asUInt();
-      return success;
+      return true;
     };
   }
 
-  static cmJSONHelper<bool, E, CallState...> Bool(E success, E fail,
-                                                  bool defval = false)
+  static cmJSONHelper<unsigned int> UInt(unsigned int defval)
+  {
+    return UInt(JsonErrors::INVALID_UINT, defval);
+  }
+
+  static cmJSONHelper<bool> Bool(
+    const JsonErrors::ErrorGenerator& error = JsonErrors::INVALID_BOOL,
+    bool defval = false)
   {
-    return [success, fail, defval](bool& out, const Json::Value* value,
-                                   CallState&&... /*state*/) -> E {
+    return [error, defval](bool& out, const Json::Value* value,
+                           cmJSONState* state) -> bool {
       if (!value) {
         out = defval;
-        return success;
+        return true;
       }
       if (!value->isBool()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out = value->asBool();
-      return success;
+      return true;
     };
   }
 
+  static cmJSONHelper<bool> Bool(bool defval)
+  {
+    return Bool(JsonErrors::INVALID_BOOL, defval);
+  }
+
   template <typename T, typename F, typename Filter>
-  static cmJSONHelper<std::vector<T>, E, CallState...> VectorFilter(
-    E success, E fail, F func, Filter filter)
+  static cmJSONHelper<std::vector<T>> VectorFilter(
+    const JsonErrors::ErrorGenerator& error, F func, Filter filter)
   {
-    return [success, fail, func, filter](std::vector<T>& out,
-                                         const Json::Value* value,
-                                         CallState&&... state) -> E {
+    return [error, func, filter](std::vector<T>& out, const Json::Value* value,
+                                 cmJSONState* state) -> bool {
+      bool success = true;
       if (!value) {
         out.clear();
-        return success;
+        return true;
       }
       if (!value->isArray()) {
-        return fail;
+        error(value, state);
+        return false;
       }
       out.clear();
+      int index = 0;
       for (auto const& item : *value) {
+        state->push_stack(cmStrCat("$vector_item_", index++), &item);
         T t;
-        E result = func(t, &item, std::forward(state)...);
-        if (result != success) {
-          return result;
+        if (!func(t, &item, state)) {
+          success = false;
         }
         if (!filter(t)) {
+          state->pop_stack();
           continue;
         }
         out.push_back(std::move(t));
+        state->pop_stack();
       }
       return success;
     };
   }
 
   template <typename T, typename F>
-  static cmJSONHelper<std::vector<T>, E, CallState...> Vector(E success,
-                                                              E fail, F func)
+  static cmJSONHelper<std::vector<T>> Vector(JsonErrors::ErrorGenerator error,
+                                             F func)
   {
-    return VectorFilter<T, F>(success, fail, func,
+    return VectorFilter<T, F>(std::move(error), func,
                               [](const T&) { return true; });
   }
 
   template <typename T, typename F, typename Filter>
-  static cmJSONHelper<std::map<std::string, T>, E, CallState...> MapFilter(
-    E success, E fail, F func, Filter filter)
+  static cmJSONHelper<std::map<std::string, T>> MapFilter(
+    const JsonErrors::ErrorGenerator& error, F func, Filter filter)
   {
-    return [success, fail, func, filter](std::map<std::string, T>& out,
-                                         const Json::Value* value,
-                                         CallState&&... state) -> E {
+    return [error, func, filter](std::map<std::string, T>& out,
+                                 const Json::Value* value,
+                                 cmJSONState* state) -> bool {
+      bool success = true;
       if (!value) {
         out.clear();
-        return success;
+        return true;
       }
       if (!value->isObject()) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
       out.clear();
       for (auto const& key : value->getMemberNames()) {
+        state->push_stack(cmStrCat(key, ""), &(*value)[key]);
         if (!filter(key)) {
+          state->pop_stack();
           continue;
         }
         T t;
-        E result = func(t, &(*value)[key], std::forward(state)...);
-        if (result != success) {
-          return result;
+        if (!func(t, &(*value)[key], state)) {
+          success = false;
         }
         out[key] = std::move(t);
+        state->pop_stack();
       }
       return success;
     };
   }
 
   template <typename T, typename F>
-  static cmJSONHelper<std::map<std::string, T>, E, CallState...> Map(E success,
-                                                                     E fail,
-                                                                     F func)
+  static cmJSONHelper<std::map<std::string, T>> Map(
+    const JsonErrors::ErrorGenerator& error, F func)
   {
-    return MapFilter<T, F>(success, fail, func,
+    return MapFilter<T, F>(error, func,
                            [](const std::string&) { return true; });
   }
 
   template <typename T, typename F>
-  static cmJSONHelper<cm::optional<T>, E, CallState...> Optional(E success,
-                                                                 F func)
+  static cmJSONHelper<cm::optional<T>> Optional(F func)
   {
-    return [success, func](cm::optional<T>& out, const Json::Value* value,
-                           CallState&&... state) -> E {
+    return [func](cm::optional<T>& out, const Json::Value* value,
+                  cmJSONState* state) -> bool {
       if (!value) {
         out.reset();
-        return success;
+        return true;
       }
       out.emplace();
-      return func(*out, value, std::forward(state)...);
+      return func(*out, value, state);
     };
   }
 
   template <typename T, typename F>
-  static cmJSONHelper<T, E, CallState...> Required(E fail, F func)
+  static cmJSONHelper<T> Required(const JsonErrors::ErrorGenerator& error,
+                                  F func)
   {
-    return [fail, func](T& out, const Json::Value* value,
-                        CallState&&... state) -> E {
+    return [error, func](T& out, const Json::Value* value,
+                         cmJSONState* state) -> bool {
       if (!value) {
-        return fail;
+        error(value, state);
+        ;
+        return false;
       }
-      return func(out, value, std::forward(state)...);
+      return func(out, value, state);
     };
   }
 };

+ 163 - 0
Source/cmJSONState.cxx

@@ -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 "cmJSONState.h"
+
+#include <sstream>
+
+#include <cm/memory>
+
+#include <cm3p/json/reader.h>
+#include <cm3p/json/value.h>
+
+#include "cmsys/FStream.hxx"
+
+#include "cmStringAlgorithms.h"
+
+cmJSONState::cmJSONState(const std::string& filename, Json::Value* root)
+{
+  cmsys::ifstream fin(filename.c_str(), std::ios::in | std::ios::binary);
+  if (!fin) {
+    this->AddError(cmStrCat("File not found: ", filename));
+    return;
+  }
+  // If there's a BOM, toss it.
+  cmsys::FStream::ReadBOM(fin);
+
+  // Save the entire document.
+  std::streampos finBegin = fin.tellg();
+  this->doc = std::string(std::istreambuf_iterator<char>(fin),
+                          std::istreambuf_iterator<char>());
+  if (this->doc.empty()) {
+    this->AddError("A JSON document cannot be empty");
+    return;
+  }
+  fin.seekg(finBegin);
+
+  // Parse the document.
+  Json::CharReaderBuilder builder;
+  Json::CharReaderBuilder::strictMode(&builder.settings_);
+  std::string errMsg;
+  if (!Json::parseFromStream(builder, fin, root, &errMsg)) {
+    errMsg = cmStrCat("JSON Parse Error: ", filename, ":\n", errMsg);
+    this->AddError(errMsg);
+  }
+}
+
+void cmJSONState::AddError(std::string const& errMsg)
+{
+  this->errors.push_back(Error(errMsg));
+}
+
+void cmJSONState::AddErrorAtValue(std::string const& errMsg,
+                                  const Json::Value* value)
+{
+  if (value && !value->isNull()) {
+    this->AddErrorAtOffset(errMsg, value->getOffsetStart());
+  } else {
+    this->AddError(errMsg);
+  }
+}
+
+void cmJSONState::AddErrorAtOffset(std::string const& errMsg,
+                                   std::ptrdiff_t offset)
+{
+  if (doc.empty()) {
+    this->AddError(errMsg);
+  } else {
+    Location loc = LocateInDocument(offset);
+    this->errors.push_back(Error(loc, errMsg));
+  }
+}
+
+std::string cmJSONState::GetErrorMessage(bool showContext)
+{
+  std::string message;
+  for (auto const& error : this->errors) {
+    message = cmStrCat(message, error.GetErrorMessage(), "\n");
+    if (showContext) {
+      Location loc = error.GetLocation();
+      if (loc.column > 0) {
+        message = cmStrCat(message, GetJsonContext(loc), "\n");
+      }
+    }
+  }
+  message = cmStrCat("\n", message);
+  message.pop_back();
+  return message;
+}
+
+std::string cmJSONState::key()
+{
+  if (!this->parseStack.empty()) {
+    return this->parseStack.back().first;
+  }
+  return "";
+}
+
+std::string cmJSONState::key_after(std::string const& k)
+{
+  for (auto it = this->parseStack.begin(); it != this->parseStack.end();
+       ++it) {
+    if (it->first == k && (++it) != this->parseStack.end()) {
+      return it->first;
+    }
+  }
+  return "";
+}
+
+const Json::Value* cmJSONState::value_after(std::string const& k)
+{
+  for (auto it = this->parseStack.begin(); it != this->parseStack.end();
+       ++it) {
+    if (it->first == k && (++it) != this->parseStack.end()) {
+      return it->second;
+    }
+  }
+  return nullptr;
+}
+
+void cmJSONState::push_stack(std::string const& k, const Json::Value* value)
+{
+  this->parseStack.push_back(JsonPair(k, value));
+}
+
+void cmJSONState::pop_stack()
+{
+  this->parseStack.pop_back();
+}
+
+std::string cmJSONState::GetJsonContext(Location loc)
+{
+  std::string line;
+  std::stringstream sstream(doc);
+  for (int i = 0; i < loc.line; ++i) {
+    std::getline(sstream, line, '\n');
+  }
+  return cmStrCat(line, '\n', std::string(loc.column - 1, ' '), '^');
+}
+
+cmJSONState::Location cmJSONState::LocateInDocument(ptrdiff_t offset)
+{
+  int line = 1;
+  int col = 1;
+  const char* beginDoc = doc.data();
+  const char* last = beginDoc + offset;
+  for (; beginDoc != last; ++beginDoc) {
+    switch (*beginDoc) {
+      case '\r':
+        if (beginDoc + 1 != last && beginDoc[1] == '\n') {
+          continue; // consume CRLF as a single token.
+        }
+        CM_FALLTHROUGH; // CR without a following LF is same as LF
+      case '\n':
+        col = 1;
+        ++line;
+        break;
+      default:
+        ++col;
+        break;
+    }
+  }
+  return { line, col };
+}

+ 73 - 0
Source/cmJSONState.h

@@ -0,0 +1,73 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "cmJSONState.h"
+#include "cmStringAlgorithms.h"
+
+namespace Json {
+class Value;
+}
+
+class cmJSONState
+{
+  using Location = struct
+  {
+    int line;
+    int column;
+  };
+
+public:
+  using JsonPair = std::pair<const std::string, const Json::Value*>;
+  cmJSONState() = default;
+  cmJSONState(const std::string& filename, Json::Value* root);
+  void AddError(std::string const& errMsg);
+  void AddErrorAtValue(std::string const& errMsg, const Json::Value* value);
+  void AddErrorAtOffset(std::string const& errMsg, std::ptrdiff_t offset);
+  std::string GetErrorMessage(bool showContext = true);
+  std::string key();
+  std::string key_after(std::string const& key);
+  const Json::Value* value_after(std::string const& key);
+  void push_stack(std::string const& key, const Json::Value* value);
+  void pop_stack();
+
+  class Error
+  {
+  public:
+    Error(Location loc, std::string errMsg)
+      : location(loc)
+      , message(std::move(errMsg)){};
+    Error(std::string errMsg)
+      : location({ -1, -1 })
+      , message(std::move(errMsg)){};
+    std::string GetErrorMessage() const
+    {
+      std::string output = message;
+      if (location.line > 0) {
+        output = cmStrCat("Error: @", location.line, ",", location.column,
+                          ": ", output);
+      }
+      return output;
+    }
+    Location GetLocation() const { return location; }
+
+  private:
+    Location location;
+    std::string message;
+  };
+
+  std::vector<JsonPair> parseStack;
+  std::vector<Error> errors;
+  std::string doc;
+
+private:
+  std::string GetJsonContext(Location loc);
+  Location LocateInDocument(ptrdiff_t offset);
+};

+ 11 - 13
Source/cmake.cxx

@@ -52,6 +52,7 @@
 #if !defined(CMAKE_BOOTSTRAP)
 #  include "cmMakefileProfilingData.h"
 #endif
+#include "cmJSONState.h"
 #include "cmMessenger.h"
 #include "cmState.h"
 #include "cmStateDirectory.h"
@@ -1411,13 +1412,10 @@ void cmake::SetArgs(const std::vector<std::string>& args)
   if (listPresets != ListPresets::None || !presetName.empty()) {
     cmCMakePresetsGraph presetsGraph;
     auto result = presetsGraph.ReadProjectPresets(this->GetHomeDirectory());
-    if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
+    if (result != true) {
       std::string errorMsg =
-        cmStrCat("Could not read presets from ", this->GetHomeDirectory(),
-                 ": ", cmCMakePresetsGraph::ResultToString(result));
-      if (!presetsGraph.errors.empty()) {
-        errorMsg = cmStrCat(errorMsg, "\nErrors:\n", presetsGraph.errors);
-      }
+        cmStrCat("Could not read presets from ", this->GetHomeDirectory(), ":",
+                 presetsGraph.parseState.GetErrorMessage());
       cmSystemTools::Error(errorMsg);
       return;
     }
@@ -3426,10 +3424,10 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
 
     cmCMakePresetsGraph settingsFile;
     auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
-    if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
+    if (result != true) {
       cmSystemTools::Error(
-        cmStrCat("Could not read presets from ", this->GetHomeDirectory(),
-                 ": ", cmCMakePresetsGraph::ResultToString(result)));
+        cmStrCat("Could not read presets from ", this->GetHomeDirectory(), ":",
+                 settingsFile.parseState.GetErrorMessage()));
       return 1;
     }
 
@@ -3782,10 +3780,10 @@ int cmake::Workflow(const std::string& presetName,
 
   cmCMakePresetsGraph settingsFile;
   auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
-  if (result != cmCMakePresetsGraph::ReadFileResult::READ_OK) {
-    cmSystemTools::Error(
-      cmStrCat("Could not read presets from ", this->GetHomeDirectory(), ": ",
-               cmCMakePresetsGraph::ResultToString(result)));
+  if (result != true) {
+    cmSystemTools::Error(cmStrCat("Could not read presets from ",
+                                  this->GetHomeDirectory(), ":",
+                                  settingsFile.parseState.GetErrorMessage()));
     return 1;
   }
 

+ 10 - 6
Tests/CMakeLib/testCTestResourceAllocator.cxx

@@ -5,12 +5,16 @@
 
 #include "cmCTestResourceAllocator.h"
 #include "cmCTestResourceSpec.h"
-
-static const cmCTestResourceSpec spec{ { {
-  /* clang-format off */
-  { "gpus", { { "0", 4 }, { "1", 8 }, { "2", 0 }, { "3", 8 } } },
-  /* clang-format on */
-} } };
+#include "cmJSONState.h"
+
+static const cmCTestResourceSpec spec{
+  { {
+    /* clang-format off */
+  { "gpus", { { "0", 4 }, { "1", 8 }, { "2", 0 }, { "3", 8 } }, },
+    /* clang-format on */
+  } },
+  cmJSONState()
+};
 
 static bool testInitializeFromResourceSpec()
 {

+ 49 - 66
Tests/CMakeLib/testCTestResourceSpec.cxx

@@ -3,89 +3,72 @@
 #include <vector>
 
 #include "cmCTestResourceSpec.h"
+#include "cmJSONState.h"
 
 struct ExpectedSpec
 {
   std::string Path;
-  cmCTestResourceSpec::ReadFileResult ParseResult;
+  bool ParseResult;
   cmCTestResourceSpec Expected;
 };
 
 static const std::vector<ExpectedSpec> expectedResourceSpecs = {
   { "spec1.json",
-    cmCTestResourceSpec::ReadFileResult::READ_OK,
+    true,
     { { {
-      { "gpus",
-        {
-          { "2", 4 },
-          { "e", 1 },
-        } },
-      { "threads", {} },
-    } } } },
-  { "spec2.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec3.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    {} },
-  { "spec4.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    {} },
-  { "spec5.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    {} },
-  { "spec6.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_SOCKET_SPEC,
-    {} },
-  { "spec7.json",
-    cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE_TYPE,
-    {} },
-  { "spec8.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec9.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec10.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec11.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec12.json", cmCTestResourceSpec::ReadFileResult::INVALID_ROOT, {} },
-  { "spec13.json", cmCTestResourceSpec::ReadFileResult::JSON_PARSE_ERROR, {} },
-  { "spec14.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec15.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec16.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec17.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec18.json", cmCTestResourceSpec::ReadFileResult::INVALID_RESOURCE, {} },
-  { "spec19.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec20.json", cmCTestResourceSpec::ReadFileResult::READ_OK, {} },
-  { "spec21.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec22.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec23.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec24.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec25.json",
-    cmCTestResourceSpec::ReadFileResult::UNSUPPORTED_VERSION,
-    {} },
-  { "spec26.json",
-    cmCTestResourceSpec::ReadFileResult::UNSUPPORTED_VERSION,
-    {} },
-  { "spec27.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec28.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec29.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec30.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec31.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec32.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec33.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec34.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec35.json", cmCTestResourceSpec::ReadFileResult::INVALID_VERSION, {} },
-  { "spec36.json", cmCTestResourceSpec::ReadFileResult::NO_VERSION, {} },
-  { "noexist.json", cmCTestResourceSpec::ReadFileResult::FILE_NOT_FOUND, {} },
+        { "gpus",
+          {
+            { "2", 4 },
+            { "e", 1 },
+          } },
+        { "threads", {} },
+      } },
+      cmJSONState() } },
+  { "spec2.json", true, {} },
+  { "spec3.json", false, {} },
+  { "spec4.json", false, {} },
+  { "spec5.json", false, {} },
+  { "spec6.json", false, {} },
+  { "spec7.json", false, {} },
+  { "spec8.json", false, {} },
+  { "spec9.json", false, {} },
+  { "spec10.json", false, {} },
+  { "spec11.json", false, {} },
+  { "spec12.json", false, {} },
+  { "spec13.json", false, {} },
+  { "spec14.json", true, {} },
+  { "spec15.json", true, {} },
+  { "spec16.json", true, {} },
+  { "spec17.json", false, {} },
+  { "spec18.json", false, {} },
+  { "spec19.json", false, {} },
+  { "spec20.json", true, {} },
+  { "spec21.json", false, {} },
+  { "spec22.json", false, {} },
+  { "spec23.json", false, {} },
+  { "spec24.json", false, {} },
+  { "spec25.json", false, {} },
+  { "spec26.json", false, {} },
+  { "spec27.json", false, {} },
+  { "spec28.json", false, {} },
+  { "spec29.json", false, {} },
+  { "spec30.json", false, {} },
+  { "spec31.json", false, {} },
+  { "spec32.json", false, {} },
+  { "spec33.json", false, {} },
+  { "spec34.json", false, {} },
+  { "spec35.json", false, {} },
+  { "spec36.json", false, {} },
+  { "noexist.json", false, {} },
 };
 
-static bool testSpec(const std::string& path,
-                     cmCTestResourceSpec::ReadFileResult expectedResult,
+static bool testSpec(const std::string& path, bool expectedResult,
                      const cmCTestResourceSpec& expected)
 {
   cmCTestResourceSpec actual;
   auto result = actual.ReadFromJSONFile(path);
   if (result != expectedResult) {
-    std::cout << "ReadFromJSONFile(\"" << path << "\") returned \""
-              << cmCTestResourceSpec::ResultToString(result)
-              << "\", should be \""
-              << cmCTestResourceSpec::ResultToString(expectedResult) << "\""
-              << std::endl;
+    std::cout << "ReadFromJSONFile(\"" << path << "\") failed \"" << std::endl;
     return false;
   }
 

+ 108 - 95
Tests/CMakeLib/testJSONHelpers.cxx

@@ -10,6 +10,7 @@
 #include <cm3p/json/value.h>
 
 #include "cmJSONHelpers.h"
+#include "cmJSONState.h"
 
 #define ASSERT_TRUE(x)                                                        \
   do {                                                                        \
@@ -31,59 +32,65 @@ struct InheritedStruct : public ObjectStruct
   std::string Field3;
 };
 
-enum class ErrorCode
+namespace ErrorMessages {
+using ErrorGenerator =
+  std::function<void(const Json::Value*, cmJSONState* state)>;
+ErrorGenerator ErrorGeneratorBuilder(std::string errorMessage)
 {
-  Success,
-  InvalidInt,
-  InvalidBool,
-  InvalidString,
-  InvalidSubObject,
-  InvalidObject,
-  InvalidArray,
-  MissingRequired,
+  return [errorMessage](const Json::Value* value, cmJSONState* state) -> void {
+    state->AddErrorAtValue(errorMessage, value);
+  };
+};
+ErrorGenerator InvalidArray = ErrorGeneratorBuilder("Invalid Array");
+ErrorGenerator MissingRequired = ErrorGeneratorBuilder("Missing Required");
+ErrorGenerator InvalidMap = ErrorGeneratorBuilder("Invalid Map");
+ErrorGenerator InvalidObject(JsonErrors::ObjectError /*errorType*/,
+                             const Json::Value::Members& extraFields)
+{
+  return [extraFields](const Json::Value* value, cmJSONState* state) -> void {
+    state->AddErrorAtValue("Invalid Object", value);
+  };
+};
 };
 
-using JSONHelperBuilder = cmJSONHelperBuilder<ErrorCode>;
+using JSONHelperBuilder = cmJSONHelperBuilder;
 
-auto const IntHelper =
-  JSONHelperBuilder::Int(ErrorCode::Success, ErrorCode::InvalidInt, 1);
+auto const IntHelper = JSONHelperBuilder::Int(1);
 auto const RequiredIntHelper =
-  JSONHelperBuilder::Required<int>(ErrorCode::MissingRequired, IntHelper);
-auto const UIntHelper =
-  JSONHelperBuilder::UInt(ErrorCode::Success, ErrorCode::InvalidInt, 1);
-auto const BoolHelper =
-  JSONHelperBuilder::Bool(ErrorCode::Success, ErrorCode::InvalidBool, false);
-auto const StringHelper = JSONHelperBuilder::String(
-  ErrorCode::Success, ErrorCode::InvalidString, "default");
+  JSONHelperBuilder::Required<int>(ErrorMessages::MissingRequired, IntHelper);
+auto const UIntHelper = JSONHelperBuilder::UInt(1);
+auto const BoolHelper = JSONHelperBuilder::Bool(false);
+auto const StringHelper = JSONHelperBuilder::String("default");
 auto const RequiredStringHelper = JSONHelperBuilder::Required<std::string>(
-  ErrorCode::MissingRequired, StringHelper);
+  ErrorMessages::MissingRequired, StringHelper);
 auto const StringVectorHelper = JSONHelperBuilder::Vector<std::string>(
-  ErrorCode::Success, ErrorCode::InvalidArray, StringHelper);
+  ErrorMessages::InvalidArray, StringHelper);
 auto const StringVectorFilterHelper =
   JSONHelperBuilder::VectorFilter<std::string>(
-    ErrorCode::Success, ErrorCode::InvalidArray, StringHelper,
+    ErrorMessages::InvalidArray, StringHelper,
     [](const std::string& value) { return value != "ignore"; });
-auto const StringMapHelper = JSONHelperBuilder::Map<std::string>(
-  ErrorCode::Success, ErrorCode::InvalidObject, StringHelper);
+auto const StringMapHelper =
+  JSONHelperBuilder::Map<std::string>(ErrorMessages::InvalidMap, StringHelper);
 auto const StringMapFilterHelper = JSONHelperBuilder::MapFilter<std::string>(
-  ErrorCode::Success, ErrorCode::InvalidObject, StringHelper,
+  ErrorMessages::InvalidMap, StringHelper,
   [](const std::string& key) { return key != "ignore"; });
 auto const OptionalStringHelper =
-  JSONHelperBuilder::Optional<std::string>(ErrorCode::Success, StringHelper);
+  JSONHelperBuilder::Optional<std::string>(StringHelper);
 
 bool testInt()
 {
   Json::Value v(2);
+  cmJSONState state;
   int i = 0;
-  ASSERT_TRUE(IntHelper(i, &v) == ErrorCode::Success);
+  ASSERT_TRUE(IntHelper(i, &v, &state));
   ASSERT_TRUE(i == 2);
 
   i = 0;
   v = Json::nullValue;
-  ASSERT_TRUE(IntHelper(i, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!IntHelper(i, &v, &state));
 
   i = 0;
-  ASSERT_TRUE(IntHelper(i, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(IntHelper(i, nullptr, &state));
   ASSERT_TRUE(i == 1);
 
   return true;
@@ -92,16 +99,16 @@ bool testInt()
 bool testUInt()
 {
   Json::Value v(2);
+  cmJSONState state;
   unsigned int i = 0;
-  ASSERT_TRUE(UIntHelper(i, &v) == ErrorCode::Success);
+  ASSERT_TRUE(UIntHelper(i, &v, &state));
   ASSERT_TRUE(i == 2);
-
   i = 0;
   v = Json::nullValue;
-  ASSERT_TRUE(UIntHelper(i, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!UIntHelper(i, &v, &state));
 
   i = 0;
-  ASSERT_TRUE(UIntHelper(i, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(UIntHelper(i, nullptr, &state));
   ASSERT_TRUE(i == 1);
 
   return true;
@@ -110,21 +117,22 @@ bool testUInt()
 bool testBool()
 {
   Json::Value v(true);
+  cmJSONState state;
   bool b = false;
-  ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::Success);
+  ASSERT_TRUE(BoolHelper(b, &v, &state));
   ASSERT_TRUE(b);
 
   b = false;
   v = false;
-  ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::Success);
+  ASSERT_TRUE(BoolHelper(b, &v, &state));
   ASSERT_TRUE(!b);
 
   b = false;
   v = 4;
-  ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::InvalidBool);
+  ASSERT_TRUE(!BoolHelper(b, &v, &state));
 
   b = true;
-  ASSERT_TRUE(BoolHelper(b, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(BoolHelper(b, nullptr, &state));
   ASSERT_TRUE(!b);
 
   return true;
@@ -133,16 +141,17 @@ bool testBool()
 bool testString()
 {
   Json::Value v("str");
+  cmJSONState state;
   std::string str = "";
-  ASSERT_TRUE(StringHelper(str, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringHelper(str, &v, &state));
   ASSERT_TRUE(str == "str");
 
   str = "";
   v = Json::nullValue;
-  ASSERT_TRUE(StringHelper(str, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!StringHelper(str, &v, &state));
 
   str = "";
-  ASSERT_TRUE(StringHelper(str, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringHelper(str, nullptr, &state));
   ASSERT_TRUE(str == "default");
 
   return true;
@@ -150,17 +159,15 @@ bool testString()
 
 bool testObject()
 {
-  auto const subhelper =
-    JSONHelperBuilder::Object<ObjectStruct>(ErrorCode::Success,
-                                            ErrorCode::InvalidSubObject)
-      .Bind("subfield"_s, &ObjectStruct::Field2, IntHelper);
-  auto const helper = JSONHelperBuilder::Object<ObjectStruct>(
-                        ErrorCode::Success, ErrorCode::InvalidObject)
+  auto const subhelper = JSONHelperBuilder::Object<ObjectStruct>().Bind(
+    "subfield"_s, &ObjectStruct::Field2, IntHelper);
+  auto const helper = JSONHelperBuilder::Object<ObjectStruct>()
                         .Bind("field1"_s, &ObjectStruct::Field1, StringHelper)
                         .Bind("field2"_s, subhelper)
                         .Bind<std::string>("field3"_s, nullptr, StringHelper);
 
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = Json::objectValue;
   v["field2"]["subfield"] = 2;
@@ -168,38 +175,38 @@ bool testObject()
   v["extra"] = "extra";
 
   ObjectStruct s1;
-  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s1, &v, &state));
   ASSERT_TRUE(s1.Field1 == "Hello");
   ASSERT_TRUE(s1.Field2 == 2);
 
   v["field2"]["subfield"] = "wrong";
   ObjectStruct s2;
-  ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!helper(s2, &v, &state));
 
   v["field2"].removeMember("subfield");
   ObjectStruct s3;
-  ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidSubObject);
+  ASSERT_TRUE(!helper(s3, &v, &state));
 
   v.removeMember("field2");
   ObjectStruct s4;
-  ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s4, &v, &state));
 
   v["field2"] = Json::objectValue;
   v["field2"]["subfield"] = 2;
   v["field3"] = 3;
   ObjectStruct s5;
-  ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!helper(s5, &v, &state));
 
   v.removeMember("field3");
   ObjectStruct s6;
-  ASSERT_TRUE(helper(s6, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s6, &v, &state));
 
   v = "Hello";
   ObjectStruct s7;
-  ASSERT_TRUE(helper(s7, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s7, &v, &state));
 
   ObjectStruct s8;
-  ASSERT_TRUE(helper(s8, nullptr) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s8, nullptr, &state));
 
   return true;
 }
@@ -207,47 +214,48 @@ bool testObject()
 bool testObjectInherited()
 {
   auto const helper =
-    JSONHelperBuilder::Object<InheritedStruct>(ErrorCode::Success,
-                                               ErrorCode::InvalidObject)
+    JSONHelperBuilder::Object<InheritedStruct>(ErrorMessages::InvalidObject,
+                                               true)
       .Bind("field1"_s, &InheritedStruct::Field1, StringHelper)
       .Bind("field2"_s, &InheritedStruct::Field2, IntHelper)
       .Bind("field3"_s, &InheritedStruct::Field3, StringHelper);
 
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = 2;
   v["field3"] = "world!";
   v["extra"] = "extra";
 
   InheritedStruct s1;
-  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s1, &v, &state));
   ASSERT_TRUE(s1.Field1 == "Hello");
   ASSERT_TRUE(s1.Field2 == 2);
   ASSERT_TRUE(s1.Field3 == "world!");
 
   v["field2"] = "wrong";
   InheritedStruct s2;
-  ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!helper(s2, &v, &state));
 
   v.removeMember("field2");
   InheritedStruct s3;
-  ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s3, &v, &state));
 
   v["field2"] = 2;
   v["field3"] = 3;
   InheritedStruct s4;
-  ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!helper(s4, &v, &state));
 
   v.removeMember("field3");
   InheritedStruct s5;
-  ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s5, &v, &state));
 
   v = "Hello";
   InheritedStruct s6;
-  ASSERT_TRUE(helper(s6, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s6, &v, &state));
 
   InheritedStruct s7;
-  ASSERT_TRUE(helper(s7, nullptr) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s7, nullptr, &state));
 
   return true;
 }
@@ -255,22 +263,23 @@ bool testObjectInherited()
 bool testObjectNoExtra()
 {
   auto const helper = JSONHelperBuilder::Object<ObjectStruct>(
-                        ErrorCode::Success, ErrorCode::InvalidObject, false)
+                        ErrorMessages::InvalidObject, false)
                         .Bind("field1"_s, &ObjectStruct::Field1, StringHelper)
                         .Bind("field2"_s, &ObjectStruct::Field2, IntHelper);
 
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = 2;
 
   ObjectStruct s1;
-  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s1, &v, &state));
   ASSERT_TRUE(s1.Field1 == "Hello");
   ASSERT_TRUE(s1.Field2 == 2);
 
   v["extra"] = "world!";
   ObjectStruct s2;
-  ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!helper(s2, &v, &state));
 
   return true;
 }
@@ -278,31 +287,31 @@ bool testObjectNoExtra()
 bool testObjectOptional()
 {
   auto const helper =
-    JSONHelperBuilder::Object<ObjectStruct>(ErrorCode::Success,
-                                            ErrorCode::InvalidObject)
+    JSONHelperBuilder::Object<ObjectStruct>(ErrorMessages::InvalidObject, true)
       .Bind("field1"_s, &ObjectStruct::Field1, StringHelper, false)
       .Bind("field2"_s, &ObjectStruct::Field2, IntHelper, false)
       .Bind<std::string>("field3_s", nullptr, StringHelper, false);
 
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = 2;
   v["field3"] = "world!";
   v["extra"] = "extra";
 
   ObjectStruct s1;
-  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s1, &v, &state));
   ASSERT_TRUE(s1.Field1 == "Hello");
   ASSERT_TRUE(s1.Field2 == 2);
 
   v = Json::objectValue;
   ObjectStruct s2;
-  ASSERT_TRUE(helper(s2, &v) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s2, &v, &state));
   ASSERT_TRUE(s2.Field1 == "default");
   ASSERT_TRUE(s2.Field2 == 1);
 
   ObjectStruct s3;
-  ASSERT_TRUE(helper(s3, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(helper(s3, nullptr, &state));
   ASSERT_TRUE(s3.Field1 == "default");
   ASSERT_TRUE(s3.Field2 == 1);
 
@@ -312,25 +321,26 @@ bool testObjectOptional()
 bool testVector()
 {
   Json::Value v(Json::arrayValue);
+  cmJSONState state;
   v.append("Hello");
   v.append("world!");
   v.append("ignore");
 
   std::vector<std::string> l{ "default" };
   std::vector<std::string> expected{ "Hello", "world!", "ignore" };
-  ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringVectorHelper(l, &v, &state));
   ASSERT_TRUE(l == expected);
 
   v[1] = 2;
   l = { "default" };
-  ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!StringVectorHelper(l, &v, &state));
 
   v = "Hello";
   l = { "default" };
-  ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::InvalidArray);
+  ASSERT_TRUE(!StringVectorHelper(l, &v, &state));
 
   l = { "default" };
-  ASSERT_TRUE(StringVectorHelper(l, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringVectorHelper(l, nullptr, &state));
   ASSERT_TRUE(l.empty());
 
   return true;
@@ -339,6 +349,7 @@ bool testVector()
 bool testVectorFilter()
 {
   Json::Value v(Json::arrayValue);
+  cmJSONState state;
   v.append("Hello");
   v.append("world!");
   v.append("ignore");
@@ -348,19 +359,19 @@ bool testVectorFilter()
     "Hello",
     "world!",
   };
-  ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringVectorFilterHelper(l, &v, &state));
   ASSERT_TRUE(l == expected);
 
   v[1] = 2;
   l = { "default" };
-  ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(!StringVectorFilterHelper(l, &v, &state));
 
   v = "Hello";
   l = { "default" };
-  ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::InvalidArray);
+  ASSERT_TRUE(!StringVectorFilterHelper(l, &v, &state));
 
   l = { "default" };
-  ASSERT_TRUE(StringVectorFilterHelper(l, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringVectorFilterHelper(l, nullptr, &state));
   ASSERT_TRUE(l.empty());
 
   return true;
@@ -372,20 +383,21 @@ bool testMap()
   v["field1"] = "Hello";
   v["field2"] = "world!";
   v["ignore"] = "ignore";
+  cmJSONState state;
 
   std::map<std::string, std::string> m{ { "key", "default" } };
   std::map<std::string, std::string> expected{ { "field1", "Hello" },
                                                { "field2", "world!" },
                                                { "ignore", "ignore" } };
-  ASSERT_TRUE(StringMapHelper(m, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringMapHelper(m, &v, &state));
   ASSERT_TRUE(m == expected);
 
   v = Json::arrayValue;
   m = { { "key", "default" } };
-  ASSERT_TRUE(StringMapHelper(m, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!StringMapHelper(m, &v, &state));
 
   m = { { "key", "default" } };
-  ASSERT_TRUE(StringMapHelper(m, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringMapHelper(m, nullptr, &state));
   ASSERT_TRUE(m.empty());
 
   return true;
@@ -394,6 +406,7 @@ bool testMap()
 bool testMapFilter()
 {
   Json::Value v(Json::objectValue);
+  cmJSONState state;
   v["field1"] = "Hello";
   v["field2"] = "world!";
   v["ignore"] = "ignore";
@@ -401,15 +414,15 @@ bool testMapFilter()
   std::map<std::string, std::string> m{ { "key", "default" } };
   std::map<std::string, std::string> expected{ { "field1", "Hello" },
                                                { "field2", "world!" } };
-  ASSERT_TRUE(StringMapFilterHelper(m, &v) == ErrorCode::Success);
+  ASSERT_TRUE(StringMapFilterHelper(m, &v, &state));
   ASSERT_TRUE(m == expected);
 
   v = Json::arrayValue;
   m = { { "key", "default" } };
-  ASSERT_TRUE(StringMapFilterHelper(m, &v) == ErrorCode::InvalidObject);
+  ASSERT_TRUE(!StringMapFilterHelper(m, &v, &state));
 
   m = { { "key", "default" } };
-  ASSERT_TRUE(StringMapFilterHelper(m, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(StringMapFilterHelper(m, nullptr, &state));
   ASSERT_TRUE(m.empty());
 
   return true;
@@ -418,13 +431,14 @@ bool testMapFilter()
 bool testOptional()
 {
   Json::Value v = "Hello";
+  cmJSONState state;
 
   cm::optional<std::string> str{ "default" };
-  ASSERT_TRUE(OptionalStringHelper(str, &v) == ErrorCode::Success);
+  ASSERT_TRUE(OptionalStringHelper(str, &v, &state));
   ASSERT_TRUE(str == "Hello");
 
   str.emplace("default");
-  ASSERT_TRUE(OptionalStringHelper(str, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(OptionalStringHelper(str, nullptr, &state));
   ASSERT_TRUE(str == cm::nullopt);
 
   return true;
@@ -433,25 +447,24 @@ bool testOptional()
 bool testRequired()
 {
   Json::Value v = "Hello";
-
   std::string str = "default";
   int i = 1;
-  ASSERT_TRUE(RequiredStringHelper(str, &v) == ErrorCode::Success);
+  cmJSONState state;
+  ASSERT_TRUE(RequiredStringHelper(str, &v, &state));
   ASSERT_TRUE(str == "Hello");
-  ASSERT_TRUE(RequiredIntHelper(i, &v) == ErrorCode::InvalidInt);
+  ASSERT_TRUE(!RequiredIntHelper(i, &v, &state));
 
   v = 2;
   str = "default";
   i = 1;
-  ASSERT_TRUE(RequiredStringHelper(str, &v) == ErrorCode::InvalidString);
-  ASSERT_TRUE(RequiredIntHelper(i, &v) == ErrorCode::Success);
+  ASSERT_TRUE(!RequiredStringHelper(str, &v, &state));
+  ASSERT_TRUE(RequiredIntHelper(i, &v, &state));
   ASSERT_TRUE(i == 2);
 
   str = "default";
   i = 1;
-  ASSERT_TRUE(RequiredStringHelper(str, nullptr) ==
-              ErrorCode::MissingRequired);
-  ASSERT_TRUE(RequiredIntHelper(i, nullptr) == ErrorCode::MissingRequired);
+  ASSERT_TRUE(!RequiredStringHelper(str, nullptr, &state));
+  ASSERT_TRUE(!RequiredIntHelper(i, nullptr, &state));
 
   return true;
 }

+ 2 - 3
Tests/RunCMake/CMakePresets/Comment-stderr.txt

@@ -1,7 +1,6 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/Comment: JSON parse error
-Errors:
-[^
+]*/Tests/RunCMake/CMakePresets/Comment:
+JSON Parse Error: [^
 ]*Comment\/CMakePresets.json:
 \* Line 1, Column 1
   Syntax error: value, object or array expected\.

+ 2 - 1
Tests/RunCMake/CMakePresets/ConditionFuture-stderr.txt

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

+ 2 - 1
Tests/RunCMake/CMakePresets/CyclicInheritance0-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/CyclicInheritance0: Cyclic preset inheritance$
+]*/Tests/RunCMake/CMakePresets/CyclicInheritance0:
+Cyclic preset inheritance for preset "CyclicInheritance0"$

+ 2 - 1
Tests/RunCMake/CMakePresets/CyclicInheritance1-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/CyclicInheritance1: Cyclic preset inheritance$
+]*/Tests/RunCMake/CMakePresets/CyclicInheritance1:
+Cyclic preset inheritance for preset "CyclicInheritance0"$

+ 2 - 1
Tests/RunCMake/CMakePresets/CyclicInheritance2-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/CyclicInheritance2: Cyclic preset inheritance$
+]*/Tests/RunCMake/CMakePresets/CyclicInheritance2:
+Cyclic preset inheritance for preset "CyclicInheritance0"$

+ 2 - 1
Tests/RunCMake/CMakePresets/DuplicatePresets-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/DuplicatePresets: Duplicate presets$
+]*/Tests/RunCMake/CMakePresets/DuplicatePresets:
+Duplicate preset: "DuplicatePresets"$

+ 2 - 1
Tests/RunCMake/CMakePresets/EmptyCacheKey-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyCacheKey: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/EmptyCacheKey:
+Invalid preset: "EmptyCacheKey"$

+ 2 - 1
Tests/RunCMake/CMakePresets/EmptyEnv-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyEnv: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/EmptyEnv:
+Invalid macro expansion in "EmptyEnv"$

+ 2 - 1
Tests/RunCMake/CMakePresets/EmptyEnvKey-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyEnvKey: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/EmptyEnvKey:
+Invalid preset: "EmptyEnvKey"$

+ 2 - 1
Tests/RunCMake/CMakePresets/EmptyPenv-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyPenv: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/EmptyPenv:
+Invalid macro expansion in "EmptyPenv"$

+ 4 - 4
Tests/RunCMake/CMakePresets/EmptyPresetName-stderr.txt

@@ -1,5 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EmptyPresetName: Invalid preset
-Errors:
-[^
-]*/EmptyPresetName/CMakePresets.json$
+]*/Tests/RunCMake/CMakePresets/EmptyPresetName:
+Error: @5,15: Invalid Preset Name
+      "name": "",
+              \^$

+ 3 - 1
Tests/RunCMake/CMakePresets/EnvCycle-stderr.txt

@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/EnvCycle: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/EnvCycle:
+Invalid preset: "EnvCycle"
+Invalid macro expansion in "EnvCycle"$

+ 2 - 1
Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/ErrorNoWarningDeprecated:
+Invalid preset: "ErrorNoWarningDeprecated"$

+ 2 - 1
Tests/RunCMake/CMakePresets/ErrorNoWarningDev-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ErrorNoWarningDev: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/ErrorNoWarningDev:
+Invalid preset: "ErrorNoWarningDev"$

+ 4 - 1
Tests/RunCMake/CMakePresets/ExtraPresetField-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ExtraPresetField: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/ExtraPresetField:
+Error: @8,18: Invalid extra field "invalid" in Preset
+      "invalid": true
+                 \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/ExtraRootField-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ExtraRootField: Invalid root object$
+]*/Tests/RunCMake/CMakePresets/ExtraRootField:
+Error: @10,14: Invalid extra field "invalid" in root object
+  "invalid": true
+             \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/ExtraVariableField-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/ExtraVariableField: Invalid CMake variable definition$
+]*/Tests/RunCMake/CMakePresets/ExtraVariableField:
+Error: @11,22: Invalid extra field "invalid" in variable "EXTRA" for preset "ExtraVariableField"
+          "invalid": true
+                     \^$

+ 2 - 1
Tests/RunCMake/CMakePresets/FileDirFuture-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/FileDirFuture: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/FileDirFuture:
+Invalid macro expansion in "FileDirFuture"$

+ 2 - 1
Tests/RunCMake/CMakePresets/FuturePresetInstallDirField-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/FuturePresetInstallDirField: File version must be 3 or higher for installDir preset support.$
+]*/Tests/RunCMake/CMakePresets/FuturePresetInstallDirField:
+File version must be 3 or higher for installDir preset support$

+ 2 - 1
Tests/RunCMake/CMakePresets/FuturePresetToolchainField-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/FuturePresetToolchainField: File version must be 3 or higher for toolchainFile preset support.$
+]*/Tests/RunCMake/CMakePresets/FuturePresetToolchainField:
+File version must be 3 or higher for toolchainFile preset support$

+ 4 - 1
Tests/RunCMake/CMakePresets/HighVersion-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/HighVersion: Unrecognized "version" field$
+]*/Tests/RunCMake/CMakePresets/HighVersion:
+Error: @2,14: Unrecognized "version" field
+  "version": 1000,
+             \^$

+ 2 - 1
Tests/RunCMake/CMakePresets/HostSystemNameFuture-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/HostSystemNameFuture: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/HostSystemNameFuture:
+Invalid macro expansion in "HostSystemNameFuture"$

+ 3 - 1
Tests/RunCMake/CMakePresets/IncludeCycle-stderr.txt

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

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

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

+ 3 - 4
Tests/RunCMake/CMakePresets/IncludeNotFound-stderr.txt

@@ -1,5 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/IncludeNotFound: File not found
-Errors:
-[^
-]*/IncludeNotFound/NotFound.json: Failed to read file$
+]*/Tests/RunCMake/CMakePresets/IncludeNotFound:
+File not found: [^
+]*/IncludeNotFound/NotFound.json$

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

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

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

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

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidArchitectureStrategy:
+Error: @9,21: Invalid preset
+        "strategy": {}
+                    \^$

+ 2 - 1
Tests/RunCMake/CMakePresets/InvalidInheritance-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidInheritance: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidInheritance:
+Invalid preset: "InvalidInheritance"$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidPresetBinaryDir:
+Error: @7,20: "binaryDir" expected a string
+      "binaryDir": \[\]
+                   \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidPresetGenerator-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresetGenerator: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidPresetGenerator:
+Error: @6,20: "generator" expected a string
+      "generator": \[\],
+                   \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidPresetName-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresetName: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidPresetName:
+Error: @5,15: Invalid Preset Name
+      "name": \[\],
+              \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidPresetVendor-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresetVendor: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidPresetVendor:
+Error: @8,17: Invalid preset
+      "vendor": true
+                \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidPresets-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidPresets: Invalid "configurePresets" field$
+]*/Tests/RunCMake/CMakePresets/InvalidPresets:
+Error: @3,23: Invalid "configurePresets" field
+  "configurePresets": {}
+                      \^$

+ 3 - 1
Tests/RunCMake/CMakePresets/InvalidRegex-stderr.txt

@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidRegex: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/InvalidRegex:
+Invalid preset: "InvalidRegex"
+Invalid macro expansion in "InvalidRegex"$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidRoot-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidRoot: Invalid root object$
+]*/Tests/RunCMake/CMakePresets/InvalidRoot:
+Error: \@1\,1\: Invalid root object
+\[\]
+\^$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidToolsetStrategy-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidToolsetStrategy: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidToolsetStrategy:
+Error: @9,21: Invalid preset
+        "strategy": {}
+                    \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidVariableValue-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidVariableValue: Invalid CMake variable definition$
+]*/Tests/RunCMake/CMakePresets/InvalidVariableValue:
+Error: @10,20: "value" expected a string
+          "value": \[\]
+                   \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidVariables-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidVariables: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/InvalidVariables:
+Error: @8,25: Invalid preset
+      "cacheVariables": \[\]
+                        \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidVendor-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidVendor: Invalid root object$
+]*/Tests/RunCMake/CMakePresets/InvalidVendor:
+Error: @3,13: Invalid root object
+  "vendor": true,
+            \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/InvalidVersion-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/InvalidVersion: Invalid "version" field$
+]*/Tests/RunCMake/CMakePresets/InvalidVersion:
+Error: @2,14: Invalid "version" field
+  "version": "1.0",
+             \^$

+ 2 - 8
Tests/RunCMake/CMakePresets/JSONParseError-stderr.txt

@@ -1,9 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/JSONParseError: JSON parse error
-Errors:
-[^
-]*JSONParseError/CMakePresets.json:
-\* Line 1, Column 1
-  Syntax error: value, object or array expected\.
-\* Line 1, Column 1
-  A valid JSON document must be either an array or an object value\.$
+]*/Tests/RunCMake/CMakePresets/JSONParseError:
+A JSON document cannot be empty

+ 4 - 1
Tests/RunCMake/CMakePresets/LowVersion-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/LowVersion: Unrecognized "version" field$
+]*/Tests/RunCMake/CMakePresets/LowVersion:
+Error: @2,14: Unrecognized "version" field
+  "version": 0,
+             \^

+ 4 - 1
Tests/RunCMake/CMakePresets/MinimumRequiredInvalid-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/MinimumRequiredInvalid: Invalid "cmakeMinimumRequired" field$
+]*/Tests/RunCMake/CMakePresets/MinimumRequiredInvalid:
+Error: @3,27: Invalid "cmakeMinimumRequired"
+  "cmakeMinimumRequired": "3.18",
+                          \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/MinimumRequiredMajor-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/MinimumRequiredMajor: "cmakeMinimumRequired" version too new$
+]*/Tests/RunCMake/CMakePresets/MinimumRequiredMajor:
+Error: @4,14: "cmakeMinimumRequired" major version 1000 must be less than [0-9]*
+    "major": 1000
+             \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/MinimumRequiredMinor-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/MinimumRequiredMinor: "cmakeMinimumRequired" version too new$
+]*/Tests/RunCMake/CMakePresets/MinimumRequiredMinor:
+Error: @5,14: "cmakeMinimumRequired" minor version 1000 must be less than [0-9]*
+    "minor": 1000
+             \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/MinimumRequiredPatch-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/MinimumRequiredPatch: "cmakeMinimumRequired" version too new$
+]*/Tests/RunCMake/CMakePresets/MinimumRequiredPatch:
+Error: @6,14: "cmakeMinimumRequired" patch version 50000000 must be less than [0-9]*
+    "patch": 50000000
+             \^$

+ 3 - 1
Tests/RunCMake/CMakePresets/NoCMakePresets-stderr.txt

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

+ 3 - 1
Tests/RunCMake/CMakePresets/NoPresetBinaryDir-stderr.txt

@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoPresetBinaryDir: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/NoPresetBinaryDir:
+Preset "NoPresetBinaryDir" missing field "binaryDir"
+Invalid preset: "NoPresetBinaryDir"$

+ 3 - 1
Tests/RunCMake/CMakePresets/NoPresetGenerator-stderr.txt

@@ -1,2 +1,4 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoPresetGenerator: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/NoPresetGenerator:
+Preset "NoPresetGenerator" missing field "generator"
+Invalid preset: "NoPresetGenerator"$

+ 4 - 1
Tests/RunCMake/CMakePresets/NoPresetName-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoPresetName: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/NoPresetName:
+Error: @4,5: Missing required field "name" in Preset
+    {
+    \^$

+ 2 - 1
Tests/RunCMake/CMakePresets/NoSuchMacro-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoSuchMacro: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/NoSuchMacro:
+Invalid macro expansion in "NoSuchMacro"$

+ 4 - 1
Tests/RunCMake/CMakePresets/NoVariableValue-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoVariableValue: Invalid CMake variable definition$
+]*/Tests/RunCMake/CMakePresets/NoVariableValue:
+Error: @9,16: Missing required field "value" in variable "VAR" for preset "NoVariableValue"
+        "VAR": {}
+               \^$

+ 2 - 1
Tests/RunCMake/CMakePresets/NoVersion-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/NoVersion: No "version" field$
+]*/Tests/RunCMake/CMakePresets/NoVersion:
+No "version" field$

+ 2 - 1
Tests/RunCMake/CMakePresets/PathListSepFuture-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/PathListSepFuture: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/PathListSepFuture:
+Invalid macro expansion in "PathListSepFuture"$

+ 4 - 1
Tests/RunCMake/CMakePresets/PresetNotObject-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/PresetNotObject: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/PresetNotObject:
+Error: @4,5: Invalid Preset
+    \[\]
+    \^$

+ 2 - 1
Tests/RunCMake/CMakePresets/SubConditionNull-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/SubConditionNull: Invalid preset condition$
+]*/Tests/RunCMake/CMakePresets/SubConditionNull:
+Invalid condition for preset "SubConditionNull"$

+ 2 - 1
Tests/RunCMake/CMakePresets/UnclosedMacro-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UnclosedMacro: Invalid macro expansion$
+]*/Tests/RunCMake/CMakePresets/UnclosedMacro:
+Invalid macro expansion in "UnclosedMacro"$

+ 4 - 1
Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/UnknownArchitectureStrategy:
+Error: @9,21: Invalid preset
+        "strategy": "unknown"
+                    \^$

+ 4 - 1
Tests/RunCMake/CMakePresets/UnknownToolsetStrategy-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UnknownToolsetStrategy: Invalid preset$
+]*/Tests/RunCMake/CMakePresets/UnknownToolsetStrategy:
+Error: @9,21: Invalid preset
+        "strategy": "unknown"
+                    \^$

+ 2 - 1
Tests/RunCMake/CMakePresets/UserDuplicateCross-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UserDuplicateCross: Duplicate presets$
+]*/Tests/RunCMake/CMakePresets/UserDuplicateCross:
+Duplicate preset: "UserDuplicateCross"$

+ 2 - 1
Tests/RunCMake/CMakePresets/UserDuplicateInUser-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/UserDuplicateInUser: Duplicate presets$
+]*/Tests/RunCMake/CMakePresets/UserDuplicateInUser:
+Duplicate preset: "UserDuplicateInUser"$

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

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

+ 4 - 1
Tests/RunCMake/CMakePresets/VariableNotObject-stderr.txt

@@ -1,2 +1,5 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresets/VariableNotObject: Invalid CMake variable definition$
+]*/Tests/RunCMake/CMakePresets/VariableNotObject:
+Error: @9,16: Invalid CMake variable "VAR" for preset "VariableNotObject"
+        "VAR": \[\]
+               \^$

+ 2 - 1
Tests/RunCMake/CMakePresetsBuild/ConditionFuture-build-conditionFuture-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/ConditionFuture: File version must be 3 or higher for condition support$
+]*/Tests/RunCMake/CMakePresetsBuild/ConditionFuture:
+File version must be 3 or higher for condition support$

+ 2 - 1
Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable-build-x-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable: Configure preset is unreachable from preset's file$
+]*/Tests/RunCMake/CMakePresetsBuild/ConfigurePresetUnreachable:
+Configure preset "x" is unreachable from preset's file$

+ 2 - 1
Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-build-badConfigurePreset-stderr.txt

@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset: Invalid "configurePreset" field
+]*/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset:
+Invalid "configurePreset": "badConfigurePreset"$

+ 2 - 1
Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset-configure-default-stderr.txt

@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset: Invalid "configurePreset" field
+]*/Tests/RunCMake/CMakePresetsBuild/InvalidConfigurePreset:
+Invalid "configurePreset": "badConfigurePreset"$

+ 2 - 1
Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset-build-noConfigurePreset-stderr.txt

@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset: Invalid preset
+]*/Tests/RunCMake/CMakePresetsBuild/NoConfigurePreset:
+Invalid preset: "noConfigurePreset"$

+ 2 - 1
Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported-build-x-stderr.txt

@@ -1,2 +1,3 @@
 CMake Error: Could not read presets from [^
-]*Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported: File version must be 2 or higher for build and test preset support.
+]*Tests/RunCMake/CMakePresetsBuild/PresetsUnsupported:
+File version must be 2 or higher for build and test preset support$

+ 2 - 1
Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion-configure-x-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion: File version must be 6 or higher for package preset support$
+]*/Tests/RunCMake/CMakePresetsPackage/UnsupportedVersion:
+File version must be 6 or higher for package preset support$

+ 2 - 1
Tests/RunCMake/CMakePresetsTest/ConditionFuture-test-x-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsTest/ConditionFuture: File version must be 3 or higher for condition support$
+]*/Tests/RunCMake/CMakePresetsTest/ConditionFuture:
+File version must be 3 or higher for condition support$

+ 2 - 1
Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable-test-x-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Could not read presets from [^
-]*/Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable: Configure preset is unreachable from preset's file$
+]*/Tests/RunCMake/CMakePresetsTest/ConfigurePresetUnreachable:
+Configure preset "x" is unreachable from preset's file

Some files were not shown because too many files changed in this diff