From 6913947986a1aebeb9eba6212ef270c31de28af8 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 28 Jun 2019 14:30:32 +0200 Subject: [PATCH] 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 --- src/plugins/qbsprojectmanager/CMakeLists.txt | 13 +- .../customqbspropertiesdialog.cpp | 7 +- .../defaultpropertyprovider.cpp | 2 - .../qbsbuildconfiguration.cpp | 10 +- .../qbsprojectmanager/qbsbuildstep.cpp | 212 +++-- src/plugins/qbsprojectmanager/qbsbuildstep.h | 34 +- .../qbsprojectmanager/qbscleanstep.cpp | 71 +- src/plugins/qbsprojectmanager/qbscleanstep.h | 9 +- .../qbsprojectmanager/qbsinstallstep.cpp | 111 +-- .../qbsprojectmanager/qbsinstallstep.h | 22 +- .../qbsprojectmanager/qbskitinformation.cpp | 5 +- src/plugins/qbsprojectmanager/qbslogsink.cpp | 94 --- src/plugins/qbsprojectmanager/qbslogsink.h | 60 -- src/plugins/qbsprojectmanager/qbsnodes.cpp | 253 ++---- src/plugins/qbsprojectmanager/qbsnodes.h | 55 +- .../qbsprojectmanager/qbsnodetreebuilder.cpp | 251 +++--- .../qbsprojectmanager/qbsnodetreebuilder.h | 11 +- .../qbsprojectmanager/qbsprofilemanager.cpp | 266 ++++++ ...bsprojectmanager.h => qbsprofilemanager.h} | 23 +- .../qbsprofilessettingspage.cpp | 140 ++-- .../qbsprofilessettingspage.h | 3 +- .../qbsprofilessettingswidget.ui | 32 +- src/plugins/qbsprojectmanager/qbsproject.cpp | 774 +++++++----------- src/plugins/qbsprojectmanager/qbsproject.h | 57 +- .../qbsprojectmanager/qbsprojectimporter.cpp | 15 +- .../qbsprojectmanager/qbsprojectmanager.cpp | 209 ----- .../qbsprojectmanager/qbsprojectmanager.pro | 30 +- .../qbsprojectmanager/qbsprojectmanager.qbs | 59 +- .../qbsprojectmanagerconstants.h | 2 +- .../qbsprojectmanagerplugin.cpp | 22 +- .../qbsprojectmanagersettings.cpp | 82 -- .../qbsprojectmanager/qbsprojectparser.cpp | 215 ++--- .../qbsprojectmanager/qbsprojectparser.h | 35 +- src/plugins/qbsprojectmanager/qbssession.cpp | 687 ++++++++++++++++ src/plugins/qbsprojectmanager/qbssession.h | 198 +++++ src/plugins/qbsprojectmanager/qbssettings.cpp | 192 +++++ ...projectmanagersettings.h => qbssettings.h} | 45 +- src/src.qbs | 5 +- 38 files changed, 2339 insertions(+), 1972 deletions(-) delete mode 100644 src/plugins/qbsprojectmanager/qbslogsink.cpp delete mode 100644 src/plugins/qbsprojectmanager/qbslogsink.h create mode 100644 src/plugins/qbsprojectmanager/qbsprofilemanager.cpp rename src/plugins/qbsprojectmanager/{qbsprojectmanager.h => qbsprofilemanager.h} (83%) delete mode 100644 src/plugins/qbsprojectmanager/qbsprojectmanager.cpp delete mode 100644 src/plugins/qbsprojectmanager/qbsprojectmanagersettings.cpp create mode 100644 src/plugins/qbsprojectmanager/qbssession.cpp create mode 100644 src/plugins/qbsprojectmanager/qbssession.h create mode 100644 src/plugins/qbsprojectmanager/qbssettings.cpp rename src/plugins/qbsprojectmanager/{qbsprojectmanagersettings.h => qbssettings.h} (63%) diff --git a/src/plugins/qbsprojectmanager/CMakeLists.txt b/src/plugins/qbsprojectmanager/CMakeLists.txt index 997e710a83e..98f69859aa0 100644 --- a/src/plugins/qbsprojectmanager/CMakeLists.txt +++ b/src/plugins/qbsprojectmanager/CMakeLists.txt @@ -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 ) diff --git a/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp b/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp index ab88086fd57..5667571d025 100644 --- a/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp +++ b/src/plugins/qbsprojectmanager/customqbspropertiesdialog.cpp @@ -26,7 +26,7 @@ #include "customqbspropertiesdialog.h" #include "ui_customqbspropertiesdialog.h" -#include +#include "qbsprofilemanager.h" #include #include @@ -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; } diff --git a/src/plugins/qbsprojectmanager/defaultpropertyprovider.cpp b/src/plugins/qbsprojectmanager/defaultpropertyprovider.cpp index cfb7b639911..ba43dc05439 100644 --- a/src/plugins/qbsprojectmanager/defaultpropertyprovider.cpp +++ b/src/plugins/qbsprojectmanager/defaultpropertyprovider.cpp @@ -41,8 +41,6 @@ #include #include -#include - #include #include #include diff --git a/src/plugins/qbsprojectmanager/qbsbuildconfiguration.cpp b/src/plugins/qbsprojectmanager/qbsbuildconfiguration.cpp index eeab2c51f77..08e4c179373 100644 --- a/src/plugins/qbsprojectmanager/qbsbuildconfiguration.cpp +++ b/src/plugins/qbsprojectmanager/qbsbuildconfiguration.cpp @@ -30,7 +30,7 @@ #include "qbsinstallstep.h" #include "qbsproject.h" #include "qbsprojectmanagerconstants.h" -#include "qbsprojectmanagersettings.h" +#include "qbssettings.h" #include @@ -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()); diff --git a/src/plugins/qbsprojectmanager/qbsbuildstep.cpp b/src/plugins/qbsprojectmanager/qbsbuildstep.cpp index e31af5a708f..38f20db7328 100644 --- a/src/plugins/qbsprojectmanager/qbsbuildstep.cpp +++ b/src/plugins/qbsprojectmanager/qbsbuildstep.cpp @@ -29,7 +29,8 @@ #include "qbsparser.h" #include "qbsproject.h" #include "qbsprojectmanagerconstants.h" -#include "qbsprojectmanagersettings.h" +#include "qbssession.h" +#include "qbssettings.h" #include #include @@ -48,11 +49,12 @@ #include #include #include +#include +#include #include #include #include - -#include +#include // -------------------------------------------------------------------- // 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(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(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); diff --git a/src/plugins/qbsprojectmanager/qbsbuildstep.h b/src/plugins/qbsprojectmanager/qbsbuildstep.h index a1c66a5ce1a..5cd6199aa57 100644 --- a/src/plugins/qbsprojectmanager/qbsbuildstep.h +++ b/src/plugins/qbsprojectmanager/qbsbuildstep.h @@ -30,13 +30,13 @@ #include #include -#include - 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; }; diff --git a/src/plugins/qbsprojectmanager/qbscleanstep.cpp b/src/plugins/qbsprojectmanager/qbscleanstep.cpp index 5e46e166e90..d369ea93002 100644 --- a/src/plugins/qbsprojectmanager/qbscleanstep.cpp +++ b/src/plugins/qbsprojectmanager/qbscleanstep.cpp @@ -28,6 +28,7 @@ #include "qbsbuildconfiguration.h" #include "qbsproject.h" #include "qbsprojectmanagerconstants.h" +#include "qbssession.h" #include #include @@ -36,6 +37,9 @@ #include #include +#include +#include + 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(buildConfiguration()); - + const auto bc = static_cast(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(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,9 +147,8 @@ 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, - ProjectExplorer::Constants::TASK_CATEGORY_COMPILE); + Task task(type, message, Utils::FilePath::fromString(file), line, + ProjectExplorer::Constants::TASK_CATEGORY_COMPILE); emit addTask(task, 1); emit addOutput(message, OutputFormat::Stdout); } diff --git a/src/plugins/qbsprojectmanager/qbscleanstep.h b/src/plugins/qbsprojectmanager/qbscleanstep.h index df2dccbf13b..205666ac558 100644 --- a/src/plugins/qbsprojectmanager/qbscleanstep.h +++ b/src/plugins/qbsprojectmanager/qbscleanstep.h @@ -31,10 +31,10 @@ #include #include -#include - 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; diff --git a/src/plugins/qbsprojectmanager/qbsinstallstep.cpp b/src/plugins/qbsprojectmanager/qbsinstallstep.cpp index 49ce7c84848..1e4f213770b 100644 --- a/src/plugins/qbsprojectmanager/qbsinstallstep.cpp +++ b/src/plugins/qbsprojectmanager/qbsinstallstep.cpp @@ -29,6 +29,7 @@ #include "qbsbuildstep.h" #include "qbsproject.h" #include "qbsprojectmanagerconstants.h" +#include "qbssession.h" #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -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(buildSystem()); - m_job = bs->install(m_qbsInstallOptions); + m_session = static_cast(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(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(); } diff --git a/src/plugins/qbsprojectmanager/qbsinstallstep.h b/src/plugins/qbsprojectmanager/qbsinstallstep.h index 5f58d006a31..aad10bcbec5 100644 --- a/src/plugins/qbsprojectmanager/qbsinstallstep.h +++ b/src/plugins/qbsprojectmanager/qbsinstallstep.h @@ -26,14 +26,15 @@ #pragma once #include "qbsbuildconfiguration.h" +#include "qbssession.h" #include #include -#include - 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; diff --git a/src/plugins/qbsprojectmanager/qbskitinformation.cpp b/src/plugins/qbsprojectmanager/qbskitinformation.cpp index 53f3b9a1429..fa0cc6469d1 100644 --- a/src/plugins/qbsprojectmanager/qbskitinformation.cpp +++ b/src/plugins/qbsprojectmanager/qbskitinformation.cpp @@ -26,14 +26,13 @@ #include "qbskitinformation.h" #include "customqbspropertiesdialog.h" +#include "qbsprofilemanager.h" #include #include #include -#include - #include 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; } diff --git a/src/plugins/qbsprojectmanager/qbslogsink.cpp b/src/plugins/qbsprojectmanager/qbslogsink.cpp deleted file mode 100644 index d73f8440525..00000000000 --- a/src/plugins/qbsprojectmanager/qbslogsink.cpp +++ /dev/null @@ -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 - -#include -#include -#include -#include - -#include -#include -#include - -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 diff --git a/src/plugins/qbsprojectmanager/qbslogsink.h b/src/plugins/qbsprojectmanager/qbslogsink.h deleted file mode 100644 index e6c7403ad92..00000000000 --- a/src/plugins/qbsprojectmanager/qbslogsink.h +++ /dev/null @@ -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 - -#include - -#include -#include -#include - -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 diff --git a/src/plugins/qbsprojectmanager/qbsnodes.cpp b/src/plugins/qbsprojectmanager/qbsnodes.cpp index 8ec0669cd31..b4cea58f800 100644 --- a/src/plugins/qbsprojectmanager/qbsnodes.cpp +++ b/src/plugins/qbsprojectmanager/qbsnodes.cpp @@ -29,6 +29,7 @@ #include "qbsproject.h" #include "qbsprojectmanagerconstants.h" #include "qbsprojectmanagerplugin.h" +#include "qbssession.h" #include #include @@ -44,8 +45,10 @@ #include #include #include +#include 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) - { - if (p) - p->children.append(this); +const QbsProductNode *parentQbsProductNode(const ProjectExplorer::Node *node) +{ + for (; node; node = node->parentFolderNode()) { + const auto prdNode = dynamic_cast(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 children; - FileTreeNode *parent; - QString name; - bool m_isFile; -}; - + return nullptr; +} // -------------------------------------------------------------------- // 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 { - setProductType(ProductType::Other); + 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(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(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 diff --git a/src/plugins/qbsprojectmanager/qbsnodes.h b/src/plugins/qbsprojectmanager/qbsnodes.h index 74a571bac6a..9c7457ad0ce 100644 --- a/src/plugins/qbsprojectmanager/qbsnodes.h +++ b/src/plugins/qbsprojectmanager/qbsnodes.h @@ -28,91 +28,60 @@ #include #include -#include +#include 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 diff --git a/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp b/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp index cc6e7e54db8..5f7b5160a1f 100644 --- a/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp +++ b/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp @@ -25,7 +25,13 @@ #include "qbsnodetreebuilder.h" +#include "qbsnodes.h" #include "qbsproject.h" +#include "qbssession.h" + +#include +#include +#include #include #include @@ -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 &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 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") - }; - auto node = std::make_unique(path, type); - node->setIsGenerated(isGenerated); - node->setListInProject(!isGenerated || Utils::toSet(ad.fileTags()).intersects(sourceTags)); - root->addNestedNode(std::move(node)); - } + // A list of human-readable file types that we can reasonably expect + // to get generated during a build. Extend as needed. + static const QSet sourceTags = { + "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(path, type); + node->setIsGenerated(isGenerated); + QSet fileTags = toSet(transform( + 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 -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(FilePath::fromString(grp.location().filePath()), - FileType::Project); - fileNode->setLine(grp.location().line()); - auto result = std::make_unique(grp, productPath); - - result->setEnabled(productIsEnabled && grp.isEnabled()); - result->setAbsoluteFilePathAndLine( - FilePath::fromString(grp.location().filePath()).parentDir(), -1); - result->setDisplayName(grp.name()); +static std::unique_ptr 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(grp); + result->setAbsoluteFilePathAndLine(baseDir, -1); + auto fileNode = std::make_unique(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 buildProductNodeTree(const QJsonObject &prd) { - auto fileNode = std::make_unique(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(prd); + result->setAbsoluteFilePathAndLine(location.filePath.parentDir(), -1); + auto fileNode = std::make_unique(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(FilePath::fromString(prd.buildDirectory())); + auto genFiles = std::make_unique( + 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 buildProductNodeTree(const qbs::ProductData &prd) -{ - auto result = std::make_unique(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(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(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( - 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(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 referencedBuildSystemFiles(const qbs::ProjectData &data) +static QSet referencedBuildSystemFiles(const QJsonObject &prjData) { QSet 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; +} + +static QStringList unreferencedBuildSystemFiles(const QJsonObject &project) +{ + QStringList unreferenced = arrayToStringList(project.value("build-system-files")); + const QList referenced = toList(referencedBuildSystemFiles(project)); + for (auto it = unreferenced.begin(); it != unreferenced.end(); ) { + if (referenced.contains(*it)) + it = unreferenced.erase(it); + else + ++it; + } + return unreferenced; +} + +std::unique_ptr QbsNodeTreeBuilder::buildTree(const QbsBuildSystem *buildSystem) +{ + const Project * const project = buildSystem->project(); + auto root = std::make_unique(buildSystem->projectData()); + + // 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(project->projectFilePath(), FileType::Project); + root->addNode(std::move(fileNode)); + } else { + setupProjectNode(root.get()); } - return result; -} + if (root->displayName().isEmpty()) + root->setDisplayName(project->displayName()); + if (root->displayName().isEmpty()) + root->setDisplayName(project->projectFilePath().toFileInfo().completeBaseName()); -QStringList unreferencedBuildSystemFiles(const qbs::Project &p) -{ - QStringList result; - if (!p.isValid()) - return result; + auto buildSystemFiles = std::make_unique(project->projectDirectory()); + buildSystemFiles->setDisplayName(QCoreApplication::translate("QbsProjectNode", "Qbs files")); - const std::set &available = p.buildSystemFiles(); - QList 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; -} - -} // namespace - -std::unique_ptr QbsNodeTreeBuilder::buildTree(QbsBuildSystem *buildSystem) -{ - if (!buildSystem->qbsProjectData().isValid()) - return {}; - - auto root = std::make_unique(buildSystem->project()); - setupProjectNode(root.get(), buildSystem->qbsProjectData(), buildSystem->qbsProject()); - - auto buildSystemFiles = std::make_unique(buildSystem->projectDirectory()); - buildSystemFiles->setDisplayName(QCoreApplication::translate("QbsRootProjectNode", "Qbs files")); - - const FilePath base = buildSystem->projectDirectory(); - const QStringList files = unreferencedBuildSystemFiles(buildSystem->qbsProject()); + 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 QbsNodeTreeBuilder::buildTree(QbsBuildSystem } buildSystemFiles->compress(); root->addNode(std::move(buildSystemFiles)); - return root; } diff --git a/src/plugins/qbsprojectmanager/qbsnodetreebuilder.h b/src/plugins/qbsprojectmanager/qbsnodetreebuilder.h index 3568cda7c6f..3c2cd450dc3 100644 --- a/src/plugins/qbsprojectmanager/qbsnodetreebuilder.h +++ b/src/plugins/qbsprojectmanager/qbsnodetreebuilder.h @@ -25,21 +25,18 @@ #pragma once -#include "qbsnodes.h" - -#include +#include namespace QbsProjectManager { namespace Internal { -// ---------------------------------------------------------------------- -// QbsNodeTreeBuilder: -// ---------------------------------------------------------------------- +class QbsBuildSystem; +class QbsProjectNode; class QbsNodeTreeBuilder { public: - static std::unique_ptr buildTree(QbsBuildSystem *project); + static std::unique_ptr buildTree(const QbsBuildSystem *buildSystem); }; } // namespace Internal diff --git a/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp b/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp new file mode 100644 index 00000000000..c221240b773 --- /dev/null +++ b/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace QbsProjectManager { + +static QList 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(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 diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanager.h b/src/plugins/qbsprojectmanager/qbsprofilemanager.h similarity index 83% rename from src/plugins/qbsprojectmanager/qbsprojectmanager.h rename to src/plugins/qbsprojectmanager/qbsprofilemanager.h index c16aedb7a34..6ec489184c6 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectmanager.h +++ b/src/plugins/qbsprojectmanager/qbsprofilemanager.h @@ -28,32 +28,33 @@ #include "qbsprojectmanager_global.h" #include -#include -#include - -namespace qbs { class Settings; } +#include 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); diff --git a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp index 3f3fe351ceb..2d1ebfb30bf 100644 --- a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp +++ b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.cpp @@ -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 #include #include #include #include #include +#include #include #include - -#include +#include #include #include #include +using namespace ProjectExplorer; + namespace QbsProjectManager { namespace Internal { +class ProfileTreeItem : public Utils::TypedTreeItem +{ +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 +{ + Q_OBJECT +public: + ProfileModel() : TreeModel(static_cast(nullptr)) + { + setHeader(QStringList{tr("Key"), tr("Value")}); + reload(); + } + + void reload() + { + ProfileTreeItem * const newRoot = new ProfileTreeItem(QString(), QString()); + QHash 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 validKits = ProjectExplorer::KitManager::kits(); - Utils::erase(validKits, [](const ProjectExplorer::Kit *k) { return !k->isValid(); }); + QList 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,24 +193,18 @@ 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")) + const QModelIndex currentProfileIndex = m_model.index(i, 0); + if (m_model.data(currentProfileIndex, Qt::DisplayRole).toString() != profileName) 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) - continue; - m_ui.propertiesView->setModel(&m_model); - m_ui.propertiesView->header()->setSectionResizeMode(m_model.keyColumn(), - QHeaderView::ResizeToContents); - m_ui.propertiesView->setRootIndex(currentProfileIndex); - return; - } + m_ui.propertiesView->setModel(&m_model); + m_ui.propertiesView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + m_ui.propertiesView->setRootIndex(currentProfileIndex); + return; } } diff --git a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.h b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.h index 6227c6f999a..8c26a316ab6 100644 --- a/src/plugins/qbsprojectmanager/qbsprofilessettingspage.h +++ b/src/plugins/qbsprojectmanager/qbsprofilessettingspage.h @@ -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 diff --git a/src/plugins/qbsprojectmanager/qbsprofilessettingswidget.ui b/src/plugins/qbsprojectmanager/qbsprofilessettingswidget.ui index 373970c7b50..8f21aa7967b 100644 --- a/src/plugins/qbsprojectmanager/qbsprofilessettingswidget.ui +++ b/src/plugins/qbsprojectmanager/qbsprofilessettingswidget.ui @@ -14,23 +14,16 @@ - - - - Store profiles in Qt Creator settings directory - - - - + Kit: - + @@ -50,14 +43,14 @@ - + Associated profile: - + @@ -67,23 +60,6 @@ - - - - Qbs version: - - - - - - - TextLabel - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - diff --git a/src/plugins/qbsprojectmanager/qbsproject.cpp b/src/plugins/qbsprojectmanager/qbsproject.cpp index 81bf08f83a9..c656a10575a 100644 --- a/src/plugins/qbsprojectmanager/qbsproject.cpp +++ b/src/plugins/qbsprojectmanager/qbsproject.cpp @@ -28,13 +28,13 @@ #include "qbsbuildconfiguration.h" #include "qbsbuildstep.h" #include "qbsinstallstep.h" -#include "qbslogsink.h" +#include "qbsnodes.h" #include "qbspmlogging.h" #include "qbsprojectimporter.h" #include "qbsprojectparser.h" #include "qbsprojectmanagerconstants.h" -#include "qbsnodes.h" #include "qbsnodetreebuilder.h" +#include "qbssession.h" #include #include @@ -71,11 +71,10 @@ #include #include -#include - #include #include #include +#include #include #include #include @@ -94,13 +93,6 @@ namespace Internal { // Constants: // -------------------------------------------------------------------- -static const char CONFIG_CPP_MODULE[] = "cpp"; -static const char CONFIG_DEFINES[] = "defines"; -static const char CONFIG_INCLUDEPATHS[] = "includePaths"; -static const char CONFIG_SYSTEM_INCLUDEPATHS[] = "systemIncludePaths"; -static const char CONFIG_FRAMEWORKPATHS[] = "frameworkPaths"; -static const char CONFIG_SYSTEM_FRAMEWORKPATHS[] = "systemFrameworkPaths"; - class OpTimer { public: @@ -146,33 +138,25 @@ ProjectImporter *QbsProject::projectImporter() const return m_importer; } -// -------------------------------------------------------------------- -// QbsBuildSystem: -// -------------------------------------------------------------------- - -static const QbsProjectNode *parentQbsProjectNode(const ProjectExplorer::Node *node) +void QbsProject::configureAsExampleProject() { - for (const ProjectExplorer::FolderNode *pn = node->managingProject(); pn; pn = pn->parentProjectNode()) { - const auto prjNode = dynamic_cast(pn); - if (prjNode) - return prjNode; + QList infoList; + const QList kits = KitManager::kits(); + for (Kit *k : kits) { + if (QtSupport::QtKitAspect::qtVersion(k) != nullptr) { + if (auto factory = BuildConfigurationFactory::find(k, projectFilePath())) + infoList << factory->allAvailableSetups(k, projectFilePath()); + } } - return nullptr; + setup(infoList); + if (activeTarget()) + static_cast(activeTarget()->buildSystem())->prepareForParsing(); } -static const QbsProductNode *parentQbsProductNode(const ProjectExplorer::Node *node) -{ - for (; node; node = node->parentFolderNode()) { - const auto prdNode = dynamic_cast(node); - if (prdNode) - return prdNode; - } - return nullptr; -} static bool supportsNodeAction(ProjectAction action, const Node *node) { - const Project * const project = parentQbsProjectNode(node)->project(); + const auto project = static_cast(node->getProject()); Target *t = project ? project->activeTarget() : nullptr; QbsBuildSystem *bs = t ? static_cast(t->buildSystem()) : nullptr; if (!bs) @@ -184,21 +168,39 @@ static bool supportsNodeAction(ProjectAction action, const Node *node) return false; } -static qbs::GroupData findMainQbsGroup(const qbs::ProductData &productData) -{ - foreach (const qbs::GroupData &grp, productData.groups()) { - if (grp.name() == productData.name() && grp.location() == productData.location()) - return grp; - } - return qbs::GroupData(); -} - QbsBuildSystem::QbsBuildSystem(QbsBuildConfiguration *bc) : BuildSystem(bc->target()), + m_session(new QbsSession(this)), m_cppCodeModelUpdater(new CppTools::CppProjectUpdater), m_buildConfiguration(bc) - { + connect(m_session, &QbsSession::newGeneratedFilesForSources, this, + [this](const QHash &generatedFiles) { + for (ExtraCompiler * const ec : qAsConst(m_extraCompilers)) + ec->deleteLater(); + m_extraCompilers.clear(); + for (auto it = m_sourcesForGeneratedFiles.cbegin(); + it != m_sourcesForGeneratedFiles.cend(); ++it) { + for (const QString &sourceFile : it.value()) { + const FilePathList generatedFilePaths = transform( + generatedFiles.value(sourceFile), + [](const QString &s) { return FilePath::fromString(s); }); + if (!generatedFilePaths.empty()) { + m_extraCompilers.append(it.key()->create( + project(), FilePath::fromString(sourceFile), + generatedFilePaths)); + } + } + } + CppTools::GeneratedCodeModelSupport::update(m_extraCompilers); + m_sourcesForGeneratedFiles.clear(); + }); + connect(m_session, &QbsSession::errorOccurred, this, [](QbsSession::Error e) { + TaskHub::addTask(Task::Error, tr("Fatal qbs error: %1").arg(QbsSession::errorString(e)), + ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); + }); + connect(m_session, &QbsSession::fileListUpdated, this, &QbsBuildSystem::delayParsing); + m_parsingDelay.setInterval(1000); // delay parsing by 1s. delayParsing(); @@ -250,37 +252,16 @@ bool QbsBuildSystem::addFiles(Node *context, const QStringList &filePaths, QStri if (!notAdded) notAdded = ¬AddedDummy; - const QbsProjectNode *prjNode = parentQbsProjectNode(n); - if (!prjNode || !qbsProject().isValid()) { - *notAdded += filePaths; - return false; - } - const QbsProductNode *prdNode = parentQbsProductNode(n); - if (!prdNode || !prdNode->qbsProductData().isValid()) { - *notAdded += filePaths; - return false; - } - - return addFilesToProduct(filePaths, prdNode->qbsProductData(), n->m_qbsGroupData, notAdded); + QTC_ASSERT(prdNode, *notAdded += filePaths; return false); + return addFilesToProduct(filePaths, prdNode->productData(), n->groupData(), notAdded); } if (auto n = dynamic_cast(context)) { QStringList notAddedDummy; if (!notAdded) notAdded = ¬AddedDummy; - - const QbsProjectNode *prjNode = parentQbsProjectNode(n); - if (!prjNode || !qbsProject().isValid()) { - *notAdded += filePaths; - return false; - } - - qbs::GroupData grp = findMainQbsGroup(n->qbsProductData()); - if (grp.isValid()) - return addFilesToProduct(filePaths, n->qbsProductData(), grp, notAdded); - - QTC_ASSERT(false, return false); + return addFilesToProduct(filePaths, n->productData(), n->mainGroup(), notAdded); } return BuildSystem::addFiles(context, filePaths, notAdded); @@ -293,40 +274,17 @@ RemovedFilesFromProject QbsBuildSystem::removeFiles(Node *context, const QString QStringList notRemovedDummy; if (!notRemoved) notRemoved = ¬RemovedDummy; - - const QbsProjectNode *prjNode = parentQbsProjectNode(n); - if (!prjNode || !qbsProject().isValid()) { - *notRemoved += filePaths; - return RemovedFilesFromProject::Error; - } - - const QbsProductNode *prdNode = parentQbsProductNode(n); - if (!prdNode || !prdNode->qbsProductData().isValid()) { - *notRemoved += filePaths; - return RemovedFilesFromProject::Error; - } - - return removeFilesFromProduct(filePaths, prdNode->qbsProductData(), - n->m_qbsGroupData, notRemoved); + const QbsProductNode * const prdNode = parentQbsProductNode(n); + QTC_ASSERT(prdNode, *notRemoved += filePaths; return RemovedFilesFromProject::Error); + return removeFilesFromProduct(filePaths, prdNode->productData(), n->groupData(), + notRemoved); } if (auto n = dynamic_cast(context)) { QStringList notRemovedDummy; if (!notRemoved) notRemoved = ¬RemovedDummy; - - const QbsProjectNode *prjNode = parentQbsProjectNode(n); - if (!prjNode || !qbsProject().isValid()) { - *notRemoved += filePaths; - return RemovedFilesFromProject::Error; - } - - qbs::GroupData grp = findMainQbsGroup(n->qbsProductData()); - if (grp.isValid()) { - return removeFilesFromProduct(filePaths, n->qbsProductData(), grp, notRemoved); - } - - QTC_ASSERT(false, return RemovedFilesFromProject::Error); + return removeFilesFromProduct(filePaths, n->productData(), n->mainGroup(), notRemoved); } return BuildSystem::removeFiles(context, filePaths, notRemoved); @@ -335,24 +293,13 @@ RemovedFilesFromProject QbsBuildSystem::removeFiles(Node *context, const QString bool QbsBuildSystem::renameFile(Node *context, const QString &filePath, const QString &newFilePath) { if (auto *n = dynamic_cast(context)) { - const QbsProjectNode *prjNode = parentQbsProjectNode(n); - if (!prjNode || !qbsProject().isValid()) - return false; - const QbsProductNode *prdNode = parentQbsProductNode(n); - if (!prdNode || !prdNode->qbsProductData().isValid()) - return false; - - return renameFileInProduct(filePath, newFilePath, - prdNode->qbsProductData(), n->m_qbsGroupData); + const QbsProductNode * const prdNode = parentQbsProductNode(n); + QTC_ASSERT(prdNode, return false); + return renameFileInProduct(filePath, newFilePath, prdNode->productData(), n->groupData()); } if (auto *n = dynamic_cast(context)) { - const QbsProjectNode * prjNode = parentQbsProjectNode(n); - if (!prjNode || !qbsProject().isValid()) - return false; - const qbs::GroupData grp = findMainQbsGroup(n->qbsProductData()); - QTC_ASSERT(grp.isValid(), return false); - return renameFileInProduct(filePath, newFilePath, n->qbsProductData(), grp); + return renameFileInProduct(filePath, newFilePath, n->productData(), n->mainGroup()); } return BuildSystem::renameFile(context, filePath, newFilePath); @@ -361,14 +308,16 @@ bool QbsBuildSystem::renameFile(Node *context, const QString &filePath, const QS QVariant QbsBuildSystem::additionalData(Id id) const { if (id == "QmlDesignerImportPath") { - const qbs::ProjectData projectData = m_qbsProject.isValid() - ? m_qbsProject.projectData() : qbs::ProjectData(); QStringList designerImportPaths; - foreach (const qbs::ProductData &product, projectData.allProducts()) { - designerImportPaths << product.properties() - .value("qmlDesignerImportPaths").toStringList(); - } - return designerImportPaths; + const QJsonObject project = session()->projectData(); + QStringList paths; + forAllProducts(project, [&paths](const QJsonObject &product) { + for (const QJsonValue &v : product.value("properties").toObject() + .value("qmlDesignerImportPaths").toArray()) { + paths << v.toString(); + } + }); + return paths; } return BuildSystem::additionalData(id); } @@ -380,15 +329,12 @@ ProjectExplorer::DeploymentKnowledge QbsProject::deploymentKnowledge() const QStringList QbsBuildSystem::filesGeneratedFrom(const QString &sourceFile) const { - QStringList generated; - foreach (const qbs::ProductData &data, m_projectData.allProducts()) - generated << m_qbsProject.generatedFiles(data, sourceFile, false); - return generated; + return session()->filesGeneratedFrom(sourceFile); } bool QbsBuildSystem::isProjectEditable() const { - return m_qbsProject.isValid() && !isParsing() && !BuildManager::isBuilding(); + return !isParsing() && !BuildManager::isBuilding(target()); } bool QbsBuildSystem::ensureWriteableQbsFile(const QString &file) @@ -412,70 +358,57 @@ bool QbsBuildSystem::ensureWriteableQbsFile(const QString &file) return true; } -bool QbsBuildSystem::addFilesToProduct(const QStringList &filePaths, - const qbs::ProductData productData, - const qbs::GroupData groupData, QStringList *notAdded) +bool QbsBuildSystem::addFilesToProduct( + const QStringList &filePaths, + const QJsonObject &product, + const QJsonObject &group, + QStringList *notAdded) { - QTC_ASSERT(m_qbsProject.isValid(), return false); - QStringList allPaths = groupData.allFilePaths(); - const QString productFilePath = productData.location().filePath(); - Core::FileChangeBlocker expector(productFilePath); - ensureWriteableQbsFile(productFilePath); - foreach (const QString &path, filePaths) { - qbs::ErrorInfo err = m_qbsProject.addFiles(productData, groupData, QStringList() << path); - if (err.hasError()) { - MessageManager::write(err.toString()); - *notAdded += path; - } else { - allPaths += path; - } - } - if (notAdded->count() != filePaths.count()) { - m_projectData = m_qbsProject.projectData(); - delayedUpdateAfterParse(); + const QString groupFilePath = group.value("location").toObject().value("file-path").toString(); + ensureWriteableQbsFile(groupFilePath); + const FileChangeResult result = session()->addFiles( + filePaths, + product.value("full-display-name").toString(), + group.value("name").toString()); + if (result.error().hasError()) { + MessageManager::write(result.error().toString(), Core::MessageManager::ModeSwitch); + *notAdded = result.failedFiles(); } return notAdded->isEmpty(); } -RemovedFilesFromProject QbsBuildSystem::removeFilesFromProduct(const QStringList &filePaths, - const qbs::ProductData &productData, - const qbs::GroupData &groupData, - QStringList *notRemoved) +RemovedFilesFromProject QbsBuildSystem::removeFilesFromProduct( + const QStringList &filePaths, + const QJsonObject &product, + const QJsonObject &group, + QStringList *notRemoved) { - QTC_ASSERT(m_qbsProject.isValid(), return RemovedFilesFromProject::Error); - - const QList allWildcardsInGroup = groupData.sourceArtifactsFromWildcards(); + const auto allWildcardsInGroup = transform( + group.value("source-artifacts-from-wildcards").toArray(), + [](const QJsonValue &v) { return v.toObject().value("file-path").toString(); }); QStringList wildcardFiles; QStringList nonWildcardFiles; for (const QString &filePath : filePaths) { - if (contains(allWildcardsInGroup, [filePath](const qbs::ArtifactData &artifact) { - return artifact.filePath() == filePath; })) { + if (allWildcardsInGroup.contains(filePath)) wildcardFiles << filePath; - } else { + else nonWildcardFiles << filePath; - } - } - const QString productFilePath = productData.location().filePath(); - Core::FileChangeBlocker expector(productFilePath); - ensureWriteableQbsFile(productFilePath); - for (const QString &path : qAsConst(nonWildcardFiles)) { - const qbs::ErrorInfo err = m_qbsProject.removeFiles(productData, groupData, {path}); - if (err.hasError()) { - MessageManager::write(err.toString()); - *notRemoved += path; - } } - if (notRemoved->count() != filePaths.count()) { - m_projectData = m_qbsProject.projectData(); - delayedUpdateAfterParse(); - } + const QString groupFilePath = group.value("location") + .toObject().value("file-path").toString(); + ensureWriteableQbsFile(groupFilePath); + const FileChangeResult result = session()->removeFiles( + nonWildcardFiles, + product.value("name").toString(), + group.value("name").toString()); + *notRemoved = result.failedFiles(); + if (result.error().hasError()) + MessageManager::write(result.error().toString(), Core::MessageManager::ModeSwitch); const bool success = notRemoved->isEmpty(); - if (!wildcardFiles.isEmpty()) { + if (!wildcardFiles.isEmpty()) *notRemoved += wildcardFiles; - delayParsing(); - } if (!success) return RemovedFilesFromProject::Error; if (!wildcardFiles.isEmpty()) @@ -483,116 +416,25 @@ RemovedFilesFromProject QbsBuildSystem::removeFilesFromProduct(const QStringList return RemovedFilesFromProject::Ok; } -bool QbsBuildSystem::renameFileInProduct(const QString &oldPath, const QString &newPath, - const qbs::ProductData productData, - const qbs::GroupData groupData) +bool QbsBuildSystem::renameFileInProduct( + const QString &oldPath, + const QString &newPath, + const QJsonObject &product, + const QJsonObject &group) { if (newPath.isEmpty()) return false; QStringList dummy; - if (removeFilesFromProduct(QStringList(oldPath), productData, groupData, &dummy) + if (removeFilesFromProduct(QStringList(oldPath), product, group, &dummy) != RemovedFilesFromProject::Ok) { return false; } - qbs::ProductData newProductData; - foreach (const qbs::ProductData &p, m_projectData.allProducts()) { - if (QbsProject::uniqueProductName(p) == QbsProject::uniqueProductName(productData)) { - newProductData = p; - break; - } - } - if (!newProductData.isValid()) - return false; - qbs::GroupData newGroupData; - foreach (const qbs::GroupData &g, newProductData.groups()) { - if (g.name() == groupData.name()) { - newGroupData = g; - break; - } - } - if (!newGroupData.isValid()) - return false; - - return addFilesToProduct(QStringList() << newPath, newProductData, newGroupData, &dummy); -} - -static qbs::AbstractJob *doBuildOrClean(const qbs::Project &project, - const QList &products, - const qbs::BuildOptions &options) -{ - if (products.isEmpty()) - return project.buildAllProducts(options); - return project.buildSomeProducts(products, options); -} - -static qbs::AbstractJob *doBuildOrClean(const qbs::Project &project, - const QList &products, - const qbs::CleanOptions &options) -{ - if (products.isEmpty()) - return project.cleanAllProducts(options); - return project.cleanSomeProducts(products, options); -} - -template -qbs::AbstractJob *QbsBuildSystem::buildOrClean(const Options &opts, const QStringList &productNames, - QString &error) -{ - QTC_ASSERT(qbsProject().isValid(), return nullptr); - QTC_ASSERT(!isParsing(), return nullptr); - - QList products; - foreach (const QString &productName, productNames) { - bool found = false; - foreach (const qbs::ProductData &data, qbsProjectData().allProducts()) { - if (QbsProject::uniqueProductName(data) == productName) { - found = true; - products.append(data); - break; - } - } - if (!found) { - const bool cleaningRequested = std::is_same::value; - error = tr("%1: Selected products do not exist anymore.") - .arg(cleaningRequested ? tr("Cannot clean") : tr("Cannot build")); - return nullptr; - } - } - return doBuildOrClean(qbsProject(), products, opts); -} - -qbs::BuildJob *QbsBuildSystem::build(const qbs::BuildOptions &opts, QStringList productNames, - QString &error) -{ - return static_cast(buildOrClean(opts, productNames, error)); -} - -qbs::CleanJob *QbsBuildSystem::clean(const qbs::CleanOptions &opts, const QStringList &productNames, - QString &error) -{ - return static_cast(buildOrClean(opts, productNames, error)); -} - -qbs::InstallJob *QbsBuildSystem::install(const qbs::InstallOptions &opts) -{ - if (!qbsProject().isValid()) - return nullptr; - return qbsProject().installAllProducts(opts); + return addFilesToProduct(QStringList(newPath), product, group, &dummy); } QString QbsBuildSystem::profile() const { - return QbsManager::profileForKit(target()->kit()); -} - -qbs::Project QbsBuildSystem::qbsProject() const -{ - return m_qbsProject; -} - -qbs::ProjectData QbsBuildSystem::qbsProjectData() const -{ - return m_projectData; + return QbsProfileManager::profileForKit(target()->kit()); } bool QbsBuildSystem::checkCancelStatus() @@ -614,7 +456,7 @@ void QbsBuildSystem::updateAfterParse() qCDebug(qbsPmLog) << "Updating data after parse"; OpTimer opTimer("updateAfterParse"); updateProjectNodes(); - updateDocuments(m_qbsProject.buildSystemFiles()); + updateDocuments(); updateBuildTargetData(); updateCppCodeModel(); updateQmlJsCodeModel(); @@ -662,13 +504,11 @@ void QbsBuildSystem::handleQbsParsingDone(bool success) generateErrors(m_qbsProjectParser->error()); - m_qbsProject = m_qbsProjectParser->qbsProject(); bool dataChanged = false; bool envChanged = m_lastParseEnv != m_qbsProjectParser->environment(); m_lastParseEnv = m_qbsProjectParser->environment(); if (success) { - QTC_ASSERT(m_qbsProject.isValid(), return); - const qbs::ProjectData &projectData = m_qbsProject.projectData(); + const QJsonObject projectData = m_qbsProjectParser->session()->projectData(); if (projectData != m_projectData) { m_projectData = projectData; dataChanged = true; @@ -687,7 +527,8 @@ void QbsBuildSystem::handleQbsParsingDone(bool success) updateAfterParse(); else if (envChanged) updateCppCodeModel(); - m_guard.markAsSuccess(); + if (success) + m_guard.markAsSuccess(); m_guard = {}; // This one used to change the executable path of a Qbs desktop run configuration @@ -698,29 +539,11 @@ void QbsBuildSystem::handleQbsParsingDone(bool success) void QbsBuildSystem::rebuildProjectTree() { - std::unique_ptr newRoot = Internal::QbsNodeTreeBuilder::buildTree(this); - project()->setDisplayName(newRoot ? newRoot->displayName() : projectFilePath().toFileInfo().completeBaseName()); + std::unique_ptr newRoot = QbsNodeTreeBuilder::buildTree(this); + project()->setDisplayName(newRoot->displayName()); setRootProjectNode(std::move(newRoot)); } -void QbsBuildSystem::handleRuleExecutionDone() -{ - qCDebug(qbsPmLog) << "Rule execution done"; - - if (checkCancelStatus()) - return; - - m_qbsProjectParser->deleteLater(); - m_qbsProjectParser = nullptr; - m_qbsUpdateFutureInterface->reportFinished(); - delete m_qbsUpdateFutureInterface; - m_qbsUpdateFutureInterface = nullptr; - - QTC_ASSERT(m_qbsProject.isValid(), return); - m_projectData = m_qbsProject.projectData(); - updateAfterParse(); -} - void QbsBuildSystem::changeActiveTarget(Target *t) { if (t) @@ -780,13 +603,10 @@ void QbsBuildSystem::parseCurrentBuildConfiguration() QTC_ASSERT(!m_qbsProjectParser, return); m_qbsProjectParser = new QbsProjectParser(this, m_qbsUpdateFutureInterface); - - connect(m_qbsProjectParser, &QbsProjectParser::ruleExecutionDone, - this, &QbsBuildSystem::handleRuleExecutionDone); connect(m_qbsProjectParser, &QbsProjectParser::done, this, &QbsBuildSystem::handleQbsParsingDone); - QbsManager::updateProfileIfNecessary(target()->kit()); + QbsProfileManager::updateProfileIfNecessary(target()->kit()); m_qbsProjectParser->parse(config, env, dir, m_buildConfiguration->configurationName()); } @@ -800,8 +620,7 @@ void QbsBuildSystem::cancelParsing() void QbsBuildSystem::updateAfterBuild() { OpTimer opTimer("updateAfterBuild"); - QTC_ASSERT(m_qbsProject.isValid(), return); - const qbs::ProjectData &projectData = m_qbsProject.projectData(); + const QJsonObject projectData = session()->projectData(); if (projectData == m_projectData) { DeploymentData deploymentDataTmp = deploymentData(); deploymentDataTmp.setLocalInstallRoot(installRoot()); @@ -813,41 +632,17 @@ void QbsBuildSystem::updateAfterBuild() m_projectData = projectData; updateProjectNodes(); updateBuildTargetData(); - if (m_extraCompilersPending) { - m_extraCompilersPending = false; - updateCppCodeModel(); - } + updateCppCodeModel(); // TODO: Should be updateExtraCompilers(). m_envCache.clear(); } -void QbsBuildSystem::generateErrors(const qbs::ErrorInfo &e) +void QbsBuildSystem::generateErrors(const ErrorInfo &e) { - foreach (const qbs::ErrorItem &item, e.items()) - TaskHub::addTask(Task::Error, item.description(), + for (const ErrorInfoItem &item : e.items) { + TaskHub::addTask(Task::Error, item.description, ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM, - FilePath::fromString(item.codeLocation().filePath()), - item.codeLocation().line()); - -} - -QString QbsProject::uniqueProductName(const qbs::ProductData &product) -{ - return product.name() + QLatin1Char('.') + product.multiplexConfigurationId(); -} - -void QbsProject::configureAsExampleProject() -{ - QList infoList; - const QList kits = KitManager::kits(); - for (Kit *k : kits) { - if (QtSupport::QtKitAspect::qtVersion(k) != nullptr) { - if (auto factory = BuildConfigurationFactory::find(k, projectFilePath())) - infoList << factory->allAvailableSetups(k, projectFilePath()); - } + item.filePath, item.line); } - setup(infoList); - if (activeTarget()) - static_cast(activeTarget()->buildSystem())->prepareForParsing(); } void QbsBuildSystem::prepareForParsing() @@ -867,13 +662,17 @@ void QbsBuildSystem::prepareForParsing() m_qbsUpdateFutureInterface->reportStarted(); } -void QbsBuildSystem::updateDocuments(const std::set &files) +void QbsBuildSystem::updateDocuments() { OpTimer opTimer("updateDocuments"); + const FilePath buildDir = FilePath::fromString( + m_projectData.value("build-directory").toString()); + const auto filePaths = transform>( + m_projectData.value("build-system-files").toArray(), + [](const QJsonValue &v) { return FilePath::fromString(v.toString()); }); - const QVector filePaths = transform(files, &FilePath::fromString); - - const FilePath buildDir = FilePath::fromString(m_projectData.buildDirectory()); + // A changed qbs file (project, module etc) should trigger a re-parse, but not if + // the file was generated by qbs itself, in which case that might cause an infinite loop. const QVector nonBuildDirFilePaths = filtered(filePaths, [buildDir](const FilePath &p) { return !p.isChildOf(buildDir); @@ -881,46 +680,47 @@ void QbsBuildSystem::updateDocuments(const std::set &files) project()->setExtraProjectFiles(nonBuildDirFilePaths); } -static QString getMimeType(const qbs::ArtifactData &sourceFile) +static QString getMimeType(const QJsonObject &sourceArtifact) { - if (sourceFile.fileTags().contains("hpp")) { - if (CppTools::ProjectFile::isAmbiguousHeader(sourceFile.filePath())) + const auto tags = sourceArtifact.value("file-tags").toArray(); + if (tags.contains("hpp")) { + if (CppTools::ProjectFile::isAmbiguousHeader(sourceArtifact.value("file-path").toString())) return QString(CppTools::Constants::AMBIGUOUS_HEADER_MIMETYPE); return QString(CppTools::Constants::CPP_HEADER_MIMETYPE); } - if (sourceFile.fileTags().contains("cpp")) + if (tags.contains("cpp")) return QString(CppTools::Constants::CPP_SOURCE_MIMETYPE); - if (sourceFile.fileTags().contains("c")) + if (tags.contains("c")) return QString(CppTools::Constants::C_SOURCE_MIMETYPE); - if (sourceFile.fileTags().contains("objc")) + if (tags.contains("objc")) return QString(CppTools::Constants::OBJECTIVE_C_SOURCE_MIMETYPE); - if (sourceFile.fileTags().contains("objcpp")) + if (tags.contains("objcpp")) return QString(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE); return {}; } -static QString groupLocationToCallGroupId(const qbs::CodeLocation &location) +static QString groupLocationToCallGroupId(const QJsonObject &location) { return QString::fromLatin1("%1:%2:%3") - .arg(location.filePath()) - .arg(location.line()) - .arg(location.column()); + .arg(location.value("file-path").toString()) + .arg(location.value("line").toString()) + .arg(location.value("column").toString()); } // TODO: Receive the values from qbs when QBS-1030 is resolved. static void getExpandedCompilerFlags(QStringList &cFlags, QStringList &cxxFlags, - const qbs::PropertyMap &properties) + const QJsonObject &properties) { const auto getCppProp = [properties](const char *propertyName) { - return properties.getModuleProperty("cpp", QLatin1String(propertyName)); + return properties.value("cpp." + QLatin1String(propertyName)); }; - const QVariant &enableExceptions = getCppProp("enableExceptions"); - const QVariant &enableRtti = getCppProp("enableRtti"); - QStringList commonFlags = getCppProp("platformCommonCompilerFlags").toStringList(); - commonFlags << getCppProp("commonCompilerFlags").toStringList() - << getCppProp("platformDriverFlags").toStringList() - << getCppProp("driverFlags").toStringList(); - const QStringList toolchain = properties.getModulePropertiesAsStringList("qbs", "toolchain"); + const QJsonValue &enableExceptions = getCppProp("enableExceptions"); + const QJsonValue &enableRtti = getCppProp("enableRtti"); + QStringList commonFlags = arrayToStringList(getCppProp("platformCommonCompilerFlags")); + commonFlags << arrayToStringList(getCppProp("commonCompilerFlags")) + << arrayToStringList(getCppProp("platformDriverFlags")) + << arrayToStringList(getCppProp("driverFlags")); + const QStringList toolchain = arrayToStringList(properties.value("qbs.toolchain")); if (toolchain.contains("gcc")) { bool hasTargetOption = false; if (toolchain.contains("clang")) { @@ -941,8 +741,7 @@ static void getExpandedCompilerFlags(QStringList &cFlags, QStringList &cxxFlags, if (!machineType.isEmpty()) commonFlags << ("-march=" + machineType); } - const QStringList targetOS = properties.getModulePropertiesAsStringList( - "qbs", "targetOS"); + const QStringList targetOS = arrayToStringList(properties.value("qbs.targetOS")); if (targetOS.contains("unix")) { const QVariant positionIndependentCode = getCppProp("positionIndependentCode"); if (!positionIndependentCode.isValid() || positionIndependentCode.toBool()) @@ -950,7 +749,7 @@ static void getExpandedCompilerFlags(QStringList &cFlags, QStringList &cxxFlags, } cFlags = cxxFlags = commonFlags; - const auto cxxLanguageVersion = getCppProp("cxxLanguageVersion").toStringList(); + const auto cxxLanguageVersion = arrayToStringList(getCppProp("cxxLanguageVersion")); if (cxxLanguageVersion.contains("c++17")) cxxFlags << "-std=c++17"; else if (cxxLanguageVersion.contains("c++14")) @@ -962,14 +761,14 @@ static void getExpandedCompilerFlags(QStringList &cFlags, QStringList &cxxFlags, const QString cxxStandardLibrary = getCppProp("cxxStandardLibrary").toString(); if (!cxxStandardLibrary.isEmpty() && toolchain.contains("clang")) cxxFlags << ("-stdlib=" + cxxStandardLibrary); - if (enableExceptions.isValid()) { + if (!enableExceptions.isUndefined()) { cxxFlags << QLatin1String(enableExceptions.toBool() ? "-fexceptions" : "-fno-exceptions"); } - if (enableRtti.isValid()) + if (!enableRtti.isUndefined()) cxxFlags << QLatin1String(enableRtti.toBool() ? "-frtti" : "-fno-rtti"); - const auto cLanguageVersion = getCppProp("cLanguageVersion").toStringList(); + const auto cLanguageVersion = arrayToStringList(getCppProp("cLanguageVersion")); if (cLanguageVersion.contains("c11")) cFlags << "-std=c11"; else if (cLanguageVersion.contains("c99")) @@ -999,63 +798,65 @@ static void getExpandedCompilerFlags(QStringList &cFlags, QStringList &cxxFlags, cFlags = cxxFlags = commonFlags; cFlags << "/TC"; cxxFlags << "/TP"; - if (enableRtti.isValid()) + if (!enableRtti.isUndefined()) cxxFlags << QLatin1String(enableRtti.toBool() ? "/GR" : "/GR-"); - if (getCppProp("cxxLanguageVersion").toStringList().contains("c++17")) + if (getCppProp("cxxLanguageVersion").toArray().contains("c++17")) cxxFlags << "/std:c++17"; } } +// TODO: Factor out the part that deals with extra compilers. void QbsBuildSystem::updateCppCodeModel() { OpTimer optimer("updateCppCodeModel"); - if (!m_projectData.isValid()) + const QJsonObject projectData = session()->projectData(); + if (projectData.isEmpty()) return; - const QList factories = ExtraCompilerFactory::extraCompilerFactories(); - const auto factoriesBegin = factories.constBegin(); - const auto factoriesEnd = factories.constEnd(); + const QList factories = + ExtraCompilerFactory::extraCompilerFactories(); + QHash sourcesForGeneratedFiles; + m_sourcesForGeneratedFiles.clear(); - qDeleteAll(m_extraCompilers); - m_extraCompilers.clear(); - - QtSupport::CppKitInfo kitInfo(kit()); + const QtSupport::CppKitInfo kitInfo(kit()); QTC_ASSERT(kitInfo.isValid(), return); RawProjectParts rpps; - foreach (const qbs::ProductData &prd, m_projectData.allProducts()) { + forAllProducts(projectData, [&, this](const QJsonObject &prd) { + const QString productName = prd.value("full-display-name").toString(); QString cPch; QString cxxPch; QString objcPch; QString objcxxPch; - const auto &pchFinder = [&cPch, &cxxPch, &objcPch, &objcxxPch](const qbs::ArtifactData &a) { - const QStringList fileTags = a.fileTags(); + const auto &pchFinder = [&cPch, &cxxPch, &objcPch, &objcxxPch](const QJsonObject &artifact) { + const QJsonArray fileTags = artifact.value("file-tags").toArray(); if (fileTags.contains("c_pch_src")) - cPch = a.filePath(); + cPch = artifact.value("file-path").toString(); if (fileTags.contains("cpp_pch_src")) - cxxPch = a.filePath(); + cxxPch = artifact.value("file-path").toString(); if (fileTags.contains("objc_pch_src")) - objcPch = a.filePath(); + objcPch = artifact.value("file-path").toString(); if (fileTags.contains("objcpp_pch_src")) - objcxxPch = a.filePath(); + objcxxPch = artifact.value("file-path").toString(); }; - const QList &generatedArtifacts = prd.generatedArtifacts(); - std::for_each(generatedArtifacts.cbegin(), generatedArtifacts.cend(), pchFinder); - foreach (const qbs::GroupData &grp, prd.groups()) { - const QList &sourceArtifacts = grp.allSourceArtifacts(); - std::for_each(sourceArtifacts.cbegin(), sourceArtifacts.cend(), pchFinder); - } + forAllArtifacts(prd, ArtifactType::All, pchFinder); const Utils::QtVersion qtVersionForPart - = prd.moduleProperties().getModuleProperty("Qt.core", "version").isValid() - ? kitInfo.projectPartQtVersion - : Utils::QtVersion::None; + = prd.value("module-properties").toObject().value("Qt.core.version").isUndefined() + ? Utils::QtVersion::None + : kitInfo.projectPartQtVersion; + + const QJsonArray groups = prd.value("groups").toArray(); + for (const QJsonValue &g : groups) { + const QJsonObject grp = g.toObject(); + const QString groupName = grp.value("name").toString(); - foreach (const qbs::GroupData &grp, prd.groups()) { RawProjectPart rpp; rpp.setQtVersion(qtVersionForPart); - const qbs::PropertyMap &props = grp.properties(); - rpp.setCallGroupId(groupLocationToCallGroupId(grp.location())); + QJsonObject props = grp.value("module-properties").toObject(); + if (props.isEmpty()) + props = prd.value("module-properties").toObject(); + rpp.setCallGroupId(groupLocationToCallGroupId(grp.value("location").toObject())); QStringList cFlags; QStringList cxxFlags; @@ -1063,50 +864,45 @@ void QbsBuildSystem::updateCppCodeModel() rpp.setFlagsForC({kitInfo.cToolChain, cFlags}); rpp.setFlagsForCxx({kitInfo.cxxToolChain, cxxFlags}); - QStringList list = props.getModulePropertiesAsStringList( - QLatin1String(CONFIG_CPP_MODULE), - QLatin1String(CONFIG_DEFINES)); - rpp.setMacros(Utils::transform(list, [](const QString &s) { return ProjectExplorer::Macro::fromKeyValue(s); })); + rpp.setMacros(transform(arrayToStringList(props.value("cpp.defines")), + [](const QString &s) { return Macro::fromKeyValue(s); })); ProjectExplorer::HeaderPaths grpHeaderPaths; - list = props.getModulePropertiesAsStringList(CONFIG_CPP_MODULE, CONFIG_INCLUDEPATHS); + QStringList list = arrayToStringList(props.value("cpp.includePaths")); list.removeDuplicates(); for (const QString &p : qAsConst(list)) grpHeaderPaths += {FilePath::fromUserInput(p).toString(), HeaderPathType::User}; - - list = props.getModulePropertiesAsStringList(CONFIG_CPP_MODULE, - CONFIG_SYSTEM_INCLUDEPATHS); - + list = arrayToStringList(props.value("cpp.systemIncludePaths")); list.removeDuplicates(); for (const QString &p : qAsConst(list)) grpHeaderPaths += {FilePath::fromUserInput(p).toString(), HeaderPathType::System}; - - list = props.getModulePropertiesAsStringList(QLatin1String(CONFIG_CPP_MODULE), - QLatin1String(CONFIG_FRAMEWORKPATHS)); - list.append(props.getModulePropertiesAsStringList(QLatin1String(CONFIG_CPP_MODULE), - QLatin1String(CONFIG_SYSTEM_FRAMEWORKPATHS))); + list = arrayToStringList(props.value("cpp.frameworkPaths")); + list.append(arrayToStringList(props.value("cpp.systemFrameworkPaths"))); list.removeDuplicates(); - foreach (const QString &p, list) - grpHeaderPaths += {FilePath::fromUserInput(p).toString(), HeaderPathType::Framework}; - + for (const QString &p : qAsConst(list)) { + grpHeaderPaths += {FilePath::fromUserInput(p).toString(), + HeaderPathType::Framework}; + } rpp.setHeaderPaths(grpHeaderPaths); + rpp.setDisplayName(groupName); + const QJsonObject location = grp.value("location").toObject(); + rpp.setProjectFileLocation(location.value("file-path").toString(), + location.value("line").toInt(), + location.value("column").toInt()); + rpp.setBuildSystemTarget(productName); + rpp.setBuildTargetType(prd.value("is-runnable").toBool() + ? BuildTargetType::Executable + : BuildTargetType::Library); - rpp.setDisplayName(grp.name()); - rpp.setProjectFileLocation(grp.location().filePath(), - grp.location().line(), grp.location().column()); - rpp.setBuildSystemTarget(QbsProject::uniqueProductName(prd)); - rpp.setBuildTargetType(prd.isRunnable() ? ProjectExplorer::BuildTargetType::Executable - : ProjectExplorer::BuildTargetType::Library); - - QHash filePathToSourceArtifact; + QHash filePathToSourceArtifact; bool hasCFiles = false; bool hasCxxFiles = false; bool hasObjcFiles = false; bool hasObjcxxFiles = false; - foreach (const qbs::ArtifactData &source, grp.allSourceArtifacts()) { - filePathToSourceArtifact.insert(source.filePath(), source); - - foreach (const QString &tag, source.fileTags()) { + const auto artifactWorker = [&, this](const QJsonObject &source) { + const QString filePath = source.value("file-path").toString(); + filePathToSourceArtifact.insert(filePath, source); + for (const QJsonValue &tag : source.value("file-tags").toArray()) { if (tag == "c") hasCFiles = true; else if (tag == "cpp") @@ -1115,65 +911,52 @@ void QbsBuildSystem::updateCppCodeModel() hasObjcFiles = true; else if (tag == "objcpp") hasObjcxxFiles = true; - for (auto i = factoriesBegin; i != factoriesEnd; ++i) { - if ((*i)->sourceTag() != tag) - continue; - QStringList generated = m_qbsProject.generatedFiles(prd, source.filePath(), - false); - if (generated.isEmpty()) { - // We don't know the target files until we build for the first time. - m_extraCompilersPending = true; - continue; + for (auto i = factories.cbegin(); i != factories.cend(); ++i) { + if ((*i)->sourceTag() == tag.toString()) { + m_sourcesForGeneratedFiles[*i] << filePath; + sourcesForGeneratedFiles[productName] << filePath; } - - const FilePathList fileNames = Utils::transform(generated, - [](const QString &s) { - return Utils::FilePath::fromString(s); - }); - m_extraCompilers.append((*i)->create( - project(), FilePath::fromString(source.filePath()), fileNames)); } } - } + }; + forAllArtifacts(grp, artifactWorker); QSet pchFiles; - if (hasCFiles && props.getModuleProperty("cpp", "useCPrecompiledHeader").toBool() + if (hasCFiles && props.value("cpp.useCPrecompiledHeader").toBool() && !cPch.isEmpty()) { pchFiles << cPch; } - if (hasCxxFiles && props.getModuleProperty("cpp", "useCxxPrecompiledHeader").toBool() + if (hasCxxFiles && props.value("cpp.useCxxPrecompiledHeader").toBool() && !cxxPch.isEmpty()) { pchFiles << cxxPch; } - if (hasObjcFiles && props.getModuleProperty("cpp", "useObjcPrecompiledHeader").toBool() + if (hasObjcFiles && props.value("cpp.useObjcPrecompiledHeader").toBool() && !objcPch.isEmpty()) { pchFiles << objcPch; } if (hasObjcxxFiles - && props.getModuleProperty("cpp", "useObjcxxPrecompiledHeader").toBool() + && props.value("cpp.useObjcxxPrecompiledHeader").toBool() && !objcxxPch.isEmpty()) { pchFiles << objcxxPch; } if (pchFiles.count() > 1) { qCWarning(qbsPmLog) << "More than one pch file enabled for source files in group" - << grp.name() << "in product" << prd.name(); + << groupName << "in product" << productName; qCWarning(qbsPmLog) << "Expect problems with code model"; } rpp.setPreCompiledHeaders(Utils::toList(pchFiles)); - rpp.setFiles(grp.allFilePaths(), - {}, + rpp.setFiles(filePathToSourceArtifact.keys(), {}, [filePathToSourceArtifact](const QString &filePath) { - // Keep this lambda thread-safe! - return getMimeType(filePathToSourceArtifact.value(filePath)); - }); - + // Keep this lambda thread-safe! + return getMimeType(filePathToSourceArtifact.value(filePath)); + }); rpps.append(rpp); - } - } + }); - CppTools::GeneratedCodeModelSupport::update(m_extraCompilers); m_cppCodeModelUpdater->update({project(), kitInfo, activeParseEnvironment(), rpps}); + if (!sourcesForGeneratedFiles.isEmpty()) + session()->requestFilesGeneratedFrom(sourcesForGeneratedFiles); } void QbsBuildSystem::updateQmlJsCodeModel() @@ -1182,16 +965,20 @@ void QbsBuildSystem::updateQmlJsCodeModel() QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); if (!modelManager) return; - QmlJS::ModelManagerInterface::ProjectInfo projectInfo = modelManager->defaultProjectInfoForProject(project()); - foreach (const qbs::ProductData &product, m_projectData.allProducts()) { - static const QString propertyName = QLatin1String("qmlImportPaths"); - foreach (const QString &path, product.properties().value(propertyName).toStringList()) { - projectInfo.importPaths.maybeInsert(Utils::FilePath::fromString(path), + + const QJsonObject projectData = session()->projectData(); + if (projectData.isEmpty()) + return; + + forAllProducts(projectData, [&projectInfo](const QJsonObject &product) { + for (const QJsonValue &path : product.value("properties").toObject() + .value("qmlImportPaths").toArray()) { + projectInfo.importPaths.maybeInsert(Utils::FilePath::fromString(path.toString()), QmlJS::Dialect::Qml); } - } + }); project()->setProjectLanguage(ProjectExplorer::Constants::QMLJS_LANGUAGE_ID, !projectInfo.sourceFiles.isEmpty()); @@ -1201,34 +988,40 @@ void QbsBuildSystem::updateQmlJsCodeModel() void QbsBuildSystem::updateApplicationTargets() { QList applications; - foreach (const qbs::ProductData &productData, m_projectData.allProducts()) { - if (!productData.isEnabled() || !productData.isRunnable()) - continue; - const bool isQtcRunnable = productData.properties().value("qtcRunnable").toBool(); - const bool usesTerminal = productData.properties().value("consoleApplication").toBool(); - const QString projectFile = productData.location().filePath(); + forAllProducts(session()->projectData(), [this, &applications](const QJsonObject &productData) { + if (!productData.value("is-enabled").toBool() || !productData.value("is-runnable").toBool()) + return; + + // TODO: Perhaps put this into a central location instead. Same for module properties etc + const auto getProp = [productData](const QString &propName) { + return productData.value("properties").toObject().value(propName); + }; + const bool isQtcRunnable = getProp("qtcRunnable").toBool(); + const bool usesTerminal = getProp("consoleApplication").toBool(); + const QString projectFile = productData.value("location").toObject() + .value("file-path").toString(); QString targetFile; - foreach (const qbs::ArtifactData &ta, productData.targetArtifacts()) { - QTC_ASSERT(ta.isValid(), continue); - if (ta.isExecutable()) { - targetFile = ta.filePath(); + for (const QJsonValue &v : productData.value("generated-artifacts").toArray()) { + const QJsonObject artifact = v.toObject(); + if (artifact.value("is-target").toBool() && artifact.value("is-executable").toBool()) { + targetFile = artifact.value("file-path").toString(); break; } } - BuildTargetInfo bti; - bti.buildKey = QbsProject::uniqueProductName(productData); + bti.buildKey = productData.value("full-display-name").toString(); bti.targetFilePath = FilePath::fromString(targetFile); bti.projectFilePath = FilePath::fromString(projectFile); bti.isQtcRunnable = isQtcRunnable; // Fixed up below. bti.usesTerminal = usesTerminal; - bti.displayName = productData.fullDisplayName(); + bti.displayName = bti.buildKey; bti.runEnvModifier = [targetFile, productData, this](Utils::Environment &env, bool usingLibraryPaths) { - if (!qbsProject().isValid()) + const QString productName = productData.value("full-display-name").toString(); + if (session()->projectData().isEmpty()) return; const QString key = env.toStringList().join(QChar()) - + QbsProject::uniqueProductName(productData) + + productName + QString::number(usingLibraryPaths); const auto it = m_envCache.constFind(key); if (it != m_envCache.constEnd()) { @@ -1237,41 +1030,48 @@ void QbsBuildSystem::updateApplicationTargets() } QProcessEnvironment procEnv = env.toProcessEnvironment(); - procEnv.insert(QLatin1String("QBS_RUN_FILE_PATH"), targetFile); + procEnv.insert("QBS_RUN_FILE_PATH", targetFile); QStringList setupRunEnvConfig; if (!usingLibraryPaths) - setupRunEnvConfig << QLatin1String("ignore-lib-dependencies"); - qbs::RunEnvironment qbsRunEnv = qbsProject().getRunEnvironment(productData, - qbs::InstallOptions(), procEnv, setupRunEnvConfig, QbsManager::settings()); - qbs::ErrorInfo error; - procEnv = qbsRunEnv.runEnvironment(&error); - if (error.hasError()) { + setupRunEnvConfig << "ignore-lib-dependencies"; + // TODO: It'd be preferable if we could somenow make this asynchronous. + RunEnvironmentResult result = session()->getRunEnvironment(productName, procEnv, + setupRunEnvConfig); + if (result.error().hasError()) { Core::MessageManager::write(tr("Error retrieving run environment: %1") - .arg(error.toString())); - } - if (!procEnv.isEmpty()) { + .arg(result.error().toString())); + } else { + QProcessEnvironment fullEnv = result.environment(); + QTC_ASSERT(!fullEnv.isEmpty(), fullEnv = procEnv); env = Utils::Environment(); - foreach (const QString &key, procEnv.keys()) - env.set(key, procEnv.value(key)); + for (const QString &key : fullEnv.keys()) + env.set(key, fullEnv.value(key)); } - m_envCache.insert(key, env); }; applications.append(bti); - } + }); setApplicationTargets(applications); } void QbsBuildSystem::updateDeploymentInfo() { + if (session()->projectData().isEmpty()) + return; DeploymentData deploymentData; - if (m_qbsProject.isValid()) { - foreach (const qbs::ArtifactData &f, m_projectData.installableArtifacts()) { - deploymentData.addFile(f.filePath(), f.installData().installDir(), - f.isExecutable() ? DeployableFile::TypeExecutable : DeployableFile::TypeNormal); - } - } + forAllProducts(session()->projectData(), [&deploymentData](const QJsonObject &product) { + forAllArtifacts(product, ArtifactType::All, [&deploymentData](const QJsonObject &artifact) { + const QJsonObject installData = artifact.value("install-data").toObject(); + if (installData.value("is-installable").toBool()) { + deploymentData.addFile( + artifact.value("file-path").toString(), + QFileInfo(installData.value("install-file-path").toString()).path(), + artifact.value("is-executable").toBool() + ? DeployableFile::TypeExecutable : DeployableFile::TypeNormal); + } + }); + }); deploymentData.setLocalInstallRoot(installRoot()); setDeploymentData(deploymentData); } diff --git a/src/plugins/qbsprojectmanager/qbsproject.h b/src/plugins/qbsprojectmanager/qbsproject.h index 69d79c89ca1..c1bc6daea37 100644 --- a/src/plugins/qbsprojectmanager/qbsproject.h +++ b/src/plugins/qbsprojectmanager/qbsproject.h @@ -25,7 +25,7 @@ #pragma once -#include "qbsprojectmanager.h" +#include "qbsprofilemanager.h" #include "qbsnodes.h" @@ -36,9 +36,8 @@ #include -#include - #include +#include #include 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 &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 &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 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 m_qbsDocuments; + QJsonObject m_projectData; // TODO: Perhaps store this in the root project node instead? + QTimer m_parsingDelay; QbsProjectParser *m_qbsProjectParser = nullptr; - QFutureInterface *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 m_sourcesForGeneratedFiles; QList m_extraCompilers; - bool m_extraCompilersPending = false; QHash m_envCache; diff --git a/src/plugins/qbsprojectmanager/qbsprojectimporter.cpp b/src/plugins/qbsprojectmanager/qbsprojectimporter.cpp index 10b7e3fa4ce..46b1719d84f 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectimporter.cpp +++ b/src/plugins/qbsprojectmanager/qbsprojectimporter.cpp @@ -27,6 +27,7 @@ #include "qbsbuildconfiguration.h" #include "qbspmlogging.h" +#include "qbssession.h" #include #include @@ -42,8 +43,6 @@ #include #include -#include - #include 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 QbsProjectImporter::examineDirectory(const FilePath &importPath) c { qCDebug(qbsPmLog) << "examining build directory" << importPath.toUserOutput(); QList 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; diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanager.cpp b/src/plugins/qbsprojectmanager/qbsprojectmanager.cpp deleted file mode 100644 index 41cbe1763bd..00000000000 --- a/src/plugins/qbsprojectmanager/qbsprojectmanager.cpp +++ /dev/null @@ -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 -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -const QChar sep = QLatin1Char('.'); - -static QString qtcProfileGroup() { return QLatin1String("preferences.qtcreator.kit"); } -static QString qtcProfilePrefix() { return qtcProfileGroup() + sep; } - -namespace QbsProjectManager { - -static QList 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(qbs::LoggerMinLevel)) - tmp = static_cast(qbs::LoggerMinLevel); - if (tmp > static_cast(qbs::LoggerMaxLevel)) - tmp = static_cast(qbs::LoggerMaxLevel); - level = tmp; - } - } - m_logSink->setLogLevel(static_cast(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(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 diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanager.pro b/src/plugins/qbsprojectmanager/qbsprojectmanager.pro index 1b8896b33c9..a03f4d4bb07 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectmanager.pro +++ b/src/plugins/qbsprojectmanager/qbsprojectmanager.pro @@ -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 \ diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanager.qbs b/src/plugins/qbsprojectmanager/qbsprojectmanager.qbs index 5c933991af8..9830c06efc9 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectmanager.qbs +++ b/src/plugins/qbsprojectmanager/qbsprojectmanager.qbs @@ -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 diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanagerconstants.h b/src/plugins/qbsprojectmanager/qbsprojectmanagerconstants.h index 43cf1ce722a..c64fead172d 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectmanagerconstants.h +++ b/src/plugins/qbsprojectmanager/qbsprojectmanagerconstants.h @@ -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"; diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp b/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp index d7f860ec17d..2a05225a1cb 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp +++ b/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp @@ -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 #include @@ -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(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 &stepType Node *node = currentEditorNode(); if (!node) return; - auto product = dynamic_cast(node->parentProjectNode()); - if (!product) + auto productNode = dynamic_cast(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 QListqbsProjectData().allProducts()) - toBuild << QbsProject::uniqueProductName(data); - + forAllProducts(subProject->projectData(), [&toBuild](const QJsonObject &data) { + toBuild << data.value("full-display-name").toString(); + }); runStepsForProducts(project, toBuild, {stepTypes}); } diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanagersettings.cpp b/src/plugins/qbsprojectmanager/qbsprojectmanagersettings.cpp deleted file mode 100644 index c94bc1c0120..00000000000 --- a/src/plugins/qbsprojectmanager/qbsprojectmanagersettings.cpp +++ /dev/null @@ -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 - -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 diff --git a/src/plugins/qbsprojectmanager/qbsprojectparser.cpp b/src/plugins/qbsprojectmanager/qbsprojectparser.cpp index 6e2c9f39368..6d36a150224 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectparser.cpp +++ b/src/plugins/qbsprojectmanager/qbsprojectparser.cpp @@ -25,19 +25,16 @@ #include "qbsprojectparser.h" -#include "qbslogsink.h" #include "qbsproject.h" #include "qbsprojectmanagerconstants.h" +#include "qbssettings.h" -#include #include -#include - -#include #include #include #include +#include using namespace Utils; @@ -48,11 +45,11 @@ namespace Internal { // QbsProjectParser: // -------------------------------------------------------------------- -QbsProjectParser::QbsProjectParser(QbsBuildSystem *buildSystem, QFutureInterface *fi) : - m_fi(fi) +QbsProjectParser::QbsProjectParser(QbsBuildSystem *buildSystem, QFutureInterface *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(this); connect(watcher, &QFutureWatcher::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 diff --git a/src/plugins/qbsprojectmanager/qbsprojectparser.h b/src/plugins/qbsprojectmanager/qbsprojectparser.h index c6353d3df4f..c4ce79adc60 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectparser.h +++ b/src/plugins/qbsprojectmanager/qbsprojectparser.h @@ -25,13 +25,14 @@ #pragma once +#include "qbssession.h" + #include #include +#include #include -#include - 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 *m_fi = nullptr; - int m_currentProgressBase = 0; }; } // namespace Internal diff --git a/src/plugins/qbsprojectmanager/qbssession.cpp b/src/plugins/qbsprojectmanager/qbssession.cpp new file mode 100644 index 00000000000..96606ebb709 --- /dev/null +++ b/src/plugins/qbsprojectmanager/qbssession.cpp @@ -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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace ProjectExplorer; +using namespace Utils; + +namespace QbsProjectManager { +namespace Internal { + +QStringList arrayToStringList(const QJsonValue &array) +{ + return transform(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 generatedFilesForSources; + optional 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::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::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 &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 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(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 diff --git a/src/plugins/qbsprojectmanager/qbssession.h b/src/plugins/qbsprojectmanager/qbssession.h new file mode 100644 index 00000000000..d9775b6f0bf --- /dev/null +++ b/src/plugins/qbsprojectmanager/qbssession.h @@ -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 +#include + +#include +#include +#include +#include +#include +#include + +#include + +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 items; +}; + +template 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 +{ +public: + using SynchronousRequestResult::SynchronousRequestResult; + QStringList failedFiles () const { return m_data; } +}; + +class RunEnvironmentResult : public SynchronousRequestResult +{ +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 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 lastError() const; + static QString errorString(Error error); + QJsonObject projectData() const; + + void sendRequest(const QJsonObject &request); + void cancelCurrentJob(); + void quit(); + void requestFilesGeneratedFrom(const QHash &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 &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 diff --git a/src/plugins/qbsprojectmanager/qbssettings.cpp b/src/plugins/qbsprojectmanager/qbssettings.cpp new file mode 100644 index 00000000000..b79ceb9a6b5 --- /dev/null +++ b/src/plugins/qbsprojectmanager/qbssettings.cpp @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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 diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanagersettings.h b/src/plugins/qbsprojectmanager/qbssettings.h similarity index 63% rename from src/plugins/qbsprojectmanager/qbsprojectmanagersettings.h rename to src/plugins/qbsprojectmanager/qbssettings.h index 08eb11cafc6..e636da65d35 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectmanagersettings.h +++ b/src/plugins/qbsprojectmanager/qbssettings.h @@ -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 +#include + #include +#include 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 m_widget; }; } // namespace Internal diff --git a/src/src.qbs b/src/src.qbs index ff52425d5de..4f8db24ca98 100644 --- a/src/src.qbs +++ b/src/src.qbs @@ -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"]