From 25641fe8d5e4a74e56fea3a5b8dce26f564f908b Mon Sep 17 00:00:00 2001 From: Tyler Yankee Date: Thu, 29 Jan 2026 11:17:07 -0500 Subject: [PATCH 1/5] Help/presets: Document behavior for native build tool parallelism This is already technically supported by the implementation, but it's not immediately intuitive to users. Relates: #27070 --- Help/manual/cmake-presets.7.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst index 70b9245139..8459469d88 100644 --- a/Help/manual/cmake-presets.7.rst +++ b/Help/manual/cmake-presets.7.rst @@ -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 @@ -544,6 +546,10 @@ that may contain the following fields: ``jobs`` An optional integer. Equivalent to passing :option:`--parallel ` or ``-j`` on the command line. + If the value is ``0``, it is equivalent to passing ``--parallel`` with + ```` omitted; alternatively, one can define the environment variable + :envvar:`CMAKE_BUILD_PARALLEL_LEVEL` as an empty string using the + ``environment`` field. ``targets`` An optional string or array of strings. Equivalent to passing From 68bd63c856fe6c683438e05ae1b69191ee05aebf Mon Sep 17 00:00:00 2001 From: Tyler Yankee Date: Thu, 29 Jan 2026 11:30:30 -0500 Subject: [PATCH 2/5] Help/presets: Document behavior for unbounded parallelism This is already technically supported by the implementation, but it's not immediately intuitive to users. Relates: #27070 --- Help/manual/cmake-presets.7.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst index 8459469d88..bdec8a0126 100644 --- a/Help/manual/cmake-presets.7.rst +++ b/Help/manual/cmake-presets.7.rst @@ -892,7 +892,8 @@ that may contain the following fields: ``jobs`` An optional integer. Equivalent to passing - :option:`--parallel ` on the command line. + :option:`--parallel ` on the command line. If the value + is ``0``, it is equivalent to unbounded parallelism. ``resourceSpecFile`` An optional string. Equivalent to passing From c2b773cb97e498e9f30edd7988263374ac48cf68 Mon Sep 17 00:00:00 2001 From: Tyler Yankee Date: Thu, 29 Jan 2026 11:25:14 -0500 Subject: [PATCH 3/5] cmake: Prefer named constant for jobs This improves readability. --- Source/cmakemain.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx index fe5f35da7a..9fd2799789 100644 --- a/Source/cmakemain.cxx +++ b/Source/cmakemain.cxx @@ -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; From 71ff5a9d6f2a0d910d820f0ce20f1e9f354c20e9 Mon Sep 17 00:00:00 2001 From: Tyler Yankee Date: Tue, 13 Jan 2026 08:01:51 -0500 Subject: [PATCH 4/5] 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. --- Help/manual/cmake-presets.7.rst | 23 +++ Help/manual/presets/schema.json | 131 ++++++++++++++++-- Source/cmCMakePresetsGraph.h | 4 +- Source/cmCMakePresetsGraphInternal.h | 6 + Source/cmCMakePresetsGraphReadJSON.cxx | 17 ++- ...mCMakePresetsGraphReadJSONBuildPresets.cxx | 2 +- ...cmCMakePresetsGraphReadJSONTestPresets.cxx | 2 +- Source/cmCTest.cxx | 4 +- Source/cmake.cxx | 6 + .../CMakePresets/HighVersion-stderr.txt | 2 +- .../CMakePresets/LowVersion-stderr.txt | 2 +- .../JobsNegative-build-default-result.txt | 1 + .../JobsNegative-build-default-stderr.txt | 5 + .../CMakePresetsBuild/JobsNegative.cmake | 0 .../CMakePresetsBuild/JobsNegative.json.in | 17 +++ .../CMakePresetsBuild/RunCMakeTest.cmake | 1 + .../InvalidJobs-test-x-result.txt | 1 + .../InvalidJobs-test-x-stderr.txt | 5 + .../CMakePresetsTest/InvalidJobs.cmake | 0 .../CMakePresetsTest/InvalidJobs.json.in | 19 +++ .../CMakePresetsTest/RunCMakeTest.cmake | 1 + 21 files changed, 227 insertions(+), 22 deletions(-) create mode 100644 Tests/RunCMake/CMakePresetsBuild/JobsNegative-build-default-result.txt create mode 100644 Tests/RunCMake/CMakePresetsBuild/JobsNegative-build-default-stderr.txt create mode 100644 Tests/RunCMake/CMakePresetsBuild/JobsNegative.cmake create mode 100644 Tests/RunCMake/CMakePresetsBuild/JobsNegative.json.in create mode 100644 Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-result.txt create mode 100644 Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-stderr.txt create mode 100644 Tests/RunCMake/CMakePresetsTest/InvalidJobs.cmake create mode 100644 Tests/RunCMake/CMakePresetsTest/InvalidJobs.json.in diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst index bdec8a0126..a4714b0681 100644 --- a/Help/manual/cmake-presets.7.rst +++ b/Help/manual/cmake-presets.7.rst @@ -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 ` 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 ` 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 ` on the command line. +.. _`CMakePresets test jobs`: + ``jobs`` An optional integer. Equivalent to passing :option:`--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 ` on @@ -1445,6 +1455,19 @@ they were added and a summary of the new features and changes is given below. * The `graphviz `_ field was added. + ``11`` + .. versionadded:: 4.3 + + * Changes to `Build Presets `_ + + * The `jobs `_ field no longer accepts negative + values. + + * Changes to `Test Presets `_ + + * The `jobs `_ field no longer accepts negative + values. + Schema ====== diff --git a/Help/manual/presets/schema.json b/Help/manual/presets/schema.json index fdc34dc038..3da3dfd0cc 100644 --- a/Help/manual/presets/schema.json +++ b/Help/manual/presets/schema.json @@ -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.", diff --git a/Source/cmCMakePresetsGraph.h b/Source/cmCMakePresetsGraph.h index 2ba6fba0ab..7485e35d08 100644 --- a/Source/cmCMakePresetsGraph.h +++ b/Source/cmCMakePresetsGraph.h @@ -167,7 +167,7 @@ public: std::string ConfigurePreset; cm::optional InheritConfigureEnvironment; - cm::optional Jobs; + cm::optional Jobs; std::vector Targets; std::string Configuration; cm::optional CleanFirst; @@ -289,7 +289,7 @@ public: cm::optional StopOnFailure; cm::optional EnableFailover; - cm::optional Jobs; + cm::optional Jobs; std::string ResourceSpecFile; cm::optional TestLoad; cm::optional ShowOnly; diff --git a/Source/cmCMakePresetsGraphInternal.h b/Source/cmCMakePresetsGraphInternal.h index eaa215a9c0..02c99eeb72 100644 --- a/Source/cmCMakePresetsGraphInternal.h +++ b/Source/cmCMakePresetsGraphInternal.h @@ -218,6 +218,12 @@ bool PresetIntHelper(int& out, Json::Value const* value, cmJSONState* state); bool PresetOptionalIntHelper(cm::optional& out, Json::Value const* value, cmJSONState* state); +bool PresetUIntHelper(unsigned int& out, Json::Value const* value, + cmJSONState* state); + +bool PresetOptionalUIntHelper(cm::optional& out, + Json::Value const* value, cmJSONState* state); + bool PresetVectorIntHelper(std::vector& out, Json::Value const* value, cmJSONState* state); diff --git a/Source/cmCMakePresetsGraphReadJSON.cxx b/Source/cmCMakePresetsGraphReadJSON.cxx index 4880186cf3..21af48fbdb 100644 --- a/Source/cmCMakePresetsGraphReadJSON.cxx +++ b/Source/cmCMakePresetsGraphReadJSON.cxx @@ -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& 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& out, + Json::Value const* value, cmJSONState* state) +{ + static auto const helper = + JSONHelperBuilder::Optional(PresetUIntHelper); + return helper(out, value, state); +} + bool PresetVectorIntHelper(std::vector& out, Json::Value const* value, cmJSONState* state) { diff --git a/Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx b/Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx index dd95137789..cadbcd1e27 100644 --- a/Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx +++ b/Source/cmCMakePresetsGraphReadJSONBuildPresets.cxx @@ -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) diff --git a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx index 2d2669af86..87ea03157d 100644 --- a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx +++ b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx @@ -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) diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index 7c38ce7bdb..583f26e91d 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -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(jobs)); this->Impl->ParallelLevelSetInCli = true; } diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 40cb855c64..0dadfed848 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -3947,6 +3948,11 @@ int cmake::Build(cmBuildArgs buildArgs, std::vector targets, if ((buildArgs.jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL || buildArgs.jobs == cmake::NO_BUILD_PARALLEL_LEVEL) && expandedPreset->Jobs) { + if (*expandedPreset->Jobs > static_cast(INT_MAX)) { + cmSystemTools::Error( + "The build preset \"jobs\" value is too large.\n"); + return 1; + } buildArgs.jobs = *expandedPreset->Jobs; } diff --git a/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt b/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt index b02663d489..ba962b44f4 100644 --- a/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt +++ b/Tests/RunCMake/CMakePresets/HighVersion-stderr.txt @@ -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, \^$ diff --git a/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt b/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt index fa9a985d67..5ed2857ab8 100644 --- a/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt +++ b/Tests/RunCMake/CMakePresets/LowVersion-stderr.txt @@ -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, \^ diff --git a/Tests/RunCMake/CMakePresetsBuild/JobsNegative-build-default-result.txt b/Tests/RunCMake/CMakePresetsBuild/JobsNegative-build-default-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/CMakePresetsBuild/JobsNegative-build-default-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CMakePresetsBuild/JobsNegative-build-default-stderr.txt b/Tests/RunCMake/CMakePresetsBuild/JobsNegative-build-default-stderr.txt new file mode 100644 index 0000000000..a4ec597182 --- /dev/null +++ b/Tests/RunCMake/CMakePresetsBuild/JobsNegative-build-default-stderr.txt @@ -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 + \^$ diff --git a/Tests/RunCMake/CMakePresetsBuild/JobsNegative.cmake b/Tests/RunCMake/CMakePresetsBuild/JobsNegative.cmake new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/RunCMake/CMakePresetsBuild/JobsNegative.json.in b/Tests/RunCMake/CMakePresetsBuild/JobsNegative.json.in new file mode 100644 index 0000000000..56096102a8 --- /dev/null +++ b/Tests/RunCMake/CMakePresetsBuild/JobsNegative.json.in @@ -0,0 +1,17 @@ +{ + "version": 11, + "configurePresets": [ + { + "name": "default", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${sourceDir}/build/${presetName}" + } + ], + "buildPresets": [ + { + "name": "default", + "configurePreset": "default", + "jobs": -10 + } + ] +} diff --git a/Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake b/Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake index 85d3baf11b..63dc4f3017 100644 --- a/Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake +++ b/Tests/RunCMake/CMakePresetsBuild/RunCMakeTest.cmake @@ -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" "") diff --git a/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-result.txt b/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-stderr.txt b/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-stderr.txt new file mode 100644 index 0000000000..57ecb8ec38 --- /dev/null +++ b/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-stderr.txt @@ -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 + \^$ diff --git a/Tests/RunCMake/CMakePresetsTest/InvalidJobs.cmake b/Tests/RunCMake/CMakePresetsTest/InvalidJobs.cmake new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/RunCMake/CMakePresetsTest/InvalidJobs.json.in b/Tests/RunCMake/CMakePresetsTest/InvalidJobs.json.in new file mode 100644 index 0000000000..6e7de7a7b4 --- /dev/null +++ b/Tests/RunCMake/CMakePresetsTest/InvalidJobs.json.in @@ -0,0 +1,19 @@ +{ + "version": 11, + "configurePresets": [ + { + "name": "default", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${sourceDir}/build/${presetName}" + } + ], + "testPresets": [ + { + "name": "x", + "configurePreset": "default", + "execution": { + "jobs": -10 + } + } + ] +} diff --git a/Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake b/Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake index 5bd0158653..d97294a4f5 100644 --- a/Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake +++ b/Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake @@ -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) From 23615b7ca4fdebf69068f34bf4a0aececa08359f Mon Sep 17 00:00:00 2001 From: Tyler Yankee Date: Mon, 15 Dec 2025 07:08:33 -0500 Subject: [PATCH 5/5] presets: Allow jobs to match `ctest -j` with no value Allow an empty string for the "jobs" field of test presets to mirror the `ctest --parallel` option without a number specified, which corresponds to setting the parallelism to the maximum of the number of processors or 2. Issue: #27070 --- Help/manual/cmake-presets.7.rst | 10 +++++-- Help/manual/presets/schema.json | 16 +++++++++-- Source/cmCMakePresetsErrors.cxx | 6 ++++ Source/cmCMakePresetsErrors.h | 2 ++ Source/cmCMakePresetsGraph.h | 2 +- Source/cmCMakePresetsGraphReadJSON.cxx | 7 +++++ ...cmCMakePresetsGraphReadJSONTestPresets.cxx | 28 ++++++++++++++++++- Source/cmCTest.cxx | 8 ++++-- .../Good-test-jobsProc-stdout.txt | 4 +++ Tests/RunCMake/CMakePresetsTest/Good.json.in | 9 +++++- .../InvalidJobs-test-x-stderr.txt | 2 +- .../JobsProcUnsupported-test-x-result.txt | 1 + .../JobsProcUnsupported-test-x-stderr.txt | 3 ++ .../JobsProcUnsupported.json.in | 19 +++++++++++++ .../ListPresets-test-x-stdout.txt | 1 + .../CMakePresetsTest/RunCMakeTest.cmake | 3 +- 16 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 Tests/RunCMake/CMakePresetsTest/Good-test-jobsProc-stdout.txt create mode 100644 Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported-test-x-result.txt create mode 100644 Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported-test-x-stderr.txt create mode 100644 Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported.json.in diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst index a4714b0681..7ad9e5eeaa 100644 --- a/Help/manual/cmake-presets.7.rst +++ b/Help/manual/cmake-presets.7.rst @@ -902,7 +902,9 @@ that may contain the following fields: :option:`--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 + 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 ```` omitted; additionally, it does not accept negative values. ``resourceSpecFile`` @@ -1465,8 +1467,10 @@ they were added and a summary of the new features and changes is given below. * Changes to `Test Presets `_ - * The `jobs `_ field no longer accepts negative - values. + * The `jobs `_ field now accepts an empty string + representing :option:`--parallel ` with ```` + omitted. In addition, when an integer is specified, it must not be + negative. Schema ====== diff --git a/Help/manual/presets/schema.json b/Help/manual/presets/schema.json index 3da3dfd0cc..ec4b62bac5 100644 --- a/Help/manual/presets/schema.json +++ b/Help/manual/presets/schema.json @@ -1006,9 +1006,19 @@ } }, "testPresetsExecutionJobsV11": { - "type": "integer", - "description": "An optional integer. Equivalent to passing --parallel on the command line.", - "minimum": 0 + "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", diff --git a/Source/cmCMakePresetsErrors.cxx b/Source/cmCMakePresetsErrors.cxx index c77a528815..f2cbe84271 100644 --- a/Source/cmCMakePresetsErrors.cxx +++ b/Source/cmCMakePresetsErrors.cxx @@ -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)); diff --git a/Source/cmCMakePresetsErrors.h b/Source/cmCMakePresetsErrors.h index 8bb9de959c..372ceb8803 100644 --- a/Source/cmCMakePresetsErrors.h +++ b/Source/cmCMakePresetsErrors.h @@ -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); diff --git a/Source/cmCMakePresetsGraph.h b/Source/cmCMakePresetsGraph.h index 7485e35d08..4ce0ace626 100644 --- a/Source/cmCMakePresetsGraph.h +++ b/Source/cmCMakePresetsGraph.h @@ -289,7 +289,7 @@ public: cm::optional StopOnFailure; cm::optional EnableFailover; - cm::optional Jobs; + cm::optional> Jobs; std::string ResourceSpecFile; cm::optional TestLoad; cm::optional ShowOnly; diff --git a/Source/cmCMakePresetsGraphReadJSON.cxx b/Source/cmCMakePresetsGraphReadJSON.cxx index 21af48fbdb..d141b10086 100644 --- a/Source/cmCMakePresetsGraphReadJSON.cxx +++ b/Source/cmCMakePresetsGraphReadJSON.cxx @@ -693,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); } diff --git a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx index 87ea03157d..d7164452ea 100644 --- a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx +++ b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx @@ -301,6 +301,32 @@ auto const TestPresetOptionalExecutionNoTestsActionHelper = JSONHelperBuilder::Optional( TestPresetExecutionNoTestsActionHelper); +bool TestPresetExecutionJobsHelper(cm::optional& 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>( + TestPresetExecutionJobsHelper); + auto const TestPresetExecutionHelper = JSONHelperBuilder::Optional( JSONHelperBuilder::Object() @@ -309,7 +335,7 @@ auto const TestPresetExecutionHelper = .Bind("enableFailover"_s, &TestPreset::ExecutionOptions::EnableFailover, cmCMakePresetsGraphInternal::PresetOptionalBoolHelper, false) .Bind("jobs"_s, &TestPreset::ExecutionOptions::Jobs, - cmCMakePresetsGraphInternal::PresetOptionalUIntHelper, false) + TestPresetOptionalExecutionJobsHelper, false) .Bind("resourceSpecFile"_s, &TestPreset::ExecutionOptions::ResourceSpecFile, cmCMakePresetsGraphInternal::PresetStringHelper, false) diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index 583f26e91d..6dc3726821 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -1710,8 +1710,12 @@ bool cmCTest::SetArgsFromPreset(std::string const& presetName, expandedPreset->Execution->EnableFailover.value_or(false); if (expandedPreset->Execution->Jobs) { - unsigned int jobs = *expandedPreset->Execution->Jobs; - this->SetParallelLevel(static_cast(jobs)); + cm::optional jobs = *expandedPreset->Execution->Jobs; + if (jobs.has_value()) { + this->SetParallelLevel(static_cast(jobs.value())); + } else { + this->SetParallelLevel(cm::nullopt); + } this->Impl->ParallelLevelSetInCli = true; } diff --git a/Tests/RunCMake/CMakePresetsTest/Good-test-jobsProc-stdout.txt b/Tests/RunCMake/CMakePresetsTest/Good-test-jobsProc-stdout.txt new file mode 100644 index 0000000000..c59dcd3afb --- /dev/null +++ b/Tests/RunCMake/CMakePresetsTest/Good-test-jobsProc-stdout.txt @@ -0,0 +1,4 @@ +Test project [^ +]*/Tests/RunCMake/CMakePresetsTest/Good/build/default +.* +100% tests passed, 0 tests failed out of 5 diff --git a/Tests/RunCMake/CMakePresetsTest/Good.json.in b/Tests/RunCMake/CMakePresetsTest/Good.json.in index a4b875a1d8..b436cc86b3 100644 --- a/Tests/RunCMake/CMakePresetsTest/Good.json.in +++ b/Tests/RunCMake/CMakePresetsTest/Good.json.in @@ -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", diff --git a/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-stderr.txt b/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-stderr.txt index 57ecb8ec38..d47899c356 100644 --- a/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-stderr.txt +++ b/Tests/RunCMake/CMakePresetsTest/InvalidJobs-test-x-stderr.txt @@ -1,5 +1,5 @@ ^CMake Error: Could not read presets from [^ ]*/Tests/RunCMake/CMakePresetsTest/InvalidJobs: -CMakePresets\.json:15: "jobs" expected an unsigned integer, got: -10 +CMakePresets\.json:15: Invalid preset "jobs": -10 \^$ diff --git a/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported-test-x-result.txt b/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported-test-x-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported-test-x-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported-test-x-stderr.txt b/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported-test-x-stderr.txt new file mode 100644 index 0000000000..0c0ae4a2e9 --- /dev/null +++ b/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported-test-x-stderr.txt @@ -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$ diff --git a/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported.json.in b/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported.json.in new file mode 100644 index 0000000000..8f09b24fd3 --- /dev/null +++ b/Tests/RunCMake/CMakePresetsTest/JobsProcUnsupported.json.in @@ -0,0 +1,19 @@ +{ + "version": 10, + "configurePresets": [ + { + "name": "default", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${sourceDir}/build" + } + ], + "testPresets": [ + { + "name": "default", + "configurePreset": "default", + "execution": { + "jobs": "" + } + } + ] +} diff --git a/Tests/RunCMake/CMakePresetsTest/ListPresets-test-x-stdout.txt b/Tests/RunCMake/CMakePresetsTest/ListPresets-test-x-stdout.txt index 46ffbcfb60..737a2a0127 100644 --- a/Tests/RunCMake/CMakePresetsTest/ListPresets-test-x-stdout.txt +++ b/Tests/RunCMake/CMakePresetsTest/ListPresets-test-x-stdout.txt @@ -9,4 +9,5 @@ Available test presets: "exclude" "index" "indexFile" + "jobsProc" "showOnly" diff --git a/Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake b/Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake index d97294a4f5..d7fcb48d7d 100644 --- a/Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake +++ b/Tests/RunCMake/CMakePresetsTest/RunCMakeTest.cmake @@ -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" "" @@ -112,6 +112,7 @@ 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)