Merge topic 'preset-test-jobs'

23615b7ca4 presets: Allow jobs to match `ctest -j` with no value
71ff5a9d6f presets: Disallow negative values for "jobs" fields
c2b773cb97 cmake: Prefer named constant for jobs
68bd63c856 Help/presets: Document behavior for unbounded parallelism
25641fe8d5 Help/presets: Document behavior for native build tool parallelism

Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: Alex Overchenko <aleksandr9809@gmail.com>
Merge-request: !11508
This commit is contained in:
Brad King
2026-01-30 16:37:26 +00:00
committed by Kitware Robot
30 changed files with 333 additions and 26 deletions

View File

@@ -493,6 +493,8 @@ that may contain the following fields:
``description``
An optional string with a human-friendly description of the preset.
.. _`CMakePresets build environment`:
``environment``
An optional map of environment variables. The key is the variable name
(which may not be an empty string), and the value is either ``null`` or
@@ -541,9 +543,18 @@ that may contain the following fields:
inherited build preset environments, but before environment variables
explicitly specified in this build preset.
.. _`CMakePresets build jobs`:
``jobs``
An optional integer. Equivalent to passing
:option:`--parallel <cmake--build --parallel>` or ``-j`` on the command line.
If the value is ``0``, it is equivalent to passing ``--parallel`` with
``<jobs>`` omitted; alternatively, one can define the environment variable
:envvar:`CMAKE_BUILD_PARALLEL_LEVEL` as an empty string using the
``environment`` field.
In preset files specifying version ``11`` or above, this field does not
accept negative values.
``targets``
An optional string or array of strings. Equivalent to passing
@@ -884,9 +895,17 @@ that may contain the following fields:
An optional bool. If true, equivalent to passing :option:`-F <ctest -F>`
on the command line.
.. _`CMakePresets test jobs`:
``jobs``
An optional integer. Equivalent to passing
:option:`--parallel <ctest --parallel>` on the command line.
:option:`--parallel <ctest --parallel>` on the command line. If the value
is ``0``, it is equivalent to unbounded parallelism.
In preset files specifying version ``11`` or above, this field can also be
a string, in which case it must be empty, and is equivalent to passing
``--parallel`` with ``<jobs>`` omitted; additionally, it does not accept
negative values.
``resourceSpecFile``
An optional string. Equivalent to passing
@@ -1438,6 +1457,21 @@ they were added and a summary of the new features and changes is given below.
* The `graphviz <CMakePresets graphviz_>`_ field was added.
``11``
.. versionadded:: 4.3
* Changes to `Build Presets <Build Preset_>`_
* The `jobs <CMakePresets build jobs_>`_ field no longer accepts negative
values.
* Changes to `Test Presets <Test Preset_>`_
* The `jobs <CMakePresets test jobs_>`_ field now accepts an empty string
representing :option:`--parallel <ctest --parallel>` with ``<jobs>``
omitted. In addition, when an integer is specified, it must not be
negative.
Schema
======

View File

@@ -161,6 +161,25 @@
"include": { "$ref": "#/definitions/include" }
},
"additionalProperties": false
},
{
"properties": {
"$schema": { "$ref": "#/definitions/$schema" },
"$comment": { "$ref": "#/definitions/$comment" },
"version": {
"const": 11,
"description": "A required integer representing the version of the JSON schema."
},
"cmakeMinimumRequired": { "$ref": "#/definitions/cmakeMinimumRequiredV10" },
"vendor": { "$ref": "#/definitions/vendor" },
"configurePresets": { "$ref": "#/definitions/configurePresetsV10" },
"buildPresets": { "$ref": "#/definitions/buildPresetsV11" },
"testPresets": { "$ref": "#/definitions/testPresetsV11" },
"packagePresets": { "$ref": "#/definitions/packagePresetsV10" },
"workflowPresets": { "$ref": "#/definitions/workflowPresetsV10" },
"include": { "$ref": "#/definitions/include" }
},
"additionalProperties": false
}
],
"required": [
@@ -793,6 +812,15 @@
"unevaluatedProperties": false
}
},
"buildPresetsJobsV11": {
"type": "integer",
"description": "An optional integer. Equivalent to passing --parallel or -j on the command line.",
"minimum": 0
},
"buildPresetsJobsV2": {
"type": "integer",
"description": "An optional integer. Equivalent to passing --parallel or -j on the command line."
},
"buildPresetsItemsV4": {
"properties": {
"resolvePackageReferences": {
@@ -841,10 +869,6 @@
"type": "boolean",
"description": "An optional boolean that defaults to true. If true, the environment variables from the associated configure preset are inherited after all inherited build preset environments, but before environment variables explicitly specified in this build preset."
},
"jobs": {
"type": "integer",
"description": "An optional integer. Equivalent to passing --parallel or -j on the command line."
},
"targets": {
"anyOf": [
{
@@ -888,14 +912,42 @@
"name"
]
},
"buildPresetsAdditionalPropertiesV11": {
"properties": {
"condition": { "$ref": "#/definitions/topConditionV10" },
"jobs": { "$ref": "#/definitions/buildPresetsJobsV11" }
}
},
"buildPresetsAdditionalPropertiesV10": {
"properties": {
"condition": { "$ref": "#/definitions/topConditionV10" }
"condition": { "$ref": "#/definitions/topConditionV10" },
"jobs": { "$ref": "#/definitions/buildPresetsJobsV2" }
}
},
"buildPresetsAdditionalPropertiesV3": {
"properties": {
"condition": { "$ref": "#/definitions/topConditionV3" }
"condition": { "$ref": "#/definitions/topConditionV3" },
"jobs": { "$ref": "#/definitions/buildPresetsJobsV2" }
}
},
"buildPresetsAdditionalPropertiesV2": {
"properties": {
"jobs": { "$ref": "#/definitions/buildPresetsJobsV2" }
}
},
"buildPresetsV11": {
"type": "array",
"description": "An optional array of build preset objects. Used to specify arguments to cmake --build. Available in version 10 and higher.",
"items": {
"type": "object",
"description": "A build preset object.",
"allOf": [
{ "$ref": "#/definitions/buildPresetsItemsV4" },
{ "$ref": "#/definitions/buildPresetsItemsV2" },
{ "$ref": "#/definitions/buildPresetsAdditionalPropertiesV11" },
{ "$ref": "#/definitions/commentAsProperty" }
],
"unevaluatedProperties": false
}
},
"buildPresetsV10": {
@@ -947,11 +999,31 @@
"type": "object",
"description": "A build preset object.",
"allOf": [
{ "$ref": "#/definitions/buildPresetsItemsV2" }
{ "$ref": "#/definitions/buildPresetsItemsV2" },
{ "$ref": "#/definitions/buildPresetsAdditionalPropertiesV2" }
],
"unevaluatedProperties": false
}
},
"testPresetsExecutionJobsV11": {
"oneOf": [
{
"type": "integer",
"description": "An optional integer. Equivalent to passing --parallel on the command line.",
"minimum": 0
},
{
"type": "string",
"description": "An optional string. Equivalent to passing --parallel on the command line with the number of jobs omitted.",
"minLength": 0,
"maxLength": 0
}
]
},
"testPresetsExecutionJobsV2": {
"type": "integer",
"description": "An optional integer. Equivalent to passing --parallel on the command line."
},
"testPresetsExecutionRepeatV10": {
"type": "object",
"description": "An optional object specifying how to repeat tests. Equivalent to passing --repeat on the command line.",
@@ -1152,6 +1224,21 @@
}
}
},
"testPresetsExecutionV11": {
"type": "object",
"description": "An optional object specifying options for test execution.",
"allOf": [
{ "$ref": "#/definitions/testPresetsExecutionObject" },
{ "$ref": "#/definitions/commentAsProperty" },
{
"properties": {
"repeat": { "$ref": "#/definitions/testPresetsExecutionRepeatV10" },
"jobs": { "$ref": "#/definitions/testPresetsExecutionJobsV11" }
}
}
],
"unevaluatedProperties": false
},
"testPresetsExecutionV10": {
"type": "object",
"description": "An optional object specifying options for test execution.",
@@ -1160,7 +1247,8 @@
{ "$ref": "#/definitions/commentAsProperty" },
{
"properties": {
"repeat": { "$ref": "#/definitions/testPresetsExecutionRepeatV10" }
"repeat": { "$ref": "#/definitions/testPresetsExecutionRepeatV10" },
"jobs": { "$ref": "#/definitions/testPresetsExecutionJobsV2" }
}
}
],
@@ -1173,7 +1261,8 @@
{ "$ref": "#/definitions/testPresetsExecutionObject" },
{
"properties": {
"repeat": { "$ref": "#/definitions/testPresetsExecutionRepeatV2" }
"repeat": { "$ref": "#/definitions/testPresetsExecutionRepeatV2" },
"jobs": { "$ref": "#/definitions/testPresetsExecutionJobsV2" }
}
}
],
@@ -1189,10 +1278,6 @@
"type": "boolean",
"description": "An optional boolean. If true, equivalent to passing -F on the command line."
},
"jobs": {
"type": "integer",
"description": "An optional integer. Equivalent to passing --parallel on the command line."
},
"resourceSpecFile": {
"type": "string",
"description": "An optional string. Equivalent to passing --resource-spec-file on the command line."
@@ -1341,6 +1426,22 @@
}
}
},
"testPresetsAdditionalPropertiesV11": {
"properties": {
"execution": { "$ref": "#/definitions/testPresetsExecutionV11" },
"filter": {
"type": "object",
"description": "An optional object specifying how to filter the tests to run.",
"properties": {
"include": { "$ref": "#/definitions/testPresetsFilterIncludeV10" },
"exclude": { "$ref": "#/definitions/testPresetsFilterExcludeV10" },
"$comment": { "$ref": "#/definitions/$comment" }
}
},
"output": { "$ref": "#/definitions/testPresetsOutputV6" },
"condition": { "$ref": "#/definitions/topConditionV10" }
}
},
"testPresetsAdditionalPropertiesV10": {
"properties": {
"execution": { "$ref": "#/definitions/testPresetsExecutionV10" },
@@ -1467,6 +1568,20 @@
"name"
]
},
"testPresetsV11": {
"type": "array",
"description": "An optional array of test preset objects. Used to specify arguments to ctest. Available in version 11 and higher.",
"items": {
"type": "object",
"description": "A test preset object",
"allOf": [
{ "$ref": "#/definitions/testPresetsItemsV2" },
{ "$ref": "#/definitions/testPresetsAdditionalPropertiesV11" },
{ "$ref": "#/definitions/commentAsProperty" }
],
"unevaluatedProperties": false
}
},
"testPresetsV10": {
"type": "array",
"description": "An optional array of test preset objects. Used to specify arguments to ctest. Available in version 10 and higher.",

View File

@@ -183,6 +183,12 @@ void GRAPHVIZ_FILE_UNSUPPORTED(cmJSONState* state)
"File version must be 10 or higher for graphviz preset support");
}
void JOBS_PROC_UNSUPPORTED(cmJSONState* state)
{
state->AddError("File version must be 11 or higher for "
"processor-count-based jobs preset support");
}
void CYCLIC_INCLUDE(std::string const& file, cmJSONState* state)
{
state->AddError(cmStrCat("Cyclic include among preset files: ", file));

View File

@@ -72,6 +72,8 @@ void TOOLCHAIN_FILE_UNSUPPORTED(cmJSONState* state);
void GRAPHVIZ_FILE_UNSUPPORTED(cmJSONState* state);
void JOBS_PROC_UNSUPPORTED(cmJSONState* state);
void CYCLIC_INCLUDE(std::string const& file, cmJSONState* state);
void TEST_OUTPUT_TRUNCATION_UNSUPPORTED(cmJSONState* state);

View File

@@ -167,7 +167,7 @@ public:
std::string ConfigurePreset;
cm::optional<bool> InheritConfigureEnvironment;
cm::optional<int> Jobs;
cm::optional<unsigned int> Jobs;
std::vector<std::string> Targets;
std::string Configuration;
cm::optional<bool> CleanFirst;
@@ -289,7 +289,7 @@ public:
cm::optional<bool> StopOnFailure;
cm::optional<bool> EnableFailover;
cm::optional<int> Jobs;
cm::optional<cm::optional<unsigned int>> Jobs;
std::string ResourceSpecFile;
cm::optional<int> TestLoad;
cm::optional<ShowOnlyEnum> ShowOnly;

View File

@@ -218,6 +218,12 @@ bool PresetIntHelper(int& out, Json::Value const* value, cmJSONState* state);
bool PresetOptionalIntHelper(cm::optional<int>& out, Json::Value const* value,
cmJSONState* state);
bool PresetUIntHelper(unsigned int& out, Json::Value const* value,
cmJSONState* state);
bool PresetOptionalUIntHelper(cm::optional<unsigned int>& out,
Json::Value const* value, cmJSONState* state);
bool PresetVectorIntHelper(std::vector<int>& out, Json::Value const* value,
cmJSONState* state);

View File

@@ -40,7 +40,7 @@ using cmCMakePresetsGraphInternal::BaseMacroExpander;
using cmCMakePresetsGraphInternal::ExpandMacros;
constexpr int MIN_VERSION = 1;
constexpr int MAX_VERSION = 10;
constexpr int MAX_VERSION = 11;
struct CMakeVersion
{
@@ -384,6 +384,21 @@ bool PresetOptionalIntHelper(cm::optional<int>& out, Json::Value const* value,
return helper(out, value, state);
}
bool PresetUIntHelper(unsigned int& out, Json::Value const* value,
cmJSONState* state)
{
static auto const helper = JSONHelperBuilder::UInt();
return helper(out, value, state);
}
bool PresetOptionalUIntHelper(cm::optional<unsigned int>& out,
Json::Value const* value, cmJSONState* state)
{
static auto const helper =
JSONHelperBuilder::Optional<unsigned int>(PresetUIntHelper);
return helper(out, value, state);
}
bool PresetVectorIntHelper(std::vector<int>& out, Json::Value const* value,
cmJSONState* state)
{
@@ -678,6 +693,13 @@ bool cmCMakePresetsGraph::ReadJSONFile(std::string const& filename,
return false;
}
// Support for processor-count-based jobs added in version 11.
if (v < 11 && preset.Execution && preset.Execution->Jobs.has_value() &&
!preset.Execution->Jobs->has_value()) {
cmCMakePresetsErrors::JOBS_PROC_UNSUPPORTED(&this->parseState);
return false;
}
this->TestPresetOrder.push_back(preset.Name);
}

View File

@@ -83,7 +83,7 @@ auto const BuildPresetHelper =
&BuildPreset::InheritConfigureEnvironment,
cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
.Bind("jobs"_s, &BuildPreset::Jobs,
cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
cmCMakePresetsGraphInternal::PresetOptionalUIntHelper, false)
.Bind("targets"_s, &BuildPreset::Targets,
cmCMakePresetsGraphInternal::PresetVectorOneOrMoreStringHelper,
false)

View File

@@ -301,6 +301,32 @@ auto const TestPresetOptionalExecutionNoTestsActionHelper =
JSONHelperBuilder::Optional<TestPreset::ExecutionOptions::NoTestsActionEnum>(
TestPresetExecutionNoTestsActionHelper);
bool TestPresetExecutionJobsHelper(cm::optional<unsigned int>& out,
Json::Value const* value,
cmJSONState* state)
{
if (value->isString()) {
if (!value->asString().empty()) {
cmCMakePresetsErrors::INVALID_PRESET(value, state);
return false;
}
out.reset();
return true;
}
if (value->isUInt()) {
out.emplace(value->asUInt());
return true;
}
cmCMakePresetsErrors::INVALID_PRESET(value, state);
return false;
}
auto const TestPresetOptionalExecutionJobsHelper =
JSONHelperBuilder::Optional<cm::optional<unsigned int>>(
TestPresetExecutionJobsHelper);
auto const TestPresetExecutionHelper =
JSONHelperBuilder::Optional<TestPreset::ExecutionOptions>(
JSONHelperBuilder::Object<TestPreset::ExecutionOptions>()
@@ -309,7 +335,7 @@ auto const TestPresetExecutionHelper =
.Bind("enableFailover"_s, &TestPreset::ExecutionOptions::EnableFailover,
cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
.Bind("jobs"_s, &TestPreset::ExecutionOptions::Jobs,
cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
TestPresetOptionalExecutionJobsHelper, false)
.Bind("resourceSpecFile"_s,
&TestPreset::ExecutionOptions::ResourceSpecFile,
cmCMakePresetsGraphInternal::PresetStringHelper, false)

View File

@@ -1710,8 +1710,12 @@ bool cmCTest::SetArgsFromPreset(std::string const& presetName,
expandedPreset->Execution->EnableFailover.value_or(false);
if (expandedPreset->Execution->Jobs) {
auto jobs = *expandedPreset->Execution->Jobs;
this->SetParallelLevel(jobs);
cm::optional<unsigned int> jobs = *expandedPreset->Execution->Jobs;
if (jobs.has_value()) {
this->SetParallelLevel(static_cast<size_t>(jobs.value()));
} else {
this->SetParallelLevel(cm::nullopt);
}
this->Impl->ParallelLevelSetInCli = true;
}

View File

@@ -6,6 +6,7 @@
#include <array>
#include <cassert>
#include <chrono>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <initializer_list>
@@ -3947,6 +3948,11 @@ int cmake::Build(cmBuildArgs buildArgs, std::vector<std::string> targets,
if ((buildArgs.jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL ||
buildArgs.jobs == cmake::NO_BUILD_PARALLEL_LEVEL) &&
expandedPreset->Jobs) {
if (*expandedPreset->Jobs > static_cast<unsigned int>(INT_MAX)) {
cmSystemTools::Error(
"The build preset \"jobs\" value is too large.\n");
return 1;
}
buildArgs.jobs = *expandedPreset->Jobs;
}

View File

@@ -428,7 +428,7 @@ int do_cmake(int ac, char const* const* av)
int extract_job_number(std::string const& command,
std::string const& jobString)
{
int jobs = -1;
int jobs = cmake::NO_BUILD_PARALLEL_LEVEL;
unsigned long numJobs = 0;
if (jobString.empty()) {
jobs = cmake::DEFAULT_BUILD_PARALLEL_LEVEL;

View File

@@ -1,5 +1,5 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresets/HighVersion:
CMakePresets\.json:2: Unrecognized "version" 1000: must be >=1 and <=10
CMakePresets\.json:2: Unrecognized "version" 1000: must be >=1 and <=11
"version": 1000,
\^$

View File

@@ -1,5 +1,5 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresets/LowVersion:
CMakePresets\.json:2: Unrecognized "version" 0: must be >=1 and <=10
CMakePresets\.json:2: Unrecognized "version" 0: must be >=1 and <=11
"version": 0,
\^

View File

@@ -0,0 +1,5 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresetsBuild/JobsNegative:
CMakePresets\.json:14: "jobs" expected an unsigned integer, got: -10
"jobs": -10
\^$

View File

@@ -0,0 +1,17 @@
{
"version": 11,
"configurePresets": [
{
"name": "default",
"generator": "@RunCMake_GENERATOR@",
"binaryDir": "${sourceDir}/build/${presetName}"
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default",
"jobs": -10
}
]
}

View File

@@ -83,6 +83,7 @@ run_cmake_build_presets(BuildDirectoryOverride "" "override" "${RunCMake_BINARY_
set(CMakePresets_SCHEMA_EXPECTED_RESULT 1)
run_cmake_build_presets(PresetsUnsupported "x" "x" "")
run_cmake_build_presets(ConditionFuture "x" "conditionFuture" "")
run_cmake_build_presets(JobsNegative "" "default" "")
set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
run_cmake_build_presets(ConfigurePresetUnreachable "x" "x" "")

View File

@@ -0,0 +1,4 @@
Test project [^
]*/Tests/RunCMake/CMakePresetsTest/Good/build/default
.*
100% tests passed, 0 tests failed out of 5

View File

@@ -1,5 +1,5 @@
{
"version": 6,
"version": 11,
"configurePresets": [
{
"name": "default",
@@ -160,6 +160,13 @@
}
}
},
{
"name": "jobsProc",
"inherits": "minimal",
"execution": {
"jobs": ""
}
},
{
"name": "showOnly",
"inherits": "minimal",

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,5 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresetsTest/InvalidJobs:
CMakePresets\.json:15: Invalid preset
"jobs": -10
\^$

View File

@@ -0,0 +1,19 @@
{
"version": 11,
"configurePresets": [
{
"name": "default",
"generator": "@RunCMake_GENERATOR@",
"binaryDir": "${sourceDir}/build/${presetName}"
}
],
"testPresets": [
{
"name": "x",
"configurePreset": "default",
"execution": {
"jobs": -10
}
}
]
}

View File

@@ -0,0 +1,3 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported:
File version must be 11 or higher for processor-count-based jobs preset support$

View File

@@ -0,0 +1,19 @@
{
"version": 10,
"configurePresets": [
{
"name": "default",
"generator": "@RunCMake_GENERATOR@",
"binaryDir": "${sourceDir}/build"
}
],
"testPresets": [
{
"name": "default",
"configurePreset": "default",
"execution": {
"jobs": ""
}
}
]
}

View File

@@ -9,4 +9,5 @@ Available test presets:
"exclude"
"index"
"indexFile"
"jobsProc"
"showOnly"

View File

@@ -82,7 +82,7 @@ set(CMakePresetsTest_ASSETS "Good-indexFile.txt")
set(GoodTestPresets
"minimal;defaults;noEnvironment;withEnvironment"
"config-debug;config-release"
"exclude;index;indexFile;showOnly;outputLog;outputJUnit")
"exclude;index;indexFile;jobsProc;showOnly;outputLog;outputJUnit")
run_cmake_test_presets(Good
"default"
""
@@ -111,6 +111,8 @@ run_cmake_test_presets(PresetsUnsupported "" "" "x")
run_cmake_test_presets(ConditionFuture "" "" "x")
run_cmake_test_presets(TestOutputTruncationUnsupported "" "" "x")
run_cmake_test_presets(OutputJUnitUnsupported "" "" "x")
run_cmake_test_presets(InvalidJobs "" "" "x")
run_cmake_test_presets(JobsProcUnsupported "" "" "x")
set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
run_cmake_test_presets(ConfigurePresetUnreachable "" "" "x")
set(CMakePresetsTest_NO_CONFIGURE 0)