CMakePM: Add supprot for CMakePresets version 3

This includes Condition and toolchainFile

https://cmake.org/cmake/help/v3.21/manual/cmake-presets.7.html

Task-number: QTCREATORBUG-24555
Change-Id: I1026390af67b2be1aa0c3b02b654fc19442d3c89
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Cristian Adam
2022-09-26 18:59:04 +02:00
parent 45a275e054
commit ee4c998ff3
7 changed files with 430 additions and 27 deletions

View File

@@ -1168,7 +1168,8 @@ static CommandLine defaultInitialCMakeCommand(const Kit *k, const QString buildT
static void addCMakeConfigurePresetToInitialArguments(QStringList &initialArguments,
const CMakeProject *project,
const Kit *k,
const Utils::Environment &env)
const Utils::Environment &env,
const Utils::FilePath &buildDirectory)
{
const CMakeConfigItem presetItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k);
@@ -1230,6 +1231,11 @@ static void addCMakeConfigurePresetToInitialArguments(QStringList &initialArgume
initialArguments.append("--debug-output");
}
CMakePresets::Macros::updateToolchainFile(configurePreset,
env,
project->projectDirectory(),
buildDirectory);
// Merge the presets cache variables
CMakeConfig cache;
if (configurePreset.cacheVariables)
@@ -1547,15 +1553,16 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id)
CMakeProject *cmakeProject = static_cast<CMakeProject *>(target->project());
setUserConfigureEnvironmentChanges(getEnvironmentItemsFromCMakeConfigurePreset(cmakeProject, k));
updateAndEmitConfigureEnvironmentChanged();
QStringList initialCMakeArguments = cmd.splitArguments();
addCMakeConfigurePresetToInitialArguments(initialCMakeArguments,
cmakeProject,
k,
configureEnvironment());
configureEnvironment(),
info.buildDirectory);
m_buildSystem->setInitialCMakeArguments(initialCMakeArguments);
m_buildSystem->setCMakeBuildType(buildType);
updateAndEmitConfigureEnvironmentChanged();
setBuildPresetToBuildSteps(target);
});
@@ -1825,24 +1832,31 @@ void CMakeBuildConfiguration::setInitialBuildAndCleanSteps(const ProjectExplorer
const CMakeConfigItem presetItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(
target->kit());
int buildSteps = 1;
if (!presetItem.isNull()) {
const QString presetName = presetItem.expandedValue(target->kit());
const CMakeProject *project = static_cast<const CMakeProject *>(target->project());
const auto buildPresets = project->presetsData().buildPresets;
const int count = std::count_if(buildPresets.begin(),
buildPresets.end(),
[presetName](const PresetsDetails::BuildPreset &preset) {
return preset.configurePreset == presetName
&& !preset.hidden.value();
});
const int count
= std::count_if(buildPresets.begin(),
buildPresets.end(),
[presetName, project](const PresetsDetails::BuildPreset &preset) {
bool enabled = true;
if (preset.condition)
enabled = CMakePresets::Macros::evaluatePresetCondition(
preset, project->projectDirectory());
for (int i = 0; i < count; ++i)
appendInitialBuildStep(Constants::CMAKE_BUILD_STEP_ID);
} else {
appendInitialBuildStep(Constants::CMAKE_BUILD_STEP_ID);
return preset.configurePreset == presetName
&& !preset.hidden.value() && enabled;
});
if (count != 0)
buildSteps = count;
}
for (int i = 0; i < buildSteps; ++i)
appendInitialBuildStep(Constants::CMAKE_BUILD_STEP_ID);
appendInitialCleanStep(Constants::CMAKE_BUILD_STEP_ID);
}
@@ -1858,10 +1872,15 @@ void CMakeBuildConfiguration::setBuildPresetToBuildSteps(const ProjectExplorer::
const CMakeProject *project = static_cast<const CMakeProject *>(target->project());
const auto allBuildPresets = project->presetsData().buildPresets;
const auto buildPresets
= Utils::filtered(allBuildPresets, [presetName](const PresetsDetails::BuildPreset &preset) {
return preset.configurePreset == presetName && !preset.hidden.value();
});
const auto buildPresets = Utils::filtered(
allBuildPresets, [presetName, project](const PresetsDetails::BuildPreset &preset) {
bool enabled = true;
if (preset.condition)
enabled = CMakePresets::Macros::evaluatePresetCondition(preset,
project->projectDirectory());
return preset.configurePreset == presetName && !preset.hidden.value() && enabled;
});
const QList<BuildStep *> buildStepList
= Utils::filtered(buildSteps()->steps(), [](const BuildStep *bs) {

View File

@@ -123,6 +123,11 @@ FilePaths CMakeProjectImporter::importCandidates()
if (configPreset.hidden.value())
continue;
if (configPreset.condition) {
if (!CMakePresets::Macros::evaluatePresetCondition(configPreset, projectFilePath()))
continue;
}
const FilePath configPresetDir = m_presetsTempDir.filePath(configPreset.name);
configPresetDir.createDir();
candidates << configPresetDir;
@@ -403,13 +408,15 @@ QList<void *> CMakeProjectImporter::examineDirectory(const FilePath &importPath,
auto data = std::make_unique<DirectoryData>();
const QString presetName = importPath.fileName();
PresetsDetails::ConfigurePreset configurePreset
= Utils::findOrDefault(m_presetsData.configurePresets,
[presetName](const PresetsDetails::ConfigurePreset &preset) {
return preset.name == presetName;
});
Environment env = Environment::systemEnvironment();
CMakePresets::Macros::expand(configurePreset, env, projectDirectory());
if (configurePreset.displayName)
data->cmakePresetDisplayname = configurePreset.displayName.value();
else
@@ -435,14 +442,16 @@ QList<void *> CMakeProjectImporter::examineDirectory(const FilePath &importPath,
QString binaryDir = importPath.toString();
if (configurePreset.binaryDir) {
binaryDir = configurePreset.binaryDir.value();
CMakePresets::Macros::expand(configurePreset,
Environment::systemEnvironment(),
projectDirectory(),
binaryDir);
CMakePresets::Macros::expand(configurePreset, env, projectDirectory(), binaryDir);
}
data->buildDirectory = Utils::FilePath::fromString(binaryDir);
CMakePresets::Macros::updateToolchainFile(configurePreset,
env,
projectDirectory(),
data->buildDirectory);
const CMakeConfig cache = configurePreset.cacheVariables
? configurePreset.cacheVariables.value()
: CMakeConfig();

View File

@@ -12,6 +12,25 @@
namespace CMakeProjectManager::Internal::CMakePresets::Macros {
QString getHostSystemName()
{
Utils::OsType osType = Utils::HostOsInfo::hostOs();
switch (osType) {
case Utils::OsTypeWindows:
return "Windows";
case Utils::OsTypeLinux:
return "Linux";
case Utils::OsTypeMac:
return "Darwin";
case Utils::OsTypeOtherUnix:
return "Unix";
case Utils::OsTypeOther:
return "Other";
}
return "Other";
}
void expandAllButEnv(const PresetsDetails::ConfigurePreset &preset,
const Utils::FilePath &sourceDirectory,
QString &value)
@@ -25,6 +44,8 @@ void expandAllButEnv(const PresetsDetails::ConfigurePreset &preset,
value.replace("${presetName}", preset.name);
if (preset.generator)
value.replace("${generator}", preset.generator.value());
value.replace("${hostSystemName}", getHostSystemName());
}
void expandAllButEnv(const PresetsDetails::BuildPreset &preset,
@@ -141,6 +162,106 @@ void expand(const PresetType &preset,
value.replace(match.captured(1), env.value(match.captured(2)));
}
void updateToolchainFile(
CMakeProjectManager::Internal::PresetsDetails::ConfigurePreset &configurePreset,
const Utils::Environment &env,
const Utils::FilePath &sourceDirectory,
const Utils::FilePath &buildDirectory)
{
if (!configurePreset.toolchainFile)
return;
QString toolchainFileName = configurePreset.toolchainFile.value();
CMakePresets::Macros::expand(configurePreset, env, sourceDirectory, toolchainFileName);
// Resolve the relative path first to source and afterwards to build directory
Utils::FilePath toolchainFile = Utils::FilePath::fromString(toolchainFileName);
if (toolchainFile.isRelativePath()) {
for (const auto &path : {sourceDirectory, buildDirectory}) {
Utils::FilePath probePath = toolchainFile.resolvePath(path);
if (probePath.exists() && probePath != path) {
toolchainFile = probePath;
break;
}
}
}
if (!toolchainFile.exists())
return;
// toolchainFile takes precedence to CMAKE_TOOLCHAIN_FILE
CMakeConfig cache = configurePreset.cacheVariables ? configurePreset.cacheVariables.value()
: CMakeConfig();
auto it = std::find_if(cache.begin(), cache.end(), [](const CMakeConfigItem &item) {
return item.key == "CMAKE_TOOLCHAIN_FILE";
});
if (it != cache.end())
it->value = toolchainFile.toString().toUtf8();
else
cache << CMakeConfigItem("CMAKE_TOOLCHAIN_FILE",
CMakeConfigItem::FILEPATH,
toolchainFile.toString().toUtf8());
configurePreset.cacheVariables = cache;
}
template<class PresetType>
void expandConditionValues(const PresetType &preset,
const Utils::Environment &env,
const Utils::FilePath &sourceDirectory,
PresetsDetails::Condition &condition)
{
if (condition.isEquals() || condition.isNotEquals()) {
if (condition.lhs)
expand(preset, env, sourceDirectory, condition.lhs.value());
if (condition.rhs)
expand(preset, env, sourceDirectory, condition.rhs.value());
}
if (condition.isInList() || condition.isNotInList()) {
if (condition.string)
expand(preset, env, sourceDirectory, condition.string.value());
if (condition.list)
for (QString &listValue : condition.list.value())
expand(preset, env, sourceDirectory, listValue);
}
if (condition.isMatches() || condition.isNotMatches()) {
if (condition.string)
expand(preset, env, sourceDirectory, condition.string.value());
if (condition.regex)
expand(preset, env, sourceDirectory, condition.regex.value());
}
if (condition.isAnyOf() || condition.isAllOf()) {
if (condition.conditions)
for (PresetsDetails::Condition::ConditionPtr &c : condition.conditions.value())
expandConditionValues(preset, env, sourceDirectory, *c);
}
if (condition.isNot()) {
if (condition.condition)
expandConditionValues(preset, env, sourceDirectory, *condition.condition.value());
}
}
template<class PresetType>
bool evaluatePresetCondition(const PresetType &preset, const Utils::FilePath &sourceDirectory)
{
if (!preset.condition)
return true;
Utils::Environment env = Utils::Environment::systemEnvironment();
expand(preset, env, sourceDirectory);
PresetsDetails::Condition condition = preset.condition.value();
expandConditionValues(preset, env, sourceDirectory, condition);
return condition.evaluate();
}
// Expand for PresetsDetails::ConfigurePreset
template void expand<PresetsDetails::ConfigurePreset>(const PresetsDetails::ConfigurePreset &preset,
Utils::Environment &env,
@@ -155,6 +276,9 @@ template void expand<PresetsDetails::ConfigurePreset>(const PresetsDetails::Conf
const Utils::FilePath &sourceDirectory,
QString &value);
template bool evaluatePresetCondition<PresetsDetails::ConfigurePreset>(
const PresetsDetails::ConfigurePreset &preset, const Utils::FilePath &sourceDirectory);
// Expand for PresetsDetails::BuildPreset
template void expand<PresetsDetails::BuildPreset>(const PresetsDetails::BuildPreset &preset,
Utils::Environment &env,
@@ -169,4 +293,7 @@ template void expand<PresetsDetails::BuildPreset>(const PresetsDetails::BuildPre
const Utils::FilePath &sourceDirectory,
QString &value);
template bool evaluatePresetCondition<PresetsDetails::BuildPreset>(
const PresetsDetails::BuildPreset &preset, const Utils::FilePath &sourceDirectory);
} // namespace CMakeProjectManager::Internal::CMakePresets::Macros

View File

@@ -12,6 +12,10 @@ class FilePath;
namespace CMakeProjectManager::Internal {
namespace PresetsDetails {
class ConfigurePreset;
}
namespace CMakePresets::Macros {
/**
* Expands the CMakePresets Macros using Utils::Environment as target and source for parent environment values.
@@ -40,6 +44,22 @@ void expand(const PresetType &preset,
const Utils::FilePath &sourceDirectory,
QString &value);
/**
* Updates the cacheVariables parameter of the configurePreset with the expandned toolchainFile parameter.
* Including macro expansion and relative paths resolving.
*/
void updateToolchainFile(PresetsDetails::ConfigurePreset &configurePreset,
const Utils::Environment &env,
const Utils::FilePath &sourceDirectory,
const Utils::FilePath &buildDirectory);
/**
* Expands the condition values and then evaluates the condition object of the preset and returns
* the boolean result.
*/
template<class PresetType>
bool evaluatePresetCondition(const PresetType &preset, const Utils::FilePath &sourceDirectory);
} // namespace CMakePresets::Macros
} // namespace CMakeProjectManager::Internal

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "presetsparser.h"
#include "utils/algorithm.h"
#include "cmakeprojectmanagertr.h"
@@ -34,6 +35,102 @@ bool parseCMakeMinimumRequired(const QJsonValue &jsonValue, QVersionNumber &vers
return true;
}
std::optional<PresetsDetails::Condition> parseCondition(const QJsonValue &jsonValue)
{
std::optional<PresetsDetails::Condition> condition;
if (jsonValue.isUndefined())
return condition;
condition = PresetsDetails::Condition();
if (jsonValue.isNull()) {
condition->type = "null";
return condition;
}
if (jsonValue.isBool()) {
condition->type = "const";
condition->constValue = jsonValue.toBool();
return condition;
}
if (!jsonValue.isObject())
return condition;
QJsonObject object = jsonValue.toObject();
QString type = object.value("type").toString();
if (type.isEmpty())
return condition;
if (type == "const") {
condition->type = type;
condition->constValue = object.value("const").toBool();
return condition;
}
for (const auto &equals : {QString("equals"), QString("notEquals")}) {
if (type == equals) {
condition->type = equals;
condition->lhs = object.value("lhs").toString();
condition->rhs = object.value("rhs").toString();
}
}
if (!condition->type.isEmpty())
return condition;
for (const auto &inList : {QString("inList"), QString("notInList")}) {
if (type == inList) {
condition->type = inList;
condition->string = object.value("string").toString();
if (object.value("list").isArray()) {
condition->list = QStringList();
const QJsonArray listArray = object.value("list").toArray();
for (const QJsonValue &listValue : listArray)
condition->list.value() << listValue.toString();
}
}
}
if (!condition->type.isEmpty())
return condition;
for (const auto &matches : {QString("matches"), QString("notMatches")}) {
if (type == matches) {
condition->type = matches;
condition->string = object.value("string").toString();
condition->regex = object.value("regex").toString();
}
}
if (!condition->type.isEmpty())
return condition;
for (const auto &anyOf : {QString("anyOf"), QString("allOf")}) {
if (type == anyOf) {
condition->type = anyOf;
if (object.value("conditions").isArray()) {
condition->conditions = std::vector<PresetsDetails::Condition::ConditionPtr>();
const QJsonArray conditionsArray = object.value("conditions").toArray();
for (const QJsonValue &conditionsValue : conditionsArray) {
condition->conditions.value().emplace_back(
std::make_shared<PresetsDetails::Condition>(
parseCondition(conditionsValue).value()));
}
}
}
}
if (!condition->type.isEmpty())
return condition;
if (type == "not") {
condition->type = type;
condition->condition = std::make_shared<PresetsDetails::Condition>(
parseCondition(object.value("condition")).value());
return condition;
}
return condition;
}
bool parseConfigurePresets(const QJsonValue &jsonValue,
QList<PresetsDetails::ConfigurePreset> &configurePresets)
{
@@ -68,6 +165,10 @@ bool parseConfigurePresets(const QJsonValue &jsonValue,
preset.inherits.value() << inheritsValue;
}
}
if (object.contains("condition"))
preset.condition = parseCondition(object.value("condition"));
if (object.contains("displayName"))
preset.displayName = object.value("displayName").toString();
if (object.contains("description"))
@@ -76,6 +177,8 @@ bool parseConfigurePresets(const QJsonValue &jsonValue,
preset.generator = object.value("generator").toString();
if (object.contains("binaryDir"))
preset.binaryDir = object.value("binaryDir").toString();
if (object.contains("toolchainFile"))
preset.toolchainFile = object.value("toolchainFile").toString();
if (object.contains("cmakeExecutable"))
preset.cmakeExecutable = object.value("cmakeExecutable").toString();
@@ -218,6 +321,10 @@ bool parseBuildPresets(const QJsonValue &jsonValue,
preset.inherits.value() << inheritsValue;
}
}
if (object.contains("condition"))
preset.condition = parseCondition(object.value("condition"));
if (object.contains("displayName"))
preset.displayName = object.value("displayName").toString();
if (object.contains("description"))
@@ -334,6 +441,9 @@ bool PresetsParser::parse(const Utils::FilePath &jsonFile, QString &errorMessage
void PresetsDetails::ConfigurePreset::inheritFrom(const ConfigurePreset &other)
{
if (!condition && other.condition && !other.condition.value().isNull())
condition = other.condition;
if (!vendor && other.vendor)
vendor = other.vendor;
@@ -346,6 +456,9 @@ void PresetsDetails::ConfigurePreset::inheritFrom(const ConfigurePreset &other)
if (!toolset && other.toolset)
toolset = other.toolset;
if (!toolchainFile && other.toolchainFile)
toolchainFile = other.toolchainFile;
if (!binaryDir && other.binaryDir)
binaryDir = other.binaryDir;
@@ -370,6 +483,9 @@ void PresetsDetails::ConfigurePreset::inheritFrom(const ConfigurePreset &other)
void PresetsDetails::BuildPreset::inheritFrom(const BuildPreset &other)
{
if (!condition && other.condition && !other.condition.value().isNull())
condition = other.condition;
if (!vendor && other.vendor)
vendor = other.vendor;
@@ -401,4 +517,46 @@ void PresetsDetails::BuildPreset::inheritFrom(const BuildPreset &other)
nativeToolOptions = other.nativeToolOptions;
}
bool PresetsDetails::Condition::evaluate() const
{
if (isNull())
return true;
if (isConst() && constValue)
return constValue.value();
if (isEquals() && lhs && rhs)
return lhs.value() == rhs.value();
if (isNotEquals() && lhs && rhs)
return lhs.value() != rhs.value();
if (isInList() && string && list)
return list.value().contains(string.value());
if (isNotInList() && string && list)
return !list.value().contains(string.value());
if (isMatches() && string && regex) {
QRegularExpression qRegex(regex.value());
return qRegex.match(string.value()).hasMatch();
}
if (isNotMatches() && string && regex) {
QRegularExpression qRegex(regex.value());
return !qRegex.match(string.value()).hasMatch();
}
if (isAnyOf() && conditions)
return Utils::anyOf(conditions.value(), [](const ConditionPtr &c) { return c->evaluate(); });
if (isAllOf() && conditions)
return Utils::allOf(conditions.value(), [](const ConditionPtr &c) { return c->evaluate(); });
if (isNot() && condition)
return !condition.value()->evaluate();
return false;
}
} // CMakeProjectManager::Internal

View File

@@ -43,6 +43,47 @@ public:
std::optional<bool> find = false;
};
class Condition {
public:
QString type;
bool isNull() const { return type == "null"; }
bool isConst() const { return type == "const"; }
bool isEquals() const { return type == "equals"; }
bool isNotEquals() const { return type == "notEquals"; }
bool isInList() const { return type == "inList"; }
bool isNotInList() const { return type == "notInList"; }
bool isMatches() const { return type == "matches"; }
bool isNotMatches() const { return type == "notMatches"; }
bool isAnyOf() const { return type == "anyOf"; }
bool isAllOf() const { return type == "allOf"; }
bool isNot() const { return type == "not"; }
bool evaluate() const;
// const
std::optional<bool> constValue;
// equals, notEquals
std::optional<QString> lhs;
std::optional<QString> rhs;
// inList, notInList
std::optional<QString> string;
std::optional<QStringList> list;
// matches, notMatches
std::optional<QString> regex;
using ConditionPtr = std::shared_ptr<Condition>;
// anyOf, allOf
std::optional<std::vector<ConditionPtr>> conditions;
// not
std::optional<ConditionPtr> condition;
};
class ConfigurePreset {
public:
void inheritFrom(const ConfigurePreset &other);
@@ -50,12 +91,14 @@ public:
QString name;
std::optional<bool> hidden = false;
std::optional<QStringList> inherits;
std::optional<Condition> condition;
std::optional<QHash<QString, QString>> vendor;
std::optional<QString> displayName;
std::optional<QString> description;
std::optional<QString> generator;
std::optional<ValueStrategyPair> architecture;
std::optional<ValueStrategyPair> toolset;
std::optional<QString> toolchainFile;
std::optional<QString> binaryDir;
std::optional<QString> cmakeExecutable;
std::optional<CMakeConfig> cacheVariables;
@@ -72,6 +115,7 @@ public:
QString name;
std::optional<bool> hidden = false;
std::optional<QStringList> inherits;
std::optional<Condition> condition;
std::optional<QHash<QString, QString>> vendor;
std::optional<QString> displayName;
std::optional<QString> description;

View File

@@ -1,8 +1,8 @@
{
"version": 2,
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20,
"minor": 21,
"patch": 0
},
"configurePresets": [
@@ -15,6 +15,11 @@
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_PREFIX_PATH": "c:/Qt/6.3.2/mingw_64"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"environment": {
"PATH": "c:/Qt/Tools/mingw1120_64/bin;$penv{PATH}"
},
@@ -36,8 +41,29 @@
"architecture" : {
"value": "x64"
},
"cacheVariables": {
"CMAKE_PREFIX_PATH": "c:/Qt/6.3.2/msvc2019_64"
"toolchainFile" : "c:/Qt/6.3.2/msvc2019_64/lib/cmake/Qt6/qt.toolchain.cmake",
"condition" : {
"type": "not",
"condition": {
"type": "notEquals",
"lhs": "${hostSystemName}",
"rhs": "$env{HOST_SYSTEM_NAME}"
}
},
"environment" : {
"HOST_SYSTEM_NAME": "Windows"
}
},
{
"name": "linux-gcc",
"displayName": "Linux GCC",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-${presetName}",
"toolchainFile" : "$penv{HOME}/Qt/6.3.2/gcc_64/lib/cmake/Qt6/qt.toolchain.cmake",
"condition" : {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
}
}
],