diff options
author | Sam Freed <safreed@microsoft.com> | 2020-12-14 18:06:08 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2021-02-01 16:59:40 (GMT) |
commit | 676ecf0d37cf5cfda390f52ce60babcb5da78108 (patch) | |
tree | 79129100ed585db7a91b82778166c1e1d3d6f7e5 | |
parent | 4f4f2028b8e2c45eb6940d1ec5069caad9b606a2 (diff) | |
download | CMake-676ecf0d37cf5cfda390f52ce60babcb5da78108.zip CMake-676ecf0d37cf5cfda390f52ce60babcb5da78108.tar.gz CMake-676ecf0d37cf5cfda390f52ce60babcb5da78108.tar.bz2 |
cmake-presets: Add build and test presets
Fixes: #21391
-rw-r--r-- | Source/QtDialog/QCMake.cxx | 13 | ||||
-rw-r--r-- | Source/cmCMakePresetsFile.cxx | 1260 | ||||
-rw-r--r-- | Source/cmCMakePresetsFile.h | 307 | ||||
-rw-r--r-- | Source/cmCTest.cxx | 340 | ||||
-rw-r--r-- | Source/cmCTest.h | 6 | ||||
-rw-r--r-- | Source/cmake.cxx | 224 | ||||
-rw-r--r-- | Source/cmake.h | 10 | ||||
-rw-r--r-- | Source/cmakemain.cxx | 49 | ||||
-rw-r--r-- | Source/ctest.cxx | 2 |
9 files changed, 1868 insertions, 343 deletions
diff --git a/Source/QtDialog/QCMake.cxx b/Source/QtDialog/QCMake.cxx index 2f41f70..f593f83 100644 --- a/Source/QtDialog/QCMake.cxx +++ b/Source/QtDialog/QCMake.cxx @@ -68,9 +68,9 @@ QCMake::QCMake(QObject* p) connect(&this->LoadPresetsTimer, &QTimer::timeout, this, [this]() { this->loadPresets(); if (!this->PresetName.isEmpty() && - this->CMakePresetsFile.Presets.find( + this->CMakePresetsFile.ConfigurePresets.find( std::string(this->PresetName.toLocal8Bit())) == - this->CMakePresetsFile.Presets.end()) { + this->CMakePresetsFile.ConfigurePresets.end()) { this->setPreset(QString{}); } }); @@ -158,7 +158,7 @@ void QCMake::setPreset(const QString& name, bool setBinary) if (!name.isNull()) { std::string presetName(name.toLocal8Bit()); auto const& expandedPreset = - this->CMakePresetsFile.Presets[presetName].Expanded; + this->CMakePresetsFile.ConfigurePresets[presetName].Expanded; if (expandedPreset) { if (setBinary) { QString binaryDir = @@ -420,7 +420,8 @@ QCMakePropertyList QCMake::properties() const if (!this->PresetName.isNull()) { std::string presetName(this->PresetName.toLocal8Bit()); - auto const& p = this->CMakePresetsFile.Presets.at(presetName).Expanded; + auto const& p = + this->CMakePresetsFile.ConfigurePresets.at(presetName).Expanded; if (p) { for (auto const& v : p->CacheVariables) { if (!v.second) { @@ -535,8 +536,8 @@ void QCMake::loadPresets() this->LastLoadPresetsResult = result; QVector<QCMakePreset> presets; - for (auto const& name : this->CMakePresetsFile.PresetOrder) { - auto const& it = this->CMakePresetsFile.Presets[name]; + for (auto const& name : this->CMakePresetsFile.ConfigurePresetOrder) { + auto const& it = this->CMakePresetsFile.ConfigurePresets[name]; auto const& p = it.Unexpanded; if (p.Hidden) { continue; diff --git a/Source/cmCMakePresetsFile.cxx b/Source/cmCMakePresetsFile.cxx index 8762158..e5557e6 100644 --- a/Source/cmCMakePresetsFile.cxx +++ b/Source/cmCMakePresetsFile.cxx @@ -5,6 +5,7 @@ #include <algorithm> #include <cstdlib> #include <functional> +#include <iostream> #include <iterator> #include <utility> @@ -20,6 +21,26 @@ #include "cmSystemTools.h" #include "cmVersion.h" +#define CHECK_OK(expr) \ + { \ + auto _result = expr; \ + if (_result != ReadFileResult::READ_OK) \ + return _result; \ + } + +#define CHECK_EXPAND(out, field, expanders) \ + { \ + switch (ExpandMacros(field, expanders)) { \ + case ExpandMacroResult::Error: \ + return false; \ + case ExpandMacroResult::Ignore: \ + out.reset(); \ + return true; \ + case ExpandMacroResult::Ok: \ + break; \ + } \ + } + namespace { enum class CycleStatus { @@ -30,12 +51,13 @@ enum class CycleStatus using ReadFileResult = cmCMakePresetsFile::ReadFileResult; using CacheVariable = cmCMakePresetsFile::CacheVariable; -using UnexpandedPreset = cmCMakePresetsFile::UnexpandedPreset; -using ExpandedPreset = cmCMakePresetsFile::ExpandedPreset; +using ConfigurePreset = cmCMakePresetsFile::ConfigurePreset; +using BuildPreset = cmCMakePresetsFile::BuildPreset; +using TestPreset = cmCMakePresetsFile::TestPreset; using ArchToolsetStrategy = cmCMakePresetsFile::ArchToolsetStrategy; constexpr int MIN_VERSION = 1; -constexpr int MAX_VERSION = 1; +constexpr int MAX_VERSION = 2; struct CMakeVersion { @@ -47,7 +69,9 @@ struct CMakeVersion struct RootPresets { CMakeVersion CMakeMinimumRequired; - std::vector<cmCMakePresetsFile::UnexpandedPreset> Presets; + std::vector<cmCMakePresetsFile::ConfigurePreset> ConfigurePresets; + std::vector<cmCMakePresetsFile::BuildPreset> BuildPresets; + std::vector<cmCMakePresetsFile::TestPreset> TestPresets; }; cmJSONHelper<std::nullptr_t, ReadFileResult> VendorHelper(ReadFileResult error) @@ -183,35 +207,43 @@ auto const PresetOptionalBoolHelper = cmJSONOptionalHelper<bool, ReadFileResult>(ReadFileResult::READ_OK, PresetBoolHelper); +auto const PresetIntHelper = cmJSONIntHelper<ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET); + +auto const PresetOptionalIntHelper = cmJSONOptionalHelper<int, ReadFileResult>( + ReadFileResult::READ_OK, PresetIntHelper); + +auto const PresetVectorIntHelper = cmJSONVectorHelper<int, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, PresetIntHelper); + auto const PresetWarningsHelper = - cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>( + cmJSONObjectHelper<ConfigurePreset, ReadFileResult>( ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) - .Bind("dev"_s, &UnexpandedPreset::WarnDev, PresetOptionalBoolHelper, false) - .Bind("deprecated"_s, &UnexpandedPreset::WarnDeprecated, + .Bind("dev"_s, &ConfigurePreset::WarnDev, PresetOptionalBoolHelper, false) + .Bind("deprecated"_s, &ConfigurePreset::WarnDeprecated, PresetOptionalBoolHelper, false) - .Bind("uninitialized"_s, &UnexpandedPreset::WarnUninitialized, + .Bind("uninitialized"_s, &ConfigurePreset::WarnUninitialized, PresetOptionalBoolHelper, false) - .Bind("unusedCli"_s, &UnexpandedPreset::WarnUnusedCli, + .Bind("unusedCli"_s, &ConfigurePreset::WarnUnusedCli, PresetOptionalBoolHelper, false) - .Bind("systemVars"_s, &UnexpandedPreset::WarnSystemVars, + .Bind("systemVars"_s, &ConfigurePreset::WarnSystemVars, PresetOptionalBoolHelper, false); auto const PresetErrorsHelper = - cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>( + cmJSONObjectHelper<ConfigurePreset, ReadFileResult>( ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) - .Bind("dev"_s, &UnexpandedPreset::ErrorDev, PresetOptionalBoolHelper, - false) - .Bind("deprecated"_s, &UnexpandedPreset::ErrorDeprecated, + .Bind("dev"_s, &ConfigurePreset::ErrorDev, PresetOptionalBoolHelper, false) + .Bind("deprecated"_s, &ConfigurePreset::ErrorDeprecated, PresetOptionalBoolHelper, false); auto const PresetDebugHelper = - cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>( + cmJSONObjectHelper<ConfigurePreset, ReadFileResult>( ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) - .Bind("output"_s, &UnexpandedPreset::DebugOutput, PresetOptionalBoolHelper, + .Bind("output"_s, &ConfigurePreset::DebugOutput, PresetOptionalBoolHelper, false) - .Bind("tryCompile"_s, &UnexpandedPreset::DebugTryCompile, + .Bind("tryCompile"_s, &ConfigurePreset::DebugTryCompile, PresetOptionalBoolHelper, false) - .Bind("find"_s, &UnexpandedPreset::DebugFind, PresetOptionalBoolHelper, + .Bind("find"_s, &ConfigurePreset::DebugFind, PresetOptionalBoolHelper, false); ReadFileResult ArchToolsetStrategyHelper( @@ -239,18 +271,18 @@ ReadFileResult ArchToolsetStrategyHelper( return ReadFileResult::INVALID_PRESET; } -std::function<ReadFileResult(UnexpandedPreset&, const Json::Value*)> +std::function<ReadFileResult(ConfigurePreset&, const Json::Value*)> ArchToolsetHelper( - std::string UnexpandedPreset::*valueField, - cm::optional<ArchToolsetStrategy> UnexpandedPreset::*strategyField) + std::string ConfigurePreset::*valueField, + cm::optional<ArchToolsetStrategy> ConfigurePreset::*strategyField) { auto const objectHelper = - cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>( + cmJSONObjectHelper<ConfigurePreset, ReadFileResult>( ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) .Bind("value", valueField, PresetStringHelper, false) .Bind("strategy", strategyField, ArchToolsetStrategyHelper, false); return [valueField, strategyField, objectHelper]( - UnexpandedPreset& out, const Json::Value* value) -> ReadFileResult { + ConfigurePreset& out, const Json::Value* value) -> ReadFileResult { if (!value) { (out.*valueField).clear(); out.*strategyField = cm::nullopt; @@ -272,41 +304,397 @@ ArchToolsetHelper( } auto const ArchitectureHelper = ArchToolsetHelper( - &UnexpandedPreset::Architecture, &UnexpandedPreset::ArchitectureStrategy); + &ConfigurePreset::Architecture, &ConfigurePreset::ArchitectureStrategy); auto const ToolsetHelper = ArchToolsetHelper( - &UnexpandedPreset::Toolset, &UnexpandedPreset::ToolsetStrategy); + &ConfigurePreset::Toolset, &ConfigurePreset::ToolsetStrategy); -auto const PresetHelper = - cmJSONObjectHelper<UnexpandedPreset, ReadFileResult>( +auto const ConfigurePresetHelper = + cmJSONObjectHelper<ConfigurePreset, ReadFileResult>( ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) - .Bind("name"_s, &UnexpandedPreset::Name, PresetStringHelper) - .Bind("inherits"_s, &UnexpandedPreset::Inherits, PresetInheritsHelper, + .Bind("name"_s, &ConfigurePreset::Name, PresetStringHelper) + .Bind("inherits"_s, &ConfigurePreset::Inherits, PresetInheritsHelper, false) - .Bind("hidden"_s, &UnexpandedPreset::Hidden, PresetBoolHelper, false) + .Bind("hidden"_s, &ConfigurePreset::Hidden, PresetBoolHelper, false) .Bind<std::nullptr_t>("vendor"_s, nullptr, VendorHelper(ReadFileResult::INVALID_PRESET), false) - .Bind("displayName"_s, &UnexpandedPreset::DisplayName, PresetStringHelper, + .Bind("displayName"_s, &ConfigurePreset::DisplayName, PresetStringHelper, false) - .Bind("description"_s, &UnexpandedPreset::Description, PresetStringHelper, + .Bind("description"_s, &ConfigurePreset::Description, PresetStringHelper, false) - .Bind("generator"_s, &UnexpandedPreset::Generator, PresetStringHelper, + .Bind("generator"_s, &ConfigurePreset::Generator, PresetStringHelper, false) .Bind("architecture"_s, ArchitectureHelper, false) .Bind("toolset"_s, ToolsetHelper, false) - .Bind("binaryDir"_s, &UnexpandedPreset::BinaryDir, PresetStringHelper, + .Bind("binaryDir"_s, &ConfigurePreset::BinaryDir, PresetStringHelper, false) .Bind<std::string>("cmakeExecutable"_s, nullptr, PresetStringHelper, false) - .Bind("cacheVariables"_s, &UnexpandedPreset::CacheVariables, + .Bind("cacheVariables"_s, &ConfigurePreset::CacheVariables, VariablesHelper, false) - .Bind("environment"_s, &UnexpandedPreset::Environment, - EnvironmentMapHelper, false) + .Bind("environment"_s, &ConfigurePreset::Environment, EnvironmentMapHelper, + false) .Bind("warnings"_s, PresetWarningsHelper, false) .Bind("errors"_s, PresetErrorsHelper, false) .Bind("debug"_s, PresetDebugHelper, false); -auto const PresetsHelper = - cmJSONVectorHelper<UnexpandedPreset, ReadFileResult>( - ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS, PresetHelper); +auto const BuildPresetHelper = + cmJSONObjectHelper<BuildPreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) + .Bind("name"_s, &BuildPreset::Name, PresetStringHelper) + .Bind("inherits"_s, &BuildPreset::Inherits, PresetInheritsHelper, false) + .Bind("hidden"_s, &BuildPreset::Hidden, PresetBoolHelper, false) + .Bind<std::nullptr_t>("vendor"_s, nullptr, + VendorHelper(ReadFileResult::INVALID_PRESET), false) + .Bind("displayName"_s, &BuildPreset::DisplayName, PresetStringHelper, + false) + .Bind("description"_s, &BuildPreset::Description, PresetStringHelper, + false) + .Bind("environment"_s, &BuildPreset::Environment, EnvironmentMapHelper, + false) + .Bind("configurePreset"_s, &BuildPreset::ConfigurePreset, + PresetStringHelper, false) + .Bind("inheritConfigureEnvironment"_s, + &BuildPreset::InheritConfigureEnvironment, PresetOptionalBoolHelper, + false) + .Bind("jobs"_s, &BuildPreset::Jobs, PresetOptionalIntHelper, false) + .Bind("targets"_s, &BuildPreset::Targets, PresetVectorStringHelper, false) + .Bind("configuration"_s, &BuildPreset::Configuration, PresetStringHelper, + false) + .Bind("cleanFirst"_s, &BuildPreset::CleanFirst, PresetOptionalBoolHelper, + false) + .Bind("verbose"_s, &BuildPreset::Verbose, PresetOptionalBoolHelper, false) + .Bind("nativeToolOptions"_s, &BuildPreset::NativeToolOptions, + PresetVectorStringHelper, false); + +ReadFileResult TestPresetOutputVerbosityHelper( + TestPreset::OutputOptions::VerbosityEnum& out, const Json::Value* value) +{ + if (!value) { + out = TestPreset::OutputOptions::VerbosityEnum::Default; + return ReadFileResult::READ_OK; + } + + if (!value->isString()) { + return ReadFileResult::INVALID_PRESET; + } + + if (value->asString() == "default") { + out = TestPreset::OutputOptions::VerbosityEnum::Default; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "verbose") { + out = TestPreset::OutputOptions::VerbosityEnum::Verbose; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "extra") { + out = TestPreset::OutputOptions::VerbosityEnum::Extra; + return ReadFileResult::READ_OK; + } + + return ReadFileResult::INVALID_PRESET; +} + +auto const TestPresetOptionalOutputVerbosityHelper = + cmJSONOptionalHelper<TestPreset::OutputOptions::VerbosityEnum, + ReadFileResult>(ReadFileResult::READ_OK, + TestPresetOutputVerbosityHelper); + +auto const TestPresetOptionalOutputHelper = + cmJSONOptionalHelper<TestPreset::OutputOptions, ReadFileResult>( + ReadFileResult::READ_OK, + cmJSONObjectHelper<TestPreset::OutputOptions, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) + .Bind("shortProgress"_s, &TestPreset::OutputOptions::ShortProgress, + PresetOptionalBoolHelper, false) + .Bind("verbosity"_s, &TestPreset::OutputOptions::Verbosity, + TestPresetOptionalOutputVerbosityHelper, false) + .Bind("debug"_s, &TestPreset::OutputOptions::Debug, + PresetOptionalBoolHelper, false) + .Bind("outputOnFailure"_s, &TestPreset::OutputOptions::OutputOnFailure, + PresetOptionalBoolHelper, false) + .Bind("quiet"_s, &TestPreset::OutputOptions::Quiet, + PresetOptionalBoolHelper, false) + .Bind("outputLogFile"_s, &TestPreset::OutputOptions::OutputLogFile, + PresetStringHelper, false) + .Bind("labelSummary"_s, &TestPreset::OutputOptions::LabelSummary, + PresetOptionalBoolHelper, false) + .Bind("subprojectSummary"_s, + &TestPreset::OutputOptions::SubprojectSummary, + PresetOptionalBoolHelper, false) + .Bind("maxPassedTestOutputSize"_s, + &TestPreset::OutputOptions::MaxPassedTestOutputSize, + PresetOptionalIntHelper, false) + .Bind("maxFailedTestOutputSize"_s, + &TestPreset::OutputOptions::MaxFailedTestOutputSize, + PresetOptionalIntHelper, false) + .Bind("maxTestNameWidth"_s, &TestPreset::OutputOptions::MaxTestNameWidth, + PresetOptionalIntHelper, false)); + +auto const TestPresetOptionalFilterIncludeIndexObjectHelper = + cmJSONOptionalHelper<TestPreset::IncludeOptions::IndexOptions, + ReadFileResult>( + ReadFileResult::READ_OK, + cmJSONObjectHelper<TestPreset::IncludeOptions::IndexOptions, + ReadFileResult>(ReadFileResult::READ_OK, + ReadFileResult::INVALID_PRESET) + .Bind("start"_s, &TestPreset::IncludeOptions::IndexOptions::Start, + PresetOptionalIntHelper, false) + .Bind("end"_s, &TestPreset::IncludeOptions::IndexOptions::End, + PresetOptionalIntHelper, false) + .Bind("stride"_s, &TestPreset::IncludeOptions::IndexOptions::Stride, + PresetOptionalIntHelper, false) + .Bind("specificTests"_s, + &TestPreset::IncludeOptions::IndexOptions::SpecificTests, + PresetVectorIntHelper, false)); + +ReadFileResult TestPresetOptionalFilterIncludeIndexHelper( + cm::optional<TestPreset::IncludeOptions::IndexOptions>& out, + const Json::Value* value) +{ + if (!value) { + out = cm::nullopt; + return ReadFileResult::READ_OK; + } + + if (value->isString()) { + out.emplace(); + out->IndexFile = value->asString(); + return ReadFileResult::READ_OK; + } + + if (value->isObject()) { + return TestPresetOptionalFilterIncludeIndexObjectHelper(out, value); + } + + return ReadFileResult::INVALID_PRESET; +} + +auto const TestPresetOptionalFilterIncludeHelper = + cmJSONOptionalHelper<TestPreset::IncludeOptions, ReadFileResult>( + ReadFileResult::READ_OK, + cmJSONObjectHelper<TestPreset::IncludeOptions, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET) + .Bind("name"_s, &TestPreset::IncludeOptions::Name, PresetStringHelper, + false) + .Bind("label"_s, &TestPreset::IncludeOptions::Label, PresetStringHelper, + false) + .Bind("index"_s, &TestPreset::IncludeOptions::Index, + TestPresetOptionalFilterIncludeIndexHelper, false) + .Bind("useUnion"_s, &TestPreset::IncludeOptions::UseUnion, + PresetOptionalBoolHelper, false)); + +auto const TestPresetOptionalFilterExcludeFixturesHelper = + cmJSONOptionalHelper<TestPreset::ExcludeOptions::FixturesOptions, + ReadFileResult>( + ReadFileResult::READ_OK, + cmJSONObjectHelper<TestPreset::ExcludeOptions::FixturesOptions, + ReadFileResult>(ReadFileResult::READ_OK, + ReadFileResult::INVALID_PRESET) + .Bind("any"_s, &TestPreset::ExcludeOptions::FixturesOptions::Any, + PresetStringHelper, false) + .Bind("setup"_s, &TestPreset::ExcludeOptions::FixturesOptions::Setup, + PresetStringHelper, false) + .Bind("cleanup"_s, &TestPreset::ExcludeOptions::FixturesOptions::Cleanup, + PresetStringHelper, false)); + +auto const TestPresetOptionalFilterExcludeHelper = + cmJSONOptionalHelper<TestPreset::ExcludeOptions, ReadFileResult>( + ReadFileResult::READ_OK, + cmJSONObjectHelper<TestPreset::ExcludeOptions, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET) + .Bind("name"_s, &TestPreset::ExcludeOptions::Name, PresetStringHelper, + false) + .Bind("label"_s, &TestPreset::ExcludeOptions::Label, PresetStringHelper, + false) + .Bind("fixtures"_s, &TestPreset::ExcludeOptions::Fixtures, + TestPresetOptionalFilterExcludeFixturesHelper, false)); + +ReadFileResult TestPresetExecutionShowOnlyHelper( + TestPreset::ExecutionOptions::ShowOnlyEnum& out, const Json::Value* value) +{ + if (!value || !value->isString()) { + return ReadFileResult::INVALID_PRESET; + } + + if (value->asString() == "human") { + out = TestPreset::ExecutionOptions::ShowOnlyEnum::Human; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "json-v1") { + out = TestPreset::ExecutionOptions::ShowOnlyEnum::JsonV1; + return ReadFileResult::READ_OK; + } + + return ReadFileResult::INVALID_PRESET; +} + +auto const TestPresetOptionalExecutionShowOnlyHelper = + cmJSONOptionalHelper<TestPreset::ExecutionOptions::ShowOnlyEnum, + ReadFileResult>(ReadFileResult::READ_OK, + TestPresetExecutionShowOnlyHelper); + +ReadFileResult TestPresetExecutionModeHelper( + TestPreset::ExecutionOptions::RepeatOptions::ModeEnum& out, + const Json::Value* value) +{ + if (!value) { + return ReadFileResult::READ_OK; + } + + if (!value->isString()) { + return ReadFileResult::INVALID_PRESET; + } + + if (value->asString() == "until-fail") { + out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilFail; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "until-pass") { + out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::UntilPass; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "after-timeout") { + out = TestPreset::ExecutionOptions::RepeatOptions::ModeEnum::AfterTimeout; + return ReadFileResult::READ_OK; + } + + return ReadFileResult::INVALID_PRESET; +} + +auto const TestPresetOptionalExecutionRepeatHelper = + cmJSONOptionalHelper<TestPreset::ExecutionOptions::RepeatOptions, + ReadFileResult>( + ReadFileResult::READ_OK, + cmJSONObjectHelper<TestPreset::ExecutionOptions::RepeatOptions, + ReadFileResult>(ReadFileResult::READ_OK, + ReadFileResult::INVALID_PRESET) + .Bind("mode"_s, &TestPreset::ExecutionOptions::RepeatOptions::Mode, + TestPresetExecutionModeHelper, true) + .Bind("count"_s, &TestPreset::ExecutionOptions::RepeatOptions::Count, + PresetIntHelper, true)); + +ReadFileResult TestPresetExecutionNoTestsActionHelper( + TestPreset::ExecutionOptions::NoTestsActionEnum& out, + const Json::Value* value) +{ + if (!value) { + out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default; + return ReadFileResult::READ_OK; + } + + if (!value->isString()) { + return ReadFileResult::INVALID_PRESET; + } + + if (value->asString() == "default") { + out = TestPreset::ExecutionOptions::NoTestsActionEnum::Default; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "error") { + out = TestPreset::ExecutionOptions::NoTestsActionEnum::Error; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "ignore") { + out = TestPreset::ExecutionOptions::NoTestsActionEnum::Ignore; + return ReadFileResult::READ_OK; + } + + return ReadFileResult::INVALID_PRESET; +} + +auto const TestPresetOptionalExecutionNoTestsActionHelper = + cmJSONOptionalHelper<TestPreset::ExecutionOptions::NoTestsActionEnum, + ReadFileResult>(ReadFileResult::READ_OK, + TestPresetExecutionNoTestsActionHelper); + +auto const TestPresetExecutionHelper = + cmJSONOptionalHelper<TestPreset::ExecutionOptions, ReadFileResult>( + ReadFileResult::READ_OK, + cmJSONObjectHelper<TestPreset::ExecutionOptions, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET) + .Bind("stopOnFailure"_s, &TestPreset::ExecutionOptions::StopOnFailure, + PresetOptionalBoolHelper, false) + .Bind("enableFailover"_s, &TestPreset::ExecutionOptions::EnableFailover, + PresetOptionalBoolHelper, false) + .Bind("jobs"_s, &TestPreset::ExecutionOptions::Jobs, + PresetOptionalIntHelper, false) + .Bind("resourceSpecFile"_s, + &TestPreset::ExecutionOptions::ResourceSpecFile, + PresetStringHelper, false) + .Bind("testLoad"_s, &TestPreset::ExecutionOptions::TestLoad, + PresetOptionalIntHelper, false) + .Bind("showOnly"_s, &TestPreset::ExecutionOptions::ShowOnly, + TestPresetOptionalExecutionShowOnlyHelper, false) + .Bind("rerunFailed"_s, &TestPreset::ExecutionOptions::RerunFailed, + PresetOptionalBoolHelper, false) + .Bind("repeat"_s, &TestPreset::ExecutionOptions::Repeat, + TestPresetOptionalExecutionRepeatHelper, false) + .Bind("interactiveDebugging"_s, + &TestPreset::ExecutionOptions::InteractiveDebugging, + PresetOptionalBoolHelper, false) + .Bind("scheduleRandom"_s, &TestPreset::ExecutionOptions::ScheduleRandom, + PresetOptionalBoolHelper, false) + .Bind("timeout"_s, &TestPreset::ExecutionOptions::Timeout, + PresetOptionalIntHelper, false) + .Bind("noTestsAction"_s, &TestPreset::ExecutionOptions::NoTestsAction, + TestPresetOptionalExecutionNoTestsActionHelper, false)); + +auto const TestPresetFilterHelper = + cmJSONOptionalHelper<TestPreset::FilterOptions, ReadFileResult>( + ReadFileResult::READ_OK, + cmJSONObjectHelper<TestPreset::FilterOptions, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET) + .Bind("include"_s, &TestPreset::FilterOptions::Include, + TestPresetOptionalFilterIncludeHelper, false) + .Bind("exclude"_s, &TestPreset::FilterOptions::Exclude, + TestPresetOptionalFilterExcludeHelper, false)); + +auto const TestPresetHelper = + cmJSONObjectHelper<TestPreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESET, false) + .Bind("name"_s, &TestPreset::Name, PresetStringHelper) + .Bind("inherits"_s, &TestPreset::Inherits, PresetInheritsHelper, false) + .Bind("hidden"_s, &TestPreset::Hidden, PresetBoolHelper, false) + .Bind<std::nullptr_t>("vendor"_s, nullptr, + VendorHelper(ReadFileResult::INVALID_PRESET), false) + .Bind("displayName"_s, &TestPreset::DisplayName, PresetStringHelper, false) + .Bind("description"_s, &TestPreset::Description, PresetStringHelper, false) + .Bind("environment"_s, &TestPreset::Environment, EnvironmentMapHelper, + false) + .Bind("configurePreset"_s, &TestPreset::ConfigurePreset, + PresetStringHelper, false) + .Bind("inheritConfigureEnvironment"_s, + &TestPreset::InheritConfigureEnvironment, PresetOptionalBoolHelper, + false) + .Bind("configuration"_s, &TestPreset::Configuration, PresetStringHelper, + false) + .Bind("overwriteConfigurationFile"_s, + &TestPreset::OverwriteConfigurationFile, PresetVectorStringHelper, + false) + .Bind("output"_s, &TestPreset::Output, TestPresetOptionalOutputHelper, + false) + .Bind("filter"_s, &TestPreset::Filter, TestPresetFilterHelper, false) + .Bind("execution"_s, &TestPreset::Execution, TestPresetExecutionHelper, + false); + +auto const ConfigurePresetsHelper = + cmJSONVectorHelper<ConfigurePreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS, + ConfigurePresetHelper); + +auto const BuildPresetsHelper = + cmJSONVectorHelper<BuildPreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS, + BuildPresetHelper); + +auto const TestPresetsHelper = cmJSONVectorHelper<TestPreset, ReadFileResult>( + ReadFileResult::READ_OK, ReadFileResult::INVALID_PRESETS, TestPresetHelper); auto const CMakeVersionUIntHelper = cmJSONUIntHelper<ReadFileResult>( ReadFileResult::READ_OK, ReadFileResult::INVALID_VERSION); @@ -322,7 +710,11 @@ auto const RootPresetsHelper = cmJSONObjectHelper<RootPresets, ReadFileResult>( ReadFileResult::READ_OK, ReadFileResult::INVALID_ROOT, false) .Bind<int>("version"_s, nullptr, VersionHelper) - .Bind("configurePresets"_s, &RootPresets::Presets, PresetsHelper, false) + .Bind("configurePresets"_s, &RootPresets::ConfigurePresets, + ConfigurePresetsHelper, false) + .Bind("buildPresets"_s, &RootPresets::BuildPresets, BuildPresetsHelper, + false) + .Bind("testPresets"_s, &RootPresets::TestPresets, TestPresetsHelper, false) .Bind("cmakeMinimumRequired"_s, &RootPresets::CMakeMinimumRequired, CMakeVersionHelper, false) .Bind<std::nullptr_t>("vendor"_s, nullptr, @@ -335,23 +727,33 @@ void InheritString(std::string& child, const std::string& parent) } } -void InheritOptionalBool(cm::optional<bool>& child, - const cm::optional<bool>& parent) +template <typename T> +void InheritOptionalValue(cm::optional<T>& child, + const cm::optional<T>& parent) { if (!child) { child = parent; } } +template <typename T> +void InheritVector(std::vector<T>& child, const std::vector<T>& parent) +{ + if (child.empty()) { + child = parent; + } +} + /** * Check preset inheritance for cycles (using a DAG check algorithm) while * also bubbling up fields through the inheritance hierarchy, then verify * that each preset has the required fields, either directly or through * inheritance. */ +template <class T> ReadFileResult VisitPreset( - std::map<std::string, cmCMakePresetsFile::PresetPair>& presets, - UnexpandedPreset& preset, std::map<std::string, CycleStatus> cycleStatus) + T& preset, std::map<std::string, cmCMakePresetsFile::PresetPair<T>>& presets, + std::map<std::string, CycleStatus> cycleStatus) { switch (cycleStatus[preset.Name]) { case CycleStatus::InProgress: @@ -364,80 +766,44 @@ ReadFileResult VisitPreset( cycleStatus[preset.Name] = CycleStatus::InProgress; - if (preset.CacheVariables.count("") != 0) { - return ReadFileResult::INVALID_PRESET; - } if (preset.Environment.count("") != 0) { return ReadFileResult::INVALID_PRESET; } + CHECK_OK(preset.VisitPresetBeforeInherit()) + for (auto const& i : preset.Inherits) { auto parent = presets.find(i); if (parent == presets.end()) { return ReadFileResult::INVALID_PRESET; } - if (!preset.User && parent->second.Unexpanded.User) { + auto& parentPreset = parent->second.Unexpanded; + if (!preset.User && parentPreset.User) { return ReadFileResult::USER_PRESET_INHERITANCE; } - auto result = VisitPreset(presets, parent->second.Unexpanded, cycleStatus); + auto result = VisitPreset(parentPreset, presets, cycleStatus); if (result != ReadFileResult::READ_OK) { return result; } - InheritString(preset.Generator, parent->second.Unexpanded.Generator); - InheritString(preset.Architecture, parent->second.Unexpanded.Architecture); - InheritString(preset.Toolset, parent->second.Unexpanded.Toolset); - if (!preset.ArchitectureStrategy) { - preset.ArchitectureStrategy = - parent->second.Unexpanded.ArchitectureStrategy; - } - if (!preset.ToolsetStrategy) { - preset.ToolsetStrategy = parent->second.Unexpanded.ToolsetStrategy; - } - InheritString(preset.BinaryDir, parent->second.Unexpanded.BinaryDir); - InheritOptionalBool(preset.WarnDev, parent->second.Unexpanded.WarnDev); - InheritOptionalBool(preset.ErrorDev, parent->second.Unexpanded.ErrorDev); - InheritOptionalBool(preset.WarnDeprecated, - parent->second.Unexpanded.WarnDeprecated); - InheritOptionalBool(preset.ErrorDeprecated, - parent->second.Unexpanded.ErrorDeprecated); - InheritOptionalBool(preset.WarnUninitialized, - parent->second.Unexpanded.WarnUninitialized); - InheritOptionalBool(preset.WarnUnusedCli, - parent->second.Unexpanded.WarnUnusedCli); - InheritOptionalBool(preset.WarnSystemVars, - parent->second.Unexpanded.WarnSystemVars); - for (auto const& v : parent->second.Unexpanded.CacheVariables) { - preset.CacheVariables.insert(v); - } - for (auto const& v : parent->second.Unexpanded.Environment) { + CHECK_OK(preset.VisitPresetInherit(parentPreset)) + + for (auto const& v : parentPreset.Environment) { preset.Environment.insert(v); } } - if (!preset.Hidden) { - if (preset.Generator.empty()) { - return ReadFileResult::INVALID_PRESET; - } - if (preset.BinaryDir.empty()) { - return ReadFileResult::INVALID_PRESET; - } - if (preset.WarnDev == false && preset.ErrorDev == true) { - return ReadFileResult::INVALID_PRESET; - } - if (preset.WarnDeprecated == false && preset.ErrorDeprecated == true) { - return ReadFileResult::INVALID_PRESET; - } - } + CHECK_OK(preset.VisitPresetAfterInherit()) cycleStatus[preset.Name] = CycleStatus::Verified; return ReadFileResult::READ_OK; } +template <class T> ReadFileResult ComputePresetInheritance( - std::map<std::string, cmCMakePresetsFile::PresetPair>& presets) + std::map<std::string, cmCMakePresetsFile::PresetPair<T>>& presets) { std::map<std::string, CycleStatus> cycleStatus; for (auto const& it : presets) { @@ -445,7 +811,7 @@ ReadFileResult ComputePresetInheritance( } for (auto& it : presets) { - auto result = VisitPreset(presets, it.second.Unexpanded, cycleStatus); + auto result = VisitPreset<T>(it.second.Unexpanded, presets, cycleStatus); if (result != ReadFileResult::READ_OK) { return result; } @@ -482,65 +848,186 @@ enum class ExpandMacroResult Error, }; -ExpandMacroResult VisitEnv(const cmCMakePresetsFile& file, - cmCMakePresetsFile::ExpandedPreset& preset, - std::map<std::string, CycleStatus>& envCycles, - std::string& value, CycleStatus& status); -ExpandMacroResult ExpandMacros(const cmCMakePresetsFile& file, - cmCMakePresetsFile::ExpandedPreset& preset, - std::map<std::string, CycleStatus>& envCycles, - std::string& out); -ExpandMacroResult ExpandMacro(const cmCMakePresetsFile& file, - cmCMakePresetsFile::ExpandedPreset& preset, - std::map<std::string, CycleStatus>& envCycles, - std::string& out, - const std::string& macroNamespace, - const std::string& macroName); +using MacroExpander = std::function<ExpandMacroResult( + const std::string&, const std::string&, std::string&)>; + +ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status, + const std::vector<MacroExpander>& macroExpanders); +ExpandMacroResult ExpandMacros( + std::string& out, const std::vector<MacroExpander>& macroExpanders); +ExpandMacroResult ExpandMacro( + std::string& out, const std::string& macroNamespace, + const std::string& macroName, + const std::vector<MacroExpander>& macroExpanders); bool ExpandMacros(const cmCMakePresetsFile& file, - const UnexpandedPreset& preset, - cm::optional<ExpandedPreset>& out) + const ConfigurePreset& preset, + cm::optional<ConfigurePreset>& out, + const std::vector<MacroExpander>& macroExpanders) { - out = preset; + std::string binaryDir = preset.BinaryDir; + CHECK_EXPAND(out, binaryDir, macroExpanders) - std::map<std::string, CycleStatus> envCycles; - for (auto const& v : out->Environment) { - envCycles[v.first] = CycleStatus::Unvisited; + if (!cmSystemTools::FileIsFullPath(binaryDir)) { + binaryDir = cmStrCat(file.SourceDir, '/', binaryDir); } + out->BinaryDir = cmSystemTools::CollapseFullPath(binaryDir); + cmSystemTools::ConvertToUnixSlashes(out->BinaryDir); - for (auto& v : out->Environment) { - if (v.second) { - switch (VisitEnv(file, *out, envCycles, *v.second, envCycles[v.first])) { - case ExpandMacroResult::Error: - return false; - case ExpandMacroResult::Ignore: - out.reset(); - return true; - case ExpandMacroResult::Ok: - break; + for (auto& variable : out->CacheVariables) { + if (variable.second) { + CHECK_EXPAND(out, variable.second->Value, macroExpanders) + } + } + + return true; +} + +bool ExpandMacros(const cmCMakePresetsFile&, const BuildPreset&, + cm::optional<BuildPreset>& out, + const std::vector<MacroExpander>& macroExpanders) +{ + for (auto& target : out->Targets) { + CHECK_EXPAND(out, target, macroExpanders) + } + + for (auto& nativeToolOption : out->NativeToolOptions) { + CHECK_EXPAND(out, nativeToolOption, macroExpanders) + } + + return true; +} + +bool ExpandMacros(const cmCMakePresetsFile&, const TestPreset&, + cm::optional<TestPreset>& out, + const std::vector<MacroExpander>& macroExpanders) +{ + for (auto& overwrite : out->OverwriteConfigurationFile) { + CHECK_EXPAND(out, overwrite, macroExpanders); + } + + if (out->Output) { + CHECK_EXPAND(out, out->Output->OutputLogFile, macroExpanders) + } + + if (out->Filter) { + if (out->Filter->Include) { + CHECK_EXPAND(out, out->Filter->Include->Name, macroExpanders) + CHECK_EXPAND(out, out->Filter->Include->Label, macroExpanders) + + if (out->Filter->Include->Index) { + CHECK_EXPAND(out, out->Filter->Include->Index->IndexFile, + macroExpanders); + } + } + + if (out->Filter->Exclude) { + CHECK_EXPAND(out, out->Filter->Exclude->Name, macroExpanders) + CHECK_EXPAND(out, out->Filter->Exclude->Label, macroExpanders) + + if (out->Filter->Exclude->Fixtures) { + CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Any, macroExpanders) + CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Setup, + macroExpanders) + CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Cleanup, + macroExpanders) } } } - std::string binaryDir = preset.BinaryDir; - switch (ExpandMacros(file, *out, envCycles, binaryDir)) { - case ExpandMacroResult::Error: - return false; - case ExpandMacroResult::Ignore: - out.reset(); - return true; - case ExpandMacroResult::Ok: - break; + if (out->Execution) { + CHECK_EXPAND(out, out->Execution->ResourceSpecFile, macroExpanders) } - if (!cmSystemTools::FileIsFullPath(binaryDir)) { - binaryDir = cmStrCat(file.SourceDir, '/', binaryDir); + + return true; +} + +template <class T> +bool ExpandMacros(const cmCMakePresetsFile& file, const T& preset, + cm::optional<T>& out) +{ + out.emplace(preset); + + std::map<std::string, CycleStatus> envCycles; + for (auto const& v : out->Environment) { + envCycles[v.first] = CycleStatus::Unvisited; } - out->BinaryDir = cmSystemTools::CollapseFullPath(binaryDir); - cmSystemTools::ConvertToUnixSlashes(out->BinaryDir); - for (auto& variable : out->CacheVariables) { - if (variable.second) { - switch (ExpandMacros(file, *out, envCycles, variable.second->Value)) { + std::vector<MacroExpander> macroExpanders; + + MacroExpander defaultMacroExpander = + [&file, &preset](const std::string& macroNamespace, + const std::string& macroName, + std::string& macroOut) -> ExpandMacroResult { + if (macroNamespace.empty()) { + if (macroName == "sourceDir") { + macroOut += file.SourceDir; + return ExpandMacroResult::Ok; + } + if (macroName == "sourceParentDir") { + macroOut += cmSystemTools::GetParentDirectory(file.SourceDir); + return ExpandMacroResult::Ok; + } + if (macroName == "sourceDirName") { + macroOut += cmSystemTools::GetFilenameName(file.SourceDir); + return ExpandMacroResult::Ok; + } + if (macroName == "presetName") { + macroOut += preset.Name; + return ExpandMacroResult::Ok; + } + if (macroName == "generator") { + // Generator only makes sense if preset is not hidden. + if (!preset.Hidden) { + macroOut += file.GetGeneratorForPreset(preset.Name); + } + return ExpandMacroResult::Ok; + } + if (macroName == "dollar") { + macroOut += '$'; + return ExpandMacroResult::Ok; + } + } + + return ExpandMacroResult::Ignore; + }; + + MacroExpander environmentMacroExpander = + [¯oExpanders, &out, &envCycles]( + const std::string& macroNamespace, const std::string& macroName, + std::string& result) -> ExpandMacroResult { + if (macroNamespace == "env" && !macroName.empty() && out) { + auto v = out->Environment.find(macroName); + if (v != out->Environment.end() && v->second) { + auto e = VisitEnv(*v->second, envCycles[macroName], macroExpanders); + if (e != ExpandMacroResult::Ok) { + return e; + } + result += *v->second; + return ExpandMacroResult::Ok; + } + } + + if (macroNamespace == "env" || macroNamespace == "penv") { + if (macroName.empty()) { + return ExpandMacroResult::Error; + } + const char* value = std::getenv(macroName.c_str()); + if (value) { + result += value; + } + return ExpandMacroResult::Ok; + } + + return ExpandMacroResult::Ignore; + }; + + macroExpanders.push_back(defaultMacroExpander); + macroExpanders.push_back(environmentMacroExpander); + + for (auto& v : out->Environment) { + if (v.second) { + switch (VisitEnv(*v.second, envCycles[v.first], macroExpanders)) { case ExpandMacroResult::Error: return false; case ExpandMacroResult::Ignore: @@ -552,13 +1039,11 @@ bool ExpandMacros(const cmCMakePresetsFile& file, } } - return true; + return ExpandMacros(file, preset, out, macroExpanders); } -ExpandMacroResult VisitEnv(const cmCMakePresetsFile& file, - cmCMakePresetsFile::ExpandedPreset& preset, - std::map<std::string, CycleStatus>& envCycles, - std::string& value, CycleStatus& status) +ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status, + const std::vector<MacroExpander>& macroExpanders) { if (status == CycleStatus::Verified) { return ExpandMacroResult::Ok; @@ -568,7 +1053,7 @@ ExpandMacroResult VisitEnv(const cmCMakePresetsFile& file, } status = CycleStatus::InProgress; - auto e = ExpandMacros(file, preset, envCycles, value); + auto e = ExpandMacros(value, macroExpanders); if (e != ExpandMacroResult::Ok) { return e; } @@ -576,10 +1061,8 @@ ExpandMacroResult VisitEnv(const cmCMakePresetsFile& file, return ExpandMacroResult::Ok; } -ExpandMacroResult ExpandMacros(const cmCMakePresetsFile& file, - cmCMakePresetsFile::ExpandedPreset& preset, - std::map<std::string, CycleStatus>& envCycles, - std::string& out) +ExpandMacroResult ExpandMacros( + std::string& out, const std::vector<MacroExpander>& macroExpanders) { std::string result; std::string macroNamespace; @@ -626,8 +1109,8 @@ ExpandMacroResult ExpandMacros(const cmCMakePresetsFile& file, case State::MacroName: if (c == '}') { - auto e = ExpandMacro(file, preset, envCycles, result, macroNamespace, - macroName); + auto e = + ExpandMacro(result, macroNamespace, macroName, macroExpanders); if (e != ExpandMacroResult::Ok) { return e; } @@ -656,70 +1139,232 @@ ExpandMacroResult ExpandMacros(const cmCMakePresetsFile& file, return ExpandMacroResult::Ok; } -ExpandMacroResult ExpandMacro(const cmCMakePresetsFile& file, - cmCMakePresetsFile::ExpandedPreset& preset, - std::map<std::string, CycleStatus>& envCycles, - std::string& out, +ExpandMacroResult ExpandMacro(std::string& out, const std::string& macroNamespace, - const std::string& macroName) + const std::string& macroName, + const std::vector<MacroExpander>& macroExpanders) { - if (macroNamespace.empty()) { - if (macroName == "sourceDir") { - out += file.SourceDir; - return ExpandMacroResult::Ok; + for (auto const& macroExpander : macroExpanders) { + auto result = macroExpander(macroNamespace, macroName, out); + if (result != ExpandMacroResult::Ignore) { + return result; } - if (macroName == "sourceParentDir") { - out += cmSystemTools::GetParentDirectory(file.SourceDir); - return ExpandMacroResult::Ok; + } + + if (macroNamespace == "vendor") { + return ExpandMacroResult::Ignore; + } + + return ExpandMacroResult::Error; +} +} + +cmCMakePresetsFile::ReadFileResult +cmCMakePresetsFile::ConfigurePreset::VisitPresetInherit( + const cmCMakePresetsFile::Preset& parentPreset) +{ + auto& preset = *this; + const ConfigurePreset& parent = + static_cast<const ConfigurePreset&>(parentPreset); + InheritString(preset.Generator, parent.Generator); + InheritString(preset.Architecture, parent.Architecture); + InheritString(preset.Toolset, parent.Toolset); + if (!preset.ArchitectureStrategy) { + preset.ArchitectureStrategy = parent.ArchitectureStrategy; + } + if (!preset.ToolsetStrategy) { + preset.ToolsetStrategy = parent.ToolsetStrategy; + } + InheritString(preset.BinaryDir, parent.BinaryDir); + InheritOptionalValue(preset.WarnDev, parent.WarnDev); + InheritOptionalValue(preset.ErrorDev, parent.ErrorDev); + InheritOptionalValue(preset.WarnDeprecated, parent.WarnDeprecated); + InheritOptionalValue(preset.ErrorDeprecated, parent.ErrorDeprecated); + InheritOptionalValue(preset.WarnUninitialized, parent.WarnUninitialized); + InheritOptionalValue(preset.WarnUnusedCli, parent.WarnUnusedCli); + InheritOptionalValue(preset.WarnSystemVars, parent.WarnSystemVars); + + for (auto const& v : parent.CacheVariables) { + preset.CacheVariables.insert(v); + } + + return ReadFileResult::READ_OK; +} + +cmCMakePresetsFile::ReadFileResult +cmCMakePresetsFile::ConfigurePreset::VisitPresetBeforeInherit() +{ + auto& preset = *this; + if (preset.Environment.count("") != 0) { + return ReadFileResult::INVALID_PRESET; + } + + return ReadFileResult::READ_OK; +} + +cmCMakePresetsFile::ReadFileResult +cmCMakePresetsFile::ConfigurePreset::VisitPresetAfterInherit() +{ + auto& preset = *this; + if (!preset.Hidden) { + if (preset.Generator.empty()) { + return ReadFileResult::INVALID_PRESET; } - if (macroName == "sourceDirName") { - out += cmSystemTools::GetFilenameName(file.SourceDir); - return ExpandMacroResult::Ok; + if (preset.BinaryDir.empty()) { + return ReadFileResult::INVALID_PRESET; } - if (macroName == "presetName") { - out += preset.Name; - return ExpandMacroResult::Ok; + if (preset.WarnDev == false && preset.ErrorDev == true) { + return ReadFileResult::INVALID_PRESET; } - if (macroName == "generator") { - out += preset.Generator; - return ExpandMacroResult::Ok; + if (preset.WarnDeprecated == false && preset.ErrorDeprecated == true) { + return ReadFileResult::INVALID_PRESET; } - if (macroName == "dollar") { - out += '$'; - return ExpandMacroResult::Ok; + if (preset.CacheVariables.count("") != 0) { + return ReadFileResult::INVALID_PRESET; } } - if (macroNamespace == "env" && !macroName.empty()) { - auto v = preset.Environment.find(macroName); - if (v != preset.Environment.end() && v->second) { - auto e = - VisitEnv(file, preset, envCycles, *v->second, envCycles[macroName]); - if (e != ExpandMacroResult::Ok) { - return e; - } - out += *v->second; - return ExpandMacroResult::Ok; + return ReadFileResult::READ_OK; +} + +cmCMakePresetsFile::ReadFileResult +cmCMakePresetsFile::BuildPreset::VisitPresetInherit( + const cmCMakePresetsFile::Preset& parentPreset) +{ + auto& preset = *this; + const BuildPreset& parent = static_cast<const BuildPreset&>(parentPreset); + + InheritString(preset.ConfigurePreset, parent.ConfigurePreset); + InheritOptionalValue(preset.InheritConfigureEnvironment, + parent.InheritConfigureEnvironment); + InheritOptionalValue(preset.Jobs, parent.Jobs); + InheritVector(preset.Targets, parent.Targets); + InheritString(preset.Configuration, parent.Configuration); + InheritOptionalValue(preset.CleanFirst, parent.CleanFirst); + InheritOptionalValue(preset.Verbose, parent.Verbose); + InheritVector(preset.NativeToolOptions, parent.NativeToolOptions); + + return ReadFileResult::READ_OK; +} + +cmCMakePresetsFile::ReadFileResult +cmCMakePresetsFile::BuildPreset::VisitPresetAfterInherit() +{ + auto& preset = *this; + if (!preset.Hidden && preset.ConfigurePreset.empty()) { + return ReadFileResult::INVALID_PRESET; + } + return ReadFileResult::READ_OK; +} + +cmCMakePresetsFile::ReadFileResult +cmCMakePresetsFile::TestPreset::VisitPresetInherit( + const cmCMakePresetsFile::Preset& parentPreset) +{ + auto& preset = *this; + const TestPreset& parent = static_cast<const TestPreset&>(parentPreset); + + InheritString(preset.ConfigurePreset, parent.ConfigurePreset); + InheritOptionalValue(preset.InheritConfigureEnvironment, + parent.InheritConfigureEnvironment); + InheritString(preset.Configuration, parent.Configuration); + InheritVector(preset.OverwriteConfigurationFile, + parent.OverwriteConfigurationFile); + + if (parent.Output) { + if (preset.Output) { + auto& output = preset.Output.value(); + const auto& parentOutput = parent.Output.value(); + InheritOptionalValue(output.ShortProgress, parentOutput.ShortProgress); + InheritOptionalValue(output.Verbosity, parentOutput.Verbosity); + InheritOptionalValue(output.Debug, parentOutput.Debug); + InheritOptionalValue(output.OutputOnFailure, + parentOutput.OutputOnFailure); + InheritString(output.OutputLogFile, parentOutput.OutputLogFile); + InheritOptionalValue(output.LabelSummary, parentOutput.LabelSummary); + InheritOptionalValue(output.SubprojectSummary, + parentOutput.SubprojectSummary); + InheritOptionalValue(output.MaxPassedTestOutputSize, + parentOutput.MaxPassedTestOutputSize); + InheritOptionalValue(output.MaxFailedTestOutputSize, + parentOutput.MaxFailedTestOutputSize); + InheritOptionalValue(output.MaxTestNameWidth, + parentOutput.MaxTestNameWidth); + } else { + preset.Output = parent.Output; } } - if (macroNamespace == "env" || macroNamespace == "penv") { - if (macroName.empty()) { - return ExpandMacroResult::Error; + if (parent.Filter) { + if (parent.Filter->Include) { + if (preset.Filter && preset.Filter->Include) { + auto& include = *preset.Filter->Include; + const auto& parentInclude = *parent.Filter->Include; + InheritString(include.Name, parentInclude.Name); + InheritString(include.Label, parentInclude.Label); + InheritOptionalValue(include.Index, parentInclude.Index); + } else { + if (!preset.Filter) { + preset.Filter.emplace(); + } + preset.Filter->Include = parent.Filter->Include; + } } - const char* value = std::getenv(macroName.c_str()); - if (value) { - out += value; + + if (parent.Filter->Exclude) { + if (preset.Filter && preset.Filter->Exclude) { + auto& exclude = *preset.Filter->Exclude; + const auto& parentExclude = *parent.Filter->Exclude; + InheritString(exclude.Name, parentExclude.Name); + InheritString(exclude.Label, parentExclude.Label); + InheritOptionalValue(exclude.Fixtures, parentExclude.Fixtures); + } else { + if (!preset.Filter) { + preset.Filter.emplace(); + } + preset.Filter->Exclude = parent.Filter->Exclude; + } } - return ExpandMacroResult::Ok; } - if (macroNamespace == "vendor") { - return ExpandMacroResult::Ignore; + if (parent.Execution) { + if (preset.Execution) { + auto& execution = *preset.Execution; + const auto& parentExecution = *parent.Execution; + InheritOptionalValue(execution.StopOnFailure, + parentExecution.StopOnFailure); + InheritOptionalValue(execution.EnableFailover, + parentExecution.EnableFailover); + InheritOptionalValue(execution.Jobs, parentExecution.Jobs); + InheritString(execution.ResourceSpecFile, + parentExecution.ResourceSpecFile); + InheritOptionalValue(execution.TestLoad, parentExecution.TestLoad); + InheritOptionalValue(execution.ShowOnly, parentExecution.ShowOnly); + InheritOptionalValue(execution.RerunFailed, parentExecution.RerunFailed); + InheritOptionalValue(execution.Repeat, parentExecution.Repeat); + InheritOptionalValue(execution.InteractiveDebugging, + parentExecution.InteractiveDebugging); + InheritOptionalValue(execution.ScheduleRandom, + parentExecution.ScheduleRandom); + InheritOptionalValue(execution.Timeout, parentExecution.Timeout); + InheritOptionalValue(execution.NoTestsAction, + parentExecution.NoTestsAction); + } else { + preset.Execution = parent.Execution; + } } - return ExpandMacroResult::Error; + return ReadFileResult::READ_OK; } + +cmCMakePresetsFile::ReadFileResult +cmCMakePresetsFile::TestPreset::VisitPresetAfterInherit() +{ + auto& preset = *this; + if (!preset.Hidden && preset.ConfigurePreset.empty()) { + return ReadFileResult::INVALID_PRESET; + } + return ReadFileResult::READ_OK; } std::string cmCMakePresetsFile::GetFilename(const std::string& sourceDir) @@ -735,17 +1380,25 @@ std::string cmCMakePresetsFile::GetUserFilename(const std::string& sourceDir) cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadProjectPresets( const std::string& sourceDir, bool allowNoFiles) { - bool haveOneFile = false; this->SourceDir = sourceDir; - this->Presets.clear(); - this->PresetOrder.clear(); + this->ClearPresets(); - std::vector<std::string> presetOrder; - std::map<std::string, PresetPair> presetMap; + auto result = this->ReadProjectPresetsInternal(allowNoFiles); + if (result != ReadFileResult::READ_OK) { + this->ClearPresets(); + } + + return result; +} + +cmCMakePresetsFile::ReadFileResult +cmCMakePresetsFile::ReadProjectPresetsInternal(bool allowNoFiles) +{ + bool haveOneFile = false; std::string filename = GetUserFilename(this->SourceDir); if (cmSystemTools::FileExists(filename)) { - auto result = this->ReadJSONFile(filename, presetOrder, presetMap, true); + auto result = this->ReadJSONFile(filename, true); if (result != ReadFileResult::READ_OK) { return result; } @@ -754,7 +1407,7 @@ cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadProjectPresets( filename = GetFilename(this->SourceDir); if (cmSystemTools::FileExists(filename)) { - auto result = this->ReadJSONFile(filename, presetOrder, presetMap, false); + auto result = this->ReadJSONFile(filename, false); if (result != ReadFileResult::READ_OK) { return result; } @@ -766,19 +1419,50 @@ cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadProjectPresets( : ReadFileResult::FILE_NOT_FOUND; } - auto result = ComputePresetInheritance(presetMap); - if (result != ReadFileResult::READ_OK) { - return result; + CHECK_OK(ComputePresetInheritance(this->ConfigurePresets)) + CHECK_OK(ComputePresetInheritance(this->BuildPresets)) + CHECK_OK(ComputePresetInheritance(this->TestPresets)) + + for (auto& it : this->ConfigurePresets) { + if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) { + return ReadFileResult::INVALID_MACRO_EXPANSION; + } } - for (auto& it : presetMap) { + for (auto& it : this->BuildPresets) { + if (!it.second.Unexpanded.Hidden) { + const auto configurePreset = + this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset); + if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true) && + configurePreset != this->ConfigurePresets.end()) { + it.second.Unexpanded.Environment.insert( + configurePreset->second.Unexpanded.Environment.begin(), + configurePreset->second.Unexpanded.Environment.end()); + } + } + + if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) { + return ReadFileResult::INVALID_MACRO_EXPANSION; + } + } + + for (auto& it : this->TestPresets) { + if (!it.second.Unexpanded.Hidden) { + const auto configurePreset = + this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset); + if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true) && + configurePreset != this->ConfigurePresets.end()) { + it.second.Unexpanded.Environment.insert( + configurePreset->second.Unexpanded.Environment.begin(), + configurePreset->second.Unexpanded.Environment.end()); + } + } + if (!ExpandMacros(*this, it.second.Unexpanded, it.second.Expanded)) { return ReadFileResult::INVALID_MACRO_EXPANSION; } } - this->PresetOrder = std::move(presetOrder); - this->Presets = std::move(presetMap); return ReadFileResult::READ_OK; } @@ -817,14 +1501,16 @@ const char* cmCMakePresetsFile::ResultToString(ReadFileResult result) return "Project preset inherits from user preset"; case ReadFileResult::INVALID_MACRO_EXPANSION: return "Invalid macro expansion"; + case ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED: + return "File version must be 2 or higher for build and test preset " + "support."; } return "Unknown error"; } cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadJSONFile( - const std::string& filename, std::vector<std::string>& presetOrder, - std::map<std::string, PresetPair>& presetMap, bool user) + const std::string& filename, bool user) { cmsys::ifstream fin(filename.c_str()); if (!fin) { @@ -848,6 +1534,12 @@ cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadJSONFile( return ReadFileResult::UNRECOGNIZED_VERSION; } + // Support for build and test presets added in version 2. + if (v < 2 && + (root.isMember("buildPresets") || root.isMember("testPresets"))) { + return ReadFileResult::BUILD_TEST_PRESETS_UNSUPPORTED; + } + RootPresets presets; if ((result = RootPresetsHelper(presets, &root)) != ReadFileResult::READ_OK) { @@ -866,16 +1558,158 @@ cmCMakePresetsFile::ReadFileResult cmCMakePresetsFile::ReadJSONFile( return ReadFileResult::UNRECOGNIZED_CMAKE_VERSION; } - for (auto& preset : presets.Presets) { + for (auto& preset : presets.ConfigurePresets) { preset.User = user; if (preset.Name.empty()) { return ReadFileResult::INVALID_PRESET; } - if (!presetMap.insert({ preset.Name, { preset, cm::nullopt } }).second) { + + PresetPair<ConfigurePreset> presetPair; + presetPair.Unexpanded = preset; + presetPair.Expanded = cm::nullopt; + if (!this->ConfigurePresets + .emplace(std::make_pair(preset.Name, presetPair)) + .second) { return ReadFileResult::DUPLICATE_PRESETS; } - presetOrder.push_back(preset.Name); + this->ConfigurePresetOrder.push_back(preset.Name); + } + + for (auto& preset : presets.BuildPresets) { + preset.User = user; + if (preset.Name.empty()) { + return ReadFileResult::INVALID_PRESET; + } + + PresetPair<BuildPreset> presetPair; + presetPair.Unexpanded = preset; + presetPair.Expanded = cm::nullopt; + if (!this->BuildPresets.emplace(preset.Name, presetPair).second) { + return ReadFileResult::DUPLICATE_PRESETS; + } + this->BuildPresetOrder.push_back(preset.Name); + } + + for (auto& preset : presets.TestPresets) { + preset.User = user; + if (preset.Name.empty()) { + return ReadFileResult::INVALID_PRESET; + } + + PresetPair<TestPreset> presetPair; + presetPair.Unexpanded = preset; + presetPair.Expanded = cm::nullopt; + if (!this->TestPresets.emplace(preset.Name, presetPair).second) { + return ReadFileResult::DUPLICATE_PRESETS; + } + this->TestPresetOrder.push_back(preset.Name); } return ReadFileResult::READ_OK; } + +void cmCMakePresetsFile::ClearPresets() +{ + this->ConfigurePresets.clear(); + this->BuildPresets.clear(); + this->TestPresets.clear(); + + this->ConfigurePresetOrder.clear(); + this->BuildPresetOrder.clear(); + this->TestPresetOrder.clear(); +} + +void cmCMakePresetsFile::PrintPresets( + const std::vector<const cmCMakePresetsFile::Preset*>& presets) +{ + if (presets.empty()) { + return; + } + + auto longestPresetName = + std::max_element(presets.begin(), presets.end(), + [](const cmCMakePresetsFile::Preset* a, + const cmCMakePresetsFile::Preset* b) { + return a->Name.length() < b->Name.length(); + }); + auto longestLength = (*longestPresetName)->Name.length(); + + for (const auto* preset : presets) { + std::cout << " \"" << preset->Name << '"'; + const auto& description = preset->DisplayName; + if (!description.empty()) { + for (std::size_t i = 0; i < longestLength - preset->Name.length(); ++i) { + std::cout << ' '; + } + std::cout << " - " << description; + } + std::cout << '\n'; + } +} + +void cmCMakePresetsFile::PrintConfigurePresetList() const +{ + PrintConfigurePresetList([](const ConfigurePreset&) { return true; }); +} + +void cmCMakePresetsFile::PrintConfigurePresetList( + const std::function<bool(const ConfigurePreset&)>& filter) const +{ + std::vector<const cmCMakePresetsFile::Preset*> presets; + for (auto const& p : this->ConfigurePresetOrder) { + auto const& preset = this->ConfigurePresets.at(p); + if (!preset.Unexpanded.Hidden && preset.Expanded && + filter(preset.Unexpanded)) { + presets.push_back( + static_cast<const cmCMakePresetsFile::Preset*>(&preset.Unexpanded)); + } + } + + if (!presets.empty()) { + std::cout << "Available configure presets:\n\n"; + cmCMakePresetsFile::PrintPresets(presets); + } +} + +void cmCMakePresetsFile::PrintBuildPresetList() const +{ + std::vector<const cmCMakePresetsFile::Preset*> presets; + for (auto const& p : this->BuildPresetOrder) { + auto const& preset = this->BuildPresets.at(p); + if (!preset.Unexpanded.Hidden && preset.Expanded) { + presets.push_back( + static_cast<const cmCMakePresetsFile::Preset*>(&preset.Unexpanded)); + } + } + + if (!presets.empty()) { + std::cout << "Available build presets:\n\n"; + cmCMakePresetsFile::PrintPresets(presets); + } +} + +void cmCMakePresetsFile::PrintTestPresetList() const +{ + std::vector<const cmCMakePresetsFile::Preset*> presets; + for (auto const& p : this->TestPresetOrder) { + auto const& preset = this->TestPresets.at(p); + if (!preset.Unexpanded.Hidden && preset.Expanded) { + presets.push_back( + static_cast<const cmCMakePresetsFile::Preset*>(&preset.Unexpanded)); + } + } + + if (!presets.empty()) { + std::cout << "Available test presets:\n\n"; + cmCMakePresetsFile::PrintPresets(presets); + } +} + +void cmCMakePresetsFile::PrintAllPresets() const +{ + this->PrintConfigurePresetList(); + std::cout << std::endl; + this->PrintBuildPresetList(); + std::cout << std::endl; + this->PrintTestPresetList(); +} diff --git a/Source/cmCMakePresetsFile.h b/Source/cmCMakePresetsFile.h index f6b159a..26810ad 100644 --- a/Source/cmCMakePresetsFile.h +++ b/Source/cmCMakePresetsFile.h @@ -2,6 +2,7 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #pragma once +#include <functional> #include <map> #include <string> #include <utility> @@ -12,6 +13,27 @@ class cmCMakePresetsFile { public: + enum class ReadFileResult + { + READ_OK, + FILE_NOT_FOUND, + JSON_PARSE_ERROR, + INVALID_ROOT, + NO_VERSION, + INVALID_VERSION, + UNRECOGNIZED_VERSION, + INVALID_CMAKE_VERSION, + UNRECOGNIZED_CMAKE_VERSION, + INVALID_PRESETS, + INVALID_PRESET, + INVALID_VARIABLE, + DUPLICATE_PRESETS, + CYCLIC_PRESET_INHERITANCE, + USER_PRESET_INHERITANCE, + INVALID_MACRO_EXPANSION, + BUILD_TEST_PRESETS_UNSUPPORTED, + }; + enum class ArchToolsetStrategy { Set, @@ -29,25 +51,51 @@ public: { public: #if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L) - Preset() = default; - Preset(const Preset& /*other*/) = default; - Preset(Preset&& /*other*/) = default; - - Preset& operator=(const Preset& /*other*/) = default; - // The move assignment operators for several STL classes did not become // noexcept until C++17, which causes some tools to warn about this move // assignment operator throwing an exception when it shouldn't. Disable the // move assignment operator until C++17 is enabled. - Preset& operator=(Preset&& /*other*/) = delete; + // Explicitly defining a copy assignment operator prevents the compiler + // from automatically generating a move assignment operator. + Preset& operator=(const Preset& /*other*/) = default; #endif + virtual ~Preset() = default; + std::string Name; std::vector<std::string> Inherits; bool Hidden; bool User; std::string DisplayName; std::string Description; + + std::map<std::string, cm::optional<std::string>> Environment; + + virtual ReadFileResult VisitPresetInherit(const Preset& parent) = 0; + virtual ReadFileResult VisitPresetBeforeInherit() + { + return ReadFileResult::READ_OK; + } + + virtual ReadFileResult VisitPresetAfterInherit() + { + return ReadFileResult::READ_OK; + } + }; + + class ConfigurePreset : public Preset + { + public: +#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L) + // The move assignment operators for several STL classes did not become + // noexcept until C++17, which causes some tools to warn about this move + // assignment operator throwing an exception when it shouldn't. Disable the + // move assignment operator until C++17 is enabled. + // Explicitly defining a copy assignment operator prevents the compiler + // from automatically generating a move assignment operator. + ConfigurePreset& operator=(const ConfigurePreset& /*other*/) = default; +#endif + std::string Generator; std::string Architecture; cm::optional<ArchToolsetStrategy> ArchitectureStrategy; @@ -56,7 +104,6 @@ public: std::string BinaryDir; std::map<std::string, cm::optional<CacheVariable>> CacheVariables; - std::map<std::string, cm::optional<std::string>> Environment; cm::optional<bool> WarnDev; cm::optional<bool> ErrorDev; @@ -69,70 +116,183 @@ public: cm::optional<bool> DebugOutput; cm::optional<bool> DebugTryCompile; cm::optional<bool> DebugFind; + + ReadFileResult VisitPresetInherit(const Preset& parent) override; + ReadFileResult VisitPresetBeforeInherit() override; + ReadFileResult VisitPresetAfterInherit() override; }; - class UnexpandedPreset : public Preset + class BuildPreset : public Preset { public: - using Preset::Preset; +#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L) + // The move assignment operators for several STL classes did not become + // noexcept until C++17, which causes some tools to warn about this move + // assignment operator throwing an exception when it shouldn't. Disable the + // move assignment operator until C++17 is enabled. + // Explicitly defining a copy assignment operator prevents the compiler + // from automatically generating a move assignment operator. + BuildPreset& operator=(const BuildPreset& /*other*/) = default; +#endif - UnexpandedPreset() = default; - UnexpandedPreset(const Preset& preset) - : Preset(preset) - { - } - UnexpandedPreset(Preset&& preset) - : Preset(std::move(preset)) - { - } + std::string ConfigurePreset; + cm::optional<bool> InheritConfigureEnvironment; + cm::optional<int> Jobs; + std::vector<std::string> Targets; + std::string Configuration; + cm::optional<bool> CleanFirst; + cm::optional<bool> Verbose; + std::vector<std::string> NativeToolOptions; + + ReadFileResult VisitPresetInherit(const Preset& parent) override; + ReadFileResult VisitPresetAfterInherit() override; }; - class ExpandedPreset : public Preset + class TestPreset : public Preset { public: - using Preset::Preset; +#if __cplusplus < 201703L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201703L) + // The move assignment operators for several STL classes did not become + // noexcept until C++17, which causes some tools to warn about this move + // assignment operator throwing an exception when it shouldn't. Disable the + // move assignment operator until C++17 is enabled. + // Explicitly defining a copy assignment operator prevents the compiler + // from automatically generating a move assignment operator. + TestPreset& operator=(const TestPreset& /*other*/) = default; +#endif - ExpandedPreset() = default; - ExpandedPreset(const Preset& preset) - : Preset(preset) + struct OutputOptions { - } - ExpandedPreset(Preset&& preset) - : Preset(std::move(preset)) + enum class VerbosityEnum + { + Default, + Verbose, + Extra + }; + + cm::optional<bool> ShortProgress; + cm::optional<VerbosityEnum> Verbosity; + cm::optional<bool> Debug; + cm::optional<bool> OutputOnFailure; + cm::optional<bool> Quiet; + std::string OutputLogFile; + cm::optional<bool> LabelSummary; + cm::optional<bool> SubprojectSummary; + cm::optional<int> MaxPassedTestOutputSize; + cm::optional<int> MaxFailedTestOutputSize; + cm::optional<int> MaxTestNameWidth; + }; + + struct IncludeOptions { - } + struct IndexOptions + { + cm::optional<int> Start; + cm::optional<int> End; + cm::optional<int> Stride; + std::vector<int> SpecificTests; + + std::string IndexFile; + }; + + std::string Name; + std::string Label; + cm::optional<IndexOptions> Index; + cm::optional<bool> UseUnion; + }; + + struct ExcludeOptions + { + struct FixturesOptions + { + std::string Any; + std::string Setup; + std::string Cleanup; + }; + + std::string Name; + std::string Label; + cm::optional<FixturesOptions> Fixtures; + }; + + struct FilterOptions + { + cm::optional<IncludeOptions> Include; + cm::optional<ExcludeOptions> Exclude; + }; + + struct ExecutionOptions + { + enum class ShowOnlyEnum + { + Human, + JsonV1 + }; + + struct RepeatOptions + { + enum class ModeEnum + { + UntilFail, + UntilPass, + AfterTimeout + }; + + ModeEnum Mode; + int Count; + }; + + enum class NoTestsActionEnum + { + Default, + Error, + Ignore + }; + + cm::optional<bool> StopOnFailure; + cm::optional<bool> EnableFailover; + cm::optional<int> Jobs; + std::string ResourceSpecFile; + cm::optional<int> TestLoad; + cm::optional<ShowOnlyEnum> ShowOnly; + cm::optional<bool> RerunFailed; + + cm::optional<RepeatOptions> Repeat; + cm::optional<bool> InteractiveDebugging; + cm::optional<bool> ScheduleRandom; + cm::optional<int> Timeout; + cm::optional<NoTestsActionEnum> NoTestsAction; + }; + + std::string ConfigurePreset; + cm::optional<bool> InheritConfigureEnvironment; + std::string Configuration; + std::vector<std::string> OverwriteConfigurationFile; + cm::optional<OutputOptions> Output; + cm::optional<FilterOptions> Filter; + cm::optional<ExecutionOptions> Execution; + + ReadFileResult VisitPresetInherit(const Preset& parent) override; + ReadFileResult VisitPresetAfterInherit() override; }; + template <class T> class PresetPair { public: - UnexpandedPreset Unexpanded; - cm::optional<ExpandedPreset> Expanded; + T Unexpanded; + cm::optional<T> Expanded; }; - std::string SourceDir; - std::map<std::string, PresetPair> Presets; - std::vector<std::string> PresetOrder; + std::map<std::string, PresetPair<ConfigurePreset>> ConfigurePresets; + std::map<std::string, PresetPair<BuildPreset>> BuildPresets; + std::map<std::string, PresetPair<TestPreset>> TestPresets; - enum class ReadFileResult - { - READ_OK, - FILE_NOT_FOUND, - JSON_PARSE_ERROR, - INVALID_ROOT, - NO_VERSION, - INVALID_VERSION, - UNRECOGNIZED_VERSION, - INVALID_CMAKE_VERSION, - UNRECOGNIZED_CMAKE_VERSION, - INVALID_PRESETS, - INVALID_PRESET, - INVALID_VARIABLE, - DUPLICATE_PRESETS, - CYCLIC_PRESET_INHERITANCE, - USER_PRESET_INHERITANCE, - INVALID_MACRO_EXPANSION, - }; + std::vector<std::string> ConfigurePresetOrder; + std::vector<std::string> BuildPresetOrder; + std::vector<std::string> TestPresetOrder; + + std::string SourceDir; static std::string GetFilename(const std::string& sourceDir); static std::string GetUserFilename(const std::string& sourceDir); @@ -140,9 +300,44 @@ public: bool allowNoFiles = false); static const char* ResultToString(ReadFileResult result); + std::string GetGeneratorForPreset(const std::string& presetName) const + { + auto configurePresetName = presetName; + + auto buildPresetIterator = this->BuildPresets.find(presetName); + if (buildPresetIterator != this->BuildPresets.end()) { + configurePresetName = + buildPresetIterator->second.Unexpanded.ConfigurePreset; + } else { + auto testPresetIterator = this->TestPresets.find(presetName); + if (testPresetIterator != this->TestPresets.end()) { + configurePresetName = + testPresetIterator->second.Unexpanded.ConfigurePreset; + } + } + + auto configurePresetIterator = + this->ConfigurePresets.find(configurePresetName); + if (configurePresetIterator != this->ConfigurePresets.end()) { + return configurePresetIterator->second.Unexpanded.Generator; + } + + // This should only happen if the preset is hidden + // or (for build or test presets) if ConfigurePreset is invalid. + return ""; + } + + static void PrintPresets( + const std::vector<const cmCMakePresetsFile::Preset*>& presets); + void PrintConfigurePresetList() const; + void PrintConfigurePresetList( + const std::function<bool(const ConfigurePreset&)>& filter) const; + void PrintBuildPresetList() const; + void PrintTestPresetList() const; + void PrintAllPresets() const; + private: - ReadFileResult ReadJSONFile(const std::string& filename, - std::vector<std::string>& presetOrder, - std::map<std::string, PresetPair>& presetMap, - bool user); + ReadFileResult ReadProjectPresetsInternal(bool allowNoFiles); + ReadFileResult ReadJSONFile(const std::string& filename, bool user); + void ClearPresets(); }; diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index 6c1071d..4bea0d5 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -18,6 +18,7 @@ #include <vector> #include <cm/memory> +#include <cm/optional> #include <cm/string_view> #include <cmext/algorithm> #include <cmext/string_view> @@ -38,6 +39,7 @@ # include <unistd.h> // IWYU pragma: keep #endif +#include "cmCMakePresetsFile.h" #include "cmCTestBuildAndTestHandler.h" #include "cmCTestBuildHandler.h" #include "cmCTestConfigureHandler.h" @@ -2257,6 +2259,311 @@ bool cmCTest::AddVariableDefinition(const std::string& arg) return false; } +void cmCTest::SetPersistentOptionIfNotEmpty(const std::string& value, + const std::string& optionName) +{ + if (!value.empty()) { + this->GetTestHandler()->SetPersistentOption(optionName, value.c_str()); + this->GetMemCheckHandler()->SetPersistentOption(optionName, value.c_str()); + } +} + +bool cmCTest::SetArgsFromPreset(const std::string& presetName, + bool listPresets) +{ + const auto workingDirectory = cmSystemTools::GetCurrentWorkingDirectory(); + + cmCMakePresetsFile settingsFile; + auto result = settingsFile.ReadProjectPresets(workingDirectory); + if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) { + cmSystemTools::Error(cmStrCat("Could not read presets from ", + workingDirectory, ": ", + cmCMakePresetsFile::ResultToString(result))); + return false; + } + + if (listPresets) { + settingsFile.PrintTestPresetList(); + return true; + } + + auto presetPair = settingsFile.TestPresets.find(presetName); + if (presetPair == settingsFile.TestPresets.end()) { + cmSystemTools::Error(cmStrCat("No such test preset in ", workingDirectory, + ": \"", presetName, '"')); + settingsFile.PrintTestPresetList(); + return false; + } + + if (presetPair->second.Unexpanded.Hidden) { + cmSystemTools::Error(cmStrCat("Cannot use hidden test preset in ", + workingDirectory, ": \"", presetName, '"')); + settingsFile.PrintTestPresetList(); + return false; + } + + auto const& expandedPreset = presetPair->second.Expanded; + if (!expandedPreset) { + cmSystemTools::Error(cmStrCat("Could not evaluate test preset \"", + presetName, "\": Invalid macro expansion")); + settingsFile.PrintTestPresetList(); + return false; + } + + auto configurePresetPair = + settingsFile.ConfigurePresets.find(expandedPreset->ConfigurePreset); + if (configurePresetPair == settingsFile.ConfigurePresets.end()) { + cmSystemTools::Error(cmStrCat("No such configure preset in ", + workingDirectory, ": \"", + expandedPreset->ConfigurePreset, '"')); + settingsFile.PrintConfigurePresetList(); + return false; + } + + if (configurePresetPair->second.Unexpanded.Hidden) { + cmSystemTools::Error(cmStrCat("Cannot use hidden configure preset in ", + workingDirectory, ": \"", + expandedPreset->ConfigurePreset, '"')); + settingsFile.PrintConfigurePresetList(); + return false; + } + + auto const& expandedConfigurePreset = configurePresetPair->second.Expanded; + if (!expandedConfigurePreset) { + cmSystemTools::Error(cmStrCat("Could not evaluate configure preset \"", + expandedPreset->ConfigurePreset, + "\": Invalid macro expansion")); + return false; + } + + auto presetEnvironment = expandedPreset->Environment; + for (auto const& var : presetEnvironment) { + if (var.second) { + cmSystemTools::PutEnv(cmStrCat(var.first, '=', *var.second)); + } + } + + if (!expandedPreset->Configuration.empty()) { + this->SetConfigType(expandedPreset->Configuration); + } + + // Set build directory to value specified by the configure preset. + this->AddCTestConfigurationOverwrite( + cmStrCat("BuildDirectory=", expandedConfigurePreset->BinaryDir)); + for (const auto& kvp : expandedPreset->OverwriteConfigurationFile) { + this->AddCTestConfigurationOverwrite(kvp); + } + + if (expandedPreset->Output) { + this->Impl->TestProgressOutput = + expandedPreset->Output->ShortProgress.value_or(false); + + if (expandedPreset->Output->Verbosity) { + const auto& verbosity = *expandedPreset->Output->Verbosity; + switch (verbosity) { + case cmCMakePresetsFile::TestPreset::OutputOptions::VerbosityEnum:: + Extra: + this->Impl->ExtraVerbose = true; + // intentional fallthrough + case cmCMakePresetsFile::TestPreset::OutputOptions::VerbosityEnum:: + Verbose: + this->Impl->Verbose = true; + break; + case cmCMakePresetsFile::TestPreset::OutputOptions::VerbosityEnum:: + Default: + default: + // leave default settings + break; + } + } + + this->Impl->Debug = expandedPreset->Output->Debug.value_or(false); + this->Impl->ShowLineNumbers = + expandedPreset->Output->Debug.value_or(false); + this->Impl->OutputTestOutputOnTestFailure = + expandedPreset->Output->OutputOnFailure.value_or(false); + this->Impl->Quiet = expandedPreset->Output->Quiet.value_or(false); + + if (!expandedPreset->Output->OutputLogFile.empty()) { + this->SetOutputLogFileName(expandedPreset->Output->OutputLogFile); + } + + this->Impl->LabelSummary = + expandedPreset->Output->LabelSummary.value_or(true); + this->Impl->SubprojectSummary = + expandedPreset->Output->SubprojectSummary.value_or(true); + + if (expandedPreset->Output->MaxPassedTestOutputSize) { + this->Impl->TestHandler.SetTestOutputSizePassed( + *expandedPreset->Output->MaxPassedTestOutputSize); + } + + if (expandedPreset->Output->MaxFailedTestOutputSize) { + this->Impl->TestHandler.SetTestOutputSizeFailed( + *expandedPreset->Output->MaxFailedTestOutputSize); + } + + if (expandedPreset->Output->MaxTestNameWidth) { + this->Impl->MaxTestNameWidth = *expandedPreset->Output->MaxTestNameWidth; + } + } + + if (expandedPreset->Filter) { + if (expandedPreset->Filter->Include) { + this->SetPersistentOptionIfNotEmpty( + expandedPreset->Filter->Include->Name, "IncludeRegularExpression"); + this->SetPersistentOptionIfNotEmpty( + expandedPreset->Filter->Include->Label, "LabelRegularExpression"); + + if (expandedPreset->Filter->Include->Index) { + if (expandedPreset->Filter->Include->Index->IndexFile.empty()) { + const auto& start = expandedPreset->Filter->Include->Index->Start; + const auto& end = expandedPreset->Filter->Include->Index->End; + const auto& stride = expandedPreset->Filter->Include->Index->Stride; + std::string indexOptions; + indexOptions += (start ? std::to_string(*start) : "") + ","; + indexOptions += (end ? std::to_string(*end) : "") + ","; + indexOptions += (stride ? std::to_string(*stride) : "") + ","; + indexOptions += + cmJoin(expandedPreset->Filter->Include->Index->SpecificTests, ","); + + this->SetPersistentOptionIfNotEmpty(indexOptions, + "TestsToRunInformation"); + } else { + this->SetPersistentOptionIfNotEmpty( + expandedPreset->Filter->Include->Index->IndexFile, + "TestsToRunInformation"); + } + } + + if (expandedPreset->Filter->Include->UseUnion.value_or(false)) { + this->GetTestHandler()->SetPersistentOption("UseUnion", "true"); + this->GetMemCheckHandler()->SetPersistentOption("UseUnion", "true"); + } + } + + if (expandedPreset->Filter->Exclude) { + this->SetPersistentOptionIfNotEmpty( + expandedPreset->Filter->Exclude->Name, "ExcludeRegularExpression"); + this->SetPersistentOptionIfNotEmpty( + expandedPreset->Filter->Exclude->Label, + "ExcludeLabelRegularExpression"); + + if (expandedPreset->Filter->Exclude->Fixtures) { + this->SetPersistentOptionIfNotEmpty( + expandedPreset->Filter->Exclude->Fixtures->Any, + "ExcludeFixtureRegularExpression"); + this->SetPersistentOptionIfNotEmpty( + expandedPreset->Filter->Exclude->Fixtures->Setup, + "ExcludeFixtureSetupRegularExpression"); + this->SetPersistentOptionIfNotEmpty( + expandedPreset->Filter->Exclude->Fixtures->Cleanup, + "ExcludeFixtureCleanupRegularExpression"); + } + } + } + + if (expandedPreset->Execution) { + this->Impl->StopOnFailure = + expandedPreset->Execution->StopOnFailure.value_or(false); + this->Impl->Failover = + expandedPreset->Execution->EnableFailover.value_or(false); + + if (expandedPreset->Execution->Jobs) { + auto jobs = *expandedPreset->Execution->Jobs; + this->SetParallelLevel(jobs); + this->Impl->ParallelLevelSetInCli = true; + } + + this->SetPersistentOptionIfNotEmpty( + expandedPreset->Execution->ResourceSpecFile, "ResourceSpecFile"); + + if (expandedPreset->Execution->TestLoad) { + auto testLoad = *expandedPreset->Execution->TestLoad; + this->SetTestLoad(testLoad); + } + + if (expandedPreset->Execution->ShowOnly) { + this->Impl->ShowOnly = true; + + switch (*expandedPreset->Execution->ShowOnly) { + case cmCMakePresetsFile::TestPreset::ExecutionOptions::ShowOnlyEnum:: + JsonV1: + this->Impl->Quiet = true; + this->Impl->OutputAsJson = true; + this->Impl->OutputAsJsonVersion = 1; + break; + case cmCMakePresetsFile::TestPreset::ExecutionOptions::ShowOnlyEnum:: + Human: + // intentional fallthrough (human is the default) + default: + break; + } + } + + if (expandedPreset->Execution->RerunFailed.value_or(false)) { + this->GetTestHandler()->SetPersistentOption("RerunFailed", "true"); + this->GetMemCheckHandler()->SetPersistentOption("RerunFailed", "true"); + } + + if (expandedPreset->Execution->Repeat) { + this->Impl->RepeatCount = expandedPreset->Execution->Repeat->Count; + switch (expandedPreset->Execution->Repeat->Mode) { + case cmCMakePresetsFile::TestPreset::ExecutionOptions::RepeatOptions:: + ModeEnum::UntilFail: + this->Impl->RepeatMode = cmCTest::Repeat::UntilFail; + break; + case cmCMakePresetsFile::TestPreset::ExecutionOptions::RepeatOptions:: + ModeEnum::UntilPass: + this->Impl->RepeatMode = cmCTest::Repeat::UntilPass; + break; + case cmCMakePresetsFile::TestPreset::ExecutionOptions::RepeatOptions:: + ModeEnum::AfterTimeout: + this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout; + break; + default: + // should never default since mode is required + return false; + } + } + + if (expandedPreset->Execution->InteractiveDebugging) { + this->Impl->InteractiveDebugMode = + *expandedPreset->Execution->InteractiveDebugging; + } + + if (expandedPreset->Execution->ScheduleRandom.value_or(false)) { + this->Impl->ScheduleType = "Random"; + } + + if (expandedPreset->Execution->Timeout) { + this->Impl->GlobalTimeout = + cmDuration(*expandedPreset->Execution->Timeout); + } + + if (expandedPreset->Execution->NoTestsAction) { + switch (*expandedPreset->Execution->NoTestsAction) { + case cmCMakePresetsFile::TestPreset::ExecutionOptions:: + NoTestsActionEnum::Error: + this->Impl->NoTestsMode = cmCTest::NoTests::Error; + break; + case cmCMakePresetsFile::TestPreset::ExecutionOptions:: + NoTestsActionEnum::Ignore: + this->Impl->NoTestsMode = cmCTest::NoTests::Ignore; + break; + case cmCMakePresetsFile::TestPreset::ExecutionOptions:: + NoTestsActionEnum::Default: + break; + default: + // should never default + return false; + } + } + } + + return true; +} + // the main entry point of ctest, called from main int cmCTest::Run(std::vector<std::string>& args, std::string* output) { @@ -2268,6 +2575,37 @@ int cmCTest::Run(std::vector<std::string>& args, std::string* output) // copy the command line cm::append(this->Impl->InitialCommandLineArguments, args); + // check if a test preset was specified + + bool listPresets = + find(args.begin(), args.end(), "--list-presets") != args.end(); + auto it = find(args.begin(), args.end(), "--preset"); + if (listPresets || it != args.end()) { + std::string errormsg; + bool success; + + if (listPresets) { + // If listing presets we don't need a presetName + success = this->SetArgsFromPreset("", listPresets); + } else { + if (++it != args.end()) { + auto presetName = *it; + success = this->SetArgsFromPreset(presetName, listPresets); + } else { + cmSystemTools::Error("'--preset' requires an argument"); + success = false; + } + } + + if (listPresets) { + return success ? 0 : 1; + } + + if (!success) { + return 1; + } + } + // process the command line arguments for (size_t i = 1; i < args.size(); ++i) { // handle the simple commandline arguments @@ -2339,7 +2677,7 @@ int cmCTest::Run(std::vector<std::string>& args, std::string* output) this->Impl->ScheduleType = "Random"; } - // pass the argument to all the handlers as well, but i may no longer be + // pass the argument to all the handlers as well, but it may no longer be // set to what it was originally so I'm not sure this is working as // intended for (auto& handler : this->Impl->GetTestingHandlers()) { diff --git a/Source/cmCTest.h b/Source/cmCTest.h index e12f8b0..4669a1c 100644 --- a/Source/cmCTest.h +++ b/Source/cmCTest.h @@ -461,6 +461,9 @@ public: void SetRunCurrentScript(bool value); private: + void SetPersistentOptionIfNotEmpty(const std::string& value, + const std::string& optionName); + int GenerateNotesFile(const std::string& files); void BlockTestErrorDiagnostics(); @@ -484,6 +487,9 @@ private: /** add a variable definition from a command line -D value */ bool AddVariableDefinition(const std::string& arg); + /** set command line arguments read from a test preset */ + bool SetArgsFromPreset(const std::string& presetName, bool listPresets); + /** parse and process most common command line arguments */ bool HandleCommandLineArguments(size_t& i, std::vector<std::string>& args, std::string& errormsg); diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 48848a7..4b57395 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -726,6 +726,17 @@ void cmake::LoadEnvironmentPresets() readGeneratorVar("CMAKE_GENERATOR_TOOLSET", this->GeneratorToolset); } +namespace { +enum class ListPresets +{ + None, + Configure, + Build, + Test, + All, +}; +} + // Parse the args void cmake::SetArgs(const std::vector<std::string>& args) { @@ -738,7 +749,8 @@ void cmake::SetArgs(const std::vector<std::string>& args) std::string profilingFormat; std::string profilingOutput; std::string presetName; - bool listPresets = false; + + ListPresets listPresets = ListPresets::None; #endif auto SourceArgLambda = [](std::string const& value, cmake* state) -> bool { @@ -995,11 +1007,27 @@ void cmake::SetArgs(const std::vector<std::string>& args) presetName = value; return true; }); - arguments.emplace_back("--list-presets", CommandArgument::Values::Zero, - [&](std::string const&, cmake*) -> bool { - listPresets = true; - return true; - }); + arguments.emplace_back( + "--list-presets", CommandArgument::Values::ZeroOrOne, + [&](std::string const& value, cmake*) -> bool { + if (value.empty() || value == "configure") { + listPresets = ListPresets::Configure; + } else if (value == "build") { + listPresets = ListPresets::Build; + } else if (value == "test") { + listPresets = ListPresets::Test; + } else if (value == "all") { + listPresets = ListPresets::All; + } else { + cmSystemTools::Error( + "Invalid value specified for --list-presets.\n" + "Valid values are configure, build, test, or all. " + "When no value is passed the default is configure."); + return false; + } + + return true; + }); #endif @@ -1119,7 +1147,7 @@ void cmake::SetArgs(const std::vector<std::string>& args) } #if !defined(CMAKE_BOOTSTRAP) - if (listPresets || !presetName.empty()) { + if (listPresets != ListPresets::None || !presetName.empty()) { cmCMakePresetsFile settingsFile; auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory()); if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) { @@ -1128,12 +1156,24 @@ void cmake::SetArgs(const std::vector<std::string>& args) ": ", cmCMakePresetsFile::ResultToString(result))); return; } - if (listPresets) { - this->PrintPresetList(settingsFile); + + if (listPresets != ListPresets::None) { + if (listPresets == ListPresets::Configure) { + this->PrintPresetList(settingsFile); + } else if (listPresets == ListPresets::Build) { + settingsFile.PrintBuildPresetList(); + } else if (listPresets == ListPresets::Test) { + settingsFile.PrintTestPresetList(); + } else if (listPresets == ListPresets::All) { + settingsFile.PrintAllPresets(); + } + + this->SetWorkingMode(WorkingMode::HELP_MODE); return; } - auto preset = settingsFile.Presets.find(presetName); - if (preset == settingsFile.Presets.end()) { + + auto preset = settingsFile.ConfigurePresets.find(presetName); + if (preset == settingsFile.ConfigurePresets.end()) { cmSystemTools::Error(cmStrCat("No such preset in ", this->GetHomeDirectory(), ": \"", presetName, '"')); @@ -1562,44 +1602,16 @@ void cmake::PrintPresetList(const cmCMakePresetsFile& file) const { std::vector<GeneratorInfo> generators; this->GetRegisteredGenerators(generators, false); + auto filter = + [&generators](const cmCMakePresetsFile::ConfigurePreset& preset) -> bool { + auto condition = [&preset](const GeneratorInfo& info) -> bool { + return info.name == preset.Generator; + }; + auto it = std::find_if(generators.begin(), generators.end(), condition); + return it != generators.end(); + }; - std::vector<cmCMakePresetsFile::UnexpandedPreset> presets; - for (auto const& p : file.PresetOrder) { - auto const& preset = file.Presets.at(p); - if (!preset.Unexpanded.Hidden && preset.Expanded && - std::find_if(generators.begin(), generators.end(), - [&preset](const GeneratorInfo& info) { - return info.name == preset.Unexpanded.Generator; - }) != generators.end()) { - presets.push_back(preset.Unexpanded); - } - } - - if (presets.empty()) { - return; - } - - std::cout << "Available presets:\n\n"; - - auto longestPresetName = - std::max_element(presets.begin(), presets.end(), - [](const cmCMakePresetsFile::UnexpandedPreset& a, - const cmCMakePresetsFile::UnexpandedPreset& b) { - return a.Name.length() < b.Name.length(); - }); - auto longestLength = longestPresetName->Name.length(); - - for (auto const& preset : presets) { - std::cout << " \"" << preset.Name << '"'; - auto const& description = preset.DisplayName; - if (!description.empty()) { - for (std::size_t i = 0; i < longestLength - preset.Name.length(); ++i) { - std::cout << ' '; - } - std::cout << " - " << description; - } - std::cout << '\n'; - } + file.PrintConfigurePresetList(filter); } #endif @@ -3068,15 +3080,119 @@ std::vector<std::string> cmake::GetDebugConfigs() return configs; } -int cmake::Build(int jobs, const std::string& dir, - const std::vector<std::string>& targets, - const std::string& config, - const std::vector<std::string>& nativeOptions, bool clean, - bool verbose) +int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets, + std::string config, std::vector<std::string> nativeOptions, + bool clean, bool verbose, const std::string& presetName, + bool listPresets) { - this->SetHomeDirectory(""); this->SetHomeOutputDirectory(""); + +#if !defined(CMAKE_BOOTSTRAP) + if (!presetName.empty() || listPresets) { + this->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory()); + this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory()); + + cmCMakePresetsFile settingsFile; + auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory()); + if (result != cmCMakePresetsFile::ReadFileResult::READ_OK) { + cmSystemTools::Error( + cmStrCat("Could not read presets from ", this->GetHomeDirectory(), + ": ", cmCMakePresetsFile::ResultToString(result))); + return 1; + } + + if (listPresets) { + settingsFile.PrintBuildPresetList(); + return 0; + } + + auto presetPair = settingsFile.BuildPresets.find(presetName); + if (presetPair == settingsFile.BuildPresets.end()) { + cmSystemTools::Error(cmStrCat("No such build preset in ", + this->GetHomeDirectory(), ": \"", + presetName, '"')); + settingsFile.PrintBuildPresetList(); + return 1; + } + + if (presetPair->second.Unexpanded.Hidden) { + cmSystemTools::Error(cmStrCat("Cannot use hidden build preset in ", + this->GetHomeDirectory(), ": \"", + presetName, '"')); + settingsFile.PrintBuildPresetList(); + return 1; + } + + auto const& expandedPreset = presetPair->second.Expanded; + if (!expandedPreset) { + cmSystemTools::Error(cmStrCat("Could not evaluate build preset \"", + presetName, + "\": Invalid macro expansion")); + settingsFile.PrintBuildPresetList(); + return 1; + } + + auto configurePresetPair = + settingsFile.ConfigurePresets.find(expandedPreset->ConfigurePreset); + if (configurePresetPair == settingsFile.ConfigurePresets.end()) { + cmSystemTools::Error(cmStrCat("No such configure preset in ", + this->GetHomeDirectory(), ": \"", + expandedPreset->ConfigurePreset, '"')); + this->PrintPresetList(settingsFile); + return 1; + } + + if (configurePresetPair->second.Unexpanded.Hidden) { + cmSystemTools::Error(cmStrCat("Cannot use hidden configure preset in ", + this->GetHomeDirectory(), ": \"", + expandedPreset->ConfigurePreset, '"')); + this->PrintPresetList(settingsFile); + return 1; + } + + auto const& expandedConfigurePreset = configurePresetPair->second.Expanded; + if (!expandedConfigurePreset) { + cmSystemTools::Error(cmStrCat("Could not evaluate configure preset \"", + expandedPreset->ConfigurePreset, + "\": Invalid macro expansion")); + return 1; + } + + dir = expandedConfigurePreset->BinaryDir; + + this->UnprocessedPresetEnvironment = expandedPreset->Environment; + this->ProcessPresetEnvironment(); + + if (jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL && expandedPreset->Jobs) { + jobs = *expandedPreset->Jobs; + } + + if (targets.empty()) { + targets.insert(targets.begin(), expandedPreset->Targets.begin(), + expandedPreset->Targets.end()); + } + + if (config.empty()) { + config = expandedPreset->Configuration; + } + + if (!clean && expandedPreset->CleanFirst) { + clean = *expandedPreset->CleanFirst; + } + + if (!verbose && expandedPreset->Verbose) { + verbose = *expandedPreset->Verbose; + } + + if (nativeOptions.empty()) { + nativeOptions.insert(nativeOptions.begin(), + expandedPreset->NativeToolOptions.begin(), + expandedPreset->NativeToolOptions.end()); + } + } +#endif + if (!cmSystemTools::FileIsDirectory(dir)) { std::cerr << "Error: " << dir << " is not a directory\n"; return 1; diff --git a/Source/cmake.h b/Source/cmake.h index d936f28..82e028c 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -238,7 +238,7 @@ public: bool CreateAndSetGlobalGenerator(const std::string& name, bool allowArch); #ifndef CMAKE_BOOTSTRAP - //! Print list of presets + //! Print list of configure presets void PrintPresetList(const cmCMakePresetsFile& file) const; #endif @@ -556,10 +556,10 @@ public: cmListFileBacktrace const& backtrace = cmListFileBacktrace()) const; //! run the --build option - int Build(int jobs, const std::string& dir, - const std::vector<std::string>& targets, const std::string& config, - const std::vector<std::string>& nativeOptions, bool clean, - bool verbose); + int Build(int jobs, std::string dir, std::vector<std::string> targets, + std::string config, std::vector<std::string> nativeOptions, + bool clean, bool verbose, const std::string& presetName, + bool listPresets); //! run the --open option bool Open(const std::string& dir, bool dryRun); diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx index ba471b7..cd3c955 100644 --- a/Source/cmakemain.cxx +++ b/Source/cmakemain.cxx @@ -425,6 +425,8 @@ int do_build(int ac, char const* const* av) bool foundClean = false; bool foundNonClean = false; bool verbose = cmSystemTools::HasEnv("VERBOSE"); + std::string presetName; + bool listPresets = false; auto jLambda = [&](std::string const& value) -> bool { jobs = extract_job_number("-j", value); @@ -464,6 +466,16 @@ int do_build(int ac, char const* const* av) cmCommandLineArgument<bool(std::string const& value)>; std::vector<CommandArgument> arguments = { + CommandArgument{ "--preset", CommandArgument::Values::One, + [&](std::string const& value) -> bool { + presetName = value; + return true; + } }, + CommandArgument{ "--list-presets", CommandArgument::Values::Zero, + [&](std::string const&) -> bool { + listPresets = true; + return true; + } }, CommandArgument{ "-j", CommandArgument::Values::ZeroOrOne, jLambda }, CommandArgument{ "--parallel", CommandArgument::Values::ZeroOrOne, parallelLambda }, @@ -494,11 +506,26 @@ int do_build(int ac, char const* const* av) }; if (ac >= 3) { - dir = cmSystemTools::CollapseFullPath(av[2]); - std::vector<std::string> inputArgs; - inputArgs.reserve(ac - 3); - cm::append(inputArgs, av + 3, av + ac); + + bool hasPreset = false; + for (int i = 2; i < ac; ++i) { + if (strcmp(av[i], "--list-presets") == 0 || + strcmp(av[i], "--preset") == 0) { + hasPreset = true; + break; + } + } + + if (hasPreset) { + inputArgs.reserve(ac - 2); + cm::append(inputArgs, av + 2, av + ac); + } else { + dir = cmSystemTools::CollapseFullPath(av[2]); + + inputArgs.reserve(ac - 3); + cm::append(inputArgs, av + 3, av + ac); + } decltype(inputArgs.size()) i = 0; for (; i < inputArgs.size() && !nativeOptionsPassed; ++i) { @@ -551,12 +578,16 @@ int do_build(int ac, char const* const* av) } } - if (dir.empty()) { + if (dir.empty() && presetName.empty() && !listPresets) { /* clang-format off */ std::cerr << - "Usage: cmake --build <dir> [options] [-- [native-options]]\n" + "Usage: cmake --build [<dir> | --preset <preset>] [options] [-- [native-options]]\n" "Options:\n" " <dir> = Project binary directory to be built.\n" + " --preset <preset>\n" + " = Specify a build preset.\n" + " --list-presets\n" + " = List available build presets.\n" " --parallel [<jobs>], -j [<jobs>]\n" " = Build in parallel using the given number of jobs. \n" " If <jobs> is omitted the native build tool's \n" @@ -587,8 +618,10 @@ int do_build(int ac, char const* const* av) cm.SetProgressCallback([&cm](const std::string& msg, float prog) { cmakemainProgressCallback(msg, prog, &cm); }); - return cm.Build(jobs, dir, targets, config, nativeOptions, cleanFirst, - verbose); + + return cm.Build(jobs, std::move(dir), std::move(targets), std::move(config), + std::move(nativeOptions), cleanFirst, verbose, presetName, + listPresets); #endif } diff --git a/Source/ctest.cxx b/Source/ctest.cxx index 600df1d..1404b0c 100644 --- a/Source/ctest.cxx +++ b/Source/ctest.cxx @@ -26,6 +26,8 @@ static const char* cmDocumentationUsage[][2] = { { nullptr, { nullptr, nullptr } }; static const char* cmDocumentationOptions[][2] = { + { "--preset <preset>", "Read arguments from a test preset." }, + { "--list-presets", "List available test presets." }, { "-C <cfg>, --build-config <cfg>", "Choose configuration to test." }, { "--progress", "Enable short progress output from tests." }, { "-V,--verbose", "Enable verbose output from tests." }, |