presets: Disallow negative values for "jobs" fields

Jobs should never be negative, to mirror the behavior on the command
line for both `cmake --build` and `ctest`. This fixes unintended
behaviors where "-1" would lead to `cmake::NO_BUILD_PARALLEL_LEVEL`
being used for build presets and an integer overflow occurring for test
presets.

Bump to presets version 11 to signify the change.
This commit is contained in:
Tyler Yankee
2026-01-29 16:25:07 -05:00
parent c2b773cb97
commit 71ff5a9d6f
21 changed files with 227 additions and 22 deletions
+23
View File
@@ -543,6 +543,8 @@ 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.
@@ -551,6 +553,9 @@ that may contain the following fields:
: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
:option:`--target <cmake--build --target>` or ``-t`` on the command line.
@@ -890,11 +895,16 @@ 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. If the value
is ``0``, it is equivalent to unbounded parallelism.
In preset files specifying version ``11`` or above, this field does not accept
negative values.
``resourceSpecFile``
An optional string. Equivalent to passing
:option:`--resource-spec-file <ctest --resource-spec-file>` on
@@ -1445,6 +1455,19 @@ 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 no longer accepts negative
values.
Schema
======
+118 -13
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,21 @@
"type": "object",
"description": "A build preset object.",
"allOf": [
{ "$ref": "#/definitions/buildPresetsItemsV2" }
{ "$ref": "#/definitions/buildPresetsItemsV2" },
{ "$ref": "#/definitions/buildPresetsAdditionalPropertiesV2" }
],
"unevaluatedProperties": false
}
},
"testPresetsExecutionJobsV11": {
"type": "integer",
"description": "An optional integer. Equivalent to passing --parallel on the command line.",
"minimum": 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 +1214,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 +1237,8 @@
{ "$ref": "#/definitions/commentAsProperty" },
{
"properties": {
"repeat": { "$ref": "#/definitions/testPresetsExecutionRepeatV10" }
"repeat": { "$ref": "#/definitions/testPresetsExecutionRepeatV10" },
"jobs": { "$ref": "#/definitions/testPresetsExecutionJobsV2" }
}
}
],
@@ -1173,7 +1251,8 @@
{ "$ref": "#/definitions/testPresetsExecutionObject" },
{
"properties": {
"repeat": { "$ref": "#/definitions/testPresetsExecutionRepeatV2" }
"repeat": { "$ref": "#/definitions/testPresetsExecutionRepeatV2" },
"jobs": { "$ref": "#/definitions/testPresetsExecutionJobsV2" }
}
}
],
@@ -1189,10 +1268,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 +1416,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 +1558,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.",
+2 -2
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<unsigned int> Jobs;
std::string ResourceSpecFile;
cm::optional<int> TestLoad;
cm::optional<ShowOnlyEnum> ShowOnly;
+6
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);
+16 -1
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)
{
@@ -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)
@@ -309,7 +309,7 @@ auto const TestPresetExecutionHelper =
.Bind("enableFailover"_s, &TestPreset::ExecutionOptions::EnableFailover,
cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false)
.Bind("jobs"_s, &TestPreset::ExecutionOptions::Jobs,
cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)
cmCMakePresetsGraphInternal::PresetOptionalUIntHelper, false)
.Bind("resourceSpecFile"_s,
&TestPreset::ExecutionOptions::ResourceSpecFile,
cmCMakePresetsGraphInternal::PresetStringHelper, false)
+2 -2
View File
@@ -1710,8 +1710,8 @@ 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);
unsigned int jobs = *expandedPreset->Execution->Jobs;
this->SetParallelLevel(static_cast<size_t>(jobs));
this->Impl->ParallelLevelSetInCli = true;
}
+6
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;
}
@@ -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,
\^$
@@ -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,
\^
@@ -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
\^$
@@ -0,0 +1,17 @@
{
"version": 11,
"configurePresets": [
{
"name": "default",
"generator": "@RunCMake_GENERATOR@",
"binaryDir": "${sourceDir}/build/${presetName}"
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default",
"jobs": -10
}
]
}
@@ -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" "")
@@ -0,0 +1 @@
1
@@ -0,0 +1,5 @@
^CMake Error: Could not read presets from [^
]*/Tests/RunCMake/CMakePresetsTest/InvalidJobs:
CMakePresets\.json:15: "jobs" expected an unsigned integer, got: -10
"jobs": -10
\^$
@@ -0,0 +1,19 @@
{
"version": 11,
"configurePresets": [
{
"name": "default",
"generator": "@RunCMake_GENERATOR@",
"binaryDir": "${sourceDir}/build/${presetName}"
}
],
"testPresets": [
{
"name": "x",
"configurePreset": "default",
"execution": {
"jobs": -10
}
}
]
}
@@ -111,6 +111,7 @@ 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")
set(CMakePresets_SCHEMA_EXPECTED_RESULT 0)
run_cmake_test_presets(ConfigurePresetUnreachable "" "" "x")
set(CMakePresetsTest_NO_CONFIGURE 0)