mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-04 14:50:23 +00:00
ctest_configure: add PRESET option
Allow users to specify a configure preset when using CTest dashboard scripts. Related to Issue #27630
This commit is contained in:
@@ -7,7 +7,9 @@ Perform the :ref:`CTest Configure Step` as a :ref:`Dashboard Client`.
|
||||
|
||||
ctest_configure([BUILD <build-dir>] [SOURCE <source-dir>] [APPEND]
|
||||
[OPTIONS <options>] [RETURN_VALUE <result-var>] [QUIET]
|
||||
[CAPTURE_CMAKE_ERROR <result-var>])
|
||||
[CAPTURE_CMAKE_ERROR <result-var>]
|
||||
[PRESET <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 <options>``
|
||||
Specify a :ref:`semicolon-separated list <CMake Language Lists>` of
|
||||
command-line arguments to pass to the configuration tool.
|
||||
This option is ignored when :variable:`CTEST_CONFIGURE_COMMAND` is used.
|
||||
|
||||
``PRESET <preset>``
|
||||
.. versionadded:: 4.4
|
||||
|
||||
Specify a :manual:`preset <cmake-presets(7)>` 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 <result-var>``
|
||||
Store in the ``<result-var>`` variable the return value of the native
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/memory>
|
||||
#include <cm/optional>
|
||||
#include <cmext/string_view>
|
||||
|
||||
#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<ConfigurePreset> 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<std::string> const& args,
|
||||
using Args = ConfigureArguments;
|
||||
static auto const parser =
|
||||
cmArgumentParser<Args>{ MakeHandlerParser<Args>() } //
|
||||
.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);
|
||||
|
||||
@@ -19,6 +19,7 @@ protected:
|
||||
struct ConfigureArguments : HandlerArguments
|
||||
{
|
||||
std::string Options;
|
||||
std::string Preset;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
21
Tests/RunCMake/ctest_configure/CMakePresets.json.in
Normal file
21
Tests/RunCMake/ctest_configure/CMakePresets.json.in
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
25
Tests/RunCMake/ctest_configure/ConfigurePreset-check.cmake
Normal file
25
Tests/RunCMake/ctest_configure/ConfigurePreset-check.cmake
Normal file
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user