aboutsummaryrefslogtreecommitdiffstats
path: root/src/app/qbs/session.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/qbs/session.cpp')
-rw-r--r--src/app/qbs/session.cpp751
1 files changed, 751 insertions, 0 deletions
diff --git a/src/app/qbs/session.cpp b/src/app/qbs/session.cpp
new file mode 100644
index 000000000..0b93aff49
--- /dev/null
+++ b/src/app/qbs/session.cpp
@@ -0,0 +1,751 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "session.h"
+
+#include "sessionpacket.h"
+#include "sessionpacketreader.h"
+
+#include <api/jobs.h>
+#include <api/project.h>
+#include <api/projectdata.h>
+#include <api/runenvironment.h>
+#include <logging/ilogsink.h>
+#include <tools/buildoptions.h>
+#include <tools/cleanoptions.h>
+#include <tools/error.h>
+#include <tools/installoptions.h>
+#include <tools/jsonhelper.h>
+#include <tools/preferences.h>
+#include <tools/processresult.h>
+#include <tools/qbsassert.h>
+#include <tools/settings.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qprocess.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <iostream>
+#include <memory>
+
+namespace qbs {
+namespace Internal {
+
+class SessionLogSink : public QObject, public ILogSink
+{
+ Q_OBJECT
+signals:
+ void newMessage(const QJsonObject &msg);
+
+private:
+ void doPrintMessage(LoggerLevel, const QString &message, const QString &) override
+ {
+ QJsonObject msg;
+ msg.insert(StringConstants::type(), QLatin1String("log-data"));
+ msg.insert(StringConstants::messageKey(), message);
+ emit newMessage(msg);
+ }
+
+ void doPrintWarning(const ErrorInfo &warning) override
+ {
+ QJsonObject msg;
+ static const QString warningString(QLatin1String("warning"));
+ msg.insert(StringConstants::type(), warningString);
+ msg.insert(warningString, warning.toJson());
+ emit newMessage(msg);
+ }
+};
+
+class Session : public QObject
+{
+ Q_OBJECT
+public:
+ Session();
+
+private:
+ enum class ProjectDataMode { Never, Always, OnlyIfChanged };
+ ProjectDataMode dataModeFromRequest(const QJsonObject &request);
+ QStringList modulePropertiesFromRequest(const QJsonObject &request);
+ void insertProjectDataIfNecessary(
+ QJsonObject &reply,
+ ProjectDataMode dataMode,
+ const ProjectData &oldProjectData,
+ bool includeTopLevelData
+ );
+ void setLogLevelFromRequest(const QJsonObject &request);
+ bool checkNormalRequestPrerequisites(const char *replyType);
+
+ void sendPacket(const QJsonObject &message);
+ void setupProject(const QJsonObject &request);
+ void buildProject(const QJsonObject &request);
+ void cleanProject(const QJsonObject &request);
+ void installProject(const QJsonObject &request);
+ void addFiles(const QJsonObject &request);
+ void removeFiles(const QJsonObject &request);
+ void getRunEnvironment(const QJsonObject &request);
+ void getGeneratedFilesForSources(const QJsonObject &request);
+ void releaseProject();
+ void cancelCurrentJob();
+ void quitSession();
+
+ void sendErrorReply(const char *replyType, const QString &message);
+ void sendErrorReply(const char *replyType, const ErrorInfo &error);
+ void insertErrorInfoIfNecessary(QJsonObject &reply, const ErrorInfo &error);
+ void connectProgressSignals(AbstractJob *job);
+ QList<ProductData> getProductsByName(const QStringList &productNames) const;
+ ProductData getProductByName(const QString &productName) const;
+
+ struct ProductSelection {
+ ProductSelection(Project::ProductSelection s) : selection(s) {}
+ ProductSelection(const QList<ProductData> &p) : products(p) {}
+
+ Project::ProductSelection selection = Project::ProductSelectionDefaultOnly;
+ QList<ProductData> products;
+ };
+ ProductSelection getProductSelection(const QJsonObject &request);
+
+ struct FileUpdateData {
+ QJsonObject createErrorReply(const char *type, const QString &mainMessage) const;
+
+ ProductData product;
+ GroupData group;
+ QStringList filePaths;
+ ErrorInfo error;
+ };
+ FileUpdateData prepareFileUpdate(const QJsonObject &request);
+
+ SessionPacketReader m_packetReader;
+ Project m_project;
+ ProjectData m_projectData;
+ SessionLogSink m_logSink;
+ std::unique_ptr<Settings> m_settings;
+ QJsonObject m_resolveRequest;
+ QStringList m_moduleProperties;
+ AbstractJob *m_currentJob = nullptr;
+};
+
+void startSession()
+{
+ const auto session = new Session;
+ QObject::connect(qApp, &QCoreApplication::aboutToQuit, session, [session] { delete session; });
+}
+
+Session::Session()
+{
+ sendPacket(SessionPacket::helloMessage());
+ connect(&m_logSink, &SessionLogSink::newMessage, this, &Session::sendPacket);
+ connect(&m_packetReader, &SessionPacketReader::errorOccurred,
+ this, [](const QString &msg) {
+ std::cerr << qPrintable(tr("Error: %1").arg(msg));
+ qApp->exit(EXIT_FAILURE);
+ });
+ connect(&m_packetReader, &SessionPacketReader::packetReceived, this, [this](const QJsonObject &packet) {
+ // qDebug() << "got packet:" << packet; // Uncomment for debugging.
+ const QString type = packet.value(StringConstants::type()).toString();
+ if (type == QLatin1String("resolve-project"))
+ setupProject(packet);
+ else if (type == QLatin1String("build-project"))
+ buildProject(packet);
+ else if (type == QLatin1String("clean-project"))
+ cleanProject(packet);
+ else if (type == QLatin1String("install-project"))
+ installProject(packet);
+ else if (type == QLatin1String("add-files"))
+ addFiles(packet);
+ else if (type == QLatin1String("remove-files"))
+ removeFiles(packet);
+ else if (type == QLatin1String("get-run-environment"))
+ getRunEnvironment(packet);
+ else if (type == QLatin1String("get-generated-files-for-sources"))
+ getGeneratedFilesForSources(packet);
+ else if (type == QLatin1String("release-project"))
+ releaseProject();
+ else if (type == QLatin1String("quit"))
+ quitSession();
+ else if (type == QLatin1String("cancel-job"))
+ cancelCurrentJob();
+ else
+ sendErrorReply("protocol-error", tr("Unknown request type '%1'.").arg(type));
+ });
+ m_packetReader.start();
+}
+
+Session::ProjectDataMode Session::dataModeFromRequest(const QJsonObject &request)
+{
+ const QString modeString = request.value(QLatin1String("data-mode")).toString();
+ if (modeString == QLatin1String("only-if-changed"))
+ return ProjectDataMode::OnlyIfChanged;
+ if (modeString == QLatin1String("always"))
+ return ProjectDataMode::Always;
+ return ProjectDataMode::Never;
+}
+
+void Session::sendPacket(const QJsonObject &message)
+{
+ std::cout << SessionPacket::createPacket(message).constData() << std::flush;
+}
+
+void Session::setupProject(const QJsonObject &request)
+{
+ if (m_currentJob) {
+ if (qobject_cast<SetupProjectJob *>(m_currentJob)
+ && m_currentJob->state() == AbstractJob::StateCanceling) {
+ m_resolveRequest = std::move(request);
+ return;
+ }
+ sendErrorReply("project-resolved",
+ tr("Cannot start resolving while another job is still running."));
+ return;
+ }
+ m_moduleProperties = modulePropertiesFromRequest(request);
+ auto params = SetupProjectParameters::fromJson(request);
+ const ProjectDataMode dataMode = dataModeFromRequest(request);
+ m_settings.reset(new Settings(params.settingsDirectory()));
+ const Preferences prefs(m_settings.get());
+ const QString appDir = QDir::cleanPath(QCoreApplication::applicationDirPath());
+ params.setSearchPaths(prefs.searchPaths(appDir + QLatin1String(
+ "/" QBS_RELATIVE_SEARCH_PATH)));
+ params.setPluginPaths(prefs.pluginPaths(appDir + QLatin1String(
+ "/" QBS_RELATIVE_PLUGINS_PATH)));
+ params.setLibexecPath(appDir + QLatin1String("/" QBS_RELATIVE_LIBEXEC_PATH));
+ params.setOverrideBuildGraphData(true);
+ setLogLevelFromRequest(request);
+ SetupProjectJob * const setupJob = m_project.setupProject(params, &m_logSink, this);
+ m_currentJob = setupJob;
+ connectProgressSignals(setupJob);
+ connect(setupJob, &AbstractJob::finished, this,
+ [this, setupJob, dataMode](bool success) {
+ if (!m_resolveRequest.isEmpty()) { // Canceled job was superseded.
+ const QJsonObject newRequest = std::move(m_resolveRequest);
+ m_resolveRequest = QJsonObject();
+ m_currentJob->deleteLater();
+ m_currentJob = nullptr;
+ setupProject(newRequest);
+ return;
+ }
+ const ProjectData oldProjectData = m_projectData;
+ m_project = setupJob->project();
+ m_projectData = m_project.projectData();
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String("project-resolved"));
+ if (success)
+ insertProjectDataIfNecessary(reply, dataMode, oldProjectData, true);
+ else
+ insertErrorInfoIfNecessary(reply, setupJob->error());
+ sendPacket(reply);
+ m_currentJob->deleteLater();
+ m_currentJob = nullptr;
+ });
+}
+
+void Session::buildProject(const QJsonObject &request)
+{
+ if (!checkNormalRequestPrerequisites("build-done"))
+ return;
+ const ProductSelection productSelection = getProductSelection(request);
+ setLogLevelFromRequest(request);
+ auto options = BuildOptions::fromJson(request);
+ options.setSettingsDirectory(m_settings->baseDirectory());
+ BuildJob * const buildJob = productSelection.products.empty()
+ ? m_project.buildAllProducts(options, productSelection.selection, this)
+ : m_project.buildSomeProducts(productSelection.products, options, this);
+ m_currentJob = buildJob;
+ m_moduleProperties = modulePropertiesFromRequest(request);
+ const ProjectDataMode dataMode = dataModeFromRequest(request);
+ connectProgressSignals(buildJob);
+ connect(buildJob, &BuildJob::reportCommandDescription, this,
+ [this](const QString &highlight, const QString &message) {
+ QJsonObject descData;
+ descData.insert(StringConstants::type(), QLatin1String("command-description"));
+ descData.insert(QLatin1String("highlight"), highlight);
+ descData.insert(StringConstants::messageKey(), message);
+ sendPacket(descData);
+ });
+ connect(buildJob, &BuildJob::reportProcessResult, this, [this](const ProcessResult &result) {
+ if (result.success() && result.stdOut().isEmpty() && result.stdErr().isEmpty())
+ return;
+ QJsonObject resultData = result.toJson();
+ resultData.insert(StringConstants::type(), QLatin1String("process-result"));
+ sendPacket(resultData);
+ });
+ connect(buildJob, &BuildJob::finished, this,
+ [this, dataMode](bool success) {
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String("project-built"));
+ const ProjectData oldProjectData = m_projectData;
+ m_projectData = m_project.projectData();
+ if (success)
+ insertProjectDataIfNecessary(reply, dataMode, oldProjectData, false);
+ else
+ insertErrorInfoIfNecessary(reply, m_currentJob->error());
+ sendPacket(reply);
+ m_currentJob->deleteLater();
+ m_currentJob = nullptr;
+ });
+}
+
+void Session::cleanProject(const QJsonObject &request)
+{
+ if (!checkNormalRequestPrerequisites("project-cleaned"))
+ return;
+ setLogLevelFromRequest(request);
+ const ProductSelection productSelection = getProductSelection(request);
+ const auto options = CleanOptions::fromJson(request);
+ m_currentJob = productSelection.products.empty()
+ ? m_project.cleanAllProducts(options, this)
+ : m_project.cleanSomeProducts(productSelection.products, options, this);
+ connectProgressSignals(m_currentJob);
+ connect(m_currentJob, &AbstractJob::finished, this, [this](bool success) {
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String("project-cleaned"));
+ if (!success)
+ insertErrorInfoIfNecessary(reply, m_currentJob->error());
+ sendPacket(reply);
+ m_currentJob->deleteLater();
+ m_currentJob = nullptr;
+ });
+}
+
+void Session::installProject(const QJsonObject &request)
+{
+ if (!checkNormalRequestPrerequisites("install-done"))
+ return;
+ setLogLevelFromRequest(request);
+ const ProductSelection productSelection = getProductSelection(request);
+ const auto options = InstallOptions::fromJson(request);
+ m_currentJob = productSelection.products.empty()
+ ? m_project.installAllProducts(options, productSelection.selection, this)
+ : m_project.installSomeProducts(productSelection.products, options, this);
+ connectProgressSignals(m_currentJob);
+ connect(m_currentJob, &AbstractJob::finished, this, [this](bool success) {
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String("install-done"));
+ if (!success)
+ insertErrorInfoIfNecessary(reply, m_currentJob->error());
+ sendPacket(reply);
+ m_currentJob->deleteLater();
+ m_currentJob = nullptr;
+ });
+}
+
+void Session::addFiles(const QJsonObject &request)
+{
+ const FileUpdateData data = prepareFileUpdate(request);
+ if (data.error.hasError()) {
+ sendPacket(data.createErrorReply("files-added", tr("Failed to add files to project: %1")
+ .arg(data.error.toString())));
+ return;
+ }
+ ErrorInfo error;
+ QStringList failedFiles;
+#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES
+ for (const QString &filePath : data.filePaths) {
+ const ErrorInfo e = m_project.addFiles(data.product, data.group, {filePath});
+ if (e.hasError()) {
+ for (const ErrorItem &ei : e.items())
+ error.append(ei);
+ failedFiles.push_back(filePath);
+ }
+ }
+#endif
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String("files-added"));
+ insertErrorInfoIfNecessary(reply, error);
+
+ if (failedFiles.size() != data.filePaths.size()) {
+ // Note that Project::addFiles() directly changes the existing project data object, so
+ // there's no need to retrieve it from m_project.
+ insertProjectDataIfNecessary(reply, ProjectDataMode::Always, {}, false);
+ }
+
+ if (!failedFiles.isEmpty())
+ reply.insert(QLatin1String("failed-files"), QJsonArray::fromStringList(failedFiles));
+ sendPacket(reply);
+}
+
+void Session::removeFiles(const QJsonObject &request)
+{
+ const FileUpdateData data = prepareFileUpdate(request);
+ if (data.error.hasError()) {
+ sendPacket(data.createErrorReply("files-removed",
+ tr("Failed to remove files from project: %1")
+ .arg(data.error.toString())));
+ return;
+ }
+ ErrorInfo error;
+ QStringList failedFiles;
+#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES
+ for (const QString &filePath : data.filePaths) {
+ const ErrorInfo e = m_project.removeFiles(data.product, data.group, {filePath});
+ if (e.hasError()) {
+ for (const ErrorItem &ei : e.items())
+ error.append(ei);
+ failedFiles.push_back(filePath);
+ }
+ }
+#endif
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String("files-removed"));
+ insertErrorInfoIfNecessary(reply, error);
+ if (failedFiles.size() != data.filePaths.size())
+ insertProjectDataIfNecessary(reply, ProjectDataMode::Always, {}, false);
+ if (!failedFiles.isEmpty())
+ reply.insert(QLatin1String("failed-files"), QJsonArray::fromStringList(failedFiles));
+ sendPacket(reply);
+}
+
+void Session::connectProgressSignals(AbstractJob *job)
+{
+ static QString maxProgressString(QLatin1String("max-progress"));
+ connect(job, &AbstractJob::taskStarted, this,
+ [this](const QString &description, int maxProgress) {
+ QJsonObject msg;
+ msg.insert(StringConstants::type(), QLatin1String("task-started"));
+ msg.insert(StringConstants::descriptionProperty(), description);
+ msg.insert(maxProgressString, maxProgress);
+ sendPacket(msg);
+ });
+ connect(job, &AbstractJob::totalEffortChanged, this, [this](int maxProgress) {
+ QJsonObject msg;
+ msg.insert(StringConstants::type(), QLatin1String("new-max-progress"));
+ msg.insert(maxProgressString, maxProgress);
+ sendPacket(msg);
+ });
+ connect(job, &AbstractJob::taskProgress, this, [this](int progress) {
+ QJsonObject msg;
+ msg.insert(StringConstants::type(), QLatin1String("task-progress"));
+ msg.insert(QLatin1String("progress"), progress);
+ sendPacket(msg);
+ });
+}
+
+static QList<ProductData> getProductsByNameForProject(const ProjectData &project,
+ QStringList &productNames)
+{
+ QList<ProductData> products;
+ if (productNames.empty())
+ return products;
+ for (const ProductData &p : project.products()) {
+ for (auto it = productNames.begin(); it != productNames.end(); ++it) {
+ if (*it == p.fullDisplayName()) {
+ products << p;
+ productNames.erase(it);
+ if (productNames.empty())
+ return products;
+ break;
+ }
+ }
+ }
+ for (const ProjectData &p : project.subProjects()) {
+ products << getProductsByNameForProject(p, productNames);
+ if (productNames.empty())
+ break;
+ }
+ return products;
+}
+
+QList<ProductData> Session::getProductsByName(const QStringList &productNames) const
+{
+ QStringList remainingNames = productNames;
+ return getProductsByNameForProject(m_projectData, remainingNames);
+}
+
+ProductData Session::getProductByName(const QString &productName) const
+{
+ const QList<ProductData> products = getProductsByName({productName});
+ return products.empty() ? ProductData() : products.first();
+}
+
+void Session::getRunEnvironment(const QJsonObject &request)
+{
+ const char * const replyType = "run-environment";
+ if (!checkNormalRequestPrerequisites(replyType))
+ return;
+ const QString productName = request.value(QLatin1String("product")).toString();
+ const ProductData product = getProductByName(productName);
+ if (!product.isValid()) {
+ sendErrorReply(replyType, tr("No such product '%1'.").arg(productName));
+ return;
+ }
+ const auto inEnv = fromJson<QProcessEnvironment>(
+ request.value(QLatin1String("base-environment")));
+ const QStringList config = fromJson<QStringList>(request.value(QLatin1String("config")));
+ const RunEnvironment runEnv = m_project.getRunEnvironment(product, InstallOptions(), inEnv,
+ config, m_settings.get());
+ ErrorInfo error;
+ const QProcessEnvironment outEnv = runEnv.runEnvironment(&error);
+ if (error.hasError()) {
+ sendErrorReply(replyType, error);
+ return;
+ }
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String(replyType));
+ QJsonObject outEnvObj;
+ const QStringList keys = outEnv.keys();
+ for (const QString &key : keys)
+ outEnvObj.insert(key, outEnv.value(key));
+ reply.insert(QLatin1String("full-environment"), outEnvObj);
+ sendPacket(reply);
+}
+
+void Session::getGeneratedFilesForSources(const QJsonObject &request)
+{
+ const char * const replyType = "generated-files-for-sources";
+ if (!checkNormalRequestPrerequisites(replyType))
+ return;
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String(replyType));
+ const QJsonArray specs = request.value(StringConstants::productsKey()).toArray();
+ QJsonArray resultProducts;
+ for (const QJsonValue &p : specs) {
+ const QJsonObject productObject = p.toObject();
+ const ProductData product = getProductByName(
+ productObject.value(StringConstants::fullDisplayNameKey()).toString());
+ if (!product.isValid())
+ continue;
+ QJsonObject resultProduct;
+ resultProduct.insert(StringConstants::fullDisplayNameKey(), product.fullDisplayName());
+ QJsonArray results;
+ const QJsonArray requests = productObject.value(QLatin1String("requests")).toArray();
+ for (const QJsonValue &r : requests) {
+ const QJsonObject request = r.toObject();
+ const QString filePath = request.value(QLatin1String("source-file")).toString();
+ const QStringList tags = fromJson<QStringList>(request.value(QLatin1String("tags")));
+ const bool recursive = request.value(QLatin1String("recursive")).toBool();
+ const QStringList generatedFiles = m_project.generatedFiles(product, filePath,
+ recursive, tags);
+ if (!generatedFiles.isEmpty()) {
+ QJsonObject result;
+ result.insert(QLatin1String("source-file"), filePath);
+ result.insert(QLatin1String("generated-files"),
+ QJsonArray::fromStringList(generatedFiles));
+ results << result;
+ }
+ }
+ if (!results.isEmpty()) {
+ resultProduct.insert(QLatin1String("results"), results);
+ resultProducts << resultProduct;
+ }
+ }
+ reply.insert(StringConstants::productsKey(), resultProducts);
+ sendPacket(reply);
+}
+
+void Session::releaseProject()
+{
+ const char * const replyType = "project-released";
+ if (!m_project.isValid()) {
+ sendErrorReply(replyType, tr("No open project."));
+ return;
+ }
+ if (m_currentJob) {
+ m_currentJob->disconnect(this);
+ m_currentJob->cancel();
+ m_currentJob = nullptr;
+ }
+ m_project = Project();
+ m_projectData = ProjectData();
+ m_resolveRequest = QJsonObject();
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String(replyType));
+ sendPacket(reply);
+}
+
+void Session::cancelCurrentJob()
+{
+ if (m_currentJob) {
+ if (!m_resolveRequest.isEmpty())
+ m_resolveRequest = QJsonObject();
+ m_currentJob->cancel();
+ }
+}
+
+Session::ProductSelection Session::getProductSelection(const QJsonObject &request)
+{
+ const QJsonValue productSelection = request.value(StringConstants::productsKey());
+ if (productSelection.isArray())
+ return ProductSelection(getProductsByName(fromJson<QStringList>(productSelection)));
+ return ProductSelection(productSelection.toString() == QLatin1String("all")
+ ? Project::ProductSelectionWithNonDefault
+ : Project::ProductSelectionDefaultOnly);
+}
+
+Session::FileUpdateData Session::prepareFileUpdate(const QJsonObject &request)
+{
+ FileUpdateData data;
+ const QString productName = request.value(QLatin1String("product")).toString();
+ data.product = getProductByName(productName);
+ if (data.product.isValid()) {
+ const QString groupName = request.value(QLatin1String("group")).toString();
+ for (const GroupData &g : data.product.groups()) {
+ if (g.name() == groupName) {
+ data.group = g;
+ break;
+ }
+ }
+ if (!data.group.isValid())
+ data.error = tr("Group '%1' not found in product '%2'.").arg(groupName, productName);
+ } else {
+ data.error = tr("Product '%1' not found in project.").arg(productName);
+ }
+ const QJsonArray filesArray = request.value(QLatin1String("files")).toArray();
+ for (const QJsonValue &v : filesArray)
+ data.filePaths << v.toString();
+ if (m_currentJob)
+ data.error = tr("Cannot update the list of source files while a job is running.");
+ if (!m_project.isValid())
+ data.error = tr("No valid project. You need to resolve first.");
+#ifndef QBS_ENABLE_PROJECT_FILE_UPDATES
+ data.error = ErrorInfo(tr("Project file updates are not enabled in this build of qbs."));
+#endif
+ return data;
+}
+
+void Session::insertProjectDataIfNecessary(QJsonObject &reply, ProjectDataMode dataMode,
+ const ProjectData &oldProjectData, bool includeTopLevelData)
+{
+ const bool sendProjectData = dataMode == ProjectDataMode::Always
+ || (dataMode == ProjectDataMode::OnlyIfChanged && m_projectData != oldProjectData);
+ if (!sendProjectData)
+ return;
+ QJsonObject projectData = m_projectData.toJson(m_moduleProperties);
+ if (includeTopLevelData) {
+ QJsonArray buildSystemFiles;
+ for (const QString &f : m_project.buildSystemFiles())
+ buildSystemFiles.push_back(f);
+ projectData.insert(StringConstants::buildDirectoryKey(), m_projectData.buildDirectory());
+ projectData.insert(QLatin1String("build-system-files"), buildSystemFiles);
+ const Project::BuildGraphInfo bgInfo = m_project.getBuildGraphInfo();
+ projectData.insert(QLatin1String("build-graph-file-path"), bgInfo.bgFilePath);
+ projectData.insert(QLatin1String("profile-data"),
+ QJsonObject::fromVariantMap(bgInfo.profileData));
+ projectData.insert(QLatin1String("overridden-properties"),
+ QJsonObject::fromVariantMap(bgInfo.overriddenProperties));
+ }
+ reply.insert(QLatin1String("project-data"), projectData);
+}
+
+void Session::setLogLevelFromRequest(const QJsonObject &request)
+{
+ const QString logLevelString = request.value(QLatin1String("log-level")).toString();
+ if (logLevelString.isEmpty())
+ return;
+ for (const LoggerLevel l : {LoggerError, LoggerWarning, LoggerInfo, LoggerDebug, LoggerTrace}) {
+ if (logLevelString == logLevelName(l)) {
+ m_logSink.setLogLevel(l);
+ return;
+ }
+ }
+}
+
+bool Session::checkNormalRequestPrerequisites(const char *replyType)
+{
+ if (m_currentJob) {
+ sendErrorReply(replyType, tr("Another job is still running."));
+ return false;
+ }
+ if (!m_project.isValid()) {
+ sendErrorReply(replyType, tr("No valid project. You need to resolve first."));
+ return false;
+ }
+ return true;
+}
+
+QStringList Session::modulePropertiesFromRequest(const QJsonObject &request)
+{
+ return fromJson<QStringList>(request.value(StringConstants::modulePropertiesKey()));
+}
+
+void Session::sendErrorReply(const char *replyType, const ErrorInfo &error)
+{
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String(replyType));
+ insertErrorInfoIfNecessary(reply, error);
+ sendPacket(reply);
+}
+
+void Session::sendErrorReply(const char *replyType, const QString &message)
+{
+ sendErrorReply(replyType, ErrorInfo(message));
+}
+
+void Session::insertErrorInfoIfNecessary(QJsonObject &reply, const ErrorInfo &error)
+{
+ if (error.hasError())
+ reply.insert(QLatin1String("error"), error.toJson());
+}
+
+void Session::quitSession()
+{
+ m_logSink.disconnect(this);
+ m_packetReader.disconnect(this);
+ if (m_currentJob) {
+ m_currentJob->disconnect(this);
+ connect(m_currentJob, &AbstractJob::finished, qApp, QCoreApplication::quit);
+ m_currentJob->cancel();
+ } else {
+ qApp->quit();
+ }
+}
+
+QJsonObject Session::FileUpdateData::createErrorReply(const char *type,
+ const QString &mainMessage) const
+{
+ QBS_ASSERT(error.hasError(), return QJsonObject());
+ ErrorInfo error(mainMessage);
+ for (const ErrorItem &ei : error.items())
+ error.append(ei);
+ QJsonObject reply;
+ reply.insert(StringConstants::type(), QLatin1String(type));
+ reply.insert(QLatin1String("error"), error.toJson());
+ reply.insert(QLatin1String("failed-files"), QJsonArray::fromStringList(filePaths));
+ return reply;
+}
+
+} // namespace Internal
+} // namespace qbs
+
+#include <session.moc>