summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyle Edwards <kyle.edwards@kitware.com>2022-09-16 15:17:27 -0400
committerKyle Edwards <kyle.edwards@kitware.com>2022-09-27 11:09:06 -0400
commit374d82bbcd461a5ee8d1d9d3a94abd8a26759c37 (patch)
treed390a842e701ad29e157ee5d097902a6305ddd02
parente3168128841485a0a579ad3b9125fdae5e12eec8 (diff)
downloadcmake-374d82bbcd461a5ee8d1d9d3a94abd8a26759c37.tar.gz
cmake: Add --workflow mode
Fixes: #23118
-rw-r--r--Help/manual/cmake-presets.7.rst56
-rw-r--r--Help/manual/cmake.1.rst22
-rw-r--r--Help/manual/presets/example.json23
-rw-r--r--Help/manual/presets/schema.json85
-rw-r--r--Help/release/dev/cmake-presets-workflow.rst4
-rw-r--r--Source/cmake.cxx209
-rw-r--r--Source/cmake.h14
-rw-r--r--Source/cmakemain.cxx65
-rw-r--r--Tests/RunCMake/CMakeLists.txt4
-rw-r--r--Tests/RunCMake/CMakePresets/DocumentationExampleListAllPresets-stdout.txt4
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stderr.txt4
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stdout.txt17
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/BadExitCode.cmake8
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/BadExitCodeTest.cmake1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/CMakeLists.txt.in3
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch.json.in32
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-result.txt0
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure.json.in27
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/Good-stdout.txt19
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/Good.cmake8
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/Good.json.in87
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/GoodUser-stdout.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/GoodUser.cmake1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/GoodUser.json.in14
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/ListPresets-stdout.txt4
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/ListPresets.json.in30
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps.json.in9
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep.json.in14
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/RunCMakeTest.cmake79
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure.json.in25
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-stderr.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep.json.in14
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/UnreachableStepUser.json.in8
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-stderr.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion.json.in4
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-stderr.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled.json.in23
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-stderr.txt2
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden.json.in20
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-result.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-stderr.txt1
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro.json.in20
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/check.cmake3
-rw-r--r--Tests/RunCMake/CMakePresetsWorkflow/cpack_staging.cmake.in1
58 files changed, 981 insertions, 11 deletions
diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst
index d5319f212e..93f929ec83 100644
--- a/Help/manual/cmake-presets.7.rst
+++ b/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
^^^^^^^^^
diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst
index 98655e5553..c05f3c8c32 100644
--- a/Help/manual/cmake.1.rst
+++ b/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
=========
diff --git a/Help/manual/presets/example.json b/Help/manual/presets/example.json
index 06a1112a5f..696ab4721e 100644
--- a/Help/manual/presets/example.json
+++ b/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
diff --git a/Help/manual/presets/schema.json b/Help/manual/presets/schema.json
index f3a7532597..b4db700754 100644
--- a/Help/manual/presets/schema.json
+++ b/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": [
{
diff --git a/Help/release/dev/cmake-presets-workflow.rst b/Help/release/dev/cmake-presets-workflow.rst
new file mode 100644
index 0000000000..db93d72106
--- /dev/null
+++ b/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.
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index 3b105e31af..7f30bb44c4 100644
--- a/Source/cmake.cxx
+++ b/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"
@@ -3665,6 +3670,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
diff --git a/Source/cmake.h b/Source/cmake.h
index 8c0feced20..54d0bb5a13 100644
--- a/Source/cmake.h
+++ b/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);
@@ -740,6 +744,16 @@ private:
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
};
diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx
index 6f3d0eb06a..b754b72812 100644
--- a/Source/cmakemain.cxx
+++ b/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));
}
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 2f5bc87ffb..4a42bda097 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/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)
diff --git a/Tests/RunCMake/CMakePresets/DocumentationExampleListAllPresets-stdout.txt b/Tests/RunCMake/CMakePresets/DocumentationExampleListAllPresets-stdout.txt
index b1fcc28ec2..57b714d746 100644
--- a/Tests/RunCMake/CMakePresets/DocumentationExampleListAllPresets-stdout.txt
+++ b/Tests/RunCMake/CMakePresets/DocumentationExampleListAllPresets-stdout.txt
@@ -15,4 +15,8 @@ Available test presets:
Available package presets:
+ "default"
+
+Available workflow presets:
+
"default"$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-result.txt
new file mode 100644
index 0000000000..45a4fb75db
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-result.txt
@@ -0,0 +1 @@
+8
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stderr.txt
new file mode 100644
index 0000000000..0690c694ba
--- /dev/null
+++ b/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\.$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stdout.txt b/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode-stdout.txt
new file mode 100644
index 0000000000..2f23f8885e
--- /dev/null
+++ b/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\)$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode.cmake b/Tests/RunCMake/CMakePresetsWorkflow/BadExitCode.cmake
new file mode 100644
index 0000000000..10b46e3f2c
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/BadExitCodeTest.cmake b/Tests/RunCMake/CMakePresetsWorkflow/BadExitCodeTest.cmake
new file mode 100644
index 0000000000..59f683e605
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/BadExitCodeTest.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR " Testing the test step at ${CMAKE_BINARY_DIR}")
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/CMakeLists.txt.in b/Tests/RunCMake/CMakePresetsWorkflow/CMakeLists.txt.in
new file mode 100644
index 0000000000..129184a89c
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/CMakeLists.txt.in
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.19)
+project("@CASE_NAME@" NONE)
+include("@CASE_SOURCE_DIR@/@CASE_NAME@.cmake")
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt
new file mode 100644
index 0000000000..22ca94d0da
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch: Invalid workflow steps$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch.json.in b/Tests/RunCMake/CMakePresetsWorkflow/ConfigureStepMismatch.json.in
new file mode 100644
index 0000000000..0864149039
--- /dev/null
+++ b/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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-result.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-result.txt
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt
new file mode 100644
index 0000000000..cbfee5a5c2
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure: Invalid workflow steps$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure.json.in b/Tests/RunCMake/CMakePresetsWorkflow/FirstStepNotConfigure.json.in
new file mode 100644
index 0000000000..2c121a8b3e
--- /dev/null
+++ b/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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/Good-stdout.txt b/Tests/RunCMake/CMakePresetsWorkflow/Good-stdout.txt
new file mode 100644
index 0000000000..a04d7ea14d
--- /dev/null
+++ b/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.*
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/Good.cmake b/Tests/RunCMake/CMakePresetsWorkflow/Good.cmake
new file mode 100644
index 0000000000..31ce7ffb26
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/Good.json.in b/Tests/RunCMake/CMakePresetsWorkflow/Good.json.in
new file mode 100644
index 0000000000..87e29368fa
--- /dev/null
+++ b/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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/GoodUser-stdout.txt b/Tests/RunCMake/CMakePresetsWorkflow/GoodUser-stdout.txt
new file mode 100644
index 0000000000..1014915697
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/GoodUser-stdout.txt
@@ -0,0 +1,2 @@
+-- Testing the configure step at [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/GoodUser/build
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/GoodUser.cmake b/Tests/RunCMake/CMakePresetsWorkflow/GoodUser.cmake
new file mode 100644
index 0000000000..9143e00b3b
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/GoodUser.cmake
@@ -0,0 +1 @@
+message(STATUS "Testing the configure step at ${CMAKE_BINARY_DIR}")
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/GoodUser.json.in b/Tests/RunCMake/CMakePresetsWorkflow/GoodUser.json.in
new file mode 100644
index 0000000000..e71b4ea180
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/GoodUser.json.in
@@ -0,0 +1,14 @@
+{
+ "version": 6,
+ "workflowPresets": [
+ {
+ "name": "GoodUser",
+ "steps": [
+ {
+ "type": "configure",
+ "name": "default"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/ListPresets-stdout.txt b/Tests/RunCMake/CMakePresetsWorkflow/ListPresets-stdout.txt
new file mode 100644
index 0000000000..57f30a4e0f
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/ListPresets-stdout.txt
@@ -0,0 +1,4 @@
+^Available workflow presets:
+
+ "default"
+ "with-description" - With Description$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/ListPresets.json.in b/Tests/RunCMake/CMakePresetsWorkflow/ListPresets.json.in
new file mode 100644
index 0000000000..9a7d5a615c
--- /dev/null
+++ b/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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt
new file mode 100644
index 0000000000..049ed6bc51
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps: Invalid workflow steps$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps.json.in b/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps.json.in
new file mode 100644
index 0000000000..27571979e9
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/NoWorkflowSteps.json.in
@@ -0,0 +1,9 @@
+{
+ "version": 6,
+ "workflowPresets": [
+ {
+ "name": "default",
+ "steps": []
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt
new file mode 100644
index 0000000000..c522b84d42
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep: Invalid workflow steps$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep.json.in b/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep.json.in
new file mode 100644
index 0000000000..235398bef1
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/NonexistentStep.json.in
@@ -0,0 +1,14 @@
+{
+ "version": 6,
+ "workflowPresets": [
+ {
+ "name": "default",
+ "steps": [
+ {
+ "type": "configure",
+ "name": "default"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/RunCMakeTest.cmake b/Tests/RunCMake/CMakePresetsWorkflow/RunCMakeTest.cmake
new file mode 100644
index 0000000000..b89a11a6af
--- /dev/null
+++ b/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)
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt
new file mode 100644
index 0000000000..b0ad7d530e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: Could not read presets from [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure: Invalid workflow steps$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure.json.in b/Tests/RunCMake/CMakePresetsWorkflow/SecondStepConfigure.json.in
new file mode 100644
index 0000000000..44e158259a
--- /dev/null
+++ b/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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep-stderr.txt
new file mode 100644
index 0000000000..425e7196f0
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep.json.in b/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep.json.in
new file mode 100644
index 0000000000..235398bef1
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStep.json.in
@@ -0,0 +1,14 @@
+{
+ "version": 6,
+ "workflowPresets": [
+ {
+ "name": "default",
+ "steps": [
+ {
+ "type": "configure",
+ "name": "default"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStepUser.json.in b/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStepUser.json.in
new file mode 100644
index 0000000000..39b6835a93
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/UnreachableStepUser.json.in
@@ -0,0 +1,8 @@
+{
+ "version": 6,
+ "configurePresets": [
+ {
+ "name": "default"
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion-stderr.txt
new file mode 100644
index 0000000000..5cf01aaa51
--- /dev/null
+++ b/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$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion.json.in b/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion.json.in
new file mode 100644
index 0000000000..4ebaa8efe1
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/UnsupportedVersion.json.in
@@ -0,0 +1,4 @@
+{
+ "version": 5,
+ "workflowPresets": []
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-stderr.txt
new file mode 100644
index 0000000000..b598b272f1
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: Cannot use disabled configure preset in [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled: "default"$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled.json.in b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepDisabled.json.in
new file mode 100644
index 0000000000..a3b6783d93
--- /dev/null
+++ b/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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-stderr.txt
new file mode 100644
index 0000000000..838ded5141
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: Cannot use hidden configure preset in [^
+]*/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden: "default"$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden.json.in b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepHidden.json.in
new file mode 100644
index 0000000000..07c4105a9b
--- /dev/null
+++ b/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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-result.txt b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-result.txt
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-stderr.txt b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-stderr.txt
new file mode 100644
index 0000000000..f132a93364
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro-stderr.txt
@@ -0,0 +1 @@
+^CMake Error: Could not evaluate configure preset "default": Invalid macro expansion$
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro.json.in b/Tests/RunCMake/CMakePresetsWorkflow/WorkflowStepInvalidMacro.json.in
new file mode 100644
index 0000000000..6aec0e3498
--- /dev/null
+++ b/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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/check.cmake b/Tests/RunCMake/CMakePresetsWorkflow/check.cmake
new file mode 100644
index 0000000000..e79c4f12a8
--- /dev/null
+++ b/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")
diff --git a/Tests/RunCMake/CMakePresetsWorkflow/cpack_staging.cmake.in b/Tests/RunCMake/CMakePresetsWorkflow/cpack_staging.cmake.in
new file mode 100644
index 0000000000..4030dfb619
--- /dev/null
+++ b/Tests/RunCMake/CMakePresetsWorkflow/cpack_staging.cmake.in
@@ -0,0 +1 @@
+message(STATUS "Testing the package step at @RunCMake_TEST_BINARY_DIR@")