ProjectExplorer: add workspace build configurations

Change-Id: Iaa23a50052ec46e50f43e79d378c07ccec5ef2e1
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2024-09-17 16:08:24 +02:00
parent 7be2b89d01
commit e326dcd787
6 changed files with 231 additions and 27 deletions

View File

@@ -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"
]
}
]
} }
} }
} }

View File

@@ -371,6 +371,13 @@ void BuildConfiguration::appendInitialCleanStep(Utils::Id id)
d->m_initialCleanSteps.append(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 void BuildConfiguration::toMap(Store &map) const
{ {
ProjectConfiguration::toMap(map); ProjectConfiguration::toMap(map);
@@ -834,12 +841,4 @@ BuildConfiguration *BuildConfigurationFactory::restore(Target *parent, const Sto
return nullptr; return nullptr;
} }
BuildConfiguration *BuildConfigurationFactory::clone(Target *parent,
const BuildConfiguration *source)
{
Store map;
source->toMap(map);
return restore(parent, map);
}
} // namespace ProjectExplorer } // namespace ProjectExplorer

View File

@@ -68,6 +68,7 @@ public:
void appendInitialBuildStep(Utils::Id id); void appendInitialBuildStep(Utils::Id id);
void appendInitialCleanStep(Utils::Id id); void appendInitialCleanStep(Utils::Id id);
virtual BuildConfiguration *clone(Target *target) const;
void fromMap(const Utils::Store &map) override; void fromMap(const Utils::Store &map) override;
void toMap(Utils::Store &map) const override; void toMap(Utils::Store &map) const override;
@@ -149,7 +150,6 @@ public:
BuildConfiguration *create(Target *parent, const BuildInfo &info) const; BuildConfiguration *create(Target *parent, const BuildInfo &info) const;
static BuildConfiguration *restore(Target *parent, const Utils::Store &map); 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(const Kit *k, const Utils::FilePath &projectPath);
static BuildConfigurationFactory *find(Target *parent); static BuildConfigurationFactory *find(Target *parent);

View File

@@ -187,7 +187,7 @@ void BuildSettingsWidget::currentIndexChanged(int index)
void BuildSettingsWidget::updateActiveConfiguration() void BuildSettingsWidget::updateActiveConfiguration()
{ {
if (!m_buildConfiguration || m_buildConfiguration == m_target->activeBuildConfiguration()) if (m_buildConfiguration == m_target->activeBuildConfiguration())
return; return;
m_buildConfiguration = m_target->activeBuildConfiguration(); 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 // Save the current build configuration settings, so that the clone gets all the settings
m_target->project()->saveSettings(); m_target->project()->saveSettings();
BuildConfiguration *bc = BuildConfigurationFactory::clone(m_target, m_buildConfiguration); BuildConfiguration *bc = m_buildConfiguration->clone(m_target);
if (!bc) if (!bc)
return; return;

View File

@@ -508,7 +508,7 @@ bool Project::copySteps(Target *sourceTarget, Target *newTarget)
const Project * const project = newTarget->project(); const Project * const project = newTarget->project();
for (BuildConfiguration *sourceBc : sourceTarget->buildConfigurations()) { for (BuildConfiguration *sourceBc : sourceTarget->buildConfigurations()) {
BuildConfiguration *newBc = BuildConfigurationFactory::clone(newTarget, sourceBc); BuildConfiguration *newBc = sourceBc->clone(newTarget);
if (!newBc) { if (!newBc) {
buildconfigurationError << sourceBc->displayName(); buildconfigurationError << sourceBc->displayName();
continue; continue;

View File

@@ -5,7 +5,9 @@
#include "buildconfiguration.h" #include "buildconfiguration.h"
#include "buildinfo.h" #include "buildinfo.h"
#include "buildsteplist.h"
#include "buildsystem.h" #include "buildsystem.h"
#include "processstep.h"
#include "projectexplorer.h" #include "projectexplorer.h"
#include "projectexplorerconstants.h" #include "projectexplorerconstants.h"
#include "projectexplorertr.h" #include "projectexplorertr.h"
@@ -35,7 +37,8 @@ using namespace Core;
namespace ProjectExplorer { 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 FOLDER_MIMETYPE{"inode/directory"};
const QLatin1StringView WORKSPACE_MIMETYPE{"text/x-workspace-project"}; 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 EXCLUDE_ACTION_ID[] = "ProjectExplorer.ExcludeFromWorkspace";
const char RESCAN_ACTION_ID[] = "ProjectExplorer.RescanWorkspace"; const char RESCAN_ACTION_ID[] = "ProjectExplorer.RescanWorkspace";
const expected_str<QJsonObject> projectDefinition(const Project *project) const expected_str<QJsonObject> projectDefinition(const FilePath &path)
{ {
if (auto fileContents = project->projectFilePath().fileContents()) if (auto fileContents = path.fileContents())
return QJsonDocument::fromJson(*fileContents).object(); return QJsonDocument::fromJson(*fileContents).object();
return {}; return {};
} }
@@ -156,7 +159,7 @@ WorkspaceBuildSystem::WorkspaceBuildSystem(Target *t)
scanNext(); scanNext();
}); });
m_scanner.setDirFilter(workspaceDirFilter); 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 Utils::anyOf(m_filters, [filePath](const QRegularExpression &filter) {
return filter.match(filePath.path()).hasMatch(); return filter.match(filePath.path()).hasMatch();
}); });
@@ -176,7 +179,7 @@ void WorkspaceBuildSystem::reparse(bool force)
m_filters.clear(); m_filters.clear();
FilePath projectPath = project()->projectDirectory(); 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); const QJsonValue projectNameValue = json.value(PROJECT_NAME_KEY);
if (projectNameValue.isString()) if (projectNameValue.isString())
project()->setDisplayName(projectNameValue.toString()); project()->setDisplayName(projectNameValue.toString());
@@ -434,40 +437,146 @@ public:
class WorkspaceBuildConfiguration : public BuildConfiguration class WorkspaceBuildConfiguration : public BuildConfiguration
{ {
Q_OBJECT
public: public:
WorkspaceBuildConfiguration(Target *target, Id id) WorkspaceBuildConfiguration(Target *target, Id id)
: BuildConfiguration(target, 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"); setBuildDirectoryHistoryCompleter("Workspace.BuildDir.History");
setConfigWidgetDisplayName(Tr::tr("Workspace Manager")); 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<WorkspaceBuildConfiguration *>(clone); QTC_GUARD(bc))
bc->resetExtraInfo();
return clone;
}
void resetExtraInfo()
{
originalExtraInfo.reset();
disconnect(buildInfoResetConnection);
}
std::optional<QVariantMap> originalExtraInfo;
QMetaObject::Connection buildInfoResetConnection;
}; };
class WorkspaceBuildConfigurationFactory : public BuildConfigurationFactory class WorkspaceBuildConfigurationFactory : public BuildConfigurationFactory
{ {
public: public:
static WorkspaceBuildConfigurationFactory *m_instance;
WorkspaceBuildConfigurationFactory() WorkspaceBuildConfigurationFactory()
{ {
QTC_CHECK(m_instance == nullptr);
m_instance = this;
registerBuildConfiguration<WorkspaceBuildConfiguration> registerBuildConfiguration<WorkspaceBuildConfiguration>
("WorkspaceProject.BuildConfiguration"); ("WorkspaceProject.BuildConfiguration");
setSupportedProjectType(WORKSPACE_PROJECT_ID); setSupportedProjectType(WORKSPACE_PROJECT_ID);
setSupportedProjectMimeTypeName(WORKSPACE_MIMETYPE);
setBuildGenerator([](const Kit *, const FilePath &projectPath, bool forSetup) { setBuildGenerator([this](const Kit *, const FilePath &projectPath, bool forSetup) {
QList<BuildInfo> result = parseBuildConfigurations(projectPath, forSetup);
if (!forSetup) {
BuildInfo info; BuildInfo info;
info.factory = this;
info.typeName = ::ProjectExplorer::Tr::tr("Build"); info.typeName = ::ProjectExplorer::Tr::tr("Build");
info.buildDirectory = projectPath.parentDir().parentDir().pathAppended("build"); info.buildDirectory = projectPath.parentDir().parentDir().pathAppended("build");
if (forSetup) {
//: The name of the build configuration created by default for a workspace project.
info.displayName = ::ProjectExplorer::Tr::tr("Default"); info.displayName = ::ProjectExplorer::Tr::tr("Default");
result << info;
} }
return QList<BuildInfo>{info}; return result;
}); });
} }
static QList<BuildInfo> parseBuildConfigurations(const FilePath &projectPath, bool forSetup = false)
{
const QJsonObject json = projectDefinition(projectPath).value_or(QJsonObject());
const QJsonArray buildConfigs = json.value("build.configuration").toArray();
QList<BuildInfo> 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 class WorkspaceProject : public Project
{ {
Q_OBJECT Q_OBJECT
@@ -486,6 +595,48 @@ public:
setId(WORKSPACE_PROJECT_ID); setId(WORKSPACE_PROJECT_ID);
setDisplayName(projectDirectory().fileName()); setDisplayName(projectDirectory().fileName());
setBuildSystemCreator<WorkspaceBuildSystem>(); setBuildSystemCreator<WorkspaceBuildSystem>();
connect(this, &Project::projectFileIsDirty, this, &WorkspaceProject::updateBuildConfigurations);
}
void updateBuildConfigurations()
{
qCDebug(wsp) << "Updating build configurations for" << displayName();
const QList<BuildInfo> buildInfos
= WorkspaceBuildConfigurationFactory::parseBuildConfigurations(projectFilePath(), true);
for (Target *target : targets()) {
qCDebug(wsp) << "Updating build configurations for target" << target->displayName();
QList<BuildInfo> toAdd = buildInfos;
QString removedActiveBuildConfiguration;
for (BuildConfiguration *bc : target->buildConfigurations()) {
auto *wbc = qobject_cast<WorkspaceBuildConfiguration *>(bc);
if (!wbc)
continue;
if (std::optional<QVariantMap> 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 FilePath projectDirectory() const override
@@ -503,7 +654,7 @@ public:
void excludePath(const FilePath &path) void excludePath(const FilePath &path)
{ {
QTC_ASSERT(projectFilePath().exists(), return); QTC_ASSERT(projectFilePath().exists(), return);
if (expected_str<QJsonObject> json = projectDefinition(this)) { if (expected_str<QJsonObject> json = projectDefinition(projectFilePath())) {
QJsonArray excludes = (*json)[FILES_EXCLUDE_KEY].toArray(); QJsonArray excludes = (*json)[FILES_EXCLUDE_KEY].toArray();
const QString relative = path.relativePathFrom(projectDirectory()).path(); const QString relative = path.relativePathFrom(projectDirectory()).path();
if (excludes.contains(relative)) if (excludes.contains(relative))