From e326dcd7876a1dde9c89f2c02b0834e6fd79da59 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 17 Sep 2024 16:08:24 +0200 Subject: [PATCH] ProjectExplorer: add workspace build configurations Change-Id: Iaa23a50052ec46e50f43e79d378c07ccec5ef2e1 Reviewed-by: Christian Stenger --- share/qtcreator/jsonschemas/project.json | 54 ++++++ .../projectexplorer/buildconfiguration.cpp | 15 +- .../projectexplorer/buildconfiguration.h | 2 +- .../buildsettingspropertiespage.cpp | 4 +- src/plugins/projectexplorer/project.cpp | 2 +- .../projectexplorer/workspaceproject.cpp | 181 ++++++++++++++++-- 6 files changed, 231 insertions(+), 27 deletions(-) diff --git a/share/qtcreator/jsonschemas/project.json b/share/qtcreator/jsonschemas/project.json index df55dbd693a..5515c02a1a3 100644 --- a/share/qtcreator/jsonschemas/project.json +++ b/share/qtcreator/jsonschemas/project.json @@ -52,6 +52,60 @@ ] } ] + }, + "build.configuration": { + "type": "array", + "description": "A list of build configurations", + "items": [ + { + "type": "object", + "properties": { + "steps": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "arguments": { + "type": "array", + "items": [ + { + "type": "string" + } + ], + "description": "Arguments to pass to the executable" + }, + "executable": { + "type": "string", + "description": "The executable to run" + }, + "workingDirectory": { + "type": "string", + "description": "The working directory to run the executable in" + } + }, + "required": [ + "executable" + ] + } + ], + "description": "The steps required for the build" + }, + "name": { + "type": "string", + "description": "The name of the build configuration" + }, + "buildDirectory": { + "type": "string", + "description": "The directory to build the project in" + } + }, + "required": [ + "steps", + "name" + ] + } + ] } } } diff --git a/src/plugins/projectexplorer/buildconfiguration.cpp b/src/plugins/projectexplorer/buildconfiguration.cpp index 6cc025d8770..580cfdd699f 100644 --- a/src/plugins/projectexplorer/buildconfiguration.cpp +++ b/src/plugins/projectexplorer/buildconfiguration.cpp @@ -371,6 +371,13 @@ void BuildConfiguration::appendInitialCleanStep(Utils::Id id) d->m_initialCleanSteps.append(id); } +BuildConfiguration *BuildConfiguration::clone(Target *target) const +{ + Store map; + toMap(map); + return BuildConfigurationFactory::restore(target, map); +} + void BuildConfiguration::toMap(Store &map) const { ProjectConfiguration::toMap(map); @@ -834,12 +841,4 @@ BuildConfiguration *BuildConfigurationFactory::restore(Target *parent, const Sto return nullptr; } -BuildConfiguration *BuildConfigurationFactory::clone(Target *parent, - const BuildConfiguration *source) -{ - Store map; - source->toMap(map); - return restore(parent, map); -} - } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/buildconfiguration.h b/src/plugins/projectexplorer/buildconfiguration.h index 16fe0f75555..8b765e32fec 100644 --- a/src/plugins/projectexplorer/buildconfiguration.h +++ b/src/plugins/projectexplorer/buildconfiguration.h @@ -68,6 +68,7 @@ public: void appendInitialBuildStep(Utils::Id id); void appendInitialCleanStep(Utils::Id id); + virtual BuildConfiguration *clone(Target *target) const; void fromMap(const Utils::Store &map) override; void toMap(Utils::Store &map) const override; @@ -149,7 +150,6 @@ public: BuildConfiguration *create(Target *parent, const BuildInfo &info) const; static BuildConfiguration *restore(Target *parent, const Utils::Store &map); - static BuildConfiguration *clone(Target *parent, const BuildConfiguration *source); static BuildConfigurationFactory *find(const Kit *k, const Utils::FilePath &projectPath); static BuildConfigurationFactory *find(Target *parent); diff --git a/src/plugins/projectexplorer/buildsettingspropertiespage.cpp b/src/plugins/projectexplorer/buildsettingspropertiespage.cpp index 77f0dc127ec..c3000ca5bcb 100644 --- a/src/plugins/projectexplorer/buildsettingspropertiespage.cpp +++ b/src/plugins/projectexplorer/buildsettingspropertiespage.cpp @@ -187,7 +187,7 @@ void BuildSettingsWidget::currentIndexChanged(int index) void BuildSettingsWidget::updateActiveConfiguration() { - if (!m_buildConfiguration || m_buildConfiguration == m_target->activeBuildConfiguration()) + if (m_buildConfiguration == m_target->activeBuildConfiguration()) return; m_buildConfiguration = m_target->activeBuildConfiguration(); @@ -281,7 +281,7 @@ void BuildSettingsWidget::cloneConfiguration() // Save the current build configuration settings, so that the clone gets all the settings m_target->project()->saveSettings(); - BuildConfiguration *bc = BuildConfigurationFactory::clone(m_target, m_buildConfiguration); + BuildConfiguration *bc = m_buildConfiguration->clone(m_target); if (!bc) return; diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp index 874c5b0848e..5ca9b57b3ee 100644 --- a/src/plugins/projectexplorer/project.cpp +++ b/src/plugins/projectexplorer/project.cpp @@ -508,7 +508,7 @@ bool Project::copySteps(Target *sourceTarget, Target *newTarget) const Project * const project = newTarget->project(); for (BuildConfiguration *sourceBc : sourceTarget->buildConfigurations()) { - BuildConfiguration *newBc = BuildConfigurationFactory::clone(newTarget, sourceBc); + BuildConfiguration *newBc = sourceBc->clone(newTarget); if (!newBc) { buildconfigurationError << sourceBc->displayName(); continue; diff --git a/src/plugins/projectexplorer/workspaceproject.cpp b/src/plugins/projectexplorer/workspaceproject.cpp index 042cbfd9346..61e2b4b6894 100644 --- a/src/plugins/projectexplorer/workspaceproject.cpp +++ b/src/plugins/projectexplorer/workspaceproject.cpp @@ -5,7 +5,9 @@ #include "buildconfiguration.h" #include "buildinfo.h" +#include "buildsteplist.h" #include "buildsystem.h" +#include "processstep.h" #include "projectexplorer.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" @@ -35,7 +37,8 @@ using namespace Core; namespace ProjectExplorer { -Q_LOGGING_CATEGORY(wsbs, "qtc.projectexplorer.workspacebuildsystem", QtWarningMsg); +Q_LOGGING_CATEGORY(wsbs, "qtc.projectexplorer.workspace.buildsystem", QtWarningMsg); +Q_LOGGING_CATEGORY(wsp, "qtc.projectexplorer.workspace.project", QtWarningMsg); const QLatin1StringView FOLDER_MIMETYPE{"inode/directory"}; const QLatin1StringView WORKSPACE_MIMETYPE{"text/x-workspace-project"}; @@ -47,9 +50,9 @@ const QLatin1StringView FILES_EXCLUDE_KEY{"files.exclude"}; const char EXCLUDE_ACTION_ID[] = "ProjectExplorer.ExcludeFromWorkspace"; const char RESCAN_ACTION_ID[] = "ProjectExplorer.RescanWorkspace"; -const expected_str projectDefinition(const Project *project) +const expected_str projectDefinition(const FilePath &path) { - if (auto fileContents = project->projectFilePath().fileContents()) + if (auto fileContents = path.fileContents()) return QJsonDocument::fromJson(*fileContents).object(); return {}; } @@ -156,7 +159,7 @@ WorkspaceBuildSystem::WorkspaceBuildSystem(Target *t) scanNext(); }); m_scanner.setDirFilter(workspaceDirFilter); - m_scanner.setFilter([&](const Utils::MimeType &, const Utils::FilePath &filePath) { + m_scanner.setFilter([&](const MimeType &, const FilePath &filePath) { return Utils::anyOf(m_filters, [filePath](const QRegularExpression &filter) { return filter.match(filePath.path()).hasMatch(); }); @@ -176,7 +179,7 @@ void WorkspaceBuildSystem::reparse(bool force) m_filters.clear(); FilePath projectPath = project()->projectDirectory(); - const QJsonObject json = projectDefinition(project()).value_or(QJsonObject()); + const QJsonObject json = projectDefinition(project()->projectFilePath()).value_or(QJsonObject()); const QJsonValue projectNameValue = json.value(PROJECT_NAME_KEY); if (projectNameValue.isString()) project()->setDisplayName(projectNameValue.toString()); @@ -434,40 +437,146 @@ public: class WorkspaceBuildConfiguration : public BuildConfiguration { + Q_OBJECT public: WorkspaceBuildConfiguration(Target *target, Id id) : BuildConfiguration(target, id) { + setInitializer([this](const BuildInfo &info) { + const QVariantMap extraInfos = info.extraInfo.toMap(); + if (extraInfos.empty()) + return; + BuildStepList *steps = buildSteps(); + for (const QVariant &buildStep : extraInfos["steps"].toList()) { + const QVariantMap bs = buildStep.toMap(); + auto step = new Internal::ProcessStep(steps, Constants::CUSTOM_PROCESS_STEP); + step->setCommand(FilePath::fromUserInput(bs["executable"].toString())); + step->setArguments(bs["arguments"].toStringList()); + FilePath wd = FilePath::fromUserInput(bs["workingDirectory"].toString()); + if (wd.isEmpty()) + wd = "%{ActiveProject:BuildConfig:Path}"; + else if (wd.isRelativePath()) + wd = project()->projectDirectory().resolvePath(wd); + step->setWorkingDirectory(wd); + steps->appendStep(step); + } + initializeExtraInfo(extraInfos); + }); setBuildDirectoryHistoryCompleter("Workspace.BuildDir.History"); setConfigWidgetDisplayName(Tr::tr("Workspace Manager")); - - //appendInitialBuildStep(Constants::CUSTOM_PROCESS_STEP); } + + void initializeExtraInfo(const QVariantMap &extraInfos) + { + resetExtraInfo(); + if (extraInfos["forSetup"].toBool()) { + originalExtraInfo = extraInfos; + buildInfoResetConnection = connect( + this, &BaseAspect::changed, this, &WorkspaceBuildConfiguration::resetExtraInfo); + } + } + + void fromMap(const Utils::Store &map) override + { + BuildConfiguration::fromMap(map); + initializeExtraInfo(mapFromStore(storeFromVariant(map.value("extraInfo")))); + } + + void toMap(Utils::Store &map) const override + { + BuildConfiguration::toMap(map); + if (originalExtraInfo) + map.insert("extraInfo", *originalExtraInfo); + } + + BuildConfiguration *clone(Target *target) const override + { + auto clone = BuildConfiguration::clone(target); + if (auto bc = qobject_cast(clone); QTC_GUARD(bc)) + bc->resetExtraInfo(); + return clone; + } + + void resetExtraInfo() + { + originalExtraInfo.reset(); + disconnect(buildInfoResetConnection); + } + + std::optional originalExtraInfo; + QMetaObject::Connection buildInfoResetConnection; }; class WorkspaceBuildConfigurationFactory : public BuildConfigurationFactory { public: + static WorkspaceBuildConfigurationFactory *m_instance; + WorkspaceBuildConfigurationFactory() { + QTC_CHECK(m_instance == nullptr); + m_instance = this; registerBuildConfiguration ("WorkspaceProject.BuildConfiguration"); setSupportedProjectType(WORKSPACE_PROJECT_ID); + setSupportedProjectMimeTypeName(WORKSPACE_MIMETYPE); - setBuildGenerator([](const Kit *, const FilePath &projectPath, bool forSetup) { - BuildInfo info; - info.typeName = ::ProjectExplorer::Tr::tr("Build"); - info.buildDirectory = projectPath.parentDir().parentDir().pathAppended("build"); - if (forSetup) { - //: The name of the build configuration created by default for a workspace project. + setBuildGenerator([this](const Kit *, const FilePath &projectPath, bool forSetup) { + QList result = parseBuildConfigurations(projectPath, forSetup); + if (!forSetup) { + BuildInfo info; + info.factory = this; + info.typeName = ::ProjectExplorer::Tr::tr("Build"); + info.buildDirectory = projectPath.parentDir().parentDir().pathAppended("build"); info.displayName = ::ProjectExplorer::Tr::tr("Default"); + result << info; } - return QList{info}; + return result; }); } + + static QList parseBuildConfigurations(const FilePath &projectPath, bool forSetup = false) + { + const QJsonObject json = projectDefinition(projectPath).value_or(QJsonObject()); + const QJsonArray buildConfigs = json.value("build.configuration").toArray(); + QList buildInfos; + for (const QJsonValue &buildConfig : buildConfigs) { + QTC_ASSERT(buildConfig.isObject(), continue); + + BuildInfo buildInfo; + const QJsonObject buildConfigObject = buildConfig.toObject(); + buildInfo.displayName = buildConfigObject["name"].toString(); + if (buildInfo.displayName.isEmpty()) + continue; + buildInfo.typeName = buildInfo.displayName; + buildInfo.factory = m_instance; + buildInfo.buildDirectory = FilePath::fromUserInput( + buildConfigObject["buildDirectory"].toString()); + if (buildInfo.buildDirectory.isRelativePath()) { + buildInfo.buildDirectory = projectPath.parentDir().parentDir().resolvePath( + buildInfo.buildDirectory); + } + + QVariantList buildSteps; + for (const QJsonValue &step : buildConfigObject["steps"].toArray()) { + if (step.isObject() && step.toObject().contains("executable")) + buildSteps.append(step.toObject().toVariantMap()); + } + if (buildSteps.isEmpty()) + continue; + QVariantMap extraInfo = buildConfigObject.toVariantMap(); + extraInfo["forSetup"] = forSetup; + buildInfo.extraInfo = extraInfo; + + buildInfos.append(buildInfo); + } + return buildInfos; + } }; +WorkspaceBuildConfigurationFactory *WorkspaceBuildConfigurationFactory::m_instance = nullptr; + class WorkspaceProject : public Project { Q_OBJECT @@ -486,6 +595,48 @@ public: setId(WORKSPACE_PROJECT_ID); setDisplayName(projectDirectory().fileName()); setBuildSystemCreator(); + + connect(this, &Project::projectFileIsDirty, this, &WorkspaceProject::updateBuildConfigurations); + } + + void updateBuildConfigurations() + { + qCDebug(wsp) << "Updating build configurations for" << displayName(); + const QList buildInfos + = WorkspaceBuildConfigurationFactory::parseBuildConfigurations(projectFilePath(), true); + for (Target *target : targets()) { + qCDebug(wsp) << "Updating build configurations for target" << target->displayName(); + QList toAdd = buildInfos; + QString removedActiveBuildConfiguration; + for (BuildConfiguration *bc : target->buildConfigurations()) { + auto *wbc = qobject_cast(bc); + if (!wbc) + continue; + if (std::optional extraInfo = wbc->originalExtraInfo) { + // remove the buildConfiguration if it is unchanged from the project file + auto equalExtraInfo = [&extraInfo](const BuildInfo &info) { + return info.extraInfo == *extraInfo; + }; + if (toAdd.removeIf(equalExtraInfo) == 0) { + qCDebug(wsp) << " Removing build configuration" << wbc->displayName(); + if (target->activeBuildConfiguration() == wbc) + removedActiveBuildConfiguration = wbc->displayName(); + target->removeBuildConfiguration(bc); + } + } + } + for (const BuildInfo &buildInfo : toAdd) { + if (BuildConfiguration *bc = buildInfo.factory->create(target, buildInfo)) { + qCDebug(wsp) << " Adding build configuration" << bc->displayName(); + target->addBuildConfiguration(bc); + if (!removedActiveBuildConfiguration.isEmpty() + && (bc->displayName() == removedActiveBuildConfiguration + || toAdd.size() == 1)) { + target->setActiveBuildConfiguration(bc, SetActive::NoCascade); + } + } + } + } } FilePath projectDirectory() const override @@ -503,7 +654,7 @@ public: void excludePath(const FilePath &path) { QTC_ASSERT(projectFilePath().exists(), return); - if (expected_str json = projectDefinition(this)) { + if (expected_str json = projectDefinition(projectFilePath())) { QJsonArray excludes = (*json)[FILES_EXCLUDE_KEY].toArray(); const QString relative = path.relativePathFrom(projectDirectory()).path(); if (excludes.contains(relative))