aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/qbs/commandlinefrontend.cpp6
-rw-r--r--src/app/qbs/parser/commandlineparser.cpp1
-rw-r--r--src/app/qbs/parser/commandpool.cpp3
-rw-r--r--src/app/qbs/parser/commandtype.h2
-rw-r--r--src/app/qbs/parser/parsercommand.cpp23
-rw-r--r--src/app/qbs/parser/parsercommand.h14
-rw-r--r--src/app/qbs/qbs.pro8
-rw-r--r--src/app/qbs/qbs.qbs8
-rw-r--r--src/app/qbs/session.cpp751
-rw-r--r--src/app/qbs/session.h51
-rw-r--r--src/app/qbs/sessionpacket.cpp113
-rw-r--r--src/app/qbs/sessionpacket.h70
-rw-r--r--src/app/qbs/sessionpacketreader.cpp85
-rw-r--r--src/app/qbs/sessionpacketreader.h70
-rw-r--r--src/app/qbs/stdinreader.cpp146
-rw-r--r--src/app/qbs/stdinreader.h66
-rw-r--r--src/lib/corelib/api/project.cpp22
-rw-r--r--src/lib/corelib/api/project.h3
-rw-r--r--src/lib/corelib/api/projectdata.cpp156
-rw-r--r--src/lib/corelib/api/projectdata.h5
-rw-r--r--src/lib/corelib/corelib.qbs1
-rw-r--r--src/lib/corelib/tools/buildoptions.cpp55
-rw-r--r--src/lib/corelib/tools/buildoptions.h3
-rw-r--r--src/lib/corelib/tools/cleanoptions.cpp12
-rw-r--r--src/lib/corelib/tools/cleanoptions.h6
-rw-r--r--src/lib/corelib/tools/codelocation.cpp15
-rw-r--r--src/lib/corelib/tools/codelocation.h2
-rw-r--r--src/lib/corelib/tools/error.cpp24
-rw-r--r--src/lib/corelib/tools/error.h5
-rw-r--r--src/lib/corelib/tools/installoptions.cpp21
-rw-r--r--src/lib/corelib/tools/installoptions.h3
-rw-r--r--src/lib/corelib/tools/jsonhelper.h89
-rw-r--r--src/lib/corelib/tools/processresult.cpp30
-rw-r--r--src/lib/corelib/tools/processresult.h3
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.cpp47
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.h2
-rw-r--r--src/lib/corelib/tools/stringconstants.h11
-rw-r--r--src/lib/corelib/tools/tools.pri1
38 files changed, 1907 insertions, 26 deletions
diff --git a/src/app/qbs/commandlinefrontend.cpp b/src/app/qbs/commandlinefrontend.cpp
index 95c3c10bc..8be06f3af 100644
--- a/src/app/qbs/commandlinefrontend.cpp
+++ b/src/app/qbs/commandlinefrontend.cpp
@@ -40,6 +40,7 @@
#include "application.h"
#include "consoleprogressobserver.h"
+#include "session.h"
#include "status.h"
#include "parser/commandlineoption.h"
#include "../shared/logging/consolelogger.h"
@@ -129,6 +130,10 @@ void CommandLineFrontend::start()
throw ErrorInfo(error);
}
break;
+ case SessionCommandType: {
+ startSession();
+ return;
+ }
default:
break;
}
@@ -418,6 +423,7 @@ void CommandLineFrontend::handleProjectsResolved()
break;
case HelpCommandType:
case VersionCommandType:
+ case SessionCommandType:
Q_ASSERT_X(false, Q_FUNC_INFO, "Impossible.");
}
}
diff --git a/src/app/qbs/parser/commandlineparser.cpp b/src/app/qbs/parser/commandlineparser.cpp
index 3c25c51e2..052f6b92f 100644
--- a/src/app/qbs/parser/commandlineparser.cpp
+++ b/src/app/qbs/parser/commandlineparser.cpp
@@ -386,6 +386,7 @@ QList<Command *> CommandLineParser::CommandLineParserPrivate::allCommands() cons
commandPool.getCommand(DumpNodesTreeCommandType),
commandPool.getCommand(ListProductsCommandType),
commandPool.getCommand(VersionCommandType),
+ commandPool.getCommand(SessionCommandType),
commandPool.getCommand(HelpCommandType)};
}
diff --git a/src/app/qbs/parser/commandpool.cpp b/src/app/qbs/parser/commandpool.cpp
index a49608c56..1362a563c 100644
--- a/src/app/qbs/parser/commandpool.cpp
+++ b/src/app/qbs/parser/commandpool.cpp
@@ -95,6 +95,9 @@ qbs::Command *CommandPool::getCommand(CommandType type) const
case VersionCommandType:
command = new VersionCommand(m_optionPool);
break;
+ case SessionCommandType:
+ command = new SessionCommand(m_optionPool);
+ break;
}
}
return command;
diff --git a/src/app/qbs/parser/commandtype.h b/src/app/qbs/parser/commandtype.h
index a8c618933..7d70356e7 100644
--- a/src/app/qbs/parser/commandtype.h
+++ b/src/app/qbs/parser/commandtype.h
@@ -45,7 +45,7 @@ enum CommandType {
ResolveCommandType, BuildCommandType, CleanCommandType, RunCommandType, ShellCommandType,
StatusCommandType, UpdateTimestampsCommandType, DumpNodesTreeCommandType,
InstallCommandType, HelpCommandType, GenerateCommandType, ListProductsCommandType,
- VersionCommandType,
+ VersionCommandType, SessionCommandType,
};
} // namespace qbs
diff --git a/src/app/qbs/parser/parsercommand.cpp b/src/app/qbs/parser/parsercommand.cpp
index 9485b0878..c7185a725 100644
--- a/src/app/qbs/parser/parsercommand.cpp
+++ b/src/app/qbs/parser/parsercommand.cpp
@@ -593,4 +593,27 @@ void VersionCommand::parseNext(QStringList &input)
throwError(Tr::tr("This command takes no arguments."));
}
+QString SessionCommand::shortDescription() const
+{
+ return Tr::tr("Starts a session for an IDE.");
+}
+
+QString SessionCommand::longDescription() const
+{
+ QString description = Tr::tr("qbs %1\n").arg(representation());
+ return description += Tr::tr("Communicates on stdin and stdout via a JSON-based API.\n"
+ "Intended for use with other tools, such as IDEs.\n");
+}
+
+QString SessionCommand::representation() const
+{
+ return QLatin1String("session");
+}
+
+void SessionCommand::parseNext(QStringList &input)
+{
+ QBS_CHECK(!input.empty());
+ throwError(Tr::tr("This command takes no arguments."));
+}
+
} // namespace qbs
diff --git a/src/app/qbs/parser/parsercommand.h b/src/app/qbs/parser/parsercommand.h
index 649563ba1..8998d38e6 100644
--- a/src/app/qbs/parser/parsercommand.h
+++ b/src/app/qbs/parser/parsercommand.h
@@ -261,6 +261,20 @@ private:
void parseNext(QStringList &input) override;
};
+class SessionCommand : public Command
+{
+public:
+ SessionCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {}
+
+private:
+ CommandType type() const override { return SessionCommandType; }
+ QString shortDescription() const override;
+ QString longDescription() const override;
+ QString representation() const override;
+ QList<CommandLineOption::Type> supportedOptions() const override { return {}; }
+ void parseNext(QStringList &input) override;
+};
+
} // namespace qbs
#endif // QBS_PARSER_COMMAND_H
diff --git a/src/app/qbs/qbs.pro b/src/app/qbs/qbs.pro
index ac9d6f0ca..e9f0061c6 100644
--- a/src/app/qbs/qbs.pro
+++ b/src/app/qbs/qbs.pro
@@ -6,6 +6,10 @@ TARGET = qbs
SOURCES += main.cpp \
ctrlchandler.cpp \
application.cpp \
+ session.cpp \
+ sessionpacket.cpp \
+ sessionpacketreader.cpp \
+ stdinreader.cpp \
status.cpp \
consoleprogressobserver.cpp \
commandlinefrontend.cpp \
@@ -14,6 +18,10 @@ SOURCES += main.cpp \
HEADERS += \
ctrlchandler.h \
application.h \
+ session.h \
+ sessionpacket.h \
+ sessionpacketreader.h \
+ stdinreader.h \
status.h \
consoleprogressobserver.h \
commandlinefrontend.h \
diff --git a/src/app/qbs/qbs.qbs b/src/app/qbs/qbs.qbs
index f22fe5de5..91357445e 100644
--- a/src/app/qbs/qbs.qbs
+++ b/src/app/qbs/qbs.qbs
@@ -23,8 +23,16 @@ QbsApp {
"main.cpp",
"qbstool.cpp",
"qbstool.h",
+ "session.cpp",
+ "session.h",
+ "sessionpacket.cpp",
+ "sessionpacket.h",
+ "sessionpacketreader.cpp",
+ "sessionpacketreader.h",
"status.cpp",
"status.h",
+ "stdinreader.cpp",
+ "stdinreader.h",
]
Group {
name: "parser"
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>
diff --git a/src/app/qbs/session.h b/src/app/qbs/session.h
new file mode 100644
index 000000000..ebbc93b1f
--- /dev/null
+++ b/src/app/qbs/session.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QBS_SESSION_H
+#define QBS_SESSION_H
+
+namespace qbs {
+namespace Internal {
+
+void startSession();
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // Include guard
diff --git a/src/app/qbs/sessionpacket.cpp b/src/app/qbs/sessionpacket.cpp
new file mode 100644
index 000000000..ce9fdaf76
--- /dev/null
+++ b/src/app/qbs/sessionpacket.cpp
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** 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 "sessionpacket.h"
+
+#include <tools/qbsassert.h>
+#include <tools/stringconstants.h>
+#include <tools/version.h>
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonvalue.h>
+#include <QtCore/qstring.h>
+
+namespace qbs {
+namespace Internal {
+
+const QByteArray packetStart = "qbsmsg:";
+
+SessionPacket::Status SessionPacket::parseInput(QByteArray &input)
+{
+ //qDebug() << m_expectedPayloadLength << m_payload << 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();
+ QBS_ASSERT(bytesToAdd >= 0, return Status::Invalid);
+ m_payload += input.left(bytesToAdd);
+ input.remove(0, bytesToAdd);
+ return isComplete() ? Status::Complete : Status::Incomplete;
+}
+
+QJsonObject SessionPacket::retrievePacket()
+{
+ QBS_ASSERT(isComplete(), return QJsonObject());
+ const auto packet = QJsonDocument::fromJson(QByteArray::fromBase64(m_payload)).object();
+ m_payload.clear();
+ m_expectedPayloadLength = -1;
+ return packet;
+}
+
+QByteArray SessionPacket::createPacket(const QJsonObject &packet)
+{
+ const QByteArray jsonData = QJsonDocument(packet).toJson(QJsonDocument::Compact).toBase64();
+ return QByteArray(packetStart).append(QByteArray::number(jsonData.length())).append('\n')
+ .append(jsonData);
+}
+
+QJsonObject SessionPacket::helloMessage()
+{
+ return QJsonObject{
+ {StringConstants::type(), QLatin1String("hello")},
+ {QLatin1String("api-level"), 1},
+ {QLatin1String("api-compat-level"), 1}
+ };
+}
+
+bool SessionPacket::isComplete() const
+{
+ return m_payload.length() == m_expectedPayloadLength;
+}
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/app/qbs/sessionpacket.h b/src/app/qbs/sessionpacket.h
new file mode 100644
index 000000000..d919ff340
--- /dev/null
+++ b/src/app/qbs/sessionpacket.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QBS_SESSIONPACKET_H
+#define QBS_SESSIONPACKET_H
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qjsonobject.h>
+
+namespace qbs {
+namespace Internal {
+
+class SessionPacket
+{
+public:
+ enum class Status { Incomplete, Complete, Invalid };
+ Status parseInput(QByteArray &input);
+
+ QJsonObject retrievePacket();
+
+ static QByteArray createPacket(const QJsonObject &packet);
+ static QJsonObject helloMessage();
+
+private:
+ bool isComplete() const;
+
+ QByteArray m_payload;
+ int m_expectedPayloadLength = -1;
+};
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // Include guard
diff --git a/src/app/qbs/sessionpacketreader.cpp b/src/app/qbs/sessionpacketreader.cpp
new file mode 100644
index 000000000..fe4b73f69
--- /dev/null
+++ b/src/app/qbs/sessionpacketreader.cpp
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** 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 "sessionpacketreader.h"
+
+#include "sessionpacket.h"
+#include "stdinreader.h"
+
+namespace qbs {
+namespace Internal {
+
+class SessionPacketReader::Private
+{
+public:
+ QByteArray incomingData;
+ SessionPacket currentPacket;
+};
+
+SessionPacketReader::SessionPacketReader(QObject *parent) : QObject(parent), d(new Private) { }
+
+SessionPacketReader::~SessionPacketReader()
+{
+ delete d;
+}
+
+void SessionPacketReader::start()
+{
+ StdinReader * const stdinReader = StdinReader::create(this);
+ connect(stdinReader, &StdinReader::errorOccurred, this, &SessionPacketReader::errorOccurred);
+ connect(stdinReader, &StdinReader::dataAvailable, this, [this](const QByteArray &data) {
+ d->incomingData += data;
+ while (!d->incomingData.isEmpty()) {
+ switch (d->currentPacket.parseInput(d->incomingData)) {
+ case SessionPacket::Status::Invalid:
+ emit errorOccurred(tr("Received invalid input."));
+ return;
+ case SessionPacket::Status::Complete:
+ emit packetReceived(d->currentPacket.retrievePacket());
+ break;
+ case SessionPacket::Status::Incomplete:
+ return;
+ }
+ }
+ });
+ stdinReader->start();
+}
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/app/qbs/sessionpacketreader.h b/src/app/qbs/sessionpacketreader.h
new file mode 100644
index 000000000..87d70cf39
--- /dev/null
+++ b/src/app/qbs/sessionpacketreader.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QBS_SESSIONPACKETREADER_H
+#define QBS_SESSIONPACKETREADER_H
+
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qobject.h>
+
+namespace qbs {
+namespace Internal {
+
+class SessionPacketReader : public QObject
+{
+ Q_OBJECT
+public:
+ explicit SessionPacketReader(QObject *parent = nullptr);
+ ~SessionPacketReader();
+
+ void start();
+
+signals:
+ void packetReceived(const QJsonObject &packet);
+ void errorOccurred(const QString &msg);
+
+private:
+ class Private;
+ Private * const d;
+};
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // Include guard
diff --git a/src/app/qbs/stdinreader.cpp b/src/app/qbs/stdinreader.cpp
new file mode 100644
index 000000000..4f784505d
--- /dev/null
+++ b/src/app/qbs/stdinreader.cpp
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** 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 "stdinreader.h"
+
+#include <tools/hostosinfo.h>
+
+#include <QtCore/qfile.h>
+#include <QtCore/qsocketnotifier.h>
+
+#include <cerrno>
+#include <cstring>
+
+#ifdef Q_OS_WIN32
+#include <qt_windows.h>
+#include <QtCore/qtimer.h>
+#else
+#include <fcntl.h>
+#endif
+
+namespace qbs {
+namespace Internal {
+
+class UnixStdinReader : public StdinReader
+{
+public:
+ UnixStdinReader(QObject *parent) : StdinReader(parent), m_notifier(0, QSocketNotifier::Read) {}
+
+private:
+ void start() override
+ {
+ if (!m_stdIn.open(stdin, QIODevice::ReadOnly)) {
+ emit errorOccurred(tr("Cannot read from standard input."));
+ return;
+ }
+ const auto emitError = [this] {
+ emit errorOccurred(tr("Failed to make standard input non-blocking: %1")
+ .arg(QLatin1String(std::strerror(errno))));
+ };
+#ifdef Q_OS_UNIX
+ const int flags = fcntl(0, F_GETFL, 0);
+ if (flags == -1) {
+ emitError();
+ return;
+ }
+ if (fcntl(0, F_SETFL, flags | O_NONBLOCK)) {
+ emitError();
+ return;
+ }
+#endif
+ connect(&m_notifier, &QSocketNotifier::activated, this, [this] {
+ emit dataAvailable(m_stdIn.readAll());
+ });
+ }
+
+ QFile m_stdIn;
+ QSocketNotifier m_notifier;
+};
+
+class WindowsStdinReader : public StdinReader
+{
+public:
+ WindowsStdinReader(QObject *parent) : StdinReader(parent) {}
+
+private:
+ void start() override
+ {
+#ifdef Q_OS_WIN32
+ m_stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
+ if (!m_stdinHandle) {
+ emit errorOccurred(tr("Failed to create handle for standard input."));
+ return;
+ }
+
+ // A timer seems slightly less awful than to block in a thread
+ // (how would we abort that one?), but ideally we'd like
+ // to have a signal-based approach like in the Unix variant.
+ const auto timer = new QTimer(this);
+ connect(timer, &QTimer::timeout, this, [this] {
+ char buf[1024];
+ DWORD bytesAvail;
+ PeekNamedPipe(m_stdinHandle, nullptr, 0, nullptr, &bytesAvail, nullptr);
+ while (bytesAvail > 0) {
+ DWORD bytesRead;
+ ReadFile(m_stdinHandle, buf, std::min<DWORD>(bytesAvail, sizeof buf), &bytesRead,
+ nullptr);
+ emit dataAvailable(QByteArray(buf, bytesRead));
+ bytesAvail -= bytesRead;
+ }
+ });
+ timer->start(10);
+#endif
+ }
+
+#ifdef Q_OS_WIN32
+ HANDLE m_stdinHandle;
+#endif
+};
+
+StdinReader *StdinReader::create(QObject *parent)
+{
+ if (HostOsInfo::isWindowsHost())
+ return new WindowsStdinReader(parent);
+ return new UnixStdinReader(parent);
+}
+
+StdinReader::StdinReader(QObject *parent) : QObject(parent) { }
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/app/qbs/stdinreader.h b/src/app/qbs/stdinreader.h
new file mode 100644
index 000000000..b3737e5ae
--- /dev/null
+++ b/src/app/qbs/stdinreader.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QBS_STDINREADER_H
+#define QBS_STDINREADER_H
+
+#include <QtCore/qobject.h>
+
+namespace qbs {
+namespace Internal {
+
+class StdinReader : public QObject
+{
+ Q_OBJECT
+public:
+ static StdinReader *create(QObject *parent);
+ virtual void start() = 0;
+
+signals:
+ void errorOccurred(const QString &error);
+ void dataAvailable(const QByteArray &data);
+
+protected:
+ explicit StdinReader(QObject *parent);
+};
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // Include guard
diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp
index 3ffd6b2e9..d0fe7296e 100644
--- a/src/lib/corelib/api/project.cpp
+++ b/src/lib/corelib/api/project.cpp
@@ -726,7 +726,7 @@ void ProjectPrivate::updateExternalCodeLocations(const ProjectData &project,
void ProjectPrivate::prepareChangeToProject()
{
if (internalProject->locked)
- throw ErrorInfo(Tr::tr("A job is currently in process."));
+ throw ErrorInfo(Tr::tr("A job is currently in progress."));
if (!m_projectData.isValid())
retrieveProjectData(m_projectData, internalProject);
}
@@ -766,7 +766,7 @@ RuleCommandList ProjectPrivate::ruleCommands(const ProductData &product,
const QString &inputFilePath, const QString &outputFileTag)
{
if (internalProject->locked)
- throw ErrorInfo(Tr::tr("A job is currently in process."));
+ throw ErrorInfo(Tr::tr("A job is currently in progress."));
const ResolvedProductConstPtr resolvedProduct = internalProduct(product);
if (!resolvedProduct)
throw ErrorInfo(Tr::tr("No such product '%1'.").arg(product.name()));
@@ -896,7 +896,7 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData,
}
for (const ResolvedProductPtr &resolvedDependentProduct
: qAsConst(resolvedProduct->dependencies)) {
- product.d->dependencies << resolvedDependentProduct->name;
+ product.d->dependencies << resolvedDependentProduct->name; // FIXME: Shouldn't this be a unique name?
}
std::sort(product.d->type.begin(), product.d->type.end());
std::sort(product.d->groups.begin(), product.d->groups.end());
@@ -1252,6 +1252,22 @@ Project::BuildGraphInfo Project::getBuildGraphInfo(const QString &bgFilePath,
return info;
}
+Project::BuildGraphInfo Project::getBuildGraphInfo() const
+{
+ QBS_ASSERT(isValid(), return {});
+ BuildGraphInfo info;
+ try {
+ if (d->internalProject->locked)
+ throw ErrorInfo(Tr::tr("A job is currently in progress."));
+ info.bgFilePath = d->internalProject->buildGraphFilePath();
+ info.overriddenProperties = d->internalProject->overriddenValues;
+ info.profileData = d->internalProject->profileConfigs;
+ } catch (const ErrorInfo &e) {
+ info.error = e;
+ }
+ return info;
+}
+
#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES
/*!
* \brief Adds a new empty group to the given product.
diff --git a/src/lib/corelib/api/project.h b/src/lib/corelib/api/project.h
index 05f08deee..9000d6548 100644
--- a/src/lib/corelib/api/project.h
+++ b/src/lib/corelib/api/project.h
@@ -155,6 +155,9 @@ public:
static BuildGraphInfo getBuildGraphInfo(const QString &bgFilePath,
const QStringList &requestedProperties);
+ // Use with loaded project. Does not set requestedProperties.
+ BuildGraphInfo getBuildGraphInfo() const;
+
#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES
ErrorInfo addGroup(const ProductData &product, const QString &groupName);
diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp
index 56700b8be..7c64bf6ff 100644
--- a/src/lib/corelib/api/projectdata.cpp
+++ b/src/lib/corelib/api/projectdata.cpp
@@ -45,21 +45,57 @@
#include <tools/fileinfo.h>
#include <tools/jsliterals.h>
#include <tools/qbsassert.h>
+#include <tools/stringconstants.h>
#include <tools/qttools.h>
#include <tools/stringconstants.h>
#include <QtCore/qdir.h>
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsonobject.h>
#include <algorithm>
namespace qbs {
+using namespace Internal;
+
+template<typename T> static QJsonArray toJsonArray(const QList<T> &list,
+ const QStringList &moduleProperties)
+{
+ QJsonArray jsonArray;
+ std::transform(list.begin(), list.end(), std::back_inserter(jsonArray),
+ [&moduleProperties](const T &v) { return v.toJson(moduleProperties);});
+ return jsonArray;
+}
+
+static QVariant getModuleProperty(const PropertyMap &properties, const QString &fullPropertyName)
+{
+ const int lastDotIndex = fullPropertyName.lastIndexOf(QLatin1Char('.'));
+ if (lastDotIndex == -1)
+ return QVariant();
+ return properties.getModuleProperty(fullPropertyName.left(lastDotIndex),
+ fullPropertyName.mid(lastDotIndex + 1));
+}
+
+static void addModuleProperties(QJsonObject &obj, const PropertyMap &properties,
+ const QStringList &propertyNames)
+{
+ QJsonObject propertyValues;
+ for (const QString &prop : propertyNames) {
+ const QVariant v = getModuleProperty(properties, prop);
+ if (v.isValid())
+ propertyValues.insert(prop, QJsonValue::fromVariant(v));
+ }
+ if (!propertyValues.isEmpty())
+ obj.insert(StringConstants::modulePropertiesKey(), propertyValues);
+}
+
/*!
* \class GroupData
* \brief The \c GroupData class corresponds to the Group item in a qbs source file.
*/
-GroupData::GroupData() : d(new Internal::GroupDataPrivate)
+GroupData::GroupData() : d(new GroupDataPrivate)
{
}
@@ -81,6 +117,22 @@ bool GroupData::isValid() const
return d->isValid;
}
+QJsonObject GroupData::toJson(const QStringList &moduleProperties) const
+{
+ QJsonObject obj;
+ if (isValid()) {
+ obj.insert(StringConstants::locationKey(), location().toJson());
+ obj.insert(StringConstants::nameProperty(), name());
+ obj.insert(StringConstants::prefixProperty(), prefix());
+ obj.insert(StringConstants::isEnabledKey(), isEnabled());
+ obj.insert(QStringLiteral("source-artifacts"), toJsonArray(sourceArtifacts(), {}));
+ obj.insert(QStringLiteral("source-artifacts-from-wildcards"),
+ toJsonArray(sourceArtifactsFromWildcards(), {}));
+ addModuleProperties(obj, properties(), moduleProperties);
+ }
+ return obj;
+}
+
/*!
* \brief The location at which the group is defined in the respective source file.
*/
@@ -204,7 +256,7 @@ bool operator<(const GroupData &lhs, const GroupData &rhs)
* or it gets generated during the build process.
*/
-ArtifactData::ArtifactData() : d(new Internal::ArtifactDataPrivate)
+ArtifactData::ArtifactData() : d(new ArtifactDataPrivate)
{
}
@@ -226,6 +278,21 @@ bool ArtifactData::isValid() const
return d->isValid;
}
+QJsonObject ArtifactData::toJson(const QStringList &moduleProperties) const
+{
+ QJsonObject obj;
+ if (isValid()) {
+ obj.insert(StringConstants::filePathKey(), filePath());
+ obj.insert(QStringLiteral("file-tags"), QJsonArray::fromStringList(fileTags()));
+ obj.insert(QStringLiteral("is-generated"), isGenerated());
+ obj.insert(QStringLiteral("is-executable"), isExecutable());
+ obj.insert(QStringLiteral("is-target"), isTargetArtifact());
+ obj.insert(QStringLiteral("install-data"), installData().toJson());
+ addModuleProperties(obj, properties(), moduleProperties);
+ }
+ return obj;
+}
+
/*!
* \brief The full path of this file.
*/
@@ -256,8 +323,8 @@ bool ArtifactData::isExecutable() const
{
const bool isBundle = d->properties.getModuleProperty(
QStringLiteral("bundle"), QStringLiteral("isBundle")).toBool();
- return Internal::isRunnableArtifact(
- Internal::FileTags::fromStringList(d->fileTags), isBundle);
+ return isRunnableArtifact(
+ FileTags::fromStringList(d->fileTags), isBundle);
}
/*!
@@ -309,7 +376,7 @@ bool operator<(const ArtifactData &ta1, const ArtifactData &ta2)
* \brief The \c InstallData class provides the installation-related data of an artifact.
*/
-InstallData::InstallData() : d(new Internal::InstallDataPrivate)
+InstallData::InstallData() : d(new InstallDataPrivate)
{
}
@@ -331,6 +398,19 @@ bool InstallData::isValid() const
return d->isValid;
}
+QJsonObject InstallData::toJson() const
+{
+ QJsonObject obj;
+ if (isValid()) {
+ obj.insert(QStringLiteral("is-installable"), isInstallable());
+ if (isInstallable()) {
+ obj.insert(QStringLiteral("install-file-path"), installFilePath());
+ obj.insert(QStringLiteral("install-root"), installRoot());
+ }
+ }
+ return obj;
+}
+
/*!
\brief Returns true if and only if \c{qbs.install} is \c true for the artifact.
*/
@@ -348,7 +428,7 @@ bool InstallData::isInstallable() const
QString InstallData::installDir() const
{
QBS_ASSERT(isValid(), return {});
- return Internal::FileInfo::path(installFilePath());
+ return FileInfo::path(installFilePath());
}
/*!
@@ -392,7 +472,7 @@ QString InstallData::localInstallFilePath() const
* \brief The \c ProductData class corresponds to the Product item in a qbs source file.
*/
-ProductData::ProductData() : d(new Internal::ProductDataPrivate)
+ProductData::ProductData() : d(new ProductDataPrivate)
{
}
@@ -414,6 +494,39 @@ bool ProductData::isValid() const
return d->isValid;
}
+QJsonObject ProductData::toJson(const QStringList &propertyNames) const
+{
+ QJsonObject obj;
+ if (!isValid())
+ return obj;
+ obj.insert(StringConstants::typeProperty(), QJsonArray::fromStringList(type()));
+ obj.insert(StringConstants::dependenciesProperty(),
+ QJsonArray::fromStringList(dependencies()));
+ obj.insert(StringConstants::nameProperty(), name());
+ obj.insert(StringConstants::fullDisplayNameKey(), fullDisplayName());
+ obj.insert(QStringLiteral("target-name"), targetName());
+ obj.insert(StringConstants::versionProperty(), version());
+ obj.insert(QStringLiteral("multiplex-configuration-id"), multiplexConfigurationId());
+ obj.insert(StringConstants::locationKey(), location().toJson());
+ obj.insert(StringConstants::buildDirectoryKey(), buildDirectory());
+ obj.insert(QStringLiteral("generated-artifacts"), toJsonArray(generatedArtifacts(),
+ propertyNames));
+ obj.insert(QStringLiteral("target-executable"), targetExecutable());
+ QJsonArray groupArray;
+ for (const GroupData &g : groups()) {
+ const QStringList groupPropNames = g.properties() == moduleProperties()
+ ? QStringList() : propertyNames;
+ groupArray << g.toJson(groupPropNames);
+ }
+ obj.insert(QStringLiteral("groups"), groupArray);
+ obj.insert(QStringLiteral("properties"), QJsonObject::fromVariantMap(properties()));
+ obj.insert(StringConstants::isEnabledKey(), isEnabled());
+ obj.insert(QStringLiteral("is-runnable"), isRunnable());
+ obj.insert(QStringLiteral("is-multiplexed"), isMultiplexed());
+ addModuleProperties(obj, moduleProperties(), propertyNames);
+ return obj;
+}
+
/*!
* \brief The product type, which is the list of file tags matching the product's target artifacts.
*/
@@ -445,7 +558,7 @@ QString ProductData::name() const
*/
QString ProductData::fullDisplayName() const
{
- return Internal::ResolvedProduct::fullDisplayName(name(), multiplexConfigurationId());
+ return ResolvedProduct::fullDisplayName(name(), multiplexConfigurationId());
}
/*!
@@ -470,8 +583,8 @@ QString ProductData::version() const
QString ProductData::profile() const
{
return d->moduleProperties.getModuleProperty(
- Internal::StringConstants::qbsModule(),
- Internal::StringConstants::profileProperty()).toString();
+ StringConstants::qbsModule(),
+ StringConstants::profileProperty()).toString();
}
QString ProductData::multiplexConfigurationId() const
@@ -661,7 +774,7 @@ bool operator<(const ProductData &lhs, const ProductData &rhs)
* \brief The products in this project.
*/
-ProjectData::ProjectData() : d(new Internal::ProjectDataPrivate)
+ProjectData::ProjectData() : d(new ProjectDataPrivate)
{
}
@@ -683,6 +796,19 @@ bool ProjectData::isValid() const
return d->isValid;
}
+QJsonObject ProjectData::toJson(const QStringList &moduleProperties) const
+{
+ QJsonObject obj;
+ if (!isValid())
+ return obj;
+ obj.insert(StringConstants::nameProperty(), name());
+ obj.insert(StringConstants::locationKey(), location().toJson());
+ obj.insert(StringConstants::isEnabledKey(), isEnabled());
+ obj.insert(StringConstants::productsKey(), toJsonArray(products(), moduleProperties));
+ obj.insert(QStringLiteral("sub-projects"), toJsonArray(subProjects(), moduleProperties));
+ return obj;
+}
+
/*!
* \brief The name of this project.
*/
@@ -788,14 +914,14 @@ bool operator<(const ProjectData &lhs, const ProjectData &rhs)
*/
PropertyMap::PropertyMap()
- : d(std::make_unique<Internal::PropertyMapPrivate>())
+ : d(std::make_unique<PropertyMapPrivate>())
{
- static Internal::PropertyMapPtr defaultInternalMap = Internal::PropertyMapInternal::create();
+ static PropertyMapPtr defaultInternalMap = PropertyMapInternal::create();
d->m_map = defaultInternalMap;
}
PropertyMap::PropertyMap(const PropertyMap &other)
- : d(std::make_unique<Internal::PropertyMapPrivate>(*other.d))
+ : d(std::make_unique<PropertyMapPrivate>(*other.d))
{
}
@@ -806,7 +932,7 @@ PropertyMap::~PropertyMap() = default;
PropertyMap &PropertyMap::operator =(const PropertyMap &other)
{
if (this != &other)
- d = std::make_unique<Internal::PropertyMapPrivate>(*other.d);
+ d = std::make_unique<PropertyMapPrivate>(*other.d);
return *this;
}
diff --git a/src/lib/corelib/api/projectdata.h b/src/lib/corelib/api/projectdata.h
index 3bd1c4540..a285f8570 100644
--- a/src/lib/corelib/api/projectdata.h
+++ b/src/lib/corelib/api/projectdata.h
@@ -110,6 +110,7 @@ public:
~ArtifactData();
bool isValid() const;
+ QJsonObject toJson(const QStringList &moduleProperties = {}) const;
QString filePath() const;
QStringList fileTags() const;
@@ -135,6 +136,7 @@ public:
~InstallData();
bool isValid() const;
+ QJsonObject toJson() const;
bool isInstallable() const;
QString installDir() const;
@@ -162,6 +164,7 @@ public:
~GroupData();
bool isValid() const;
+ QJsonObject toJson(const QStringList &moduleProperties = {}) const;
CodeLocation location() const;
QString name() const;
@@ -193,6 +196,7 @@ public:
~ProductData();
bool isValid() const;
+ QJsonObject toJson(const QStringList &propertyNames = {}) const;
QStringList type() const;
QStringList dependencies() const;
@@ -235,6 +239,7 @@ public:
~ProjectData();
bool isValid() const;
+ QJsonObject toJson(const QStringList &moduleProperties = {}) const;
QString name() const;
CodeLocation location() const;
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 92d7eb052..2f0ced926 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -418,6 +418,7 @@ QbsLibrary {
"joblimits.cpp",
"jsliterals.cpp",
"jsliterals.h",
+ "jsonhelper.h",
"installoptions.cpp",
"launcherinterface.cpp",
"launcherinterface.h",
diff --git a/src/lib/corelib/tools/buildoptions.cpp b/src/lib/corelib/tools/buildoptions.cpp
index 5507e0842..75417ab0b 100644
--- a/src/lib/corelib/tools/buildoptions.cpp
+++ b/src/lib/corelib/tools/buildoptions.cpp
@@ -38,6 +38,9 @@
****************************************************************************/
#include "buildoptions.h"
+#include "jsonhelper.h"
+
+#include <QtCore/qjsonobject.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qthread.h>
@@ -413,4 +416,56 @@ bool operator==(const BuildOptions &bo1, const BuildOptions &bo2)
&& bo1.removeExistingInstallation() == bo2.removeExistingInstallation();
}
+namespace Internal {
+template<> JobLimits fromJson(const QJsonValue &limitsData)
+{
+ JobLimits limits;
+ const QJsonArray &limitsArray = limitsData.toArray();
+ for (const QJsonValue &v : limitsArray) {
+ const QJsonObject limitData = v.toObject();
+ QString pool;
+ int limit = 0;
+ setValueFromJson(pool, limitData, "pool");
+ setValueFromJson(limit, limitData, "limit");
+ if (!pool.isEmpty() && limit > 0)
+ limits.setJobLimit(pool, limit);
+ }
+ return limits;
+}
+
+template<> CommandEchoMode fromJson(const QJsonValue &modeData)
+{
+ const QString modeString = modeData.toString();
+ if (modeString == QLatin1String("silent"))
+ return CommandEchoModeSilent;
+ if (modeString == QLatin1String("command-line"))
+ return CommandEchoModeCommandLine;
+ if (modeString == QLatin1String("command-line-with-environment"))
+ return CommandEchoModeCommandLineWithEnvironment;
+ return CommandEchoModeSummary;
+}
+} // namespace Internal
+
+qbs::BuildOptions qbs::BuildOptions::fromJson(const QJsonObject &data)
+{
+ using namespace Internal;
+ BuildOptions opt;
+ setValueFromJson(opt.d->changedFiles, data, "changed-files");
+ setValueFromJson(opt.d->filesToConsider, data, "files-to-consider");
+ setValueFromJson(opt.d->activeFileTags, data, "active-file-tags");
+ setValueFromJson(opt.d->jobLimits, data, "job-limits");
+ setValueFromJson(opt.d->maxJobCount, data, "max-job-count");
+ setValueFromJson(opt.d->dryRun, data, "dry-run");
+ setValueFromJson(opt.d->keepGoing, data, "keep-going");
+ setValueFromJson(opt.d->forceTimestampCheck, data, "check-timestamps");
+ setValueFromJson(opt.d->forceOutputCheck, data, "check-outputs");
+ setValueFromJson(opt.d->logElapsedTime, data, "log-time");
+ setValueFromJson(opt.d->echoMode, data, "command-echo-mode");
+ setValueFromJson(opt.d->install, data, "install");
+ setValueFromJson(opt.d->removeExistingInstallation, data, "clean-install-root");
+ setValueFromJson(opt.d->onlyExecuteRules, data, "only-execute-rules");
+ setValueFromJson(opt.d->jobLimitsFromProjectTakePrecedence, data, "enforce-project-job-limits");
+ return opt;
+}
+
} // namespace qbs
diff --git a/src/lib/corelib/tools/buildoptions.h b/src/lib/corelib/tools/buildoptions.h
index cea89d0ea..bd0fb22cb 100644
--- a/src/lib/corelib/tools/buildoptions.h
+++ b/src/lib/corelib/tools/buildoptions.h
@@ -47,6 +47,7 @@
#include <QtCore/qshareddata.h>
QT_BEGIN_NAMESPACE
+class QJsonObject;
class QStringList;
QT_END_NAMESPACE
@@ -61,6 +62,8 @@ public:
BuildOptions &operator=(const BuildOptions &other);
~BuildOptions();
+ static BuildOptions fromJson(const QJsonObject &data);
+
QStringList filesToConsider() const;
void setFilesToConsider(const QStringList &files);
diff --git a/src/lib/corelib/tools/cleanoptions.cpp b/src/lib/corelib/tools/cleanoptions.cpp
index 4fbe77b5d..b888fb1e8 100644
--- a/src/lib/corelib/tools/cleanoptions.cpp
+++ b/src/lib/corelib/tools/cleanoptions.cpp
@@ -38,6 +38,8 @@
****************************************************************************/
#include "cleanoptions.h"
+#include "jsonhelper.h"
+
#include <QtCore/qshareddata.h>
namespace qbs {
@@ -151,4 +153,14 @@ void CleanOptions::setLogElapsedTime(bool log)
d->logElapsedTime = log;
}
+qbs::CleanOptions qbs::CleanOptions::fromJson(const QJsonObject &data)
+{
+ CleanOptions opt;
+ using namespace Internal;
+ setValueFromJson(opt.d->dryRun, data, "dry-run");
+ setValueFromJson(opt.d->keepGoing, data, "keep-going");
+ setValueFromJson(opt.d->logElapsedTime, data, "log-time");
+ return opt;
+}
+
} // namespace qbs
diff --git a/src/lib/corelib/tools/cleanoptions.h b/src/lib/corelib/tools/cleanoptions.h
index 3f67cf5a5..7827697bb 100644
--- a/src/lib/corelib/tools/cleanoptions.h
+++ b/src/lib/corelib/tools/cleanoptions.h
@@ -43,6 +43,10 @@
#include <QtCore/qshareddata.h>
+QT_BEGIN_NAMESPACE
+class QJsonObject;
+QT_END_NAMESPACE
+
namespace qbs {
namespace Internal { class CleanOptionsPrivate; }
@@ -56,6 +60,8 @@ public:
CleanOptions &operator=(CleanOptions &&other) Q_DECL_NOEXCEPT;
~CleanOptions();
+ static CleanOptions fromJson(const QJsonObject &data);
+
bool dryRun() const;
void setDryRun(bool dryRun);
diff --git a/src/lib/corelib/tools/codelocation.cpp b/src/lib/corelib/tools/codelocation.cpp
index 2c6ade3b0..5eff378e1 100644
--- a/src/lib/corelib/tools/codelocation.cpp
+++ b/src/lib/corelib/tools/codelocation.cpp
@@ -41,9 +41,12 @@
#include <tools/fileinfo.h>
#include <tools/persistence.h>
#include <tools/qbsassert.h>
+#include <tools/stringconstants.h>
#include <QtCore/qdatastream.h>
#include <QtCore/qdir.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qjsonvalue.h>
#include <QtCore/qregexp.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qstring.h>
@@ -134,6 +137,18 @@ QString CodeLocation::toString() const
return str;
}
+QJsonObject CodeLocation::toJson() const
+{
+ QJsonObject obj;
+ if (!filePath().isEmpty())
+ obj.insert(Internal::StringConstants::filePathKey(), filePath());
+ if (line() != -1)
+ obj.insert(QStringLiteral("line"), line());
+ if (column() != -1)
+ obj.insert(QStringLiteral("column"), column());
+ return obj;
+}
+
void CodeLocation::load(Internal::PersistentPool &pool)
{
const bool isValid = pool.load<bool>();
diff --git a/src/lib/corelib/tools/codelocation.h b/src/lib/corelib/tools/codelocation.h
index 3dc8f26b1..3e84ce2d1 100644
--- a/src/lib/corelib/tools/codelocation.h
+++ b/src/lib/corelib/tools/codelocation.h
@@ -47,6 +47,7 @@
QT_BEGIN_NAMESPACE
class QDataStream;
+class QJsonObject;
class QString;
QT_END_NAMESPACE
@@ -70,6 +71,7 @@ public:
bool isValid() const;
QString toString() const;
+ QJsonObject toJson() const;
void load(Internal::PersistentPool &pool);
void store(Internal::PersistentPool &pool) const;
diff --git a/src/lib/corelib/tools/error.cpp b/src/lib/corelib/tools/error.cpp
index 185dc0531..fc0b9377e 100644
--- a/src/lib/corelib/tools/error.cpp
+++ b/src/lib/corelib/tools/error.cpp
@@ -41,7 +41,10 @@
#include "persistence.h"
#include "qttools.h"
+#include "stringconstants.h"
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsonobject.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qstringlist.h>
@@ -156,6 +159,14 @@ QString ErrorItem::toString() const
return str += description();
}
+QJsonObject ErrorItem::toJson() const
+{
+ QJsonObject data;
+ data.insert(Internal::StringConstants::descriptionProperty(), description());
+ data.insert(Internal::StringConstants::locationKey(), codeLocation().toJson());
+ return data;
+}
+
class ErrorInfo::ErrorInfoPrivate : public QSharedData
{
@@ -248,7 +259,7 @@ void ErrorInfo::prepend(const QString &description, const CodeLocation &location
* Most often, there will be one element in this list, but there can be more e.g. to illustrate
* how an error condition propagates through several source files.
*/
-QList<ErrorItem> ErrorInfo::items() const
+const QList<ErrorItem> ErrorInfo::items() const
{
return d->items;
}
@@ -282,6 +293,17 @@ QString ErrorInfo::toString() const
return lines.join(QLatin1Char('\n'));
}
+QJsonObject ErrorInfo::toJson() const
+{
+ QJsonObject data;
+ data.insert(QLatin1String("is-internal"), isInternalError());
+ QJsonArray itemsArray;
+ for (const ErrorItem &item : items())
+ itemsArray.append(item.toJson());
+ data.insert(QLatin1String("items"), itemsArray);
+ return data;
+}
+
/*!
* \brief Returns true if this error represents a bug in qbs, false otherwise.
*/
diff --git a/src/lib/corelib/tools/error.h b/src/lib/corelib/tools/error.h
index 4832499af..abad85bad 100644
--- a/src/lib/corelib/tools/error.h
+++ b/src/lib/corelib/tools/error.h
@@ -47,6 +47,7 @@
#include <QtCore/qshareddata.h>
QT_BEGIN_NAMESPACE
+class QJsonObject;
template <class T> class QList;
class QString;
class QStringList;
@@ -68,6 +69,7 @@ public:
QString description() const;
CodeLocation codeLocation() const;
QString toString() const;
+ QJsonObject toJson() const;
bool isBacktraceItem() const;
@@ -97,10 +99,11 @@ public:
void append(const ErrorItem &item);
void append(const QString &description, const CodeLocation &location = CodeLocation());
void prepend(const QString &description, const CodeLocation &location = CodeLocation());
- QList<ErrorItem> items() const;
+ const QList<ErrorItem> items() const;
bool hasError() const { return !items().empty(); }
void clear();
QString toString() const;
+ QJsonObject toJson() const;
bool isInternalError() const;
bool hasLocation() const;
diff --git a/src/lib/corelib/tools/installoptions.cpp b/src/lib/corelib/tools/installoptions.cpp
index 5cddae4ad..93fd54efe 100644
--- a/src/lib/corelib/tools/installoptions.cpp
+++ b/src/lib/corelib/tools/installoptions.cpp
@@ -36,9 +36,13 @@
** $QT_END_LICENSE$
**
****************************************************************************/
+
#include "installoptions.h"
-#include "language/language.h"
-#include <tools/stringconstants.h>
+
+#include "jsonhelper.h"
+#include "stringconstants.h"
+
+#include <language/language.h>
#include <QtCore/qdir.h>
#include <QtCore/qshareddata.h>
@@ -230,4 +234,17 @@ void InstallOptions::setLogElapsedTime(bool logElapsedTime)
d->logElapsedTime = logElapsedTime;
}
+qbs::InstallOptions qbs::InstallOptions::fromJson(const QJsonObject &data)
+{
+ using namespace Internal;
+ InstallOptions opt;
+ setValueFromJson(opt.d->installRoot, data, "install-root");
+ setValueFromJson(opt.d->useSysroot, data, "use-sysroot");
+ setValueFromJson(opt.d->removeExisting, data, "clean-install-root");
+ setValueFromJson(opt.d->dryRun, data, "dry-run");
+ setValueFromJson(opt.d->keepGoing, data, "keep-going");
+ setValueFromJson(opt.d->logElapsedTime, data, "log-time");
+ return opt;
+}
+
} // namespace qbs
diff --git a/src/lib/corelib/tools/installoptions.h b/src/lib/corelib/tools/installoptions.h
index 69e00aae5..16511aa3d 100644
--- a/src/lib/corelib/tools/installoptions.h
+++ b/src/lib/corelib/tools/installoptions.h
@@ -44,6 +44,7 @@
#include <QtCore/qshareddata.h>
QT_BEGIN_NAMESPACE
+class QJsonObject;
class QString;
QT_END_NAMESPACE
@@ -65,6 +66,8 @@ public:
InstallOptions &operator=(InstallOptions &&other) Q_DECL_NOEXCEPT;
~InstallOptions();
+ static InstallOptions fromJson(const QJsonObject &data);
+
static QString defaultInstallRoot();
QString installRoot() const;
void setInstallRoot(const QString &installRoot);
diff --git a/src/lib/corelib/tools/jsonhelper.h b/src/lib/corelib/tools/jsonhelper.h
new file mode 100644
index 000000000..d87802c0a
--- /dev/null
+++ b/src/lib/corelib/tools/jsonhelper.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QBS_JSON_HELPER_H
+#define QBS_JSON_HELPER_H
+
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qjsonvalue.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qvariant.h>
+
+#include <algorithm>
+#include <iterator>
+
+namespace qbs {
+namespace Internal {
+
+template<typename T> inline T fromJson(const QJsonValue &v);
+template<> inline bool fromJson(const QJsonValue &v) { return v.toBool(); }
+template<> inline int fromJson(const QJsonValue &v) { return v.toInt(); }
+template<> inline QString fromJson(const QJsonValue &v) { return v.toString(); }
+template<> inline QStringList fromJson(const QJsonValue &v)
+{
+ const QJsonArray &jsonList = v.toArray();
+ QStringList stringList;
+ std::transform(jsonList.begin(), jsonList.end(), std::back_inserter(stringList),
+ [](const QVariant &v) { return v.toString(); });
+ return stringList;
+}
+template<> inline QVariantMap fromJson(const QJsonValue &v) { return v.toObject().toVariantMap(); }
+template<> inline QProcessEnvironment fromJson(const QJsonValue &v)
+{
+ const QJsonObject obj = v.toObject();
+ QProcessEnvironment env;
+ for (auto it = obj.begin(); it != obj.end(); ++it)
+ env.insert(it.key(), it.value().toString());
+ return env;
+}
+
+template<typename T> inline void setValueFromJson(T &targetValue, const QJsonObject &data,
+ const char *jsonProperty)
+{
+ const QJsonValue v = data.value(QLatin1String(jsonProperty));
+ if (!v.isNull())
+ targetValue = fromJson<T>(v);
+}
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // Include guard
diff --git a/src/lib/corelib/tools/processresult.cpp b/src/lib/corelib/tools/processresult.cpp
index 12e45b251..3fb2f8dbc 100644
--- a/src/lib/corelib/tools/processresult.cpp
+++ b/src/lib/corelib/tools/processresult.cpp
@@ -39,6 +39,9 @@
#include "processresult.h"
#include "processresult_p.h"
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsonobject.h>
+
/*!
* \class SetupProjectParameters
* \brief The \c ProcessResult class describes a finished qbs process command.
@@ -129,4 +132,31 @@ QStringList ProcessResult::stdErr() const
return d->stdErr;
}
+static QJsonValue processErrorToJson(QProcess::ProcessError error)
+{
+ switch (error) {
+ case QProcess::FailedToStart: return QLatin1String("failed-to-start");
+ case QProcess::Crashed: return QLatin1String("crashed");
+ case QProcess::Timedout: return QLatin1String("timed-out");
+ case QProcess::WriteError: return QLatin1String("write-error");
+ case QProcess::ReadError: return QLatin1String("read-error");
+ case QProcess::UnknownError: return QStringLiteral("unknown-error");
+ }
+ return {}; // For dumb compilers.
+}
+
+QJsonObject qbs::ProcessResult::toJson() const
+{
+ return QJsonObject{
+ {QStringLiteral("success"), success()},
+ {QStringLiteral("executable-file-path"), executableFilePath()},
+ {QStringLiteral("arguments"), QJsonArray::fromStringList(arguments())},
+ {QStringLiteral("working-directory"), workingDirectory()},
+ {QStringLiteral("error"), processErrorToJson(error())},
+ {QStringLiteral("exit-code"), exitCode()},
+ {QStringLiteral("stdout"), QJsonArray::fromStringList(stdOut())},
+ {QStringLiteral("stderr"), QJsonArray::fromStringList(stdErr())}
+ };
+}
+
} // namespace qbs
diff --git a/src/lib/corelib/tools/processresult.h b/src/lib/corelib/tools/processresult.h
index 2d2ebbfb4..92408aa31 100644
--- a/src/lib/corelib/tools/processresult.h
+++ b/src/lib/corelib/tools/processresult.h
@@ -46,6 +46,7 @@
#include <QtCore/qprocess.h>
QT_BEGIN_NAMESPACE
+class QJsonObject;
class QString;
class QStringList;
QT_END_NAMESPACE
@@ -65,6 +66,8 @@ public:
ProcessResult &operator=(const ProcessResult &other);
~ProcessResult();
+ QJsonObject toJson() const;
+
bool success() const;
QString executableFilePath() const;
QStringList arguments() const;
diff --git a/src/lib/corelib/tools/setupprojectparameters.cpp b/src/lib/corelib/tools/setupprojectparameters.cpp
index 6d817c8f3..41af7b926 100644
--- a/src/lib/corelib/tools/setupprojectparameters.cpp
+++ b/src/lib/corelib/tools/setupprojectparameters.cpp
@@ -42,6 +42,7 @@
#include <logging/translator.h>
#include <tools/buildgraphlocker.h>
#include <tools/installoptions.h>
+#include <tools/jsonhelper.h>
#include <tools/profile.h>
#include <tools/qbsassert.h>
#include <tools/scripttools.h>
@@ -50,6 +51,7 @@
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qprocess.h>
+#include <QtCore/qjsonobject.h>
namespace qbs {
namespace Internal {
@@ -69,14 +71,14 @@ public:
, forceProbeExecution(false)
, waitLockBuildGraph(false)
, restoreBehavior(SetupProjectParameters::RestoreAndTrackChanges)
- , propertyCheckingMode(ErrorHandlingMode::Relaxed)
+ , propertyCheckingMode(ErrorHandlingMode::Strict)
, productErrorMode(ErrorHandlingMode::Strict)
{
}
QString projectFilePath;
QString topLevelProfile;
- QString configurationName;
+ QString configurationName = QLatin1String("default");
QString buildRoot;
QStringList searchPaths;
QStringList pluginPaths;
@@ -121,6 +123,47 @@ SetupProjectParameters &SetupProjectParameters::operator=(const SetupProjectPara
return *this;
}
+namespace Internal {
+template<> ErrorHandlingMode fromJson(const QJsonValue &v)
+{
+ if (v.toString() == QLatin1String("relaxed"))
+ return ErrorHandlingMode::Relaxed;
+ return ErrorHandlingMode::Strict;
+}
+
+template<> SetupProjectParameters::RestoreBehavior fromJson(const QJsonValue &v)
+{
+ const QString value = v.toString();
+ if (value == QLatin1String("restore-only"))
+ return SetupProjectParameters::RestoreOnly;
+ if (value == QLatin1String("resolve-only"))
+ return SetupProjectParameters::ResolveOnly;
+ return SetupProjectParameters::RestoreAndTrackChanges;
+}
+} // namespace Internal
+
+SetupProjectParameters SetupProjectParameters::fromJson(const QJsonObject &data)
+{
+ using namespace Internal;
+ SetupProjectParameters params;
+ setValueFromJson(params.d->topLevelProfile, data, "top-level-profile");
+ setValueFromJson(params.d->configurationName, data, "configuration-name");
+ setValueFromJson(params.d->projectFilePath, data, "project-file-path");
+ setValueFromJson(params.d->buildRoot, data, "build-root");
+ setValueFromJson(params.d->settingsBaseDir, data, "settings-directory");
+ setValueFromJson(params.d->overriddenValues, data, "overridden-properties");
+ setValueFromJson(params.d->dryRun, data, "dry-run");
+ setValueFromJson(params.d->logElapsedTime, data, "log-time");
+ setValueFromJson(params.d->forceProbeExecution, data, "force-probe-execution");
+ setValueFromJson(params.d->waitLockBuildGraph, data, "wait-lock-build-graph");
+ setValueFromJson(params.d->fallbackProviderEnabled, data, "fallback-provider-enabled");
+ setValueFromJson(params.d->environment, data, "environment");
+ setValueFromJson(params.d->restoreBehavior, data, "restore-behavior");
+ setValueFromJson(params.d->propertyCheckingMode, data, "error-handling-mode");
+ params.d->productErrorMode = params.d->propertyCheckingMode;
+ return params;
+}
+
SetupProjectParameters &SetupProjectParameters::operator=(SetupProjectParameters &&other) Q_DECL_NOEXCEPT = default;
/*!
diff --git a/src/lib/corelib/tools/setupprojectparameters.h b/src/lib/corelib/tools/setupprojectparameters.h
index cf3b200cb..a4d090ec5 100644
--- a/src/lib/corelib/tools/setupprojectparameters.h
+++ b/src/lib/corelib/tools/setupprojectparameters.h
@@ -71,6 +71,8 @@ public:
SetupProjectParameters &operator=(const SetupProjectParameters &other);
SetupProjectParameters &operator=(SetupProjectParameters &&other) Q_DECL_NOEXCEPT;
+ static SetupProjectParameters fromJson(const QJsonObject &data);
+
QString topLevelProfile() const;
void setTopLevelProfile(const QString &profile);
diff --git a/src/lib/corelib/tools/stringconstants.h b/src/lib/corelib/tools/stringconstants.h
index cd41f3768..79cbcd125 100644
--- a/src/lib/corelib/tools/stringconstants.h
+++ b/src/lib/corelib/tools/stringconstants.h
@@ -69,6 +69,7 @@ public:
QBS_STRING_CONSTANT(baseNameProperty, "baseName")
QBS_STRING_CONSTANT(baseProfileProperty, "baseProfile")
QBS_STRING_CONSTANT(buildDirectoryProperty, "buildDirectory")
+ QBS_STRING_CONSTANT(buildDirectoryKey, "build-directory")
QBS_STRING_CONSTANT(builtByDefaultProperty, "builtByDefault")
QBS_STRING_CONSTANT(classNameProperty, "className")
QBS_STRING_CONSTANT(completeBaseNameProperty, "completeBaseName")
@@ -90,11 +91,13 @@ public:
static const QString &fileNameProperty() { return fileName(); }
static const QString &filePathProperty() { return filePath(); }
static const QString &filePathVar() { return filePath(); }
+ QBS_STRING_CONSTANT(filePathKey, "file-path")
QBS_STRING_CONSTANT(fileTagsFilterProperty, "fileTagsFilter")
QBS_STRING_CONSTANT(fileTagsProperty, "fileTags")
QBS_STRING_CONSTANT(filesProperty, "files")
QBS_STRING_CONSTANT(filesAreTargetsProperty, "filesAreTargets")
QBS_STRING_CONSTANT(foundProperty, "found")
+ QBS_STRING_CONSTANT(fullDisplayNameKey, "full-display-name")
QBS_STRING_CONSTANT(imports, "imports")
static const QString &importsDir() { return imports(); }
static const QString &importsProperty() { return imports(); }
@@ -106,12 +109,16 @@ public:
QBS_STRING_CONSTANT(installPrefixProperty, "installPrefix")
QBS_STRING_CONSTANT(installDirProperty, "installDir")
QBS_STRING_CONSTANT(installSourceBaseProperty, "installSourceBase")
+ QBS_STRING_CONSTANT(isEnabledKey, "is-enabled")
QBS_STRING_CONSTANT(jobCountProperty, "jobCount")
QBS_STRING_CONSTANT(jobPoolProperty, "jobPool")
QBS_STRING_CONSTANT(lengthProperty, "length")
QBS_STRING_CONSTANT(limitToSubProjectProperty, "limitToSubProject")
+ QBS_STRING_CONSTANT(locationKey, "location")
+ QBS_STRING_CONSTANT(messageKey, "message")
QBS_STRING_CONSTANT(minimumQbsVersionProperty, "minimumQbsVersion")
QBS_STRING_CONSTANT(moduleNameProperty, "moduleName")
+ QBS_STRING_CONSTANT(modulePropertiesKey, "module-properties")
QBS_STRING_CONSTANT(moduleProviders, "moduleProviders")
QBS_STRING_CONSTANT(multiplexByQbsPropertiesProperty, "multiplexByQbsProperties")
QBS_STRING_CONSTANT(multiplexConfigurationIdProperty, "multiplexConfigurationId")
@@ -135,6 +142,7 @@ public:
QBS_STRING_CONSTANT(profileProperty, "profile")
static const QString &profilesProperty() { return profiles(); }
QBS_STRING_CONSTANT(productTypesProperty, "productTypes")
+ QBS_STRING_CONSTANT(productsKey, "products")
QBS_STRING_CONSTANT(qbsSearchPathsProperty, "qbsSearchPaths")
QBS_STRING_CONSTANT(referencesProperty, "references")
QBS_STRING_CONSTANT(recursiveProperty, "recursive")
@@ -149,7 +157,8 @@ public:
QBS_STRING_CONSTANT(sourceDirectoryProperty, "sourceDirectory")
QBS_STRING_CONSTANT(submodulesProperty, "submodules")
QBS_STRING_CONSTANT(targetNameProperty, "targetName")
- QBS_STRING_CONSTANT(typeProperty, "type")
+ static const QString &typeProperty() { return type(); }
+ QBS_STRING_CONSTANT(type, "type")
QBS_STRING_CONSTANT(validateProperty, "validate")
QBS_STRING_CONSTANT(versionProperty, "version")
QBS_STRING_CONSTANT(versionAtLeastProperty, "versionAtLeast")
diff --git a/src/lib/corelib/tools/tools.pri b/src/lib/corelib/tools/tools.pri
index f9c6be9a5..89d752671 100644
--- a/src/lib/corelib/tools/tools.pri
+++ b/src/lib/corelib/tools/tools.pri
@@ -23,6 +23,7 @@ HEADERS += \
$$PWD/iosutils.h \
$$PWD/joblimits.h \
$$PWD/jsliterals.h \
+ $$PWD/jsonhelper.h \
$$PWD/launcherinterface.h \
$$PWD/launcherpackets.h \
$$PWD/launchersocket.h \