From e02f4a05189a4224d9e4797bcce840c6a953c5c1 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Mon, 5 Sep 2022 20:09:12 +0200 Subject: [PATCH] CMakePM: Add support for build CMake presets This patchset will add support for version 2 of the CMakePresets feature that has been implemented in CMake 3.20 https://cmake.org/cmake/help/v3.20/manual/cmake-presets.7.html Task-number: QTCREATORBUG-24555 Change-Id: I08934243cc04487d38c4b59c2ad4a4a8d0484492 Reviewed-by: Reviewed-by: Alessandro Portale --- .../cmakebuildconfiguration.cpp | 130 ++++++++++++- .../cmakebuildconfiguration.h | 3 + .../cmakeprojectmanager/cmakebuildstep.cpp | 171 +++++++++++++++++- .../cmakeprojectmanager/cmakebuildstep.h | 25 +++ .../cmakeprojectmanager/cmakeproject.cpp | 106 +++++++---- .../cmakeprojectmanager/cmakeproject.h | 1 + .../cmakeprojectmanager/presetsmacros.cpp | 83 ++++++--- .../cmakeprojectmanager/presetsmacros.h | 13 +- .../cmakeprojectmanager/presetsparser.cpp | 148 ++++++++++++++- .../cmakeprojectmanager/presetsparser.h | 22 +++ tests/manual/cmakepresets/CMakePresets.json | 40 +++- 11 files changed, 661 insertions(+), 81 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index 91e1cd80479..e746f2374f2 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -1301,8 +1301,8 @@ static void addCMakeConfigurePresetToInitialArguments(QStringList &initialArgume } } -static Utils::EnvironmentItems getEnvironmentItemsFromCMakePreset(const CMakeProject *project, - const Kit *k) +static Utils::EnvironmentItems getEnvironmentItemsFromCMakeConfigurePreset( + const CMakeProject *project, const Kit *k) { Utils::EnvironmentItems envItems; @@ -1324,6 +1324,27 @@ static Utils::EnvironmentItems getEnvironmentItemsFromCMakePreset(const CMakePro return envItems; } +static Utils::EnvironmentItems getEnvironmentItemsFromCMakeBuildPreset( + const CMakeProject *project, const Kit *k, const QString &buildPresetName) + +{ + Utils::EnvironmentItems envItems; + + const CMakeConfigItem presetItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k); + if (presetItem.isNull()) + return envItems; + + PresetsDetails::BuildPreset buildPreset + = Utils::findOrDefault(project->presetsData().buildPresets, + [buildPresetName](const PresetsDetails::BuildPreset &preset) { + return preset.name == buildPresetName; + }); + + CMakePresets::Macros::expand(buildPreset, envItems, project->projectDirectory()); + + return envItems; +} + // ----------------------------------------------------------------------------- // CMakeBuildConfigurationPrivate: // ----------------------------------------------------------------------------- @@ -1423,8 +1444,7 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id) addAspect(); addAspect(this); - appendInitialBuildStep(Constants::CMAKE_BUILD_STEP_ID); - appendInitialCleanStep(Constants::CMAKE_BUILD_STEP_ID); + setInitialBuildAndCleanSteps(target); setInitializer([this, target](const BuildInfo &info) { const Kit *k = target->kit(); @@ -1527,7 +1547,7 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id) cmd.addArg("-DCMAKE_CXX_FLAGS_INIT:STRING=%{" + QLatin1String(QT_QML_DEBUG_FLAG) + "}"); CMakeProject *cmakeProject = static_cast(target->project()); - setUserConfigureEnvironmentChanges(getEnvironmentItemsFromCMakePreset(cmakeProject, k)); + setUserConfigureEnvironmentChanges(getEnvironmentItemsFromCMakeConfigurePreset(cmakeProject, k)); QStringList initialCMakeArguments = cmd.splitArguments(); addCMakeConfigurePresetToInitialArguments(initialCMakeArguments, @@ -1537,6 +1557,8 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id) m_buildSystem->setInitialCMakeArguments(initialCMakeArguments); m_buildSystem->setCMakeBuildType(buildType); updateAndEmitConfigureEnvironmentChanged(); + + setBuildPresetToBuildSteps(target); }); } @@ -1799,6 +1821,104 @@ CMakeConfig CMakeBuildConfiguration::signingFlags() const return {}; } +void CMakeBuildConfiguration::setInitialBuildAndCleanSteps(const ProjectExplorer::Target *target) +{ + const CMakeConfigItem presetItem = CMakeConfigurationKitAspect::cmakePresetConfigItem( + target->kit()); + + if (!presetItem.isNull()) { + const QString presetName = presetItem.expandedValue(target->kit()); + const CMakeProject *project = static_cast(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(); + }); + + for (int i = 0; i < count; ++i) + appendInitialBuildStep(Constants::CMAKE_BUILD_STEP_ID); + + } else { + appendInitialBuildStep(Constants::CMAKE_BUILD_STEP_ID); + } + appendInitialCleanStep(Constants::CMAKE_BUILD_STEP_ID); +} + +void CMakeBuildConfiguration::setBuildPresetToBuildSteps(const ProjectExplorer::Target *target) +{ + const CMakeConfigItem presetItem = CMakeConfigurationKitAspect::cmakePresetConfigItem( + target->kit()); + + if (presetItem.isNull()) + return; + + const QString presetName = presetItem.expandedValue(target->kit()); + const CMakeProject *project = static_cast(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 QList buildStepList + = Utils::filtered(buildSteps()->steps(), [](const BuildStep *bs) { + return bs->id() == Constants::CMAKE_BUILD_STEP_ID; + }); + + if (buildPresets.size() != buildStepList.size()) + return; + + for (qsizetype i = 0; i < buildStepList.size(); ++i) { + CMakeBuildStep *cbs = qobject_cast(buildStepList[i]); + cbs->setBuildPreset(buildPresets[i].name); + cbs->setUserEnvironmentChanges( + getEnvironmentItemsFromCMakeBuildPreset(project, target->kit(), buildPresets[i].name)); + + if (buildPresets[i].targets) { + QString targets = buildPresets[i].targets.value().join(" "); + + CMakePresets::Macros::expand(buildPresets[i], + cbs->environment(), + project->projectDirectory(), + targets); + + cbs->setBuildTargets(targets.split(" ")); + } + + QStringList cmakeArguments; + if (buildPresets[i].jobs) + cmakeArguments.append(QString("-j %1").arg(buildPresets[i].jobs.value())); + if (buildPresets[i].verbose && buildPresets[i].verbose.value()) + cmakeArguments.append("--verbose"); + if (buildPresets[i].cleanFirst && buildPresets[i].cleanFirst.value()) + cmakeArguments.append("--clean-first"); + if (!cmakeArguments.isEmpty()) + cbs->setCMakeArguments(cmakeArguments); + + if (buildPresets[i].nativeToolOptions) { + QString nativeToolOptions = buildPresets[i].nativeToolOptions.value().join(" "); + + CMakePresets::Macros::expand(buildPresets[i], + cbs->environment(), + project->projectDirectory(), + nativeToolOptions); + + cbs->setToolArguments(nativeToolOptions.split(" ")); + } + + if (buildPresets[i].configuration) + cbs->setConfiguration(buildPresets[i].configuration.value()); + + // Leave only the first build step enabled + if (i > 0) + cbs->setEnabled(false); + } +} + /*! \class CMakeBuildConfigurationFactory */ diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h index 6512baf553c..448e0706f7b 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h @@ -69,6 +69,9 @@ private: virtual CMakeConfig signingFlags() const; + void setInitialBuildAndCleanSteps(const ProjectExplorer::Target *target); + void setBuildPresetToBuildSteps(const ProjectExplorer::Target *target); + Internal::CMakeBuildSystem *m_buildSystem = nullptr; friend class Internal::CMakeBuildSettingsWidget; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp index 4f371f2c6b5..c9fcf2acf7c 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp @@ -7,11 +7,14 @@ #include "cmakebuildsystem.h" #include "cmakekitinformation.h" #include "cmakeparser.h" +#include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "cmaketool.h" #include #include +#include +#include #include #include #include @@ -29,6 +32,7 @@ #include #include #include +#include using namespace Core; using namespace ProjectExplorer; @@ -42,6 +46,9 @@ const char CMAKE_ARGUMENTS_KEY[] = "CMakeProjectManager.MakeStep.CMakeArguments" const char TOOL_ARGUMENTS_KEY[] = "CMakeProjectManager.MakeStep.AdditionalArguments"; const char IOS_AUTOMATIC_PROVISIONG_UPDATES_ARGUMENTS_KEY[] = "CMakeProjectManager.MakeStep.iOSAutomaticProvisioningUpdates"; +const char CLEAR_SYSTEM_ENVIRONMENT_KEY[] = "CMakeProjectManager.MakeStep.ClearSystemEnvironment"; +const char USER_ENVIRONMENT_CHANGES_KEY[] = "CMakeProjectManager.MakeStep.UserEnvironmentChanges"; +const char BUILD_PRESET_KEY[] = "CMakeProjectManager.MakeStep.BuildPreset"; // CmakeProgressParser @@ -186,11 +193,12 @@ CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Utils::Id id) : setCommandLineProvider([this] { return cmakeCommand(); }); - setEnvironmentModifier([](Environment &env) { + setEnvironmentModifier([this](Environment &env) { const QString ninjaProgressString = "[%f/%t "; // ninja: [33/100 env.setupEnglishOutput(); if (!env.expandedValueForKey("NINJA_STATUS").startsWith(ninjaProgressString)) env.set("NINJA_STATUS", ninjaProgressString + "%o/sec] "); + env.modify(m_userEnvironmentChanges); }); connect(target(), &Target::parsingFinished, this, [this](bool success) { @@ -207,12 +215,26 @@ QVariantMap CMakeBuildStep::toMap() const { QVariantMap map(AbstractProcessStep::toMap()); map.insert(BUILD_TARGETS_KEY, m_buildTargets); + map.insert(QLatin1String(CLEAR_SYSTEM_ENVIRONMENT_KEY), m_clearSystemEnvironment); + map.insert(QLatin1String(USER_ENVIRONMENT_CHANGES_KEY), EnvironmentItem::toStringList(m_userEnvironmentChanges)); + map.insert(QLatin1String(BUILD_PRESET_KEY), m_buildPreset); + return map; } bool CMakeBuildStep::fromMap(const QVariantMap &map) { setBuildTargets(map.value(BUILD_TARGETS_KEY).toStringList()); + + m_clearSystemEnvironment = map.value(QLatin1String(CLEAR_SYSTEM_ENVIRONMENT_KEY)) + .toBool(); + m_userEnvironmentChanges = EnvironmentItem::fromStringList( + map.value(QLatin1String(USER_ENVIRONMENT_CHANGES_KEY)).toStringList()); + + updateAndEmitEnvironmentChanged(); + + m_buildPreset = map.value(QLatin1String(BUILD_PRESET_KEY)).toString(); + return BuildStep::fromMap(map); } @@ -348,6 +370,14 @@ QString CMakeBuildStep::defaultBuildTarget() const return allTarget(); } +bool CMakeBuildStep::isCleanStep() const +{ + const BuildStepList *const bsl = stepList(); + QTC_ASSERT(bsl, return false); + const Utils::Id parentId = bsl->id(); + return parentId == ProjectExplorer::Constants::BUILDSTEPS_CLEAN; +} + QStringList CMakeBuildStep::buildTargets() const { return m_buildTargets; @@ -401,7 +431,10 @@ CommandLine CMakeBuildStep::cmakeCommand() const auto bs = qobject_cast(buildSystem()); if (bs && bs->isMultiConfigReader()) { cmd.addArg("--config"); - cmd.addArg(bs->cmakeBuildType()); + if (m_configuration) + cmd.addArg(m_configuration.value()); + else + cmd.addArg(bs->cmakeBuildType()); } if (!m_cmakeArguments->value().isEmpty()) @@ -453,13 +486,36 @@ QString CMakeBuildStep::activeRunConfigTarget() const return rc ? rc->buildKey() : QString(); } +void CMakeBuildStep::setBuildPreset(const QString &preset) +{ + m_buildPreset = preset; +} + QWidget *CMakeBuildStep::createConfigWidget() { auto updateDetails = [this] { ProcessParameters param; setupProcessParameters(¶m); param.setCommandLine(cmakeCommand()); - setSummaryText(param.summary(displayName())); + + QString summaryText = param.summary(displayName()); + + if (!m_buildPreset.isEmpty()) { + const CMakeProject *cp = static_cast(project()); + + const auto buildPresets = cp->presetsData().buildPresets; + const PresetsDetails::BuildPreset preset + = Utils::findOrDefault(buildPresets, [this](const PresetsDetails::BuildPreset &bp) { + return bp.name == m_buildPreset; + }); + + const QString presetDisplayName = preset.displayName ? preset.displayName.value() + : preset.name; + if (!presetDisplayName.isEmpty()) + summaryText.append(QString("
Preset: %1").arg(presetDisplayName)); + } + + setSummaryText(summaryText); }; setDisplayName(tr("Build", "ConfigWidget display name.")); @@ -473,6 +529,34 @@ QWidget *CMakeBuildStep::createConfigWidget() auto frame = ItemViewFind::createSearchableWrapper(buildTargetsView, ItemViewFind::LightColored); + auto createAndAddEnvironmentWidgets = [this](Layouting::Form &builder) { + auto clearBox = new QCheckBox(tr("Clear system environment")); + clearBox->setChecked(useClearEnvironment()); + + auto envWidget = new EnvironmentWidget(nullptr, EnvironmentWidget::TypeLocal, clearBox); + envWidget->setBaseEnvironment(baseEnvironment()); + envWidget->setBaseEnvironmentText(baseEnvironmentText()); + envWidget->setUserChanges(userEnvironmentChanges()); + + connect(envWidget, &EnvironmentWidget::userChangesChanged, this, [this, envWidget] { + setUserEnvironmentChanges(envWidget->userChanges()); + }); + + connect(clearBox, &QAbstractButton::toggled, this, [this, envWidget](bool checked) { + setUseClearEnvironment(checked); + envWidget->setBaseEnvironment(baseEnvironment()); + envWidget->setBaseEnvironmentText(baseEnvironmentText()); + }); + + connect(this, &CMakeBuildStep::environmentChanged, this, [this, envWidget] { + envWidget->setBaseEnvironment(baseEnvironment()); + envWidget->setBaseEnvironmentText(baseEnvironmentText()); + }); + + builder.addRow(clearBox); + builder.addRow(envWidget); + }; + Layouting::Form builder; builder.addRow(m_cmakeArguments); builder.addRow(m_toolArguments); @@ -481,6 +565,10 @@ QWidget *CMakeBuildStep::createConfigWidget() builder.addRow(m_useiOSAutomaticProvisioningUpdates); builder.addRow({new QLabel(tr("Targets:")), frame}); + + if (!isCleanStep() && !m_buildPreset.isEmpty()) + createAndAddEnvironmentWidgets(builder); + auto widget = builder.emerge(Layouting::WithoutMargins); updateDetails(); @@ -552,6 +640,83 @@ void CMakeBuildStep::updateBuildTargetsModel() emit buildTargetsChanged(); } +void CMakeBuildStep::setConfiguration(const QString &configuration) +{ + m_configuration = configuration; +} + +void CMakeBuildStep::setToolArguments(const QStringList &nativeToolArguments) +{ + m_toolArguments->setValue(nativeToolArguments.join(" ")); +} + +void CMakeBuildStep::setCMakeArguments(const QStringList &cmakeArguments) +{ + m_cmakeArguments->setValue(cmakeArguments.join(" ")); +} + +Environment CMakeBuildStep::environment() const +{ + return m_environment; +} + +void CMakeBuildStep::setUserEnvironmentChanges(const Utils::EnvironmentItems &diff) +{ + if (m_userEnvironmentChanges == diff) + return; + m_userEnvironmentChanges = diff; + updateAndEmitEnvironmentChanged(); +} + +EnvironmentItems CMakeBuildStep::userEnvironmentChanges() const +{ + return m_userEnvironmentChanges; +} + +bool CMakeBuildStep::useClearEnvironment() const +{ + return m_clearSystemEnvironment; +} + +void CMakeBuildStep::setUseClearEnvironment(bool b) +{ + if (useClearEnvironment() == b) + return; + m_clearSystemEnvironment = b; + updateAndEmitEnvironmentChanged(); +} + +void CMakeBuildStep::updateAndEmitEnvironmentChanged() +{ + Environment env = baseEnvironment(); + env.modify(userEnvironmentChanges()); + if (env == m_environment) + return; + m_environment = env; + emit environmentChanged(); +} + +Environment CMakeBuildStep::baseEnvironment() const +{ + Environment result; + if (!useClearEnvironment()) { + ProjectExplorer::IDevice::ConstPtr devicePtr = BuildDeviceKitAspect::device(kit()); + result = devicePtr ? devicePtr->systemEnvironment() : Environment::systemEnvironment(); + } + buildConfiguration()->addToEnvironment(result); + kit()->addToBuildEnvironment(result); + result.modify(project()->additionalEnvironment()); + return result; +} + +QString CMakeBuildStep::baseEnvironmentText() const +{ + if (useClearEnvironment()) + return tr("Clean Environment"); + else + return tr("System Environment"); +} + void CMakeBuildStep::processFinished(int exitCode, QProcess::ExitStatus status) { AbstractProcessStep::processFinished(exitCode, status); diff --git a/src/plugins/cmakeprojectmanager/cmakebuildstep.h b/src/plugins/cmakeprojectmanager/cmakebuildstep.h index 2124727a65e..f44474cdaac 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildstep.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildstep.h @@ -54,8 +54,26 @@ public: QString activeRunConfigTarget() const; + void setBuildPreset(const QString &preset); + + Utils::Environment environment() const; + void setUserEnvironmentChanges(const Utils::EnvironmentItems &diff); + Utils::EnvironmentItems userEnvironmentChanges() const; + bool useClearEnvironment() const; + void setUseClearEnvironment(bool b); + void updateAndEmitEnvironmentChanged(); + + Utils::Environment baseEnvironment() const; + QString baseEnvironmentText() const; + + void setCMakeArguments(const QStringList &cmakeArguments); + void setToolArguments(const QStringList &nativeToolArguments); + + void setConfiguration(const QString &configuration); + signals: void buildTargetsChanged(); + void environmentChanged(); private: Utils::CommandLine cmakeCommand() const; @@ -69,6 +87,7 @@ private: QWidget *createConfigWidget() override; QString defaultBuildTarget() const; + bool isCleanStep() const; void runImpl(); void handleProjectWasParsed(bool success); @@ -90,6 +109,12 @@ private: QString m_installTarget = "install"; Utils::TreeModel m_buildTargetModel; + + Utils::Environment m_environment; + Utils::EnvironmentItems m_userEnvironmentChanges; + bool m_clearSystemEnvironment = false; + QString m_buildPreset; + std::optional m_configuration; }; class CMakeBuildStepFactory : public ProjectExplorer::BuildStepFactory diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index cbee35001bd..e3873d35df5 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -88,54 +88,93 @@ Internal::PresetsData CMakeProject::combinePresets(Internal::PresetsData &cmakeP result.version = cmakePresetsData.version; result.cmakeMinimimRequired = cmakePresetsData.cmakeMinimimRequired; - QHash configurePresets; + auto combinePresetsInternal = [](auto &presetsHash, + auto &presets, + auto &userPresets, + const QString &presetType) { + // Populate the hash map with the CMakePresets + for (const auto &p : presets) + presetsHash.insert(p.name, p); - // Populate the hash map with the CMakePresets - for (const PresetsDetails::ConfigurePreset &p: cmakePresetsData.configurePresets) - configurePresets.insert(p.name, p); - - auto resolveInherits = - [configurePresets](std::vector &configurePresetsList) { - for (PresetsDetails::ConfigurePreset &cp : configurePresetsList) { - if (!cp.inherits) + auto resolveInherits = [](const auto &presetsHash, auto &presetsList) { + for (auto &p : presetsList) { + if (!p.inherits) continue; - for (const QString &inheritFromName : cp.inherits.value()) - if (configurePresets.contains(inheritFromName)) - cp.inheritFrom(configurePresets[inheritFromName]); + for (const QString &inheritFromName : p.inherits.value()) + if (presetsHash.contains(inheritFromName)) + p.inheritFrom(presetsHash[inheritFromName]); } }; - // First resolve the CMakePresets - resolveInherits(cmakePresetsData.configurePresets); + // First resolve the CMakePresets + resolveInherits(presetsHash, presets); - // Add the CMakeUserPresets to the resolve hash map - for (const PresetsDetails::ConfigurePreset &cp : cmakeUserPresetsData.configurePresets) { - if (configurePresets.contains(cp.name)) { - TaskHub::addTask(BuildSystemTask( - Task::TaskType::Error, - tr("CMakeUserPresets.json cannot re-define the configure preset: %1").arg(cp.name), - "CMakeUserPresets.json")); - TaskHub::requestPopup(); - } else { - configurePresets.insert(cp.name, cp); + // Add the CMakeUserPresets to the resolve hash map + for (const auto &p : userPresets) { + if (presetsHash.contains(p.name)) { + TaskHub::addTask( + BuildSystemTask(Task::TaskType::Error, + tr("CMakeUserPresets.json cannot re-define the %1 preset: %2") + .arg(presetType) + .arg(p.name), + "CMakeUserPresets.json")); + TaskHub::requestPopup(); + } else { + presetsHash.insert(p.name, p); + } } - } - // Then resolve the CMakeUserPresets - resolveInherits(cmakeUserPresetsData.configurePresets); + // Then resolve the CMakeUserPresets + resolveInherits(presetsHash, userPresets); - // Get both CMakePresets and CMakeUserPresets into the result - result.configurePresets = cmakePresetsData.configurePresets; + // Get both CMakePresets and CMakeUserPresets into the result + auto result = presets; - // std::vector doesn't have append - std::copy(cmakeUserPresetsData.configurePresets.begin(), - cmakeUserPresetsData.configurePresets.end(), - std::back_inserter(result.configurePresets)); + // std::vector doesn't have append + std::copy(userPresets.begin(), userPresets.end(), std::back_inserter(result)); + return result; + }; + + QHash configurePresetsHash; + QHash buildPresetsHash; + + result.configurePresets = combinePresetsInternal(configurePresetsHash, + cmakePresetsData.configurePresets, + cmakeUserPresetsData.configurePresets, + "configure"); + result.buildPresets = combinePresetsInternal(buildPresetsHash, + cmakePresetsData.buildPresets, + cmakeUserPresetsData.buildPresets, + "build"); return result; } +void CMakeProject::setupBuildPresets(Internal::PresetsData &presetsData) +{ + for (auto &buildPreset : presetsData.buildPresets) { + if (buildPreset.inheritConfigureEnvironment) { + if (!buildPreset.configurePreset) { + TaskHub::addTask(BuildSystemTask( + Task::TaskType::Error, + tr("Build preset %1 is missing a corresponding configure preset.") + .arg(buildPreset.name))); + TaskHub::requestPopup(); + } + + const QString &configurePresetName = buildPreset.configurePreset.value(); + buildPreset.environment + = Utils::findOrDefault(presetsData.configurePresets, + [configurePresetName]( + const PresetsDetails::ConfigurePreset &configurePreset) { + return configurePresetName == configurePreset.name; + }) + .environment; + } + } +} + void CMakeProject::readPresets() { auto parsePreset = [](const Utils::FilePath &presetFile) -> Internal::PresetsData { @@ -168,6 +207,7 @@ void CMakeProject::readPresets() Internal::PresetsData cmakeUserPresetsData = parsePreset(cmakeUserPresetsJson); m_presetsData = combinePresets(cmakePresetsData, cmakeUserPresetsData); + setupBuildPresets(m_presetsData); } bool CMakeProject::setupTarget(Target *t) diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h index 4854ed1ffc1..14cebc2fc32 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.h +++ b/src/plugins/cmakeprojectmanager/cmakeproject.h @@ -38,6 +38,7 @@ private: ProjectExplorer::DeploymentKnowledge deploymentKnowledge() const override; Internal::PresetsData combinePresets(Internal::PresetsData &cmakePresetsData, Internal::PresetsData &cmakeUserPresetsData); + void setupBuildPresets(Internal::PresetsData &presetsData); mutable Internal::CMakeProjectImporter *m_projectImporter = nullptr; diff --git a/src/plugins/cmakeprojectmanager/presetsmacros.cpp b/src/plugins/cmakeprojectmanager/presetsmacros.cpp index c5a140f4601..29ecdf946bf 100644 --- a/src/plugins/cmakeprojectmanager/presetsmacros.cpp +++ b/src/plugins/cmakeprojectmanager/presetsmacros.cpp @@ -12,9 +12,9 @@ namespace CMakeProjectManager::Internal::CMakePresets::Macros { -static void expandAllButEnv(const PresetsDetails::ConfigurePreset &configurePreset, - const Utils::FilePath &sourceDirectory, - QString &value) +void expandAllButEnv(const PresetsDetails::ConfigurePreset &preset, + const Utils::FilePath &sourceDirectory, + QString &value) { value.replace("${dollar}", "$"); @@ -22,23 +22,36 @@ static void expandAllButEnv(const PresetsDetails::ConfigurePreset &configurePres value.replace("${sourceParentDir}", sourceDirectory.parentDir().toString()); value.replace("${sourceDirName}", sourceDirectory.fileName()); - value.replace("${presetName}", configurePreset.name); - if (configurePreset.generator) - value.replace("${generator}", configurePreset.generator.value()); + value.replace("${presetName}", preset.name); + if (preset.generator) + value.replace("${generator}", preset.generator.value()); } -void expand(const PresetsDetails::ConfigurePreset &configurePreset, +void expandAllButEnv(const PresetsDetails::BuildPreset &preset, + const Utils::FilePath &sourceDirectory, + QString &value) +{ + value.replace("${dollar}", "$"); + + value.replace("${sourceDir}", sourceDirectory.toString()); + value.replace("${sourceParentDir}", sourceDirectory.parentDir().toString()); + value.replace("${sourceDirName}", sourceDirectory.fileName()); + + value.replace("${presetName}", preset.name); +} + +template +void expand(const PresetType &preset, Utils::Environment &env, const Utils::FilePath &sourceDirectory) { - const QHash presetEnv = configurePreset.environment - ? configurePreset.environment.value() - : QHash(); + const QHash presetEnv = preset.environment ? preset.environment.value() + : QHash(); for (auto it = presetEnv.constKeyValueBegin(); it != presetEnv.constKeyValueEnd(); ++it) { const QString key = it->first; QString value = it->second; - expandAllButEnv(configurePreset, sourceDirectory, value); + expandAllButEnv(preset, sourceDirectory, value); QRegularExpression envRegex(R"((\$env\{(\w+)\}))"); for (const QRegularExpressionMatch &match : envRegex.globalMatch(value)) { @@ -69,19 +82,19 @@ void expand(const PresetsDetails::ConfigurePreset &configurePreset, } } -void expand(const PresetsDetails::ConfigurePreset &configurePreset, +template +void expand(const PresetType &preset, Utils::EnvironmentItems &envItems, const Utils::FilePath &sourceDirectory) { - const QHash presetEnv = configurePreset.environment - ? configurePreset.environment.value() - : QHash(); + const QHash presetEnv = preset.environment ? preset.environment.value() + : QHash(); for (auto it = presetEnv.constKeyValueBegin(); it != presetEnv.constKeyValueEnd(); ++it) { const QString key = it->first; QString value = it->second; - expandAllButEnv(configurePreset, sourceDirectory, value); + expandAllButEnv(preset, sourceDirectory, value); QRegularExpression envRegex(R"((\$env\{(\w+)\}))"); for (const QRegularExpressionMatch &match : envRegex.globalMatch(value)) { @@ -108,16 +121,16 @@ void expand(const PresetsDetails::ConfigurePreset &configurePreset, } } -void expand(const PresetsDetails::ConfigurePreset &configurePreset, +template +void expand(const PresetType &preset, const Utils::Environment &env, const Utils::FilePath &sourceDirectory, QString &value) { - expandAllButEnv(configurePreset, sourceDirectory, value); + expandAllButEnv(preset, sourceDirectory, value); - const QHash presetEnv = configurePreset.environment - ? configurePreset.environment.value() - : QHash(); + const QHash presetEnv = preset.environment ? preset.environment.value() + : QHash(); QRegularExpression envRegex(R"((\$env\{(\w+)\}))"); for (const QRegularExpressionMatch &match : envRegex.globalMatch(value)) @@ -128,4 +141,32 @@ void expand(const PresetsDetails::ConfigurePreset &configurePreset, value.replace(match.captured(1), env.value(match.captured(2))); } +// Expand for PresetsDetails::ConfigurePreset +template void expand(const PresetsDetails::ConfigurePreset &preset, + Utils::Environment &env, + const Utils::FilePath &sourceDirectory); + +template void expand(const PresetsDetails::ConfigurePreset &preset, + Utils::EnvironmentItems &envItems, + const Utils::FilePath &sourceDirectory); + +template void expand(const PresetsDetails::ConfigurePreset &preset, + const Utils::Environment &env, + const Utils::FilePath &sourceDirectory, + QString &value); + +// Expand for PresetsDetails::BuildPreset +template void expand(const PresetsDetails::BuildPreset &preset, + Utils::Environment &env, + const Utils::FilePath &sourceDirectory); + +template void expand(const PresetsDetails::BuildPreset &preset, + Utils::EnvironmentItems &envItems, + const Utils::FilePath &sourceDirectory); + +template void expand(const PresetsDetails::BuildPreset &preset, + const Utils::Environment &env, + const Utils::FilePath &sourceDirectory, + QString &value); + } // namespace CMakeProjectManager::Internal::CMakePresets::Macros diff --git a/src/plugins/cmakeprojectmanager/presetsmacros.h b/src/plugins/cmakeprojectmanager/presetsmacros.h index 49f738ac784..9da2734da2a 100644 --- a/src/plugins/cmakeprojectmanager/presetsmacros.h +++ b/src/plugins/cmakeprojectmanager/presetsmacros.h @@ -12,16 +12,13 @@ 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. * $penv{PATH} is taken from Utils::Environment */ -void expand(const PresetsDetails::ConfigurePreset &configurePreset, +template +void expand(const PresetType &preset, Utils::Environment &env, const Utils::FilePath &sourceDirectory); @@ -29,14 +26,16 @@ void expand(const PresetsDetails::ConfigurePreset &configurePreset, * Expands the CMakePresets Macros using Utils::Environment as target * $penv{PATH} is replaced with Qt Creator macros ${PATH} */ -void expand(const PresetsDetails::ConfigurePreset &configurePreset, +template +void expand(const PresetType &preset, Utils::EnvironmentItems &envItems, const Utils::FilePath &sourceDirectory); /** * Expands the CMakePresets macros inside the @value QString parameter. */ -void expand(const PresetsDetails::ConfigurePreset &configurePreset, +template +void expand(const PresetType &preset, const Utils::Environment &env, const Utils::FilePath &sourceDirectory, QString &value); diff --git a/src/plugins/cmakeprojectmanager/presetsparser.cpp b/src/plugins/cmakeprojectmanager/presetsparser.cpp index cf760d509e3..47e46cb0137 100644 --- a/src/plugins/cmakeprojectmanager/presetsparser.cpp +++ b/src/plugins/cmakeprojectmanager/presetsparser.cpp @@ -13,7 +13,7 @@ namespace Internal { bool parseVersion(const QJsonValue &jsonValue, int &version) { - if (jsonValue.isNull()) + if (jsonValue.isUndefined()) return false; const int invalidVersion = -1; @@ -23,7 +23,7 @@ bool parseVersion(const QJsonValue &jsonValue, int &version) bool parseCMakeMinimumRequired(const QJsonValue &jsonValue, QVersionNumber &versionNumber) { - if (jsonValue.isNull() || !jsonValue.isObject()) + if (jsonValue.isUndefined() || !jsonValue.isObject()) return false; QJsonObject object = jsonValue.toObject(); @@ -38,7 +38,7 @@ bool parseConfigurePresets(const QJsonValue &jsonValue, std::vector &configurePresets) { // The whole section is optional - if (jsonValue.isNull()) + if (jsonValue.isUndefined()) return true; if (!jsonValue.isArray()) @@ -56,7 +56,7 @@ bool parseConfigurePresets(const QJsonValue &jsonValue, preset.hidden = object.value("hidden").toBool(); QJsonValue inherits = object.value("inherits"); - if (!inherits.isNull()) { + if (!inherits.isUndefined()) { preset.inherits = QStringList(); if (inherits.isArray()) { const QJsonArray inheritsArray = inherits.toArray(); @@ -184,6 +184,97 @@ bool parseConfigurePresets(const QJsonValue &jsonValue, return true; } +bool parseBuildPresets(const QJsonValue &jsonValue, + std::vector &buildPresets) +{ + // The whole section is optional + if (jsonValue.isUndefined()) + return true; + + if (!jsonValue.isArray()) + return false; + + const QJsonArray buildPresetsArray = jsonValue.toArray(); + for (const QJsonValue &presetJson : buildPresetsArray) { + if (!presetJson.isObject()) + continue; + + QJsonObject object = presetJson.toObject(); + PresetsDetails::BuildPreset preset; + + preset.name = object.value("name").toString(); + preset.hidden = object.value("hidden").toBool(); + + QJsonValue inherits = object.value("inherits"); + if (!inherits.isUndefined()) { + preset.inherits = QStringList(); + if (inherits.isArray()) { + const QJsonArray inheritsArray = inherits.toArray(); + for (const QJsonValue &inheritsValue : inheritsArray) + preset.inherits.value() << inheritsValue.toString(); + } else { + QString inheritsValue = inherits.toString(); + if (!inheritsValue.isEmpty()) + preset.inherits.value() << inheritsValue; + } + } + if (object.contains("displayName")) + preset.displayName = object.value("displayName").toString(); + if (object.contains("description")) + preset.description = object.value("description").toString(); + + const QJsonObject environmentObj = object.value("environment").toObject(); + for (const QString &envKey : environmentObj.keys()) { + if (!preset.environment) + preset.environment = QHash(); + + QJsonValue envValue = environmentObj.value(envKey); + preset.environment.value().insert(envKey, envValue.toString()); + } + + if (object.contains("configurePreset")) + preset.configurePreset = object.value("configurePreset").toString(); + if (object.contains("inheritConfigureEnvironment")) + preset.inheritConfigureEnvironment = object.value("inheritConfigureEnvironment").toBool(); + if (object.contains("jobs")) + preset.jobs = object.value("jobs").toInt(); + + QJsonValue targets = object.value("targets"); + if (!targets.isUndefined()) { + preset.targets = QStringList(); + if (targets.isArray()) { + const QJsonArray targetsArray = targets.toArray(); + for (const QJsonValue &targetsValue : targetsArray) + preset.targets.value() << targetsValue.toString(); + } else { + QString targetsValue = targets.toString(); + if (!targetsValue.isEmpty()) + preset.targets.value() << targetsValue; + } + } + if (object.contains("configuration")) + preset.configuration = object.value("configuration").toString(); + if (object.contains("verbose")) + preset.verbose = object.value("verbose").toBool(); + if (object.contains("cleanFirst")) + preset.cleanFirst = object.value("cleanFirst").toBool(); + + QJsonValue nativeToolOptions = object.value("nativeToolOptions"); + if (!nativeToolOptions.isUndefined()) { + if (nativeToolOptions.isArray()) { + preset.nativeToolOptions = QStringList(); + const QJsonArray toolOptionsArray = nativeToolOptions.toArray(); + for (const QJsonValue &toolOptionsValue : toolOptionsArray) + preset.nativeToolOptions.value() << toolOptionsValue.toString(); + } + } + + buildPresets.emplace_back(preset); + } + + return true; +} + const PresetsData &PresetsParser::presetsData() const { return m_presetsData; @@ -232,9 +323,19 @@ bool PresetsParser::parse(const Utils::FilePath &jsonFile, QString &errorMessage // optional if (!parseConfigurePresets(root.value("configurePresets"), m_presetsData.configurePresets)) { - errorMessage = QCoreApplication::translate( - "CMakeProjectManager::Internal", - "Invalid \"configurePresets\" section in %1 file").arg(jsonFile.fileName()); + errorMessage + = QCoreApplication::translate("CMakeProjectManager::Internal", + "Invalid \"configurePresets\" section in %1 file") + .arg(jsonFile.fileName()); + return false; + } + + // optional + if (!parseBuildPresets(root.value("buildPresets"), m_presetsData.buildPresets)) { + errorMessage + = QCoreApplication::translate("CMakeProjectManager::Internal", + "Invalid \"buildPresets\" section in %1 file") + .arg(jsonFile.fileName()); return false; } @@ -277,5 +378,38 @@ void PresetsDetails::ConfigurePreset::inheritFrom(const ConfigurePreset &other) debug = other.debug; } +void PresetsDetails::BuildPreset::inheritFrom(const BuildPreset &other) +{ + if (!vendor && other.vendor) + vendor = other.vendor; + + if (!environment && other.environment) + environment = other.environment; + + if (!configurePreset && other.configurePreset) + configurePreset = other.configurePreset; + + if (!inheritConfigureEnvironment && other.inheritConfigureEnvironment) + inheritConfigureEnvironment = other.inheritConfigureEnvironment; + + if (!jobs && other.jobs) + jobs = other.jobs; + + if (!targets && other.targets) + targets = other.targets; + + if (!configuration && other.configuration) + configuration = other.configuration; + + if (!verbose && other.verbose) + verbose = other.verbose; + + if (!cleanFirst && other.cleanFirst) + cleanFirst = other.cleanFirst; + + if (!nativeToolOptions && other.nativeToolOptions) + nativeToolOptions = other.nativeToolOptions; +} + } // namespace Internal } // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/presetsparser.h b/src/plugins/cmakeprojectmanager/presetsparser.h index 0f4700f382f..c067507a2a2 100644 --- a/src/plugins/cmakeprojectmanager/presetsparser.h +++ b/src/plugins/cmakeprojectmanager/presetsparser.h @@ -67,6 +67,27 @@ public: std::optional debug; }; +class BuildPreset { +public: + void inheritFrom(const BuildPreset &other); + + QString name; + std::optional hidden = false; + std::optional inherits; + std::optional> vendor; + std::optional displayName; + std::optional description; + std::optional> environment; + std::optional configurePreset; + std::optional inheritConfigureEnvironment = true; + std::optional jobs; + std::optional targets; + std::optional configuration; + std::optional verbose; + std::optional cleanFirst; + std::optional nativeToolOptions; +}; + } // namespace PresetsDetails class PresetsData @@ -76,6 +97,7 @@ public: QVersionNumber cmakeMinimimRequired; QHash vendor; std::vector configurePresets; + std::vector buildPresets; }; class PresetsParser diff --git a/tests/manual/cmakepresets/CMakePresets.json b/tests/manual/cmakepresets/CMakePresets.json index 74f4c1ba22e..9af5b24cd40 100644 --- a/tests/manual/cmakepresets/CMakePresets.json +++ b/tests/manual/cmakepresets/CMakePresets.json @@ -1,8 +1,8 @@ { - "version": 1, + "version": 2, "cmakeMinimumRequired": { "major": 3, - "minor": 19, + "minor": 20, "patch": 0 }, "configurePresets": [ @@ -13,10 +13,10 @@ "binaryDir": "${sourceDir}/build-${presetName}-release", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", - "CMAKE_PREFIX_PATH": "c:/Qt/6.3.1/mingw_64" + "CMAKE_PREFIX_PATH": "c:/Qt/6.3.2/mingw_64" }, "environment": { - "PATH": "c:/mingw64/bin;$penv{PATH}" + "PATH": "c:/Qt/Tools/mingw1120_64/bin;$penv{PATH}" }, "debug" : { "find" : true @@ -37,8 +37,38 @@ "value": "x64" }, "cacheVariables": { - "CMAKE_PREFIX_PATH": "c:/Qt/6.3.1/msvc2019_64" + "CMAKE_PREFIX_PATH": "c:/Qt/6.3.2/msvc2019_64" } } + ], + "buildPresets": [ + { + "name": "mingw", + "displayName": "MinGW default", + "configurePreset": "mingw", + "targets": "${sourceDirName}" + }, + { + "name": "mingw-verbose", + "inherits": "mingw", + "displayName": "MinGW verbose", + "verbose": true + }, + { + "name": "mingw-make", + "displayName": "MinGW make 4 CPUs", + "configurePreset": "mingw-make", + "jobs": 4 + }, + { + "name": "visualc-debug", + "configurePreset": "visualc", + "configuration": "Debug" + }, + { + "name": "visualc-relwithdebinfo", + "inherits": "visualc-debug", + "configuration": "RelWithDebInfo" + } ] }