Files
cmake/Source/cmCMakePresetsGraph.cxx
T
Richard b1f15344c9 Presets: Clarify workflow step error messages
Report both the workflow step type and preset name when workflow
preset validation or execution fails.  This disambiguates workflows
where configure, build, test, and package steps share the same preset
name.

Update the workflow preset RunCMake expectations.
2026-07-31 10:44:57 -06:00

1530 lines
49 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmCMakePresetsGraph.h"
#include <algorithm>
#include <cassert>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <utility>
#include <cm/memory>
#include <cm/string_view>
#include "cmsys/RegularExpression.hxx"
#include "cmCMakePresetsErrors.h"
#include "cmCMakePresetsGraphInternal.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#define CHECK_EXPAND(out, field, expanders, version) \
do { \
switch (ExpandMacros(field, expanders, version)) { \
case ExpandMacroResult::Error: \
return false; \
case ExpandMacroResult::Ignore: \
out.reset(); \
return true; \
case ExpandMacroResult::Defer: \
CM_FALLTHROUGH; \
case ExpandMacroResult::Ok: \
break; \
} \
} while (false)
namespace {
enum class CycleStatus
{
Unvisited,
InProgress,
Verified,
};
using ConfigurePreset = cmCMakePresetsGraph::ConfigurePreset;
using BuildPreset = cmCMakePresetsGraph::BuildPreset;
using TestPreset = cmCMakePresetsGraph::TestPreset;
using PackagePreset = cmCMakePresetsGraph::PackagePreset;
using WorkflowPreset = cmCMakePresetsGraph::WorkflowPreset;
template <typename T>
using PresetPair = cmCMakePresetsGraph::PresetPair<T>;
using ExpandMacroResult = cmCMakePresetsGraphInternal::ExpandMacroResult;
using MacroExpander = cmCMakePresetsGraphInternal::MacroExpander;
using MacroExpanderVector = cmCMakePresetsGraphInternal::MacroExpanderVector;
using BaseMacroExpander = cmCMakePresetsGraphInternal::BaseMacroExpander;
template <typename T>
using PresetMacroExpander =
cmCMakePresetsGraphInternal::PresetMacroExpander<T>;
template <typename T>
using ImmediateMacroExpander =
cmCMakePresetsGraphInternal::ImmediateMacroExpander<T>;
using cmCMakePresetsGraphInternal::ExpandMacros;
bool gSkipNewLine = true;
char const* WorkflowStepTypeToString(WorkflowPreset::WorkflowStep::Type type)
{
switch (type) {
case WorkflowPreset::WorkflowStep::Type::Configure:
return "configure";
case WorkflowPreset::WorkflowStep::Type::Build:
return "build";
case WorkflowPreset::WorkflowStep::Type::Test:
return "test";
case WorkflowPreset::WorkflowStep::Type::Package:
return "package";
}
return "";
}
void InheritString(std::string& child, std::string const& parent)
{
if (child.empty()) {
child = parent;
}
}
template <typename T>
void InheritOptionalValue(cm::optional<T>& child,
cm::optional<T> const& parent)
{
if (!child) {
child = parent;
}
}
template <typename T>
void InheritVector(std::vector<T>& child, std::vector<T> const& parent)
{
if (child.empty()) {
child = parent;
}
}
template <typename K, typename V>
void InheritMap(std::map<K, V>& child, std::map<K, V> const& parent)
{
for (auto const& item : parent) {
child.insert(item);
}
}
/**
* 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 <typename T>
bool VisitPreset(
T& preset,
std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
std::map<std::string, CycleStatus> cycleStatus, cmCMakePresetsGraph& graph)
{
switch (cycleStatus[preset.Name]) {
case CycleStatus::InProgress:
cmCMakePresetsErrors::CYCLIC_PRESET_INHERITANCE(
preset.Name, preset.kind(), &graph.parseState);
return false;
case CycleStatus::Verified:
return true;
default:
break;
}
cycleStatus[preset.Name] = CycleStatus::InProgress;
if (preset.Environment.count("") != 0) {
cmCMakePresetsErrors::INVALID_PRESET_NAMED(
preset.Name, preset.kind(), &graph.parseState,
"Empty environment variable names are not allowed");
return false;
}
bool result = preset.VisitPresetBeforeInherit();
if (!result) {
cmCMakePresetsErrors::INVALID_PRESET_NAMED(
preset.Name, preset.kind(), &graph.parseState, preset.ErrorDetail);
return false;
}
for (auto const& i : preset.Inherits) {
auto parent = presets.find(i);
if (parent == presets.end()) {
cmCMakePresetsErrors::INVALID_PRESET_NAMED(
preset.Name, preset.kind(), &graph.parseState,
cmStrCat("Could not find inherited preset \"", i, "\""));
return false;
}
auto& parentPreset = parent->second.Unexpanded;
if (!preset.OriginFile->ReachableFiles.count(parentPreset.OriginFile)) {
cmCMakePresetsErrors::INHERITED_PRESET_UNREACHABLE_FROM_FILE(
preset.Name, i, preset.kind(), &graph.parseState);
return false;
}
if (!VisitPreset(parentPreset, presets, cycleStatus, graph)) {
return false;
}
result = preset.VisitPresetInherit(parentPreset);
if (!result) {
cmCMakePresetsErrors::INVALID_PRESET_NAMED(
preset.Name, preset.kind(), &graph.parseState, preset.ErrorDetail);
return false;
}
InheritMap(preset.Environment, parentPreset.Environment);
if (!preset.ConditionEvaluator) {
preset.ConditionEvaluator = parentPreset.ConditionEvaluator;
}
}
if (preset.ConditionEvaluator && preset.ConditionEvaluator->IsNull()) {
preset.ConditionEvaluator.reset();
}
result = preset.VisitPresetAfterInherit(graph.GetVersion(preset),
&graph.parseState);
if (!result) {
cmCMakePresetsErrors::INVALID_PRESET_NAMED(
preset.Name, preset.kind(), &graph.parseState, preset.ErrorDetail);
return false;
}
cycleStatus[preset.Name] = CycleStatus::Verified;
return true;
}
template <typename T>
bool ComputePresetInheritance(
std::map<std::string, cmCMakePresetsGraph::PresetPair<T>>& presets,
cmCMakePresetsGraph& graph)
{
std::map<std::string, CycleStatus> cycleStatus;
for (auto const& it : presets) {
cycleStatus[it.first] = CycleStatus::Unvisited;
}
for (auto& it : presets) {
auto& preset = it.second.Unexpanded;
if (!VisitPreset<T>(preset, presets, cycleStatus, graph)) {
return false;
}
}
return true;
}
constexpr char const* ValidPrefixes[] = {
"",
"env",
"penv",
"vendor",
};
bool PrefixesValidMacroNamespace(std::string const& str)
{
return std::any_of(
std::begin(ValidPrefixes), std::end(ValidPrefixes),
[&str](char const* prefix) -> bool { return cmHasPrefix(prefix, str); });
}
bool IsValidMacroNamespace(std::string const& str)
{
return std::any_of(
std::begin(ValidPrefixes), std::end(ValidPrefixes),
[&str](char const* prefix) -> bool { return str == prefix; });
}
ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status,
MacroExpanderVector const& macroExpanders,
int version);
template <typename T>
class EnvironmentMacroExpander : public MacroExpander
{
std::map<std::string, CycleStatus>& EnvCycles;
cm::optional<T>& Out;
MacroExpanderVector& MacroExpanders;
public:
EnvironmentMacroExpander(MacroExpanderVector& macroExpanders,
cm::optional<T>& out,
std::map<std::string, CycleStatus>& envCycles)
: EnvCycles(envCycles)
, Out(out)
, MacroExpanders(macroExpanders)
{
}
ExpandMacroResult operator()(std::string const& macroNamespace,
std::string const& macroName,
std::string& macroOut,
int version) const override
{
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, version);
if (e != ExpandMacroResult::Ok) {
return e;
}
macroOut += *v->second;
return ExpandMacroResult::Ok;
}
}
if (macroNamespace == "env" || macroNamespace == "penv") {
if (macroName.empty()) {
return ExpandMacroResult::Error;
}
if (cm::optional<std::string> value =
cmSystemTools::GetEnvVar(macroName)) {
macroOut += *value;
}
return ExpandMacroResult::Ok;
}
return ExpandMacroResult::Ignore;
}
};
bool CheckExpandMacros(ConfigurePreset const& preset,
cm::optional<ConfigurePreset>& out,
MacroExpanderVector const& macroExpanders,
cmCMakePresetsGraph const* graph = nullptr)
{
int version = graph ? graph->GetVersion(preset) : preset.OriginFile->Version;
std::string binaryDir = preset.BinaryDir;
CHECK_EXPAND(out, binaryDir, macroExpanders, version);
if (!binaryDir.empty()) {
if (graph) {
if (!cmSystemTools::FileIsFullPath(binaryDir)) {
binaryDir = cmStrCat(graph->SourceDir, '/', binaryDir);
}
out->BinaryDir = cmSystemTools::CollapseFullPath(binaryDir);
cmSystemTools::ConvertToUnixSlashes(out->BinaryDir);
} else {
// Defer path normalization until the graph is merged, but store the
// intermediate result.
out->BinaryDir = binaryDir;
}
}
if (!preset.InstallDir.empty()) {
std::string installDir = preset.InstallDir;
CHECK_EXPAND(out, installDir, macroExpanders, version);
if (graph) {
if (!cmSystemTools::FileIsFullPath(installDir)) {
installDir = cmStrCat(graph->SourceDir, '/', installDir);
}
out->InstallDir = cmSystemTools::CollapseFullPath(installDir);
cmSystemTools::ConvertToUnixSlashes(out->InstallDir);
} else {
// Defer path normalization until the graph is merged, but store the
// intermediate result.
out->InstallDir = installDir;
}
}
if (!preset.ToolchainFile.empty()) {
std::string toolchain = preset.ToolchainFile;
CHECK_EXPAND(out, toolchain, macroExpanders, version);
out->ToolchainFile = toolchain;
}
if (!preset.GraphVizFile.empty()) {
std::string graphVizFile = preset.GraphVizFile;
CHECK_EXPAND(out, graphVizFile, macroExpanders, version);
out->GraphVizFile = graphVizFile;
}
for (auto& variable : out->CacheVariables) {
if (variable.second) {
CHECK_EXPAND(out, variable.second->Value, macroExpanders, version);
}
}
return true;
}
bool CheckExpandMacros(BuildPreset const& preset,
cm::optional<BuildPreset>& out,
MacroExpanderVector const& macroExpanders,
cmCMakePresetsGraph const* graph = nullptr)
{
int version = graph ? graph->GetVersion(preset) : preset.OriginFile->Version;
for (auto& target : out->Targets) {
CHECK_EXPAND(out, target, macroExpanders, version);
}
for (auto& nativeToolOption : out->NativeToolOptions) {
CHECK_EXPAND(out, nativeToolOption, macroExpanders, version);
}
return true;
}
bool CheckExpandMacros(TestPreset const& preset, cm::optional<TestPreset>& out,
MacroExpanderVector const& macroExpanders,
cmCMakePresetsGraph const* graph = nullptr)
{
int version = graph ? graph->GetVersion(preset) : preset.OriginFile->Version;
for (auto& overwrite : out->OverwriteConfigurationFile) {
CHECK_EXPAND(out, overwrite, macroExpanders, version);
}
if (out->Output) {
CHECK_EXPAND(out, out->Output->OutputLogFile, macroExpanders, version);
CHECK_EXPAND(out, out->Output->OutputJUnitFile, macroExpanders, version);
}
if (out->Filter) {
if (out->Filter->Include) {
CHECK_EXPAND(out, out->Filter->Include->Name, macroExpanders, version);
CHECK_EXPAND(out, out->Filter->Include->Label, macroExpanders, version);
if (out->Filter->Include->Index) {
CHECK_EXPAND(out, out->Filter->Include->Index->IndexFile,
macroExpanders, version);
}
}
if (out->Filter->Exclude) {
CHECK_EXPAND(out, out->Filter->Exclude->Name, macroExpanders, version);
CHECK_EXPAND(out, out->Filter->Exclude->Label, macroExpanders, version);
if (out->Filter->Exclude->Fixtures) {
CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Any, macroExpanders,
version);
CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Setup,
macroExpanders, version);
CHECK_EXPAND(out, out->Filter->Exclude->Fixtures->Cleanup,
macroExpanders, version);
}
}
}
if (out->Execution) {
CHECK_EXPAND(out, out->Execution->ResourceSpecFile, macroExpanders,
version);
}
return true;
}
bool CheckExpandMacros(PackagePreset const& preset,
cm::optional<PackagePreset>& out,
MacroExpanderVector const& macroExpanders,
cmCMakePresetsGraph const* graph = nullptr)
{
int version = graph ? graph->GetVersion(preset) : preset.OriginFile->Version;
for (auto& variable : out->Variables) {
CHECK_EXPAND(out, variable.second, macroExpanders, version);
}
CHECK_EXPAND(out, out->ConfigFile, macroExpanders, version);
CHECK_EXPAND(out, out->PackageName, macroExpanders, version);
CHECK_EXPAND(out, out->PackageVersion, macroExpanders, version);
CHECK_EXPAND(out, out->PackageDirectory, macroExpanders, version);
CHECK_EXPAND(out, out->VendorName, macroExpanders, version);
return true;
}
bool CheckExpandMacros(WorkflowPreset const& /*preset*/,
cm::optional<WorkflowPreset>& /*out*/,
MacroExpanderVector const& /*macroExpanders*/,
cmCMakePresetsGraph const* /*graph*/ = nullptr)
{
return true;
}
template <typename T>
bool ExpandMacros(cmCMakePresetsGraph* graph, T const& 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;
}
MacroExpanderVector macroExpanders{};
macroExpanders.push_back(cm::make_unique<BaseMacroExpander>(*graph));
macroExpanders.push_back(
cm::make_unique<PresetMacroExpander<T>>(*graph, preset));
macroExpanders.push_back(cm::make_unique<EnvironmentMacroExpander<T>>(
macroExpanders, out, envCycles));
for (auto& v : out->Environment) {
if (v.second) {
switch (VisitEnv(*v.second, envCycles[v.first], macroExpanders,
graph->GetVersion(preset))) {
case ExpandMacroResult::Error:
cmCMakePresetsErrors::INVALID_PRESET_NAMED(
preset.Name, preset.kind(), &graph->parseState,
"Invalid macro expansion");
return false;
case ExpandMacroResult::Ignore:
out.reset();
return true;
case ExpandMacroResult::Defer:
CM_FALLTHROUGH;
case ExpandMacroResult::Ok:
break;
}
}
}
if (preset.ConditionEvaluator) {
cm::optional<bool> result;
if (!preset.ConditionEvaluator->Evaluate(
macroExpanders, graph->GetVersion(preset), result)) {
cmCMakePresetsErrors::INVALID_PRESET_NAMED(
preset.Name, preset.kind(), &graph->parseState, "Invalid condition");
return false;
}
if (!result) {
out.reset();
return true;
}
out->ConditionResult = *result;
}
return CheckExpandMacros(preset, out, macroExpanders, graph);
}
ExpandMacroResult VisitEnv(std::string& value, CycleStatus& status,
MacroExpanderVector const& macroExpanders,
int version)
{
if (status == CycleStatus::Verified) {
return ExpandMacroResult::Ok;
}
if (status == CycleStatus::InProgress) {
return ExpandMacroResult::Error;
}
status = CycleStatus::InProgress;
auto e = ExpandMacros(value, macroExpanders, version);
if (e != ExpandMacroResult::Ok) {
return e;
}
status = CycleStatus::Verified;
return ExpandMacroResult::Ok;
}
void PrintPresets(
std::vector<cmCMakePresetsGraph::Preset const*> const& presets)
{
if (presets.empty()) {
return;
}
auto presetWithLongestName =
std::max_element(presets.begin(), presets.end(),
[](cmCMakePresetsGraph::Preset const* a,
cmCMakePresetsGraph::Preset const* b) {
return a->Name.length() < b->Name.length();
});
auto longestLength = (*presetWithLongestName)->Name.length();
for (auto const* preset : presets) {
std::string name = cmStrCat(" \"", preset->Name, '"');
if (!preset->DisplayName.empty()) {
int const width = static_cast<int>(longestLength + name.length() -
preset->Name.length());
std::cout << std::left << std::setw(width) << name << " - "
<< preset->DisplayName << '\n';
} else {
std::cout << name << '\n';
}
}
}
struct AlwaysTrue
{
template <typename T>
constexpr bool operator()(T const&) const noexcept
{
return true;
}
};
template <typename PresetType, typename Filter = AlwaysTrue>
void PrintPresetList(
cmCMakePresetsGraph const* const graph,
std::map<std::string, cmCMakePresetsGraph::PresetPair<PresetType>>
cmCMakePresetsGraph::*data,
std::vector<std::string> cmCMakePresetsGraph::*index, Filter filter = {})
{
std::vector<cmCMakePresetsGraph::Preset const*> presets;
presets.reserve((graph->*index).size());
for (auto const& p : graph->*index) {
auto const& preset = (graph->*data).at(p);
if (!preset.Unexpanded.Hidden && preset.Expanded &&
preset.Expanded->ConditionResult && filter(preset.Unexpanded)) {
presets.push_back(
static_cast<cmCMakePresetsGraph::Preset const*>(&preset.Unexpanded));
}
}
if (!presets.empty()) {
std::cout << (gSkipNewLine ? "" : "\n") << "Available "
<< PresetType::kind() << " presets:\n\n";
gSkipNewLine = false;
PrintPresets(presets);
}
}
}
template <typename T>
bool cmCMakePresetsGraphInternal::ExpandImmediateMacros(T& preset)
{
MacroExpanderVector macroExpanders{};
macroExpanders.push_back(cm::make_unique<ImmediateMacroExpander<T>>(preset));
cm::optional<T> out = preset;
bool result = CheckExpandMacros(preset, out, macroExpanders);
if (out.has_value()) {
preset = out.value();
};
return result;
}
template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros<
ConfigurePreset>(ConfigurePreset&);
template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros<BuildPreset>(
BuildPreset&);
template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros<TestPreset>(
TestPreset&);
template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros<
PackagePreset>(PackagePreset&);
template bool cmCMakePresetsGraphInternal::ExpandImmediateMacros<
WorkflowPreset>(WorkflowPreset&);
ExpandMacroResult cmCMakePresetsGraphInternal::ExpandMacros(
std::string& out, MacroExpanderVector const& macroExpanders, int version)
{
std::string result;
std::string macroNamespace;
std::string macroName;
enum class State
{
Default,
MacroNamespace,
MacroName,
} state = State::Default;
for (auto c : out) {
switch (state) {
case State::Default:
if (c == '$') {
state = State::MacroNamespace;
} else {
result += c;
}
break;
case State::MacroNamespace:
if (c == '{') {
if (IsValidMacroNamespace(macroNamespace)) {
state = State::MacroName;
} else {
result += '$';
result += macroNamespace;
result += '{';
macroNamespace.clear();
state = State::Default;
}
} else {
macroNamespace += c;
if (!PrefixesValidMacroNamespace(macroNamespace)) {
result += '$';
result += macroNamespace;
macroNamespace.clear();
state = State::Default;
}
}
break;
case State::MacroName:
if (c == '}') {
auto e = ExpandMacro(result, macroNamespace, macroName,
macroExpanders, version);
if (e != ExpandMacroResult::Ok) {
return e;
}
macroNamespace.clear();
macroName.clear();
state = State::Default;
} else {
macroName += c;
}
break;
}
}
switch (state) {
case State::Default:
break;
case State::MacroNamespace:
result += '$';
result += macroNamespace;
break;
case State::MacroName:
return ExpandMacroResult::Error;
}
out = std::move(result);
return ExpandMacroResult::Ok;
}
ExpandMacroResult cmCMakePresetsGraphInternal::ExpandMacro(
std::string& out, std::string const& macroNamespace,
std::string const& macroName, MacroExpanderVector const& macroExpanders,
int version)
{
for (auto const& macroExpander : macroExpanders) {
auto result = (*macroExpander)(macroNamespace, macroName, out, version);
if (result != ExpandMacroResult::Ignore) {
return result;
}
}
if (macroNamespace == "vendor") {
return ExpandMacroResult::Ignore;
}
return ExpandMacroResult::Error;
}
namespace {
template <typename T>
bool SetupWorkflowConfigurePreset(T const& preset,
ConfigurePreset const*& configurePreset,
char const*, std::string const&,
cmJSONState* state)
{
if (preset.ConfigurePreset != configurePreset->Name) {
cmCMakePresetsErrors::WORKFLOW_STEP_CONFIGURE_PRESET_MISMATCH(
preset.kind(), preset.Name, preset.ConfigurePreset,
configurePreset->Name, state);
return false;
}
return true;
}
template <>
bool SetupWorkflowConfigurePreset<ConfigurePreset>(
ConfigurePreset const& preset, ConfigurePreset const*& configurePreset,
char const*, std::string const&, cmJSONState*)
{
configurePreset = &preset;
return true;
}
template <typename T>
bool TryReachPresetFromWorkflow(
WorkflowPreset const& origin,
std::map<std::string, PresetPair<T>> const& presets, std::string const& name,
char const* workflowStepType, ConfigurePreset const*& configurePreset,
cmJSONState* state)
{
auto it = presets.find(name);
if (it == presets.end()) {
cmCMakePresetsErrors::INVALID_WORKFLOW_STEPS(workflowStepType, name,
state);
return false;
}
if (!origin.OriginFile->ReachableFiles.count(
it->second.Unexpanded.OriginFile)) {
cmCMakePresetsErrors::WORKFLOW_STEP_UNREACHABLE_FROM_FILE(workflowStepType,
name, state);
return false;
}
return SetupWorkflowConfigurePreset<T>(
it->second.Unexpanded, configurePreset, workflowStepType, name, state);
}
}
ExpandMacroResult BaseMacroExpander::operator()(
std::string const& macroNamespace, std::string const& macroName,
std::string& macroOut, int version) const
{
if (macroNamespace.empty()) {
if (macroName == "sourceDir") {
macroOut += Graph.SourceDir;
return ExpandMacroResult::Ok;
}
if (macroName == "sourceParentDir") {
macroOut += cmSystemTools::GetParentDirectory(Graph.SourceDir);
return ExpandMacroResult::Ok;
}
if (macroName == "sourceDirName") {
macroOut += cmSystemTools::GetFilenameName(Graph.SourceDir);
return ExpandMacroResult::Ok;
}
if (macroName == "dollar") {
macroOut += '$';
return ExpandMacroResult::Ok;
}
if (macroName == "hostSystemName") {
if (version < 3) {
return ExpandMacroResult::Error;
}
macroOut += cmSystemTools::GetSystemName();
return ExpandMacroResult::Ok;
}
// Enable fileDir macro expansion for non-preset expanders
if (macroName == "fileDir" && File) {
if (version < 4) {
return ExpandMacroResult::Error;
}
macroOut += cmSystemTools::GetParentDirectory(File.value());
return ExpandMacroResult::Ok;
}
if (macroName == "pathListSep") {
if (version < 5) {
return ExpandMacroResult::Error;
}
macroOut += cmSystemTools::GetSystemPathlistSeparator();
return ExpandMacroResult::Ok;
}
}
return ExpandMacroResult::Ignore;
}
bool cmCMakePresetsGraphInternal::EqualsCondition::Evaluate(
MacroExpanderVector const& expanders, int version,
cm::optional<bool>& out) const
{
std::string lhs = this->Lhs;
CHECK_EXPAND(out, lhs, expanders, version);
std::string rhs = this->Rhs;
CHECK_EXPAND(out, rhs, expanders, version);
out = (lhs == rhs);
return true;
}
bool cmCMakePresetsGraphInternal::InListCondition::Evaluate(
MacroExpanderVector const& expanders, int version,
cm::optional<bool>& out) const
{
std::string str = this->String;
CHECK_EXPAND(out, str, expanders, version);
for (auto item : this->List) {
CHECK_EXPAND(out, item, expanders, version);
if (str == item) {
out = true;
return true;
}
}
out = false;
return true;
}
bool cmCMakePresetsGraphInternal::MatchesCondition::Evaluate(
MacroExpanderVector const& expanders, int version,
cm::optional<bool>& out) const
{
std::string str = this->String;
CHECK_EXPAND(out, str, expanders, version);
std::string regexStr = this->Regex;
CHECK_EXPAND(out, regexStr, expanders, version);
cmsys::RegularExpression regex;
if (!regex.compile(regexStr)) {
return false;
}
out = regex.find(str);
return true;
}
bool cmCMakePresetsGraphInternal::AnyAllOfCondition::Evaluate(
MacroExpanderVector const& expanders, int version,
cm::optional<bool>& out) const
{
for (auto const& condition : this->Conditions) {
cm::optional<bool> result;
if (!condition->Evaluate(expanders, version, result)) {
out.reset();
return false;
}
if (!result) {
out.reset();
return true;
}
if (result == this->StopValue) {
out = result;
return true;
}
}
out = !this->StopValue;
return true;
}
bool cmCMakePresetsGraphInternal::NotCondition::Evaluate(
MacroExpanderVector const& expanders, int version,
cm::optional<bool>& out) const
{
out.reset();
if (!this->SubCondition->Evaluate(expanders, version, out)) {
out.reset();
return false;
}
if (out) {
*out = !*out;
}
return true;
}
bool cmCMakePresetsGraph::ConfigurePreset::VisitPresetInherit(
cmCMakePresetsGraph::Preset const& parentPreset)
{
auto& preset = *this;
ConfigurePreset const& parent =
static_cast<ConfigurePreset const&>(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);
InheritString(preset.InstallDir, parent.InstallDir);
InheritString(preset.ToolchainFile, parent.ToolchainFile);
InheritString(preset.GraphVizFile, parent.GraphVizFile);
InheritMap(preset.Warnings, parent.Warnings);
InheritMap(preset.Errors, parent.Errors);
InheritOptionalValue(preset.WarnSystemVars, parent.WarnSystemVars);
InheritMap(preset.CacheVariables, parent.CacheVariables);
InheritOptionalValue(preset.DebugOutput, parent.DebugOutput);
InheritOptionalValue(preset.DebugTryCompile, parent.DebugTryCompile);
InheritOptionalValue(preset.DebugFind, parent.DebugFind);
InheritOptionalValue(preset.TraceMode, parent.TraceMode);
InheritOptionalValue(preset.TraceFormat, parent.TraceFormat);
InheritVector(preset.TraceSource, parent.TraceSource);
InheritString(preset.TraceRedirect, parent.TraceRedirect);
return true;
}
bool cmCMakePresetsGraph::ConfigurePreset::VisitPresetBeforeInherit()
{
auto& preset = *this;
if (preset.Environment.count("") != 0) {
this->ErrorDetail =
"Empty environment variable names are not allowed in configure presets";
return false;
}
return true;
}
bool cmCMakePresetsGraph::ConfigurePreset::VisitPresetAfterInherit(
int version, cmJSONState* state)
{
auto& preset = *this;
if (!preset.Hidden) {
if (version < 3) {
if (preset.Generator.empty()) {
cmCMakePresetsErrors::PRESET_MISSING_FIELD(preset.Name, "generator",
state);
return false;
}
if (preset.BinaryDir.empty()) {
cmCMakePresetsErrors::PRESET_MISSING_FIELD(preset.Name, "binaryDir",
state);
return false;
}
}
for (auto const& w : preset.Warnings) {
auto const ei = preset.Errors.find(w.first);
if (ei != preset.Errors.end()) {
if (w.second == false && ei->second == true) {
cm::string_view const diagnostic =
version < 12 && w.first == cmDiagnostics::CMD_AUTHOR
? cm::string_view{ "dev" }
: cmCMakePresetsGraphInternal::GetDiagnosticJSONName(w.first);
this->ErrorDetail = cmStrCat("\"errors.", diagnostic,
"\" is enabled while \"warnings.",
diagnostic, "\" is disabled");
return false;
}
}
}
if (preset.CacheVariables.count("") != 0) {
this->ErrorDetail = "Empty cache variable names are not allowed";
return false;
}
}
return true;
}
bool cmCMakePresetsGraph::BuildPreset::VisitPresetInherit(
cmCMakePresetsGraph::Preset const& parentPreset)
{
auto& preset = *this;
BuildPreset const& parent = static_cast<BuildPreset const&>(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);
if (!preset.ResolvePackageReferences) {
preset.ResolvePackageReferences = parent.ResolvePackageReferences;
}
return true;
}
bool cmCMakePresetsGraph::BuildPreset::VisitPresetAfterInherit(
int /* version */, cmJSONState* /*stat*/)
{
if (!this->Hidden && this->ConfigurePreset.empty()) {
this->ErrorDetail = "Build presets must either be hidden or have an "
"associated configure preset";
return false;
}
return true;
}
bool cmCMakePresetsGraph::TestPreset::VisitPresetInherit(
cmCMakePresetsGraph::Preset const& parentPreset)
{
auto& preset = *this;
TestPreset const& parent = static_cast<TestPreset const&>(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();
auto const& parentOutput = parent.Output.value();
InheritOptionalValue(output.ShortProgress, parentOutput.ShortProgress);
InheritOptionalValue(output.Verbosity, parentOutput.Verbosity);
InheritOptionalValue(output.Debug, parentOutput.Debug);
InheritOptionalValue(output.OutputOnFailure,
parentOutput.OutputOnFailure);
InheritOptionalValue(output.Quiet, parentOutput.Quiet);
InheritString(output.OutputLogFile, parentOutput.OutputLogFile);
InheritString(output.OutputJUnitFile, parentOutput.OutputJUnitFile);
InheritOptionalValue(output.LabelSummary, parentOutput.LabelSummary);
InheritOptionalValue(output.SubprojectSummary,
parentOutput.SubprojectSummary);
InheritOptionalValue(output.MaxPassedTestOutputSize,
parentOutput.MaxPassedTestOutputSize);
InheritOptionalValue(output.MaxFailedTestOutputSize,
parentOutput.MaxFailedTestOutputSize);
InheritOptionalValue(output.TestOutputTruncation,
parentOutput.TestOutputTruncation);
InheritOptionalValue(output.MaxTestNameWidth,
parentOutput.MaxTestNameWidth);
} else {
preset.Output = parent.Output;
}
}
if (parent.Filter) {
if (parent.Filter->Include) {
if (preset.Filter && preset.Filter->Include) {
auto& include = *preset.Filter->Include;
auto const& 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;
}
}
if (parent.Filter->Exclude) {
if (preset.Filter && preset.Filter->Exclude) {
auto& exclude = *preset.Filter->Exclude;
auto const& 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;
}
}
}
if (parent.Execution) {
if (preset.Execution) {
auto& execution = *preset.Execution;
auto const& 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.Repeat, parentExecution.Repeat);
InheritOptionalValue(execution.InteractiveDebugging,
parentExecution.InteractiveDebugging);
InheritOptionalValue(execution.ScheduleRandom,
parentExecution.ScheduleRandom);
InheritOptionalValue(execution.Timeout, parentExecution.Timeout);
InheritOptionalValue(execution.NoTestsAction,
parentExecution.NoTestsAction);
InheritVector(execution.TestPassthroughArguments,
parentExecution.TestPassthroughArguments);
} else {
preset.Execution = parent.Execution;
}
}
return true;
}
bool cmCMakePresetsGraph::TestPreset::VisitPresetAfterInherit(
int /* version */, cmJSONState* /*state*/)
{
if (!this->Hidden && this->ConfigurePreset.empty()) {
this->ErrorDetail = "Test presets must either be hidden or have an "
"associated configure preset";
return false;
}
return true;
}
bool cmCMakePresetsGraph::PackagePreset::VisitPresetInherit(
cmCMakePresetsGraph::Preset const& parentPreset)
{
auto& preset = *this;
PackagePreset const& parent =
static_cast<PackagePreset const&>(parentPreset);
InheritString(preset.ConfigurePreset, parent.ConfigurePreset);
InheritOptionalValue(preset.InheritConfigureEnvironment,
parent.InheritConfigureEnvironment);
InheritVector(preset.Generators, parent.Generators);
InheritVector(preset.Configurations, parent.Configurations);
InheritMap(preset.Variables, parent.Variables);
InheritOptionalValue(preset.DebugOutput, parent.DebugOutput);
InheritOptionalValue(preset.VerboseOutput, parent.VerboseOutput);
InheritString(preset.PackageName, parent.PackageName);
InheritString(preset.PackageVersion, parent.PackageVersion);
InheritString(preset.PackageDirectory, parent.PackageDirectory);
InheritString(preset.VendorName, parent.VendorName);
return true;
}
bool cmCMakePresetsGraph::PackagePreset::VisitPresetAfterInherit(
int /* version */, cmJSONState* /*state*/)
{
if (!this->Hidden && this->ConfigurePreset.empty()) {
this->ErrorDetail = "Package presets must either be hidden or have an "
"associated configure preset";
return false;
}
return true;
}
bool cmCMakePresetsGraph::WorkflowPreset::VisitPresetInherit(
cmCMakePresetsGraph::Preset const& /*parentPreset*/)
{
return true;
}
bool cmCMakePresetsGraph::WorkflowPreset::VisitPresetAfterInherit(
int /* version */, cmJSONState* /*state*/)
{
return true;
}
std::string cmCMakePresetsGraph::GetFilename(std::string const& sourceDir)
{
return cmStrCat(sourceDir, "/CMakePresets.json");
}
std::string cmCMakePresetsGraph::GetUserFilename(std::string const& sourceDir)
{
return cmStrCat(sourceDir, "/CMakeUserPresets.json");
}
bool cmCMakePresetsGraph::ReadProjectPresets(std::string const& sourceDir,
std::string const& presetsFile,
ReadOption readFilesOption)
{
this->SourceDir = cmSystemTools::CollapseFullPath(sourceDir);
this->ClearPresets();
if (!this->ReadProjectPresetsInternal(presetsFile, readFilesOption)) {
this->ClearPresets();
return false;
}
return true;
}
std::string cmCMakePresetsGraph::GetGeneratorForPreset(
std::string const& 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 {};
}
bool cmCMakePresetsGraph::ReadProjectPresetsInternal(
std::string const& presetsFile, ReadOption readFilesOption)
{
bool haveOneFile = false;
File* file;
std::string filename;
std::vector<File*> inProgressFiles;
if (!presetsFile.empty()) {
filename = presetsFile;
if (cmSystemTools::FileExists(filename)) {
if (!this->ReadJSONFile(filename, RootType::Any, ReadReason::Root,
inProgressFiles, file, this->errors)) {
return false;
}
haveOneFile = true;
}
} else {
filename = GetUserFilename(this->SourceDir);
if (cmSystemTools::FileExists(filename)) {
if (!this->ReadJSONFile(filename, RootType::User, ReadReason::Root,
inProgressFiles, file, this->errors)) {
return false;
}
haveOneFile = true;
} else {
filename = GetFilename(this->SourceDir);
if (cmSystemTools::FileExists(filename)) {
if (!this->ReadJSONFile(filename, RootType::Project, ReadReason::Root,
inProgressFiles, file, this->errors)) {
return false;
}
haveOneFile = true;
}
}
}
assert(inProgressFiles.empty());
if (!haveOneFile) {
if (readFilesOption == ReadOption::AllowNoFiles) {
return true;
}
cmCMakePresetsErrors::FILE_NOT_FOUND(filename, &this->parseState);
return false;
}
bool result = ComputePresetInheritance(this->ConfigurePresets, *this) &&
ComputePresetInheritance(this->BuildPresets, *this) &&
ComputePresetInheritance(this->TestPresets, *this) &&
ComputePresetInheritance(this->PackagePresets, *this) &&
ComputePresetInheritance(this->WorkflowPresets, *this);
if (!result) {
return false;
}
for (auto& it : this->ConfigurePresets) {
if (!ExpandMacros(this, it.second.Unexpanded, it.second.Expanded)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(it.first,
&this->parseState);
return false;
}
}
for (auto& it : this->BuildPresets) {
if (!it.second.Unexpanded.Hidden) {
auto const configurePreset =
this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
if (configurePreset == this->ConfigurePresets.end()) {
cmCMakePresetsErrors::CONFIGURE_PRESET_NOT_FOUND(
it.first, BuildPreset::kind(), it.second.Unexpanded.ConfigurePreset,
&this->parseState);
return false;
}
if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
configurePreset->second.Unexpanded.OriginFile)) {
cmCMakePresetsErrors::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE(
it.first, BuildPreset::kind(), it.second.Unexpanded.ConfigurePreset,
&this->parseState);
return false;
}
if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
it.second.Unexpanded.Environment.insert(
configurePreset->second.Unexpanded.Environment.begin(),
configurePreset->second.Unexpanded.Environment.end());
}
}
if (!ExpandMacros(this, it.second.Unexpanded, it.second.Expanded)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(it.first,
&this->parseState);
return false;
}
}
for (auto& it : this->TestPresets) {
if (!it.second.Unexpanded.Hidden) {
auto const configurePreset =
this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
if (configurePreset == this->ConfigurePresets.end()) {
cmCMakePresetsErrors::CONFIGURE_PRESET_NOT_FOUND(
it.first, TestPreset::kind(), it.second.Unexpanded.ConfigurePreset,
&this->parseState);
return false;
}
if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
configurePreset->second.Unexpanded.OriginFile)) {
cmCMakePresetsErrors::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE(
it.first, TestPreset::kind(), it.second.Unexpanded.ConfigurePreset,
&this->parseState);
return false;
}
if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
it.second.Unexpanded.Environment.insert(
configurePreset->second.Unexpanded.Environment.begin(),
configurePreset->second.Unexpanded.Environment.end());
}
}
if (!ExpandMacros(this, it.second.Unexpanded, it.second.Expanded)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(it.first,
&this->parseState);
return false;
}
}
for (auto& it : this->PackagePresets) {
if (!it.second.Unexpanded.Hidden) {
auto const configurePreset =
this->ConfigurePresets.find(it.second.Unexpanded.ConfigurePreset);
if (configurePreset == this->ConfigurePresets.end()) {
cmCMakePresetsErrors::CONFIGURE_PRESET_NOT_FOUND(
it.first, PackagePreset::kind(),
it.second.Unexpanded.ConfigurePreset, &this->parseState);
return false;
}
if (!it.second.Unexpanded.OriginFile->ReachableFiles.count(
configurePreset->second.Unexpanded.OriginFile)) {
cmCMakePresetsErrors::CONFIGURE_PRESET_UNREACHABLE_FROM_FILE(
it.first, PackagePreset::kind(),
it.second.Unexpanded.ConfigurePreset, &this->parseState);
return false;
}
if (it.second.Unexpanded.InheritConfigureEnvironment.value_or(true)) {
it.second.Unexpanded.Environment.insert(
configurePreset->second.Unexpanded.Environment.begin(),
configurePreset->second.Unexpanded.Environment.end());
}
}
if (!ExpandMacros(this, it.second.Unexpanded, it.second.Expanded)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(it.first,
&this->parseState);
return false;
}
}
for (auto& it : this->WorkflowPresets) {
using Type = WorkflowPreset::WorkflowStep::Type;
ConfigurePreset const* configurePreset = nullptr;
for (auto const& step : it.second.Unexpanded.Steps) {
char const* const stepType = WorkflowStepTypeToString(step.PresetType);
if (!configurePreset && step.PresetType != Type::Configure) {
cmCMakePresetsErrors::FIRST_WORKFLOW_STEP_NOT_CONFIGURE(
stepType, step.PresetName, &this->parseState);
return false;
}
if (configurePreset && step.PresetType == Type::Configure) {
cmCMakePresetsErrors::CONFIGURE_WORKFLOW_STEP_NOT_FIRST(
stepType, step.PresetName, &this->parseState);
return false;
}
switch (step.PresetType) {
case Type::Configure:
result = TryReachPresetFromWorkflow(
it.second.Unexpanded, this->ConfigurePresets, step.PresetName,
stepType, configurePreset, &this->parseState);
break;
case Type::Build:
result = TryReachPresetFromWorkflow(
it.second.Unexpanded, this->BuildPresets, step.PresetName,
stepType, configurePreset, &this->parseState);
break;
case Type::Test:
result = TryReachPresetFromWorkflow(
it.second.Unexpanded, this->TestPresets, step.PresetName, stepType,
configurePreset, &this->parseState);
break;
case Type::Package:
result = TryReachPresetFromWorkflow(
it.second.Unexpanded, this->PackagePresets, step.PresetName,
stepType, configurePreset, &this->parseState);
break;
}
if (!result) {
return false;
}
}
if (!configurePreset) {
cmCMakePresetsErrors::NO_WORKFLOW_STEPS(it.first, &this->parseState);
return false;
}
if (!ExpandMacros(this, it.second.Unexpanded, it.second.Expanded)) {
cmCMakePresetsErrors::INVALID_MACRO_EXPANSION(it.first,
&this->parseState);
return false;
}
}
return true;
}
void cmCMakePresetsGraph::ClearPresets()
{
this->ConfigurePresets.clear();
this->BuildPresets.clear();
this->TestPresets.clear();
this->PackagePresets.clear();
this->WorkflowPresets.clear();
this->ConfigurePresetOrder.clear();
this->BuildPresetOrder.clear();
this->TestPresetOrder.clear();
this->PackagePresetOrder.clear();
this->WorkflowPresetOrder.clear();
this->Files.clear();
}
void cmCMakePresetsGraph::PrintConfigurePresetList() const
{
PrintPresetList<ConfigurePreset>(this,
&cmCMakePresetsGraph::ConfigurePresets,
&cmCMakePresetsGraph::ConfigurePresetOrder);
}
void cmCMakePresetsGraph::PrintConfigurePresetList(
std::function<bool(ConfigurePreset const&)> const& filter) const
{
PrintPresetList<ConfigurePreset>(
this, &cmCMakePresetsGraph::ConfigurePresets,
&cmCMakePresetsGraph::ConfigurePresetOrder, filter);
}
void cmCMakePresetsGraph::PrintBuildPresetList() const
{
PrintPresetList<BuildPreset>(this, &cmCMakePresetsGraph::BuildPresets,
&cmCMakePresetsGraph::BuildPresetOrder);
}
void cmCMakePresetsGraph::PrintTestPresetList() const
{
PrintPresetList<TestPreset>(this, &cmCMakePresetsGraph::TestPresets,
&cmCMakePresetsGraph::TestPresetOrder);
}
void cmCMakePresetsGraph::PrintPackagePresetList() const
{
PrintPresetList<PackagePreset>(this, &cmCMakePresetsGraph::PackagePresets,
&cmCMakePresetsGraph::PackagePresetOrder);
}
void cmCMakePresetsGraph::PrintPackagePresetList(
std::function<bool(PackagePreset const&)> const& filter) const
{
PrintPresetList<PackagePreset>(this, &cmCMakePresetsGraph::PackagePresets,
&cmCMakePresetsGraph::PackagePresetOrder,
filter);
}
void cmCMakePresetsGraph::PrintWorkflowPresetList() const
{
PrintPresetList<WorkflowPreset>(this, &cmCMakePresetsGraph::WorkflowPresets,
&cmCMakePresetsGraph::WorkflowPresetOrder);
}
void cmCMakePresetsGraph::PrintAllPresets() const
{
this->PrintConfigurePresetList();
this->PrintBuildPresetList();
this->PrintTestPresetList();
this->PrintPackagePresetList();
this->PrintWorkflowPresetList();
}