QbsProjectManager: Switch to an out-of-process approach

That is, do not link to the qbscore library anymore. Instead, use the
JSON-based API.
Advantages:
    - We can build Qt Creator with qbs support without qbs being present
      on the build machine.
    - Smaller memory footprint for Qt Creator, as the qbs build graphs
      are now being managed by a separate process.
    - Potential crashes in qbs will not kill the Qt Creator process.

Fixes: QTCREATORBUG-20622
Task-number: QTCREATORBUG-22904
Change-Id: If7d344b0ac65a99ff0a3a3db215d61b8d903e47e
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
This commit is contained in:
Christian Kandeler
2019-06-28 14:30:32 +02:00
parent c2127c9ec4
commit 6913947986
38 changed files with 2339 additions and 1972 deletions

View File

@@ -1,10 +1,6 @@
find_package(Qbs)
add_qtc_plugin(QbsProjectManager
CONDITION TARGET Qbs::QbsCore
DEPENDS Qbs::QbsCore Qt5::Widgets qmljs
DEPENDS Qt5::Qml Qt5::Widgets qmljs
DEFINES
QBS_INSTALL_DIR="${QBS_INSTALL_DIR}"
IDE_LIBRARY_BASENAME="${IDE_LIBRARY_BASE_PATH}"
PLUGIN_DEPENDS Core ProjectExplorer CppTools QtSupport QmlJSTools
SOURCES
@@ -17,19 +13,20 @@ add_qtc_plugin(QbsProjectManager
qbscleanstepconfigwidget.ui
qbsinstallstep.cpp qbsinstallstep.h
qbskitinformation.cpp qbskitinformation.h
qbslogsink.cpp qbslogsink.h
qbsnodes.cpp qbsnodes.h
qbsnodetreebuilder.cpp qbsnodetreebuilder.h
qbsparser.cpp qbsparser.h
qbspmlogging.cpp qbspmlogging.h
qbsprofilemanager.cpp qbsprofilemanager.h
qbsprofilessettingspage.cpp qbsprofilessettingspage.h
qbsprofilessettingswidget.ui
qbsproject.cpp qbsproject.h
qbsprojectimporter.cpp qbsprojectimporter.h
qbsprojectmanager.cpp qbsprojectmanager.h qbsprojectmanager.qrc
qbsprojectmanager.qrc
qbsprojectmanager_global.h
qbsprojectmanagerconstants.h
qbsprojectmanagerplugin.cpp qbsprojectmanagerplugin.h
qbsprojectmanagersettings.cpp qbsprojectmanagersettings.h
qbsprojectparser.cpp qbsprojectparser.h
qbssession.cpp qbssession.h
qbssettings.cpp qbssettings.h
)

View File

@@ -26,7 +26,7 @@
#include "customqbspropertiesdialog.h"
#include "ui_customqbspropertiesdialog.h"
#include <qbs.h>
#include "qbsprofilemanager.h"
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
@@ -49,7 +49,7 @@ CustomQbsPropertiesDialog::CustomQbsPropertiesDialog(const QVariantMap &properti
nameItem->setData(Qt::DisplayRole, it.key());
m_ui->propertiesTable->setItem(currentRow, 0, nameItem);
auto * const valueItem = new QTableWidgetItem;
valueItem->setData(Qt::DisplayRole, qbs::settingsValueToRepresentation(it.value()));
valueItem->setData(Qt::DisplayRole, toJSLiteral(it.value()));
m_ui->propertiesTable->setItem(currentRow, 1, valueItem);
++currentRow;
}
@@ -70,8 +70,7 @@ QVariantMap CustomQbsPropertiesDialog::properties() const
const QString name = nameItem->text();
if (name.isEmpty())
continue;
const QString rawString = m_ui->propertiesTable->item(row, 1)->text();
properties.insert(name, qbs::representationToSettingsValue(rawString));
properties.insert(name, fromJSLiteral(m_ui->propertiesTable->item(row, 1)->text()));
}
return properties;
}

View File

@@ -41,8 +41,6 @@
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
#include <qbs.h>
#include <android/androidconstants.h>
#include <ios/iosconstants.h>
#include <qtsupport/baseqtversion.h>

View File

@@ -30,7 +30,7 @@
#include "qbsinstallstep.h"
#include "qbsproject.h"
#include "qbsprojectmanagerconstants.h"
#include "qbsprojectmanagersettings.h"
#include "qbssettings.h"
#include <coreplugin/documentmanager.h>
@@ -167,7 +167,7 @@ bool QbsBuildConfiguration::fromMap(const QVariantMap &map)
return false;
if (m_configurationName->value().isEmpty()) { // pre-4.4 backwards compatibility
const QString profileName = QbsManager::profileForKit(target()->kit());
const QString profileName = QbsProfileManager::profileForKit(target()->kit());
const QString buildVariant = qbsConfiguration()
.value(QLatin1String(Constants::QBS_CONFIG_VARIANT_KEY)).toString();
m_configurationName->setValue(profileName + '-' + buildVariant);
@@ -328,9 +328,9 @@ QString QbsBuildConfiguration::equivalentCommandLine(const BuildStep *buildStep)
const QString buildDir = buildDirectory().toUserOutput();
commandLine.addArgs({"-d", buildDir});
commandLine.addArgs({"-f", buildStep->project()->projectFilePath().toUserOutput()});
if (QbsProjectManagerSettings::useCreatorSettingsDirForQbs()) {
if (QbsSettings::useCreatorSettingsDirForQbs()) {
commandLine.addArgs({"--settings-dir",
QDir::toNativeSeparators(QbsProjectManagerSettings::qbsSettingsBaseDir())});
QDir::toNativeSeparators(QbsSettings::qbsSettingsBaseDir())});
}
if (stepProxy.dryRun())
commandLine.addArg("--dry-run");
@@ -350,7 +350,7 @@ QString QbsBuildConfiguration::equivalentCommandLine(const BuildStep *buildStep)
if (jobCount > 0)
commandLine.addArgs({"--jobs", QString::number(jobCount)});
const QString profileName = QbsManager::profileForKit(buildStep->target()->kit());
const QString profileName = QbsProfileManager::profileForKit(buildStep->target()->kit());
const QString buildVariant = qbsConfiguration()
.value(QLatin1String(Constants::QBS_CONFIG_VARIANT_KEY)).toString();
commandLine.addArg("config:" + configurationName());

View File

@@ -29,7 +29,8 @@
#include "qbsparser.h"
#include "qbsproject.h"
#include "qbsprojectmanagerconstants.h"
#include "qbsprojectmanagersettings.h"
#include "qbssession.h"
#include "qbssettings.h"
#include <coreplugin/icore.h>
#include <coreplugin/variablechooser.h>
@@ -48,11 +49,12 @@
#include <QCheckBox>
#include <QComboBox>
#include <QFormLayout>
#include <QJsonArray>
#include <QJsonObject>
#include <QLabel>
#include <QPlainTextEdit>
#include <QSpinBox>
#include <qbs.h>
#include <QThread>
// --------------------------------------------------------------------
// Constants:
@@ -161,16 +163,14 @@ QbsBuildStep::QbsBuildStep(ProjectExplorer::BuildStepList *bsl) :
QbsBuildStep::~QbsBuildStep()
{
doCancel();
if (m_job) {
m_job->deleteLater();
m_job = nullptr;
}
if (m_session)
m_session->disconnect(this);
delete m_parser;
}
bool QbsBuildStep::init()
{
if (qbsBuildSystem()->isParsing() || m_job)
if (qbsBuildSystem()->isParsing() || m_session)
return false;
auto bc = static_cast<QbsBuildConfiguration *>(buildConfiguration());
@@ -201,6 +201,7 @@ void QbsBuildStep::doRun()
{
// We need a pre-build parsing step in order not to lose project file changes done
// right before building (but before the delay has elapsed).
m_parsingAfterBuild = false;
parseProject();
}
@@ -213,8 +214,8 @@ void QbsBuildStep::doCancel()
{
if (m_parsingProject)
qbsBuildSystem()->cancelParsing();
else if (m_job)
m_job->cancel();
else if (m_session)
m_session->cancelCurrentJob();
}
QVariantMap QbsBuildStep::qbsConfiguration(VariableHandling variableHandling) const
@@ -230,7 +231,7 @@ QVariantMap QbsBuildStep::qbsConfiguration(VariableHandling variableHandling) co
for (auto it = config.begin(), end = config.end(); it != end; ++it) {
const QString rawString = it.value().toString();
const QString expandedString = expander->expand(rawString);
it.value() = qbs::representationToSettingsValue(expandedString);
it.value() = expandedString;
}
}
return config;
@@ -252,26 +253,6 @@ void QbsBuildStep::setQbsConfiguration(const QVariantMap &config)
emit qbsConfigurationChanged();
}
bool QbsBuildStep::keepGoing() const
{
return m_qbsBuildOptions.keepGoing();
}
bool QbsBuildStep::showCommandLines() const
{
return m_qbsBuildOptions.echoMode() == qbs::CommandEchoModeCommandLine;
}
bool QbsBuildStep::install() const
{
return m_qbsBuildOptions.install();
}
bool QbsBuildStep::cleanInstallRoot() const
{
return m_qbsBuildOptions.removeExistingInstallation();
}
bool QbsBuildStep::hasCustomInstallRoot() const
{
return m_qbsConfiguration.contains(Constants::QBS_INSTALL_ROOT_KEY);
@@ -286,15 +267,14 @@ Utils::FilePath QbsBuildStep::installRoot(VariableHandling variableHandling) con
const QbsBuildConfiguration * const bc
= static_cast<QbsBuildConfiguration *>(buildConfiguration());
return bc->buildDirectory().pathAppended(bc->configurationName())
.pathAppended(qbs::InstallOptions::defaultInstallRoot());
return bc->buildDirectory().pathAppended(bc->configurationName()).pathAppended("install-root");
}
int QbsBuildStep::maxJobs() const
{
if (m_qbsBuildOptions.maxJobCount() > 0)
return m_qbsBuildOptions.maxJobCount();
return qbs::BuildOptions::defaultMaxJobCount();
if (m_maxJobCount > 0)
return m_maxJobCount;
return QThread::idealThreadCount();
}
static QString forceProbesKey() { return QLatin1String("Qbs.forceProbesKey"); }
@@ -306,15 +286,11 @@ bool QbsBuildStep::fromMap(const QVariantMap &map)
return false;
setQbsConfiguration(map.value(QBS_CONFIG).toMap());
m_qbsBuildOptions.setDryRun(map.value(QBS_DRY_RUN).toBool());
m_qbsBuildOptions.setKeepGoing(map.value(QBS_KEEP_GOING).toBool());
m_qbsBuildOptions.setMaxJobCount(map.value(QBS_MAXJOBCOUNT).toInt());
const bool showCommandLines = map.value(QBS_SHOWCOMMANDLINES).toBool();
m_qbsBuildOptions.setEchoMode(showCommandLines ? qbs::CommandEchoModeCommandLine
: qbs::CommandEchoModeSummary);
m_qbsBuildOptions.setInstall(map.value(QBS_INSTALL, true).toBool());
m_qbsBuildOptions.setRemoveExistingInstallation(map.value(QBS_CLEAN_INSTALL_ROOT)
.toBool());
m_keepGoing = map.value(QBS_KEEP_GOING).toBool();
m_maxJobCount = map.value(QBS_MAXJOBCOUNT).toInt();
m_showCommandLines = map.value(QBS_SHOWCOMMANDLINES).toBool();
m_install = map.value(QBS_INSTALL, true).toBool();
m_cleanInstallDir = map.value(QBS_CLEAN_INSTALL_ROOT).toBool();
m_forceProbes = map.value(forceProbesKey()).toBool();
m_enableQmlDebugging = map.value(enableQmlDebuggingKey()).toBool();
return true;
@@ -324,43 +300,47 @@ QVariantMap QbsBuildStep::toMap() const
{
QVariantMap map = ProjectExplorer::BuildStep::toMap();
map.insert(QBS_CONFIG, m_qbsConfiguration);
map.insert(QBS_DRY_RUN, m_qbsBuildOptions.dryRun());
map.insert(QBS_KEEP_GOING, m_qbsBuildOptions.keepGoing());
map.insert(QBS_MAXJOBCOUNT, m_qbsBuildOptions.maxJobCount());
map.insert(QBS_SHOWCOMMANDLINES,
m_qbsBuildOptions.echoMode() == qbs::CommandEchoModeCommandLine);
map.insert(QBS_INSTALL, m_qbsBuildOptions.install());
map.insert(QBS_CLEAN_INSTALL_ROOT,
m_qbsBuildOptions.removeExistingInstallation());
map.insert(QBS_KEEP_GOING, m_keepGoing);
map.insert(QBS_MAXJOBCOUNT, m_maxJobCount);
map.insert(QBS_SHOWCOMMANDLINES, m_showCommandLines);
map.insert(QBS_INSTALL, m_install);
map.insert(QBS_CLEAN_INSTALL_ROOT, m_cleanInstallDir);
map.insert(forceProbesKey(), m_forceProbes);
map.insert(enableQmlDebuggingKey(), m_enableQmlDebugging);
return map;
}
void QbsBuildStep::buildingDone(bool success)
void QbsBuildStep::buildingDone(const ErrorInfo &error)
{
m_lastWasSuccess = success;
// Report errors:
foreach (const qbs::ErrorItem &item, m_job->error().items())
createTaskAndOutput(ProjectExplorer::Task::Error, item.description(),
item.codeLocation().filePath(), item.codeLocation().line());
m_session->disconnect(this);
m_session = nullptr;
m_lastWasSuccess = !error.hasError();
for (const ErrorInfoItem &item : qAsConst(error.items)) {
createTaskAndOutput(
ProjectExplorer::Task::Error,
item.description,
item.filePath.toString(),
item.line);
}
// Building can uncover additional target artifacts.
qbsBuildSystem()->updateAfterBuild();
// The reparsing, if it is necessary, has to be done before finished() is emitted, as
// otherwise a potential additional build step could conflict with the parsing step.
if (qbsBuildSystem()->parsingScheduled())
if (qbsBuildSystem()->parsingScheduled()) {
m_parsingAfterBuild = true;
parseProject();
else
} else {
finish();
}
}
void QbsBuildStep::reparsingDone(bool success)
{
disconnect(target(), &Target::parsingFinished, this, &QbsBuildStep::reparsingDone);
m_parsingProject = false;
if (m_job) { // This was a scheduled reparsing after building.
if (m_parsingAfterBuild) {
finish();
} else if (!success) {
m_lastWasSuccess = false;
@@ -382,30 +362,31 @@ void QbsBuildStep::handleProgress(int value)
emit progress(value * 100 / m_maxProgress, m_currentTask);
}
void QbsBuildStep::handleCommandDescriptionReport(const QString &highlight, const QString &message)
void QbsBuildStep::handleCommandDescription(const QString &message)
{
Q_UNUSED(highlight)
emit addOutput(message, OutputFormat::Stdout);
}
void QbsBuildStep::handleProcessResultReport(const qbs::ProcessResult &result)
void QbsBuildStep::handleProcessResult(
const FilePath &executable,
const QStringList &arguments,
const FilePath &workingDir,
const QStringList &stdOut,
const QStringList &stdErr,
bool success)
{
bool hasOutput = !result.stdOut().isEmpty() || !result.stdErr().isEmpty();
if (result.success() && !hasOutput)
const bool hasOutput = !stdOut.isEmpty() || !stdErr.isEmpty();
if (success && !hasOutput)
return;
m_parser->setWorkingDirectory(result.workingDirectory());
QString commandline = result.executableFilePath() + ' '
+ Utils::QtcProcess::joinArgs(result.arguments());
emit addOutput(commandline, OutputFormat::Stdout);
foreach (const QString &line, result.stdErr()) {
m_parser->setWorkingDirectory(workingDir.toString());
emit addOutput(executable.toUserOutput() + ' ' + QtcProcess::joinArgs(arguments),
OutputFormat::Stdout);
for (const QString &line : stdErr) {
m_parser->stdError(line);
emit addOutput(line, OutputFormat::Stderr);
}
foreach (const QString &line, result.stdOut()) {
for (const QString &line : stdOut) {
m_parser->stdOutput(line);
emit addOutput(line, OutputFormat::Stdout);
}
@@ -449,17 +430,17 @@ QString QbsBuildStep::profile() const
void QbsBuildStep::setKeepGoing(bool kg)
{
if (m_qbsBuildOptions.keepGoing() == kg)
if (m_keepGoing == kg)
return;
m_qbsBuildOptions.setKeepGoing(kg);
m_keepGoing = kg;
emit qbsBuildOptionsChanged();
}
void QbsBuildStep::setMaxJobs(int jobcount)
{
if (m_qbsBuildOptions.maxJobCount() == jobcount)
if (m_maxJobCount == jobcount)
return;
m_qbsBuildOptions.setMaxJobCount(jobcount);
m_maxJobCount = jobcount;
emit qbsBuildOptionsChanged();
}
@@ -467,24 +448,23 @@ void QbsBuildStep::setShowCommandLines(bool show)
{
if (showCommandLines() == show)
return;
m_qbsBuildOptions.setEchoMode(show ? qbs::CommandEchoModeCommandLine
: qbs::CommandEchoModeSummary);
m_showCommandLines = show;
emit qbsBuildOptionsChanged();
}
void QbsBuildStep::setInstall(bool install)
{
if (m_qbsBuildOptions.install() == install)
if (m_install == install)
return;
m_qbsBuildOptions.setInstall(install);
m_install = install;
emit qbsBuildOptionsChanged();
}
void QbsBuildStep::setCleanInstallRoot(bool clean)
{
if (m_qbsBuildOptions.removeExistingInstallation() == clean)
if (m_cleanInstallDir == clean)
return;
m_qbsBuildOptions.setRemoveExistingInstallation(clean);
m_cleanInstallDir = clean;
emit qbsBuildOptionsChanged();
}
@@ -497,41 +477,49 @@ void QbsBuildStep::parseProject()
void QbsBuildStep::build()
{
qbs::BuildOptions options(m_qbsBuildOptions);
options.setChangedFiles(m_changedFiles);
options.setFilesToConsider(m_changedFiles);
options.setActiveFileTags(m_activeFileTags);
options.setLogElapsedTime(!qEnvironmentVariableIsEmpty(Constants::QBS_PROFILING_ENV));
QString error;
m_job = qbsBuildSystem()->build(options, m_products, error);
if (!m_job) {
emit addOutput(error, OutputFormat::ErrorMessage);
m_session = qbsBuildSystem()->session();
if (!m_session) {
emit addOutput(tr("No qbs session exists for this target."), OutputFormat::ErrorMessage);
emit finished(false);
return;
}
QJsonObject request;
request.insert("type", "build-project");
request.insert("max-job-count", maxJobs());
request.insert("keep-going", keepGoing());
request.insert("command-echo-mode", showCommandLines() ? "command-line" : "summary");
request.insert("install", install());
QbsSession::insertRequestedModuleProperties(request);
request.insert("clean-install-root", cleanInstallRoot());
if (!m_products.isEmpty())
request.insert("products", QJsonArray::fromStringList(m_products));
if (!m_changedFiles.isEmpty()) {
const auto changedFilesArray = QJsonArray::fromStringList(m_changedFiles);
request.insert("changed-files", changedFilesArray);
request.insert("files-to-consider", changedFilesArray);
}
if (!m_activeFileTags.isEmpty())
request.insert("active-file-tags", QJsonArray::fromStringList(m_activeFileTags));
request.insert("data-mode", "only-if-changed");
m_session->sendRequest(request);
m_maxProgress = 0;
connect(m_job, &qbs::AbstractJob::finished, this, &QbsBuildStep::buildingDone);
connect(m_job, &qbs::AbstractJob::taskStarted,
this, &QbsBuildStep::handleTaskStarted);
connect(m_job, &qbs::AbstractJob::taskProgress,
this, &QbsBuildStep::handleProgress);
connect(m_job, &qbs::BuildJob::reportCommandDescription,
this, &QbsBuildStep::handleCommandDescriptionReport);
connect(m_job, &qbs::BuildJob::reportProcessResult,
this, &QbsBuildStep::handleProcessResultReport);
connect(m_session, &QbsSession::projectBuilt, this, &QbsBuildStep::buildingDone);
connect(m_session, &QbsSession::taskStarted, this, &QbsBuildStep::handleTaskStarted);
connect(m_session, &QbsSession::taskProgress, this, &QbsBuildStep::handleProgress);
connect(m_session, &QbsSession::commandDescription,
this, &QbsBuildStep::handleCommandDescription);
connect(m_session, &QbsSession::processResult, this, &QbsBuildStep::handleProcessResult);
connect(m_session, &QbsSession::errorOccurred, this, [this] {
buildingDone(ErrorInfo(tr("Build canceled: Qbs session failed.")));
});
}
void QbsBuildStep::finish()
{
m_session = nullptr;
emit finished(m_lastWasSuccess);
if (m_job) {
m_job->deleteLater();
m_job = nullptr;
}
}
// --------------------------------------------------------------------
@@ -548,7 +536,7 @@ QbsBuildStepConfigWidget::QbsBuildStepConfigWidget(QbsBuildStep *step) :
this, &QbsBuildStepConfigWidget::updateState);
connect(step, &QbsBuildStep::qbsBuildOptionsChanged,
this, &QbsBuildStepConfigWidget::updateState);
connect(&QbsProjectManagerSettings::instance(), &QbsProjectManagerSettings::settingsBaseChanged,
connect(&QbsSettings::instance(), &QbsSettings::settingsChanged,
this, &QbsBuildStepConfigWidget::updateState);
connect(step->buildConfiguration(), &BuildConfiguration::buildDirectoryChanged,
this, &QbsBuildStepConfigWidget::updateState);

View File

@@ -30,13 +30,13 @@
#include <projectexplorer/buildstep.h>
#include <projectexplorer/task.h>
#include <qbs.h>
namespace Utils { class FancyLineEdit; }
namespace QbsProjectManager {
namespace Internal {
class ErrorInfo;
class QbsProject;
class QbsSession;
class QbsBuildStepConfigWidget;
@@ -61,10 +61,10 @@ public:
QVariantMap qbsConfiguration(VariableHandling variableHandling) const;
void setQbsConfiguration(const QVariantMap &config);
bool keepGoing() const;
bool showCommandLines() const;
bool install() const;
bool cleanInstallRoot() const;
bool keepGoing() const { return m_keepGoing; }
bool showCommandLines() const { return m_showCommandLines; }
bool install() const { return m_install; }
bool cleanInstallRoot() const { return m_cleanInstallDir; }
bool hasCustomInstallRoot() const;
Utils::FilePath installRoot(VariableHandling variableHandling = ExpandVariables) const;
int maxJobs() const;
@@ -93,12 +93,18 @@ private:
bool fromMap(const QVariantMap &map) override;
QVariantMap toMap() const override;
void buildingDone(bool success);
void buildingDone(const ErrorInfo &error);
void reparsingDone(bool success);
void handleTaskStarted(const QString &desciption, int max);
void handleProgress(int value);
void handleCommandDescriptionReport(const QString &highlight, const QString &message);
void handleProcessResultReport(const qbs::ProcessResult &result);
void handleCommandDescription(const QString &message);
void handleProcessResult(
const Utils::FilePath &executable,
const QStringList &arguments,
const Utils::FilePath &workingDir,
const QStringList &stdOut,
const QStringList &stdErr,
bool success);
void createTaskAndOutput(ProjectExplorer::Task::TaskType type,
const QString &message, const QString &file, int line);
@@ -117,7 +123,11 @@ private:
void finish();
QVariantMap m_qbsConfiguration;
qbs::BuildOptions m_qbsBuildOptions;
int m_maxJobCount = 0;
bool m_keepGoing = false;
bool m_showCommandLines = false;
bool m_install = true;
bool m_cleanInstallDir = false;
bool m_forceProbes = false;
bool m_enableQmlDebugging;
@@ -126,12 +136,14 @@ private:
QStringList m_activeFileTags;
QStringList m_products;
qbs::BuildJob *m_job = nullptr;
QbsSession *m_session = nullptr;
QString m_currentTask;
int m_maxProgress;
bool m_lastWasSuccess;
ProjectExplorer::IOutputParser *m_parser = nullptr;
bool m_parsingProject = false;
bool m_parsingAfterBuild = false;
friend class QbsBuildStepConfigWidget;
};

View File

@@ -28,6 +28,7 @@
#include "qbsbuildconfiguration.h"
#include "qbsproject.h"
#include "qbsprojectmanagerconstants.h"
#include "qbssession.h"
#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/kit.h>
@@ -36,6 +37,9 @@
#include <projectexplorer/target.h>
#include <utils/qtcassert.h>
#include <QJsonArray>
#include <QJsonObject>
using namespace ProjectExplorer;
namespace QbsProjectManager {
@@ -73,66 +77,60 @@ QbsCleanStep::QbsCleanStep(ProjectExplorer::BuildStepList *bsl) :
QbsCleanStep::~QbsCleanStep()
{
doCancel();
if (m_job) {
m_job->deleteLater();
m_job = nullptr;
}
if (m_session)
m_session->disconnect(this);
}
bool QbsCleanStep::init()
{
if (buildSystem()->isParsing() || m_job)
if (buildSystem()->isParsing() || m_session)
return false;
auto bc = static_cast<QbsBuildConfiguration *>(buildConfiguration());
const auto bc = static_cast<QbsBuildConfiguration *>(buildConfiguration());
if (!bc)
return false;
m_products = bc->products();
return true;
}
void QbsCleanStep::doRun()
{
qbs::CleanOptions options;
options.setDryRun(m_dryRunAspect->value());
options.setKeepGoing(m_keepGoingAspect->value());
QString error;
m_job = qbsBuildSystem()->clean(options, m_products, error);
if (!m_job) {
emit addOutput(error, OutputFormat::ErrorMessage);
m_session = static_cast<QbsBuildSystem*>(buildSystem())->session();
if (!m_session) {
emit addOutput(tr("No qbs session exists for this target."), OutputFormat::ErrorMessage);
emit finished(false);
return;
}
QJsonObject request;
request.insert("type", "clean-project");
if (!m_products.isEmpty())
request.insert("products", QJsonArray::fromStringList(m_products));
request.insert("dry-run", m_dryRunAspect->value());
request.insert("keep-going", m_keepGoingAspect->value());
m_session->sendRequest(request);
m_maxProgress = 0;
connect(m_job, &qbs::AbstractJob::finished, this, &QbsCleanStep::cleaningDone);
connect(m_job, &qbs::AbstractJob::taskStarted,
this, &QbsCleanStep::handleTaskStarted);
connect(m_job, &qbs::AbstractJob::taskProgress,
this, &QbsCleanStep::handleProgress);
connect(m_session, &QbsSession::projectCleaned, this, &QbsCleanStep::cleaningDone);
connect(m_session, &QbsSession::taskStarted, this, &QbsCleanStep::handleTaskStarted);
connect(m_session, &QbsSession::taskProgress, this, &QbsCleanStep::handleProgress);
connect(m_session, &QbsSession::errorOccurred, this, [this] {
cleaningDone(ErrorInfo(tr("Cleaning canceled: Qbs session failed.")));
});
}
void QbsCleanStep::doCancel()
{
if (m_job)
m_job->cancel();
if (m_session)
m_session->cancelCurrentJob();
}
void QbsCleanStep::cleaningDone(bool success)
void QbsCleanStep::cleaningDone(const ErrorInfo &error)
{
// Report errors:
foreach (const qbs::ErrorItem &item, m_job->error().items()) {
createTaskAndOutput(ProjectExplorer::Task::Error, item.description(),
item.codeLocation().filePath(), item.codeLocation().line());
}
m_session->disconnect(this);
m_session = nullptr;
emit finished(success);
m_job->deleteLater();
m_job = nullptr;
for (const ErrorInfoItem &item : error.items)
createTaskAndOutput(Task::Error, item.description, item.filePath.toString(), item.line);
emit finished(!error.hasError());
}
void QbsCleanStep::handleTaskStarted(const QString &desciption, int max)
@@ -149,8 +147,7 @@ void QbsCleanStep::handleProgress(int value)
void QbsCleanStep::createTaskAndOutput(ProjectExplorer::Task::TaskType type, const QString &message, const QString &file, int line)
{
ProjectExplorer::Task task = ProjectExplorer::Task(type, message,
Utils::FilePath::fromString(file), line,
Task task(type, message, Utils::FilePath::fromString(file), line,
ProjectExplorer::Constants::TASK_CATEGORY_COMPILE);
emit addTask(task, 1);
emit addOutput(message, OutputFormat::Stdout);

View File

@@ -31,10 +31,10 @@
#include <projectexplorer/projectconfigurationaspects.h>
#include <projectexplorer/task.h>
#include <qbs.h>
namespace QbsProjectManager {
namespace Internal {
class ErrorInfo;
class QbsSession;
class QbsCleanStep : public ProjectExplorer::BuildStep
{
@@ -52,7 +52,7 @@ private:
void doRun() override;
void doCancel() override;
void cleaningDone(bool success);
void cleaningDone(const ErrorInfo &error);
void handleTaskStarted(const QString &desciption, int max);
void handleProgress(int value);
@@ -65,8 +65,7 @@ private:
QbsBuildSystem *qbsBuildSystem() const;
QStringList m_products;
qbs::CleanJob *m_job = nullptr;
QbsSession *m_session = nullptr;
QString m_description;
int m_maxProgress;
bool m_showCompilerOutput = true;

View File

@@ -29,6 +29,7 @@
#include "qbsbuildstep.h"
#include "qbsproject.h"
#include "qbsprojectmanagerconstants.h"
#include "qbssession.h"
#include <coreplugin/icore.h>
#include <projectexplorer/buildsteplist.h>
@@ -41,6 +42,7 @@
#include <QCheckBox>
#include <QFileInfo>
#include <QFormLayout>
#include <QJsonObject>
#include <QLabel>
#include <QPlainTextEdit>
#include <QSpacerItem>
@@ -91,45 +93,45 @@ QbsInstallStep::QbsInstallStep(ProjectExplorer::BuildStepList *bsl) :
setDisplayName(tr("Qbs Install"));
const QbsBuildConfiguration * const bc = buildConfig();
connect(bc, &QbsBuildConfiguration::qbsConfigurationChanged,
this, &QbsInstallStep::handleBuildConfigChanged);
connect(bc, &QbsBuildConfiguration::qbsConfigurationChanged, this, &QbsInstallStep::changed);
if (bc->qbsStep()) {
connect(bc->qbsStep(), &QbsBuildStep::qbsBuildOptionsChanged,
this, &QbsInstallStep::handleBuildConfigChanged);
this, &QbsInstallStep::changed);
}
}
QbsInstallStep::~QbsInstallStep()
{
doCancel();
if (m_job)
m_job->deleteLater();
m_job = nullptr;
if (m_session)
m_session->disconnect(this);
}
bool QbsInstallStep::init()
{
QTC_ASSERT(!buildConfiguration()->buildSystem()->isParsing() && !m_job, return false);
QTC_ASSERT(!buildConfiguration()->buildSystem()->isParsing() && !m_session, return false);
return true;
}
void QbsInstallStep::doRun()
{
auto bs = static_cast<QbsBuildSystem *>(buildSystem());
m_job = bs->install(m_qbsInstallOptions);
m_session = static_cast<QbsBuildSystem *>(buildSystem())->session();
if (!m_job) {
emit finished(false);
return;
}
QJsonObject request;
request.insert("type", "install");
request.insert("install-root", installRoot());
request.insert("clean-install-root", m_cleanInstallRoot);
request.insert("keep-going", m_keepGoing);
request.insert("dry-run", m_dryRun);
m_session->sendRequest(request);
m_maxProgress = 0;
connect(m_job, &qbs::AbstractJob::finished, this, &QbsInstallStep::installDone);
connect(m_job, &qbs::AbstractJob::taskStarted,
this, &QbsInstallStep::handleTaskStarted);
connect(m_job, &qbs::AbstractJob::taskProgress,
this, &QbsInstallStep::handleProgress);
connect(m_session, &QbsSession::projectInstalled, this, &QbsInstallStep::installDone);
connect(m_session, &QbsSession::taskStarted, this, &QbsInstallStep::handleTaskStarted);
connect(m_session, &QbsSession::taskProgress, this, &QbsInstallStep::handleProgress);
connect(m_session, &QbsSession::errorOccurred, this, [this] {
installDone(ErrorInfo(tr("Installing canceled: Qbs session failed.")));
});
}
ProjectExplorer::BuildStepConfigWidget *QbsInstallStep::createConfigWidget()
@@ -139,8 +141,8 @@ ProjectExplorer::BuildStepConfigWidget *QbsInstallStep::createConfigWidget()
void QbsInstallStep::doCancel()
{
if (m_job)
m_job->cancel();
if (m_session)
m_session->cancelCurrentJob();
}
QString QbsInstallStep::installRoot() const
@@ -149,21 +151,6 @@ QString QbsInstallStep::installRoot() const
return bs ? bs->installRoot().toString() : QString();
}
bool QbsInstallStep::removeFirst() const
{
return m_qbsInstallOptions.removeExistingInstallation();
}
bool QbsInstallStep::dryRun() const
{
return m_qbsInstallOptions.dryRun();
}
bool QbsInstallStep::keepGoing() const
{
return m_qbsInstallOptions.keepGoing();
}
const QbsBuildConfiguration *QbsInstallStep::buildConfig() const
{
return static_cast<QbsBuildConfiguration *>(buildConfiguration());
@@ -174,40 +161,30 @@ bool QbsInstallStep::fromMap(const QVariantMap &map)
if (!ProjectExplorer::BuildStep::fromMap(map))
return false;
m_qbsInstallOptions.setInstallRoot(installRoot());
m_qbsInstallOptions.setRemoveExistingInstallation(
map.value(QLatin1String(QBS_REMOVE_FIRST), false).toBool());
m_qbsInstallOptions.setDryRun(map.value(QLatin1String(QBS_DRY_RUN), false).toBool());
m_qbsInstallOptions.setKeepGoing(map.value(QLatin1String(QBS_KEEP_GOING), false).toBool());
m_cleanInstallRoot = map.value(QBS_REMOVE_FIRST, false).toBool();
m_dryRun = map.value(QBS_DRY_RUN, false).toBool();
m_keepGoing = map.value(QBS_KEEP_GOING, false).toBool();
return true;
}
QVariantMap QbsInstallStep::toMap() const
{
QVariantMap map = ProjectExplorer::BuildStep::toMap();
map.insert(QLatin1String(QBS_REMOVE_FIRST), m_qbsInstallOptions.removeExistingInstallation());
map.insert(QLatin1String(QBS_DRY_RUN), m_qbsInstallOptions.dryRun());
map.insert(QLatin1String(QBS_KEEP_GOING), m_qbsInstallOptions.keepGoing());
map.insert(QBS_REMOVE_FIRST, m_cleanInstallRoot);
map.insert(QBS_DRY_RUN, m_dryRun);
map.insert(QBS_KEEP_GOING, m_keepGoing);
return map;
}
qbs::InstallOptions QbsInstallStep::installOptions() const
void QbsInstallStep::installDone(const ErrorInfo &error)
{
return m_qbsInstallOptions;
}
m_session->disconnect(this);
m_session = nullptr;
void QbsInstallStep::installDone(bool success)
{
// Report errors:
foreach (const qbs::ErrorItem &item, m_job->error().items()) {
createTaskAndOutput(ProjectExplorer::Task::Error, item.description(),
item.codeLocation().filePath(), item.codeLocation().line());
}
for (const ErrorInfoItem &item : error.items)
createTaskAndOutput(Task::Error, item.description, item.filePath.toString(), item.line);
emit finished(success);
m_job->deleteLater();
m_job = nullptr;
emit finished(!error.hasError());
}
void QbsInstallStep::handleTaskStarted(const QString &desciption, int max)
@@ -234,31 +211,25 @@ void QbsInstallStep::createTaskAndOutput(ProjectExplorer::Task::TaskType type,
void QbsInstallStep::setRemoveFirst(bool rf)
{
if (m_qbsInstallOptions.removeExistingInstallation() == rf)
if (m_cleanInstallRoot == rf)
return;
m_qbsInstallOptions.setRemoveExistingInstallation(rf);
m_cleanInstallRoot = rf;
emit changed();
}
void QbsInstallStep::setDryRun(bool dr)
{
if (m_qbsInstallOptions.dryRun() == dr)
if (m_dryRun == dr)
return;
m_qbsInstallOptions.setDryRun(dr);
m_dryRun = dr;
emit changed();
}
void QbsInstallStep::setKeepGoing(bool kg)
{
if (m_qbsInstallOptions.keepGoing() == kg)
if (m_keepGoing == kg)
return;
m_qbsInstallOptions.setKeepGoing(kg);
emit changed();
}
void QbsInstallStep::handleBuildConfigChanged()
{
m_qbsInstallOptions.setInstallRoot(installRoot());
m_keepGoing = kg;
emit changed();
}

View File

@@ -26,14 +26,15 @@
#pragma once
#include "qbsbuildconfiguration.h"
#include "qbssession.h"
#include <projectexplorer/buildstep.h>
#include <projectexplorer/task.h>
#include <qbs.h>
namespace QbsProjectManager {
namespace Internal {
class ErrorInfo;
class QbsSession;
class QbsInstallStep : public ProjectExplorer::BuildStep
{
@@ -43,11 +44,10 @@ public:
explicit QbsInstallStep(ProjectExplorer::BuildStepList *bsl);
~QbsInstallStep() override;
qbs::InstallOptions installOptions() const;
QString installRoot() const;
bool removeFirst() const;
bool dryRun() const;
bool keepGoing() const;
bool removeFirst() const { return m_cleanInstallRoot; }
bool dryRun() const { return m_dryRun; }
bool keepGoing() const { return m_keepGoing; }
signals:
void changed();
@@ -61,7 +61,7 @@ private:
QVariantMap toMap() const override;
const QbsBuildConfiguration *buildConfig() const;
void installDone(bool success);
void installDone(const ErrorInfo &error);
void handleTaskStarted(const QString &desciption, int max);
void handleProgress(int value);
@@ -71,14 +71,14 @@ private:
void setRemoveFirst(bool rf);
void setDryRun(bool dr);
void setKeepGoing(bool kg);
void handleBuildConfigChanged();
qbs::InstallOptions m_qbsInstallOptions;
bool m_cleanInstallRoot = false;
bool m_dryRun = false;
bool m_keepGoing = false;
qbs::InstallJob *m_job = nullptr;
QbsSession *m_session = nullptr;
QString m_description;
int m_maxProgress;
bool m_showCompilerOutput = true;
ProjectExplorer::IOutputParser *m_parser = nullptr;
friend class QbsInstallStepConfigWidget;

View File

@@ -26,14 +26,13 @@
#include "qbskitinformation.h"
#include "customqbspropertiesdialog.h"
#include "qbsprofilemanager.h"
#include <projectexplorer/kitmanager.h>
#include <utils/elidinglabel.h>
#include <utils/qtcassert.h>
#include <qbs.h>
#include <QPushButton>
using namespace ProjectExplorer;
@@ -85,7 +84,7 @@ QString QbsKitAspect::representation(const Kit *kit)
for (auto it = props.begin(); it != props.end(); ++it) {
if (!repr.isEmpty())
repr += ' ';
repr += it.key() + ':' + qbs::settingsValueToRepresentation(it.value());
repr += it.key() + ':' + toJSLiteral(it.value());
}
return repr;
}

View File

@@ -1,94 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qbslogsink.h"
#include <qbs.h>
#include <coreplugin/messagemanager.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/taskhub.h>
#include <utils/fileutils.h>
#include <QCoreApplication>
#include <QMutexLocker>
#include <QTimer>
using namespace ProjectExplorer;
namespace QbsProjectManager {
namespace Internal {
// --------------------------------------------------------------------
// QbsLogSink:
// --------------------------------------------------------------------
QbsLogSink::QbsLogSink(QObject *parent) : QObject(parent)
{
connect(this, &QbsLogSink::newTask,
TaskHub::instance(),
[](const Task &task) { TaskHub::addTask(task); }, Qt::QueuedConnection);
}
void QbsLogSink::sendMessages()
{
QStringList toSend;
{
QMutexLocker l(&m_mutex);
toSend = m_messages;
m_messages.clear();
}
foreach (const QString &msg, toSend)
Core::MessageManager::write(msg, Core::MessageManager::Silent);
}
void QbsLogSink::doPrintWarning(const qbs::ErrorInfo &warning)
{
foreach (const qbs::ErrorItem &item, warning.items())
emit newTask(Task(Task::Warning,
item.description(),
Utils::FilePath::fromString(item.codeLocation().filePath()),
item.codeLocation().line(),
Constants::TASK_CATEGORY_BUILDSYSTEM));
}
void QbsLogSink::doPrintMessage(qbs::LoggerLevel level, const QString &message, const QString &tag)
{
Q_UNUSED(tag)
{
QMutexLocker l(&m_mutex);
if (level <= qbs::LoggerWarning) {
doPrintWarning(qbs::ErrorInfo(message));
return;
}
m_messages.append(qbs::logLevelTag(level) + message);
}
QMetaObject::invokeMethod(this, "sendMessages", Qt::QueuedConnection);
}
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -1,60 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <projectexplorer/task.h>
#include <qbs.h>
#include <QMutex>
#include <QObject>
#include <QStringList>
namespace QbsProjectManager {
namespace Internal {
class QbsLogSink : public QObject, public qbs::ILogSink
{
Q_OBJECT
public:
QbsLogSink(QObject *parent = nullptr);
signals:
void newTask(const ProjectExplorer::Task &task);
private:
Q_INVOKABLE void sendMessages();
void doPrintWarning(const qbs::ErrorInfo &warning) override;
void doPrintMessage(qbs::LoggerLevel level, const QString &message,
const QString &tag) override;
QStringList m_messages;
QMutex m_mutex;
};
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -29,6 +29,7 @@
#include "qbsproject.h"
#include "qbsprojectmanagerconstants.h"
#include "qbsprojectmanagerplugin.h"
#include "qbssession.h"
#include <android/androidconstants.h>
#include <coreplugin/fileiconprovider.h>
@@ -44,8 +45,10 @@
#include <QtDebug>
#include <QDir>
#include <QIcon>
#include <QJsonArray>
using namespace ProjectExplorer;
using namespace Utils;
// ----------------------------------------------------------------------
// Helpers:
@@ -54,147 +57,26 @@ using namespace ProjectExplorer;
namespace QbsProjectManager {
namespace Internal {
class FileTreeNode {
public:
explicit FileTreeNode(const QString &n = QString(), FileTreeNode *p = nullptr, bool f = false) :
parent(p), name(n), m_isFile(f)
const QbsProductNode *parentQbsProductNode(const ProjectExplorer::Node *node)
{
if (p)
p->children.append(this);
for (; node; node = node->parentFolderNode()) {
const auto prdNode = dynamic_cast<const QbsProductNode *>(node);
if (prdNode)
return prdNode;
}
~FileTreeNode()
{
qDeleteAll(children);
}
FileTreeNode *addPart(const QString &n, bool isFile)
{
foreach (FileTreeNode *c, children) {
if (c->name == n)
return c;
}
return new FileTreeNode(n, this, isFile);
}
bool isFile() const { return m_isFile; }
static FileTreeNode *moveChildrenUp(FileTreeNode *node)
{
QTC_ASSERT(node, return nullptr);
FileTreeNode *newParent = node->parent;
if (!newParent)
return nullptr;
// disconnect node and parent:
node->parent = nullptr;
newParent->children.removeOne(node);
foreach (FileTreeNode *c, node->children) {
// update path, make sure there will be no / before "C:" on windows:
if (Utils::HostOsInfo::isWindowsHost() && node->name.isEmpty())
c->name = node->name;
else
c->name = node->name + QLatin1Char('/') + c->name;
newParent->children.append(c);
c->parent = newParent;
}
// Delete node
node->children.clear();
delete node;
return newParent;
}
// Moves the children of the node pointing to basedir to the root of the tree.
static void reorder(FileTreeNode *node, const QString &basedir)
{
QTC_CHECK(!basedir.isEmpty());
QString prefix = basedir;
if (basedir.startsWith(QLatin1Char('/')))
prefix = basedir.mid(1);
prefix.append(QLatin1Char('/'));
if (node->path() == basedir) {
// Find root node:
FileTreeNode *root = node;
while (root->parent)
root = root->parent;
foreach (FileTreeNode *c, node->children) {
// Update children names by prepending basedir
c->name = prefix + c->name;
// Update parent information:
c->parent = root;
root->children.append(c);
}
// Clean up node:
node->children.clear();
node->parent->children.removeOne(node);
node->parent = nullptr;
delete node;
return;
}
foreach (FileTreeNode *n, node->children)
reorder(n, basedir);
}
static void simplify(FileTreeNode *node)
{
foreach (FileTreeNode *c, node->children)
simplify(c);
if (!node->parent)
return;
if (node->children.isEmpty() && !node->isFile()) {
// Clean up empty folder nodes:
node->parent->children.removeOne(node);
node->parent = nullptr;
delete node;
} else if (node->children.count() == 1 && !node->children.at(0)->isFile()) {
// Compact folder nodes with one child only:
moveChildrenUp(node);
}
}
QString path() const
{
QString p = name;
FileTreeNode *node = parent;
while (node) {
if (!Utils::HostOsInfo::isWindowsHost() || !node->name.isEmpty())
p = node->name + QLatin1Char('/') + p;
node = node->parent;
}
return p;
}
QList<FileTreeNode *> children;
FileTreeNode *parent;
QString name;
bool m_isFile;
};
// --------------------------------------------------------------------
// QbsGroupNode:
// --------------------------------------------------------------------
QbsGroupNode::QbsGroupNode(const qbs::GroupData &grp, const QString &productPath) :
ProjectNode(Utils::FilePath())
QbsGroupNode::QbsGroupNode(const QJsonObject &grp) : ProjectNode(FilePath()), m_groupData(grp)
{
static QIcon groupIcon = QIcon(QString(Constants::QBS_GROUP_ICON));
setIcon(groupIcon);
m_productPath = productPath;
m_qbsGroupData = grp;
setDisplayName(grp.value("name").toString());
setEnabled(grp.value("is-enabled").toBool());
}
FolderNode::AddNewInformation QbsGroupNode::addNewInformation(const QStringList &files,
@@ -208,8 +90,15 @@ FolderNode::AddNewInformation QbsGroupNode::addNewInformation(const QStringList
QVariant QbsGroupNode::data(Core::Id role) const
{
if (role == ProjectExplorer::Constants::QT_KEYWORDS_ENABLED)
return m_qbsGroupData.properties().getModuleProperty("Qt.core", "enableKeywords");
if (role == ProjectExplorer::Constants::QT_KEYWORDS_ENABLED) {
QJsonObject modProps = m_groupData.value("module-properties").toObject();
if (modProps.isEmpty()) {
const QbsProductNode * const prdNode = parentQbsProductNode(this);
QTC_ASSERT(prdNode, return QVariant());
modProps = prdNode->productData().value("module-properties").toObject();
}
return modProps.value("Qt.core.enableKeywords").toBool();
}
return QVariant();
}
@@ -217,106 +106,104 @@ QVariant QbsGroupNode::data(Core::Id role) const
// QbsProductNode:
// --------------------------------------------------------------------
QbsProductNode::QbsProductNode(const qbs::ProductData &prd) :
ProjectNode(Utils::FilePath::fromString(prd.location().filePath())),
m_qbsProductData(prd)
QbsProductNode::QbsProductNode(const QJsonObject &prd) : ProjectNode(FilePath()), m_productData(prd)
{
static QIcon productIcon = Core::FileIconProvider::directoryIcon(Constants::QBS_PRODUCT_OVERLAY_ICON);
static QIcon productIcon = Core::FileIconProvider::directoryIcon(
Constants::QBS_PRODUCT_OVERLAY_ICON);
setIcon(productIcon);
if (m_qbsProductData.isRunnable()) {
if (prd.value("is-runnable").toBool()) {
setProductType(ProductType::App);
} else if (m_qbsProductData.type().contains("dynamiclibrary")
|| m_qbsProductData.type().contains("staticlibrary")) {
setProductType(ProductType::Lib);
} else {
const QJsonArray type = prd.value("type").toArray();
if (type.contains("dynamiclibrary") || type.contains("staticlibrary"))
setProductType(ProductType::Lib);
else
setProductType(ProductType::Other);
}
setEnabled(prd.value("is-enabled").toBool());
setDisplayName(prd.value("full-display-name").toString());
}
void QbsProductNode::build()
{
QbsProjectManagerPlugin::buildNamedProduct(static_cast<QbsProject *>(getProject()),
QbsProject::uniqueProductName(qbsProductData()));
m_productData.value("full-display-name").toString());
}
QStringList QbsProductNode::targetApplications() const
{
return QStringList{m_qbsProductData.targetExecutable()};
return QStringList{m_productData.value("target-executable").toString()};
}
QString QbsProductNode::buildKey() const
{
return QbsProject::uniqueProductName(m_qbsProductData);
return m_productData.value("full-display-name").toString();
}
QVariant QbsProductNode::data(Core::Id role) const
{
if (role == Android::Constants::AndroidDeploySettingsFile) {
for (const auto &artifact : m_qbsProductData.generatedArtifacts()) {
if (artifact.fileTags().contains("qt_androiddeployqt_input"))
return artifact.filePath();
for (const auto &a : m_productData.value("generated-artifacts").toArray()) {
const QJsonObject artifact = a.toObject();
if (artifact.value("file-tags").toArray().contains("qt_androiddeployqt_input"))
return artifact.value("file-path").toString();
}
return {};
}
if (role == Android::Constants::AndroidSoLibPath) {
QStringList ret{m_qbsProductData.buildDirectory()};
for (const auto &artifact : m_qbsProductData.generatedArtifacts()) {
if (artifact.fileTags().contains("dynamiclibrary")) {
ret << QFileInfo(artifact.filePath()).path();
}
}
QStringList ret{m_productData.value("build-directory").toString()};
forAllArtifacts(m_productData, ArtifactType::Generated, [&ret](const QJsonObject &artifact) {
if (artifact.value("file-tags").toArray().contains("dynamiclibrary"))
ret << QFileInfo(artifact.value("file-path").toString()).path();
});
ret.removeDuplicates();
return ret;
}
if (role == Android::Constants::AndroidManifest) {
for (const auto &artifact : m_qbsProductData.generatedArtifacts()) {
if (artifact.fileTags().contains("android.manifest_final"))
return artifact.filePath();
for (const auto &a : m_productData.value("generated-artifacts").toArray()) {
const QJsonObject artifact = a.toObject();
if (artifact.value("file-tags").toArray().contains("android.manifest_final"))
return artifact.value("file-path").toString();
}
return {};
}
if (role == Android::Constants::AndroidApk)
return m_qbsProductData.targetExecutable();
return m_productData.value("target-executable").toString();
if (role == ProjectExplorer::Constants::QT_KEYWORDS_ENABLED)
return m_qbsProductData.moduleProperties().getModuleProperty("Qt.core", "enableKeywords");
return m_productData.value("module-properties").toObject()
.value("Qt.core.enableKeywords").toBool();
return {};
}
QJsonObject QbsProductNode::mainGroup() const
{
for (const QJsonValue &g : m_productData.value("groups").toArray()) {
const QJsonObject grp = g.toObject();
if (grp.value("name") == m_productData.value("name")
&& grp.value("location") == m_productData.value("location")) {
return grp;
}
}
return {};
}
// --------------------------------------------------------------------
// QbsProjectNode:
// --------------------------------------------------------------------
QbsProjectNode::QbsProjectNode(const Utils::FilePath &projectDirectory) :
ProjectNode(projectDirectory)
QbsProjectNode::QbsProjectNode(const QJsonObject &projectData)
: ProjectNode(FilePath()), m_projectData(projectData)
{
static QIcon projectIcon = Core::FileIconProvider::directoryIcon(ProjectExplorer::Constants::FILEOVERLAY_QT);
static QIcon projectIcon = Core::FileIconProvider::directoryIcon(
ProjectExplorer::Constants::FILEOVERLAY_QT);
setIcon(projectIcon);
setDisplayName(projectData.value("name").toString());
}
Project *QbsProjectNode::project() const
{
return static_cast<QbsProjectNode *>(parentFolderNode())->project();
}
void QbsProjectNode::setProjectData(const qbs::ProjectData &data)
{
m_projectData = data;
}
// --------------------------------------------------------------------
// QbsRootProjectNode:
// --------------------------------------------------------------------
QbsRootProjectNode::QbsRootProjectNode(Project *project) :
QbsProjectNode(project->projectDirectory()),
m_project(project)
{ }
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -28,91 +28,60 @@
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/projectnodes.h>
#include <qbs.h>
#include <QJsonObject>
namespace QbsProjectManager {
namespace Internal {
class QbsNodeTreeBuilder;
class QbsProject;
class QbsBuildSystem;
// --------------------------------------------------------------------
// QbsGroupNode:
// --------------------------------------------------------------------
class QbsGroupNode : public ProjectExplorer::ProjectNode
{
public:
QbsGroupNode(const qbs::GroupData &grp, const QString &productPath);
QbsGroupNode(const QJsonObject &grp);
bool showInSimpleTree() const final { return false; }
QJsonObject groupData() const { return m_groupData; }
private:
friend class QbsBuildSystem;
AddNewInformation addNewInformation(const QStringList &files, Node *context) const override;
QVariant data(Core::Id role) const override;
qbs::GroupData m_qbsGroupData;
QString m_productPath;
const QJsonObject m_groupData;
};
// --------------------------------------------------------------------
// QbsProductNode:
// --------------------------------------------------------------------
class QbsProductNode : public ProjectExplorer::ProjectNode
{
public:
explicit QbsProductNode(const qbs::ProductData &prd);
explicit QbsProductNode(const QJsonObject &prd);
void build() override;
QStringList targetApplications() const override;
QString buildKey() const override;
const qbs::ProductData qbsProductData() const { return m_qbsProductData; }
const QJsonObject productData() const { return m_productData; }
QJsonObject mainGroup() const;
QVariant data(Core::Id role) const override;
private:
const qbs::ProductData m_qbsProductData;
const QJsonObject m_productData;
};
// ---------------------------------------------------------------------------
// QbsProjectNode:
// ---------------------------------------------------------------------------
class QbsProjectNode : public ProjectExplorer::ProjectNode
{
public:
explicit QbsProjectNode(const Utils::FilePath &projectDirectory);
explicit QbsProjectNode(const QJsonObject &projectData);
virtual ProjectExplorer::Project *project() const;
const qbs::ProjectData qbsProjectData() const { return m_projectData; }
void setProjectData(const qbs::ProjectData &data); // FIXME: Needed?
const QJsonObject projectData() const { return m_projectData; }
private:
qbs::ProjectData m_projectData;
friend class QbsNodeTreeBuilder;
};
// --------------------------------------------------------------------
// QbsRootProjectNode:
// --------------------------------------------------------------------
class QbsRootProjectNode : public QbsProjectNode
{
public:
explicit QbsRootProjectNode(ProjectExplorer::Project *project);
ProjectExplorer::Project *project() const override { return m_project; }
private:
ProjectExplorer::Project *const m_project;
const QJsonObject m_projectData;
};
const QbsProductNode *parentQbsProductNode(const ProjectExplorer::Node *node);
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -25,7 +25,13 @@
#include "qbsnodetreebuilder.h"
#include "qbsnodes.h"
#include "qbsproject.h"
#include "qbssession.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
@@ -36,13 +42,9 @@ using namespace Utils;
namespace QbsProjectManager {
namespace Internal {
namespace {
FileType fileType(const qbs::ArtifactData &artifact)
static FileType fileType(const QJsonObject &artifact)
{
QTC_ASSERT(artifact.isValid(), return FileType::Unknown);
const QStringList fileTags = artifact.fileTags();
const QJsonArray fileTags = artifact.value("file-tags").toArray();
if (fileTags.contains("c")
|| fileTags.contains("cpp")
|| fileTags.contains("objc")
@@ -62,160 +64,160 @@ FileType fileType(const qbs::ArtifactData &artifact)
return FileType::Unknown;
}
void setupArtifacts(FolderNode *root, const QList<qbs::ArtifactData> &artifacts)
void setupArtifact(FolderNode *root, const QJsonObject &artifact)
{
for (const qbs::ArtifactData &ad : artifacts) {
const FilePath path = FilePath::fromString(ad.filePath());
const FileType type = fileType(ad);
const bool isGenerated = ad.isGenerated();
const FilePath path = FilePath::fromString(artifact.value("file-path").toString());
const FileType type = fileType(artifact);
const bool isGenerated = artifact.value("is-generated").toBool();
// A list of human-readable file types that we can reasonably expect
// to get generated during a build. Extend as needed.
static const QSet<QString> sourceTags = {
QLatin1String("c"), QLatin1String("cpp"), QLatin1String("hpp"),
QLatin1String("objc"), QLatin1String("objcpp"),
QLatin1String("c_pch_src"), QLatin1String("cpp_pch_src"),
QLatin1String("objc_pch_src"), QLatin1String("objcpp_pch_src"),
QLatin1String("asm"), QLatin1String("asm_cpp"),
QLatin1String("linkerscript"),
QLatin1String("qrc"), QLatin1String("java.java")
"c", "cpp", "hpp", "objc", "objcpp", "c_pch_src", "cpp_pch_src", "objc_pch_src",
"objcpp_pch_src", "asm", "asm_cpp", "linkerscript", "qrc", "java.java"
};
auto node = std::make_unique<FileNode>(path, type);
node->setIsGenerated(isGenerated);
node->setListInProject(!isGenerated || Utils::toSet(ad.fileTags()).intersects(sourceTags));
QSet<QString> fileTags = toSet(transform<QStringList, QJsonArray>(
artifact.value("file-tags").toArray(),
[](const QJsonValue &v) { return v.toString(); }));
node->setListInProject(!isGenerated || fileTags.intersects(sourceTags));
root->addNestedNode(std::move(node));
}
static void setupArtifactsForGroup(FolderNode *root, const QJsonObject &group)
{
forAllArtifacts(group, [root](const QJsonObject &artifact) { setupArtifact(root, artifact); });
root->compress();
}
std::unique_ptr<QbsGroupNode>
buildGroupNodeTree(const qbs::GroupData &grp, const QString &productPath, bool productIsEnabled)
static void setupGeneratedArtifacts(FolderNode *root, const QJsonObject &product)
{
QTC_ASSERT(grp.isValid(), return nullptr);
forAllArtifacts(product, ArtifactType::Generated,
[root](const QJsonObject &artifact) { setupArtifact(root, artifact); });
root->compress();
}
auto fileNode = std::make_unique<FileNode>(FilePath::fromString(grp.location().filePath()),
FileType::Project);
fileNode->setLine(grp.location().line());
auto result = std::make_unique<QbsGroupNode>(grp, productPath);
result->setEnabled(productIsEnabled && grp.isEnabled());
result->setAbsoluteFilePathAndLine(
FilePath::fromString(grp.location().filePath()).parentDir(), -1);
result->setDisplayName(grp.name());
static std::unique_ptr<QbsGroupNode> buildGroupNodeTree(const QJsonObject &grp)
{
const Location location = locationFromObject(grp);
FilePath baseDir = location.filePath.parentDir();
QString prefix = grp.value("prefix").toString();
if (prefix.endsWith('/')) {
prefix.chop(1);
if (QFileInfo(prefix).isAbsolute())
baseDir = FilePath::fromString(prefix);
else
baseDir = baseDir.pathAppended(prefix);
}
auto result = std::make_unique<QbsGroupNode>(grp);
result->setAbsoluteFilePathAndLine(baseDir, -1);
auto fileNode = std::make_unique<FileNode>(FilePath(), FileType::Project);
fileNode->setAbsoluteFilePathAndLine(location.filePath, location.line);
result->addNode(std::move(fileNode));
setupArtifacts(result.get(), grp.allSourceArtifacts());
setupArtifactsForGroup(result.get(), grp);
return result;
}
void setupQbsProductData(QbsProductNode *node, const qbs::ProductData &prd)
static std::unique_ptr<QbsProductNode> buildProductNodeTree(const QJsonObject &prd)
{
auto fileNode = std::make_unique<FileNode>(FilePath::fromString(prd.location().filePath()),
FileType::Project);
fileNode->setLine(prd.location().line());
node->setEnabled(prd.isEnabled());
node->setDisplayName(prd.fullDisplayName());
node->setAbsoluteFilePathAndLine(FilePath::fromString(prd.location().filePath()).parentDir(), -1);
node->addNode(std::move(fileNode));
const QString &productPath = QFileInfo(prd.location().filePath()).absolutePath();
foreach (const qbs::GroupData &grp, prd.groups()) {
if (grp.name() == prd.name() && grp.location() == prd.location()) {
const Location location = locationFromObject(prd);
auto result = std::make_unique<QbsProductNode>(prd);
result->setAbsoluteFilePathAndLine(location.filePath.parentDir(), -1);
auto fileNode = std::make_unique<FileNode>(FilePath(), FileType::Project);
fileNode->setAbsoluteFilePathAndLine(location.filePath, location.line);
result->addNode(std::move(fileNode));
for (const QJsonValue &v : prd.value("groups").toArray()) {
const QJsonObject grp = v.toObject();
if (grp.value("name") == prd.value("name")
&& grp.value("location") == prd.value("location")) {
// Set implicit product group right onto this node:
setupArtifacts(node, grp.allSourceArtifacts());
setupArtifactsForGroup(result.get(), grp);
continue;
}
node->addNode(buildGroupNodeTree(grp, productPath, prd.isEnabled()));
result->addNode(buildGroupNodeTree(grp));
}
// Add "Generated Files" Node:
auto genFiles = std::make_unique<VirtualFolderNode>(FilePath::fromString(prd.buildDirectory()));
auto genFiles = std::make_unique<VirtualFolderNode>(
FilePath::fromString(prd.value("build-directory").toString()));
genFiles->setDisplayName(QCoreApplication::translate("QbsProductNode", "Generated files"));
setupArtifacts(genFiles.get(), prd.generatedArtifacts());
node->addNode(std::move(genFiles));
}
std::unique_ptr<QbsProductNode> buildProductNodeTree(const qbs::ProductData &prd)
{
auto result = std::make_unique<QbsProductNode>(prd);
setupQbsProductData(result.get(), prd);
setupGeneratedArtifacts(genFiles.get(), prd);
result->addNode(std::move(genFiles));
return result;
}
void setupProjectNode(QbsProjectNode *node, const qbs::ProjectData &prjData,
const qbs::Project &qbsProject)
static void setupProjectNode(QbsProjectNode *node)
{
auto fileNode = std::make_unique<FileNode>(FilePath::fromString(prjData.location().filePath()),
FileType::Project);
fileNode->setLine(prjData.location().line());
const Location loc = locationFromObject(node->projectData());
node->setAbsoluteFilePathAndLine(loc.filePath.parentDir(), -1);
auto fileNode = std::make_unique<FileNode>(node->filePath(), FileType::Project);
fileNode->setAbsoluteFilePathAndLine(loc.filePath, loc.line);
node->addNode(std::move(fileNode));
foreach (const qbs::ProjectData &subData, prjData.subProjects()) {
auto subProject = std::make_unique<QbsProjectNode>(
FilePath::fromString(subData.location().filePath()).parentDir());
setupProjectNode(subProject.get(), subData, qbsProject);
for (const QJsonValue &v : node->projectData().value("sub-projects").toArray()) {
auto subProject = std::make_unique<QbsProjectNode>(v.toObject());
setupProjectNode(subProject.get());
node->addNode(std::move(subProject));
}
foreach (const qbs::ProductData &prd, prjData.products())
node->addNode(buildProductNodeTree(prd));
if (!prjData.name().isEmpty())
node->setDisplayName(prjData.name());
else
node->setDisplayName(node->project()->displayName());
node->setProjectData(prjData);
for (const QJsonValue &v : node->projectData().value("products").toArray())
node->addNode(buildProductNodeTree(v.toObject()));
}
QSet<QString> referencedBuildSystemFiles(const qbs::ProjectData &data)
static QSet<QString> referencedBuildSystemFiles(const QJsonObject &prjData)
{
QSet<QString> result;
result.insert(data.location().filePath());
foreach (const qbs::ProjectData &subProject, data.subProjects())
result.unite(referencedBuildSystemFiles(subProject));
foreach (const qbs::ProductData &product, data.products()) {
result.insert(product.location().filePath());
foreach (const qbs::GroupData &group, product.groups())
result.insert(group.location().filePath());
result.insert(prjData.value("location").toObject().value("file-path").toString());
for (const QJsonValue &v : prjData.value("sub-projects").toArray())
result.unite(referencedBuildSystemFiles(v.toObject()));
for (const QJsonValue &v : prjData.value("products").toArray()) {
const QJsonObject product = v.toObject();
result.insert(product.value("location").toObject().value("file-path").toString());
for (const QJsonValue &v : product.value("groups").toArray())
result.insert(v.toObject().value("location").toObject().value("file-path").toString());
}
return result;
}
QStringList unreferencedBuildSystemFiles(const qbs::Project &p)
static QStringList unreferencedBuildSystemFiles(const QJsonObject &project)
{
QStringList result;
if (!p.isValid())
return result;
const std::set<QString> &available = p.buildSystemFiles();
QList<QString> referenced = Utils::toList(referencedBuildSystemFiles(p.projectData()));
Utils::sort(referenced);
std::set_difference(available.begin(), available.end(), referenced.begin(), referenced.end(),
std::back_inserter(result));
return result;
QStringList unreferenced = arrayToStringList(project.value("build-system-files"));
const QList<QString> referenced = toList(referencedBuildSystemFiles(project));
for (auto it = unreferenced.begin(); it != unreferenced.end(); ) {
if (referenced.contains(*it))
it = unreferenced.erase(it);
else
++it;
}
return unreferenced;
}
} // namespace
std::unique_ptr<QbsRootProjectNode> QbsNodeTreeBuilder::buildTree(QbsBuildSystem *buildSystem)
std::unique_ptr<QbsProjectNode> QbsNodeTreeBuilder::buildTree(const QbsBuildSystem *buildSystem)
{
if (!buildSystem->qbsProjectData().isValid())
return {};
const Project * const project = buildSystem->project();
auto root = std::make_unique<QbsProjectNode>(buildSystem->projectData());
auto root = std::make_unique<QbsRootProjectNode>(buildSystem->project());
setupProjectNode(root.get(), buildSystem->qbsProjectData(), buildSystem->qbsProject());
// If we have no project information at all (i.e. it could not be properly parsed),
// create the main project file node "manually".
if (buildSystem->projectData().isEmpty()) {
auto fileNode = std::make_unique<FileNode>(project->projectFilePath(), FileType::Project);
root->addNode(std::move(fileNode));
} else {
setupProjectNode(root.get());
}
auto buildSystemFiles = std::make_unique<FolderNode>(buildSystem->projectDirectory());
buildSystemFiles->setDisplayName(QCoreApplication::translate("QbsRootProjectNode", "Qbs files"));
if (root->displayName().isEmpty())
root->setDisplayName(project->displayName());
if (root->displayName().isEmpty())
root->setDisplayName(project->projectFilePath().toFileInfo().completeBaseName());
const FilePath base = buildSystem->projectDirectory();
const QStringList files = unreferencedBuildSystemFiles(buildSystem->qbsProject());
auto buildSystemFiles = std::make_unique<FolderNode>(project->projectDirectory());
buildSystemFiles->setDisplayName(QCoreApplication::translate("QbsProjectNode", "Qbs files"));
const FilePath base = project->projectDirectory();
const QStringList files = unreferencedBuildSystemFiles(buildSystem->projectData());
for (const QString &f : files) {
const FilePath filePath = FilePath::fromString(f);
if (filePath.isChildOf(base))
@@ -223,7 +225,6 @@ std::unique_ptr<QbsRootProjectNode> QbsNodeTreeBuilder::buildTree(QbsBuildSystem
}
buildSystemFiles->compress();
root->addNode(std::move(buildSystemFiles));
return root;
}

View File

@@ -25,21 +25,18 @@
#pragma once
#include "qbsnodes.h"
#include <qbs.h>
#include <memory>
namespace QbsProjectManager {
namespace Internal {
// ----------------------------------------------------------------------
// QbsNodeTreeBuilder:
// ----------------------------------------------------------------------
class QbsBuildSystem;
class QbsProjectNode;
class QbsNodeTreeBuilder
{
public:
static std::unique_ptr<QbsRootProjectNode> buildTree(QbsBuildSystem *project);
static std::unique_ptr<QbsProjectNode> buildTree(const QbsBuildSystem *buildSystem);
};
} // namespace Internal

View File

@@ -0,0 +1,266 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qbsprofilemanager.h"
#include "defaultpropertyprovider.h"
#include "qbsproject.h"
#include "qbsprojectmanagerconstants.h"
#include "qbsprojectmanagerplugin.h"
#include "qbssettings.h"
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <projectexplorer/kit.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/projectexplorer.h>
#include <qmljstools/qmljstoolsconstants.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <utils/qtcassert.h>
#include <QCryptographicHash>
#include <QJSEngine>
#include <QProcess>
#include <QRegExp>
#include <QVariantMap>
namespace QbsProjectManager {
static QList<PropertyProvider *> g_propertyProviders;
PropertyProvider::PropertyProvider()
{
g_propertyProviders.append(this);
}
PropertyProvider::~PropertyProvider()
{
g_propertyProviders.removeOne(this);
}
namespace Internal {
static QString toJSLiteral(const bool b)
{
return QLatin1String(b ? "true" : "false");
}
static QString toJSLiteral(const QString &str)
{
QString js = str;
js.replace(QRegExp("([\\\\\"])"), "\\\\1");
js.prepend('"');
js.append('"');
return js;
}
QString toJSLiteral(const QVariant &val)
{
if (!val.isValid())
return QString("undefined");
if (val.type() == QVariant::List || val.type() == QVariant::StringList) {
QString res;
const auto list = val.toList();
for (const QVariant &child : list) {
if (res.length()) res.append(", ");
res.append(toJSLiteral(child));
}
res.prepend('[');
res.append(']');
return res;
}
if (val.type() == QVariant::Map) {
const QVariantMap &vm = val.toMap();
QString str("{");
for (auto it = vm.begin(); it != vm.end(); ++it) {
if (it != vm.begin())
str += ',';
str += toJSLiteral(it.key()) + ':' + toJSLiteral(it.value());
}
str += '}';
return str;
}
if (val.type() == QVariant::Bool)
return toJSLiteral(val.toBool());
if (val.canConvert(QVariant::String))
return toJSLiteral(val.toString());
return QString::fromLatin1("Unconvertible type %1").arg(QLatin1String(val.typeName()));
}
static QbsProfileManager *m_instance = nullptr;
static QString kitNameKeyInQbsSettings(const ProjectExplorer::Kit *kit)
{
return "preferences.qtcreator.kit." + kit->id().toString();
}
QbsProfileManager::QbsProfileManager() : m_defaultPropertyProvider(new DefaultPropertyProvider)
{
m_instance = this;
setObjectName(QLatin1String("QbsProjectManager"));
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::kitsLoaded, this,
[this]() { m_kitsToBeSetupForQbs = ProjectExplorer::KitManager::kits(); } );
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::kitAdded, this,
&QbsProfileManager::addProfileFromKit);
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::kitUpdated, this,
&QbsProfileManager::handleKitUpdate);
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::kitRemoved, this,
&QbsProfileManager::handleKitRemoval);
connect(&QbsSettings::instance(), &QbsSettings::settingsChanged,
this, &QbsProfileManager::updateAllProfiles);
}
QbsProfileManager::~QbsProfileManager()
{
delete m_defaultPropertyProvider;
m_instance = nullptr;
}
QbsProfileManager *QbsProfileManager::instance()
{
return m_instance;
}
QString QbsProfileManager::profileForKit(const ProjectExplorer::Kit *k)
{
if (!k)
return QString();
m_instance->updateProfileIfNecessary(k);
const QString output = runQbsConfig(QbsConfigOp::Get, kitNameKeyInQbsSettings(k));
const int endQuoteIdx = output.lastIndexOf('"');
QTC_ASSERT(endQuoteIdx != -1, return QString());
const int startQuoteIdx = output.lastIndexOf('"', endQuoteIdx - 1);
QTC_ASSERT(startQuoteIdx != -1, return QString());
return output.mid(startQuoteIdx + 1, endQuoteIdx - startQuoteIdx - 1);
}
void QbsProfileManager::setProfileForKit(const QString &name, const ProjectExplorer::Kit *k)
{
runQbsConfig(QbsConfigOp::Set, kitNameKeyInQbsSettings(k), name);
}
void QbsProfileManager::updateProfileIfNecessary(const ProjectExplorer::Kit *kit)
{
// kit in list <=> profile update is necessary
// Note that the const_cast is safe, as we do not call any non-const methods on the object.
if (m_instance->m_kitsToBeSetupForQbs.removeOne(const_cast<ProjectExplorer::Kit *>(kit)))
m_instance->addProfileFromKit(kit);
}
void QbsProfileManager::updateAllProfiles()
{
for (const auto * const kit : ProjectExplorer::KitManager::kits())
addProfileFromKit(kit);
}
void QbsProfileManager::addProfile(const QString &name, const QVariantMap &data)
{
const QString keyPrefix = "profiles." + name + ".";
for (auto it = data.begin(); it != data.end(); ++it)
runQbsConfig(QbsConfigOp::Set, keyPrefix + it.key(), it.value());
emit qbsProfilesUpdated();
}
void QbsProfileManager::addQtProfileFromKit(const QString &profileName, const ProjectExplorer::Kit *k)
{
if (const QtSupport::BaseQtVersion * const qt = QtSupport::QtKitAspect::qtVersion(k)) {
runQbsConfig(QbsConfigOp::Set,
"profiles." + profileName + ".moduleProviders.Qt.qmakeFilePaths",
qt->qmakeCommand().toString());
}
}
void QbsProfileManager::addProfileFromKit(const ProjectExplorer::Kit *k)
{
const QString name = QString::fromLatin1("qtc_%1_%2").arg(k->fileSystemFriendlyName().left(8),
QString::fromLatin1(QCryptographicHash::hash(k->id().name(),
QCryptographicHash::Sha1).toHex().left(8)));
runQbsConfig(QbsConfigOp::Unset, "profiles." + name);
setProfileForKit(name, k);
addQtProfileFromKit(name, k);
// set up properties:
QVariantMap data = m_defaultPropertyProvider->properties(k, QVariantMap());
for (PropertyProvider *provider : g_propertyProviders) {
if (provider->canHandle(k))
data = provider->properties(k, data);
}
addProfile(name, data);
}
void QbsProfileManager::handleKitUpdate(ProjectExplorer::Kit *kit)
{
m_kitsToBeSetupForQbs.removeOne(kit);
addProfileFromKit(kit);
}
void QbsProfileManager::handleKitRemoval(ProjectExplorer::Kit *kit)
{
m_kitsToBeSetupForQbs.removeOne(kit);
runQbsConfig(QbsConfigOp::Unset, kitNameKeyInQbsSettings(kit));
runQbsConfig(QbsConfigOp::Unset, "profiles." + profileForKit(kit));
emit qbsProfilesUpdated();
}
QString QbsProfileManager::runQbsConfig(QbsConfigOp op, const QString &key, const QVariant &value)
{
QProcess qbsConfig;
QStringList args("config");
if (QbsSettings::useCreatorSettingsDirForQbs())
args << "--settings-dir" << QbsSettings::qbsSettingsBaseDir();
switch (op) {
case QbsConfigOp::Get:
args << key;
break;
case QbsConfigOp::Set:
args << key << toJSLiteral(value);
break;
case QbsConfigOp::Unset:
args << "--unset" << key;
break;
}
qbsConfig.start(QbsSettings::qbsExecutableFilePath().toString(), args);
if (!qbsConfig.waitForStarted(3000) || !qbsConfig.waitForFinished(5000)) {
Core::MessageManager::write(tr("Failed run qbs config: %1").arg(qbsConfig.errorString()));
} else if (qbsConfig.exitCode() != 0) {
Core::MessageManager::write(tr("Failed to run qbs config: %1")
.arg(QString::fromLocal8Bit(qbsConfig.readAllStandardError())));
}
return QString::fromLocal8Bit(qbsConfig.readAllStandardOutput()).trimmed();
}
QVariant fromJSLiteral(const QString &str)
{
QJSEngine engine;
QJSValue sv = engine.evaluate("(function(){return " + str + ";})()");
return sv.isError() ? str : sv.toVariant();
}
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -28,32 +28,33 @@
#include "qbsprojectmanager_global.h"
#include <QList>
#include <QString>
#include <QVariantMap>
namespace qbs { class Settings; }
#include <QVariant>
namespace ProjectExplorer { class Kit; }
namespace QbsProjectManager {
namespace Internal {
class DefaultPropertyProvider;
class QbsLogSink;
class QbsManager : public QObject
QString toJSLiteral(const QVariant &val);
QVariant fromJSLiteral(const QString &str);
class QbsProfileManager : public QObject
{
Q_OBJECT
public:
QbsManager();
~QbsManager() override;
QbsProfileManager();
~QbsProfileManager() override;
static QbsProfileManager *instance();
// QBS profiles management:
static QString profileForKit(const ProjectExplorer::Kit *k);
static void updateProfileIfNecessary(const ProjectExplorer::Kit *kit);
enum class QbsConfigOp { Get, Set, Unset }; static QString runQbsConfig(QbsConfigOp op, const QString &key, const QVariant &value = {});
static qbs::Settings *settings();
static Internal::QbsLogSink *logSink();
signals:
void qbsProfilesUpdated();
private:
void setProfileForKit(const QString &name, const ProjectExplorer::Kit *k);

View File

@@ -26,50 +26,111 @@
#include "qbsprofilessettingspage.h"
#include "ui_qbsprofilessettingswidget.h"
#include "qbsprojectmanager.h"
#include "qbsprofilemanager.h"
#include "qbsprojectmanagerconstants.h"
#include "qbsprojectmanagersettings.h"
#include "qbssettings.h"
#include <app/app_version.h>
#include <coreplugin/icore.h>
#include <projectexplorer/kit.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectexplorericons.h>
#include <projectexplorer/taskhub.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <qbs.h>
#include <utils/treemodel.h>
#include <QCoreApplication>
#include <QHash>
#include <QWidget>
using namespace ProjectExplorer;
namespace QbsProjectManager {
namespace Internal {
class ProfileTreeItem : public Utils::TypedTreeItem<ProfileTreeItem, ProfileTreeItem>
{
public:
ProfileTreeItem() = default;
ProfileTreeItem(const QString &key, const QString &value) : m_key(key), m_value(value) { }
private:
QVariant data(int column, int role) const override
{
if (role != Qt::DisplayRole)
return {};
if (column == 0)
return m_key;
if (column == 1)
return m_value;
return {};
}
const QString m_key;
const QString m_value;
};
class ProfileModel : public Utils::TreeModel<ProfileTreeItem>
{
Q_OBJECT
public:
ProfileModel() : TreeModel(static_cast<QObject *>(nullptr))
{
setHeader(QStringList{tr("Key"), tr("Value")});
reload();
}
void reload()
{
ProfileTreeItem * const newRoot = new ProfileTreeItem(QString(), QString());
QHash<QStringList, ProfileTreeItem *> itemMap;
const QStringList output = QbsProfileManager::runQbsConfig(
QbsProfileManager::QbsConfigOp::Get, "profiles").split('\n', QString::SkipEmptyParts);
for (QString line : output) {
line = line.trimmed();
line = line.mid(QString("profiles.").length());
const int colonIndex = line.indexOf(':');
if (colonIndex == -1)
continue;
const QStringList key = line.left(colonIndex).trimmed()
.split('.', QString::SkipEmptyParts);
const QString value = line.mid(colonIndex + 1).trimmed();
QStringList partialKey;
ProfileTreeItem *parent = newRoot;
for (const QString &keyComponent : key) {
partialKey << keyComponent;
ProfileTreeItem *&item = itemMap[partialKey];
if (!item) {
item = new ProfileTreeItem(keyComponent, partialKey == key ? value : QString());
parent->appendChild(item);
}
parent = item;
}
}
setRootItem(newRoot);
}
};
class QbsProfilesSettingsWidget : public QWidget
{
Q_OBJECT
public:
QbsProfilesSettingsWidget();
void apply();
private:
void refreshKitsList();
void displayCurrentProfile();
Ui::QbsProfilesSettingsWidget m_ui;
qbs::SettingsModel m_model;
ProfileModel m_model;
};
QbsProfilesSettingsPage::QbsProfilesSettingsPage()
: m_useQtcSettingsDirPersistent(QbsProjectManagerSettings::useCreatorSettingsDirForQbs())
{
setId("Y.QbsProfiles");
setDisplayName(QCoreApplication::translate("QbsProjectManager", "Qbs"));
setCategory(ProjectExplorer::Constants::KITS_SETTINGS_CATEGORY);
setDisplayName(QCoreApplication::translate("QbsProjectManager", "Profiles"));
setCategory(Constants::QBS_SETTINGS_CATEGORY);
}
QWidget *QbsProfilesSettingsPage::widget()
@@ -79,38 +140,17 @@ QWidget *QbsProfilesSettingsPage::widget()
return m_widget;
}
void QbsProfilesSettingsPage::apply()
{
if (m_widget)
m_widget->apply();
m_useQtcSettingsDirPersistent = QbsProjectManagerSettings::useCreatorSettingsDirForQbs();
}
void QbsProfilesSettingsPage::finish()
{
delete m_widget;
m_widget = nullptr;
QbsProjectManagerSettings::setUseCreatorSettingsDirForQbs(m_useQtcSettingsDirPersistent);
QbsProjectManagerSettings::writeSettings();
}
QbsProfilesSettingsWidget::QbsProfilesSettingsWidget()
: m_model(QbsProjectManagerSettings::qbsSettingsBaseDir(), qbs::Settings::UserScope)
{
m_model.setEditable(false);
m_ui.setupUi(this);
m_ui.settingsDirCheckBox->setText(tr("Store profiles in %1 settings directory")
.arg(Core::Constants::IDE_DISPLAY_NAME));
m_ui.settingsDirCheckBox->setChecked(QbsProjectManagerSettings::useCreatorSettingsDirForQbs());
m_ui.versionValueLabel->setText(qbs::LanguageInfo::qbsVersion().toString());
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::kitsChanged,
connect(QbsProfileManager::instance(), &QbsProfileManager::qbsProfilesUpdated,
this, &QbsProfilesSettingsWidget::refreshKitsList);
connect(m_ui.settingsDirCheckBox, &QCheckBox::stateChanged, [this]() {
QbsProjectManagerSettings::setUseCreatorSettingsDirForQbs(m_ui.settingsDirCheckBox->isChecked());
m_model.updateSettingsDir(QbsProjectManagerSettings::qbsSettingsBaseDir());
displayCurrentProfile();
});
connect(m_ui.expandButton, &QAbstractButton::clicked,
m_ui.propertiesView, &QTreeView::expandAll);
connect(m_ui.collapseButton, &QAbstractButton::clicked,
@@ -118,12 +158,6 @@ QbsProfilesSettingsWidget::QbsProfilesSettingsWidget()
refreshKitsList();
}
void QbsProfilesSettingsWidget::apply()
{
m_model.reload();
displayCurrentProfile();
}
void QbsProfilesSettingsWidget::refreshKitsList()
{
m_ui.kitsComboBox->disconnect(this);
@@ -135,10 +169,10 @@ void QbsProfilesSettingsWidget::refreshKitsList()
currentId = Core::Id::fromSetting(m_ui.kitsComboBox->currentData());
m_ui.kitsComboBox->clear();
int newCurrentIndex = -1;
QList<ProjectExplorer::Kit *> validKits = ProjectExplorer::KitManager::kits();
Utils::erase(validKits, [](const ProjectExplorer::Kit *k) { return !k->isValid(); });
QList<Kit *> validKits = KitManager::kits();
Utils::erase(validKits, [](const Kit *k) { return !k->isValid(); });
const bool hasKits = !validKits.isEmpty();
foreach (const ProjectExplorer::Kit * const kit, validKits) {
for (const Kit * const kit : qAsConst(validKits)) {
if (kit->id() == currentId)
newCurrentIndex = m_ui.kitsComboBox->count();
m_ui.kitsComboBox->addItem(kit->displayName(), kit->id().toSetting());
@@ -159,26 +193,20 @@ void QbsProfilesSettingsWidget::displayCurrentProfile()
if (m_ui.kitsComboBox->currentIndex() == -1)
return;
const Core::Id kitId = Core::Id::fromSetting(m_ui.kitsComboBox->currentData());
const ProjectExplorer::Kit * const kit = ProjectExplorer::KitManager::kit(kitId);
const Kit * const kit = KitManager::kit(kitId);
QTC_ASSERT(kit, return);
const QString profileName = QbsManager::profileForKit(kit);
const QString profileName = QbsProfileManager::profileForKit(kit);
m_ui.profileValueLabel->setText(profileName);
for (int i = 0; i < m_model.rowCount(); ++i) {
const QModelIndex profilesIndex = m_model.index(i, 0);
if (m_model.data(profilesIndex).toString() != QLatin1String("profiles"))
continue;
for (int i = 0; i < m_model.rowCount(profilesIndex); ++i) {
const QModelIndex currentProfileIndex = m_model.index(i, 0, profilesIndex);
if (m_model.data(currentProfileIndex).toString() != profileName)
const QModelIndex currentProfileIndex = m_model.index(i, 0);
if (m_model.data(currentProfileIndex, Qt::DisplayRole).toString() != profileName)
continue;
m_ui.propertiesView->setModel(&m_model);
m_ui.propertiesView->header()->setSectionResizeMode(m_model.keyColumn(),
QHeaderView::ResizeToContents);
m_ui.propertiesView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
m_ui.propertiesView->setRootIndex(currentProfileIndex);
return;
}
}
}
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -38,11 +38,10 @@ public:
private:
QWidget *widget() override;
void apply() override;
void apply() override { }
void finish() override;
QbsProfilesSettingsWidget *m_widget = nullptr;
bool m_useQtcSettingsDirPersistent;
};
} // namespace Internal

View File

@@ -14,23 +14,16 @@
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="settingsDirCheckBox">
<property name="text">
<string>Store profiles in Qt Creator settings directory</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<item row="0" column="0">
<widget class="QLabel" name="kitLabel">
<property name="text">
<string>Kit:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="kitsComboBox"/>
@@ -50,14 +43,14 @@
</item>
</layout>
</item>
<item row="2" column="0">
<item row="1" column="0">
<widget class="QLabel" name="profileKeyLabel">
<property name="text">
<string>Associated profile:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="1" column="1">
<widget class="QLabel" name="profileValueLabel">
<property name="text">
<string/>
@@ -67,23 +60,6 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="versionKeyLabel">
<property name="text">
<string>Qbs version:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="versionValueLabel">
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
<item>

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@
#pragma once
#include "qbsprojectmanager.h"
#include "qbsprofilemanager.h"
#include "qbsnodes.h"
@@ -36,9 +36,8 @@
#include <utils/environment.h>
#include <qbs.h>
#include <QHash>
#include <QJsonObject>
#include <QTimer>
namespace CppTools { class CppProjectUpdater; }
@@ -46,8 +45,10 @@ namespace CppTools { class CppProjectUpdater; }
namespace QbsProjectManager {
namespace Internal {
class ErrorInfo;
class QbsBuildConfiguration;
class QbsProjectParser;
class QbsSession;
class QbsProject : public ProjectExplorer::Project
{
@@ -63,8 +64,6 @@ public:
void configureAsExampleProject() final;
static QString uniqueProductName(const qbs::ProductData &product);
private:
mutable ProjectExplorer::ProjectImporter *m_importer = nullptr;
};
@@ -94,24 +93,17 @@ public:
QVariant additionalData(Core::Id id) const final;
bool isProjectEditable() const;
// qbs::ProductData and qbs::GroupData are held by the nodes in the project tree.
// These methods change those trees and invalidate the lot, so pass in copies of
// the data we are interested in!
// The overhead is not as big as it seems at first glance: These all are handles
// for shared data.
bool addFilesToProduct(const QStringList &filePaths, const qbs::ProductData productData,
const qbs::GroupData groupData, QStringList *notAdded);
bool addFilesToProduct(const QStringList &filePaths,
const QJsonObject &product,
const QJsonObject &group,
QStringList *notAdded);
ProjectExplorer::RemovedFilesFromProject removeFilesFromProduct(const QStringList &filePaths,
const qbs::ProductData &productData, const qbs::GroupData &groupData,
const QJsonObject &product,
const QJsonObject &group,
QStringList *notRemoved);
bool renameFileInProduct(const QString &oldPath,
const QString &newPath, const qbs::ProductData productData,
const qbs::GroupData groupData);
qbs::BuildJob *build(const qbs::BuildOptions &opts, QStringList products, QString &error);
qbs::CleanJob *clean(const qbs::CleanOptions &opts, const QStringList &productNames,
QString &error);
qbs::InstallJob *install(const qbs::InstallOptions &opts);
const QString &newPath, const QJsonObject &product,
const QJsonObject &group);
static ProjectExplorer::FileType fileTypeFor(const QSet<QString> &tags);
@@ -122,15 +114,16 @@ public:
void cancelParsing();
void updateAfterBuild();
qbs::Project qbsProject() const;
qbs::ProjectData qbsProjectData() const;
QbsSession *session() const { return m_session; }
QJsonObject projectData() const { return m_projectData; }
void generateErrors(const qbs::ErrorInfo &e);
void generateErrors(const ErrorInfo &e);
void delayParsing();
private:
friend class QbsProject;
void handleQbsParsingDone(bool success);
void rebuildProjectTree();
@@ -138,13 +131,12 @@ private:
void changeActiveTarget(ProjectExplorer::Target *t);
void prepareForParsing();
void updateDocuments(const std::set<QString> &files);
void updateDocuments();
void updateCppCodeModel();
void updateQmlJsCodeModel();
void updateApplicationTargets();
void updateDeploymentInfo();
void updateBuildTargetData();
void handleRuleExecutionDone();
bool checkCancelStatus();
void updateAfterParse();
void delayedUpdateAfterParse();
@@ -153,16 +145,14 @@ private:
static bool ensureWriteableQbsFile(const QString &file);
template<typename Options> qbs::AbstractJob *buildOrClean(const Options &opts,
const QStringList &productNames, QString &error);
qbs::Project m_qbsProject;
qbs::ProjectData m_projectData; // Cached m_qbsProject.projectData()
Utils::Environment m_lastParseEnv;
QbsSession * const m_session;
QSet<Core::IDocument *> m_qbsDocuments;
QJsonObject m_projectData; // TODO: Perhaps store this in the root project node instead?
QTimer m_parsingDelay;
QbsProjectParser *m_qbsProjectParser = nullptr;
QFutureInterface<bool> *m_qbsUpdateFutureInterface = nullptr;
Utils::Environment m_lastParseEnv;
bool m_parsingScheduled = false;
enum CancelStatus {
@@ -173,9 +163,8 @@ private:
CppTools::CppProjectUpdater *m_cppCodeModelUpdater = nullptr;
QTimer m_parsingDelay;
QHash<ProjectExplorer::ExtraCompilerFactory *, QStringList> m_sourcesForGeneratedFiles;
QList<ProjectExplorer::ExtraCompiler *> m_extraCompilers;
bool m_extraCompilersPending = false;
QHash<QString, Utils::Environment> m_envCache;

View File

@@ -27,6 +27,7 @@
#include "qbsbuildconfiguration.h"
#include "qbspmlogging.h"
#include "qbssession.h"
#include <coreplugin/documentmanager.h>
#include <projectexplorer/buildconfiguration.h>
@@ -42,8 +43,6 @@
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <qbs.h>
#include <QFileInfo>
using namespace ProjectExplorer;
@@ -62,10 +61,10 @@ struct BuildGraphData
FilePath sysroot;
QString buildVariant;
};
static BuildGraphData extractBgData(const qbs::Project::BuildGraphInfo &bgInfo)
static BuildGraphData extractBgData(const QbsSession::BuildGraphInfo &bgInfo)
{
BuildGraphData bgData;
bgData.bgFilePath = FilePath::fromString(bgInfo.bgFilePath);
bgData.bgFilePath = bgInfo.bgFilePath;
bgData.overriddenProperties = bgInfo.overriddenProperties;
const QVariantMap &moduleProps = bgInfo.requestedProperties;
const QVariantMap prjCompilerPathByLanguage
@@ -139,15 +138,15 @@ QList<void *> QbsProjectImporter::examineDirectory(const FilePath &importPath) c
{
qCDebug(qbsPmLog) << "examining build directory" << importPath.toUserOutput();
QList<void *> data;
const QString bgFilePath = importPath.toString() + QLatin1Char('/') + importPath.fileName()
+ QLatin1String(".bg");
const FilePath bgFilePath = importPath.pathAppended(importPath.fileName() + ".bg");
const QStringList relevantProperties({
"qbs.buildVariant", "qbs.sysroot", "qbs.toolchain",
"cpp.compilerPath", "cpp.compilerPathByLanguage",
"Qt.core.binPath"
});
const qbs::Project::BuildGraphInfo bgInfo
= qbs::Project::getBuildGraphInfo(bgFilePath, relevantProperties);
const QbsSession::BuildGraphInfo bgInfo = QbsSession::getBuildGraphInfo(
bgFilePath, relevantProperties);
if (bgInfo.error.hasError()) {
qCDebug(qbsPmLog) << "error getting build graph info:" << bgInfo.error.toString();
return data;

View File

@@ -1,209 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qbsprojectmanager.h"
#include "defaultpropertyprovider.h"
#include "qbslogsink.h"
#include "qbsproject.h"
#include "qbsprojectmanagerconstants.h"
#include "qbsprojectmanagerplugin.h"
#include "qbsprojectmanagersettings.h"
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <projectexplorer/kit.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/projectexplorer.h>
#include <qmljstools/qmljstoolsconstants.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <QCryptographicHash>
#include <QVariantMap>
#include <qbs.h>
const QChar sep = QLatin1Char('.');
static QString qtcProfileGroup() { return QLatin1String("preferences.qtcreator.kit"); }
static QString qtcProfilePrefix() { return qtcProfileGroup() + sep; }
namespace QbsProjectManager {
static QList<PropertyProvider *> g_propertyProviders;
PropertyProvider::PropertyProvider()
{
g_propertyProviders.append(this);
}
PropertyProvider::~PropertyProvider()
{
g_propertyProviders.removeOne(this);
}
namespace Internal {
static qbs::Settings *m_settings = nullptr;
static Internal::QbsLogSink *m_logSink = nullptr;
static QbsManager *m_instance = nullptr;
QbsManager::QbsManager() : m_defaultPropertyProvider(new DefaultPropertyProvider)
{
m_instance = this;
setObjectName(QLatin1String("QbsProjectManager"));
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::kitsLoaded, this,
[this]() { m_kitsToBeSetupForQbs = ProjectExplorer::KitManager::kits(); } );
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::kitAdded, this,
&QbsManager::addProfileFromKit);
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::kitUpdated, this,
&QbsManager::handleKitUpdate);
connect(ProjectExplorer::KitManager::instance(), &ProjectExplorer::KitManager::kitRemoved, this,
&QbsManager::handleKitRemoval);
connect(&QbsProjectManagerSettings::instance(), &QbsProjectManagerSettings::settingsBaseChanged,
this, &QbsManager::updateAllProfiles);
m_logSink = new QbsLogSink(this);
int level = qbs::LoggerInfo;
const QString levelEnv = QString::fromLocal8Bit(qgetenv("QBS_LOG_LEVEL"));
if (!levelEnv.isEmpty()) {
bool ok = false;
int tmp = levelEnv.toInt(&ok);
if (ok) {
if (tmp < static_cast<int>(qbs::LoggerMinLevel))
tmp = static_cast<int>(qbs::LoggerMinLevel);
if (tmp > static_cast<int>(qbs::LoggerMaxLevel))
tmp = static_cast<int>(qbs::LoggerMaxLevel);
level = tmp;
}
}
m_logSink->setLogLevel(static_cast<qbs::LoggerLevel>(level));
}
QbsManager::~QbsManager()
{
delete m_defaultPropertyProvider;
delete m_settings;
m_instance = nullptr;
}
QString QbsManager::profileForKit(const ProjectExplorer::Kit *k)
{
if (!k)
return QString();
m_instance->updateProfileIfNecessary(k);
return settings()->value(qtcProfilePrefix() + k->id().toString(), qbs::Settings::UserScope)
.toString();
}
void QbsManager::setProfileForKit(const QString &name, const ProjectExplorer::Kit *k)
{
settings()->setValue(qtcProfilePrefix() + k->id().toString(), name);
}
void QbsManager::updateProfileIfNecessary(const ProjectExplorer::Kit *kit)
{
// kit in list <=> profile update is necessary
// Note that the const_cast is safe, as we do not call any non-const methods on the object.
if (m_instance->m_kitsToBeSetupForQbs.removeOne(const_cast<ProjectExplorer::Kit *>(kit)))
m_instance->addProfileFromKit(kit);
}
void QbsManager::updateAllProfiles()
{
foreach (const auto * const kit, ProjectExplorer::KitManager::kits())
addProfileFromKit(kit);
}
qbs::Settings *QbsManager::settings()
{
if (!m_settings
|| m_settings->baseDirectory() != QbsProjectManagerSettings::qbsSettingsBaseDir()) {
delete m_settings;
m_settings = new qbs::Settings(QbsProjectManagerSettings::qbsSettingsBaseDir());
}
return m_settings;
}
QbsLogSink *QbsManager::logSink()
{
return m_logSink;
}
void QbsManager::addProfile(const QString &name, const QVariantMap &data)
{
qbs::Profile profile(name, settings());
const QVariantMap::ConstIterator cend = data.constEnd();
for (QVariantMap::ConstIterator it = data.constBegin(); it != cend; ++it)
profile.setValue(it.key(), it.value());
}
void QbsManager::addQtProfileFromKit(const QString &profileName, const ProjectExplorer::Kit *k)
{
if (const QtSupport::BaseQtVersion * const qt = QtSupport::QtKitAspect::qtVersion(k)) {
qbs::Profile(profileName, settings()).setValue("moduleProviders.Qt.qmakeFilePaths",
qt->qmakeCommand().toString());
}
}
void QbsManager::addProfileFromKit(const ProjectExplorer::Kit *k)
{
const QString name = QString::fromLatin1("qtc_%1_%2").arg(k->fileSystemFriendlyName().left(8),
QString::fromLatin1(QCryptographicHash::hash(k->id().name(),
QCryptographicHash::Sha1).toHex().left(8)));
qbs::Profile(name, settings()).removeProfile();
setProfileForKit(name, k);
addQtProfileFromKit(name, k);
// set up properties:
QVariantMap data = m_defaultPropertyProvider->properties(k, QVariantMap());
for (PropertyProvider *provider : g_propertyProviders) {
if (provider->canHandle(k))
data = provider->properties(k, data);
}
addProfile(name, data);
}
void QbsManager::handleKitUpdate(ProjectExplorer::Kit *kit)
{
m_kitsToBeSetupForQbs.removeOne(kit);
addProfileFromKit(kit);
}
void QbsManager::handleKitRemoval(ProjectExplorer::Kit *kit)
{
m_kitsToBeSetupForQbs.removeOne(kit);
const QString key = qtcProfilePrefix() + kit->id().toString();
const QString profileName = settings()->value(key, qbs::Settings::UserScope).toString();
settings()->remove(key);
qbs::Profile(profileName, settings()).removeProfile();
}
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -1,20 +1,10 @@
include(../../qtcreatorplugin.pri)
# Look for qbs in the environment
isEmpty(QBS_INSTALL_DIR): QBS_INSTALL_DIR = $$(QBS_INSTALL_DIR)
isEmpty(QBS_INSTALL_DIR) {
QBS_SOURCE_DIR = $$PWD/../../shared/qbs
include($$QBS_SOURCE_DIR/src/lib/corelib/use_corelib.pri)
osx:QMAKE_LFLAGS += -Wl,-rpath,@loader_path/../Frameworks # OS X: fix rpath for qbscore soname
} else {
include($${QBS_INSTALL_DIR}/include/qbs/use_installed_corelib.pri)
}
QBS_INSTALL_DIR_FWD_SLASHES = $$replace(QBS_INSTALL_DIR, \\\\, /)
DEFINES += QBS_INSTALL_DIR=\\\"$$QBS_INSTALL_DIR_FWD_SLASHES\\\"
DEFINES += \
QBSPROJECTMANAGER_LIBRARY
QT += qml
HEADERS = \
customqbspropertiesdialog.h \
defaultpropertyprovider.h \
@@ -24,20 +14,20 @@ HEADERS = \
qbscleanstep.h \
qbskitinformation.h \
qbsinstallstep.h \
qbslogsink.h \
qbsnodes.h \
qbsnodetreebuilder.h \
qbsparser.h \
qbspmlogging.h \
qbsprofilemanager.h \
qbsprofilessettingspage.h \
qbsproject.h \
qbsprojectimporter.h \
qbsprojectmanager.h \
qbsprojectmanager_global.h \
qbsprojectmanagerconstants.h \
qbsprojectmanagerplugin.h \
qbsprojectmanagersettings.h \
qbsprojectparser.h
qbsprojectparser.h \
qbssession.h \
qbssettings.h
SOURCES = \
customqbspropertiesdialog.cpp \
@@ -47,18 +37,18 @@ SOURCES = \
qbscleanstep.cpp \
qbsinstallstep.cpp \
qbskitinformation.cpp \
qbslogsink.cpp \
qbsnodes.cpp \
qbsnodetreebuilder.cpp \
qbsparser.cpp \
qbspmlogging.cpp \
qbsprofilemanager.cpp \
qbsprofilessettingspage.cpp \
qbsproject.cpp \
qbsprojectimporter.cpp \
qbsprojectmanager.cpp \
qbsprojectmanagerplugin.cpp \
qbsprojectmanagersettings.cpp \
qbsprojectparser.cpp
qbsprojectparser.cpp \
qbssession.cpp \
qbssettings.cpp
FORMS = \
customqbspropertiesdialog.ui \

View File

@@ -6,33 +6,8 @@ QtcPlugin {
name: "QbsProjectManager"
type: base.concat(["qmltype-update"])
property var externalQbsIncludes: project.useExternalQbs
? [project.qbs_install_dir + "/include/qbs"] : []
property var externalQbsLibraryPaths: project.useExternalQbs
? [project.qbs_install_dir + '/' + qtc.libDirName] : []
property var externalQbsDynamicLibraries: {
var libs = []
if (!project.useExternalQbs)
return libs;
var suffix = "";
if (qbs.targetOS.contains("windows")) {
libs.push("shell32")
if (qbs.enableDebugCode)
suffix = "d";
}
libs.push("qbscore" + suffix);
return libs
}
Depends { name: "Qt"; submodules: [ "qml", "widgets" ] }
condition: project.buildQbsProjectManager
property bool useInternalQbsProducts: project.qbsSubModuleExists && !project.useExternalQbs
Depends { name: "Qt"; submodules: [ "widgets" ] }
Depends {
name: "qbscore"
condition: product.useInternalQbsProducts
}
Depends { name: "QmlJS" }
Depends { name: "Utils" }
@@ -43,20 +18,6 @@ QtcPlugin {
Depends { name: "QmlJSTools" }
Depends { name: "app_version_header" }
cpp.defines: base.concat([
'QML_BUILD_STATIC_LIB',
'QBS_ENABLE_PROJECT_FILE_UPDATES', // TODO: Take from installed qbscore module
'QBS_INSTALL_DIR="'
+ (project.useExternalQbs
? FileInfo.fromWindowsSeparators(project.qbs_install_dir)
: '')
+ '"'
])
cpp.includePaths: base.concat(externalQbsIncludes)
cpp.libraryPaths: base.concat(externalQbsLibraryPaths)
cpp.rpaths: base.concat(externalQbsLibraryPaths)
cpp.dynamicLibraries: base.concat(externalQbsDynamicLibraries)
files: [
"customqbspropertiesdialog.h",
"customqbspropertiesdialog.cpp",
@@ -75,8 +36,6 @@ QtcPlugin {
"qbsinstallstep.h",
"qbskitinformation.cpp",
"qbskitinformation.h",
"qbslogsink.cpp",
"qbslogsink.h",
"qbsnodes.cpp",
"qbsnodes.h",
"qbsnodetreebuilder.cpp",
@@ -85,6 +44,8 @@ QtcPlugin {
"qbsparser.h",
"qbspmlogging.cpp",
"qbspmlogging.h",
"qbsprofilemanager.cpp",
"qbsprofilemanager.h",
"qbsprofilessettingspage.cpp",
"qbsprofilessettingspage.h",
"qbsprofilessettingswidget.ui",
@@ -92,23 +53,21 @@ QtcPlugin {
"qbsproject.h",
"qbsprojectimporter.cpp",
"qbsprojectimporter.h",
"qbsprojectmanager.cpp",
"qbsprojectmanager.h",
"qbsprojectmanager.qrc",
"qbsprojectmanager_global.h",
"qbsprojectmanagerconstants.h",
"qbsprojectmanagerplugin.cpp",
"qbsprojectmanagerplugin.h",
"qbsprojectmanagersettings.cpp",
"qbsprojectmanagersettings.h",
"qbsprojectparser.cpp",
"qbsprojectparser.h",
"qbssession.cpp",
"qbssession.h",
"qbssettings.cpp",
"qbssettings.h",
]
// QML typeinfo stuff
property bool updateQmlTypeInfo: useInternalQbsProducts
Group {
condition: !updateQmlTypeInfo
name: "qbs qml type info"
qbs.install: true
qbs.installDir: FileInfo.joinPaths(qtc.ide_data_path, "qtcreator",
@@ -121,9 +80,9 @@ QtcPlugin {
]
}
Depends { name: "qbs resources"; condition: updateQmlTypeInfo }
Depends { name: "qbs resources"; condition: project.qbsSubModuleExists }
Rule {
condition: updateQmlTypeInfo
condition: project.qbsSubModuleExists
inputsFromDependencies: ["qbs qml type descriptions", "qbs qml type bundle"]
Artifact {
filePath: "dummy." + input.fileName

View File

@@ -93,7 +93,7 @@ const char XCODE_DEVELOPERPATH[] = "xcode.developerPath";
const char XCODE_SDK[] = "xcode.sdk";
// Settings page
const char QBS_SETTINGS_CATEGORY[] = "YM.qbs";
const char QBS_SETTINGS_CATEGORY[] = "K.Qbs";
const char QBS_SETTINGS_TR_CATEGORY[] = QT_TRANSLATE_NOOP("QbsProjectManager", "Qbs");
const char QBS_SETTINGS_CATEGORY_ICON[] = ":/projectexplorer/images/build.png";

View File

@@ -31,10 +31,11 @@
#include "qbsinstallstep.h"
#include "qbskitinformation.h"
#include "qbsnodes.h"
#include "qbsprofilemanager.h"
#include "qbsprofilessettingspage.h"
#include "qbsproject.h"
#include "qbsprojectmanager.h"
#include "qbsprojectmanagerconstants.h"
#include "qbssettings.h"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
@@ -85,12 +86,13 @@ static QbsProject *currentEditorProject()
class QbsProjectManagerPluginPrivate
{
public:
QbsManager manager;
QbsProfileManager manager;
QbsBuildConfigurationFactory buildConfigFactory;
QbsBuildStepFactory buildStepFactory;
QbsCleanStepFactory cleanStepFactory;
QbsInstallStepFactory installStepFactory;
QbsProfilesSettingsPage profilesSettingsPage;
QbsSettingsPage settingsPage;
QbsProfilesSettingsPage profilesSetttingsPage;
QbsKitAspect qbsKitAspect;
};
@@ -419,7 +421,7 @@ void QbsProjectManagerPlugin::runStepsForProductContextMenu(const QList<Core::Id
const auto * const productNode = dynamic_cast<const QbsProductNode *>(node);
QTC_ASSERT(productNode, return);
runStepsForProducts(project, {QbsProject::uniqueProductName(productNode->qbsProductData())},
runStepsForProducts(project, {productNode->productData().value("full-display-name").toString()},
{stepTypes});
}
@@ -446,13 +448,13 @@ void QbsProjectManagerPlugin::runStepsForProduct(const QList<Core::Id> &stepType
Node *node = currentEditorNode();
if (!node)
return;
auto product = dynamic_cast<QbsProductNode *>(node->parentProjectNode());
if (!product)
auto productNode = dynamic_cast<QbsProductNode *>(node->parentProjectNode());
if (!productNode)
return;
QbsProject *project = currentEditorProject();
if (!project)
return;
runStepsForProducts(project, {QbsProject::uniqueProductName(product->qbsProductData())},
runStepsForProducts(project, {productNode->productData().value("full-display-name").toString()},
{stepTypes});
}
@@ -485,9 +487,9 @@ void QbsProjectManagerPlugin::runStepsForSubprojectContextMenu(const QList<Core:
QTC_ASSERT(subProject, return);
QStringList toBuild;
foreach (const qbs::ProductData &data, subProject->qbsProjectData().allProducts())
toBuild << QbsProject::uniqueProductName(data);
forAllProducts(subProject->projectData(), [&toBuild](const QJsonObject &data) {
toBuild << data.value("full-display-name").toString();
});
runStepsForProducts(project, toBuild, {stepTypes});
}

View File

@@ -1,82 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qbsprojectmanagersettings.h"
#include <coreplugin/icore.h>
namespace QbsProjectManager {
namespace Internal {
static QString settingsId() { return QLatin1String("QbsProjectManager"); }
static QString useCreatorDirKey() { return QLatin1String("useCreatorDir"); }
QbsProjectManagerSettings::QbsProjectManagerSettings()
{
readSettings();
}
void QbsProjectManagerSettings::readSettings()
{
QSettings * const settings = Core::ICore::settings();
settings->beginGroup(settingsId());
m_useCreatorSettings = settings->value(useCreatorDirKey(), true).toBool();
settings->endGroup();
}
void QbsProjectManagerSettings::doWriteSettings()
{
QSettings * const settings = Core::ICore::settings();
settings->beginGroup(settingsId());
settings->setValue(useCreatorDirKey(), m_useCreatorSettings);
settings->endGroup();
}
QbsProjectManagerSettings &QbsProjectManagerSettings::instance()
{
static QbsProjectManagerSettings settings;
return settings;
}
void QbsProjectManagerSettings::setUseCreatorSettingsDirForQbs(bool useCreatorDir)
{
if (instance().m_useCreatorSettings == useCreatorDir)
return;
instance().m_useCreatorSettings = useCreatorDir;
emit instance().settingsBaseChanged();
}
bool QbsProjectManagerSettings::useCreatorSettingsDirForQbs()
{
return instance().m_useCreatorSettings;
}
QString QbsProjectManagerSettings::qbsSettingsBaseDir()
{
return instance().m_useCreatorSettings ? Core::ICore::userResourcePath() : QString();
}
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -25,19 +25,16 @@
#include "qbsprojectparser.h"
#include "qbslogsink.h"
#include "qbsproject.h"
#include "qbsprojectmanagerconstants.h"
#include "qbssettings.h"
#include <coreplugin/icore.h>
#include <utils/qtcassert.h>
#include <qbs.h>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QFutureWatcher>
#include <QJsonArray>
using namespace Utils;
@@ -48,11 +45,11 @@ namespace Internal {
// QbsProjectParser:
// --------------------------------------------------------------------
QbsProjectParser::QbsProjectParser(QbsBuildSystem *buildSystem, QFutureInterface<bool> *fi) :
QbsProjectParser::QbsProjectParser(QbsBuildSystem *buildSystem, QFutureInterface<bool> *fi)
: m_projectFilePath(buildSystem->project()->projectFilePath().toString()),
m_session(buildSystem->session()),
m_fi(fi)
{
m_project = buildSystem->qbsProject();
m_projectFilePath = buildSystem->projectFilePath().toString();
auto * const watcher = new QFutureWatcher<bool>(this);
connect(watcher, &QFutureWatcher<bool>::canceled, this, &QbsProjectParser::cancel);
watcher->setFuture(fi->future());
@@ -60,171 +57,77 @@ QbsProjectParser::QbsProjectParser(QbsBuildSystem *buildSystem, QFutureInterface
QbsProjectParser::~QbsProjectParser()
{
const auto deleteJob = [this](qbs::AbstractJob *job) {
if (!job)
return;
if (job->state() == qbs::AbstractJob::StateFinished) {
job->deleteLater();
return;
}
connect(job, &qbs::AbstractJob::finished, job, &qbs::AbstractJob::deleteLater);
job->cancel();
};
deleteJob(m_qbsSetupProjectJob);
deleteJob(m_ruleExecutionJob);
if (m_session && m_parsing)
m_session->cancelCurrentJob();
m_fi = nullptr; // we do not own m_fi, do not delete
}
void QbsProjectParser::parse(const QVariantMap &config, const Environment &env, const QString &dir,
const QString &configName)
{
QTC_ASSERT(!m_qbsSetupProjectJob, return);
QTC_ASSERT(m_session, return);
QTC_ASSERT(!dir.isEmpty(), return);
m_currentProgressBase = 0;
m_environment = env;
qbs::SetupProjectParameters params;
QJsonObject request;
request.insert("type", "resolve-project");
QVariantMap userConfig = config;
QString specialKey = QLatin1String(Constants::QBS_CONFIG_PROFILE_KEY);
const QString profileName = userConfig.take(specialKey).toString();
params.setTopLevelProfile(profileName);
params.setConfigurationName(configName);
specialKey = QLatin1String(Constants::QBS_FORCE_PROBES_KEY);
params.setForceProbeExecution(userConfig.take(specialKey).toBool());
params.setSettingsDirectory(QbsManager::settings()->baseDirectory());
params.setOverriddenValues(userConfig);
request.insert("top-level-profile",
userConfig.take(Constants::QBS_CONFIG_PROFILE_KEY).toString());
request.insert("configuration-name", configName);
request.insert("force-probe-execution",
userConfig.take(Constants::QBS_FORCE_PROBES_KEY).toBool());
if (QbsSettings::useCreatorSettingsDirForQbs())
request.insert("settings-directory", QbsSettings::qbsSettingsBaseDir());
request.insert("overridden-properties", QJsonObject::fromVariantMap(userConfig));
// Some people don't like it when files are created as a side effect of opening a project,
// People don't like it when files are created as a side effect of opening a project,
// so do not store the build graph if the build directory does not exist yet.
m_dryRun = !QFileInfo::exists(dir);
params.setDryRun(m_dryRun);
request.insert("dry-run", !QFileInfo::exists(dir));
params.setBuildRoot(dir);
params.setProjectFilePath(m_projectFilePath);
params.setOverrideBuildGraphData(true);
params.setEnvironment(env.toProcessEnvironment());
const qbs::Preferences prefs(QbsManager::settings(), profileName);
params.setSearchPaths(prefs.searchPaths(resourcesBaseDirectory()));
params.setPluginPaths(prefs.pluginPaths(pluginsBaseDirectory()));
params.setLibexecPath(libExecDirectory());
params.setProductErrorMode(qbs::ErrorHandlingMode::Relaxed);
params.setPropertyCheckingMode(qbs::ErrorHandlingMode::Relaxed);
params.setLogElapsedTime(!qEnvironmentVariableIsEmpty(Constants::QBS_PROFILING_ENV));
request.insert("build-root", dir);
request.insert("project-file-path", m_projectFilePath);
request.insert("override-build-graph-data", true);
static const auto envToJson = [](const Environment &env) {
QJsonObject envObj;
for (auto it = env.constBegin(); it != env.constEnd(); ++it) {
if (env.isEnabled(it))
envObj.insert(env.key(it), env.value(it));
}
return envObj;
};
request.insert("environment", envToJson(env));
request.insert("error-handling-mode", "relaxed");
request.insert("data-mode", "only-if-changed");
QbsSession::insertRequestedModuleProperties(request);
m_qbsSetupProjectJob = m_project.setupProject(params, QbsManager::logSink(), nullptr);
connect(m_qbsSetupProjectJob, &qbs::AbstractJob::finished,
this, &QbsProjectParser::handleQbsParsingDone);
connect(m_qbsSetupProjectJob, &qbs::AbstractJob::taskStarted,
this, &QbsProjectParser::handleQbsParsingTaskSetup);
connect(m_qbsSetupProjectJob, &qbs::AbstractJob::taskProgress,
this, &QbsProjectParser::handleQbsParsingProgress);
connect(m_session, &QbsSession::projectResolved, this, [this](const ErrorInfo &error) {
m_error = error;
m_projectData = m_session->projectData();
emit done(!m_error.hasError());
});
connect(m_session, &QbsSession::errorOccurred, this, [this] { emit done(false); });
connect(m_session, &QbsSession::taskStarted, this,
[this](const QString &description, int maxProgress) {
Q_UNUSED(description)
if (m_fi)
m_fi->setProgressRange(0, maxProgress);
});
connect(m_session, &QbsSession::maxProgressChanged, this, [this](int maxProgress) {
if (m_fi)
m_fi->setProgressRange(0, maxProgress);
});
connect(m_session, &QbsSession::taskProgress, this, [this](int progress) {
if (m_fi)
m_fi->setProgressValue(progress);
});
m_session->sendRequest(request);
}
void QbsProjectParser::cancel()
{
QTC_ASSERT(m_qbsSetupProjectJob, return);
if (m_ruleExecutionJob)
m_ruleExecutionJob->cancel();
else
m_qbsSetupProjectJob->cancel();
}
qbs::Project QbsProjectParser::qbsProject() const
{
return m_project;
}
qbs::ErrorInfo QbsProjectParser::error()
{
return m_error;
}
void QbsProjectParser::handleQbsParsingDone(bool success)
{
QTC_ASSERT(m_qbsSetupProjectJob, return);
m_project = m_qbsSetupProjectJob->project();
m_error = m_qbsSetupProjectJob->error();
// Do not report the operation as canceled here, as we might want to make overlapping
// parses appear atomic to the user.
emit done(success);
}
void QbsProjectParser::startRuleExecution()
{
qbs::BuildOptions options;
options.setDryRun(m_dryRun);
options.setExecuteRulesOnly(true);
m_ruleExecutionJob = m_project.buildAllProducts(
options, qbs::Project::ProductSelectionWithNonDefault, nullptr);
connect(m_ruleExecutionJob, &qbs::AbstractJob::finished,
this, &QbsProjectParser::handleRuleExecutionDone);
connect(m_ruleExecutionJob, &qbs::AbstractJob::taskStarted,
this, &QbsProjectParser::handleQbsParsingTaskSetup);
connect(m_ruleExecutionJob, &qbs::AbstractJob::taskProgress,
this, &QbsProjectParser::handleQbsParsingProgress);
}
void QbsProjectParser::handleRuleExecutionDone()
{
QTC_ASSERT(m_ruleExecutionJob, return);
// Execution of some very dynamic rules might fail due
// to artifacts not being present. No genuine errors will get lost, as they will re-appear
// on the next build attempt.
emit ruleExecutionDone();
}
void QbsProjectParser::handleQbsParsingProgress(int progress)
{
if (m_fi)
m_fi->setProgressValue(m_currentProgressBase + progress);
}
void QbsProjectParser::handleQbsParsingTaskSetup(const QString &description, int maximumProgressValue)
{
Q_UNUSED(description)
if (m_fi) {
m_currentProgressBase = m_fi->progressValue();
m_fi->setProgressRange(0, m_currentProgressBase + maximumProgressValue);
}
}
QString QbsProjectParser::resourcesBaseDirectory() const
{
const QString qbsInstallDir = QLatin1String(QBS_INSTALL_DIR);
if (!qbsInstallDir.isEmpty())
return qbsInstallDir;
return Core::ICore::resourcePath() + QLatin1String("/qbs");
}
QString QbsProjectParser::libExecDirectory() const
{
const QString qbsInstallDir = QLatin1String(QBS_INSTALL_DIR);
if (!qbsInstallDir.isEmpty())
return qbsInstallDir + QLatin1String("/libexec");
return Core::ICore::libexecPath();
}
QString QbsProjectParser::pluginsBaseDirectory() const
{
const QString qbsInstallDir = QLatin1String(QBS_INSTALL_DIR);
// Note: We assume here that Qt Creator and qbs use the same name for their library dir.
const QString qbsLibDirName = QLatin1String(IDE_LIBRARY_BASENAME);
if (!qbsInstallDir.isEmpty())
return qbsInstallDir + QLatin1Char('/') + qbsLibDirName;
if (HostOsInfo::isMacHost())
return QDir::cleanPath(QCoreApplication::applicationDirPath()
+ QLatin1String("/../PlugIns"));
else
return QDir::cleanPath(QCoreApplication::applicationDirPath()
+ QLatin1String("/../" IDE_LIBRARY_BASENAME "/qtcreator/plugins"));
if (m_session)
m_session->cancelCurrentJob();
}
} // namespace Internal

View File

@@ -25,13 +25,14 @@
#pragma once
#include "qbssession.h"
#include <utils/environment.h>
#include <QFutureInterface>
#include <QJsonObject>
#include <QObject>
#include <qbs.h>
namespace QbsProjectManager {
namespace Internal {
@@ -47,38 +48,24 @@ public:
void parse(const QVariantMap &config, const Utils::Environment &env, const QString &dir,
const QString &configName);
void startRuleExecution();
void cancel();
Utils::Environment environment() const { return m_environment; }
qbs::Project qbsProject() const;
qbs::ErrorInfo error();
QbsSession *session() const { return m_session; }
QJsonObject projectData() const { return m_projectData; }
ErrorInfo error() const { return m_error; }
signals:
void done(bool success);
void ruleExecutionDone();
private:
void handleQbsParsingDone(bool success);
void handleQbsParsingProgress(int progress);
void handleQbsParsingTaskSetup(const QString &description, int maximumProgressValue);
QString pluginsBaseDirectory() const;
QString resourcesBaseDirectory() const;
QString libExecDirectory() const;
void handleRuleExecutionDone();
Utils::Environment m_environment;
QString m_projectFilePath;
qbs::SetupProjectJob *m_qbsSetupProjectJob = nullptr;
qbs::BuildJob *m_ruleExecutionJob = nullptr;
qbs::ErrorInfo m_error;
qbs::Project m_project;
bool m_dryRun;
const QString m_projectFilePath;
QbsSession * const m_session;
ErrorInfo m_error;
QJsonObject m_projectData;
bool m_parsing = false;
QFutureInterface<bool> *m_fi = nullptr;
int m_currentProgressBase = 0;
};
} // namespace Internal

View File

@@ -0,0 +1,687 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qbssession.h"
#include "qbspmlogging.h"
#include "qbsprojectmanagerconstants.h"
#include "qbssettings.h"
#include <coreplugin/messagemanager.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/taskhub.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QDir>
#include <QEventLoop>
#include <QJsonArray>
#include <QJsonDocument>
#include <QProcess>
#include <QProcessEnvironment>
#include <QTimer>
using namespace ProjectExplorer;
using namespace Utils;
namespace QbsProjectManager {
namespace Internal {
QStringList arrayToStringList(const QJsonValue &array)
{
return transform<QStringList>(array.toArray(),
[](const QJsonValue &v) { return v.toString(); });
}
const QByteArray packetStart = "qbsmsg:";
class Packet
{
public:
enum class Status { Incomplete, Complete, Invalid };
Status parseInput(QByteArray &input)
{
if (m_expectedPayloadLength == -1) {
const int packetStartOffset = input.indexOf(packetStart);
if (packetStartOffset == -1)
return Status::Incomplete;
const int numberOffset = packetStartOffset + packetStart.length();
const int newLineOffset = input.indexOf('\n', numberOffset);
if (newLineOffset == -1)
return Status::Incomplete;
const QByteArray sizeString = input.mid(numberOffset, newLineOffset - numberOffset);
bool isNumber;
const int payloadLen = sizeString.toInt(&isNumber);
if (!isNumber || payloadLen < 0)
return Status::Invalid;
m_expectedPayloadLength = payloadLen;
input.remove(0, newLineOffset + 1);
}
const int bytesToAdd = m_expectedPayloadLength - m_payload.length();
QTC_ASSERT(bytesToAdd >= 0, return Status::Invalid);
m_payload += input.left(bytesToAdd);
input.remove(0, bytesToAdd);
return isComplete() ? Status::Complete : Status::Incomplete;
}
QJsonObject retrievePacket()
{
QTC_ASSERT(isComplete(), return QJsonObject());
const auto packet = QJsonDocument::fromJson(QByteArray::fromBase64(m_payload)).object();
m_payload.clear();
m_expectedPayloadLength = -1;
return packet;
}
static QByteArray createPacket(const QJsonObject &packet)
{
const QByteArray jsonData = QJsonDocument(packet).toJson().toBase64();
return QByteArray(packetStart).append(QByteArray::number(jsonData.length())).append('\n')
.append(jsonData);
}
private:
bool isComplete() const { return m_payload.length() == m_expectedPayloadLength; }
QByteArray m_payload;
int m_expectedPayloadLength = -1;
};
class PacketReader : public QObject
{
Q_OBJECT
public:
PacketReader(QObject *parent) : QObject(parent) {}
void handleData(const QByteArray &data)
{
m_incomingData += data;
handleData();
}
signals:
void packetReceived(const QJsonObject &packet);
void errorOccurred(const QString &msg);
private:
void handleData()
{
switch (m_currentPacket.parseInput(m_incomingData)) {
case Packet::Status::Invalid:
emit errorOccurred(tr("Received invalid input."));
break;
case Packet::Status::Complete:
emit packetReceived(m_currentPacket.retrievePacket());
handleData();
break;
case Packet::Status::Incomplete:
break;
}
}
QByteArray m_incomingData;
Packet m_currentPacket;
};
class QbsSession::Private
{
public:
QProcess *qbsProcess = nullptr;
PacketReader *packetReader = nullptr;
QJsonObject currentRequest;
QJsonObject projectData;
QEventLoop eventLoop;
QJsonObject reply;
QHash<QString, QStringList> generatedFilesForSources;
optional<Error> lastError;
State state = State::Inactive;
};
QbsSession::QbsSession(QObject *parent) : QObject(parent), d(new Private)
{
initialize();
}
void QbsSession::initialize()
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert("QT_FORCE_STDERR_LOGGING", "1");
d->packetReader = new PacketReader(this);
d->qbsProcess = new QProcess(this);
d->qbsProcess->setProcessEnvironment(env);
connect(d->qbsProcess, &QProcess::readyReadStandardOutput, this, [this] {
d->packetReader->handleData(d->qbsProcess->readAllStandardOutput());
});
connect(d->qbsProcess, &QProcess::readyReadStandardError, this, [this] {
qCDebug(qbsPmLog) << "[qbs stderr]: " << d->qbsProcess->readAllStandardError();
});
connect(d->qbsProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError e) {
d->eventLoop.exit(1);
if (state() == State::ShuttingDown || state() == State::Inactive)
return;
switch (e) {
case QProcess::FailedToStart:
setError(Error::QbsFailedToStart);
break;
case QProcess::WriteError:
case QProcess::ReadError:
setError(Error::ProtocolError);
break;
case QProcess::Crashed:
case QProcess::Timedout:
case QProcess::UnknownError:
break;
}
});
connect(d->qbsProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[this] {
d->qbsProcess->deleteLater();
switch (state()) {
case State::Inactive:
break;
case State::ShuttingDown:
setInactive();
break;
case State::Active:
setError(Error::QbsQuit);
break;
case State::Initializing:
setError(Error::ProtocolError);
break;
}
d->qbsProcess = nullptr;
});
connect(d->packetReader, &PacketReader::errorOccurred, this, [this](const QString &msg) {
qCDebug(qbsPmLog) << "session error" << msg;
setError(Error::ProtocolError);
});
connect(d->packetReader, &PacketReader::packetReceived, this, &QbsSession::handlePacket);
d->state = State::Initializing;
d->qbsProcess->start(QbsSettings::qbsExecutableFilePath().toString(), {"session"});
}
void QbsSession::sendQuitPacket()
{
d->qbsProcess->write(Packet::createPacket(QJsonObject{qMakePair(QString("type"),
QJsonValue("quit"))}));
}
QbsSession::~QbsSession()
{
if (d->packetReader)
d->packetReader->disconnect(this);
if (d->qbsProcess) {
d->qbsProcess->disconnect(this);
quit();
if (d->qbsProcess->state() == QProcess::Running && !d->qbsProcess->waitForFinished(10000))
d->qbsProcess->terminate();
if (d->qbsProcess->state() == QProcess::Running && !d->qbsProcess->waitForFinished(10000))
d->qbsProcess->kill();
d->qbsProcess->waitForFinished(1000);
}
delete d;
}
QbsSession::State QbsSession::state() const
{
return d->state;
}
optional<QbsSession::Error> QbsSession::lastError() const
{
return d->lastError;
}
QString QbsSession::errorString(QbsSession::Error error)
{
switch (error) {
case Error::QbsQuit:
return tr("The qbs process quit unexpectedly");
case Error::QbsFailedToStart:
return tr("The qbs process failed to start.");
case Error::ProtocolError:
return tr("The qbs process sent invalid data.");
case Error::VersionMismatch:
return tr("The qbs API level is not compatible with what Qt Creator expects.");
}
return QString(); // For dumb compilers.
}
QJsonObject QbsSession::projectData() const
{
return d->projectData;
}
void QbsSession::sendRequest(const QJsonObject &request)
{
QTC_ASSERT(d->currentRequest.isEmpty(),
qDebug() << request.value("type").toString()
<< d->currentRequest.value("type").toString(); return);
d->currentRequest = request;
const QString logLevelFromEnv = qEnvironmentVariable("QBS_LOG_LEVEL");
if (!logLevelFromEnv.isEmpty())
d->currentRequest.insert("log-level", logLevelFromEnv);
if (!qEnvironmentVariableIsEmpty(Constants::QBS_PROFILING_ENV))
d->currentRequest.insert("log-time", true);
if (d->state == State::Active)
sendQueuedRequest();
else if (d->state == State::Inactive)
initialize();
}
void QbsSession::cancelCurrentJob()
{
if (d->state == State::Active)
sendRequest(QJsonObject{qMakePair(QString("type"), QJsonValue("cancel-job"))});
}
void QbsSession::quit()
{
if (d->state == State::ShuttingDown || d->state == State::Inactive)
return;
d->state = State::ShuttingDown;
sendQuitPacket();
}
void QbsSession::requestFilesGeneratedFrom(const QHash<QString, QStringList> &sourceFilesPerProduct)
{
QJsonObject request;
request.insert("type", "get-generated-files-for-sources");
QJsonArray products;
for (auto it = sourceFilesPerProduct.cbegin(); it != sourceFilesPerProduct.cend(); ++it) {
QJsonObject product;
product.insert("full-display-name", it->first());
QJsonArray requests;
for (const QString &sourceFile : it.value())
requests << QJsonObject({qMakePair(QString("source-file"), sourceFile)});
product.insert("requests", requests);
products << product;
}
request.insert("products", products);
sendRequest(request);
}
QStringList QbsSession::filesGeneratedFrom(const QString &sourceFile) const
{
return d->generatedFilesForSources.value(sourceFile);
}
FileChangeResult QbsSession::addFiles(const QStringList &files, const QString &product,
const QString &group)
{
return updateFileList("add-files", files, product, group);
}
FileChangeResult QbsSession::removeFiles(const QStringList &files, const QString &product,
const QString &group)
{
return updateFileList("remove-files", files, product, group);
}
RunEnvironmentResult QbsSession::getRunEnvironment(
const QString &product,
const QProcessEnvironment &baseEnv,
const QStringList &config)
{
d->reply = QJsonObject();
QJsonObject request;
request.insert("type", "get-run-environment");
request.insert("product", product);
QJsonObject inEnv;
const QStringList baseEnvKeys = baseEnv.keys();
for (const QString &key : baseEnvKeys)
inEnv.insert(key, baseEnv.value(key));
request.insert("base-environment", inEnv);
request.insert("config", QJsonArray::fromStringList(config));
sendRequest(request);
QTimer::singleShot(10000, this, [this] { d->eventLoop.exit(1); });
if (d->eventLoop.exec(QEventLoop::ExcludeUserInputEvents) == 1)
return RunEnvironmentResult(ErrorInfo(tr("Request timed out.")));
QProcessEnvironment env;
const QJsonObject outEnv = d->reply.value("full-environment").toObject();
for (auto it = outEnv.begin(); it != outEnv.end(); ++it)
env.insert(it.key(), it.value().toString());
return RunEnvironmentResult(env, getErrorInfo(d->reply));
}
void QbsSession::insertRequestedModuleProperties(QJsonObject &request)
{
request.insert("module-properties", QJsonArray::fromStringList({
"cpp.commonCompilerFlags",
"cpp.compilerVersionMajor",
"cpp.compilerVersionMinor",
"cpp.cLanguageVersion",
"cpp.cxxLanguageVersion",
"cpp.cxxStandardLibrary",
"cpp.defines",
"cpp.driverFlags",
"cpp.enableExceptions",
"cpp.enableRtti",
"cpp.exceptionHandlingModel",
"cpp.frameworkPaths",
"cpp.includePaths",
"cpp.machineType",
"cpp.minimumDarwinVersion",
"cpp.minimumDarwinVersionCompilerFlag",
"cpp.platformCommonCompilerFlags",
"cpp.platformDriverFlags",
"cpp.positionIndependentCode",
"cpp.systemFrameworkPaths",
"cpp.systemIncludePaths",
"cpp.target",
"cpp.targetArch",
"cpp.useCPrecompiledHeader",
"cpp.useCxxPrecompiledHeader",
"cpp.useObjcPrecompiledHeader",
"cpp.useObjcxxPrecompiledHeader",
"qbs.targetOS",
"qbs.toolchain",
"Qt.core.enableKeywords",
"Qt.core.version",
}));
}
// TODO: We can do better: Give out a (managed) session pointer here. Then we can re-use it
// if the user chooses the imported project, saving the second build graph load.
QbsSession::BuildGraphInfo QbsSession::getBuildGraphInfo(const FilePath &bgFilePath,
const QStringList &requestedProperties)
{
const QFileInfo bgFi = bgFilePath.toFileInfo();
QDir buildRoot = bgFi.dir();
buildRoot.cdUp();
QJsonObject request;
request.insert("type", "resolve-project");
request.insert("restore-behavior", "restore-only");
request.insert("configuration-name", bgFi.completeBaseName());
if (QbsSettings::useCreatorSettingsDirForQbs())
request.insert("settings-directory", QbsSettings::qbsSettingsBaseDir());
request.insert("build-root", buildRoot.path());
request.insert("error-handling-mode", "relaxed");
request.insert("data-mode", "only-if-changed");
request.insert("module-properties", QJsonArray::fromStringList(requestedProperties));
QbsSession session(nullptr);
session.sendRequest(request);
QJsonObject reply;
BuildGraphInfo bgInfo;
bgInfo.bgFilePath = bgFilePath;
QTimer::singleShot(10000, &session, [&session] { session.d->eventLoop.exit(1); });
connect(&session, &QbsSession::errorOccurred, [&] {
bgInfo.error = ErrorInfo(tr("Failed to load qbs build graph."));
});
connect(&session, &QbsSession::projectResolved, [&](const ErrorInfo &error) {
bgInfo.error = error;
session.d->eventLoop.quit();
});
if (session.d->eventLoop.exec(QEventLoop::ExcludeUserInputEvents) == 1) {
bgInfo.error = ErrorInfo(tr("Request timed out."));
return bgInfo;
}
if (bgInfo.error.hasError())
return bgInfo;
bgInfo.profileData = session.projectData().value("profile-data").toObject().toVariantMap();
bgInfo.overriddenProperties = session.projectData().value("overridden-properties").toObject()
.toVariantMap();
QStringList props = requestedProperties;
forAllProducts(session.projectData(), [&](const QJsonObject &product) {
if (props.empty())
return;
for (auto it = props.begin(); it != props.end();) {
const QVariant value = product.value("module-properties").toObject().value(*it);
if (value.isValid()) {
bgInfo.requestedProperties.insert(*it, value);
it = props.erase(it);
} else {
++it;
}
}
});
return bgInfo;
}
void QbsSession::handlePacket(const QJsonObject &packet)
{
const QString type = packet.value("type").toString();
if (type == "hello") {
QTC_CHECK(d->state == State::Initializing);
if (packet.value("api-compat-level").toInt() != 1) {
setError(Error::VersionMismatch);
return;
}
d->state = State::Active;
sendQueuedRequest();
} else if (type == "project-resolved") {
setProjectDataFromReply(packet, true);
emit projectResolved(getErrorInfo(packet));
} else if (type == "project-built") {
setProjectDataFromReply(packet, false);
emit projectBuilt(getErrorInfo(packet));
} else if (type == "project-cleaned") {
emit projectCleaned(getErrorInfo(packet));
} else if (type == "install-done") {
emit projectInstalled(getErrorInfo(packet));
} else if (type == "log-data") {
Core::MessageManager::write("[qbs] " + packet.value("message").toString(),
Core::MessageManager::Silent);
} else if (type == "warning") {
const ErrorInfo errorInfo = ErrorInfo(packet.value("warning").toObject());
// TODO: This loop occurs a lot. Factor it out.
for (const ErrorInfoItem &item : errorInfo.items) {
TaskHub::addTask(Task::Warning, item.description,
ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM,
item.filePath, item.line);
}
} else if (type == "task-started") {
emit taskStarted(packet.value("description").toString(),
packet.value("max-progress").toInt());
} else if (type == "task-progress") {
emit taskProgress(packet.value("progress").toInt());
} else if (type == "new-max-progress") {
emit maxProgressChanged(packet.value("max-progress").toInt());
} else if (type == "generated-files-for-source") {
QHash<QString, QStringList> generatedFiles;
for (const QJsonValue &product : packet.value("products").toArray()) {
for (const QJsonValue &r : product.toObject().value("results").toArray()) {
const QJsonObject result = r.toObject();
generatedFiles[result.value("source-file").toString()]
<< arrayToStringList(result.value("generated-files").toArray());
}
}
if (generatedFiles != d->generatedFilesForSources) {
d->generatedFilesForSources = generatedFiles;
emit newGeneratedFilesForSources(generatedFiles);
}
} else if (type == "command-description") {
emit commandDescription(packet.value("message").toString());
} else if (type == "files-added" || type == "files-removed") {
handleFileListUpdated(packet);
} else if (type == "process-result") {
emit processResult(
FilePath::fromString(packet.value("executable-file-path").toString()),
arrayToStringList(packet.value("arguments")),
FilePath::fromString(packet.value("working-directory").toString()),
arrayToStringList(packet.value("stdout")),
arrayToStringList(packet.value("stderr")),
packet.value("success").toBool());
} else if (type == "run-environment") {
d->reply = packet;
d->eventLoop.quit();
}
}
void QbsSession::sendQueuedRequest()
{
sendRequestNow(d->currentRequest);
d->currentRequest = QJsonObject();
}
void QbsSession::sendRequestNow(const QJsonObject &request)
{
QTC_ASSERT(d->state == State::Active, return);
if (!request.isEmpty())
d->qbsProcess->write(Packet::createPacket(request));
}
ErrorInfo QbsSession::getErrorInfo(const QJsonObject &packet)
{
return ErrorInfo(packet.value("error").toObject());
}
void QbsSession::setProjectDataFromReply(const QJsonObject &packet, bool withBuildSystemFiles)
{
const QJsonObject projectData = packet.value("project-data").toObject();
if (!projectData.isEmpty()) {
const QJsonValue buildSystemFiles = d->projectData.value("build-system-files");
d->projectData = projectData;
if (!withBuildSystemFiles)
d->projectData.insert("build-system-files", buildSystemFiles);
}
}
void QbsSession::setError(QbsSession::Error error)
{
d->lastError = error;
setInactive();
emit errorOccurred(error);
}
void QbsSession::setInactive()
{
if (d->state == State::Inactive)
return;
d->state = State::Inactive;
d->qbsProcess->disconnect(this);
d->currentRequest = QJsonObject();
d->packetReader->disconnect(this);
d->packetReader->deleteLater();
d->packetReader = nullptr;
if (d->qbsProcess->state() == QProcess::Running)
sendQuitPacket();
d->qbsProcess = nullptr;
}
FileChangeResult QbsSession::updateFileList(const char *action, const QStringList &files,
const QString &product, const QString &group)
{
if (d->state != State::Active)
return FileChangeResult(files, tr("The qbs session is not in a valid state."));
sendRequestNow(QJsonObject{
{"type", QLatin1String(action)},
{"files", QJsonArray::fromStringList(files)},
{"product", product},
{"group", group}
});
return FileChangeResult(QStringList());
}
void QbsSession::handleFileListUpdated(const QJsonObject &reply)
{
setProjectDataFromReply(reply, false);
const QStringList failedFiles = arrayToStringList(reply.value("failed-files"));
if (!failedFiles.isEmpty()) {
Core::MessageManager::write(tr("Failed to update files in Qbs project: %1.\n"
"The affected files are: \n\t%2")
.arg(getErrorInfo(reply).toString(),
failedFiles.join("\n\t")),
Core::MessageManager::ModeSwitch);
}
emit fileListUpdated();
}
ErrorInfoItem::ErrorInfoItem(const QJsonObject &data)
{
description = data.value("description").toString();
const QJsonObject locationData = data.value("location").toObject();
filePath = FilePath::fromString(locationData.value("file-path").toString());
line = locationData.value("line").toInt(-1);
}
QString ErrorInfoItem::toString() const
{
QString s = filePath.toUserOutput();
if (!s.isEmpty() && line != -1)
s.append(':').append(QString::number(line));
if (!s.isEmpty())
s.append(':');
return s.append(description);
}
ErrorInfo::ErrorInfo(const QJsonObject &data)
{
for (const QJsonValue &v : data.value("items").toArray())
items << ErrorInfoItem(v.toObject());
}
ErrorInfo::ErrorInfo(const QString &msg)
{
items << ErrorInfoItem(msg);
}
QString ErrorInfo::toString() const
{
return transform<QStringList>(items, [](const ErrorInfoItem &i) { return i.toString(); })
.join('\n');
}
void forAllProducts(const QJsonObject &project, const WorkerFunction &productFunction)
{
for (const QJsonValue &p : project.value("products").toArray())
productFunction(p.toObject());
for (const QJsonValue &p : project.value("sub-projects").toArray())
forAllProducts(p.toObject(), productFunction);
}
void forAllArtifacts(const QJsonObject &product, ArtifactType type,
const WorkerFunction &artifactFunction)
{
if (type == ArtifactType::Source || type == ArtifactType::All) {
for (const QJsonValue &g : product.value("groups").toArray())
forAllArtifacts(g.toObject(), artifactFunction);
}
if (type == ArtifactType::Generated || type == ArtifactType::All) {
for (const QJsonValue &v : product.value("generated-artifacts").toArray())
artifactFunction(v.toObject());
}
}
void forAllArtifacts(const QJsonObject &group, const WorkerFunction &artifactFunction)
{
for (const QJsonValue &v : group.value("source-artifacts").toArray())
artifactFunction(v.toObject());
for (const QJsonValue &v : group.value("source-artifacts-from-wildcards").toArray())
artifactFunction(v.toObject());
}
Location locationFromObject(const QJsonObject &o)
{
const QJsonObject loc = o.value("location").toObject();
return Location(FilePath::fromString(loc.value("file-path").toString()),
loc.value("line").toInt());
}
} // namespace Internal
} // namespace QbsProjectManager
#include <qbssession.moc>

View File

@@ -0,0 +1,198 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <utils/fileutils.h>
#include <utils/optional.h>
#include <QHash>
#include <QJsonObject>
#include <QObject>
#include <QProcess>
#include <QString>
#include <QVariant>
#include <functional>
namespace ProjectExplorer { class Target; }
namespace QbsProjectManager {
namespace Internal {
class ErrorInfoItem
{
public:
ErrorInfoItem(const QJsonObject &data);
ErrorInfoItem(const QString &msg) : description(msg) {}
QString toString() const;
QString description;
Utils::FilePath filePath;
int line = -1;
};
class ErrorInfo
{
public:
ErrorInfo() = default;
ErrorInfo(const QJsonObject &data);
ErrorInfo(const QString &msg);
QString toString() const;
bool hasError() const { return !items.isEmpty(); }
QList<ErrorInfoItem> items;
};
template<typename Data> class SynchronousRequestResult
{
public:
ErrorInfo error() const { return m_error; }
SynchronousRequestResult(const Data &d, const ErrorInfo &e = {}) : m_error(e), m_data(d) {}
SynchronousRequestResult(const ErrorInfo &e) : SynchronousRequestResult(Data(), e) {}
protected:
const ErrorInfo m_error;
const Data m_data;
};
class FileChangeResult : public SynchronousRequestResult<QStringList>
{
public:
using SynchronousRequestResult::SynchronousRequestResult;
QStringList failedFiles () const { return m_data; }
};
class RunEnvironmentResult : public SynchronousRequestResult<QProcessEnvironment>
{
public:
using SynchronousRequestResult::SynchronousRequestResult;
QProcessEnvironment environment() { return m_data; }
};
// TODO: Put the helper function somewhere else. E.g. in qbsnodes.cpp we don't want a session include.
QStringList arrayToStringList(const QJsonValue &array);
using WorkerFunction = std::function<void(const QJsonObject &)>;
void forAllProducts(const QJsonObject &projectData, const WorkerFunction &productFunction);
enum class ArtifactType { Source, Generated, All };
void forAllArtifacts(const QJsonObject &product, ArtifactType type,
const WorkerFunction &artifactFunction);
void forAllArtifacts(const QJsonObject &group, const WorkerFunction &artifactFunction);
class Location
{
public:
Location(const Utils::FilePath &p, int l) : filePath(p), line (l) {}
const Utils::FilePath filePath;
const int line;
};
Location locationFromObject(const QJsonObject &o); // Project, Product or Group
class QbsSession : public QObject
{
Q_OBJECT
public:
explicit QbsSession(QObject *parent = nullptr);
~QbsSession() override;
enum class State { Initializing, Active, ShuttingDown, Inactive };
enum class Error { QbsFailedToStart, QbsQuit, ProtocolError, VersionMismatch };
State state() const;
Utils::optional<Error> lastError() const;
static QString errorString(Error error);
QJsonObject projectData() const;
void sendRequest(const QJsonObject &request);
void cancelCurrentJob();
void quit();
void requestFilesGeneratedFrom(const QHash<QString, QStringList> &sourceFilesPerProduct);
QStringList filesGeneratedFrom(const QString &sourceFile) const;
FileChangeResult addFiles(const QStringList &files, const QString &product,
const QString &group);
FileChangeResult removeFiles(const QStringList &files, const QString &product,
const QString &group);
RunEnvironmentResult getRunEnvironment(const QString &product,
const QProcessEnvironment &baseEnv,
const QStringList &config);
static void insertRequestedModuleProperties(QJsonObject &request);
class BuildGraphInfo
{
public:
Utils::FilePath bgFilePath;
QVariantMap overriddenProperties;
QVariantMap profileData;
QVariantMap requestedProperties;
ErrorInfo error;
};
static BuildGraphInfo getBuildGraphInfo(const Utils::FilePath &bgFilePath,
const QStringList &requestedProperties);
signals:
void errorOccurred(Error lastError);
void projectResolved(const ErrorInfo &error);
void projectBuilt(const ErrorInfo &error);
void projectCleaned(const ErrorInfo &error);
void projectInstalled(const ErrorInfo &error);
void newGeneratedFilesForSources(const QHash<QString, QStringList> &generatedFiles);
void taskStarted(const QString &description, int maxProgress);
void maxProgressChanged(int maxProgress);
void taskProgress(int progress);
void commandDescription(const QString &description);
void processResult(
const Utils::FilePath &executable,
const QStringList &arguments,
const Utils::FilePath &workingDir,
const QStringList &stdOut,
const QStringList &stdErr,
bool success);
void fileListUpdated();
private:
void initialize();
void sendQuitPacket();
void handlePacket(const QJsonObject &packet);
void sendQueuedRequest();
void sendRequestNow(const QJsonObject &request);
ErrorInfo getErrorInfo(const QJsonObject &packet);
void setProjectDataFromReply(const QJsonObject &packet, bool withBuildSystemFiles);
void setError(Error error);
void setInactive();
FileChangeResult updateFileList(const char *action, const QStringList &files,
const QString &product, const QString &group);
void handleFileListUpdated(const QJsonObject &reply);
class Private;
Private * const d;
};
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -0,0 +1,192 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qbssettings.h"
#include "qbsprojectmanagerconstants.h"
#include <app/app_version.h>
#include <coreplugin/icore.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/pathchooser.h>
#include <QCoreApplication>
#include <QCheckBox>
#include <QFormLayout>
#include <QLabel>
#include <QProcess>
using namespace Utils;
namespace QbsProjectManager {
namespace Internal {
const char QBS_EXE_KEY[] = "QbsProjectManager/QbsExecutable";
const char USE_CREATOR_SETTINGS_KEY[] = "QbsProjectManager/useCreatorDir";
static bool operator==(const QbsSettingsData &s1, const QbsSettingsData &s2)
{
return s1.qbsExecutableFilePath == s2.qbsExecutableFilePath
&& s1.useCreatorSettings == s2.useCreatorSettings;
}
static bool operator!=(const QbsSettingsData &s1, const QbsSettingsData &s2)
{
return !(s1 == s2);
}
FilePath QbsSettings::qbsExecutableFilePath()
{
const QString fileName = HostOsInfo::withExecutableSuffix("qbs");
FilePath candidate = instance().m_settings.qbsExecutableFilePath;
if (!candidate.exists()) {
candidate = FilePath::fromString(QCoreApplication::applicationDirPath())
.pathAppended(fileName);
}
if (!candidate.exists())
candidate = Environment::systemEnvironment().searchInPath(fileName);
return candidate;
}
bool QbsSettings::useCreatorSettingsDirForQbs()
{
return instance().m_settings.useCreatorSettings;
}
QString QbsSettings::qbsSettingsBaseDir()
{
return useCreatorSettingsDirForQbs() ? Core::ICore::userResourcePath() : QString();
}
QbsSettings &QbsSettings::instance()
{
static QbsSettings theSettings;
return theSettings;
}
void QbsSettings::setSettingsData(const QbsSettingsData &settings)
{
if (instance().m_settings != settings) {
instance().m_settings = settings;
instance().storeSettings();
emit instance().settingsChanged();
}
}
QbsSettings::QbsSettings()
{
loadSettings();
}
void QbsSettings::loadSettings()
{
QSettings * const s = Core::ICore::settings();
m_settings.qbsExecutableFilePath = FilePath::fromString(s->value(QBS_EXE_KEY).toString());
m_settings.useCreatorSettings = s->value(USE_CREATOR_SETTINGS_KEY, true).toBool();
}
void QbsSettings::storeSettings() const
{
QSettings * const s = Core::ICore::settings();
s->setValue(QBS_EXE_KEY, m_settings.qbsExecutableFilePath.toString());
s->setValue(USE_CREATOR_SETTINGS_KEY, m_settings.useCreatorSettings);
}
class QbsSettingsPage::SettingsWidget : public QWidget
{
Q_DECLARE_TR_FUNCTIONS(QbsProjectManager::Internal::QbsSettingsPage)
public:
SettingsWidget()
{
m_qbsExePathChooser.setExpectedKind(PathChooser::ExistingCommand);
m_qbsExePathChooser.setFileName(QbsSettings::qbsExecutableFilePath());
m_versionLabel.setText(getQbsVersion());
m_settingsDirCheckBox.setText(tr("Use %1 settings directory for Qbs")
.arg(Core::Constants::IDE_DISPLAY_NAME));
m_settingsDirCheckBox.setChecked(QbsSettings::useCreatorSettingsDirForQbs());
const auto layout = new QFormLayout(this);
layout->addRow(&m_settingsDirCheckBox);
layout->addRow(tr("Path to qbs executable:"), &m_qbsExePathChooser);
layout->addRow(tr("Qbs version:"), &m_versionLabel);
}
void apply()
{
QbsSettingsData settings;
if (m_qbsExePathChooser.isValid())
settings.qbsExecutableFilePath = m_qbsExePathChooser.fileName();
settings.useCreatorSettings = m_settingsDirCheckBox.isChecked();
QbsSettings::setSettingsData(settings);
}
private:
static QString getQbsVersion()
{
QProcess qbsProc;
qbsProc.start(QbsSettings::qbsExecutableFilePath().toString(), {"--version"});
if (!qbsProc.waitForStarted(3000) || !qbsProc.waitForFinished(5000)
|| qbsProc.exitCode() != 0) {
return tr("Failed to retrieve version.");
}
return QString::fromLocal8Bit(qbsProc.readAllStandardOutput()).trimmed();
}
PathChooser m_qbsExePathChooser;
QLabel m_versionLabel;
QCheckBox m_settingsDirCheckBox;
};
QbsSettingsPage::QbsSettingsPage()
{
setId("A.QbsProjectManager.QbsSettings");
setDisplayName(tr("General"));
setCategory(Constants::QBS_SETTINGS_CATEGORY);
setDisplayCategory(QCoreApplication::translate("QbsProjectManager",
Constants::QBS_SETTINGS_TR_CATEGORY));
}
QWidget *QbsSettingsPage::widget()
{
if (!m_widget)
m_widget = new SettingsWidget;
return m_widget;
}
void QbsSettingsPage::apply()
{
if (m_widget)
m_widget->apply();
}
void QbsSettingsPage::finish()
{
delete m_widget;
}
} // namespace Internal
} // namespace QbsProjectManager

View File

@@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
@@ -25,32 +25,57 @@
#pragma once
#include <coreplugin/dialogs/ioptionspage.h>
#include <utils/fileutils.h>
#include <QObject>
#include <QPointer>
namespace QbsProjectManager {
namespace Internal {
class QbsProjectManagerSettings : public QObject
class QbsSettingsData {
public:
Utils::FilePath qbsExecutableFilePath;
bool useCreatorSettings = true;
};
class QbsSettings : public QObject
{
Q_OBJECT
public:
static QbsProjectManagerSettings &instance();
static QbsSettings &instance();
static void setUseCreatorSettingsDirForQbs(bool useCreatorDir);
static Utils::FilePath qbsExecutableFilePath();
static bool useCreatorSettingsDirForQbs();
static QString qbsSettingsBaseDir();
static void writeSettings() { instance().doWriteSettings(); }
static void setSettingsData(const QbsSettingsData &settings);
signals:
void settingsBaseChanged();
void settingsChanged();
private:
QbsProjectManagerSettings();
QbsSettings();
void loadSettings();
void storeSettings() const;
void readSettings();
void doWriteSettings();
QbsSettingsData m_settings;
};
bool m_useCreatorSettings;
class QbsSettingsPage : public Core::IOptionsPage
{
Q_OBJECT
public:
QbsSettingsPage();
private:
QWidget *widget() override;
void apply() override;
void finish() override;
class SettingsWidget;
QPointer<SettingsWidget> m_widget;
};
} // namespace Internal

View File

@@ -17,14 +17,11 @@ Project {
]
property bool qbsSubModuleExists: File.exists(qbsProject.qbsBaseDir + "/qbs.qbs")
property path qbs_install_dir: Environment.getEnv("QBS_INSTALL_DIR")
property bool useExternalQbs: qbs_install_dir
property bool buildQbsProjectManager: useExternalQbs || qbsSubModuleExists
Project {
name: "qbs project"
id: qbsProject
property string qbsBaseDir: project.sharedSourcesDir + "/qbs"
condition: qbsSubModuleExists && !useExternalQbs
condition: qbsSubModuleExists
// The first entry is for overriding qbs' own qbsbuildconfig module.
qbsSearchPaths: [project.ide_source_tree + "/qbs", qbsBaseDir + "/qbs-resources"]