diff --git a/Help/command/ctest_configure.rst b/Help/command/ctest_configure.rst index d9b77a9817..5911554bf8 100644 --- a/Help/command/ctest_configure.rst +++ b/Help/command/ctest_configure.rst @@ -7,7 +7,9 @@ Perform the :ref:`CTest Configure Step` as a :ref:`Dashboard Client`. ctest_configure([BUILD ] [SOURCE ] [APPEND] [OPTIONS ] [RETURN_VALUE ] [QUIET] - [CAPTURE_CMAKE_ERROR ]) + [CAPTURE_CMAKE_ERROR ] + [PRESET ] + ) Configure the project build tree and record results in ``Configure.xml`` for submission with the :command:`ctest_submit` command. @@ -32,6 +34,19 @@ The options are: ``OPTIONS `` Specify a :ref:`semicolon-separated list ` of command-line arguments to pass to the configuration tool. + This option is ignored when :variable:`CTEST_CONFIGURE_COMMAND` is used. + +``PRESET `` + .. versionadded:: 4.4 + + Specify a :manual:`preset ` to use when configuring the + project. Any value set in the CTest script will take priority over a + corresponding setting from the preset. For example, the + :variable:`CTEST_BINARY_DIRECTORY` variable will override the + :preset:`configurePresets.binaryDir` setting from the chosen preset. + + This option is ignored when :variable:`CTEST_CONFIGURE_COMMAND` + is used. ``RETURN_VALUE `` Store in the ```` variable the return value of the native diff --git a/Source/CTest/cmCTestConfigureCommand.cxx b/Source/CTest/cmCTestConfigureCommand.cxx index 459274cb6f..024cc7b1b5 100644 --- a/Source/CTest/cmCTestConfigureCommand.cxx +++ b/Source/CTest/cmCTestConfigureCommand.cxx @@ -4,14 +4,18 @@ #include #include +#include #include #include +#include #include #include +#include #include #include "cmArgumentParser.h" +#include "cmCMakePresetsGraph.h" #include "cmCTest.h" #include "cmDuration.h" #include "cmExecutionStatus.h" @@ -19,6 +23,7 @@ #include "cmGlobalGenerator.h" #include "cmInstrumentation.h" #include "cmInstrumentationQuery.h" +#include "cmJSONState.h" #include "cmList.h" #include "cmMakefile.h" #include "cmStringAlgorithms.h" @@ -27,16 +32,108 @@ #include "cmXMLWriter.h" #include "cmake.h" +using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset; + namespace { +cm::optional LoadPreset(cmExecutionStatus& status, + std::string sourceDirectory, + std::string presetName) +{ + // Load a configure preset after verifying its existence and validity. + cmCMakePresetsGraph presetsGraph; + if (!presetsGraph.ReadProjectPresets(sourceDirectory)) { + status.SetError( + cmStrCat("Could not read presets from \"", sourceDirectory, + "\": ", presetsGraph.parseState.GetErrorMessage())); + return cm::nullopt; + } + + auto preset = presetsGraph.ConfigurePresets.find(presetName); + if (preset == presetsGraph.ConfigurePresets.end()) { + status.SetError(cmStrCat("No such preset in ", sourceDirectory, ": \"", + presetName, '"')); + return cm::nullopt; + } + + if (preset->second.Unexpanded.Hidden) { + status.SetError(cmStrCat("Cannot use hidden preset in ", sourceDirectory, + ": \"", presetName, '"')); + return cm::nullopt; + } + + auto const& expandedPreset = preset->second.Expanded; + if (!expandedPreset) { + status.SetError(cmStrCat("Could not evaluate preset \"", presetName, + "\": Invalid macro expansion.")); + return cm::nullopt; + } + + if (!expandedPreset->ConditionResult) { + status.SetError( + cmStrCat("Cannot use disabled preset \"", presetName, "\".")); + return cm::nullopt; + } + + return expandedPreset; +} + bool ConstructConfigureCommand(cmExecutionStatus& status, cmMakefile& mf, std::string const sourceDirectory, std::string const buildDirectory, std::string const options, + std::string const presetName, std::string& configureCommand) { + configureCommand = cmStrCat('"', cmSystemTools::GetCMakeCommand(), '"'); + configureCommand += " \"-S"; + configureCommand += cmSystemTools::CollapseFullPath(sourceDirectory); + configureCommand += "\""; + + if (!buildDirectory.empty()) { + configureCommand += " \"-B"; + configureCommand += cmSystemTools::CollapseFullPath(buildDirectory); + configureCommand += "\""; + } + cmValue cmakeGenerator = mf.GetDefinition("CTEST_CMAKE_GENERATOR"); - if (!cmNonempty(cmakeGenerator)) { + if (cmNonempty(cmakeGenerator)) { + configureCommand += " \"-G"; + configureCommand += cmakeGenerator; + configureCommand += "\""; + } + + bool presetProvidesBuildDir = false; + bool presetProvidesGenerator = false; + + if (!presetName.empty()) { + auto expandedPreset = LoadPreset(status, sourceDirectory, presetName); + if (!expandedPreset) { + return false; + } + + configureCommand += " \"--preset\""; + configureCommand += " \""; + configureCommand += presetName; + configureCommand += "\""; + + if (!expandedPreset->BinaryDir.empty()) { + presetProvidesBuildDir = true; + } + + if (!expandedPreset->Generator.empty()) { + presetProvidesGenerator = true; + } + } + + if (buildDirectory.empty() && !presetProvidesBuildDir) { + status.SetError("called with no build directory specified. " + "Either use the BUILD argument or set the " + "CTEST_BINARY_DIRECTORY variable."); + return false; + } + + if (!cmNonempty(cmakeGenerator) && !presetProvidesGenerator) { status.SetError( "called with no configure command specified. " "If this is a \"built with CMake\" project, set " @@ -44,40 +141,10 @@ bool ConstructConfigureCommand(cmExecutionStatus& status, cmMakefile& mf, return false; } - bool const multiConfig = [&]() -> bool { - cmake* cm = mf.GetCMakeInstance(); - auto gg = cm->CreateGlobalGenerator(cmakeGenerator); - return gg && gg->IsMultiConfig(); - }(); - - bool const buildTypeInOptions = - options.find("CMAKE_BUILD_TYPE=") != std::string::npos || - options.find("CMAKE_BUILD_TYPE:STRING=") != std::string::npos; - - configureCommand = cmStrCat('"', cmSystemTools::GetCMakeCommand(), '"'); - - auto const optionsList = cmList(options); - for (std::string const& option : optionsList) { - configureCommand += " \""; - configureCommand += option; - configureCommand += "\""; - } - - cmValue cmakeBuildType = mf.GetDefinition("CTEST_CONFIGURATION_TYPE"); - if (!multiConfig && !buildTypeInOptions && cmNonempty(cmakeBuildType)) { - configureCommand += " \"-DCMAKE_BUILD_TYPE:STRING="; - configureCommand += cmakeBuildType; - configureCommand += "\""; - } - if (mf.IsOn("CTEST_USE_LAUNCHERS")) { configureCommand += " \"-DCTEST_USE_LAUNCHERS:BOOL=TRUE\""; } - configureCommand += " \"-G"; - configureCommand += cmakeGenerator; - configureCommand += "\""; - cmValue cmakeGeneratorPlatform = mf.GetDefinition("CTEST_CMAKE_GENERATOR_PLATFORM"); if (cmNonempty(cmakeGeneratorPlatform)) { @@ -94,13 +161,31 @@ bool ConstructConfigureCommand(cmExecutionStatus& status, cmMakefile& mf, configureCommand += "\""; } - configureCommand += " \"-S"; - configureCommand += cmSystemTools::CollapseFullPath(sourceDirectory); - configureCommand += "\""; + // Append OPTIONS to the configure command. + bool const multiConfig = [&]() -> bool { + cmake* cm = mf.GetCMakeInstance(); + auto gg = cm->CreateGlobalGenerator(cmakeGenerator); + return gg && gg->IsMultiConfig(); + }(); + + bool const buildTypeInOptions = + options.find("CMAKE_BUILD_TYPE=") != std::string::npos || + options.find("CMAKE_BUILD_TYPE:STRING=") != std::string::npos; + + auto const optionsList = cmList(options); + for (std::string const& option : optionsList) { + configureCommand += " \""; + configureCommand += option; + configureCommand += "\""; + } + + cmValue cmakeBuildType = mf.GetDefinition("CTEST_CONFIGURATION_TYPE"); + if (!multiConfig && !buildTypeInOptions && cmNonempty(cmakeBuildType)) { + configureCommand += " \"-DCMAKE_BUILD_TYPE:STRING="; + configureCommand += cmakeBuildType; + configureCommand += "\""; + } - configureCommand += " \"-B"; - configureCommand += cmSystemTools::CollapseFullPath(buildDirectory); - configureCommand += "\""; return true; } @@ -111,15 +196,9 @@ bool cmCTestConfigureCommand::ExecuteConfigure(ConfigureArguments const& args, { cmMakefile& mf = status.GetMakefile(); - std::string const buildDirectory = !args.Build.empty() + std::string buildDirectory = !args.Build.empty() ? args.Build : mf.GetDefinition("CTEST_BINARY_DIRECTORY"); - if (buildDirectory.empty()) { - status.SetError("called with no build directory specified. " - "Either use the BUILD argument or set the " - "CTEST_BINARY_DIRECTORY variable."); - return false; - } std::string const sourceDirectory = !args.Source.empty() ? args.Source @@ -135,7 +214,8 @@ bool cmCTestConfigureCommand::ExecuteConfigure(ConfigureArguments const& args, std::string configureCommand = mf.GetDefinition("CTEST_CONFIGURE_COMMAND"); if (configureCommand.empty() && !ConstructConfigureCommand(status, mf, sourceDirectory, buildDirectory, - args.Options, configureCommand)) { + args.Options, args.Preset, + configureCommand)) { return false; } @@ -238,7 +318,8 @@ bool cmCTestConfigureCommand::InitialPass(std::vector const& args, using Args = ConfigureArguments; static auto const parser = cmArgumentParser{ MakeHandlerParser() } // - .Bind("OPTIONS"_s, &ConfigureArguments::Options); + .Bind("OPTIONS"_s, &ConfigureArguments::Options) + .Bind("PRESET"_s, &ConfigureArguments::Preset); return this->Invoke(parser, args, status, [&](ConfigureArguments& a) { return this->ExecuteConfigure(a, status); diff --git a/Source/CTest/cmCTestConfigureCommand.h b/Source/CTest/cmCTestConfigureCommand.h index 6caa56b7fa..bd4de07b23 100644 --- a/Source/CTest/cmCTestConfigureCommand.h +++ b/Source/CTest/cmCTestConfigureCommand.h @@ -19,6 +19,7 @@ protected: struct ConfigureArguments : HandlerArguments { std::string Options; + std::string Preset; }; private: diff --git a/Tests/RunCMake/ctest_configure/CMakePresets.json.in b/Tests/RunCMake/ctest_configure/CMakePresets.json.in new file mode 100644 index 0000000000..4bd4d588ee --- /dev/null +++ b/Tests/RunCMake/ctest_configure/CMakePresets.json.in @@ -0,0 +1,21 @@ +{ + "version": 1, + "cmakeMinimumRequired": { + "major": 3, + "minor": 18, + "patch": 0 + }, + "configurePresets": [ + { + "name": "my-preset", + "generator": "@RunCMake_GENERATOR@", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "MY_CUSTOM_VAR": { + "type": "STRING", + "value": "this-gets-set" + } + } + } + ] +} diff --git a/Tests/RunCMake/ctest_configure/ConfigurePreset-check.cmake b/Tests/RunCMake/ctest_configure/ConfigurePreset-check.cmake new file mode 100644 index 0000000000..b89b32bc9e --- /dev/null +++ b/Tests/RunCMake/ctest_configure/ConfigurePreset-check.cmake @@ -0,0 +1,25 @@ +if(IS_DIRECTORY "${RunCMake_TEST_SOURCE_DIR}/build") + set(RunCMake_TEST_FAILED + "CTEST_BINARY_DIRECTORY did not override buildDir from preset") +endif() + +file(GLOB configure_xml_file "${RunCMake_TEST_BINARY_DIR}/Testing/*/Configure.xml") +if(configure_xml_file) + file(READ "${configure_xml_file}" configure_xml) + if(NOT configure_xml MATCHES "\"--preset\" \"my-preset\"") + set(RunCMake_TEST_FAILED + "Configure.xml does not contain the expected --preset argument") + endif() +else() + set(RunCMake_TEST_FAILED "Configure.xml not found") +endif() + +set(cmakecache_file "${RunCMake_TEST_BINARY_DIR}/CMakeCache.txt") +if(EXISTS "${cmakecache_file}") + file(READ "${cmakecache_file}" cmakecache_txt) + if(NOT cmakecache_txt MATCHES "MY_CUSTOM_VAR:STRING=this-gets-set") + set(RunCMake_TEST_FAILED "CMakeCache.txt does not contain MY_CUSTOM_VAR") + endif() +else() + set(RunCMake_TEST_FAILED "CMakeCache.txt not found") +endif() diff --git a/Tests/RunCMake/ctest_configure/RunCMakeTest.cmake b/Tests/RunCMake/ctest_configure/RunCMakeTest.cmake index fc1b02cf07..a36911b427 100644 --- a/Tests/RunCMake/ctest_configure/RunCMakeTest.cmake +++ b/Tests/RunCMake/ctest_configure/RunCMakeTest.cmake @@ -8,3 +8,10 @@ function(run_ctest_configure CASE_NAME) endfunction() run_ctest_configure(ConfigureQuiet QUIET) + +set(RunCMake_TEST_SOURCE_DIR "${RunCMake_BINARY_DIR}/ConfigurePreset") +configure_file( + "${RunCMake_SOURCE_DIR}/CMakePresets.json.in" + "${RunCMake_TEST_SOURCE_DIR}/CMakePresets.json" + @ONLY) +run_ctest_configure(ConfigurePreset PRESET my-preset)