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

@@ -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);