Browse Source

Merge topic 'cmake-presets-workflow'

374d82bbcd cmake: Add --workflow mode
e316812884 CMakePresets.json: Add workflow presets to schema

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Merge-request: !7711
Brad King 3 years ago
parent
commit
b9968f3006
64 changed files with 1297 additions and 16 deletions
  1. 48 8
      Help/manual/cmake-presets.7.rst
  2. 22 0
      Help/manual/cmake.1.rst
  3. 23 0
      Help/manual/presets/example.json
  4. 82 3
      Help/manual/presets/schema.json
  5. 4 0
      Help/release/dev/cmake-presets-workflow.rst
  6. 1 0
      Source/CMakeLists.txt
  7. 139 0
      Source/cmCMakePresetsGraph.cxx
  8. 44 1
      Source/cmCMakePresetsGraph.h
  9. 4 0
      Source/cmCMakePresetsGraphInternal.h
  10. 33 4
      Source/cmCMakePresetsGraphReadJSON.cxx
  11. 95 0
      Source/cmCMakePresetsGraphReadJSONWorkflowPresets.cxx
  12. 209 0
      Source/cmake.cxx
  13. 14 0
      Source/cmake.h
  14. 65 0
      Source/cmakemain.cxx
  15. 4 0
      Tests/RunCMake/CMakeLists.txt
  16. 4 0
      Tests/RunCMake/CMakePresets/DocumentationExampleListAllPresets-stdout.txt
  17. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-result.txt
  18. 4 0
      Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stderr.txt
  19. 17 0
      Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stdout.txt
  20. 8 0
      Tests/RunCMake/CMakePresetsWorkflow/BadExitCode.cmake
  21. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/BadExitCodeTest.cmake
  22. 3 0
      Tests/RunCMake/CMakePresetsWorkflow/CMakeLists.txt.in
  23. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-result.txt
  24. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt
  25. 32 0
      Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch.json.in
  26. 0 0
      Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-result.txt
  27. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt
  28. 27 0
      Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure.json.in
  29. 19 0
      Tests/RunCMake/CMakePresetsWorkflow/Good-stdout.txt
  30. 8 0
      Tests/RunCMake/CMakePresetsWorkflow/Good.cmake
  31. 87 0
      Tests/RunCMake/CMakePresetsWorkflow/Good.json.in
  32. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/GoodUser-stdout.txt
  33. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/GoodUser.cmake
  34. 14 0
      Tests/RunCMake/CMakePresetsWorkflow/GoodUser.json.in
  35. 4 0
      Tests/RunCMake/CMakePresetsWorkflow/ListPresets-stdout.txt
  36. 30 0
      Tests/RunCMake/CMakePresetsWorkflow/ListPresets.json.in
  37. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-result.txt
  38. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt
  39. 9 0
      Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps.json.in
  40. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-result.txt
  41. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt
  42. 14 0
      Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep.json.in
  43. 79 0
      Tests/RunCMake/CMakePresetsWorkflow/RunCMakeTest.cmake
  44. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-result.txt
  45. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt
  46. 25 0
      Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure.json.in
  47. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-result.txt
  48. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-stderr.txt
  49. 14 0
      Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep.json.in
  50. 8 0
      Tests/RunCMake/CMakePresetsWorkflow/UnreachableStepUser.json.in
  51. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-result.txt
  52. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-stderr.txt
  53. 4 0
      Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion.json.in
  54. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-result.txt
  55. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-stderr.txt
  56. 23 0
      Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled.json.in
  57. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-result.txt
  58. 2 0
      Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-stderr.txt
  59. 20 0
      Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden.json.in
  60. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-result.txt
  61. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-stderr.txt
  62. 20 0
      Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro.json.in
  63. 3 0
      Tests/RunCMake/CMakePresetsWorkflow/check.cmake
  64. 1 0
      Tests/RunCMake/CMakePresetsWorkflow/cpack_staging.cmake.in

+ 48 - 8
Help/manual/cmake-presets.7.rst

@@ -106,6 +106,10 @@ The root object recognizes the following fields:
   An optional array of `Package Preset`_ objects.
   This is allowed in preset files specifying version ``6`` or above.
 
+``workflowPresets``
+  An optional array of `Workflow Preset`_ objects.
+  This is allowed in preset files specifying version ``6`` or above.
+
 Includes
 ^^^^^^^^
 
@@ -137,8 +141,8 @@ that may contain the following fields:
   This identifier is used in the :ref:`cmake --preset <CMake Options>` option.
   There must not be two configure presets in the union of ``CMakePresets.json``
   and ``CMakeUserPresets.json`` in the same directory with the same name.
-  However, a configure preset may have the same name as a build, test, or
-  package preset.
+  However, a configure preset may have the same name as a build, test,
+  package, or workflow preset.
 
 ``hidden``
   An optional boolean specifying whether or not a preset should be hidden.
@@ -364,8 +368,8 @@ that may contain the following fields:
   :ref:`cmake --build --preset <Build Tool Mode>` option.
   There must not be two build presets in the union of ``CMakePresets.json``
   and ``CMakeUserPresets.json`` in the same directory with the same name.
-  However, a build preset may have the same name as a configure, test, or
-  package preset.
+  However, a build preset may have the same name as a configure, test,
+  package, or workflow preset.
 
 ``hidden``
   An optional boolean specifying whether or not a preset should be hidden.
@@ -525,8 +529,8 @@ that may contain the following fields:
   This identifier is used in the :option:`ctest --preset` option.
   There must not be two test presets in the union of ``CMakePresets.json``
   and ``CMakeUserPresets.json`` in the same directory with the same name.
-  However, a test preset may have the same name as a configure, build, or
-  package preset.
+  However, a test preset may have the same name as a configure, build,
+  package, or workflow preset.
 
 ``hidden``
   An optional boolean specifying whether or not a preset should be hidden.
@@ -861,8 +865,8 @@ fields:
   This identifier is used in the :option:`cpack --preset` option.
   There must not be two package presets in the union of ``CMakePresets.json``
   and ``CMakeUserPresets.json`` in the same directory with the same name.
-  However, a package preset may have the same name as a configure, build, or
-  test preset.
+  However, a package preset may have the same name as a configure, build,
+  test, or workflow preset.
 
 ``hidden``
   An optional boolean specifying whether or not a preset should be hidden.
@@ -977,6 +981,42 @@ fields:
 ``vendorName``
   An optional string representing the vendor name.
 
+Workflow Preset
+^^^^^^^^^^^^^^^
+
+Workflow presets may be used in schema version ``6`` or above. Each entry of
+the ``workflowPresets`` array is a JSON object that may contain the following
+fields:
+
+``name``
+  A required string representing the machine-friendly name of the preset.
+  This identifier is used in the
+  :ref:`cmake --workflow --preset <Workflow Mode>` option. There must not be
+  two workflow presets in the union of ``CMakePresets.json`` and
+  ``CMakeUserPresets.json`` in the same directory with the same name. However,
+  a workflow preset may have the same name as a configure, build, test, or
+  package preset.
+
+``displayName``
+  An optional string with a human-friendly name of the preset.
+
+``description``
+  An optional string with a human-friendly description of the preset.
+
+``steps``
+  A required array of objects describing the steps of the workflow. The first
+  step must be a configure preset, and all subsequent steps must be non-
+  configure presets whose ``configurePreset`` field matches the starting
+  configure preset. Each object may contain the following fields:
+
+  ``type``
+    A required string. The first step must be ``configure``. Subsequent steps
+    must be either ``build``, ``test``, or ``package``.
+
+  ``name``
+    A required string representing the name of the configure, build, test, or
+    package preset to run as this workflow step.
+
 Condition
 ^^^^^^^^^
 

+ 22 - 0
Help/manual/cmake.1.rst

@@ -30,6 +30,9 @@ Synopsis
  `Run the Find-Package Tool`_
   cmake --find-package [<options>]
 
+ `Run a Workflow Preset`_
+  cmake --workflow [<options>]
+
  `View Help`_
   cmake --help[-<topic>]
 
@@ -1177,6 +1180,25 @@ autoconf-based projects (via ``share/aclocal/cmake.m4``).
   This mode is not well-supported due to some technical limitations.
   It is kept for compatibility but should not be used in new projects.
 
+.. _`Workflow Mode`:
+
+Run a Workflow Preset
+=====================
+
+:manual:`CMake Presets <cmake-presets(7)>` provides a way to execute multiple
+build steps in order:
+
+.. option:: --preset <preset>, --preset=<preset>
+
+  Use a workflow preset to specify a workflow. The project binary directory
+  is inferred from the initial configure preset. The current working directory
+  must contain CMake preset files.
+  See :manual:`preset <cmake-presets(7)>` for more details.
+
+.. option:: --list-presets
+
+  Lists the available workflow presets. The current working directory must
+  contain CMake preset files.
 
 View Help
 =========

+ 23 - 0
Help/manual/presets/example.json

@@ -75,6 +75,29 @@
       ]
     }
   ],
+  "workflowPresets": [
+    {
+      "name": "default",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        },
+        {
+          "type": "build",
+          "name": "default"
+        },
+        {
+          "type": "test",
+          "name": "default"
+        },
+        {
+          "type": "package",
+          "name": "default"
+        }
+      ]
+    }
+  ],
   "vendor": {
     "example.com/ExampleIDE/1.0": {
       "autoFormat": false

+ 82 - 3
Help/manual/presets/schema.json

@@ -85,6 +85,7 @@
         "buildPresets": { "$ref": "#/definitions/buildPresetsV4"},
         "testPresets": { "$ref": "#/definitions/testPresetsV5"},
         "packagePresets": { "$ref": "#/definitions/packagePresetsV6"},
+        "workflowPresets": { "$ref": "#/definitions/workflowPresetsV6" },
         "include": { "$ref": "#/definitions/include"}
       },
       "additionalProperties": false
@@ -492,7 +493,7 @@
         "properties": {
           "name": {
             "type": "string",
-            "description": "A required string representing the machine-friendly name of the preset. This identifier is used in the --preset argument. There must not be two presets (configure, build, test, or package) in the union of CMakePresets.json and CMakeUserPresets.json in the same directory with the same name.",
+            "description": "A required string representing the machine-friendly name of the preset. This identifier is used in the --preset argument. There must not be two presets (configure, build, test, package, or workflow) in the union of CMakePresets.json and CMakeUserPresets.json in the same directory with the same name.",
             "minLength": 1
           },
           "hidden": {
@@ -744,7 +745,7 @@
         "properties": {
           "name": {
             "type": "string",
-            "description": "A required string representing the machine-friendly name of the preset. This identifier is used in the --preset argument. There must not be two presets (configure, build, test, or package) in the union of CMakePresets.json and CMakeUserPresets.json in the same directory with the same name.",
+            "description": "A required string representing the machine-friendly name of the preset. This identifier is used in the --preset argument. There must not be two presets (configure, build, test, package, or workflow) in the union of CMakePresets.json and CMakeUserPresets.json in the same directory with the same name.",
             "minLength": 1
           },
           "hidden": {
@@ -1153,7 +1154,7 @@
         "properties": {
           "name": {
             "type": "string",
-            "description": "A required string representing the machine-friendly name of the preset. This identifier is used in the --preset argument. There must not be two presets (configure, build, test, or package) in the union of CMakePresets.json and CMakeUserPresets.json in the same directory with the same name.",
+            "description": "A required string representing the machine-friendly name of the preset. This identifier is used in the --preset argument. There must not be two presets (configure, build, test, package, or workflow) in the union of CMakePresets.json and CMakeUserPresets.json in the same directory with the same name.",
             "minLength": 1
           },
           "hidden": {
@@ -1321,6 +1322,84 @@
         "additionalProperties": false
       }
     },
+    "workflowPresetsItemsV6": {
+      "type": "array",
+      "description": "An optional array of workflow preset objects. Used to execute configure, build, test, and package presets in order. Available in version 6 and higher.",
+      "items": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "A required string representing the machine-friendly name of the preset. This identifier is used in the --preset argument. There must not be two presets (configure, build, test, package, or workflow) in the union of CMakePresets.json and CMakeUserPresets.json in the same directory with the same name.",
+            "minLength": 1
+          },
+          "vendor": {
+            "type": "object",
+            "description": "An optional map containing vendor-specific information. CMake does not interpret the contents of this field except to verify that it is a map if it does exist. However, it should follow the same conventions as the root-level vendor field.",
+            "properties": {}
+          },
+          "displayName": {
+            "type": "string",
+            "description": "An optional string with a human-friendly name of the preset."
+          },
+          "description": {
+            "type": "string",
+            "description": "An optional string with a human-friendly description of the preset."
+          },
+          "steps": {
+            "type": "array",
+            "description": "A required array of objects describing the steps of the workflow. The first step must be a configure preset, and all subsequent steps must be non-configure presets whose configurePreset field matches the starting configure preset.",
+            "items": {
+              "type": "object",
+              "properties": {
+                "type": {
+                  "type": "string",
+                  "description": "A required string. The first step must be configure. Subsequent steps must be either build, test, or package.",
+                  "enum": ["configure", "build", "test", "package"]
+                },
+                "name": {
+                  "type": "string",
+                  "description": "A required string representing the name of the configure, build, test, or package preset to run as this workflow step.",
+                  "minLength": 1
+                }
+              },
+              "required": [
+                "type",
+                "name"
+              ],
+              "additionalProperties": false
+            }
+          }
+        },
+        "required": [
+          "name",
+          "steps"
+        ],
+        "additionalProperties": false
+      }
+    },
+    "workflowPresetsV6": {
+      "type": "array",
+      "description": "An optional array of workflow preset objects. Used to execute configure, build, test, and package presets in order. Available in version 6 and higher.",
+      "allOf": [
+        { "$ref": "#/definitions/workflowPresetsItemsV6" }
+      ],
+      "items": {
+        "type": "object",
+        "properties": {
+          "name": {},
+          "vendor": {},
+          "displayName": {},
+          "description": {},
+          "steps": {}
+        },
+        "required": [
+          "name",
+          "steps"
+        ],
+        "additionalProperties": false
+      }
+    },
     "condition": {
       "anyOf": [
         {

+ 4 - 0
Help/release/dev/cmake-presets-workflow.rst

@@ -0,0 +1,4 @@
+cmake-presets-workflow
+----------------------
+
+* The :manual:`cmake-presets(7)` format now supports a ``workflowPresets`` field.

+ 1 - 0
Source/CMakeLists.txt

@@ -148,6 +148,7 @@ add_library(
   cmCMakePresetsGraphReadJSONConfigurePresets.cxx
   cmCMakePresetsGraphReadJSONPackagePresets.cxx
   cmCMakePresetsGraphReadJSONTestPresets.cxx
+  cmCMakePresetsGraphReadJSONWorkflowPresets.cxx
   cmCommandArgumentParserHelper.cxx
   cmCommonTargetGenerator.cxx
   cmCommonTargetGenerator.h

+ 139 - 0
Source/cmCMakePresetsGraph.cxx

@@ -44,6 +44,9 @@ using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
 using TestPreset = cmCMakePresetsGraph::TestPreset;
 using PackagePreset = cmCMakePresetsGraph::PackagePreset;
+using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
+template <typename T>
+using PresetPair = cmCMakePresetsGraph::PresetPair<T>;
 using ExpandMacroResult = cmCMakePresetsGraphInternal::ExpandMacroResult;
 using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander;
 
@@ -324,6 +327,14 @@ bool ExpandMacros(const cmCMakePresetsGraph& graph,
   return true;
 }
 
+bool ExpandMacros(const cmCMakePresetsGraph& /*graph*/,
+                  const WorkflowPreset& /*preset*/,
+                  cm::optional<WorkflowPreset>& /*out*/,
+                  const std::vector<MacroExpander>& /*macroExpanders*/)
+{
+  return true;
+}
+
 template <class T>
 bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
                   cm::optional<T>& out)
@@ -579,6 +590,42 @@ ExpandMacroResult ExpandMacro(std::string& out,
 
   return ExpandMacroResult::Error;
 }
+
+template <typename T>
+ReadFileResult SetupWorkflowConfigurePreset(
+  const T& preset, const ConfigurePreset*& configurePreset)
+{
+  if (preset.ConfigurePreset != configurePreset->Name) {
+    return ReadFileResult::INVALID_WORKFLOW_STEPS;
+  }
+  return ReadFileResult::READ_OK;
+}
+
+template <>
+ReadFileResult SetupWorkflowConfigurePreset<ConfigurePreset>(
+  const ConfigurePreset& preset, const ConfigurePreset*& configurePreset)
+{
+  configurePreset = &preset;
+  return ReadFileResult::READ_OK;
+}
+
+template <typename T>
+ReadFileResult TryReachPresetFromWorkflow(
+  const WorkflowPreset& origin,
+  const std::map<std::string, PresetPair<T>>& presets, const std::string& name,
+  const ConfigurePreset*& configurePreset)
+{
+  auto it = presets.find(name);
+  if (it == presets.end()) {
+    return ReadFileResult::INVALID_WORKFLOW_STEPS;
+  }
+  if (!origin.OriginFile->ReachableFiles.count(
+        it->second.Unexpanded.OriginFile)) {
+    return ReadFileResult::WORKFLOW_STEP_UNREACHABLE_FROM_FILE;
+  }
+  return SetupWorkflowConfigurePreset<T>(it->second.Unexpanded,
+                                         configurePreset);
+}
 }
 
 bool cmCMakePresetsGraphInternal::EqualsCondition::Evaluate(
@@ -929,6 +976,19 @@ cmCMakePresetsGraph::PackagePreset::VisitPresetAfterInherit(int /* version */)
   return ReadFileResult::READ_OK;
 }
 
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::WorkflowPreset::VisitPresetInherit(
+  const cmCMakePresetsGraph::Preset& /*parentPreset*/)
+{
+  return ReadFileResult::READ_OK;
+}
+
+cmCMakePresetsGraph::ReadFileResult
+cmCMakePresetsGraph::WorkflowPreset::VisitPresetAfterInherit(int /* version */)
+{
+  return ReadFileResult::READ_OK;
+}
+
 std::string cmCMakePresetsGraph::GetFilename(const std::string& sourceDir)
 {
   return cmStrCat(sourceDir, "/CMakePresets.json");
@@ -992,6 +1052,7 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
   CHECK_OK(ComputePresetInheritance(this->BuildPresets, *this));
   CHECK_OK(ComputePresetInheritance(this->TestPresets, *this));
   CHECK_OK(ComputePresetInheritance(this->PackagePresets, *this));
+  CHECK_OK(ComputePresetInheritance(this->WorkflowPresets, *this));
 
   for (auto& it : this->ConfigurePresets) {
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
@@ -1071,6 +1132,55 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
     }
   }
 
+  for (auto& it : this->WorkflowPresets) {
+    using Type = WorkflowPreset::WorkflowStep::Type;
+
+    const ConfigurePreset* configurePreset = nullptr;
+    for (auto const& step : it.second.Unexpanded.Steps) {
+      if (configurePreset == nullptr && step.PresetType != Type::Configure) {
+        return ReadFileResult::INVALID_WORKFLOW_STEPS;
+      }
+      if (configurePreset != nullptr && step.PresetType == Type::Configure) {
+        return ReadFileResult::INVALID_WORKFLOW_STEPS;
+      }
+
+      ReadFileResult result;
+      switch (step.PresetType) {
+        case Type::Configure:
+          result = TryReachPresetFromWorkflow(
+            it.second.Unexpanded, this->ConfigurePresets, step.PresetName,
+            configurePreset);
+          break;
+        case Type::Build:
+          result = TryReachPresetFromWorkflow(
+            it.second.Unexpanded, this->BuildPresets, step.PresetName,
+            configurePreset);
+          break;
+        case Type::Test:
+          result =
+            TryReachPresetFromWorkflow(it.second.Unexpanded, this->TestPresets,
+                                       step.PresetName, configurePreset);
+          break;
+        case Type::Package:
+          result = TryReachPresetFromWorkflow(
+            it.second.Unexpanded, this->PackagePresets, step.PresetName,
+            configurePreset);
+          break;
+      }
+      if (result != ReadFileResult::READ_OK) {
+        return result;
+      }
+    }
+
+    if (configurePreset == nullptr) {
+      return ReadFileResult::INVALID_WORKFLOW_STEPS;
+    }
+
+    if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
+      return ReadFileResult::INVALID_MACRO_EXPANSION;
+    }
+  }
+
   return ReadFileResult::READ_OK;
 }
 
@@ -1116,6 +1226,8 @@ const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
              "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:
@@ -1137,6 +1249,10 @@ const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
     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";
   }
 
   return "Unknown error";
@@ -1148,11 +1264,13 @@ void cmCMakePresetsGraph::ClearPresets()
   this->BuildPresets.clear();
   this->TestPresets.clear();
   this->PackagePresets.clear();
+  this->WorkflowPresets.clear();
 
   this->ConfigurePresetOrder.clear();
   this->BuildPresetOrder.clear();
   this->TestPresetOrder.clear();
   this->PackagePresetOrder.clear();
+  this->WorkflowPresetOrder.clear();
 
   this->Files.clear();
 }
@@ -1291,6 +1409,26 @@ void cmCMakePresetsGraph::PrintPackagePresetList(
   }
 }
 
+void cmCMakePresetsGraph::PrintWorkflowPresetList(
+  PrintPrecedingNewline* newline) const
+{
+  std::vector<const cmCMakePresetsGraph::Preset*> presets;
+  for (auto const& p : this->WorkflowPresetOrder) {
+    auto const& preset = this->WorkflowPresets.at(p);
+    if (!preset.Unexpanded.Hidden && preset.Expanded &&
+        preset.Expanded->ConditionResult) {
+      presets.push_back(
+        static_cast<const cmCMakePresetsGraph::Preset*>(&preset.Unexpanded));
+    }
+  }
+
+  if (!presets.empty()) {
+    printPrecedingNewline(newline);
+    std::cout << "Available workflow presets:\n\n";
+    cmCMakePresetsGraph::PrintPresets(presets);
+  }
+}
+
 void cmCMakePresetsGraph::PrintAllPresets() const
 {
   PrintPrecedingNewline newline = PrintPrecedingNewline::False;
@@ -1298,4 +1436,5 @@ void cmCMakePresetsGraph::PrintAllPresets() const
   this->PrintBuildPresetList(&newline);
   this->PrintTestPresetList(&newline);
   this->PrintPackagePresetList(&newline);
+  this->PrintWorkflowPresetList(&newline);
 }

+ 44 - 1
Source/cmCMakePresetsGraph.h

@@ -42,6 +42,7 @@ public:
     INVALID_MACRO_EXPANSION,
     BUILD_TEST_PRESETS_UNSUPPORTED,
     PACKAGE_PRESETS_UNSUPPORTED,
+    WORKFLOW_PRESETS_UNSUPPORTED,
     INCLUDE_UNSUPPORTED,
     INVALID_INCLUDE,
     INVALID_CONFIGURE_PRESET,
@@ -51,6 +52,8 @@ public:
     TOOLCHAIN_FILE_UNSUPPORTED,
     CYCLIC_INCLUDE,
     TEST_OUTPUT_TRUNCATION_UNSUPPORTED,
+    INVALID_WORKFLOW_STEPS,
+    WORKFLOW_STEP_UNREACHABLE_FROM_FILE,
   };
 
   std::string errors;
@@ -97,7 +100,7 @@ public:
 
     std::string Name;
     std::vector<std::string> Inherits;
-    bool Hidden;
+    bool Hidden = false;
     File* OriginFile;
     std::string DisplayName;
     std::string Description;
@@ -363,6 +366,43 @@ public:
     ReadFileResult VisitPresetAfterInherit(int /* version */) override;
   };
 
+  class WorkflowPreset : public Preset
+  {
+  public:
+    WorkflowPreset() = default;
+    WorkflowPreset(WorkflowPreset&& /*other*/) = default;
+    WorkflowPreset(const WorkflowPreset& /*other*/) = default;
+    WorkflowPreset& operator=(const WorkflowPreset& /*other*/) = default;
+    ~WorkflowPreset() override = default;
+#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+    WorkflowPreset& operator=(WorkflowPreset&& /*other*/) = default;
+#else
+    // The move assignment operators for several STL classes did not become
+    // noexcept until C++17, which causes some tools to warn about this move
+    // assignment operator throwing an exception when it shouldn't.
+    WorkflowPreset& operator=(WorkflowPreset&& /*other*/) = delete;
+#endif
+
+    class WorkflowStep
+    {
+    public:
+      enum class Type
+      {
+        Configure,
+        Build,
+        Test,
+        Package,
+      };
+      Type PresetType;
+      std::string PresetName;
+    };
+
+    std::vector<WorkflowStep> Steps;
+
+    ReadFileResult VisitPresetInherit(const Preset& parent) override;
+    ReadFileResult VisitPresetAfterInherit(int /* version */) override;
+  };
+
   template <class T>
   class PresetPair
   {
@@ -375,11 +415,13 @@ public:
   std::map<std::string, PresetPair<BuildPreset>> BuildPresets;
   std::map<std::string, PresetPair<TestPreset>> TestPresets;
   std::map<std::string, PresetPair<PackagePreset>> PackagePresets;
+  std::map<std::string, PresetPair<WorkflowPreset>> WorkflowPresets;
 
   std::vector<std::string> ConfigurePresetOrder;
   std::vector<std::string> BuildPresetOrder;
   std::vector<std::string> TestPresetOrder;
   std::vector<std::string> PackagePresetOrder;
+  std::vector<std::string> WorkflowPresetOrder;
 
   std::string SourceDir;
   std::vector<std::unique_ptr<File>> Files;
@@ -442,6 +484,7 @@ public:
   void PrintPackagePresetList(
     const std::function<bool(const PackagePreset&)>& filter,
     PrintPrecedingNewline* newline = nullptr) const;
+  void PrintWorkflowPresetList(PrintPrecedingNewline* newline = nullptr) const;
   void PrintAllPresets() const;
 
 private:

+ 4 - 0
Source/cmCMakePresetsGraphInternal.h

@@ -151,6 +151,10 @@ cmCMakePresetsGraph::ReadFileResult PackagePresetsHelper(
   std::vector<cmCMakePresetsGraph::PackagePreset>& out,
   const Json::Value* value);
 
+cmCMakePresetsGraph::ReadFileResult WorkflowPresetsHelper(
+  std::vector<cmCMakePresetsGraph::WorkflowPreset>& out,
+  const Json::Value* value);
+
 cmJSONHelper<std::nullptr_t, cmCMakePresetsGraph::ReadFileResult> VendorHelper(
   cmCMakePresetsGraph::ReadFileResult error);
 

+ 33 - 4
Source/cmCMakePresetsGraphReadJSON.cxx

@@ -30,6 +30,8 @@ using CacheVariable = cmCMakePresetsGraph::CacheVariable;
 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
 using TestPreset = cmCMakePresetsGraph::TestPreset;
+using PackagePreset = cmCMakePresetsGraph::PackagePreset;
+using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
 using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
 using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
 
@@ -46,10 +48,11 @@ struct CMakeVersion
 struct RootPresets
 {
   CMakeVersion CMakeMinimumRequired;
-  std::vector<cmCMakePresetsGraph::ConfigurePreset> ConfigurePresets;
-  std::vector<cmCMakePresetsGraph::BuildPreset> BuildPresets;
-  std::vector<cmCMakePresetsGraph::TestPreset> TestPresets;
-  std::vector<cmCMakePresetsGraph::PackagePreset> PackagePresets;
+  std::vector<ConfigurePreset> ConfigurePresets;
+  std::vector<BuildPreset> BuildPresets;
+  std::vector<TestPreset> TestPresets;
+  std::vector<PackagePreset> PackagePresets;
+  std::vector<WorkflowPreset> WorkflowPresets;
   std::vector<std::string> Include;
 };
 
@@ -284,6 +287,8 @@ auto const RootPresetsHelper =
           cmCMakePresetsGraphInternal::TestPresetsHelper, false)
     .Bind("packagePresets"_s, &RootPresets::PackagePresets,
           cmCMakePresetsGraphInternal::PackagePresetsHelper, false)
+    .Bind("workflowPresets"_s, &RootPresets::WorkflowPresets,
+          cmCMakePresetsGraphInternal::WorkflowPresetsHelper, false)
     .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
           CMakeVersionHelper, false)
     .Bind("include"_s, &RootPresets::Include, IncludeVectorHelper, false)
@@ -466,6 +471,11 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
     return ReadFileResult::PACKAGE_PRESETS_UNSUPPORTED;
   }
 
+  // Support for workflow presets added in version 6.
+  if (v < 6 && root.isMember("workflowPresets")) {
+    return ReadFileResult::WORKFLOW_PRESETS_UNSUPPORTED;
+  }
+
   // Support for include added in version 4.
   if (v < 4 && root.isMember("include")) {
     return ReadFileResult::INCLUDE_UNSUPPORTED;
@@ -600,6 +610,25 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
     this->PackagePresetOrder.push_back(preset.Name);
   }
 
+  for (auto& preset : presets.WorkflowPresets) {
+    preset.OriginFile = file;
+    if (preset.Name.empty()) {
+      return ReadFileResult::INVALID_PRESET;
+    }
+
+    PresetPair<WorkflowPreset> presetPair;
+    presetPair.Unexpanded = preset;
+    presetPair.Expanded = cm::nullopt;
+    if (!this->WorkflowPresets.emplace(preset.Name, presetPair).second) {
+      return ReadFileResult::DUPLICATE_PRESETS;
+    }
+
+    // Support for conditions added in version 3, but this requires version 6
+    // already, so no action needed.
+
+    this->WorkflowPresetOrder.push_back(preset.Name);
+  }
+
   auto const includeFile = [this, &inProgressFiles, file](
                              const std::string& include, RootType rootType2,
                              ReadReason readReason2,

+ 95 - 0
Source/cmCMakePresetsGraphReadJSONWorkflowPresets.cxx

@@ -0,0 +1,95 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include <cstddef>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <cmext/string_view>
+
+#include <cm3p/json/value.h>
+
+#include "cmCMakePresetsGraph.h"
+#include "cmCMakePresetsGraphInternal.h"
+#include "cmJSONHelpers.h"
+
+namespace {
+using ReadFileResult = cmCMakePresetsGraph::ReadFileResult;
+using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
+
+ReadFileResult WorkflowStepTypeHelper(WorkflowPreset::WorkflowStep::Type& out,
+                                      const Json::Value* value)
+{
+  if (!value) {
+    return ReadFileResult::INVALID_PRESET;
+  }
+
+  if (!value->isString()) {
+    return ReadFileResult::INVALID_PRESET;
+  }
+
+  if (value->asString() == "configure") {
+    out = WorkflowPreset::WorkflowStep::Type::Configure;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "build") {
+    out = WorkflowPreset::WorkflowStep::Type::Build;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "test") {
+    out = WorkflowPreset::WorkflowStep::Type::Test;
+    return ReadFileResult::READ_OK;
+  }
+
+  if (value->asString() == "package") {
+    out = WorkflowPreset::WorkflowStep::Type::Package;
+    return ReadFileResult::READ_OK;
+  }
+
+  return ReadFileResult::INVALID_PRESET;
+}
+
+auto const WorkflowStepHelper =
+  cmJSONHelperBuilder<ReadFileResult>::Object<WorkflowPreset::WorkflowStep>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, 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);
+
+auto const WorkflowPresetHelper =
+  cmJSONHelperBuilder<ReadFileResult>::Object<WorkflowPreset>(
+    ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false)
+    .Bind("name"_s, &WorkflowPreset::Name,
+          cmCMakePresetsGraphInternal::PresetStringHelper)
+    .Bind<std::nullptr_t>("vendor"_s, nullptr,
+                          cmCMakePresetsGraphInternal::VendorHelper(
+                            ReadFileResult::INVALID_PRESET),
+                          false)
+    .Bind("displayName"_s, &WorkflowPreset::DisplayName,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("description"_s, &WorkflowPreset::Description,
+          cmCMakePresetsGraphInternal::PresetStringHelper, false)
+    .Bind("steps"_s, &WorkflowPreset::Steps, WorkflowStepsHelper);
+}
+
+namespace cmCMakePresetsGraphInternal {
+cmCMakePresetsGraph::ReadFileResult WorkflowPresetsHelper(
+  std::vector<cmCMakePresetsGraph::WorkflowPreset>& out,
+  const Json::Value* value)
+{
+  static auto const helper =
+    cmJSONHelperBuilder<ReadFileResult>::Vector<WorkflowPreset>(
+      ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS,
+      WorkflowPresetHelper);
+
+  return helper(out, value);
+}
+}

+ 209 - 0
Source/cmake.cxx

@@ -23,6 +23,10 @@
 #include <cmext/algorithm>
 #include <cmext/string_view>
 
+#if !defined(CMAKE_BOOTSTRAP) && !defined(_WIN32)
+#  include <unistd.h>
+#endif
+
 #include "cmsys/FStream.hxx"
 #include "cmsys/Glob.hxx"
 #include "cmsys/RegularExpression.hxx"
@@ -56,6 +60,7 @@
 #include "cmSystemTools.h"
 #include "cmTarget.h"
 #include "cmTargetLinkLibraryType.h"
+#include "cmUVProcessChain.h"
 #include "cmUtils.hxx"
 #include "cmVersionConfig.h"
 #include "cmWorkingDirectory.h"
@@ -3662,6 +3667,210 @@ bool cmake::Open(const std::string& dir, bool dryRun)
   return gen->Open(dir, *cachedProjectName, dryRun);
 }
 
+#if !defined(CMAKE_BOOTSTRAP)
+template <typename T>
+const T* cmake::FindPresetForWorkflow(
+  cm::static_string_view type,
+  const std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
+  const cmCMakePresetsGraph::WorkflowPreset::WorkflowStep& step)
+{
+  auto it = presets.find(step.PresetName);
+  if (it == presets.end()) {
+    cmSystemTools::Error(cmStrCat("No such ", type, " preset in ",
+                                  this->GetHomeDirectory(), ": \"",
+                                  step.PresetName, '"'));
+    return nullptr;
+  }
+
+  if (it->second.Unexpanded.Hidden) {
+    cmSystemTools::Error(cmStrCat("Cannot use hidden ", type, " preset in ",
+                                  this->GetHomeDirectory(), ": \"",
+                                  step.PresetName, '"'));
+    return nullptr;
+  }
+
+  if (!it->second.Expanded) {
+    cmSystemTools::Error(cmStrCat("Could not evaluate ", type, " preset \"",
+                                  step.PresetName,
+                                  "\": Invalid macro expansion"));
+    return nullptr;
+  }
+
+  if (!it->second.Expanded->ConditionResult) {
+    cmSystemTools::Error(cmStrCat("Cannot use disabled ", type, " preset in ",
+                                  this->GetHomeDirectory(), ": \"",
+                                  step.PresetName, '"'));
+    return nullptr;
+  }
+
+  return &*it->second.Expanded;
+}
+
+std::function<int()> cmake::BuildWorkflowStep(
+  const std::vector<std::string>& args)
+{
+  cmUVProcessChainBuilder builder;
+  builder
+    .AddCommand(args)
+#  ifdef _WIN32
+    .SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT, _fileno(stdout))
+    .SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR, _fileno(stderr));
+#  else
+    .SetExternalStream(cmUVProcessChainBuilder::Stream_OUTPUT, STDOUT_FILENO)
+    .SetExternalStream(cmUVProcessChainBuilder::Stream_ERROR, STDERR_FILENO);
+#  endif
+  return [builder]() -> int {
+    auto chain = builder.Start();
+    chain.Wait();
+    return static_cast<int>(chain.GetStatus().front()->ExitStatus);
+  };
+}
+#endif
+
+int cmake::Workflow(const std::string& presetName, bool listPresets)
+{
+#ifndef CMAKE_BOOTSTRAP
+  this->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+  this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+
+  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)));
+    return 1;
+  }
+
+  if (listPresets) {
+    settingsFile.PrintWorkflowPresetList();
+    return 0;
+  }
+
+  auto presetPair = settingsFile.WorkflowPresets.find(presetName);
+  if (presetPair == settingsFile.WorkflowPresets.end()) {
+    cmSystemTools::Error(cmStrCat("No such workflow preset in ",
+                                  this->GetHomeDirectory(), ": \"", presetName,
+                                  '"'));
+    settingsFile.PrintWorkflowPresetList();
+    return 1;
+  }
+
+  if (presetPair->second.Unexpanded.Hidden) {
+    cmSystemTools::Error(cmStrCat("Cannot use hidden workflow preset in ",
+                                  this->GetHomeDirectory(), ": \"", presetName,
+                                  '"'));
+    settingsFile.PrintWorkflowPresetList();
+    return 1;
+  }
+
+  auto const& expandedPreset = presetPair->second.Expanded;
+  if (!expandedPreset) {
+    cmSystemTools::Error(cmStrCat("Could not evaluate workflow preset \"",
+                                  presetName, "\": Invalid macro expansion"));
+    settingsFile.PrintWorkflowPresetList();
+    return 1;
+  }
+
+  if (!expandedPreset->ConditionResult) {
+    cmSystemTools::Error(cmStrCat("Cannot use disabled workflow preset in ",
+                                  this->GetHomeDirectory(), ": \"", presetName,
+                                  '"'));
+    settingsFile.PrintWorkflowPresetList();
+    return 1;
+  }
+
+  struct CalculatedStep
+  {
+    int StepNumber;
+    cm::static_string_view Type;
+    std::string Name;
+    std::function<int()> Action;
+
+    CalculatedStep(int stepNumber, cm::static_string_view type,
+                   std::string name, std::function<int()> action)
+      : StepNumber(stepNumber)
+      , Type(type)
+      , Name(std::move(name))
+      , Action(std::move(action))
+    {
+    }
+  };
+
+  std::vector<CalculatedStep> steps;
+  steps.reserve(expandedPreset->Steps.size());
+  int stepNumber = 1;
+  for (auto const& step : expandedPreset->Steps) {
+    switch (step.PresetType) {
+      case cmCMakePresetsGraph::WorkflowPreset::WorkflowStep::Type::
+        Configure: {
+        auto const* configurePreset = this->FindPresetForWorkflow(
+          "configure"_s, settingsFile.ConfigurePresets, step);
+        if (!configurePreset) {
+          return 1;
+        }
+        steps.emplace_back(
+          stepNumber, "configure"_s, step.PresetName,
+          this->BuildWorkflowStep({ cmSystemTools::GetCMakeCommand(),
+                                    "--preset", step.PresetName }));
+      } break;
+      case cmCMakePresetsGraph::WorkflowPreset::WorkflowStep::Type::Build: {
+        auto const* buildPreset = this->FindPresetForWorkflow(
+          "build"_s, settingsFile.BuildPresets, step);
+        if (!buildPreset) {
+          return 1;
+        }
+        steps.emplace_back(
+          stepNumber, "build"_s, step.PresetName,
+          this->BuildWorkflowStep({ cmSystemTools::GetCMakeCommand(),
+                                    "--build", "--preset", step.PresetName }));
+      } break;
+      case cmCMakePresetsGraph::WorkflowPreset::WorkflowStep::Type::Test: {
+        auto const* testPreset = this->FindPresetForWorkflow(
+          "test"_s, settingsFile.TestPresets, step);
+        if (!testPreset) {
+          return 1;
+        }
+        steps.emplace_back(
+          stepNumber, "test"_s, step.PresetName,
+          this->BuildWorkflowStep({ cmSystemTools::GetCTestCommand(),
+                                    "--preset", step.PresetName }));
+      } break;
+      case cmCMakePresetsGraph::WorkflowPreset::WorkflowStep::Type::Package: {
+        auto const* packagePreset = this->FindPresetForWorkflow(
+          "package"_s, settingsFile.PackagePresets, step);
+        if (!packagePreset) {
+          return 1;
+        }
+        steps.emplace_back(
+          stepNumber, "package"_s, step.PresetName,
+          this->BuildWorkflowStep({ cmSystemTools::GetCPackCommand(),
+                                    "--preset", step.PresetName }));
+      } break;
+    }
+    stepNumber++;
+  }
+
+  int stepResult;
+  bool first = true;
+  for (auto const& step : steps) {
+    if (!first) {
+      std::cout << "\n";
+    }
+    std::cout << "Executing workflow step " << step.StepNumber << " of "
+              << steps.size() << ": " << step.Type << " preset \"" << step.Name
+              << "\"\n\n"
+              << std::flush;
+    if ((stepResult = step.Action()) != 0) {
+      return stepResult;
+    }
+    first = false;
+  }
+#endif
+
+  return 0;
+}
+
 void cmake::WatchUnusedCli(const std::string& var)
 {
 #ifndef CMAKE_BOOTSTRAP

+ 14 - 0
Source/cmake.h

@@ -16,6 +16,7 @@
 #include <vector>
 
 #include <cm/string_view>
+#include <cmext/string_view>
 
 #include "cmGeneratedFileStream.h"
 #include "cmInstalledFile.h"
@@ -600,6 +601,9 @@ public:
   //! run the --open option
   bool Open(const std::string& dir, bool dryRun);
 
+  //! run the --workflow option
+  int Workflow(const std::string& presetName, bool listPresets);
+
   void UnwatchUnusedCli(const std::string& var);
   void WatchUnusedCli(const std::string& var);
 
@@ -739,6 +743,16 @@ private:
   void AppendGlobalGeneratorsDocumentation(std::vector<cmDocumentationEntry>&);
   void AppendExtraGeneratorsDocumentation(std::vector<cmDocumentationEntry>&);
 
+#if !defined(CMAKE_BOOTSTRAP)
+  template <typename T>
+  const T* FindPresetForWorkflow(
+    cm::static_string_view type,
+    const std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
+    const cmCMakePresetsGraph::WorkflowPreset::WorkflowStep& step);
+
+  std::function<int()> BuildWorkflowStep(const std::vector<std::string>& args);
+#endif
+
 #if !defined(CMAKE_BOOTSTRAP)
   std::unique_ptr<cmMakefileProfilingData> ProfilingOutput;
 #endif

+ 65 - 0
Source/cmakemain.cxx

@@ -911,6 +911,68 @@ int do_install(int ac, char const* const* av)
 #endif
 }
 
+int do_workflow(int ac, char const* const* av)
+{
+#ifdef CMAKE_BOOTSTRAP
+  std::cerr << "This cmake does not support --workflow\n";
+  return -1;
+#else
+  std::string presetName;
+  bool listPresets = false;
+
+  using CommandArgument =
+    cmCommandLineArgument<bool(std::string const& value)>;
+
+  std::vector<CommandArgument> arguments = {
+    CommandArgument{ "--preset", CommandArgument::Values::One,
+                     CommandArgument::setToValue(presetName) },
+    CommandArgument{ "--list-presets", CommandArgument::Values::Zero,
+                     CommandArgument::setToTrue(listPresets) }
+  };
+
+  std::vector<std::string> inputArgs;
+
+  inputArgs.reserve(ac - 2);
+  cm::append(inputArgs, av + 2, av + ac);
+
+  decltype(inputArgs.size()) i = 0;
+  for (; i < inputArgs.size(); ++i) {
+    std::string const& arg = inputArgs[i];
+    bool matched = false;
+    bool parsed = false;
+    for (auto const& m : arguments) {
+      matched = m.matches(arg);
+      if (matched) {
+        parsed = m.parse(arg, i, inputArgs);
+        break;
+      }
+    }
+    if (!(matched && parsed)) {
+      if (!matched) {
+        std::cerr << "Unknown argument " << arg << std::endl;
+      }
+      break;
+    }
+  }
+
+  if (presetName.empty() && !listPresets) {
+    std::cerr << "TODO: Usage\n";
+    return 1;
+  }
+
+  cmake cm(cmake::RoleInternal, cmState::Project);
+  cmSystemTools::SetMessageCallback(
+    [&cm](const std::string& msg, const cmMessageMetadata& md) {
+      cmakemainMessageCallback(msg, md, &cm);
+    });
+  cm.SetProgressCallback([&cm](const std::string& msg, float prog) {
+    cmakemainProgressCallback(msg, prog, &cm);
+  });
+
+  return cm.Workflow(presetName, listPresets);
+#endif
+}
+
 int do_open(int ac, char const* const* av)
 {
 #ifdef CMAKE_BOOTSTRAP
@@ -980,6 +1042,9 @@ int main(int ac, char const* const* av)
     if (strcmp(av[1], "--open") == 0) {
       return do_open(ac, av);
     }
+    if (strcmp(av[1], "--workflow") == 0) {
+      return do_workflow(ac, av);
+    }
     if (strcmp(av[1], "-E") == 0) {
       return do_command(ac, av, std::move(consoleBuf));
     }

+ 4 - 0
Tests/RunCMake/CMakeLists.txt

@@ -1008,6 +1008,10 @@ add_RunCMake_test(CMakePresetsPackage
   -DPython_EXECUTABLE=${Python_EXECUTABLE}
   -DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA}
   )
+add_RunCMake_test(CMakePresetsWorkflow
+  -DPython_EXECUTABLE=${Python_EXECUTABLE}
+  -DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA}
+  )
 
 add_RunCMake_test(VerifyHeaderSets)
 

+ 4 - 0
Tests/RunCMake/CMakePresets/DocumentationExampleListAllPresets-stdout.txt

@@ -15,4 +15,8 @@ Available test presets:
 
 Available package presets:
 
+  "default"
+
+Available workflow presets:
+
   "default"$

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-result.txt

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

+ 4 - 0
Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stderr.txt

@@ -0,0 +1,4 @@
+^Errors while running CTest
+Output from these tests are in: [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode/build/Testing/Temporary/LastTest\.log
+Use "--rerun-failed --output-on-failure" to re-run the failed cases verbosely\.$

+ 17 - 0
Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stdout.txt

@@ -0,0 +1,17 @@
+^Executing workflow step 1 of 4: configure preset "default"
+
+.*Testing the configure step at [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode/build.*
+
+Executing workflow step 2 of 4: build preset "default"
+
+.*Testing the build step at [^
+]*[\\/]Tests[\\/]RunCMake[\\/]CMakePresetsWorkflow[\\/]BadExitCode[\\/]build.*
+
+Executing workflow step 3 of 4: test preset "default"
+
+.*Testing the test step at [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode/build.*
+
+The following tests FAILED:
+.* +1 - EchoTest \(Failed\)$

+ 8 - 0
Tests/RunCMake/CMakePresetsWorkflow/BadExitCode.cmake

@@ -0,0 +1,8 @@
+message(STATUS "Testing the configure step at ${CMAKE_BINARY_DIR}")
+
+add_custom_target(echo_test ALL COMMAND ${CMAKE_COMMAND} -E echo "Testing the build step at ${CMAKE_BINARY_DIR}")
+
+enable_testing()
+add_test(NAME EchoTest COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/BadExitCodeTest.cmake")
+
+include(CPack)

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/BadExitCodeTest.cmake

@@ -0,0 +1 @@
+message(FATAL_ERROR "  Testing the test step at ${CMAKE_BINARY_DIR}")

+ 3 - 0
Tests/RunCMake/CMakePresetsWorkflow/CMakeLists.txt.in

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.19)
+project("@CASE_NAME@" NONE)
+include("@CASE_SOURCE_DIR@/@[email protected]")

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch: Invalid workflow steps$

+ 32 - 0
Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch.json.in

@@ -0,0 +1,32 @@
+{
+  "version": 6,
+  "configurePresets": [
+    {
+      "name": "default"
+    },
+    {
+      "name": "mismatch"
+    }
+  ],
+  "buildPresets": [
+    {
+      "name": "mismatch",
+      "configurePreset": "mismatch"
+    }
+  ],
+  "workflowPresets": [
+    {
+      "name": "default",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        },
+        {
+          "type": "build",
+          "name": "mismatch"
+        }
+      ]
+    }
+  ]
+}

+ 0 - 0
Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-result.txt


+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure: Invalid workflow steps$

+ 27 - 0
Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure.json.in

@@ -0,0 +1,27 @@
+{
+  "version": 6,
+  "configurePresets": [
+    {
+      "name": "default",
+      "binaryDir": "${sourceDir}/build",
+      "generator": "@RunCMake_GENERATOR@"
+    }
+  ],
+  "buildPresets": [
+    {
+      "name": "default",
+      "configurePreset": "default"
+    }
+  ],
+  "workflowPresets": [
+    {
+      "name": "default",
+      "steps": [
+        {
+          "type": "build",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 19 - 0
Tests/RunCMake/CMakePresetsWorkflow/Good-stdout.txt

@@ -0,0 +1,19 @@
+^Executing workflow step 1 of 4: configure preset "default"
+
+.*Testing the configure step at [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/Good/build.*
+
+Executing workflow step 2 of 4: build preset "default"
+
+.*Testing the build step at [^
+]*[\\/]Tests[\\/]RunCMake[\\/]CMakePresetsWorkflow[\\/]Good[\\/]build.*
+
+Executing workflow step 3 of 4: test preset "default"
+
+.*Testing the test step at [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/Good/build.*
+
+Executing workflow step 4 of 4: package preset "default"
+
+.*Testing the package step at [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/Good/build.*

+ 8 - 0
Tests/RunCMake/CMakePresetsWorkflow/Good.cmake

@@ -0,0 +1,8 @@
+message(STATUS "Testing the configure step at ${CMAKE_BINARY_DIR}")
+
+add_custom_target(echo_test ALL COMMAND ${CMAKE_COMMAND} -E echo "Testing the build step at ${CMAKE_BINARY_DIR}")
+
+enable_testing()
+add_test(NAME EchoTest COMMAND ${CMAKE_COMMAND} -E echo "Testing the test step at ${CMAKE_BINARY_DIR}")
+
+include(CPack)

+ 87 - 0
Tests/RunCMake/CMakePresetsWorkflow/Good.json.in

@@ -0,0 +1,87 @@
+{
+  "version": 6,
+  "configurePresets": [
+    {
+      "name": "default",
+      "binaryDir": "${sourceDir}/build",
+      "generator": "@RunCMake_GENERATOR@"
+    }
+  ],
+  "buildPresets": [
+    {
+      "name": "default",
+      "configurePreset": "default",
+      "configuration": "Debug"
+    }
+  ],
+  "testPresets": [
+    {
+      "name": "default",
+      "configurePreset": "default",
+      "output": {
+        "verbosity": "verbose"
+      },
+      "configuration": "Debug"
+    }
+  ],
+  "packagePresets": [
+    {
+      "name": "default",
+      "configurePreset": "default",
+      "generators": [
+        "External"
+      ],
+      "variables": {
+        "CPACK_EXTERNAL_PACKAGE_SCRIPT": "${sourceDir}/cpack_staging.cmake"
+      },
+      "configurations": ["Debug"]
+    }
+  ],
+  "workflowPresets": [
+    {
+      "name": "Good",
+      "displayName": "Good Workflow Preset",
+      "description": "This workflow preset works properly.",
+      "vendor": {},
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        },
+        {
+          "type": "build",
+          "name": "default"
+        },
+        {
+          "type": "test",
+          "name": "default"
+        },
+        {
+          "type": "package",
+          "name": "default"
+        }
+      ]
+    },
+    {
+      "name": "BadExitCode",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        },
+        {
+          "type": "build",
+          "name": "default"
+        },
+        {
+          "type": "test",
+          "name": "default"
+        },
+        {
+          "type": "package",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/GoodUser-stdout.txt

@@ -0,0 +1,2 @@
+-- Testing the configure step at [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/GoodUser/build

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/GoodUser.cmake

@@ -0,0 +1 @@
+message(STATUS "Testing the configure step at ${CMAKE_BINARY_DIR}")

+ 14 - 0
Tests/RunCMake/CMakePresetsWorkflow/GoodUser.json.in

@@ -0,0 +1,14 @@
+{
+  "version": 6,
+  "workflowPresets": [
+    {
+      "name": "GoodUser",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 4 - 0
Tests/RunCMake/CMakePresetsWorkflow/ListPresets-stdout.txt

@@ -0,0 +1,4 @@
+^Available workflow presets:
+
+  "default"
+  "with-description" - With Description$

+ 30 - 0
Tests/RunCMake/CMakePresetsWorkflow/ListPresets.json.in

@@ -0,0 +1,30 @@
+{
+  "version": 6,
+  "configurePresets": [
+    {
+      "name": "default"
+    }
+  ],
+  "workflowPresets": [
+    {
+      "name": "default",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        }
+      ]
+    },
+    {
+      "name": "with-description",
+      "displayName": "With Description",
+      "description": "This preset has a description.",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps: Invalid workflow steps$

+ 9 - 0
Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps.json.in

@@ -0,0 +1,9 @@
+{
+  "version": 6,
+  "workflowPresets": [
+    {
+      "name": "default",
+      "steps": []
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep: Invalid workflow steps$

+ 14 - 0
Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep.json.in

@@ -0,0 +1,14 @@
+{
+  "version": 6,
+  "workflowPresets": [
+    {
+      "name": "default",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 79 - 0
Tests/RunCMake/CMakePresetsWorkflow/RunCMakeTest.cmake

@@ -0,0 +1,79 @@
+include(RunCMake)
+
+# Presets do not support legacy VS generator name architecture suffix.
+if(RunCMake_GENERATOR MATCHES "^(Visual Studio [0-9]+ [0-9]+) ")
+  set(RunCMake_GENERATOR "${CMAKE_MATCH_1}")
+endif()
+
+function(run_cmake_workflow_presets name)
+  set(RunCMake_TEST_SOURCE_DIR "${RunCMake_BINARY_DIR}/${name}")
+  set(RunCMake_TEST_BINARY_DIR "${RunCMake_TEST_SOURCE_DIR}/build")
+  set(RunCMake_TEST_COMMAND_WORKING_DIRECTORY "${RunCMake_TEST_SOURCE_DIR}")
+
+  set(RunCMake_TEST_NO_CLEAN TRUE)
+
+  file(REMOVE_RECURSE "${RunCMake_TEST_SOURCE_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_SOURCE_DIR}")
+
+  set(CASE_NAME "${name}")
+  set(CASE_SOURCE_DIR "${RunCMake_SOURCE_DIR}")
+  configure_file("${RunCMake_SOURCE_DIR}/CMakeLists.txt.in" "${RunCMake_TEST_SOURCE_DIR}/CMakeLists.txt" @ONLY)
+
+  if(NOT CMakePresets_FILE)
+    set(CMakePresets_FILE "${RunCMake_SOURCE_DIR}/${name}.json.in")
+  endif()
+  if(EXISTS "${CMakePresets_FILE}")
+    configure_file("${CMakePresets_FILE}" "${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json" @ONLY)
+  endif()
+
+  if(NOT CMakeUserPresets_FILE)
+    set(CMakeUserPresets_FILE "${RunCMake_SOURCE_DIR}/${name}User.json.in")
+  endif()
+  if(EXISTS "${CMakeUserPresets_FILE}")
+    configure_file("${CMakeUserPresets_FILE}" "${RunCMake_TEST_SOURCE_DIR}/CMakeUserPresets.json" @ONLY)
+  endif()
+
+  foreach(ASSET ${CMakePresets_ASSETS})
+    configure_file("${RunCMake_SOURCE_DIR}/${ASSET}.in" "${RunCMake_TEST_SOURCE_DIR}/${ASSET}" @ONLY)
+  endforeach()
+
+  if(EXISTS "${RunCMake_SOURCE_DIR}/${name}-check.cmake")
+    set(RunCMake-check-file "${name}-check.cmake")
+  else()
+    set(RunCMake-check-file "check.cmake")
+  endif()
+
+  if(eq)
+    set(eq 0 PARENT_SCOPE)
+    set(preset_arg "--preset=${name}")
+  else()
+    set(eq 1 PARENT_SCOPE)
+    set(preset_arg "--preset" "${name}")
+  endif()
+  run_cmake_command("${name}" "${CMAKE_COMMAND}" "--workflow" ${preset_arg} ${ARGN})
+endfunction()
+
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 1)
+run_cmake_workflow_presets(UnsupportedVersion)
+set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
+run_cmake_workflow_presets(NoWorkflowSteps)
+run_cmake_workflow_presets(FirstStepNotConfigure)
+run_cmake_workflow_presets(SecondStepConfigure)
+run_cmake_workflow_presets(NonexistentStep)
+run_cmake_workflow_presets(UnreachableStep)
+run_cmake_workflow_presets(WorkflowStepHidden)
+run_cmake_workflow_presets(WorkflowStepDisabled)
+run_cmake_workflow_presets(WorkflowStepInvalidMacro)
+run_cmake_workflow_presets(ConfigureStepMismatch)
+
+set(CMakePresets_FILE "${RunCMake_SOURCE_DIR}/Good.json.in")
+set(CMakeUserPresets_FILE "${RunCMake_SOURCE_DIR}/GoodUser.json.in")
+set(CMakePresets_ASSETS cpack_staging.cmake)
+run_cmake_workflow_presets(Good)
+run_cmake_workflow_presets(GoodUser)
+run_cmake_workflow_presets(BadExitCode)
+unset(CMakePresets_FILE)
+unset(CMakeUserPresets_FILE)
+unset(CMakePresets_ASSETS)
+
+run_cmake_workflow_presets(ListPresets --list-presets)

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure: Invalid workflow steps$

+ 25 - 0
Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure.json.in

@@ -0,0 +1,25 @@
+{
+  "version": 6,
+  "configurePresets": [
+    {
+      "name": "default",
+      "binaryDir": "${sourceDir}/build",
+      "generator": "@RunCMake_GENERATOR@"
+    }
+  ],
+  "workflowPresets": [
+    {
+      "name": "default",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        },
+        {
+          "type": "configure",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep: Workflow step is unreachable from preset's file$

+ 14 - 0
Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep.json.in

@@ -0,0 +1,14 @@
+{
+  "version": 6,
+  "workflowPresets": [
+    {
+      "name": "default",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 8 - 0
Tests/RunCMake/CMakePresetsWorkflow/UnreachableStepUser.json.in

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

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-stderr.txt

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

+ 4 - 0
Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion.json.in

@@ -0,0 +1,4 @@
+{
+  "version": 5,
+  "workflowPresets": []
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Cannot use disabled configure preset in [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled: "default"$

+ 23 - 0
Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled.json.in

@@ -0,0 +1,23 @@
+{
+  "version": 6,
+  "configurePresets": [
+    {
+      "name": "default",
+      "condition": {
+        "type": "const",
+        "value": false
+      }
+    }
+  ],
+  "workflowPresets": [
+    {
+      "name": "WorkflowStepDisabled",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-result.txt

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

+ 2 - 0
Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-stderr.txt

@@ -0,0 +1,2 @@
+^CMake Error: Cannot use hidden configure preset in [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden: "default"$

+ 20 - 0
Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden.json.in

@@ -0,0 +1,20 @@
+{
+  "version": 6,
+  "configurePresets": [
+    {
+      "name": "default",
+      "hidden": true
+    }
+  ],
+  "workflowPresets": [
+    {
+      "name": "WorkflowStepHidden",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-result.txt

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

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-stderr.txt

@@ -0,0 +1 @@
+^CMake Error: Could not evaluate configure preset "default": Invalid macro expansion$

+ 20 - 0
Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro.json.in

@@ -0,0 +1,20 @@
+{
+  "version": 6,
+  "configurePresets": [
+    {
+      "name": "default",
+      "binaryDir": "$vendor{invalidMacro}"
+    }
+  ],
+  "workflowPresets": [
+    {
+      "name": "WorkflowStepInvalidMacro",
+      "steps": [
+        {
+          "type": "configure",
+          "name": "default"
+        }
+      ]
+    }
+  ]
+}

+ 3 - 0
Tests/RunCMake/CMakePresetsWorkflow/check.cmake

@@ -0,0 +1,3 @@
+set(CMakePresets_VALIDATE_SCRIPT_PATH "${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.py")
+include("${RunCMake_SOURCE_DIR}/../CMakePresets/validate_schema.cmake")
+include("${RunCMake_SOURCE_DIR}/../CMakePresets/check.cmake")

+ 1 - 0
Tests/RunCMake/CMakePresetsWorkflow/cpack_staging.cmake.in

@@ -0,0 +1 @@
+message(STATUS "Testing the package step at @RunCMake_TEST_BINARY_DIR@")