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.
   An optional array of `Package Preset`_ objects.
   This is allowed in preset files specifying version ``6`` or above.
   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
 Includes
 ^^^^^^^^
 ^^^^^^^^
 
 
@@ -137,8 +141,8 @@ that may contain the following fields:
   This identifier is used in the :ref:`cmake --preset <CMake Options>` option.
   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``
   There must not be two configure presets in the union of ``CMakePresets.json``
   and ``CMakeUserPresets.json`` in the same directory with the same name.
   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``
 ``hidden``
   An optional boolean specifying whether or not a preset should be 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.
   :ref:`cmake --build --preset <Build Tool Mode>` option.
   There must not be two build presets in the union of ``CMakePresets.json``
   There must not be two build presets in the union of ``CMakePresets.json``
   and ``CMakeUserPresets.json`` in the same directory with the same name.
   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``
 ``hidden``
   An optional boolean specifying whether or not a preset should be 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.
   This identifier is used in the :option:`ctest --preset` option.
   There must not be two test presets in the union of ``CMakePresets.json``
   There must not be two test presets in the union of ``CMakePresets.json``
   and ``CMakeUserPresets.json`` in the same directory with the same name.
   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``
 ``hidden``
   An optional boolean specifying whether or not a preset should be 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.
   This identifier is used in the :option:`cpack --preset` option.
   There must not be two package presets in the union of ``CMakePresets.json``
   There must not be two package presets in the union of ``CMakePresets.json``
   and ``CMakeUserPresets.json`` in the same directory with the same name.
   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``
 ``hidden``
   An optional boolean specifying whether or not a preset should be hidden.
   An optional boolean specifying whether or not a preset should be hidden.
@@ -977,6 +981,42 @@ fields:
 ``vendorName``
 ``vendorName``
   An optional string representing the vendor name.
   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
 Condition
 ^^^^^^^^^
 ^^^^^^^^^
 
 

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

@@ -30,6 +30,9 @@ Synopsis
  `Run the Find-Package Tool`_
  `Run the Find-Package Tool`_
   cmake --find-package [<options>]
   cmake --find-package [<options>]
 
 
+ `Run a Workflow Preset`_
+  cmake --workflow [<options>]
+
  `View Help`_
  `View Help`_
   cmake --help[-<topic>]
   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.
   This mode is not well-supported due to some technical limitations.
   It is kept for compatibility but should not be used in new projects.
   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
 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": {
   "vendor": {
     "example.com/ExampleIDE/1.0": {
     "example.com/ExampleIDE/1.0": {
       "autoFormat": false
       "autoFormat": false

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

@@ -85,6 +85,7 @@
         "buildPresets": { "$ref": "#/definitions/buildPresetsV4"},
         "buildPresets": { "$ref": "#/definitions/buildPresetsV4"},
         "testPresets": { "$ref": "#/definitions/testPresetsV5"},
         "testPresets": { "$ref": "#/definitions/testPresetsV5"},
         "packagePresets": { "$ref": "#/definitions/packagePresetsV6"},
         "packagePresets": { "$ref": "#/definitions/packagePresetsV6"},
+        "workflowPresets": { "$ref": "#/definitions/workflowPresetsV6" },
         "include": { "$ref": "#/definitions/include"}
         "include": { "$ref": "#/definitions/include"}
       },
       },
       "additionalProperties": false
       "additionalProperties": false
@@ -492,7 +493,7 @@
         "properties": {
         "properties": {
           "name": {
           "name": {
             "type": "string",
             "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
             "minLength": 1
           },
           },
           "hidden": {
           "hidden": {
@@ -744,7 +745,7 @@
         "properties": {
         "properties": {
           "name": {
           "name": {
             "type": "string",
             "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
             "minLength": 1
           },
           },
           "hidden": {
           "hidden": {
@@ -1153,7 +1154,7 @@
         "properties": {
         "properties": {
           "name": {
           "name": {
             "type": "string",
             "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
             "minLength": 1
           },
           },
           "hidden": {
           "hidden": {
@@ -1321,6 +1322,84 @@
         "additionalProperties": false
         "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": {
     "condition": {
       "anyOf": [
       "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
   cmCMakePresetsGraphReadJSONConfigurePresets.cxx
   cmCMakePresetsGraphReadJSONPackagePresets.cxx
   cmCMakePresetsGraphReadJSONPackagePresets.cxx
   cmCMakePresetsGraphReadJSONTestPresets.cxx
   cmCMakePresetsGraphReadJSONTestPresets.cxx
+  cmCMakePresetsGraphReadJSONWorkflowPresets.cxx
   cmCommandArgumentParserHelper.cxx
   cmCommandArgumentParserHelper.cxx
   cmCommonTargetGenerator.cxx
   cmCommonTargetGenerator.cxx
   cmCommonTargetGenerator.h
   cmCommonTargetGenerator.h

+ 139 - 0
Source/cmCMakePresetsGraph.cxx

@@ -44,6 +44,9 @@ using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
 using TestPreset = cmCMakePresetsGraph::TestPreset;
 using TestPreset = cmCMakePresetsGraph::TestPreset;
 using PackagePreset = cmCMakePresetsGraph::PackagePreset;
 using PackagePreset = cmCMakePresetsGraph::PackagePreset;
+using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
+template <typename T>
+using PresetPair = cmCMakePresetsGraph::PresetPair<T>;
 using ExpandMacroResult = cmCMakePresetsGraphInternal::ExpandMacroResult;
 using ExpandMacroResult = cmCMakePresetsGraphInternal::ExpandMacroResult;
 using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander;
 using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander;
 
 
@@ -324,6 +327,14 @@ bool ExpandMacros(const cmCMakePresetsGraph& graph,
   return true;
   return true;
 }
 }
 
 
+bool ExpandMacros(const cmCMakePresetsGraph& /*graph*/,
+                  const WorkflowPreset& /*preset*/,
+                  cm::optional<WorkflowPreset>& /*out*/,
+                  const std::vector<MacroExpander>& /*macroExpanders*/)
+{
+  return true;
+}
+
 template <class T>
 template <class T>
 bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
 bool ExpandMacros(const cmCMakePresetsGraph& graph, const T& preset,
                   cm::optional<T>& out)
                   cm::optional<T>& out)
@@ -579,6 +590,42 @@ ExpandMacroResult ExpandMacro(std::string& out,
 
 
   return ExpandMacroResult::Error;
   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(
 bool cmCMakePresetsGraphInternal::EqualsCondition::Evaluate(
@@ -929,6 +976,19 @@ cmCMakePresetsGraph::PackagePreset::VisitPresetAfterInherit(int /* version */)
   return ReadFileResult::READ_OK;
   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)
 std::string cmCMakePresetsGraph::GetFilename(const std::string& sourceDir)
 {
 {
   return cmStrCat(sourceDir, "/CMakePresets.json");
   return cmStrCat(sourceDir, "/CMakePresets.json");
@@ -992,6 +1052,7 @@ cmCMakePresetsGraph::ReadProjectPresetsInternal(bool allowNoFiles)
   CHECK_OK(ComputePresetInheritance(this->BuildPresets, *this));
   CHECK_OK(ComputePresetInheritance(this->BuildPresets, *this));
   CHECK_OK(ComputePresetInheritance(this->TestPresets, *this));
   CHECK_OK(ComputePresetInheritance(this->TestPresets, *this));
   CHECK_OK(ComputePresetInheritance(this->PackagePresets, *this));
   CHECK_OK(ComputePresetInheritance(this->PackagePresets, *this));
+  CHECK_OK(ComputePresetInheritance(this->WorkflowPresets, *this));
 
 
   for (auto& it : this->ConfigurePresets) {
   for (auto& it : this->ConfigurePresets) {
     if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) {
     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;
   return ReadFileResult::READ_OK;
 }
 }
 
 
@@ -1116,6 +1226,8 @@ const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
              "support.";
              "support.";
     case ReadFileResult::PACKAGE_PRESETS_UNSUPPORTED:
     case ReadFileResult::PACKAGE_PRESETS_UNSUPPORTED:
       return "File version must be 6 or higher for package preset support";
       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:
     case ReadFileResult::INCLUDE_UNSUPPORTED:
       return "File version must be 4 or higher for include support";
       return "File version must be 4 or higher for include support";
     case ReadFileResult::INVALID_INCLUDE:
     case ReadFileResult::INVALID_INCLUDE:
@@ -1137,6 +1249,10 @@ const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result)
     case ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED:
     case ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED:
       return "File version must be 5 or higher for testOutputTruncation "
       return "File version must be 5 or higher for testOutputTruncation "
              "preset support.";
              "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";
   return "Unknown error";
@@ -1148,11 +1264,13 @@ void cmCMakePresetsGraph::ClearPresets()
   this->BuildPresets.clear();
   this->BuildPresets.clear();
   this->TestPresets.clear();
   this->TestPresets.clear();
   this->PackagePresets.clear();
   this->PackagePresets.clear();
+  this->WorkflowPresets.clear();
 
 
   this->ConfigurePresetOrder.clear();
   this->ConfigurePresetOrder.clear();
   this->BuildPresetOrder.clear();
   this->BuildPresetOrder.clear();
   this->TestPresetOrder.clear();
   this->TestPresetOrder.clear();
   this->PackagePresetOrder.clear();
   this->PackagePresetOrder.clear();
+  this->WorkflowPresetOrder.clear();
 
 
   this->Files.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
 void cmCMakePresetsGraph::PrintAllPresets() const
 {
 {
   PrintPrecedingNewline newline = PrintPrecedingNewline::False;
   PrintPrecedingNewline newline = PrintPrecedingNewline::False;
@@ -1298,4 +1436,5 @@ void cmCMakePresetsGraph::PrintAllPresets() const
   this->PrintBuildPresetList(&newline);
   this->PrintBuildPresetList(&newline);
   this->PrintTestPresetList(&newline);
   this->PrintTestPresetList(&newline);
   this->PrintPackagePresetList(&newline);
   this->PrintPackagePresetList(&newline);
+  this->PrintWorkflowPresetList(&newline);
 }
 }

+ 44 - 1
Source/cmCMakePresetsGraph.h

@@ -42,6 +42,7 @@ public:
     INVALID_MACRO_EXPANSION,
     INVALID_MACRO_EXPANSION,
     BUILD_TEST_PRESETS_UNSUPPORTED,
     BUILD_TEST_PRESETS_UNSUPPORTED,
     PACKAGE_PRESETS_UNSUPPORTED,
     PACKAGE_PRESETS_UNSUPPORTED,
+    WORKFLOW_PRESETS_UNSUPPORTED,
     INCLUDE_UNSUPPORTED,
     INCLUDE_UNSUPPORTED,
     INVALID_INCLUDE,
     INVALID_INCLUDE,
     INVALID_CONFIGURE_PRESET,
     INVALID_CONFIGURE_PRESET,
@@ -51,6 +52,8 @@ public:
     TOOLCHAIN_FILE_UNSUPPORTED,
     TOOLCHAIN_FILE_UNSUPPORTED,
     CYCLIC_INCLUDE,
     CYCLIC_INCLUDE,
     TEST_OUTPUT_TRUNCATION_UNSUPPORTED,
     TEST_OUTPUT_TRUNCATION_UNSUPPORTED,
+    INVALID_WORKFLOW_STEPS,
+    WORKFLOW_STEP_UNREACHABLE_FROM_FILE,
   };
   };
 
 
   std::string errors;
   std::string errors;
@@ -97,7 +100,7 @@ public:
 
 
     std::string Name;
     std::string Name;
     std::vector<std::string> Inherits;
     std::vector<std::string> Inherits;
-    bool Hidden;
+    bool Hidden = false;
     File* OriginFile;
     File* OriginFile;
     std::string DisplayName;
     std::string DisplayName;
     std::string Description;
     std::string Description;
@@ -363,6 +366,43 @@ public:
     ReadFileResult VisitPresetAfterInherit(int /* version */) override;
     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>
   template <class T>
   class PresetPair
   class PresetPair
   {
   {
@@ -375,11 +415,13 @@ public:
   std::map<std::string, PresetPair<BuildPreset>> BuildPresets;
   std::map<std::string, PresetPair<BuildPreset>> BuildPresets;
   std::map<std::string, PresetPair<TestPreset>> TestPresets;
   std::map<std::string, PresetPair<TestPreset>> TestPresets;
   std::map<std::string, PresetPair<PackagePreset>> PackagePresets;
   std::map<std::string, PresetPair<PackagePreset>> PackagePresets;
+  std::map<std::string, PresetPair<WorkflowPreset>> WorkflowPresets;
 
 
   std::vector<std::string> ConfigurePresetOrder;
   std::vector<std::string> ConfigurePresetOrder;
   std::vector<std::string> BuildPresetOrder;
   std::vector<std::string> BuildPresetOrder;
   std::vector<std::string> TestPresetOrder;
   std::vector<std::string> TestPresetOrder;
   std::vector<std::string> PackagePresetOrder;
   std::vector<std::string> PackagePresetOrder;
+  std::vector<std::string> WorkflowPresetOrder;
 
 
   std::string SourceDir;
   std::string SourceDir;
   std::vector<std::unique_ptr<File>> Files;
   std::vector<std::unique_ptr<File>> Files;
@@ -442,6 +484,7 @@ public:
   void PrintPackagePresetList(
   void PrintPackagePresetList(
     const std::function<bool(const PackagePreset&)>& filter,
     const std::function<bool(const PackagePreset&)>& filter,
     PrintPrecedingNewline* newline = nullptr) const;
     PrintPrecedingNewline* newline = nullptr) const;
+  void PrintWorkflowPresetList(PrintPrecedingNewline* newline = nullptr) const;
   void PrintAllPresets() const;
   void PrintAllPresets() const;
 
 
 private:
 private:

+ 4 - 0
Source/cmCMakePresetsGraphInternal.h

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

+ 33 - 4
Source/cmCMakePresetsGraphReadJSON.cxx

@@ -30,6 +30,8 @@ using CacheVariable = cmCMakePresetsGraph::CacheVariable;
 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
 using BuildPreset = cmCMakePresetsGraph::BuildPreset;
 using TestPreset = cmCMakePresetsGraph::TestPreset;
 using TestPreset = cmCMakePresetsGraph::TestPreset;
+using PackagePreset = cmCMakePresetsGraph::PackagePreset;
+using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
 using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
 using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy;
 using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
 using JSONHelperBuilder = cmJSONHelperBuilder<ReadFileResult>;
 
 
@@ -46,10 +48,11 @@ struct CMakeVersion
 struct RootPresets
 struct RootPresets
 {
 {
   CMakeVersion CMakeMinimumRequired;
   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;
   std::vector<std::string> Include;
 };
 };
 
 
@@ -284,6 +287,8 @@ auto const RootPresetsHelper =
           cmCMakePresetsGraphInternal::TestPresetsHelper, false)
           cmCMakePresetsGraphInternal::TestPresetsHelper, false)
     .Bind("packagePresets"_s, &RootPresets::PackagePresets,
     .Bind("packagePresets"_s, &RootPresets::PackagePresets,
           cmCMakePresetsGraphInternal::PackagePresetsHelper, false)
           cmCMakePresetsGraphInternal::PackagePresetsHelper, false)
+    .Bind("workflowPresets"_s, &RootPresets::WorkflowPresets,
+          cmCMakePresetsGraphInternal::WorkflowPresetsHelper, false)
     .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
     .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired,
           CMakeVersionHelper, false)
           CMakeVersionHelper, false)
     .Bind("include"_s, &RootPresets::Include, IncludeVectorHelper, false)
     .Bind("include"_s, &RootPresets::Include, IncludeVectorHelper, false)
@@ -466,6 +471,11 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
     return ReadFileResult::PACKAGE_PRESETS_UNSUPPORTED;
     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.
   // Support for include added in version 4.
   if (v < 4 && root.isMember("include")) {
   if (v < 4 && root.isMember("include")) {
     return ReadFileResult::INCLUDE_UNSUPPORTED;
     return ReadFileResult::INCLUDE_UNSUPPORTED;
@@ -600,6 +610,25 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile(
     this->PackagePresetOrder.push_back(preset.Name);
     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](
   auto const includeFile = [this, &inProgressFiles, file](
                              const std::string& include, RootType rootType2,
                              const std::string& include, RootType rootType2,
                              ReadReason readReason2,
                              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/algorithm>
 #include <cmext/string_view>
 #include <cmext/string_view>
 
 
+#if !defined(CMAKE_BOOTSTRAP) && !defined(_WIN32)
+#  include <unistd.h>
+#endif
+
 #include "cmsys/FStream.hxx"
 #include "cmsys/FStream.hxx"
 #include "cmsys/Glob.hxx"
 #include "cmsys/Glob.hxx"
 #include "cmsys/RegularExpression.hxx"
 #include "cmsys/RegularExpression.hxx"
@@ -56,6 +60,7 @@
 #include "cmSystemTools.h"
 #include "cmSystemTools.h"
 #include "cmTarget.h"
 #include "cmTarget.h"
 #include "cmTargetLinkLibraryType.h"
 #include "cmTargetLinkLibraryType.h"
+#include "cmUVProcessChain.h"
 #include "cmUtils.hxx"
 #include "cmUtils.hxx"
 #include "cmVersionConfig.h"
 #include "cmVersionConfig.h"
 #include "cmWorkingDirectory.h"
 #include "cmWorkingDirectory.h"
@@ -3662,6 +3667,210 @@ bool cmake::Open(const std::string& dir, bool dryRun)
   return gen->Open(dir, *cachedProjectName, 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)
 void cmake::WatchUnusedCli(const std::string& var)
 {
 {
 #ifndef CMAKE_BOOTSTRAP
 #ifndef CMAKE_BOOTSTRAP

+ 14 - 0
Source/cmake.h

@@ -16,6 +16,7 @@
 #include <vector>
 #include <vector>
 
 
 #include <cm/string_view>
 #include <cm/string_view>
+#include <cmext/string_view>
 
 
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratedFileStream.h"
 #include "cmInstalledFile.h"
 #include "cmInstalledFile.h"
@@ -600,6 +601,9 @@ public:
   //! run the --open option
   //! run the --open option
   bool Open(const std::string& dir, bool dryRun);
   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 UnwatchUnusedCli(const std::string& var);
   void WatchUnusedCli(const std::string& var);
   void WatchUnusedCli(const std::string& var);
 
 
@@ -739,6 +743,16 @@ private:
   void AppendGlobalGeneratorsDocumentation(std::vector<cmDocumentationEntry>&);
   void AppendGlobalGeneratorsDocumentation(std::vector<cmDocumentationEntry>&);
   void AppendExtraGeneratorsDocumentation(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)
 #if !defined(CMAKE_BOOTSTRAP)
   std::unique_ptr<cmMakefileProfilingData> ProfilingOutput;
   std::unique_ptr<cmMakefileProfilingData> ProfilingOutput;
 #endif
 #endif

+ 65 - 0
Source/cmakemain.cxx

@@ -911,6 +911,68 @@ int do_install(int ac, char const* const* av)
 #endif
 #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)
 int do_open(int ac, char const* const* av)
 {
 {
 #ifdef CMAKE_BOOTSTRAP
 #ifdef CMAKE_BOOTSTRAP
@@ -980,6 +1042,9 @@ int main(int ac, char const* const* av)
     if (strcmp(av[1], "--open") == 0) {
     if (strcmp(av[1], "--open") == 0) {
       return do_open(ac, av);
       return do_open(ac, av);
     }
     }
+    if (strcmp(av[1], "--workflow") == 0) {
+      return do_workflow(ac, av);
+    }
     if (strcmp(av[1], "-E") == 0) {
     if (strcmp(av[1], "-E") == 0) {
       return do_command(ac, av, std::move(consoleBuf));
       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}
   -DPython_EXECUTABLE=${Python_EXECUTABLE}
   -DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA}
   -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)
 add_RunCMake_test(VerifyHeaderSets)
 
 

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

@@ -15,4 +15,8 @@ Available test presets:
 
 
 Available package presets:
 Available package presets:
 
 
+  "default"
+
+Available workflow presets:
+
   "default"$
   "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@")