diff --git a/Help/command/ctest_build.rst b/Help/command/ctest_build.rst index bce17396e0..10922a7870 100644 --- a/Help/command/ctest_build.rst +++ b/Help/command/ctest_build.rst @@ -15,6 +15,7 @@ Perform the :ref:`CTest Build Step` as a :ref:`Dashboard Client`. [NUMBER_WARNINGS ] [RETURN_VALUE ] [CAPTURE_CMAKE_ERROR ] + [PRESET ] ) Build the project and store results in ``Build.xml`` @@ -75,6 +76,15 @@ The options are: ``NUMBER_WARNINGS `` Store the number of build warnings detected in the given variable. +``PRESET `` + .. versionadded:: 4.4 + + Specify a :manual:`preset ` to use when building the + project. Any value set in the CTest script will take priority over a + corresponding setting from the preset. For example, the ``TARGET`` + argument will override the :preset:`buildPresets.targets` setting from + the chosen preset. + ``RETURN_VALUE `` Store the return value of the native build tool in the given variable. diff --git a/Help/release/dev/ctest_build_preset_option.rst b/Help/release/dev/ctest_build_preset_option.rst new file mode 100644 index 0000000000..d46e6dc7ac --- /dev/null +++ b/Help/release/dev/ctest_build_preset_option.rst @@ -0,0 +1,5 @@ +ctest_build_preset_option +------------------------- + +* The :command:`ctest_build` command gained a ``PRESET`` option + drive the build through a build preset. diff --git a/Source/CTest/cmCTestBuildCommand.cxx b/Source/CTest/cmCTestBuildCommand.cxx index d74ab55390..7bbf5a366c 100644 --- a/Source/CTest/cmCTestBuildCommand.cxx +++ b/Source/CTest/cmCTestBuildCommand.cxx @@ -6,14 +6,17 @@ #include #include +#include #include #include "cmArgumentParser.h" +#include "cmCMakePresetsGraph.h" #include "cmCTest.h" #include "cmCTestBuildHandler.h" #include "cmCTestGenericHandler.h" #include "cmExecutionStatus.h" #include "cmGlobalGenerator.h" +#include "cmJSONState.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmStringAlgorithms.h" @@ -21,6 +24,8 @@ #include "cmValue.h" #include "cmake.h" +using BuildPreset = cmCMakePresetsGraph::BuildPreset; + bool cmCTestBuildCommand::InitialPass(std::vector const& args, cmExecutionStatus& status) const { @@ -32,7 +37,8 @@ bool cmCTestBuildCommand::InitialPass(std::vector const& args, .Bind("CONFIGURATION"_s, &BuildArguments::Configuration) .Bind("FLAGS"_s, &BuildArguments::Flags) .Bind("PROJECT_NAME"_s, &BuildArguments::ProjectName) - .Bind("PARALLEL_LEVEL"_s, &BuildArguments::ParallelLevel); + .Bind("PARALLEL_LEVEL"_s, &BuildArguments::ParallelLevel) + .Bind("PRESET"_s, &BuildArguments::Preset); return this->Invoke(parser, args, status, [&](BuildArguments& a) { return this->ExecuteHandlerCommand(a, status); @@ -46,32 +52,87 @@ std::unique_ptr cmCTestBuildCommand::InitializeHandler( auto const& args = static_cast(arguments); auto handler = cm::make_unique(this->CTest); + // Build configuration is set according to the following priority order: + // 1) The CONFIGURATION option to ctest_build() + // 2) CTEST_BUILD_CONFIGURATION script variable + // 3) CTEST_CONFIGURATION_TYPE script variable + // 4) The ctest -C command line argument + // 5) The configuration entry from the build preset + cmValue ctestBuildConfiguration = + mf.GetDefinition("CTEST_BUILD_CONFIGURATION"); + std::string cmakeBuildConfiguration = cmNonempty(args.Configuration) + ? args.Configuration + : cmNonempty(ctestBuildConfiguration) ? *ctestBuildConfiguration + : this->CTest->GetConfigType(); + + std::string const& cmakeBuildAdditionalFlags = cmNonempty(args.Flags) + ? args.Flags + : mf.GetSafeDefinition("CTEST_BUILD_FLAGS"); + std::string const& cmakeBuildTarget = cmNonempty(args.Target) + ? args.Target + : mf.GetSafeDefinition("CTEST_BUILD_TARGET"); + cmValue ctestBuildCommand = mf.GetDefinition("CTEST_BUILD_COMMAND"); if (cmNonempty(ctestBuildCommand)) { this->CTest->SetCTestConfiguration("MakeCommand", *ctestBuildCommand, args.Quiet); + } else if (!args.Preset.empty()) { + std::string const sourceDirectory = + mf.GetSafeDefinition("CTEST_SOURCE_DIRECTORY"); + + cmCMakePresetsGraph presetsGraph; + if (!presetsGraph.ReadProjectPresets(sourceDirectory)) { + status.SetError( + cmStrCat("Could not read presets from \"", sourceDirectory, + "\": ", presetsGraph.parseState.GetErrorMessage())); + return nullptr; + } + + auto resolveResult = + presetsGraph.ResolvePreset(args.Preset, presetsGraph.BuildPresets); + auto resolveError = cmCMakePresetsGraph::FormatPresetError( + resolveResult.StatusCode, resolveResult.ErrorPresetName, + sourceDirectory); + if (resolveError) { + status.SetError(*resolveError); + return nullptr; + } + + std::string buildCommand = + cmStrCat('"', cmSystemTools::GetCMakeCommand(), '"'); + buildCommand += " --build . --preset \""; + buildCommand += args.Preset; + buildCommand += "\""; + + if (!cmakeBuildConfiguration.empty()) { + buildCommand += " --config \""; + buildCommand += cmakeBuildConfiguration; + buildCommand += "\""; + } + + if (!cmakeBuildTarget.empty()) { + buildCommand += " --target \""; + buildCommand += cmakeBuildTarget; + buildCommand += "\""; + } + + if (!args.ParallelLevel.empty()) { + buildCommand += " --parallel "; + buildCommand += args.ParallelLevel; + } + + if (!cmakeBuildAdditionalFlags.empty()) { + buildCommand += " -- "; + buildCommand += cmakeBuildAdditionalFlags; + } + + cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "SetMakeCommand:" << buildCommand << "\n", args.Quiet); + this->CTest->SetCTestConfiguration("MakeCommand", buildCommand, + args.Quiet); } else { cmValue cmakeGeneratorName = mf.GetDefinition("CTEST_CMAKE_GENERATOR"); - // Build configuration is determined by: CONFIGURATION argument, - // or CTEST_BUILD_CONFIGURATION script variable, or - // CTEST_CONFIGURATION_TYPE script variable, or ctest -C command - // line argument... in that order. - // - cmValue ctestBuildConfiguration = - mf.GetDefinition("CTEST_BUILD_CONFIGURATION"); - std::string cmakeBuildConfiguration = cmNonempty(args.Configuration) - ? args.Configuration - : cmNonempty(ctestBuildConfiguration) ? *ctestBuildConfiguration - : this->CTest->GetConfigType(); - - std::string const& cmakeBuildAdditionalFlags = cmNonempty(args.Flags) - ? args.Flags - : mf.GetSafeDefinition("CTEST_BUILD_FLAGS"); - std::string const& cmakeBuildTarget = cmNonempty(args.Target) - ? args.Target - : mf.GetSafeDefinition("CTEST_BUILD_TARGET"); - if (cmNonempty(cmakeGeneratorName)) { if (cmakeBuildConfiguration.empty()) { cmakeBuildConfiguration = "Release"; diff --git a/Source/CTest/cmCTestBuildCommand.h b/Source/CTest/cmCTestBuildCommand.h index b83f5dba9d..04a7068aba 100644 --- a/Source/CTest/cmCTestBuildCommand.h +++ b/Source/CTest/cmCTestBuildCommand.h @@ -23,6 +23,7 @@ protected: std::string Flags; std::string ProjectName; std::string ParallelLevel; + std::string Preset; }; private: diff --git a/Tests/RunCMake/ctest_build/BuildPreset-check.cmake b/Tests/RunCMake/ctest_build/BuildPreset-check.cmake new file mode 100644 index 0000000000..7dd0228a84 --- /dev/null +++ b/Tests/RunCMake/ctest_build/BuildPreset-check.cmake @@ -0,0 +1,10 @@ +file(GLOB build_xml_file "${RunCMake_TEST_BINARY_DIR}/Testing/*/Build.xml") +if(build_xml_file) + file(READ "${build_xml_file}" build_xml) + if(NOT build_xml MATCHES "--preset.*my-build-preset") + set(RunCMake_TEST_FAILED + "Build.xml does not contain the expected --preset argument") + endif() +else() + set(RunCMake_TEST_FAILED "Build.xml not found") +endif() diff --git a/Tests/RunCMake/ctest_build/CMakePresets.json.in b/Tests/RunCMake/ctest_build/CMakePresets.json.in new file mode 100644 index 0000000000..a676de1f33 --- /dev/null +++ b/Tests/RunCMake/ctest_build/CMakePresets.json.in @@ -0,0 +1,21 @@ +{ + "version": 2, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "my-configure-preset", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${sourceDir}/../BuildPreset-build" + } + ], + "buildPresets": [ + { + "name": "my-build-preset", + "configurePreset": "my-configure-preset" + } + ] +} diff --git a/Tests/RunCMake/ctest_build/RunCMakeTest.cmake b/Tests/RunCMake/ctest_build/RunCMakeTest.cmake index 34ef64fe5f..5ade9be188 100644 --- a/Tests/RunCMake/ctest_build/RunCMakeTest.cmake +++ b/Tests/RunCMake/ctest_build/RunCMakeTest.cmake @@ -71,6 +71,15 @@ set(RunCMake_BUILD_COMMAND "${COLOR_WARNING}") run_ctest(IgnoreColor) unset(RunCMake_BUILD_COMMAND) +set(RunCMake_USE_CUSTOM_BUILD_COMMAND FALSE) +set(RunCMake_TEST_SOURCE_DIR "${RunCMake_BINARY_DIR}/BuildPreset") +configure_file( + "${RunCMake_SOURCE_DIR}/CMakePresets.json.in" + "${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json" + @ONLY) +run_ctest_build(BuildPreset PRESET my-build-preset) +unset(RunCMake_TEST_SOURCE_DIR) + set(RunCMake_USE_CUSTOM_BUILD_COMMAND FALSE) if(RunCMake_GENERATOR MATCHES "Ninja") function(run_NinjaLauncherSingleBuildFailure)