aboutsummaryrefslogtreecommitdiffstats
path: root/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/app')
-rw-r--r--src/app/CMakeLists.txt8
-rw-r--r--src/app/app.pri23
-rw-r--r--src/app/app.pro10
-rw-r--r--src/app/apps.qbs3
-rw-r--r--src/app/config-ui/CMakeLists.txt19
-rw-r--r--src/app/config-ui/config-ui.pro26
-rw-r--r--src/app/config-ui/config-ui.qbs3
-rw-r--r--src/app/config-ui/main.cpp2
-rw-r--r--src/app/config-ui/mainwindow.cpp11
-rw-r--r--src/app/config/CMakeLists.txt13
-rw-r--r--src/app/config/config.pro13
-rw-r--r--src/app/config/config.qbs2
-rw-r--r--src/app/config/configcommand.h2
-rw-r--r--src/app/config/configcommandexecutor.cpp106
-rw-r--r--src/app/config/configcommandlineparser.cpp33
-rw-r--r--src/app/config/configcommandlineparser.h2
-rw-r--r--src/app/qbs-create-project/CMakeLists.txt10
-rw-r--r--src/app/qbs-create-project/create-project-main.cpp5
-rw-r--r--src/app/qbs-create-project/createproject.cpp29
-rw-r--r--src/app/qbs-create-project/createproject.h6
-rw-r--r--src/app/qbs-create-project/qbs-create-project.pro9
-rw-r--r--src/app/qbs-create-project/qbs-create-project.qbs2
-rw-r--r--src/app/qbs-setup-android/CMakeLists.txt12
-rw-r--r--src/app/qbs-setup-android/android-setup.cpp40
-rw-r--r--src/app/qbs-setup-android/commandlineparser.cpp7
-rw-r--r--src/app/qbs-setup-android/commandlineparser.h4
-rw-r--r--src/app/qbs-setup-android/main.cpp1
-rw-r--r--src/app/qbs-setup-android/qbs-setup-android.pro12
-rw-r--r--src/app/qbs-setup-android/qbs-setup-android.qbs9
-rw-r--r--src/app/qbs-setup-qt/CMakeLists.txt12
-rw-r--r--src/app/qbs-setup-qt/main.cpp3
-rw-r--r--src/app/qbs-setup-qt/qbs-setup-qt.pro16
-rw-r--r--src/app/qbs-setup-qt/qbs-setup-qt.qbs9
-rw-r--r--src/app/qbs-setup-qt/setupqt.cpp30
-rw-r--r--src/app/qbs-setup-toolchains/CMakeLists.txt32
-rw-r--r--src/app/qbs-setup-toolchains/clangclprobe.cpp127
-rw-r--r--src/app/qbs-setup-toolchains/cosmicprobe.cpp179
-rw-r--r--src/app/qbs-setup-toolchains/cosmicprobe.h61
-rw-r--r--src/app/qbs-setup-toolchains/dmcprobe.cpp264
-rw-r--r--src/app/qbs-setup-toolchains/dmcprobe.h61
-rw-r--r--src/app/qbs-setup-toolchains/gccprobe.cpp99
-rw-r--r--src/app/qbs-setup-toolchains/gccprobe.h2
-rw-r--r--src/app/qbs-setup-toolchains/iarewprobe.cpp94
-rw-r--r--src/app/qbs-setup-toolchains/keilprobe.cpp341
-rw-r--r--src/app/qbs-setup-toolchains/main.cpp1
-rw-r--r--src/app/qbs-setup-toolchains/msvcprobe.cpp310
-rw-r--r--src/app/qbs-setup-toolchains/msvcprobe.h10
-rw-r--r--src/app/qbs-setup-toolchains/probe.cpp82
-rw-r--r--src/app/qbs-setup-toolchains/probe.h13
-rw-r--r--src/app/qbs-setup-toolchains/qbs-setup-toolchains.pro30
-rw-r--r--src/app/qbs-setup-toolchains/qbs-setup-toolchains.qbs15
-rw-r--r--src/app/qbs-setup-toolchains/sdccprobe.cpp105
-rw-r--r--src/app/qbs-setup-toolchains/sdccprobe.h2
-rw-r--r--src/app/qbs-setup-toolchains/watcomprobe.cpp255
-rw-r--r--src/app/qbs-setup-toolchains/watcomprobe.h59
-rw-r--r--src/app/qbs-setup-toolchains/xcodeprobe.cpp26
-rw-r--r--src/app/qbs/CMakeLists.txt56
-rw-r--r--src/app/qbs/commandlinefrontend.cpp58
-rw-r--r--src/app/qbs/lspserver.cpp629
-rw-r--r--src/app/qbs/lspserver.h65
-rw-r--r--src/app/qbs/parser/commandlineoption.cpp44
-rw-r--r--src/app/qbs/parser/commandlineoption.h33
-rw-r--r--src/app/qbs/parser/commandlineoptionpool.cpp16
-rw-r--r--src/app/qbs/parser/commandlineoptionpool.h2
-rw-r--r--src/app/qbs/parser/commandlineparser.cpp76
-rw-r--r--src/app/qbs/parser/commandlineparser.h9
-rw-r--r--src/app/qbs/parser/parser.pri16
-rw-r--r--src/app/qbs/parser/parsercommand.cpp25
-rw-r--r--src/app/qbs/qbs.pro54
-rw-r--r--src/app/qbs/qbs.qbs15
-rw-r--r--src/app/qbs/session.cpp37
-rw-r--r--src/app/qbs/sessionpacket.cpp9
-rw-r--r--src/app/qbs/sessionpacket.h2
-rw-r--r--src/app/qbs/sessionpacketreader.cpp9
-rw-r--r--src/app/qbs/status.cpp58
-rw-r--r--src/app/qbs/stdinreader.cpp206
-rw-r--r--src/app/shared/CMakeLists.txt1
-rw-r--r--src/app/shared/logging/CMakeLists.txt12
-rw-r--r--src/app/shared/logging/coloredoutput.cpp16
-rw-r--r--src/app/shared/logging/coloredoutput.h4
-rw-r--r--src/app/shared/logging/consolelogger.cpp8
-rw-r--r--src/app/shared/logging/logging.pri2
-rw-r--r--src/app/shared/logging/logging.qbs14
-rw-r--r--src/app/shared/shared.qbs5
84 files changed, 2975 insertions, 1166 deletions
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
new file mode 100644
index 000000000..3a4ab38e0
--- /dev/null
+++ b/src/app/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_subdirectory(config)
+add_subdirectory(config-ui)
+add_subdirectory(qbs)
+add_subdirectory(qbs-create-project)
+add_subdirectory(qbs-setup-android)
+add_subdirectory(qbs-setup-qt)
+add_subdirectory(qbs-setup-toolchains)
+add_subdirectory(shared)
diff --git a/src/app/app.pri b/src/app/app.pri
deleted file mode 100644
index d0c94a770..000000000
--- a/src/app/app.pri
+++ /dev/null
@@ -1,23 +0,0 @@
-include(../install_prefix.pri)
-
-QT = core
-TEMPLATE = app
-DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_PROCESS_COMBINED_ARGUMENT_START
-!isEmpty(QBS_APPS_DESTDIR):DESTDIR = $${QBS_APPS_DESTDIR}
-else:DESTDIR = ../../../bin
-
-!isEmpty(QBS_APPS_RPATH_DIR) {
- linux-*:QMAKE_LFLAGS += -Wl,-z,origin \'-Wl,-rpath,$${QBS_APPS_RPATH_DIR}\'
- macx:QMAKE_LFLAGS += -Wl,-rpath,$${QBS_APPS_RPATH_DIR}
-}
-
-CONFIG += console
-CONFIG -= app_bundle
-CONFIG += c++14
-
-include($${PWD}/../lib/corelib/use_corelib.pri)
-include($${PWD}/shared/logging/logging.pri)
-
-!isEmpty(QBS_APPS_INSTALL_DIR):target.path = $${QBS_APPS_INSTALL_DIR}
-else:target.path = $${QBS_INSTALL_PREFIX}/bin
-INSTALLS += target
diff --git a/src/app/app.pro b/src/app/app.pro
deleted file mode 100644
index 935ce1776..000000000
--- a/src/app/app.pro
+++ /dev/null
@@ -1,10 +0,0 @@
-TEMPLATE = subdirs
-SUBDIRS =\
- qbs\
- qbs-create-project \
- qbs-setup-android \
- qbs-setup-toolchains \
- qbs-setup-qt \
- config
-
-!isEmpty(QT.widgets.name):SUBDIRS += config-ui
diff --git a/src/app/apps.qbs b/src/app/apps.qbs
index 1fcb15e43..4ea0a2c87 100644
--- a/src/app/apps.qbs
+++ b/src/app/apps.qbs
@@ -1,5 +1,3 @@
-import qbs
-
Project {
references: [
"config/config.qbs",
@@ -9,5 +7,6 @@ Project {
"qbs-setup-android/qbs-setup-android.qbs",
"qbs-setup-qt/qbs-setup-qt.qbs",
"qbs-setup-toolchains/qbs-setup-toolchains.qbs",
+ "shared/shared.qbs",
]
}
diff --git a/src/app/config-ui/CMakeLists.txt b/src/app/config-ui/CMakeLists.txt
new file mode 100644
index 000000000..f3375d5ea
--- /dev/null
+++ b/src/app/config-ui/CMakeLists.txt
@@ -0,0 +1,19 @@
+set(SOURCES
+ commandlineparser.cpp
+ commandlineparser.h
+ main.cpp
+ mainwindow.cpp
+ mainwindow.h
+ mainwindow.ui
+ )
+
+# TODO: support Info.plist
+if(APPLE)
+ set(MACOS_SOURCES fgapp.mm)
+ set(MACOS_FRAMEWORKS "-framework ApplicationServices" "-framework Cocoa")
+endif()
+
+add_qbs_app(qbs-config-ui
+ DEPENDS qbscore qbsconsolelogger Qt${QT_VERSION_MAJOR}::Widgets ${MACOS_FRAMEWORKS}
+ SOURCES ${SOURCES} ${MACOS_SOURCES}
+ )
diff --git a/src/app/config-ui/config-ui.pro b/src/app/config-ui/config-ui.pro
deleted file mode 100644
index 84d3b3e4d..000000000
--- a/src/app/config-ui/config-ui.pro
+++ /dev/null
@@ -1,26 +0,0 @@
-include(../app.pri)
-
-CONFIG -= console
-QT += gui widgets
-
-TARGET = qbs-config-ui
-
-HEADERS += \
- commandlineparser.h \
- mainwindow.h
-
-SOURCES += \
- commandlineparser.cpp \
- main.cpp \
- mainwindow.cpp
-
-OTHER_FILES += \
- Info.plist
-
-osx {
- QMAKE_LFLAGS += -sectcreate __TEXT __info_plist $$shell_quote($$PWD/Info.plist)
- OBJECTIVE_SOURCES += fgapp.mm
- LIBS += -framework ApplicationServices -framework Cocoa
-}
-
-FORMS += mainwindow.ui
diff --git a/src/app/config-ui/config-ui.qbs b/src/app/config-ui/config-ui.qbs
index 2222e6a17..544542ca4 100644
--- a/src/app/config-ui/config-ui.qbs
+++ b/src/app/config-ui/config-ui.qbs
@@ -1,5 +1,3 @@
-import qbs 1.0
-
QbsApp {
Depends { name: "Qt.widgets" }
name: "qbs-config-ui"
@@ -24,6 +22,7 @@ QbsApp {
Properties {
condition: qbs.targetOS.contains("macos")
cpp.frameworks: ["ApplicationServices", "Cocoa"]
+ bundle.isBundle: false
}
Properties {
diff --git a/src/app/config-ui/main.cpp b/src/app/config-ui/main.cpp
index a7c2662e6..dcf538989 100644
--- a/src/app/config-ui/main.cpp
+++ b/src/app/config-ui/main.cpp
@@ -47,8 +47,6 @@
#include <cstdlib>
#include <iostream>
-using qbs::Internal::Tr;
-
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
diff --git a/src/app/config-ui/mainwindow.cpp b/src/app/config-ui/mainwindow.cpp
index 2bf7fad5e..31239dc41 100644
--- a/src/app/config-ui/mainwindow.cpp
+++ b/src/app/config-ui/mainwindow.cpp
@@ -48,7 +48,12 @@
#include <QtGui/qevent.h>
#include <QtGui/qkeysequence.h>
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+#include <QtGui/qaction.h>
+#else
#include <QtWidgets/qaction.h>
+#endif
+
#include <QtWidgets/qlineedit.h>
#include <QtWidgets/qmenu.h>
#include <QtWidgets/qmenubar.h>
@@ -62,7 +67,7 @@ class TrimValidator : public QValidator
public:
explicit TrimValidator(QObject *parent = nullptr) : QValidator(parent) {}
-public: // QValidator interface
+ // QValidator interface
State validate(QString &input, int &pos) const override
{
Q_UNUSED(pos);
@@ -119,10 +124,10 @@ MainWindow::MainWindow(const QString &settingsDir, qbs::Settings::Scope scope, Q
saveAction->setShortcut(QKeySequence::Save);
connect(saveAction, &QAction::triggered, this, &MainWindow::saveSettings);
const auto expandAllAction = new QAction(tr("&Expand All"), this);
- expandAllAction->setShortcut(Qt::CTRL | Qt::Key_E);
+ expandAllAction->setShortcut(int(Qt::CTRL) | int(Qt::Key_E));
connect(expandAllAction, &QAction::triggered, this, &MainWindow::expandAll);
const auto collapseAllAction = new QAction(tr("C&ollapse All"), this);
- collapseAllAction->setShortcut(Qt::CTRL | Qt::Key_O);
+ collapseAllAction->setShortcut(int(Qt::CTRL) | int(Qt::Key_O));
connect(collapseAllAction, &QAction::triggered, this, &MainWindow::collapseAll);
const auto exitAction = new QAction(tr("E&xit"), this);
exitAction->setShortcut(QKeySequence::Quit);
diff --git a/src/app/config/CMakeLists.txt b/src/app/config/CMakeLists.txt
new file mode 100644
index 000000000..064d8fb22
--- /dev/null
+++ b/src/app/config/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(SOURCES
+ configcommand.h
+ configcommandexecutor.cpp
+ configcommandexecutor.h
+ configcommandlineparser.cpp
+ configcommandlineparser.h
+ configmain.cpp
+ )
+
+add_qbs_app(qbs-config
+ DEPENDS qbscore qbsconsolelogger
+ SOURCES ${SOURCES}
+ )
diff --git a/src/app/config/config.pro b/src/app/config/config.pro
deleted file mode 100644
index 278c481d1..000000000
--- a/src/app/config/config.pro
+++ /dev/null
@@ -1,13 +0,0 @@
-include(../app.pri)
-
-TARGET = qbs-config
-
-SOURCES += \
- configcommandexecutor.cpp \
- configcommandlineparser.cpp \
- configmain.cpp
-
-HEADERS += \
- configcommand.h \
- configcommandexecutor.h \
- configcommandlineparser.h
diff --git a/src/app/config/config.qbs b/src/app/config/config.qbs
index 9daa701f6..2744b93f7 100644
--- a/src/app/config/config.qbs
+++ b/src/app/config/config.qbs
@@ -1,5 +1,3 @@
-import qbs 1.0
-
QbsApp {
name: "qbs-config"
files: [
diff --git a/src/app/config/configcommand.h b/src/app/config/configcommand.h
index 1574e3745..f8e2a8ae9 100644
--- a/src/app/config/configcommand.h
+++ b/src/app/config/configcommand.h
@@ -47,7 +47,7 @@
class ConfigCommand
{
public:
- enum Command { CfgSet, CfgUnset, CfgList, CfgExport, CfgImport, CfgNone };
+ enum Command { CfgSet, CfgUnset, CfgList, CfgExport, CfgImport, CfgAddProfile, CfgNone };
ConfigCommand() : command(CfgNone) {}
diff --git a/src/app/config/configcommandexecutor.cpp b/src/app/config/configcommandexecutor.cpp
index 1290ba2f0..cbcb5ee73 100644
--- a/src/app/config/configcommandexecutor.cpp
+++ b/src/app/config/configcommandexecutor.cpp
@@ -41,17 +41,60 @@
#include "configcommand.h"
#include "../shared/logging/consolelogger.h"
-#include <tools/settingsrepresentation.h>
#include <tools/error.h>
+#include <tools/profile.h>
+#include <tools/qttools.h>
+#include <tools/settingsrepresentation.h>
#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/qtextstream.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
#include <cstdio>
using namespace qbs;
+static QJsonObject settingsToJSONObject(
+ Settings &settings, qbs::Settings::Scopes scopes, const QString &parentGroup = {})
+{
+ QJsonObject result;
+
+ const auto allKeys = settings.directChildren(parentGroup, scopes);
+ for (const auto& key : allKeys) {
+ const auto fullKey = parentGroup.isEmpty()
+ ? key
+ : QStringLiteral("%1.%2").arg(parentGroup, key);
+ const auto value = settings.value(fullKey, scopes);
+ if (value.isValid()) { // looks like a real value
+ result[key] = QJsonValue::fromVariant(value);
+ } else { // looks like a group
+ result[key] = settingsToJSONObject(settings, scopes, fullKey);
+ }
+ }
+
+ return result;
+}
+
+static void settingsFromJSONObject(
+ Settings &settings, const QJsonObject &object, const QString &parentGroup = {})
+{
+ for (auto it = object.begin(), end = object.end(); it != end; ++it) {
+ const auto key = it.key();
+ const auto value = it.value();
+ const auto fullKey = parentGroup.isEmpty()
+ ? key
+ : QStringLiteral("%1.%2").arg(parentGroup, key);
+ if (value.isObject()) {
+ settingsFromJSONObject(settings, it.value().toObject(), fullKey);
+ } else {
+ settings.setValue(fullKey, value.toVariant());
+ }
+ }
+}
+
+
ConfigCommandExecutor::ConfigCommandExecutor(Settings *settings, Settings::Scopes scope)
: m_settings(settings), m_scope(scope)
{
@@ -72,15 +115,26 @@ void ConfigCommandExecutor::execute(const ConfigCommand &command)
for (const QString &varName : command.varNames)
m_settings->remove(varName);
break;
+ case ConfigCommand::CfgAddProfile: {
+ Profile profile(command.varValue, m_settings);
+ profile.removeProfile();
+ Q_ASSERT(command.varNames.size() % 2 == 0);
+ for (int i = 0; i < command.varNames.size(); i += 2) {
+ const QString &key = command.varNames.at(i);
+ const QString &rawValue = command.varNames.at(i + 1);
+ profile.setValue(key, representationToSettingsValue(rawValue));
+ }
+ break;
+ }
case ConfigCommand::CfgExport:
exportSettings(command.fileName);
break;
case ConfigCommand::CfgImport:
// Display old and new settings, in case import fails or user accidentally nukes everything
- printf("old "); // Will end up as "old settings:"
+ std::printf("old "); // Will end up as "old settings:"
printSettings(command);
importSettings(command.fileName);
- printf("\nnew ");
+ std::printf("\nnew ");
printSettings(command);
break;
case ConfigCommand::CfgNone:
@@ -115,7 +169,7 @@ void ConfigCommandExecutor::printSettings(const ConfigCommand &command)
void ConfigCommandExecutor::printOneSetting(const QString &key)
{
- printf("%s: %s\n", qPrintable(key),
+ std::printf("%s: %s\n", qPrintable(key),
qPrintable(qbs::settingsValueToRepresentation(m_settings->value(key, m_scope))));
}
@@ -126,12 +180,20 @@ void ConfigCommandExecutor::exportSettings(const QString &filename)
throw ErrorInfo(tr("Could not open file '%1' for writing: %2")
.arg(QDir::toNativeSeparators(filename), file.errorString()));
}
- QTextStream stream(&file);
- stream.setCodec("UTF-8");
- const auto keys = m_settings->allKeys(m_scope);
- for (const QString &key : keys)
- stream << key << ": " << qbs::settingsValueToRepresentation(m_settings->value(key, m_scope))
- << endl;
+
+ if (QFileInfo(filename).suffix() == u"json") {
+ QJsonDocument doc;
+ doc.setObject(settingsToJSONObject(*m_settings, m_scope));
+ file.write(doc.toJson());
+ } else {
+ QTextStream stream(&file);
+ setupDefaultCodec(stream);
+ const auto keys = m_settings->allKeys(m_scope);
+ for (const QString &key : keys)
+ stream << key << ": "
+ << qbs::settingsValueToRepresentation(m_settings->value(key, m_scope))
+ << Qt::endl;
+ }
}
void ConfigCommandExecutor::importSettings(const QString &filename)
@@ -146,15 +208,21 @@ void ConfigCommandExecutor::importSettings(const QString &filename)
for (const QString &key : keys)
m_settings->remove(key);
- QTextStream stream(&file);
- stream.setCodec("UTF-8");
- while (!stream.atEnd()) {
- QString line = stream.readLine();
- int colon = line.indexOf(QLatin1Char(':'));
- if (colon >= 0 && !line.startsWith(QLatin1Char('#'))) {
- const QString key = line.left(colon).trimmed();
- const QString value = line.mid(colon + 1).trimmed();
- m_settings->setValue(key, representationToSettingsValue(value));
+ if (QFileInfo(filename).suffix() == u"json") {
+ const auto doc = QJsonDocument::fromJson(file.readAll());
+ const auto object = doc.object();
+ settingsFromJSONObject(*m_settings, doc.object());
+ } else {
+ QTextStream stream(&file);
+ setupDefaultCodec(stream);
+ while (!stream.atEnd()) {
+ QString line = stream.readLine();
+ int colon = line.indexOf(QLatin1Char(':'));
+ if (colon >= 0 && !line.startsWith(QLatin1Char('#'))) {
+ const QString key = line.left(colon).trimmed();
+ const QString value = line.mid(colon + 1).trimmed();
+ m_settings->setValue(key, representationToSettingsValue(value));
+ }
}
}
}
diff --git a/src/app/config/configcommandlineparser.cpp b/src/app/config/configcommandlineparser.cpp
index 2f2d1610e..1ad0cc4b1 100644
--- a/src/app/config/configcommandlineparser.cpp
+++ b/src/app/config/configcommandlineparser.cpp
@@ -71,6 +71,8 @@ void ConfigCommandLineParser::parse(const QStringList &commandLine)
setCommand(ConfigCommand::CfgExport);
else if (arg == QLatin1String("import"))
setCommand(ConfigCommand::CfgImport);
+ else if (arg == QLatin1String("add-profile"))
+ setCommand(ConfigCommand::CfgAddProfile);
else if (arg == QLatin1String("settings-dir"))
assignOptionArgument(arg, m_settingsDir);
else if (arg == QLatin1String("user"))
@@ -113,6 +115,22 @@ void ConfigCommandLineParser::parse(const QStringList &commandLine)
case ConfigCommand::CfgList:
m_command.varNames = m_commandLine;
break;
+ case ConfigCommand::CfgAddProfile:
+ if (m_commandLine.empty())
+ throw Error(Tr::tr("Profile name missing."));
+ m_command.varValue = m_commandLine.takeFirst();
+ if (m_command.varValue.isEmpty())
+ throw Error(Tr::tr("Profile name must not be empty."));
+ m_command.varNames = m_commandLine;
+ if (m_command.varNames.isEmpty())
+ throw Error(Tr::tr("Profile properties must be provided."));
+ if (m_command.varNames.size() % 2 != 0)
+ throw Error(Tr::tr("Profile properties must be key/value pairs."));
+ for (const auto &varName : std::as_const(m_command.varNames)) {
+ if (varName.isEmpty())
+ throw Error(Tr::tr("Property names must not be empty."));
+ }
+ break;
default:
break;
}
@@ -134,18 +152,19 @@ void ConfigCommandLineParser::setScope(Settings::Scope scope)
void ConfigCommandLineParser::printUsage() const
{
- puts("Usage:\n"
+ std::puts("Usage:\n"
" qbs config [--settings-dir <settings directory] <options>\n"
" qbs config [--settings-dir <settings directory] <key>\n"
" qbs config [--settings-dir <settings directory] <key> <value>"
"\n"
"Options:\n"
- " --list [<root> ...] list keys under key <root> or all keys\n"
- " --user consider only user-level settings\n"
- " --system consider only system-level settings\n"
- " --unset <name> remove key with given name\n"
- " --import <file> import settings from given file\n"
- " --export <file> export settings to given file\n");
+ " --list [<root> ...] list keys under key <root> or all keys\n"
+ " --user consider only user-level settings\n"
+ " --system consider only system-level settings\n"
+ " --unset <name> remove key with given name\n"
+ " --add-profile <name> <key> <value> ... add profile with the given name and properties\n"
+ " --import <file> import settings from given file\n"
+ " --export <file> export settings to given file\n");
}
void ConfigCommandLineParser::assignOptionArgument(const QString &option, QString &argument)
diff --git a/src/app/config/configcommandlineparser.h b/src/app/config/configcommandlineparser.h
index b567134fd..f1f026678 100644
--- a/src/app/config/configcommandlineparser.h
+++ b/src/app/config/configcommandlineparser.h
@@ -60,7 +60,7 @@ public:
class Error
{
public:
- Error(const QString &message) : m_message(message) { }
+ Error(QString message) : m_message(std::move(message)) { }
QString message() const { return m_message; }
private:
QString m_message;
diff --git a/src/app/qbs-create-project/CMakeLists.txt b/src/app/qbs-create-project/CMakeLists.txt
new file mode 100644
index 000000000..a627d11ff
--- /dev/null
+++ b/src/app/qbs-create-project/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(SOURCES
+ createproject.cpp
+ createproject.h
+ create-project-main.cpp
+ )
+
+add_qbs_app(qbs-create-project
+ DEPENDS qbscore qbsconsolelogger
+ SOURCES ${SOURCES}
+ )
diff --git a/src/app/qbs-create-project/create-project-main.cpp b/src/app/qbs-create-project/create-project-main.cpp
index f26df3dbf..51e7ce514 100644
--- a/src/app/qbs-create-project/create-project-main.cpp
+++ b/src/app/qbs-create-project/create-project-main.cpp
@@ -41,6 +41,7 @@
#include <logging/translator.h>
#include <tools/error.h>
+#include <tools/qttools.h>
#include <QtCore/qcommandlineoption.h>
#include <QtCore/qcommandlineparser.h>
@@ -79,9 +80,9 @@ int main(int argc, char *argv[])
const ProjectStructure projectStructure = parser.isSet(flatOpt)
? ProjectStructure::Flat : ProjectStructure::Composite;
const QStringList whiteList = parser.value(whiteListOpt).split(QLatin1Char(','),
- QString::SkipEmptyParts);
+ Qt::SkipEmptyParts);
const QStringList blackList = parser.value(blackListOpt).split(QLatin1Char(','),
- QString::SkipEmptyParts);
+ Qt::SkipEmptyParts);
try {
ProjectCreator().run(QDir::currentPath(), projectStructure, whiteList, blackList);
} catch (const ErrorInfo &e) {
diff --git a/src/app/qbs-create-project/createproject.cpp b/src/app/qbs-create-project/createproject.cpp
index 26147b484..22c845994 100644
--- a/src/app/qbs-create-project/createproject.cpp
+++ b/src/app/qbs-create-project/createproject.cpp
@@ -43,6 +43,7 @@
#include <tools/error.h>
#include <tools/jsliterals.h>
#include <tools/qttools.h>
+#include <tools/stlutils.h>
#include <QtCore/qdebug.h>
#include <QtCore/qdiriterator.h>
@@ -61,10 +62,10 @@ void ProjectCreator::run(const QString &topLevelDir, ProjectStructure projectStr
const QStringList &whiteList, const QStringList &blackList)
{
m_projectStructure = projectStructure;
- for (const QString &s : whiteList)
- m_whiteList.push_back(QRegExp(s, Qt::CaseSensitive, QRegExp::Wildcard));
- for (const QString &s : blackList)
- m_blackList.push_back(QRegExp(s, Qt::CaseSensitive, QRegExp::Wildcard));
+ qbs::Internal::transform(whiteList, m_whiteList, [](const QString &s) {
+ return QRegularExpression(QRegularExpression::wildcardToRegularExpression(s)); });
+ qbs::Internal::transform(blackList, m_blackList, [](const QString &s) {
+ return QRegularExpression(QRegularExpression::wildcardToRegularExpression(s)); });
m_topLevelProject.dirPath = topLevelDir;
setupProject(&m_topLevelProject);
serializeProject(m_topLevelProject);
@@ -103,8 +104,7 @@ void ProjectCreator::serializeProject(const ProjectCreator::Project &project)
.arg(projectFile.fileName(), projectFile.errorString()));
}
QTextStream fileContents(&projectFile);
- fileContents.setCodec("UTF-8");
- fileContents << "import qbs\n\n";
+ qbs::setupDefaultCodec(fileContents);
if (!project.fileNames.empty() || m_projectStructure == ProjectStructure::Flat) {
fileContents << "Product {\n";
const ProductFlags productFlags = getFlags(project);
@@ -124,7 +124,7 @@ void ProjectCreator::serializeProject(const ProjectCreator::Project &project)
fileContents << indent << "Depends { name: \"cpp\" }\n";
}
fileContents << indent << "files: [\n";
- for (const QString &fileName : qAsConst(project.fileNames))
+ for (const QString &fileName : std::as_const(project.fileNames))
fileContents << indent << indent << qbs::toJSLiteral(fileName) << ",\n";
fileContents << indent << "]\n";
for (const ProjectPtr &p : project.subProjects)
@@ -152,7 +152,7 @@ void ProjectCreator::addGroups(QTextStream &stream, const QDir &baseDir,
<< qbs::toJSLiteral(baseDir.relativeFilePath(subProject.dirPath) + QLatin1Char('/'))
<< '\n';
stream << indent << indent << "files: [\n";
- for (const QString &fileName : qAsConst(subProject.fileNames))
+ for (const QString &fileName : std::as_const(subProject.fileNames))
stream << indent << indent << indent << qbs::toJSLiteral(fileName) << ",\n";
stream << indent << indent << "]\n";
stream << indent << "}\n";
@@ -162,10 +162,11 @@ void ProjectCreator::addGroups(QTextStream &stream, const QDir &baseDir,
bool ProjectCreator::isSourceFile(const QString &fileName)
{
- const auto isMatch = [fileName](const QRegExp &rex) { return rex.exactMatch(fileName); };
- return !std::any_of(m_blackList.cbegin(), m_blackList.cend(), isMatch)
- && (m_whiteList.empty()
- || std::any_of(m_whiteList.cbegin(), m_whiteList.cend(), isMatch));
+ const auto isMatch = [fileName](const QRegularExpression &rex) {
+ return rex.match(fileName).hasMatch();
+ };
+ return !qbs::Internal::any_of(m_blackList, isMatch)
+ && (m_whiteList.empty() || qbs::Internal::any_of(m_whiteList, isMatch));
}
ProjectCreator::ProductFlags ProjectCreator::getFlags(const ProjectCreator::Project &project)
@@ -183,7 +184,7 @@ ProjectCreator::ProductFlags ProjectCreator::getFlags(const ProjectCreator::Proj
void ProjectCreator::getFlagsFromFileNames(const ProjectCreator::Project &project,
ProductFlags &flags)
{
- for (const QString &fileName : qAsConst(project.fileNames)) {
+ for (const QString &fileName : std::as_const(project.fileNames)) {
if (flags.testFlag(IsApp) && flags.testFlag(NeedsQt))
return;
const QFileInfo fi(project.dirPath + QLatin1Char('/') + fileName);
@@ -209,7 +210,7 @@ void ProjectCreator::getFlagsFromFileNames(const ProjectCreator::Project &projec
void ProjectCreator::getFlagsFromFileContents(const ProjectCreator::Project &project,
ProductFlags &flags)
{
- for (const QString &fileName : qAsConst(project.fileNames)) {
+ for (const QString &fileName : std::as_const(project.fileNames)) {
QFile f (project.dirPath + QLatin1Char('/') + fileName);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "Ignoring failure to read" << f.fileName();
diff --git a/src/app/qbs-create-project/createproject.h b/src/app/qbs-create-project/createproject.h
index efc217b68..ac7d53e1a 100644
--- a/src/app/qbs-create-project/createproject.h
+++ b/src/app/qbs-create-project/createproject.h
@@ -42,7 +42,7 @@
#include <QtCore/qflags.h>
#include <QtCore/qhash.h>
-#include <QtCore/qregexp.h>
+#include <QtCore/qregularexpression.h>
#include <QtCore/qstringlist.h>
#include <memory>
@@ -83,8 +83,8 @@ private:
};
Project m_topLevelProject;
ProjectStructure m_projectStructure = ProjectStructure::Flat;
- QList<QRegExp> m_whiteList;
- QList<QRegExp> m_blackList;
+ QList<QRegularExpression> m_whiteList;
+ QList<QRegularExpression> m_blackList;
};
#endif // QBS_CREATEPROJECT_H
diff --git a/src/app/qbs-create-project/qbs-create-project.pro b/src/app/qbs-create-project/qbs-create-project.pro
deleted file mode 100644
index 1edb85214..000000000
--- a/src/app/qbs-create-project/qbs-create-project.pro
+++ /dev/null
@@ -1,9 +0,0 @@
-include(../app.pri)
-
-TARGET = qbs-create-project
-
-HEADERS += \
- createproject.h
-SOURCES += \
- createproject.cpp \
- create-project-main.cpp
diff --git a/src/app/qbs-create-project/qbs-create-project.qbs b/src/app/qbs-create-project/qbs-create-project.qbs
index b65f9177f..ae79001ee 100644
--- a/src/app/qbs-create-project/qbs-create-project.qbs
+++ b/src/app/qbs-create-project/qbs-create-project.qbs
@@ -1,5 +1,3 @@
-import qbs
-
QbsApp {
name: "qbs-create-project"
files: [
diff --git a/src/app/qbs-setup-android/CMakeLists.txt b/src/app/qbs-setup-android/CMakeLists.txt
new file mode 100644
index 000000000..46930318b
--- /dev/null
+++ b/src/app/qbs-setup-android/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(SOURCES
+ android-setup.cpp
+ android-setup.h
+ commandlineparser.cpp
+ commandlineparser.h
+ main.cpp
+ )
+
+add_qbs_app(qbs-setup-android
+ DEPENDS qbscore qbsconsolelogger
+ SOURCES ${SOURCES}
+ )
diff --git a/src/app/qbs-setup-android/android-setup.cpp b/src/app/qbs-setup-android/android-setup.cpp
index 029628419..fad24d018 100644
--- a/src/app/qbs-setup-android/android-setup.cpp
+++ b/src/app/qbs-setup-android/android-setup.cpp
@@ -45,6 +45,7 @@
#include <tools/profile.h>
#include <tools/settings.h>
#include <tools/version.h>
+#include <tools/qttools.h>
#include <QtCore/qbytearraylist.h>
#include <QtCore/qcoreapplication.h>
@@ -94,10 +95,10 @@ static QString mapArch(const QString &androidName)
}
struct QtAndroidInfo {
- bool isValid() const { return !arch.isEmpty(); }
+ bool isValid() const { return !archs.isEmpty(); }
QString qmakePath;
- QString arch;
+ QStringList archs;
QString platform;
};
@@ -111,19 +112,31 @@ static QtAndroidInfo getInfoForQtDir(const QString &qtDir)
if (!qdevicepri.open(QIODevice::ReadOnly))
return info;
while (!qdevicepri.atEnd()) {
+ // For Qt < 5.14 use DEFAULT_ANDROID_TARGET_ARCH (which is the abi) to compute
+ // the architecture
+ // DEFAULT_ANDROID_ABIS doesn't exit
+ // For Qt >= 5.14:
+ // DEFAULT_ANDROID_TARGET_ARCH doesn't exist, use DEFAULT_ANDROID_ABIS to compute
+ // the architectures
const QByteArray line = qdevicepri.readLine().simplified();
const bool isArchLine = line.startsWith("DEFAULT_ANDROID_TARGET_ARCH");
+ const bool isAbisLine = line.startsWith("DEFAULT_ANDROID_ABIS");
const bool isPlatformLine = line.startsWith("DEFAULT_ANDROID_PLATFORM");
- if (!isArchLine && !isPlatformLine)
+ if (!isArchLine && !isPlatformLine && !isAbisLine)
continue;
const QList<QByteArray> elems = line.split('=');
if (elems.size() != 2)
continue;
const QString rhs = QString::fromLatin1(elems.at(1).trimmed());
- if (isArchLine)
- info.arch = mapArch(rhs);
- else
+ if (isArchLine) {
+ info.archs << mapArch(rhs);
+ } else if (isAbisLine) {
+ const auto abis = rhs.split(QLatin1Char(' '));
+ for (const QString &abi: abis)
+ info.archs << mapArch(abi);
+ } else {
info.platform = rhs;
+ }
}
return info;
}
@@ -136,13 +149,16 @@ static QtInfoPerArch getQtAndroidInfo(const QString &qtSdkDir)
return archs;
QStringList qtDirs(qtSdkDir);
- QDirIterator dit(qtSdkDir, QStringList() << QStringLiteral("android_*"), QDir::Dirs);
+ const QStringList nameFilters{QStringLiteral("android_*"), QStringLiteral("android")};
+ QDirIterator dit(qtSdkDir, nameFilters, QDir::Dirs);
while (dit.hasNext())
qtDirs << dit.next();
- for (const auto &qtDir : qtDirs) {
+ for (const auto &qtDir : std::as_const(qtDirs)) {
const QtAndroidInfo info = getInfoForQtDir(qtDir);
- if (info.isValid())
- archs.insert(info.arch, info);
+ if (info.isValid()) {
+ for (const QString &arch: info.archs)
+ archs.insert(arch, info);
+ }
}
return archs;
}
@@ -224,8 +240,10 @@ static void setupNdk(qbs::Settings *settings, const QString &profileName, const
qmakeFilePaths << qtAndroidInfo.qmakePath;
platform = maximumPlatform(platform, qtAndroidInfo.platform);
}
- if (!qmakeFilePaths.empty())
+ if (!qmakeFilePaths.empty()) {
+ qmakeFilePaths.removeDuplicates();
mainProfile.setValue(qls("moduleProviders.Qt.qmakeFilePaths"), qmakeFilePaths);
+ }
if (!platform.isEmpty())
mainProfile.setValue(qls("Android.ndk.platform"), platform);
}
diff --git a/src/app/qbs-setup-android/commandlineparser.cpp b/src/app/qbs-setup-android/commandlineparser.cpp
index 8beeeb601..c4ff09947 100644
--- a/src/app/qbs-setup-android/commandlineparser.cpp
+++ b/src/app/qbs-setup-android/commandlineparser.cpp
@@ -53,6 +53,7 @@ static QString settingsDirOption() { return QStringLiteral("--settings-dir"); }
static QString sdkDirOption() { return QStringLiteral("--sdk-dir"); }
static QString ndkDirOption() { return QStringLiteral("--ndk-dir"); }
static QString qtSdkDirOption() { return QStringLiteral("--qt-dir"); }
+static QString systemOption() { return QStringLiteral("--system"); }
void CommandLineParser::parse(const QStringList &commandLine)
{
@@ -77,6 +78,8 @@ void CommandLineParser::parse(const QStringList &commandLine)
m_helpRequested = true;
else if (arg == settingsDirOption())
assignOptionArgument(settingsDirOption(), m_settingsDir);
+ else if (arg == systemOption())
+ m_settingsScope = qbs::Settings::SystemScope;
else if (arg == sdkDirOption())
assignOptionArgument(sdkDirOption(), m_sdkDir);
else if (arg == ndkDirOption())
@@ -116,9 +119,9 @@ QString CommandLineParser::usageString() const
{
QString s = Tr::tr("This tool creates qbs profiles from Android SDK and NDK installations.\n");
s += Tr::tr("Usage:\n");
- s += Tr::tr(" %1 [%2 <settings dir>] [%3 <NDK dir>] [%4 <SDK dir>] [%5 <Qt dir>] "
+ s += Tr::tr(" %1 [%2 <settings dir>] [%6] [%3 <NDK dir>] [%4 <SDK dir>] [%5 <Qt dir>] "
"<profile name>\n")
- .arg(m_command, settingsDirOption(), ndkDirOption(), sdkDirOption(), qtSdkDirOption());
+ .arg(m_command, settingsDirOption(), ndkDirOption(), sdkDirOption(), qtSdkDirOption(), systemOption());
s += Tr::tr(" %1 %2|%3\n").arg(m_command, helpOptionShort(), helpOptionLong());
s += Tr::tr("If an NDK path is given, the profile will be suitable for use with Android "
"projects that contain native C/C++ code.\n");
diff --git a/src/app/qbs-setup-android/commandlineparser.h b/src/app/qbs-setup-android/commandlineparser.h
index 3ce3eeb40..47f424634 100644
--- a/src/app/qbs-setup-android/commandlineparser.h
+++ b/src/app/qbs-setup-android/commandlineparser.h
@@ -39,6 +39,8 @@
#ifndef QBS_SETUP_ANDROID_COMMANDLINEPARSER_H
#define QBS_SETUP_ANDROID_COMMANDLINEPARSER_H
+#include <tools/settings.h>
+
#include <QtCore/qstringlist.h>
class CommandLineParser
@@ -55,6 +57,7 @@ public:
QString qtSdkDir() const { return m_qtSdkDir; }
QString profileName() const { return m_profileName; }
QString settingsDir() const { return m_settingsDir; }
+ qbs::Settings::Scope settingsScope() const { return m_settingsScope; }
QString usageString() const;
@@ -64,6 +67,7 @@ private:
[[noreturn]] void complainAboutExtraArguments();
bool m_helpRequested = false;
+ qbs::Settings::Scope m_settingsScope = qbs::Settings::UserScope;
QString m_sdkDir;
QString m_ndkDir;
QString m_qtSdkDir;
diff --git a/src/app/qbs-setup-android/main.cpp b/src/app/qbs-setup-android/main.cpp
index 8822751f4..18e5dbe41 100644
--- a/src/app/qbs-setup-android/main.cpp
+++ b/src/app/qbs-setup-android/main.cpp
@@ -60,6 +60,7 @@ int main(int argc, char *argv[])
return EXIT_SUCCESS;
}
qbs::Settings settings(clParser.settingsDir());
+ settings.setScopeForWriting(clParser.settingsScope());
setupAndroid(&settings, clParser.profileName(), clParser.sdkDir(), clParser.ndkDir(),
clParser.qtSdkDir());
} catch (const qbs::ErrorInfo &e) {
diff --git a/src/app/qbs-setup-android/qbs-setup-android.pro b/src/app/qbs-setup-android/qbs-setup-android.pro
deleted file mode 100644
index b5e57761d..000000000
--- a/src/app/qbs-setup-android/qbs-setup-android.pro
+++ /dev/null
@@ -1,12 +0,0 @@
-include(../app.pri)
-
-TARGET = qbs-setup-android
-
-SOURCES += \
- android-setup.cpp \
- commandlineparser.cpp \
- main.cpp
-
-HEADERS += \
- android-setup.h \
- commandlineparser.h
diff --git a/src/app/qbs-setup-android/qbs-setup-android.qbs b/src/app/qbs-setup-android/qbs-setup-android.qbs
index edadd5dd1..b18e152b0 100644
--- a/src/app/qbs-setup-android/qbs-setup-android.qbs
+++ b/src/app/qbs-setup-android/qbs-setup-android.qbs
@@ -1,5 +1,3 @@
-import qbs
-
QbsApp {
name: "qbs-setup-android"
files: [
@@ -12,6 +10,11 @@ QbsApp {
Group {
name: "MinGW specific files"
condition: qbs.toolchain.contains("mingw")
- files: ["qbs-setup-android.exe.manifest", "qbs-setup-android.rc"]
+ files: "qbs-setup-android.rc"
+ Group {
+ name: "qbs-setup-android manifest"
+ files: "qbs-setup-android.exe.manifest"
+ fileTags: [] // the manifest is referenced by the rc file
+ }
}
}
diff --git a/src/app/qbs-setup-qt/CMakeLists.txt b/src/app/qbs-setup-qt/CMakeLists.txt
new file mode 100644
index 000000000..a91ab1b26
--- /dev/null
+++ b/src/app/qbs-setup-qt/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(SOURCES
+ commandlineparser.cpp
+ commandlineparser.h
+ main.cpp
+ setupqt.cpp
+ setupqt.h
+ )
+
+add_qbs_app(qbs-setup-qt
+ DEPENDS qbscore qbsconsolelogger
+ SOURCES ${SOURCES}
+ )
diff --git a/src/app/qbs-setup-qt/main.cpp b/src/app/qbs-setup-qt/main.cpp
index 94185c4d7..b739bff2d 100644
--- a/src/app/qbs-setup-qt/main.cpp
+++ b/src/app/qbs-setup-qt/main.cpp
@@ -41,6 +41,7 @@
#include "commandlineparser.h"
#include <logging/translator.h>
+#include <tools/qttools.h>
#include <tools/settings.h>
#include <QtCore/qcoreapplication.h>
@@ -81,7 +82,7 @@ int main(int argc, char *argv[])
QString profileName = QLatin1String("qt-") + qtEnvironment.qtVersion.toString();
if (SetupQt::checkIfMoreThanOneQtWithTheSameVersion(qtEnvironment.qtVersion, qtEnvironments)) {
QStringList prefixPathParts = QFileInfo(qtEnvironment.qmakeFilePath).path()
- .split(QLatin1Char('/'), QString::SkipEmptyParts);
+ .split(QLatin1Char('/'), Qt::SkipEmptyParts);
if (!prefixPathParts.empty())
profileName += QLatin1String("-") + prefixPathParts.last();
}
diff --git a/src/app/qbs-setup-qt/qbs-setup-qt.pro b/src/app/qbs-setup-qt/qbs-setup-qt.pro
deleted file mode 100644
index a5694d6b7..000000000
--- a/src/app/qbs-setup-qt/qbs-setup-qt.pro
+++ /dev/null
@@ -1,16 +0,0 @@
-include(../app.pri)
-
-TARGET = qbs-setup-qt
-
-SOURCES += \
- commandlineparser.cpp \
- main.cpp \
- setupqt.cpp
-
-HEADERS += \
- commandlineparser.h \
- setupqt.h
-
-mingw {
- RC_FILE = qbs-setup-qt.rc
-}
diff --git a/src/app/qbs-setup-qt/qbs-setup-qt.qbs b/src/app/qbs-setup-qt/qbs-setup-qt.qbs
index f4cd9d6b8..3be090155 100644
--- a/src/app/qbs-setup-qt/qbs-setup-qt.qbs
+++ b/src/app/qbs-setup-qt/qbs-setup-qt.qbs
@@ -1,5 +1,3 @@
-import qbs 1.0
-
QbsApp {
name: "qbs-setup-qt"
files: [
@@ -12,7 +10,12 @@ QbsApp {
Group {
name: "MinGW specific files"
condition: qbs.toolchain.contains("mingw")
- files: ["qbs-setup-qt.exe.manifest", "qbs-setup-qt.rc"]
+ files: "qbs-setup-qt.rc"
+ Group {
+ name: "qbs-setup-qt manifest"
+ files: "qbs-setup-qt.exe.manifest"
+ fileTags: [] // the manifest is referenced by the rc file
+ }
}
}
diff --git a/src/app/qbs-setup-qt/setupqt.cpp b/src/app/qbs-setup-qt/setupqt.cpp
index 947cbc5fc..bac766482 100644
--- a/src/app/qbs-setup-qt/setupqt.cpp
+++ b/src/app/qbs-setup-qt/setupqt.cpp
@@ -47,6 +47,7 @@
#include <tools/set.h>
#include <tools/settings.h>
#include <tools/stlutils.h>
+#include <tools/toolchains.h>
#include <tools/version.h>
#include <QtCore/qbytearraymatcher.h>
@@ -64,6 +65,7 @@ using Internal::none_of;
using Internal::contains;
using Internal::HostOsInfo;
using Internal::Tr;
+using Internal::rangeTo;
static QStringList qmakeExecutableNames()
{
@@ -154,7 +156,7 @@ static QString archFromDirName(const QString &dir)
const std::string dirString = dir.toStdString();
if (!std::regex_match(dirString, match, regexp))
return {};
- const QString arch = QString::fromStdString(match[1]);
+ QString arch = QString::fromStdString(match[1]);
if (arch == QLatin1String("32"))
return QStringLiteral("x86");
if (arch == QLatin1String("64"))
@@ -170,7 +172,7 @@ static QString platformFromDirName(const QString &dir)
return QStringLiteral("android");
if (dir == QLatin1String("Boot2Qt"))
return QStringLiteral("linux");
- return QString::fromStdString(HostOsInfo::hostOSIdentifier());
+ return HostOsInfo::hostOSIdentifier();
}
QtEnvironment SetupQt::fetchEnvironment(const QString &qmakePath)
@@ -194,9 +196,9 @@ QtEnvironment SetupQt::fetchEnvironment(const QString &qmakePath)
static bool isToolchainProfile(const Profile &profile)
{
- const auto actual = Internal::Set<QString>::fromList(
+ const auto actual = rangeTo<Internal::Set<QString>>(
profile.allKeys(Profile::KeySelectionRecursive));
- Internal::Set<QString> expected = Internal::Set<QString> { QStringLiteral("qbs.toolchain") };
+ Internal::Set<QString> expected{ QStringLiteral("qbs.toolchainType") };
if (HostOsInfo::isMacosHost())
expected.insert(QStringLiteral("qbs.targetPlatform")); // match only Xcode profiles
return Internal::Set<QString>(actual).unite(expected) == actual;
@@ -212,7 +214,7 @@ static bool isQtProfile(const Profile &profile)
// For Profiles created with setup-qt < 5.13.
const QStringList searchPaths
= profile.value(QStringLiteral("preferences.qbsSearchPaths")).toStringList();
- return std::any_of(searchPaths.cbegin(), searchPaths.cend(), [] (const QString &path) {
+ return Internal::any_of(searchPaths, [] (const QString &path) {
return QFileInfo(path + QStringLiteral("/modules/Qt")).isDir();
});
}
@@ -230,9 +232,14 @@ static Match compatibility(const QtEnvironment &env, const Profile &toolchainPro
{
Match match = MatchFull;
- const auto toolchainNames = Internal::Set<QString>::fromList(
- toolchainProfile.value(QStringLiteral("qbs.toolchain")).toStringList());
- const auto qtToolchainNames = Internal::Set<QString>::fromList(env.qbsToolchain);
+ const auto toolchainType =
+ toolchainProfile.value(QStringLiteral("qbs.toolchainType")).toString();
+ const auto toolchain = !toolchainType.isEmpty()
+ ? canonicalToolchain(toolchainType)
+ : toolchainProfile.value(QStringLiteral("qbs.toolchain")).toStringList();
+
+ const auto toolchainNames = rangeTo<Internal::Set<QString>>(toolchain);
+ const auto qtToolchainNames = rangeTo<Internal::Set<QString>>(env.qbsToolchain);
if (areProfilePropertiesIncompatible(toolchainNames, qtToolchainNames)) {
auto intersection = toolchainNames;
intersection.intersect(qtToolchainNames);
@@ -292,15 +299,12 @@ QString profileNameWithoutHostArch(const QString &profileName)
// then we drop the crosscompiling toolchain MSVC2017-x86_x64.
static void compressMsvcProfiles(QStringList &profiles)
{
- auto it = std::remove_if(profiles.begin(), profiles.end(),
- [&profiles] (const QString &profileName) {
+ Internal::removeIf(profiles, [&profiles] (const QString &profileName) {
int idx = profileName.indexOf(QLatin1Char('_'));
if (idx == -1)
return false;
return contains(profiles, profileNameWithoutHostArch(profileName));
});
- if (it != profiles.end())
- profiles.erase(it, profiles.end());
}
void SetupQt::saveToQbsSettings(const QString &qtVersionName,
@@ -310,7 +314,7 @@ void SetupQt::saveToQbsSettings(const QString &qtVersionName,
const QString cleanQtVersionName = Profile::cleanName(qtVersionName);
QString msg = QCoreApplication::translate("SetupQt", "Creating profile '%1'.")
.arg(cleanQtVersionName);
- printf("%s\n", qPrintable(msg));
+ std::printf("%s\n", qPrintable(msg));
Profile profile(cleanQtVersionName, settings);
profile.removeProfile();
diff --git a/src/app/qbs-setup-toolchains/CMakeLists.txt b/src/app/qbs-setup-toolchains/CMakeLists.txt
new file mode 100644
index 000000000..64347cd6c
--- /dev/null
+++ b/src/app/qbs-setup-toolchains/CMakeLists.txt
@@ -0,0 +1,32 @@
+set(SOURCES
+ clangclprobe.cpp
+ clangclprobe.h
+ commandlineparser.cpp
+ commandlineparser.h
+ cosmicprobe.cpp
+ cosmicprobe.h
+ dmcprobe.cpp
+ dmcprobe.h
+ gccprobe.cpp
+ gccprobe.h
+ iarewprobe.cpp
+ iarewprobe.h
+ keilprobe.cpp
+ keilprobe.h
+ main.cpp
+ msvcprobe.cpp
+ msvcprobe.h
+ probe.cpp
+ probe.h
+ sdccprobe.cpp
+ sdccprobe.h
+ watcomprobe.cpp
+ watcomprobe.h
+ xcodeprobe.cpp
+ xcodeprobe.h
+ )
+
+add_qbs_app(qbs-setup-toolchains
+ DEPENDS qbscore qbsconsolelogger
+ SOURCES ${SOURCES}
+ )
diff --git a/src/app/qbs-setup-toolchains/clangclprobe.cpp b/src/app/qbs-setup-toolchains/clangclprobe.cpp
index 4cb38b774..272f953fc 100644
--- a/src/app/qbs-setup-toolchains/clangclprobe.cpp
+++ b/src/app/qbs-setup-toolchains/clangclprobe.cpp
@@ -44,27 +44,26 @@
#include "../shared/logging/consolelogger.h"
#include <logging/translator.h>
+#include <tools/clangclinfo.h>
#include <tools/hostosinfo.h>
+#include <tools/msvcinfo.h>
#include <tools/profile.h>
#include <tools/qttools.h>
#include <tools/settings.h>
+#include <tools/stlutils.h>
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
using qbs::Settings;
using qbs::Profile;
+using qbs::Internal::ClangClInfo;
using qbs::Internal::HostOsInfo;
using qbs::Internal::Tr;
namespace {
-QString getToolchainInstallPath(const QFileInfo &compiler)
-{
- return compiler.path(); // 1 level up
-}
-
Profile createProfileHelper(
Settings *settings,
const QString &profileName,
@@ -75,9 +74,7 @@ Profile createProfileHelper(
Profile profile(profileName, settings);
profile.removeProfile();
profile.setValue(QStringLiteral("qbs.architecture"), architecture);
- profile.setValue(
- QStringLiteral("qbs.toolchain"),
- QStringList{QStringLiteral("clang-cl"), QStringLiteral("msvc")});
+ profile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("clang-cl"));
profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), toolchainInstallPath);
profile.setValue(QStringLiteral("cpp.vcvarsallPath"), vcvarsallPath);
qbsInfo() << Tr::tr("Profile '%1' created for '%2'.")
@@ -85,75 +82,13 @@ Profile createProfileHelper(
return profile;
}
-std::vector<MSVCInstallInfo> compatibleMsvcs()
-{
- auto msvcs = installedMSVCs();
- auto filter = [](const MSVCInstallInfo &info)
- {
- const auto versions = info.version.split(QLatin1Char('.'));
- if (versions.empty())
- return true;
- bool ok = false;
- const int major = versions.at(0).toInt(&ok);
- return !(ok && major >= 15); // support MSVC2017 and above
- };
- const auto it = std::remove_if(msvcs.begin(), msvcs.end(), filter);
- msvcs.erase(it, msvcs.end());
- for (const auto &msvc: msvcs) {
- auto vcvarsallPath = msvc.findVcvarsallBat();
- if (vcvarsallPath.isEmpty())
- continue;
- }
- return msvcs;
-}
-
-QString findCompatibleVcsarsallBat()
-{
- for (const auto &msvc: compatibleMsvcs()) {
- const auto vcvarsallPath = msvc.findVcvarsallBat();
- if (!vcvarsallPath.isEmpty())
- return vcvarsallPath;
- }
- return {};
-}
-
-QString wow6432Key()
-{
-#ifdef Q_OS_WIN64
- return QStringLiteral("\\Wow6432Node");
-#else
- return {};
-#endif
-}
-
QString findClangCl()
{
const auto compilerName = HostOsInfo::appendExecutableSuffix(QStringLiteral("clang-cl"));
- const auto compilerFromPath = findExecutable(compilerName);
+ auto compilerFromPath = findExecutable(compilerName);
if (!compilerFromPath.isEmpty())
return compilerFromPath;
- const QSettings registry(
- QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE%1\\LLVM\\LLVM").arg(wow6432Key()),
- QSettings::NativeFormat);
- const auto key = QStringLiteral(".");
- if (registry.contains(key)) {
- const auto compilerPath = QDir::fromNativeSeparators(registry.value(key).toString())
- + QStringLiteral("/bin/") + compilerName;
- if (QFileInfo(compilerPath).exists())
- return compilerPath;
- }
-
- // this branch can be useful in case user had two LLVM installations (e.g. 32bit & 64bit)
- // but uninstalled one - in that case, registry will be empty
- static const char * const envVarCandidates[] = {"ProgramFiles", "ProgramFiles(x86)"};
- for (const auto &envVar : envVarCandidates) {
- const auto value
- = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv(envVar)));
- const auto compilerPath = value + QStringLiteral("/LLVM/bin/") + compilerName;
- if (QFileInfo(compilerPath).exists())
- return compilerPath;
- }
return {};
}
@@ -162,18 +97,13 @@ QString findClangCl()
void createClangClProfile(const QFileInfo &compiler, Settings *settings,
const QString &profileName)
{
- const auto compilerName = QStringLiteral("clang-cl");
- const auto vcvarsallPath = findCompatibleVcsarsallBat();
- if (vcvarsallPath.isEmpty()) {
- qbsWarning()
- << Tr::tr("%1 requires installed Visual Studio 2017 or newer, but none was found.")
- .arg(compilerName);
+ const auto clangCl = ClangClInfo::fromCompilerFilePath(
+ compiler.filePath(), ConsoleLogger::instance());
+ if (clangCl.isEmpty())
return;
- }
-
- const auto toolchainInstallPath = getToolchainInstallPath(compiler);
- const auto hostArch = QString::fromStdString(HostOsInfo::hostOSArchitecture());
- createProfileHelper(settings, profileName, toolchainInstallPath, vcvarsallPath, hostArch);
+ const auto hostArch = HostOsInfo::hostOSArchitecture();
+ createProfileHelper(
+ settings, profileName, clangCl.toolchainInstallPath, clangCl.vcvarsallPath, hostArch);
}
/*!
@@ -184,29 +114,30 @@ void clangClProbe(Settings *settings, std::vector<Profile> &profiles)
{
const auto compilerName = QStringLiteral("clang-cl");
qbsInfo() << Tr::tr("Trying to detect %1...").arg(compilerName);
- const QString compilerFilePath = findClangCl();
- if (compilerFilePath.isEmpty()) {
+ const auto clangCls = ClangClInfo::installedCompilers(
+ {findClangCl()}, ConsoleLogger::instance());
+ if (clangCls.empty()) {
qbsInfo() << Tr::tr("%1 was not found.").arg(compilerName);
return;
}
- const QFileInfo compiler(compilerFilePath);
- const auto vcvarsallPath = findCompatibleVcsarsallBat();
- if (vcvarsallPath.isEmpty()) {
- qbsWarning()
- << Tr::tr("%1 requires installed Visual Studio 2017 or newer, but none was found.")
- .arg(compilerName);
- return;
- }
const QString architectures[] = {
QStringLiteral("x86_64"),
QStringLiteral("x86")
};
- const auto toolchainInstallPath = getToolchainInstallPath(compiler);
- for (const auto &arch: architectures) {
- const auto profileName = QStringLiteral("clang-cl-%1").arg(arch);
- auto profile = createProfileHelper(
- settings, profileName, toolchainInstallPath, vcvarsallPath, arch);
- profiles.push_back(std::move(profile));
+ for (size_t index = 0; index < clangCls.size(); ++index) {
+ const auto &clangCl = clangCls[index];
+ const QString suffix = index == 0 ? QString() : QStringLiteral("-%1").arg(index);
+ qbs::Internal::transform(
+ architectures, profiles, [settings, clangCl, suffix](const auto &arch) {
+ const auto profileName = QStringLiteral("clang-cl") + suffix
+ + QStringLiteral("-%1").arg(arch);
+ return createProfileHelper(
+ settings,
+ profileName,
+ clangCl.toolchainInstallPath,
+ clangCl.vcvarsallPath,
+ arch);
+ });
}
}
diff --git a/src/app/qbs-setup-toolchains/cosmicprobe.cpp b/src/app/qbs-setup-toolchains/cosmicprobe.cpp
new file mode 100644
index 000000000..82b7193bc
--- /dev/null
+++ b/src/app/qbs-setup-toolchains/cosmicprobe.cpp
@@ -0,0 +1,179 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 Denis Shienkov <denis.shienkov@gmail.com>
+** 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 "probe.h"
+#include "cosmicprobe.h"
+
+#include "../shared/logging/consolelogger.h"
+
+#include <logging/translator.h>
+
+#include <tools/hostosinfo.h>
+#include <tools/profile.h>
+
+#include <QtCore/qprocess.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qsettings.h>
+#include <QtCore/qstandardpaths.h>
+
+using namespace qbs;
+using Internal::Tr;
+using Internal::HostOsInfo;
+
+static QStringList knownCosmicCompilerNames()
+{
+ return {QStringLiteral("cxcorm"), QStringLiteral("cxstm8"),
+ QStringLiteral("cx6808"), QStringLiteral("cx6812"),
+ QStringLiteral("cx332")};
+}
+
+static QString guessCosmicArchitecture(const QFileInfo &compiler)
+{
+ const auto baseName = compiler.baseName();
+ if (baseName == QLatin1String("cxcorm"))
+ return QStringLiteral("arm");
+ if (baseName == QLatin1String("cxstm8"))
+ return QStringLiteral("stm8");
+ if (baseName == QLatin1String("cx6808"))
+ return QStringLiteral("hcs8");
+ if (baseName == QLatin1String("cx6812"))
+ return QStringLiteral("hcs12");
+ if (baseName == QLatin1String("cx332"))
+ return QStringLiteral("m68k");
+ return {};
+}
+
+static Profile createCosmicProfileHelper(const ToolchainInstallInfo &info,
+ Settings *settings,
+ QString profileName = QString())
+{
+ const QFileInfo compiler = info.compilerPath;
+ const QString architecture = guessCosmicArchitecture(compiler);
+
+ // In case the profile is auto-detected.
+ if (profileName.isEmpty()) {
+ if (!info.compilerVersion.isValid()) {
+ profileName = QStringLiteral("cosmic-unknown-%1").arg(architecture);
+ } else {
+ const QString version = info.compilerVersion.toString(QLatin1Char('_'),
+ QLatin1Char('_'));
+ profileName = QStringLiteral("cosmic-%1-%2").arg(
+ version, architecture);
+ }
+ }
+
+ Profile profile(profileName, settings);
+ profile.setValue(QLatin1String("cpp.toolchainInstallPath"), compiler.absolutePath());
+ profile.setValue(QLatin1String("qbs.toolchainType"), QLatin1String("cosmic"));
+ if (!architecture.isEmpty())
+ profile.setValue(QLatin1String("qbs.architecture"), architecture);
+
+ qbsInfo() << Tr::tr("Profile '%1' created for '%2'.").arg(
+ profile.name(), compiler.absoluteFilePath());
+ return profile;
+}
+
+static Version dumpCosmicCompilerVersion(const QFileInfo &compiler)
+{
+ QProcess p;
+ QStringList args;
+ p.start(compiler.absoluteFilePath(), args);
+ p.waitForFinished(3000);
+ const auto es = p.exitStatus();
+ if (es != QProcess::NormalExit) {
+ const QByteArray out = p.readAll();
+ qbsWarning() << Tr::tr("Compiler dumping failed:\n%1")
+ .arg(QString::fromUtf8(out));
+ return Version{};
+ }
+
+ const QByteArray output = p.readAllStandardError();
+ const QRegularExpression re(QLatin1String("^COSMIC.+V(\\d+)\\.?(\\d+)\\.?(\\*|\\d+)?"));
+ const QRegularExpressionMatch match = re.match(QString::fromLatin1(output));
+ if (!match.hasMatch())
+ return Version{};
+
+ const auto major = match.captured(1).toInt();
+ const auto minor = match.captured(2).toInt();
+ const auto patch = match.captured(3).toInt();
+ return Version{major, minor, patch};
+}
+
+static std::vector<ToolchainInstallInfo> installedCosmicsFromPath()
+{
+ std::vector<ToolchainInstallInfo> infos;
+ const auto compilerNames = knownCosmicCompilerNames();
+ for (const QString &compilerName : compilerNames) {
+ const QFileInfo cosmicPath(
+ findExecutable(
+ HostOsInfo::appendExecutableSuffix(compilerName)));
+ if (!cosmicPath.exists())
+ continue;
+ const Version version = dumpCosmicCompilerVersion(cosmicPath);
+ infos.push_back({cosmicPath, version});
+ }
+ std::sort(infos.begin(), infos.end());
+ return infos;
+}
+
+bool isCosmicCompiler(const QString &compilerName)
+{
+ return Internal::any_of(knownCosmicCompilerNames(), [compilerName](const QString &knownName) {
+ return compilerName.contains(knownName);
+ });
+}
+
+void createCosmicProfile(const QFileInfo &compiler, Settings *settings,
+ QString profileName)
+{
+ const ToolchainInstallInfo info = {compiler, Version{}};
+ createCosmicProfileHelper(info, settings, std::move(profileName));
+}
+
+void cosmicProbe(Settings *settings, std::vector<Profile> &profiles)
+{
+ qbsInfo() << Tr::tr("Trying to detect COSMIC toolchains...");
+
+ const std::vector<ToolchainInstallInfo> allInfos = installedCosmicsFromPath();
+ qbs::Internal::transform(allInfos, profiles, [settings](const auto &info) {
+ return createCosmicProfileHelper(info, settings); });
+
+ if (allInfos.empty())
+ qbsInfo() << Tr::tr("No COSMIC toolchains found.");
+}
diff --git a/src/app/qbs-setup-toolchains/cosmicprobe.h b/src/app/qbs-setup-toolchains/cosmicprobe.h
new file mode 100644
index 000000000..9b7b4bb10
--- /dev/null
+++ b/src/app/qbs-setup-toolchains/cosmicprobe.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 Denis Shienkov <denis.shienkov@gmail.com>
+** 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_SETUPTOOLCHAINS_COSMICPROBE_H
+#define QBS_SETUPTOOLCHAINS_COSMICPROBE_H
+
+#include <QtCore/qlist.h>
+
+QT_BEGIN_NAMESPACE
+class QFileInfo;
+QT_END_NAMESPACE
+
+namespace qbs {
+class Profile;
+class Settings;
+}
+
+bool isCosmicCompiler(const QString &compilerName);
+
+void createCosmicProfile(const QFileInfo &compiler, qbs::Settings *settings,
+ QString profileName);
+
+void cosmicProbe(qbs::Settings *settings, std::vector<qbs::Profile> &profiles);
+
+#endif // Header guard
diff --git a/src/app/qbs-setup-toolchains/dmcprobe.cpp b/src/app/qbs-setup-toolchains/dmcprobe.cpp
new file mode 100644
index 000000000..097b1caed
--- /dev/null
+++ b/src/app/qbs-setup-toolchains/dmcprobe.cpp
@@ -0,0 +1,264 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 Denis Shienkov <denis.shienkov@gmail.com>
+** 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 "probe.h"
+#include "dmcprobe.h"
+
+#include "../shared/logging/consolelogger.h"
+
+#include <logging/translator.h>
+
+#include <tools/hostosinfo.h>
+#include <tools/profile.h>
+
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qsettings.h>
+#include <QtCore/qstandardpaths.h>
+
+#include <optional>
+
+using namespace qbs;
+using Internal::Tr;
+using Internal::HostOsInfo;
+
+namespace {
+struct Target {
+ QString platform;
+ QString architecture;
+ QString extender;
+};
+}
+
+static QStringList knownDmcCompilerNames()
+{
+ return {QStringLiteral("dmc")};
+}
+
+static QStringList dumpOutput(const QFileInfo &compiler, const QStringList &flags,
+ const QStringList &keys)
+{
+ const QString filePath = QDir(QDir::tempPath()).absoluteFilePath(
+ QLatin1String("dmc-dump.c"));
+ QFile fakeIn(filePath);
+ if (!fakeIn.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
+ qbsWarning() << Tr::tr("Unable to open temporary file %1 for output: %2")
+ .arg(fakeIn.fileName(), fakeIn.errorString());
+ return {};
+ }
+ fakeIn.write("#define VALUE_TO_STRING(x) #x\n");
+ fakeIn.write("#define VALUE(x) VALUE_TO_STRING(x)\n");
+ fakeIn.write("#define VAR_NAME_VALUE(var) \"#define \" #var\" \"VALUE(var)\n");
+ for (const auto &key : keys) {
+ const auto content = key.toLatin1();
+ fakeIn.write("#if defined(" + content + ")\n");
+ fakeIn.write("#pragma message (VAR_NAME_VALUE(" + content + "))\n");
+ fakeIn.write("#endif\n");
+ }
+ fakeIn.close();
+
+ QStringList args = {QStringLiteral("-e")};
+ args.reserve(args.size() + int(flags.size()));
+ std::copy(flags.cbegin(), flags.cend(), std::back_inserter(args));
+ args.push_back(QDir::toNativeSeparators(filePath));
+
+ QProcess p;
+ p.start(compiler.absoluteFilePath(), args);
+ p.waitForFinished(3000);
+ fakeIn.remove();
+ static QRegularExpression re(QLatin1String("\\r?\\n"));
+ return QString::fromUtf8(p.readAllStandardOutput()).split(re);
+}
+
+static std::optional<Target> dumpDmcTarget(const QFileInfo &compiler, const QStringList &flags)
+{
+ const QStringList keys = {
+ QStringLiteral("DOS16RM"),
+ QStringLiteral("DOS386"),
+ QStringLiteral("MSDOS"),
+ QStringLiteral("__NT__"),
+ QStringLiteral("_WINDOWS"),
+ };
+ const auto macros = dumpMacros([&compiler, &flags, &keys]() {
+ return dumpOutput(compiler, flags, keys); });
+
+ if (macros.contains(QLatin1String("__NT__"))) {
+ return Target{QLatin1String("windows"), QLatin1String("x86"), QLatin1String("")};
+ } else if (macros.contains(QLatin1String("_WINDOWS"))) {
+ return Target{QLatin1String("windows"), QLatin1String("x86_16"), QLatin1String("")};
+ } else if (macros.contains(QLatin1String("DOS386"))) {
+ if (flags.contains(QLatin1String("-mx")))
+ return Target{QLatin1String("dos"), QLatin1String("x86"), QLatin1String("dosx")};
+ else if (flags.contains(QLatin1String("-mp")))
+ return Target{QLatin1String("dos"), QLatin1String("x86"), QLatin1String("dosp")};
+ } else if (macros.contains(QLatin1String("DOS16RM"))) {
+ if (flags.contains(QLatin1String("-mz")))
+ return Target{QLatin1String("dos"), QLatin1String("x86_16"), QLatin1String("dosz")};
+ else if (flags.contains(QLatin1String("-mr")))
+ return Target{QLatin1String("dos"), QLatin1String("x86_16"), QLatin1String("dosr")};
+ } else if (macros.contains(QLatin1String("MSDOS"))) {
+ return Target{QLatin1String("dos"), QLatin1String("x86_16"), QLatin1String("")};
+ }
+
+ return {};
+}
+
+static std::vector<Profile> createDmcProfileHelper(const ToolchainInstallInfo &info,
+ Settings *settings,
+ QStringView profileName = {})
+{
+ const QFileInfo compiler = info.compilerPath;
+ std::vector<Profile> profiles;
+
+ const QVector<QStringList> probes = {
+ { QStringLiteral("-mn"), QStringLiteral("-WA") }, // Windows NT 32 bit.
+ { QStringLiteral("-ml"), QStringLiteral("-WA") }, // Windows 3.x 16 bit.
+ { QStringLiteral("-mx") }, // DOS with DOSX extender 32 bit.
+ { QStringLiteral("-mp") }, // DOS with Phar Lap extender 32 bit.
+ { QStringLiteral("-mr") }, // DOS with Rational DOS Extender 16 bit.
+ { QStringLiteral("-mz") }, // DOS with ZPM DOS Extender 16 bit.
+ { QStringLiteral("-mc") }, // DOS 16 bit.
+ };
+
+ for (const auto &flags : probes) {
+ const auto target = dumpDmcTarget(compiler, flags);
+ if (!target)
+ continue;
+
+ QString fullProfilename;
+ QString platform = target->extender.isEmpty() ? target->platform : target->extender;
+ if (profileName.isEmpty()) {
+ // Create a full profile name in case we is in auto-detecting mode.
+ if (!info.compilerVersion.isValid()) {
+ fullProfilename = QStringLiteral("dmc-unknown-%1-%2")
+ .arg(platform, target->architecture);
+ } else {
+ const QString version = info.compilerVersion.toString(QLatin1Char('_'),
+ QLatin1Char('_'));
+ fullProfilename = QStringLiteral("dmc-%1-%2-%3")
+ .arg(version, platform, target->architecture);
+ }
+ } else {
+ // Append the detected actual architecture name to the proposed profile name.
+ fullProfilename = QStringLiteral("%1-%2-%3")
+ .arg(profileName, platform, target->architecture);
+ }
+
+ Profile profile(fullProfilename, settings);
+ profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), compiler.absolutePath());
+ profile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("dmc"));
+ profile.setValue(QStringLiteral("qbs.architecture"), target->architecture);
+ profile.setValue(QStringLiteral("qbs.targetPlatform"), target->platform);
+ if (!target->extender.isEmpty())
+ profile.setValue(QStringLiteral("cpp.extenderName"), target->extender);
+
+ qbsInfo() << Tr::tr("Profile '%1' created for '%2'.")
+ .arg(profile.name(), compiler.absoluteFilePath());
+
+ profiles.push_back(std::move(profile));
+ }
+
+ return profiles;
+}
+
+static Version dumpDmcVersion(const QFileInfo &compiler)
+{
+ const QStringList keys = {QStringLiteral("__DMC__")};
+ const auto macros = dumpMacros([&compiler, keys]() {
+ return dumpOutput(compiler, {}, keys); });
+ for (const auto &macro : macros) {
+ if (!macro.startsWith(QLatin1String("0x")))
+ continue;
+ const int verCode = macro.mid(2).toInt();
+ return Version{(verCode / 100), (verCode % 100), 0};
+ }
+ qbsWarning() << Tr::tr("No __DMC__ token was found in the compiler dump");
+ return Version{};
+}
+
+static std::vector<ToolchainInstallInfo> installedDmcsFromPath()
+{
+ std::vector<ToolchainInstallInfo> infos;
+ const auto compilerNames = knownDmcCompilerNames();
+ for (const QString &compilerName : compilerNames) {
+ const QFileInfo dmcPath(
+ findExecutable(
+ HostOsInfo::appendExecutableSuffix(compilerName)));
+ if (!dmcPath.exists())
+ continue;
+ const Version version = dumpDmcVersion(dmcPath);
+ infos.push_back({dmcPath, version});
+ }
+ std::sort(infos.begin(), infos.end());
+ return infos;
+}
+
+bool isDmcCompiler(const QString &compilerName)
+{
+ return Internal::any_of(knownDmcCompilerNames(),
+ [compilerName](const QString &knownName) {
+ return compilerName.contains(knownName);
+ });
+}
+
+void createDmcProfile(const QFileInfo &compiler, Settings *settings,
+ QStringView profileName)
+{
+ const ToolchainInstallInfo info = {compiler, Version{}};
+ createDmcProfileHelper(info, settings, profileName);
+}
+
+void dmcProbe(Settings *settings, std::vector<Profile> &profiles)
+{
+ qbsInfo() << Tr::tr("Trying to detect DMC toolchains...");
+
+ const std::vector<ToolchainInstallInfo> allInfos = installedDmcsFromPath();
+ if (allInfos.empty()) {
+ qbsInfo() << Tr::tr("No DMC toolchains found.");
+ return;
+ }
+
+ for (const ToolchainInstallInfo &info : allInfos) {
+ const auto newProfiles = createDmcProfileHelper(info, settings);
+ profiles.reserve(profiles.size() + int(newProfiles.size()));
+ std::copy(newProfiles.cbegin(), newProfiles.cend(), std::back_inserter(profiles));
+ }
+}
diff --git a/src/app/qbs-setup-toolchains/dmcprobe.h b/src/app/qbs-setup-toolchains/dmcprobe.h
new file mode 100644
index 000000000..3159c2df5
--- /dev/null
+++ b/src/app/qbs-setup-toolchains/dmcprobe.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 Denis Shienkov <denis.shienkov@gmail.com>
+** 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_SETUPTOOLCHAINS_DMCPROBE_H
+#define QBS_SETUPTOOLCHAINS_DMCPROBE_H
+
+#include <QtCore/qlist.h>
+
+QT_BEGIN_NAMESPACE
+class QFileInfo;
+QT_END_NAMESPACE
+
+namespace qbs {
+class Profile;
+class Settings;
+}
+
+bool isDmcCompiler(const QString &compilerName);
+
+void createDmcProfile(const QFileInfo &compiler, qbs::Settings *settings,
+ QStringView profileName);
+
+void dmcProbe(qbs::Settings *settings, std::vector<qbs::Profile> &profiles);
+
+#endif // Header guard
diff --git a/src/app/qbs-setup-toolchains/gccprobe.cpp b/src/app/qbs-setup-toolchains/gccprobe.cpp
index 1e0f9c142..c8c04b0cc 100644
--- a/src/app/qbs-setup-toolchains/gccprobe.cpp
+++ b/src/app/qbs-setup-toolchains/gccprobe.cpp
@@ -47,6 +47,7 @@
#include <tools/hostosinfo.h>
#include <tools/profile.h>
#include <tools/settings.h>
+#include <tools/stlutils.h>
#include <tools/toolchains.h>
#include <QtCore/qdir.h>
@@ -111,7 +112,7 @@ class ToolchainDetails
public:
explicit ToolchainDetails(const QFileInfo &compiler)
{
- auto baseName = compiler.completeBaseName();
+ auto baseName = HostOsInfo::stripExecutableSuffix(compiler.fileName());
// Extract the version sub-string if it exists. We assume that a version
// sub-string located after the compiler prefix && suffix. E.g. this code
// parses a version from the compiler names, like this:
@@ -138,9 +139,9 @@ public:
};
static void setCommonProperties(Profile &profile, const QFileInfo &compiler,
- const QStringList &toolchainTypes, const ToolchainDetails &details)
+ const QString &toolchainType, const ToolchainDetails &details)
{
- if (toolchainTypes.contains(QStringLiteral("mingw")))
+ if (toolchainType == QStringLiteral("mingw"))
profile.setValue(QStringLiteral("qbs.targetPlatform"),
QStringLiteral("windows"));
@@ -149,7 +150,7 @@ static void setCommonProperties(Profile &profile, const QFileInfo &compiler,
profile.setValue(QStringLiteral("cpp.toolchainInstallPath"),
compiler.absolutePath());
- profile.setValue(QStringLiteral("qbs.toolchain"), toolchainTypes);
+ profile.setValue(QStringLiteral("qbs.toolchainType"), toolchainType);
if (!standardCompilerFileNames().contains(
HostOsInfo::appendExecutableSuffix(details.suffix))) {
@@ -230,12 +231,9 @@ private:
static bool doesProfileTargetOS(const Profile &profile, const QString &os)
{
const auto target = profile.value(QStringLiteral("qbs.targetPlatform"));
- if (target.isValid()) {
- return Internal::contains(HostOsInfo::canonicalOSIdentifiers(
- target.toString().toStdString()),
- os.toStdString());
- }
- return Internal::contains(HostOsInfo::hostOSIdentifiers(), os.toStdString());
+ if (target.isValid())
+ return Internal::contains(HostOsInfo::canonicalOSIdentifiers(target.toString()), os);
+ return Internal::contains(HostOsInfo::hostOSIdentifiers(), os);
}
static QString buildProfileName(const QFileInfo &cfi)
@@ -269,11 +267,8 @@ static QStringList buildCompilerNameFilters(const QString &compilerName)
QLatin1String("*-*-*-*-") + compilerName + QLatin1String("-[1-9]*")
};
- std::transform(filters.begin(), filters.end(), filters.begin(),
- [](const auto &filter) {
- return HostOsInfo::appendExecutableSuffix(filter);
- });
-
+ Internal::transform(filters, [](const auto &filter) {
+ return HostOsInfo::appendExecutableSuffix(filter); });
return filters;
}
@@ -282,30 +277,28 @@ static QStringList gnuRegistrySearchPaths()
if (!HostOsInfo::isWindowsHost())
return {};
- QStringList searchPaths;
+#ifdef Q_OS_WIN64
+ static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\ARM";
+#else
+ static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\ARM";
+#endif
- QSettings registry(QLatin1String(kUninstallRegistryKey), QSettings::NativeFormat);
- const auto productGroups = registry.childGroups();
- for (const QString &productKey : productGroups) {
- // Registry token for the "GNU Tools for ARM Embedded Processors".
- if (!productKey.startsWith(
- QLatin1String("GNU Tools for ARM Embedded Processors"))) {
- continue;
+ QStringList searchPaths;
+ QSettings registry(QLatin1String(kRegistryNode), QSettings::NativeFormat);
+ const auto groupKeys = registry.childGroups();
+ for (const QString &groupKey : groupKeys) {
+ registry.beginGroup(groupKey);
+ const QString rootPath = registry.value(QStringLiteral("InstallFolder")).toString();
+ if (!rootPath.isEmpty()) {
+ const QFileInfo toolchainPath(rootPath + QLatin1String("/bin"));
+ if (toolchainPath.exists()) {
+ const auto filePath = toolchainPath.absoluteFilePath();
+ if (!searchPaths.contains(filePath))
+ searchPaths.push_back(filePath);
+ }
}
- registry.beginGroup(productKey);
- QString uninstallFilePath = registry.value(
- QLatin1String("UninstallString")).toString();
- if (uninstallFilePath.startsWith(QLatin1Char('"')))
- uninstallFilePath.remove(0, 1);
- if (uninstallFilePath.endsWith(QLatin1Char('"')))
- uninstallFilePath.remove(uninstallFilePath.size() - 1, 1);
registry.endGroup();
-
- const QString toolkitRootPath = QFileInfo(uninstallFilePath).path();
- const QString toolchainPath = toolkitRootPath + QLatin1String("/bin");
- searchPaths.push_back(toolchainPath);
}
-
return searchPaths;
}
@@ -410,8 +403,8 @@ static QStringList renesasRl78RegistrySearchPaths()
if (installLocation.isEmpty())
continue;
- const QFileInfo toolchainPath = QDir(installLocation).absolutePath()
- + QLatin1String("/rl78-elf/rl78-elf/bin");
+ const QFileInfo toolchainPath(QDir(installLocation).absolutePath()
+ + QLatin1String("/rl78-elf/rl78-elf/bin"));
if (!toolchainPath.exists())
continue;
searchPaths.push_back(toolchainPath.absoluteFilePath());
@@ -442,8 +435,8 @@ static QStringList mplabX32RegistrySearchPaths()
if (installLocation.isEmpty())
continue;
- const QFileInfo toolchainPath = QDir(installLocation).absolutePath()
- + QLatin1String("/bin");
+ const QFileInfo toolchainPath(QDir(installLocation).absolutePath()
+ + QLatin1String("/bin"));
if (!toolchainPath.exists())
continue;
searchPaths.push_back(toolchainPath.absoluteFilePath());
@@ -453,12 +446,12 @@ static QStringList mplabX32RegistrySearchPaths()
}
Profile createGccProfile(const QFileInfo &compiler, Settings *settings,
- const QStringList &toolchainTypes,
+ const QString &toolchainType,
const QString &profileName)
{
const QString machineName = gccMachineName(compiler);
- if (toolchainTypes.contains(QLatin1String("mingw"))) {
+ if (toolchainType == QLatin1String("mingw")) {
if (!validMinGWMachines().contains(machineName)) {
throw ErrorInfo(Tr::tr("Detected gcc platform '%1' is not supported.")
.arg(machineName));
@@ -470,9 +463,9 @@ Profile createGccProfile(const QFileInfo &compiler, Settings *settings,
const ToolchainDetails details(compiler);
- setCommonProperties(profile, compiler, toolchainTypes, details);
+ setCommonProperties(profile, compiler, toolchainType, details);
- if (HostOsInfo::isWindowsHost() && toolchainTypes.contains(QLatin1String("clang"))) {
+ if (HostOsInfo::isWindowsHost() && toolchainType == QLatin1String("clang")) {
const QStringList profileNames = settings->profiles();
bool foundMingw = false;
for (const QString &profileName : profileNames) {
@@ -497,7 +490,7 @@ Profile createGccProfile(const QFileInfo &compiler, Settings *settings,
}
}
- if (!toolchainTypes.contains(QLatin1String("clang"))) {
+ if (toolchainType != QLatin1String("clang")) {
// Check whether auxiliary tools reside within the toolchain's install path.
// This might not be the case when using icecc or another compiler wrapper.
const QString compilerDirPath = compiler.absolutePath();
@@ -533,7 +526,7 @@ void gccProbe(Settings *settings, std::vector<Profile> &profiles, const QString
std::vector<QFileInfo> candidates;
const auto filters = buildCompilerNameFilters(compilerName);
- for (const auto &searchPath : searchPaths) {
+ for (const auto &searchPath : std::as_const(searchPaths)) {
const QDir dir(searchPath);
const QStringList fileNames = dir.entryList(
filters, QDir::Files | QDir::Executable);
@@ -543,7 +536,7 @@ void gccProbe(Settings *settings, std::vector<Profile> &profiles, const QString
|| fileName.startsWith(QLatin1String("c99-gcc"))) {
continue;
}
- const QFileInfo candidate = dir.filePath(fileName);
+ const QFileInfo candidate(dir.filePath(fileName));
// Filter duplicates.
const auto existingEnd = candidates.end();
const auto existingIt = std::find_if(
@@ -581,12 +574,16 @@ void gccProbe(Settings *settings, std::vector<Profile> &profiles, const QString
});
}
- for (const auto &candidate : qAsConst(candidates)) {
- const QStringList toolchainTypes = toolchainTypeFromCompilerName(
+ for (const auto &candidate : std::as_const(candidates)) {
+ const QString toolchainType = toolchainTypeFromCompilerName(
candidate.baseName());
const QString profileName = buildProfileName(candidate);
- auto profile = createGccProfile(candidate, settings,
- toolchainTypes, profileName);
- profiles.push_back(std::move(profile));
+ try {
+ auto profile = createGccProfile(candidate, settings,
+ toolchainType, profileName);
+ profiles.push_back(std::move(profile));
+ } catch (const qbs::ErrorInfo &info) {
+ qbsWarning() << Tr::tr("Skipping %1: %2").arg(profileName, info.toString());
+ }
}
}
diff --git a/src/app/qbs-setup-toolchains/gccprobe.h b/src/app/qbs-setup-toolchains/gccprobe.h
index ae389dafc..98e7eaa1f 100644
--- a/src/app/qbs-setup-toolchains/gccprobe.h
+++ b/src/app/qbs-setup-toolchains/gccprobe.h
@@ -53,7 +53,7 @@ class Settings;
qbs::Profile createGccProfile(const QFileInfo &compiler,
qbs::Settings *settings,
- const QStringList &toolchainTypes,
+ const QString &toolchainType,
const QString &profileName = QString());
void gccProbe(qbs::Settings *settings, std::vector<qbs::Profile> &profiles,
diff --git a/src/app/qbs-setup-toolchains/iarewprobe.cpp b/src/app/qbs-setup-toolchains/iarewprobe.cpp
index 7f710532a..adf5febb3 100644
--- a/src/app/qbs-setup-toolchains/iarewprobe.cpp
+++ b/src/app/qbs-setup-toolchains/iarewprobe.cpp
@@ -59,7 +59,14 @@ static QStringList knownIarCompilerNames()
{
return {QStringLiteral("icc8051"), QStringLiteral("iccarm"),
QStringLiteral("iccavr"), QStringLiteral("iccstm8"),
- QStringLiteral("icc430"), QStringLiteral("iccrl78")};
+ QStringLiteral("icc430"), QStringLiteral("iccrl78"),
+ QStringLiteral("iccrx"), QStringLiteral("iccrh850"),
+ QStringLiteral("iccv850"), QStringLiteral("icc78k"),
+ QStringLiteral("iccavr32"), QStringLiteral("iccsh"),
+ QStringLiteral("iccriscv"), QStringLiteral("icccf"),
+ QStringLiteral("iccm32c"), QStringLiteral("iccr32c"),
+ QStringLiteral("iccm16c"), QStringLiteral("icccr16c"),
+ QStringLiteral("icchcs12"), QStringLiteral("iccs08")};
}
static QString guessIarArchitecture(const QFileInfo &compiler)
@@ -77,6 +84,34 @@ static QString guessIarArchitecture(const QFileInfo &compiler)
return QStringLiteral("msp430");
if (baseName == QLatin1String("iccrl78"))
return QStringLiteral("rl78");
+ if (baseName == QLatin1String("iccrx"))
+ return QStringLiteral("rx");
+ if (baseName == QLatin1String("iccrh850"))
+ return QStringLiteral("rh850");
+ if (baseName == QLatin1String("iccv850"))
+ return QStringLiteral("v850");
+ if (baseName == QLatin1String("icc78k"))
+ return QStringLiteral("78k");
+ if (baseName == QLatin1String("iccavr32"))
+ return QStringLiteral("avr32");
+ if (baseName == QLatin1String("iccsh"))
+ return QStringLiteral("sh");
+ if (baseName == QLatin1String("iccriscv"))
+ return QStringLiteral("riscv");
+ if (baseName == QLatin1String("icccf"))
+ return QStringLiteral("m68k");
+ if (baseName == QLatin1String("iccm32c"))
+ return QStringLiteral("m32c");
+ if (baseName == QLatin1String("iccr32c"))
+ return QStringLiteral("r32c");
+ if (baseName == QLatin1String("iccm16c"))
+ return QStringLiteral("m16c");
+ if (baseName == QLatin1String("icccr16c"))
+ return QStringLiteral("cr16");
+ if (baseName == QLatin1String("icchcs12"))
+ return QStringLiteral("hcs12");
+ if (baseName == QLatin1String("iccs08"))
+ return QStringLiteral("hcs8");
return {};
}
@@ -142,13 +177,27 @@ static Version dumpIarCompilerVersion(const QFileInfo &compiler)
}
const QString arch = guessIarArchitecture(compiler);
- if (arch == QLatin1String("arm")) {
+ if (arch == QLatin1String("arm"))
return Version{verCode / 1000000, (verCode / 1000) % 1000, verCode % 1000};
- } else if (arch == QLatin1String("avr")
+ if (arch == QLatin1String("avr")
|| arch == QLatin1String("mcs51")
|| arch == QLatin1String("stm8")
|| arch == QLatin1String("msp430")
- || arch == QLatin1String("rl78")) {
+ || arch == QLatin1String("rl78")
+ || arch == QLatin1String("rx")
+ || arch == QLatin1String("rh850")
+ || arch == QLatin1String("v850")
+ || arch == QLatin1String("78k")
+ || arch == QLatin1String("avr32")
+ || arch == QLatin1String("sh")
+ || arch == QLatin1String("riscv")
+ || arch == QLatin1String("m68k")
+ || arch == QLatin1String("m32c")
+ || arch == QLatin1String("r32c")
+ || arch == QLatin1String("m16c")
+ || arch == QLatin1String("rc16")
+ || arch == QLatin1String("hcs12")
+ || arch == QLatin1String("hcs8")) {
return Version{verCode / 100, verCode % 100};
}
@@ -189,12 +238,26 @@ static std::vector<ToolchainInstallInfo> installedIarsFromRegistry()
QString registryKey;
QString subExePath;
} knowToolchains[] = {
- {QStringLiteral("EWARM"), QStringLiteral("\\arm\\bin\\iccarm.exe")},
- {QStringLiteral("EWAVR"), QStringLiteral("\\avr\\bin\\iccavr.exe")},
- {QStringLiteral("EW8051"), QStringLiteral("\\8051\\bin\\icc8051.exe")},
- {QStringLiteral("EWSTM8"), QStringLiteral("\\stm8\\bin\\iccstm8.exe")},
- {QStringLiteral("EW430"), QStringLiteral("\\430\\bin\\icc430.exe")},
- {QStringLiteral("EWRL78"), QStringLiteral("\\rl78\\bin\\iccrl78.exe")},
+ {QStringLiteral("EWARM"), QStringLiteral("/arm/bin/iccarm.exe")},
+ {QStringLiteral("EWAVR"), QStringLiteral("/avr/bin/iccavr.exe")},
+ {QStringLiteral("EW8051"), QStringLiteral("/8051/bin/icc8051.exe")},
+ {QStringLiteral("EWSTM8"), QStringLiteral("/stm8/bin/iccstm8.exe")},
+ {QStringLiteral("EW430"), QStringLiteral("/430/bin/icc430.exe")},
+ {QStringLiteral("EWRL78"), QStringLiteral("/rl78/bin/iccrl78.exe")},
+ {QStringLiteral("EWRX"), QStringLiteral("/rx/bin/iccrx.exe")},
+ {QStringLiteral("EWRH850"), QStringLiteral("/rh850/bin/iccrh850.exe")},
+ {QStringLiteral("EWV850"), QStringLiteral("/v850/bin/iccv850.exe")},
+ {QStringLiteral("EW78K"), QStringLiteral("/78k/bin/icc78k.exe")},
+ {QStringLiteral("EWAVR32"), QStringLiteral("/avr32/bin/iccavr32.exe")},
+ {QStringLiteral("EWSH"), QStringLiteral("/sh/bin/iccsh.exe")},
+ {QStringLiteral("EWRISCV"), QStringLiteral("/riscv/bin/iccriscv.exe")},
+ {QStringLiteral("EWCF"), QStringLiteral("/cf/bin/icccf.exe")},
+ {QStringLiteral("EWM32C"), QStringLiteral("/m32c/bin/iccm32c.exe")},
+ {QStringLiteral("EWR32C"), QStringLiteral("/r32c/bin/iccr32c.exe")},
+ {QStringLiteral("EWM16C"), QStringLiteral("/m16c/bin/iccm16c.exe")},
+ {QStringLiteral("EWCR16C"), QStringLiteral("/cr16c/bin/icccr16c.exe")},
+ {QStringLiteral("EWHCS12"), QStringLiteral("/hcs12/bin/icchcs12.exe")},
+ {QStringLiteral("EWS08"), QStringLiteral("/s08/bin/iccs08.exe")},
};
QSettings registry(QLatin1String(kRegistryNode), QSettings::NativeFormat);
@@ -215,8 +278,7 @@ static std::vector<ToolchainInstallInfo> installedIarsFromRegistry()
const QFileInfo iarPath(rootPath + entry.subExePath);
if (iarPath.exists()) {
// Note: threeLevelKey is a guessed toolchain version.
- const QString version = threeLevelKey;
- infos.push_back({iarPath, Version::fromString(version)});
+ infos.push_back({iarPath, Version::fromString(threeLevelKey, true)});
}
}
registry.endGroup();
@@ -245,7 +307,7 @@ void createIarProfile(const QFileInfo &compiler, Settings *settings,
QString profileName)
{
const ToolchainInstallInfo info = {compiler, Version{}};
- createIarProfileHelper(info, settings, profileName);
+ createIarProfileHelper(info, settings, std::move(profileName));
}
void iarProbe(Settings *settings, std::vector<Profile> &profiles)
@@ -261,10 +323,8 @@ void iarProbe(Settings *settings, std::vector<Profile> &profiles)
pathInfos.cbegin(), pathInfos.cend(),
std::back_inserter(allInfos));
- for (const ToolchainInstallInfo &info : allInfos) {
- const auto profile = createIarProfileHelper(info, settings);
- profiles.push_back(profile);
- }
+ qbs::Internal::transform(allInfos, profiles, [settings](const auto &info) {
+ return createIarProfileHelper(info, settings); });
if (allInfos.empty())
qbsInfo() << Tr::tr("No IAR toolchains found.");
diff --git a/src/app/qbs-setup-toolchains/keilprobe.cpp b/src/app/qbs-setup-toolchains/keilprobe.cpp
index 728c5b3f2..4bab54c92 100644
--- a/src/app/qbs-setup-toolchains/keilprobe.cpp
+++ b/src/app/qbs-setup-toolchains/keilprobe.cpp
@@ -47,6 +47,7 @@
#include <tools/hostosinfo.h>
#include <tools/profile.h>
+#include <QtCore/qdir.h>
#include <QtCore/qprocess.h>
#include <QtCore/qsettings.h>
#include <QtCore/qtemporaryfile.h>
@@ -57,7 +58,9 @@ using Internal::HostOsInfo;
static QStringList knownKeilCompilerNames()
{
- return {QStringLiteral("c51"), QStringLiteral("armcc")};
+ return {QStringLiteral("c51"), QStringLiteral("c251"),
+ QStringLiteral("c166"), QStringLiteral("armcc"),
+ QStringLiteral("armclang")};
}
static QString guessKeilArchitecture(const QFileInfo &compiler)
@@ -65,11 +68,22 @@ static QString guessKeilArchitecture(const QFileInfo &compiler)
const auto baseName = compiler.baseName();
if (baseName == QLatin1String("c51"))
return QStringLiteral("mcs51");
+ if (baseName == QLatin1String("c251"))
+ return QStringLiteral("mcs251");
+ if (baseName == QLatin1String("c166"))
+ return QStringLiteral("c166");
if (baseName == QLatin1String("armcc"))
return QStringLiteral("arm");
+ if (baseName == QLatin1String("armclang"))
+ return QStringLiteral("arm");
return {};
}
+static bool isArmClangCompiler(const QFileInfo &compiler)
+{
+ return compiler.baseName() == QLatin1String("armclang");
+}
+
static Profile createKeilProfileHelper(const ToolchainInstallInfo &info,
Settings *settings,
QString profileName = QString())
@@ -84,13 +98,19 @@ static Profile createKeilProfileHelper(const ToolchainInstallInfo &info,
} else {
const QString version = info.compilerVersion.toString(QLatin1Char('_'),
QLatin1Char('_'));
- profileName = QStringLiteral("keil-%1-%2").arg(
- version, architecture);
+ if (architecture == QLatin1String("arm") && isArmClangCompiler(compiler)) {
+ profileName = QStringLiteral("keil-llvm-%1-%2").arg(
+ version, architecture);
+ } else {
+ profileName = QStringLiteral("keil-%1-%2").arg(
+ version, architecture);
+ }
}
}
Profile profile(profileName, settings);
profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), compiler.absolutePath());
+ profile.setValue(QStringLiteral("cpp.compilerName"), compiler.fileName());
profile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("keil"));
if (!architecture.isEmpty())
profile.setValue(QStringLiteral("qbs.architecture"), architecture);
@@ -100,74 +120,202 @@ static Profile createKeilProfileHelper(const ToolchainInstallInfo &info,
return profile;
}
-static Version dumpKeilCompilerVersion(const QFileInfo &compiler)
+static Version dumpMcsCompilerVersion(const QFileInfo &compiler)
{
- const QString arch = guessKeilArchitecture(compiler);
- if (arch == QLatin1String("mcs51")) {
- QTemporaryFile fakeIn;
- if (!fakeIn.open()) {
- qbsWarning() << Tr::tr("Unable to open temporary file %1 for output: %2")
- .arg(fakeIn.fileName(), fakeIn.errorString());
- return Version{};
- }
- fakeIn.write("#define VALUE_TO_STRING(x) #x\n");
- fakeIn.write("#define VALUE(x) VALUE_TO_STRING(x)\n");
- fakeIn.write("#define VAR_NAME_VALUE(var) \"\"\"|\"#var\"|\"VALUE(var)\n");
- fakeIn.write("#ifdef __C51__\n");
- fakeIn.write("#pragma message(VAR_NAME_VALUE(__C51__))\n");
- fakeIn.write("#endif\n");
- fakeIn.write("#ifdef __CX51__\n");
- fakeIn.write("#pragma message(VAR_NAME_VALUE(__CX51__))\n");
- fakeIn.write("#endif\n");
- fakeIn.close();
-
- const QStringList args = {fakeIn.fileName()};
- QProcess p;
- p.start(compiler.absoluteFilePath(), args);
- p.waitForFinished(3000);
- const auto es = p.exitStatus();
- if (es != QProcess::NormalExit) {
- const QByteArray out = p.readAll();
- qbsWarning() << Tr::tr("Compiler dumping failed:\n%1")
- .arg(QString::fromUtf8(out));
- return Version{};
- }
+ QTemporaryFile fakeIn;
+ if (!fakeIn.open()) {
+ qbsWarning() << Tr::tr("Unable to open temporary file %1 for output: %2")
+ .arg(fakeIn.fileName(), fakeIn.errorString());
+ return Version{};
+ }
- const QByteArray dump = p.readAllStandardOutput();
- const int verCode = extractVersion(dump, "\"__C51__\"|\"");
- if (verCode < 0) {
- qbsWarning() << Tr::tr("No '__C51__' token was found"
- " in the compiler dump:\n%1")
- .arg(QString::fromUtf8(dump));
- return Version{};
- }
- return Version{verCode / 100, verCode % 100};
- } else if (arch == QLatin1String("arm")) {
- const QStringList args = {QStringLiteral("-E"),
- QStringLiteral("--list-macros"),
- QStringLiteral("nul")};
- QProcess p;
- p.start(compiler.absoluteFilePath(), args);
- p.waitForFinished(3000);
- const auto es = p.exitStatus();
- if (es != QProcess::NormalExit) {
- const QByteArray out = p.readAll();
- qbsWarning() << Tr::tr("Compiler dumping failed:\n%1")
- .arg(QString::fromUtf8(out));
- return Version{};
+ fakeIn.write("#define VALUE_TO_STRING(x) #x\n");
+ fakeIn.write("#define VALUE(x) VALUE_TO_STRING(x)\n");
+
+ // Prepare for C51 compiler.
+ fakeIn.write("#if defined(__C51__) || defined(__CX51__)\n");
+ fakeIn.write("# define VAR_NAME_VALUE(var) \"(\"\"\"\"|\"#var\"|\"VALUE(var)\"|\"\"\"\")\"\n");
+ fakeIn.write("# if defined (__C51__)\n");
+ fakeIn.write("# pragma message (VAR_NAME_VALUE(__C51__))\n");
+ fakeIn.write("# endif\n");
+ fakeIn.write("# if defined(__CX51__)\n");
+ fakeIn.write("# pragma message (VAR_NAME_VALUE(__CX51__))\n");
+ fakeIn.write("# endif\n");
+ fakeIn.write("#endif\n");
+
+ // Prepare for C251 compiler.
+ fakeIn.write("#if defined(__C251__)\n");
+ fakeIn.write("# define VAR_NAME_VALUE(var) \"\"|#var|VALUE(var)|\"\"\n");
+ fakeIn.write("# if defined(__C251__)\n");
+ fakeIn.write("# warning (VAR_NAME_VALUE(__C251__))\n");
+ fakeIn.write("# endif\n");
+ fakeIn.write("#endif\n");
+
+ fakeIn.close();
+
+ const QStringList args = {fakeIn.fileName()};
+ QProcess p;
+ p.start(compiler.absoluteFilePath(), args);
+ p.waitForFinished(3000);
+
+ const QStringList knownKeys = {QStringLiteral("__C51__"),
+ QStringLiteral("__CX51__"),
+ QStringLiteral("__C251__")};
+
+ auto extractVersion = [&knownKeys](const QByteArray &output) {
+ QTextStream stream(output);
+ QString line;
+ while (stream.readLineInto(&line)) {
+ if (!line.startsWith(QLatin1String("***")))
+ continue;
+ enum { KEY_INDEX = 1, VALUE_INDEX = 2, ALL_PARTS = 4 };
+ const QStringList parts = line.split(QLatin1String("\"|\""));
+ if (parts.count() != ALL_PARTS)
+ continue;
+ if (!knownKeys.contains(parts.at(KEY_INDEX)))
+ continue;
+ return parts.at(VALUE_INDEX).toInt();
}
+ return -1;
+ };
+
+ const QByteArray dump = p.readAllStandardOutput();
+ const int verCode = extractVersion(dump);
+ if (verCode < 0) {
+ qbsWarning() << Tr::tr("No %1 tokens was found"
+ " in the compiler dump:\n%2")
+ .arg(knownKeys.join(QLatin1Char(',')), QString::fromUtf8(dump));
+ return Version{};
+ }
+ return Version{verCode / 100, verCode % 100};
+}
- const QByteArray dump = p.readAll();
- const int verCode = extractVersion(dump, "__ARMCC_VERSION ");
- if (verCode < 0) {
- qbsWarning() << Tr::tr("No '__ARMCC_VERSION' token was found "
- "in the compiler dump:\n%1")
- .arg(QString::fromUtf8(dump));
- return Version{};
+static Version dumpC166CompilerVersion(const QFileInfo &compiler)
+{
+ QTemporaryFile fakeIn;
+ if (!fakeIn.open()) {
+ qbsWarning() << Tr::tr("Unable to open temporary file %1 for output: %2")
+ .arg(fakeIn.fileName(), fakeIn.errorString());
+ return Version{};
+ }
+
+ fakeIn.write("#if defined(__C166__)\n");
+ fakeIn.write("# warning __C166__\n");
+ fakeIn.write("# pragma __C166__\n");
+ fakeIn.write("#endif\n");
+
+ fakeIn.close();
+
+ const QStringList args = {fakeIn.fileName()};
+ QProcess p;
+ p.start(compiler.absoluteFilePath(), args);
+ p.waitForFinished(3000);
+
+ // Extract the compiler version pattern in the form, like:
+ //
+ // *** WARNING C320 IN LINE 41 OF c51.c: __C166__
+ // *** WARNING C2 IN LINE 42 OF c51.c: '757': unknown #pragma/control, line ignored
+ //
+ // where the '__C166__' is a key, and the '757' is a value (aka version).
+ auto extractVersion = [](const QString &output) {
+ const QStringList lines = output.split(QStringLiteral("\r\n"));
+ for (auto it = lines.cbegin(); it != lines.cend();) {
+ if (it->startsWith(QLatin1String("***")) && it->endsWith(QLatin1String("__C166__"))) {
+ ++it;
+ if (it == lines.cend() || !it->startsWith(QLatin1String("***")))
+ break;
+ const int startIndex = it->indexOf(QLatin1Char('\''));
+ if (startIndex == -1)
+ break;
+ const int stopIndex = it->indexOf(QLatin1Char('\''), startIndex + 1);
+ if (stopIndex == -1)
+ break;
+ const QString v = it->mid(startIndex + 1, stopIndex - startIndex - 1);
+ return v.toInt();
+ }
+ ++it;
}
- return Version{verCode / 1000000, (verCode / 10000) % 100, verCode % 10000};
+ return -1;
+ };
+
+ const QByteArray dump = p.readAllStandardOutput();
+ const int verCode = extractVersion(QString::fromUtf8(dump));
+ if (verCode < 0) {
+ qbsWarning() << Tr::tr("No __C166__ token was found"
+ " in the compiler dump:\n%1")
+ .arg(QString::fromUtf8(dump));
+ return Version{};
+ }
+ return Version{verCode / 100, verCode % 100};
+}
+
+static Version dumpArmCCCompilerVersion(const QFileInfo &compiler)
+{
+ const QStringList args = {QStringLiteral("-E"),
+ QStringLiteral("--list-macros"),
+ QStringLiteral("nul")};
+ QProcess p;
+ p.start(compiler.absoluteFilePath(), args);
+ p.waitForFinished(3000);
+ const auto es = p.exitStatus();
+ if (es != QProcess::NormalExit) {
+ const QByteArray out = p.readAll();
+ qbsWarning() << Tr::tr("Compiler dumping failed:\n%1")
+ .arg(QString::fromUtf8(out));
+ return Version{};
}
+ const QByteArray dump = p.readAll();
+ const int verCode = extractVersion(dump, "__ARMCC_VERSION ");
+ if (verCode < 0) {
+ qbsWarning() << Tr::tr("No '__ARMCC_VERSION' token was found "
+ "in the compiler dump:\n%1")
+ .arg(QString::fromUtf8(dump));
+ return Version{};
+ }
+ return Version{verCode / 1000000, (verCode / 10000) % 100, verCode % 10000};
+}
+
+static Version dumpArmClangCompilerVersion(const QFileInfo &compiler)
+{
+ const QStringList args = {QStringLiteral("-dM"), QStringLiteral("-E"),
+ QStringLiteral("-xc"),
+ QStringLiteral("--target=arm-arm-none-eabi"),
+ QStringLiteral("-mcpu=cortex-m0"),
+ QStringLiteral("nul")};
+ QProcess p;
+ p.start(compiler.absoluteFilePath(), args);
+ p.waitForFinished(3000);
+ const auto es = p.exitStatus();
+ if (es != QProcess::NormalExit) {
+ const QByteArray out = p.readAll();
+ qbsWarning() << Tr::tr("Compiler dumping failed:\n%1")
+ .arg(QString::fromUtf8(out));
+ return Version{};
+ }
+ const QByteArray dump = p.readAll();
+ const int verCode = extractVersion(dump, "__ARMCC_VERSION ");
+ if (verCode < 0) {
+ qbsWarning() << Tr::tr("No '__ARMCC_VERSION' token was found "
+ "in the compiler dump:\n%1")
+ .arg(QString::fromUtf8(dump));
+ return Version{};
+ }
+ return Version{verCode / 1000000, (verCode / 10000) % 100, verCode % 10000};
+}
+
+static Version dumpKeilCompilerVersion(const QFileInfo &compiler)
+{
+ const QString arch = guessKeilArchitecture(compiler);
+ if (arch == QLatin1String("mcs51") || arch == QLatin1String("mcs251"))
+ return dumpMcsCompilerVersion(compiler);
+ if (arch == QLatin1String("c166"))
+ return dumpC166CompilerVersion(compiler);
+ if (arch == QLatin1String("arm")) {
+ if (isArmClangCompiler(compiler))
+ return dumpArmClangCompilerVersion(compiler);
+ return dumpArmCCCompilerVersion(compiler);
+ }
return Version{};
}
@@ -200,43 +348,40 @@ static std::vector<ToolchainInstallInfo> installedKeilsFromRegistry()
static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Keil\\Products";
#endif
- // Dictionary for know toolchains.
- static const struct Entry {
- QString productKey;
- QString subExePath;
- } knowToolchains[] = {
- {QStringLiteral("MDK"), QStringLiteral("\\ARMCC\\bin\\armcc.exe")},
- {QStringLiteral("C51"), QStringLiteral("\\BIN\\c51.exe")},
- };
-
QSettings registry(QLatin1String(kRegistryNode), QSettings::NativeFormat);
const auto productGroups = registry.childGroups();
for (const QString &productKey : productGroups) {
- const auto entryEnd = std::end(knowToolchains);
- const auto entryIt = std::find_if(std::begin(knowToolchains), entryEnd,
- [productKey](const Entry &entry) {
- return entry.productKey == productKey;
- });
- if (entryIt == entryEnd)
- continue;
-
registry.beginGroup(productKey);
- const QString rootPath = registry.value(QStringLiteral("Path"))
- .toString();
- if (!rootPath.isEmpty()) {
- // Build full compiler path.
- const QFileInfo keilPath(rootPath + entryIt->subExePath);
- if (keilPath.exists()) {
- QString version = registry.value(QStringLiteral("Version"))
- .toString();
- if (version.startsWith(QLatin1Char('V')))
- version.remove(0, 1);
- infos.push_back({keilPath, Version::fromString(version)});
- }
+ const QString productPath = registry.value(QStringLiteral("Path"))
+ .toString();
+ QString productVersion = registry.value(QStringLiteral("Version"))
+ .toString();
+ if (productVersion.startsWith(QLatin1Char('V')))
+ productVersion.remove(0, 1);
+
+ if (productKey == QLatin1String("MDK")) {
+ const QFileInfo ccPath(productPath + QStringLiteral("/ARMCC/bin/armcc.exe"));
+ if (ccPath.exists())
+ infos.push_back({ccPath, Version::fromString(productVersion)});
+ const QFileInfo clangPath(productPath + QStringLiteral("/ARMCLANG/bin/armclang.exe"));
+ if (clangPath.exists())
+ infos.push_back({clangPath, Version::fromString(productVersion)});
+ } if (productKey == QLatin1String("C51")) {
+ const QFileInfo cPath(productPath + QStringLiteral("/BIN/c51.exe"));
+ if (cPath.exists())
+ infos.push_back({cPath, Version::fromString(productVersion)});
+ } if (productKey == QLatin1String("C251")) {
+ const QFileInfo cPath(productPath + QStringLiteral("/BIN/c251.exe"));
+ if (cPath.exists())
+ infos.push_back({cPath, Version::fromString(productVersion)});
+ } if (productKey == QLatin1String("C166")) {
+ const QFileInfo cPath(productPath + QStringLiteral("/BIN/c166.exe"));
+ if (cPath.exists())
+ infos.push_back({cPath, Version::fromString(productVersion)});
}
+
registry.endGroup();
}
-
}
std::sort(infos.begin(), infos.end());
@@ -255,7 +400,7 @@ void createKeilProfile(const QFileInfo &compiler, Settings *settings,
QString profileName)
{
const ToolchainInstallInfo info = {compiler, Version{}};
- createKeilProfileHelper(info, settings, profileName);
+ createKeilProfileHelper(info, settings, std::move(profileName));
}
void keilProbe(Settings *settings, std::vector<Profile> &profiles)
@@ -271,10 +416,8 @@ void keilProbe(Settings *settings, std::vector<Profile> &profiles)
pathInfos.cbegin(), pathInfos.cend(),
std::back_inserter(allInfos));
- for (const ToolchainInstallInfo &info : allInfos) {
- const auto profile = createKeilProfileHelper(info, settings);
- profiles.push_back(profile);
- }
+ qbs::Internal::transform(allInfos, profiles, [settings](const auto &info) {
+ return createKeilProfileHelper(info, settings); });
if (allInfos.empty())
qbsInfo() << Tr::tr("No KEIL toolchains found.");
diff --git a/src/app/qbs-setup-toolchains/main.cpp b/src/app/qbs-setup-toolchains/main.cpp
index 87a2a842a..475bcf07b 100644
--- a/src/app/qbs-setup-toolchains/main.cpp
+++ b/src/app/qbs-setup-toolchains/main.cpp
@@ -49,7 +49,6 @@
#include <cstdlib>
#include <iostream>
-using qbs::Internal::Tr;
using qbs::Settings;
static void printUsage(const QString &usageString)
diff --git a/src/app/qbs-setup-toolchains/msvcprobe.cpp b/src/app/qbs-setup-toolchains/msvcprobe.cpp
index 9f2faaa99..84d36753e 100644
--- a/src/app/qbs-setup-toolchains/msvcprobe.cpp
+++ b/src/app/qbs-setup-toolchains/msvcprobe.cpp
@@ -50,19 +50,15 @@
#include <tools/qttools.h>
#include <tools/settings.h>
#include <tools/version.h>
-#include <tools/visualstudioversioninfo.h>
#include <tools/vsenvironmentdetector.h>
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
-#include <QtCore/qjsonarray.h>
-#include <QtCore/qjsondocument.h>
-#include <QtCore/qjsonobject.h>
-#include <QtCore/qprocess.h>
#include <QtCore/qsettings.h>
#include <QtCore/qstringlist.h>
#include <algorithm>
+#include <set>
#include <vector>
using namespace qbs;
@@ -90,67 +86,15 @@ static void setQtHelperProperties(Profile &p, const MSVC *msvc)
static void addMSVCPlatform(Settings *settings, std::vector<Profile> &profiles, QString name, MSVC *msvc)
{
qbsInfo() << Tr::tr("Setting up profile '%1'.").arg(name);
- Profile p(name, settings);
+ Profile p(std::move(name), settings);
p.removeProfile();
p.setValue(QStringLiteral("qbs.targetPlatform"), QStringLiteral("windows"));
- p.setValue(QStringLiteral("qbs.toolchain"), QStringList(QStringLiteral("msvc")));
+ p.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("msvc"));
p.setValue(QStringLiteral("cpp.toolchainInstallPath"), msvc->binPath);
setQtHelperProperties(p, msvc);
profiles.push_back(p);
}
-struct MSVCArchInfo
-{
- QString arch;
- QString binPath;
-};
-
-static std::vector<MSVCArchInfo> findSupportedArchitectures(const MSVC &msvc)
-{
- std::vector<MSVCArchInfo> result;
- auto addResult = [&result](const MSVCArchInfo &ai) {
- if (QFile::exists(ai.binPath + QLatin1String("/cl.exe")))
- result.push_back(ai);
- };
- if (msvc.internalVsVersion.majorVersion() < 15) {
- static const QStringList knownArchitectures = QStringList()
- << QStringLiteral("x86")
- << QStringLiteral("amd64_x86")
- << QStringLiteral("amd64")
- << QStringLiteral("x86_amd64")
- << QStringLiteral("ia64")
- << QStringLiteral("x86_ia64")
- << QStringLiteral("x86_arm")
- << QStringLiteral("amd64_arm");
- for (const QString &knownArchitecture : knownArchitectures) {
- MSVCArchInfo ai;
- ai.arch = knownArchitecture;
- ai.binPath = msvc.binPathForArchitecture(knownArchitecture);
- addResult(ai);
- }
- } else {
- QDir vcInstallDir(msvc.vcInstallPath);
- const auto hostArchs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
- for (const QString &hostArch : hostArchs) {
- QDir subdir = vcInstallDir;
- if (!subdir.cd(hostArch))
- continue;
- const QString shortHostArch = hostArch.mid(4).toLower();
- const auto archs = subdir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
- for (const QString &arch : archs) {
- MSVCArchInfo ai;
- ai.binPath = subdir.absoluteFilePath(arch);
- if (shortHostArch == arch)
- ai.arch = arch;
- else
- ai.arch = shortHostArch + QLatin1Char('_') + arch;
- addResult(ai);
- }
- }
- }
- return result;
-}
-
static QString wow6432Key()
{
#ifdef Q_OS_WIN64
@@ -160,202 +104,6 @@ static QString wow6432Key()
#endif
}
-static QString vswhereFilePath()
-{
- static const std::vector<const char *> envVarCandidates{"ProgramFiles", "ProgramFiles(x86)"};
- for (const char * const envVar : envVarCandidates) {
- const QString value = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv(envVar)));
- const QString cmd = value
- + QStringLiteral("/Microsoft Visual Studio/Installer/vswhere.exe");
- if (QFileInfo(cmd).exists())
- return cmd;
- }
- return {};
-}
-
-enum class ProductType { VisualStudio, BuildTools };
-static std::vector<MSVCInstallInfo> retrieveInstancesFromVSWhere(ProductType productType)
-{
- std::vector<MSVCInstallInfo> result;
- const QString cmd = vswhereFilePath();
- if (cmd.isEmpty())
- return result;
- QProcess vsWhere;
- QStringList args = productType == ProductType::VisualStudio
- ? QStringList({QStringLiteral("-all"), QStringLiteral("-legacy"),
- QStringLiteral("-prerelease")})
- : QStringList({QStringLiteral("-products"),
- QStringLiteral("Microsoft.VisualStudio.Product.BuildTools")});
- args << QStringLiteral("-format") << QStringLiteral("json") << QStringLiteral("-utf8");
- vsWhere.start(cmd, args);
- if (!vsWhere.waitForStarted(-1))
- return result;
- if (!vsWhere.waitForFinished(-1)) {
- qbsWarning() << Tr::tr("The vswhere tool failed to run: %1").arg(vsWhere.errorString());
- return result;
- }
- if (vsWhere.exitCode() != 0) {
- qbsWarning() << Tr::tr("The vswhere tool failed to run: %1")
- .arg(QString::fromLocal8Bit(vsWhere.readAllStandardError()));
- return result;
- }
- QJsonParseError parseError;
- QJsonDocument jsonOutput = QJsonDocument::fromJson(vsWhere.readAllStandardOutput(),
- &parseError);
- if (parseError.error != QJsonParseError::NoError) {
- qbsWarning() << Tr::tr("The vswhere tool produced invalid JSON output: %1")
- .arg(parseError.errorString());
- return result;
- }
- const auto jsonArray = jsonOutput.array();
- for (const QJsonValue &v : jsonArray) {
- const QJsonObject o = v.toObject();
- MSVCInstallInfo info;
- info.version = o.value(QStringLiteral("installationVersion")).toString();
- if (productType == ProductType::BuildTools) {
- // For build tools, the version is e.g. "15.8.28010.2036", rather than "15.0".
- const int dotIndex = info.version.indexOf(QLatin1Char('.'));
- if (dotIndex != -1)
- info.version = info.version.left(dotIndex);
- }
- info.installDir = o.value(QStringLiteral("installationPath")).toString();
- if (!info.version.isEmpty() && !info.installDir.isEmpty())
- result.push_back(info);
- }
- return result;
-}
-
-static std::vector<MSVCInstallInfo> installedMSVCsFromVsWhere()
-{
- const std::vector<MSVCInstallInfo> vsInstallations
- = retrieveInstancesFromVSWhere(ProductType::VisualStudio);
- const std::vector<MSVCInstallInfo> buildToolInstallations
- = retrieveInstancesFromVSWhere(ProductType::BuildTools);
- std::vector<MSVCInstallInfo> all;
- std::copy(vsInstallations.begin(), vsInstallations.end(), std::back_inserter(all));
- std::copy(buildToolInstallations.begin(), buildToolInstallations.end(),
- std::back_inserter(all));
- return all;
-}
-
-static std::vector<MSVCInstallInfo> installedMSVCsFromRegistry()
-{
- std::vector<MSVCInstallInfo> result;
-
- // Detect Visual Studio
- const QSettings vsRegistry(
- QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key()
- + QStringLiteral("\\Microsoft\\VisualStudio\\SxS\\VS7"),
- QSettings::NativeFormat);
- const auto vsNames = vsRegistry.childKeys();
- for (const QString &vsName : vsNames) {
- MSVCInstallInfo entry;
- entry.version = vsName;
- entry.installDir = vsRegistry.value(vsName).toString();
- result.push_back(entry);
- }
-
- // Detect Visual C++ Build Tools
- QSettings vcbtRegistry(
- QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key()
- + QStringLiteral("\\Microsoft\\VisualCppBuildTools"),
- QSettings::NativeFormat);
- const QStringList &vcbtRegistryChildGroups = vcbtRegistry.childGroups();
- for (const QString &childGroup : vcbtRegistryChildGroups) {
- vcbtRegistry.beginGroup(childGroup);
- bool ok;
- int installed = vcbtRegistry.value(QStringLiteral("Installed")).toInt(&ok);
- if (ok && installed) {
- MSVCInstallInfo entry;
- entry.version = childGroup;
- const QSettings vsRegistry(
- QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key()
- + QStringLiteral("\\Microsoft\\VisualStudio\\") + childGroup
- + QStringLiteral("\\Setup\\VC"),
- QSettings::NativeFormat);
- entry.installDir = vsRegistry.value(QStringLiteral("ProductDir")).toString();
- result.push_back(entry);
- }
- vcbtRegistry.endGroup();
- }
-
- return result;
-}
-
-QString MSVCInstallInfo::findVcvarsallBat() const
-{
- static const auto vcvarsall2017 = QStringLiteral("VC/Auxiliary/Build/vcvarsall.bat");
- // 2015, 2013 and 2012
- static const auto vcvarsallOld = QStringLiteral("VC/vcvarsall.bat");
- QDir dir(installDir);
- if (dir.exists(vcvarsall2017))
- return dir.absoluteFilePath(vcvarsall2017);
- if (dir.exists(vcvarsallOld))
- return dir.absoluteFilePath(vcvarsallOld);
- return {};
-}
-
-std::vector<MSVCInstallInfo> installedMSVCs()
-{
- const auto installInfos = installedMSVCsFromVsWhere();
- if (installInfos.empty())
- return installedMSVCsFromRegistry();
- return installInfos;
-}
-
-static std::vector<MSVC> installedCompilers()
-{
- std::vector<MSVC> msvcs;
- std::vector<MSVCInstallInfo> installInfos = installedMSVCsFromVsWhere();
- if (installInfos.empty())
- installInfos = installedMSVCsFromRegistry();
- for (const MSVCInstallInfo &installInfo : installInfos) {
- MSVC msvc;
- msvc.internalVsVersion = Version::fromString(installInfo.version, true);
- if (!msvc.internalVsVersion.isValid())
- continue;
-
- QDir vsInstallDir(installInfo.installDir);
- msvc.vsInstallPath = vsInstallDir.absolutePath();
- if (vsInstallDir.dirName() != QStringLiteral("VC")
- && !vsInstallDir.cd(QStringLiteral("VC"))) {
- continue;
- }
-
- msvc.version = QString::number(Internal::VisualStudioVersionInfo(
- msvc.internalVsVersion).marketingVersion());
- if (msvc.version.isEmpty()) {
- qbsWarning() << Tr::tr("Unknown MSVC version %1 found.").arg(installInfo.version);
- continue;
- }
-
- if (msvc.internalVsVersion.majorVersion() < 15) {
- QDir vcInstallDir = vsInstallDir;
- if (!vcInstallDir.cd(QStringLiteral("bin")))
- continue;
- msvc.vcInstallPath = vcInstallDir.absolutePath();
- msvcs.push_back(msvc);
- } else {
- QDir vcInstallDir = vsInstallDir;
- vcInstallDir.cd(QStringLiteral("Tools/MSVC"));
- const auto vcVersionStrs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
- for (const QString &vcVersionStr : vcVersionStrs) {
- const Version vcVersion = Version::fromString(vcVersionStr);
- if (!vcVersion.isValid())
- continue;
- QDir specificVcInstallDir = vcInstallDir;
- if (!specificVcInstallDir.cd(vcVersionStr)
- || !specificVcInstallDir.cd(QStringLiteral("bin"))) {
- continue;
- }
- msvc.vcInstallPath = specificVcInstallDir.absolutePath();
- msvcs.push_back(msvc);
- }
- }
- }
- return msvcs;
-}
-
void msvcProbe(Settings *settings, std::vector<Profile> &profiles)
{
qbsInfo() << Tr::tr("Detecting MSVC toolchains...");
@@ -381,17 +129,18 @@ void msvcProbe(Settings *settings, std::vector<Profile> &profiles)
sdk.vcInstallPath.chop(1);
if (sdk.isDefault)
defaultWinSDK = sdk;
- const auto ais = findSupportedArchitectures(sdk);
- for (const MSVCArchInfo &ai : ais) {
+ const auto ais = MSVC::findSupportedArchitectures(sdk);
+
+ qbs::Internal::transform(ais, winSDKs, [&sdk](const auto &ai) {
WinSDK specificSDK = sdk;
specificSDK.architecture = ai.arch;
specificSDK.binPath = ai.binPath;
- winSDKs.push_back(specificSDK);
- }
+ return specificSDK;
+ });
}
}
- for (const WinSDK &sdk : qAsConst(winSDKs)) {
+ for (const WinSDK &sdk : std::as_const(winSDKs)) {
qbsInfo() << Tr::tr(" Windows SDK %1 detected:\n"
" installed in %2").arg(sdk.version, sdk.vcInstallPath);
if (sdk.isDefault)
@@ -399,26 +148,9 @@ void msvcProbe(Settings *settings, std::vector<Profile> &profiles)
}
// 2) Installed MSVCs
- std::vector<MSVC> msvcs;
- const auto instMsvcs = installedCompilers();
- for (const MSVC &msvc : instMsvcs) {
- if (msvc.internalVsVersion.majorVersion() < 15) {
- // Check existence of various install scripts
- const QString vcvars32bat = msvc.vcInstallPath + QLatin1String("/vcvars32.bat");
- if (!QFileInfo(vcvars32bat).isFile())
- continue;
- }
-
- const auto ais = findSupportedArchitectures(msvc);
- for (const MSVCArchInfo &ai : ais) {
- MSVC specificMSVC = msvc;
- specificMSVC.architecture = ai.arch;
- specificMSVC.binPath = ai.binPath;
- msvcs.push_back(specificMSVC);
- }
- }
+ std::vector<MSVC> msvcs = MSVC::installedCompilers(ConsoleLogger::instance());
- for (const MSVC &msvc : qAsConst(msvcs)) {
+ for (const MSVC &msvc : std::as_const(msvcs)) {
qbsInfo() << Tr::tr(" MSVC %1 (%2) detected in\n"
" %3").arg(msvc.version, msvc.architecture,
QDir::toNativeSeparators(msvc.binPath));
@@ -432,10 +164,10 @@ void msvcProbe(Settings *settings, std::vector<Profile> &profiles)
qbsInfo() << Tr::tr("Detecting build environment...");
std::vector<MSVC *> msvcPtrs;
- msvcPtrs.resize(winSDKs.size() + msvcs.size());
- std::transform(winSDKs.begin(), winSDKs.end(), msvcPtrs.begin(),
+ msvcPtrs.reserve(winSDKs.size() + msvcs.size());
+ std::transform(winSDKs.begin(), winSDKs.end(), std::back_inserter(msvcPtrs),
[] (WinSDK &sdk) -> MSVC * { return &sdk; });
- std::transform(msvcs.begin(), msvcs.end(), msvcPtrs.begin() + winSDKs.size(),
+ std::transform(msvcs.begin(), msvcs.end(), std::back_inserter(msvcPtrs),
[] (MSVC &msvc) -> MSVC * { return &msvc; });
VsEnvironmentDetector envDetector;
@@ -452,8 +184,20 @@ void msvcProbe(Settings *settings, std::vector<Profile> &profiles)
}
}
+ // we want the same MSVC version share the same suffix in profiles, thus use
+ // a set to know the number of versions processed so far
+ std::map<QString /*VS*/, std::set<QString /*vcInstallPath*/>> msvcCounters;
for (MSVC &msvc : msvcs) {
- const QString name = QLatin1String("MSVC") + msvc.version + QLatin1Char('-')
+ // each VS needs its own counter
+ auto &msvcVersions = msvcCounters[msvc.version];
+ // vcInstallPath is "Microsoft Visual Studio/2019/Enterprise/VC/Tools/MSVC/14.16.27023/bin"
+ // Since msvcs are sorted by version, when the new vcInstallPath is inserted, we start
+ // a new group of compilers of the same version incrementing the set size
+ msvcVersions.insert(msvc.vcInstallPath);
+ // index is the number of specific vcInstallPaths (e.g. compiler versions) seen so far
+ const size_t index = msvcVersions.size() - 1;
+ const QString suffix = index == 0 ? QString() : QStringLiteral("-%1").arg(index);
+ const QString name = QLatin1String("MSVC") + msvc.version + suffix + QLatin1Char('-')
+ msvc.architecture;
try {
msvc.init();
diff --git a/src/app/qbs-setup-toolchains/msvcprobe.h b/src/app/qbs-setup-toolchains/msvcprobe.h
index 3120a96f2..981bbc561 100644
--- a/src/app/qbs-setup-toolchains/msvcprobe.h
+++ b/src/app/qbs-setup-toolchains/msvcprobe.h
@@ -53,16 +53,6 @@ class Profile;
class Settings;
}
-struct MSVCInstallInfo
-{
- QString version;
- QString installDir;
-
- QString findVcvarsallBat() const;
-};
-
-std::vector<MSVCInstallInfo> installedMSVCs();
-
void createMsvcProfile(const QFileInfo &compiler, qbs::Settings *settings,
const QString &profileName);
diff --git a/src/app/qbs-setup-toolchains/probe.cpp b/src/app/qbs-setup-toolchains/probe.cpp
index 513008fd3..ceb95948b 100644
--- a/src/app/qbs-setup-toolchains/probe.cpp
+++ b/src/app/qbs-setup-toolchains/probe.cpp
@@ -39,17 +39,21 @@
#include "probe.h"
#include "clangclprobe.h"
+#include "cosmicprobe.h"
+#include "dmcprobe.h"
#include "gccprobe.h"
#include "iarewprobe.h"
#include "keilprobe.h"
#include "msvcprobe.h"
#include "sdccprobe.h"
+#include "watcomprobe.h"
#include "xcodeprobe.h"
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/hostosinfo.h>
#include <tools/profile.h>
+#include <tools/qttools.h>
#include <tools/settings.h>
#include <tools/toolchains.h>
@@ -96,26 +100,32 @@ QString findExecutable(const QString &fileName)
return {};
}
-QStringList toolchainTypeFromCompilerName(const QString &compilerName)
+QString toolchainTypeFromCompilerName(const QString &compilerName)
{
if (compilerName == QLatin1String("cl.exe"))
- return canonicalToolchain(QStringLiteral("msvc"));
+ return QStringLiteral("msvc");
if (compilerName == QLatin1String("clang-cl.exe"))
- return canonicalToolchain(QLatin1String("clang-cl"));
+ return QStringLiteral("clang-cl");
const auto types = { QStringLiteral("clang"), QStringLiteral("llvm"),
QStringLiteral("mingw"), QStringLiteral("gcc") };
for (const auto &type : types) {
if (compilerName.contains(type))
- return canonicalToolchain(type);
+ return type;
}
if (compilerName == QLatin1String("g++"))
- return canonicalToolchain(QStringLiteral("gcc"));
+ return QStringLiteral("gcc");
if (isIarCompiler(compilerName))
- return canonicalToolchain(QStringLiteral("iar"));
+ return QStringLiteral("iar");
if (isKeilCompiler(compilerName))
- return canonicalToolchain(QStringLiteral("keil"));
+ return QStringLiteral("keil");
if (isSdccCompiler(compilerName))
- return canonicalToolchain(QStringLiteral("sdcc"));
+ return QStringLiteral("sdcc");
+ if (isCosmicCompiler(compilerName))
+ return QStringLiteral("cosmic");
+ if (isDmcCompiler(compilerName))
+ return QStringLiteral("dmc");
+ if (isWatcomCompiler(compilerName))
+ return QStringLiteral("watcom");
return {};
}
@@ -135,12 +145,15 @@ void probe(Settings *settings)
iarProbe(settings, profiles);
keilProbe(settings, profiles);
sdccProbe(settings, profiles);
+ cosmicProbe(settings, profiles);
+ dmcProbe(settings, profiles);
+ watcomProbe(settings, profiles);
if (profiles.empty()) {
- qStderr << Tr::tr("Could not detect any toolchains. No profile created.") << endl;
+ qStderr << Tr::tr("Could not detect any toolchains. No profile created.") << Qt::endl;
} else if (profiles.size() == 1 && settings->defaultProfile().isEmpty()) {
const QString profileName = profiles.front().name();
- qStdout << Tr::tr("Making profile '%1' the default.").arg(profileName) << endl;
+ qStdout << Tr::tr("Making profile '%1' the default.").arg(profileName) << Qt::endl;
settings->setValue(QStringLiteral("defaultProfile"), profileName);
}
}
@@ -157,24 +170,29 @@ void createProfile(const QString &profileName, const QString &toolchainType,
.arg(compilerFilePath));
}
- QStringList toolchainTypes;
- if (toolchainType.isEmpty())
- toolchainTypes = toolchainTypeFromCompilerName(compiler.fileName());
- else
- toolchainTypes = canonicalToolchain(toolchainType);
+ const QString realToolchainType = !toolchainType.isEmpty()
+ ? toolchainType
+ : toolchainTypeFromCompilerName(compiler.fileName());
+ const QStringList toolchain = canonicalToolchain(realToolchainType);
- if (toolchainTypes.contains(QLatin1String("msvc")))
- createMsvcProfile(compiler, settings, profileName);
- else if (toolchainTypes.contains(QLatin1String("clang-cl")))
+ if (toolchain.contains(QLatin1String("clang-cl")))
createClangClProfile(compiler, settings, profileName);
- else if (toolchainTypes.contains(QLatin1String("gcc")))
- createGccProfile(compiler, settings, toolchainTypes, profileName);
- else if (toolchainTypes.contains(QLatin1String("iar")))
+ else if (toolchain.contains(QLatin1String("msvc")))
+ createMsvcProfile(compiler, settings, profileName);
+ else if (toolchain.contains(QLatin1String("gcc")))
+ createGccProfile(compiler, settings, realToolchainType, profileName);
+ else if (toolchain.contains(QLatin1String("iar")))
createIarProfile(compiler, settings, profileName);
- else if (toolchainTypes.contains(QLatin1String("keil")))
+ else if (toolchain.contains(QLatin1String("keil")))
createKeilProfile(compiler, settings, profileName);
- else if (toolchainTypes.contains(QLatin1String("sdcc")))
+ else if (toolchain.contains(QLatin1String("sdcc")))
createSdccProfile(compiler, settings, profileName);
+ else if (toolchain.contains(QLatin1String("cosmic")))
+ createCosmicProfile(compiler, settings, profileName);
+ else if (toolchain.contains(QLatin1String("dmc")))
+ createDmcProfile(compiler, settings, profileName);
+ else if (toolchain.contains(QLatin1String("watcom")))
+ createWatcomProfile(compiler, settings, profileName);
else
throw qbs::ErrorInfo(Tr::tr("Cannot create profile: Unknown toolchain type."));
}
@@ -301,3 +319,21 @@ bool isSameExecutable(const QString &filePath1, const QString &filePath2)
return false;
}
+
+MacrosMap dumpMacros(const std::function<QStringList()> &func)
+{
+ MacrosMap macros;
+ const QStringList lines = func();
+ for (const QString &line : lines) {
+ const QString prefix = QLatin1String("#define ");
+ if (!line.startsWith(prefix))
+ return macros;
+ const auto index = line.indexOf(QLatin1String(" "), prefix.length());
+ if (index != -1) {
+ const auto key = line.mid(prefix.length(), index - prefix.length());
+ const auto value = line.mid(index + 1);
+ macros.insert(key, value);
+ }
+ }
+ return macros;
+}
diff --git a/src/app/qbs-setup-toolchains/probe.h b/src/app/qbs-setup-toolchains/probe.h
index 235d7a899..827171fb2 100644
--- a/src/app/qbs-setup-toolchains/probe.h
+++ b/src/app/qbs-setup-toolchains/probe.h
@@ -43,21 +43,19 @@
#include <tools/version.h>
#include <QtCore/qfileinfo.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <functional> // for std::function
#include <tuple> // for std::tie
-QT_BEGIN_NAMESPACE
-class QString;
-class QStringList;
-QT_END_NAMESPACE
-
namespace qbs { class Settings; }
QStringList systemSearchPaths();
QString findExecutable(const QString &fileName);
-QStringList toolchainTypeFromCompilerName(const QString &compilerName);
+QString toolchainTypeFromCompilerName(const QString &compilerName);
void createProfile(const QString &profileName, const QString &toolchainType,
const QString &compilerFilePath, qbs::Settings *settings);
@@ -81,4 +79,7 @@ int extractVersion(const QByteArray &macroDump, const QByteArray &keyToken);
bool isSameExecutable(const QString &exe1, const QString &exe2);
+using MacrosMap = QMap<QString, QString>;
+MacrosMap dumpMacros(const std::function<QStringList()> &func);
+
#endif // Header guard
diff --git a/src/app/qbs-setup-toolchains/qbs-setup-toolchains.pro b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.pro
deleted file mode 100644
index 6581bec63..000000000
--- a/src/app/qbs-setup-toolchains/qbs-setup-toolchains.pro
+++ /dev/null
@@ -1,30 +0,0 @@
-include(../app.pri)
-
-TARGET = qbs-setup-toolchains
-
-HEADERS += \
- clangclprobe.h \
- commandlineparser.h \
- gccprobe.h \
- iarewprobe.h \
- keilprobe.h \
- msvcprobe.h \
- probe.h \
- sdccprobe.h \
- xcodeprobe.h \
-
-SOURCES += \
- clangclprobe.cpp \
- commandlineparser.cpp \
- gccprobe.cpp \
- iarewprobe.cpp \
- keilprobe.cpp \
- main.cpp \
- msvcprobe.cpp \
- probe.cpp \
- sdccprobe.cpp \
- xcodeprobe.cpp \
-
-mingw {
- RC_FILE = qbs-setup-toolchains.rc
-}
diff --git a/src/app/qbs-setup-toolchains/qbs-setup-toolchains.qbs b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.qbs
index 554891fc4..6987f3717 100644
--- a/src/app/qbs-setup-toolchains/qbs-setup-toolchains.qbs
+++ b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.qbs
@@ -1,5 +1,3 @@
-import qbs 1.0
-
QbsApp {
name: "qbs-setup-toolchains"
cpp.dynamicLibraries: qbs.targetOS.contains("windows") ? base.concat("shell32") : base
@@ -8,6 +6,10 @@ QbsApp {
"clangclprobe.h",
"commandlineparser.cpp",
"commandlineparser.h",
+ "cosmicprobe.cpp",
+ "cosmicprobe.h",
+ "dmcprobe.cpp",
+ "dmcprobe.h",
"gccprobe.cpp",
"gccprobe.h",
"iarewprobe.cpp",
@@ -21,12 +23,19 @@ QbsApp {
"probe.h",
"sdccprobe.cpp",
"sdccprobe.h",
+ "watcomprobe.cpp",
+ "watcomprobe.h",
"xcodeprobe.cpp",
"xcodeprobe.h",
]
Group {
name: "MinGW specific files"
condition: qbs.toolchain.contains("mingw")
- files: ["qbs-setup-toolchains.exe.manifest", "qbs-setup-toolchains.rc"]
+ files: "qbs-setup-toolchains.rc"
+ Group {
+ name: "qbs-setup-toolchains manifest"
+ files: "qbs-setup-toolchains.exe.manifest"
+ fileTags: [] // the manifest is referenced by the rc file
+ }
}
}
diff --git a/src/app/qbs-setup-toolchains/sdccprobe.cpp b/src/app/qbs-setup-toolchains/sdccprobe.cpp
index 3899ac3c9..25a3d9751 100644
--- a/src/app/qbs-setup-toolchains/sdccprobe.cpp
+++ b/src/app/qbs-setup-toolchains/sdccprobe.cpp
@@ -48,6 +48,7 @@
#include <tools/profile.h>
#include <QtCore/qprocess.h>
+#include <QtCore/qregularexpression.h>
#include <QtCore/qsettings.h>
#include <QtCore/qtemporaryfile.h>
@@ -60,8 +61,7 @@ static QStringList knownSdccCompilerNames()
return {QStringLiteral("sdcc")};
}
-static QByteArray dumpSdccMacros(const QFileInfo &compiler,
- const QString &targetFlag = QString())
+static QStringList dumpOutput(const QFileInfo &compiler, const QString &targetFlag = QString())
{
QTemporaryFile fakeIn(QStringLiteral("XXXXXX.c"));
if (!fakeIn.open()) {
@@ -71,103 +71,92 @@ static QByteArray dumpSdccMacros(const QFileInfo &compiler,
}
fakeIn.close();
- const QStringList args = {QStringLiteral("-dM"),
- QStringLiteral("-E"),
- targetFlag,
- fakeIn.fileName()};
+ const QStringList args = {QStringLiteral("-dM"), QStringLiteral("-E"),
+ targetFlag, fakeIn.fileName()};
QProcess p;
p.start(compiler.absoluteFilePath(), args);
p.waitForFinished(3000);
const auto es = p.exitStatus();
if (es != QProcess::NormalExit) {
const QByteArray out = p.readAll();
- qbsWarning() << Tr::tr("Compiler dumping failed:\n%1")
- .arg(QString::fromUtf8(out));
+ qbsWarning() << Tr::tr("Compiler dumping failed:\n%1").arg(QString::fromUtf8(out));
return {};
}
- return p.readAllStandardOutput();
+ static QRegularExpression re(QStringLiteral("\\r?\\n"));
+ return QString::fromUtf8(p.readAllStandardOutput()).split(re);
}
-static QString dumpSdccArchitecture(const QFileInfo &compiler,
- const QString &arch)
+static bool supportsSdccArchitecture(const QFileInfo &compiler, QStringView flag)
{
- const auto targetFlag = QStringLiteral("-m%1").arg(arch);
- const auto token = QStringLiteral("__SDCC_%1").arg(arch);
- const auto macros = dumpSdccMacros(compiler, targetFlag);
- return macros.contains(token.toLatin1()) ? arch : QString();
+ const auto target = QStringLiteral("-m%1").arg(flag);
+ const auto macros = dumpMacros([&compiler, &target]() {
+ return dumpOutput(compiler, target); });
+ const auto token = QStringLiteral("__SDCC_%1").arg(flag);
+ return macros.contains(token);
}
-static std::vector<Profile> createSdccProfileHelper(
- const ToolchainInstallInfo &info,
- Settings *settings,
- const QString &profileName = QString())
+static std::vector<Profile> createSdccProfileHelper(const ToolchainInstallInfo &info,
+ Settings *settings,
+ const QString &profileName = QString())
{
const QFileInfo compiler = info.compilerPath;
-
std::vector<Profile> profiles;
- const char *knownArchs[] = {"mcs51", "stm8"};
+ static constexpr struct KnownArch {
+ QStringView architecture;
+ QStringView flag;
+ } knownArchs[] = {{u"mcs51", u"mcs51"}, {u"stm8", u"stm8"}, {u"hcs8", u"hc08"}};
+
for (const auto &knownArch : knownArchs) {
- const auto actualArch = dumpSdccArchitecture(
- compiler, QString::fromLatin1(knownArch));
// Don't create a profile in case the compiler does
// not support the proposed architecture.
- if (actualArch != QString::fromLatin1(knownArch))
+ if (!supportsSdccArchitecture(compiler, knownArch.flag))
continue;
- QString fullProfileName = profileName;
- if (fullProfileName.isEmpty()) {
+ QString fullProfileName;
+ if (profileName.isEmpty()) {
// Create a full profile name in case we is
// in auto-detecting mode.
if (!info.compilerVersion.isValid()) {
- fullProfileName = QStringLiteral("sdcc-unknown-%1")
- .arg(actualArch);
+ fullProfileName = QStringLiteral("sdcc-unknown-%1").arg(knownArch.architecture);
} else {
const QString version = info.compilerVersion.toString(
QLatin1Char('_'), QLatin1Char('_'));
fullProfileName = QStringLiteral("sdcc-%1-%2").arg(
- version, actualArch);
+ version, knownArch.architecture);
}
} else {
// Append the detected actual architecture name
// to the proposed profile name.
- fullProfileName = QStringLiteral("%1-%2").arg(
- fullProfileName, actualArch);
+ fullProfileName = QStringLiteral("%1-%2").arg(profileName, knownArch.architecture);
}
Profile profile(fullProfileName, settings);
- profile.setValue(QStringLiteral("cpp.toolchainInstallPath"),
- compiler.absolutePath());
- profile.setValue(QStringLiteral("qbs.toolchainType"),
- QStringLiteral("sdcc"));
- if (!actualArch.isEmpty())
- profile.setValue(QStringLiteral("qbs.architecture"), actualArch);
+ profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), compiler.absolutePath());
+ profile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("sdcc"));
+ profile.setValue(QStringLiteral("qbs.architecture"), knownArch.architecture.toString());
qbsInfo() << Tr::tr("Profile '%1' created for '%2'.").arg(
profile.name(), compiler.absoluteFilePath());
+
+ profiles.push_back(std::move(profile));
}
return profiles;
}
-static Version dumpSdccCompilerVersion(const QFileInfo &compiler)
+static Version dumpSdccVersion(const QFileInfo &compiler)
{
- const QByteArray dump = dumpSdccMacros(compiler);
- if (dump.isEmpty())
- return Version{};
-
- const int major = extractVersion(dump, "__SDCC_VERSION_MAJOR ");
- const int minor = extractVersion(dump, "__SDCC_VERSION_MINOR ");
- const int patch = extractVersion(dump, "__SDCC_VERSION_PATCH ");
- if (major < 0 || minor < 0 || patch < 0) {
- qbsWarning() << Tr::tr("No '__SDCC_VERSION_xxx' token was found "
- "in the compiler dump:\n%1")
- .arg(QString::fromUtf8(dump));
+ const auto macros = dumpMacros([&compiler]() { return dumpOutput(compiler); });
+ if (!macros.contains(QLatin1String("__SDCC"))) {
+ qbsWarning() << Tr::tr("No __SDCC token was found in the compiler dump");
return Version{};
}
- return Version{major, minor, patch};
+ auto value = macros.value(QLatin1String("__SDCC"));
+ value.replace(QLatin1Char('_'), QLatin1Char('.'));
+ return Version::fromString(value);
}
static std::vector<ToolchainInstallInfo> installedSdccsFromPath()
@@ -180,7 +169,7 @@ static std::vector<ToolchainInstallInfo> installedSdccsFromPath()
HostOsInfo::appendExecutableSuffix(compilerName)));
if (!sdccPath.exists())
continue;
- const Version version = dumpSdccCompilerVersion(sdccPath);
+ const Version version = dumpSdccVersion(sdccPath);
infos.push_back({sdccPath, version});
}
std::sort(infos.begin(), infos.end());
@@ -234,8 +223,10 @@ static std::vector<ToolchainInstallInfo> installedSdccsFromRegistry()
return candidate == SdccInstallInfo{
info.compilerPath.filePath(), info.compilerVersion.toString()};
});
- if (infosIt == infosEnd)
- infos.push_back({candidate.compilerPath, Version::fromString(candidate.version)});
+ if (infosIt == infosEnd) {
+ infos.push_back({QFileInfo(candidate.compilerPath),
+ Version::fromString(candidate.version)});
+ }
}
}
@@ -252,7 +243,7 @@ bool isSdccCompiler(const QString &compilerName)
}
void createSdccProfile(const QFileInfo &compiler, Settings *settings,
- QString profileName)
+ const QString &profileName)
{
const ToolchainInstallInfo info = {compiler, Version{}};
createSdccProfileHelper(info, settings, profileName);
@@ -271,13 +262,13 @@ void sdccProbe(Settings *settings, std::vector<Profile> &profiles)
pathInfos.cbegin(), pathInfos.cend(),
std::back_inserter(allInfos));
+ if (allInfos.empty())
+ qbsInfo() << Tr::tr("No SDCC toolchains found.");
+
for (const ToolchainInstallInfo &info : allInfos) {
const auto newProfiles = createSdccProfileHelper(info, settings);
profiles.reserve(profiles.size() + int(newProfiles.size()));
std::copy(newProfiles.cbegin(), newProfiles.cend(),
std::back_inserter(profiles));
}
-
- if (allInfos.empty())
- qbsInfo() << Tr::tr("No SDCC toolchains found.");
}
diff --git a/src/app/qbs-setup-toolchains/sdccprobe.h b/src/app/qbs-setup-toolchains/sdccprobe.h
index 3d26583d9..4c913ddeb 100644
--- a/src/app/qbs-setup-toolchains/sdccprobe.h
+++ b/src/app/qbs-setup-toolchains/sdccprobe.h
@@ -68,7 +68,7 @@ inline bool operator==(const SdccInstallInfo &lhs, const SdccInstallInfo &rhs)
bool isSdccCompiler(const QString &compilerName);
void createSdccProfile(const QFileInfo &compiler, qbs::Settings *settings,
- QString profileName);
+ const QString &profileName);
void sdccProbe(qbs::Settings *settings, std::vector<qbs::Profile> &profiles);
diff --git a/src/app/qbs-setup-toolchains/watcomprobe.cpp b/src/app/qbs-setup-toolchains/watcomprobe.cpp
new file mode 100644
index 000000000..cc9fee2b4
--- /dev/null
+++ b/src/app/qbs-setup-toolchains/watcomprobe.cpp
@@ -0,0 +1,255 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 Denis Shienkov <denis.shienkov@gmail.com>
+** 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 "watcomprobe.h"
+#include "probe.h"
+
+#include "../shared/logging/consolelogger.h"
+
+#include <logging/translator.h>
+
+#include <tools/hostosinfo.h>
+#include <tools/profile.h>
+
+#include <QtCore/qdir.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qsettings.h>
+#include <QtCore/qtemporaryfile.h>
+
+using namespace qbs;
+using Internal::HostOsInfo;
+using Internal::Tr;
+
+namespace {
+
+struct Details {
+ QStringView architecture;
+ QStringView platform;
+};
+
+constexpr struct Platform {
+ QStringView flag;
+ Details keys;
+ Details target;
+} knownPlatforms[] = {
+ // DOS 16/32 bit.
+ {u"-bdos", {u"__I86__", u"__DOS__"}, {u"x86_16", u"dos"}},
+ {u"-bdos4g", {u"__386__", u"__DOS__"}, {u"x86", u"dos"}},
+ // Windows 16/32 bit.
+ {u"-bwindows", {u"__I86__", u"__WINDOWS__"}, {u"x86_16", u"windows"}},
+ {u"-bnt", {u"__386__", u"__NT__"}, {u"x86", u"windows"}},
+ // OS/2 16/32 bit.
+ {u"-bos2", {u"__I86__", u"__OS2__"}, {u"x86_16", u"os2"}},
+ {u"-bos2v2", {u"__386__", u"__OS2__"}, {u"x86", u"os2"}},
+ // Linux 32 bit.
+ {u"-blinux", {u"__386__", u"__LINUX__"}, {u"x86", u"linux"}},
+};
+
+} // namespace
+
+static QStringList knownWatcomCompilerNames()
+{
+ return {QStringLiteral("owcc")};
+}
+
+static QStringList dumpOutput(const QFileInfo &compiler, QStringView flag,
+ const QList<QStringView> &keys)
+{
+ const QString filePath = QDir(QDir::tempPath()).absoluteFilePath(
+ QLatin1String("watcom-dump.c"));
+ QFile fakeIn(filePath);
+ if (!fakeIn.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
+ qbsWarning() << Tr::tr("Unable to open temporary file %1 for output: %2")
+ .arg(fakeIn.fileName(), fakeIn.errorString());
+ return {};
+ }
+ fakeIn.write("#define VALUE_TO_STRING(x) #x\n");
+ fakeIn.write("#define VALUE(x) VALUE_TO_STRING(x)\n");
+ fakeIn.write("#define VAR_NAME_VALUE(var) \"#define \" #var\" \"VALUE(var)\n");
+ for (const auto &key : keys) {
+ fakeIn.write("#if defined(" + key.toLatin1() + ")\n");
+ fakeIn.write("#pragma message (VAR_NAME_VALUE(" + key.toLatin1() + "))\n");
+ fakeIn.write("#endif\n");
+ }
+ fakeIn.close();
+ QProcess p;
+ QStringList args;
+ if (!flag.isEmpty())
+ args.push_back(flag.toString());
+ args.push_back(QDir::toNativeSeparators(filePath));
+ p.start(compiler.absoluteFilePath(), args);
+ p.waitForFinished(3000);
+ fakeIn.remove();
+ QStringList lines = QString::fromUtf8(p.readAllStandardOutput())
+ .split(QRegularExpression(QLatin1String("\\r?\\n")));
+ return lines;
+}
+
+static bool supportsWatcomPlatform(const QFileInfo &compiler, const Platform &platform)
+{
+ const auto macros = dumpMacros([&compiler, &platform]() {
+ const QList<QStringView> keys = {platform.keys.architecture, platform.keys.platform};
+ return dumpOutput(compiler, platform.flag, keys); });
+
+ auto matches = [&macros](QStringView key) {
+ const auto k = key.toString();
+ if (!macros.contains(k))
+ return false;
+ return macros.value(k) == QLatin1String("1");
+ };
+
+ return matches(platform.keys.architecture) && matches(platform.keys.platform);
+}
+
+static std::vector<Profile> createWatcomProfileHelper(const ToolchainInstallInfo &info,
+ Settings *settings,
+ QStringView profileName = {})
+{
+ const QFileInfo compiler = info.compilerPath;
+ std::vector<Profile> profiles;
+
+ for (const auto &knownPlatform : knownPlatforms) {
+ // Don't create a profile in case the compiler does
+ // not support the proposed architecture.
+ if (!supportsWatcomPlatform(compiler, knownPlatform))
+ continue;
+
+ QString fullProfilename;
+ if (profileName.isEmpty()) {
+ // Create a full profile name in case we is in auto-detecting mode.
+ if (!info.compilerVersion.isValid()) {
+ fullProfilename = QStringLiteral("watcom-unknown-%1-%2")
+ .arg(knownPlatform.target.platform)
+ .arg(knownPlatform.target.architecture);
+ } else {
+ const QString version= info.compilerVersion.toString(QLatin1Char('_'),
+ QLatin1Char('_'));
+ fullProfilename = QStringLiteral("watcom-%1-%2-%3")
+ .arg(version)
+ .arg(knownPlatform.target.platform)
+ .arg(knownPlatform.target.architecture);
+ }
+ } else {
+ // Append the detected actual architecture name to the proposed profile name.
+ fullProfilename = QStringLiteral("%1-%2-%3")
+ .arg(profileName)
+ .arg(knownPlatform.target.platform)
+ .arg(knownPlatform.target.architecture);
+ }
+
+ Profile profile(fullProfilename, settings);
+ profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), compiler.absolutePath());
+ profile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("watcom"));
+ profile.setValue(QStringLiteral("qbs.architecture"),
+ knownPlatform.target.architecture.toString());
+ profile.setValue(QStringLiteral("qbs.targetPlatform"),
+ knownPlatform.target.platform.toString());
+
+ qbsInfo() << Tr::tr("Profile '%1' created for '%2'.")
+ .arg(profile.name(), compiler.absoluteFilePath());
+
+ profiles.push_back(std::move(profile));
+ }
+
+ return profiles;
+}
+
+static Version dumpWatcomVersion(const QFileInfo &compiler)
+{
+ const QList<QStringView> keys = {u"__WATCOMC__", u"__WATCOM_CPLUSPLUS__"};
+ const auto macros = dumpMacros([&compiler, &keys]() {
+ return dumpOutput(compiler, u"", keys); });
+ for (const auto &macro : macros) {
+ const int verCode = macro.toInt();
+ return Version{(verCode - 1100) / 100,
+ (verCode / 10) % 10,
+ ((verCode % 10) > 0) ? (verCode % 10) : 0};
+ }
+ qbsWarning() << Tr::tr("No __WATCOMC__ or __WATCOM_CPLUSPLUS__ tokens was found"
+ " in the compiler dump");
+ return Version{};
+}
+
+static std::vector<ToolchainInstallInfo> installedWatcomsFromPath()
+{
+ std::vector<ToolchainInstallInfo> infos;
+ const auto compilerNames = knownWatcomCompilerNames();
+ for (const QString &compilerName : compilerNames) {
+ const QFileInfo watcomPath(findExecutable(
+ HostOsInfo::appendExecutableSuffix(compilerName)));
+ if (!watcomPath.exists())
+ continue;
+ const Version version = dumpWatcomVersion(watcomPath);
+ infos.push_back({watcomPath, version});
+ }
+ std::sort(infos.begin(), infos.end());
+ return infos;
+}
+
+bool isWatcomCompiler(const QString &compilerName)
+{
+ return Internal::any_of(knownWatcomCompilerNames(), [compilerName](const QString &knownName) {
+ return compilerName.contains(knownName);
+ });
+}
+
+void createWatcomProfile(const QFileInfo &compiler, Settings *settings, QStringView profileName)
+{
+ const ToolchainInstallInfo info = {compiler, Version{}};
+ createWatcomProfileHelper(info, settings, profileName);
+}
+
+void watcomProbe(Settings *settings, std::vector<Profile> &profiles)
+{
+ qbsInfo() << Tr::tr("Trying to detect WATCOM toolchains...");
+
+ const std::vector<ToolchainInstallInfo> allInfos = installedWatcomsFromPath();
+ if (allInfos.empty()) {
+ qbsInfo() << Tr::tr("No WATCOM toolchains found.");
+ return;
+ }
+
+ for (const ToolchainInstallInfo &info : allInfos) {
+ const auto newProfiles = createWatcomProfileHelper(info, settings);
+ profiles.reserve(profiles.size() + int(newProfiles.size()));
+ std::copy(newProfiles.cbegin(), newProfiles.cend(), std::back_inserter(profiles));
+ }
+}
diff --git a/src/app/qbs-setup-toolchains/watcomprobe.h b/src/app/qbs-setup-toolchains/watcomprobe.h
new file mode 100644
index 000000000..ea010461d
--- /dev/null
+++ b/src/app/qbs-setup-toolchains/watcomprobe.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 Denis Shienkov <denis.shienkov@gmail.com>
+** 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_SETUPTOOLCHAINS_WATCOMPROBE_H
+#define QBS_SETUPTOOLCHAINS_WATCOMPROBE_H
+
+#include <QtCore/qlist.h>
+
+QT_BEGIN_NAMESPACE
+class QFileInfo;
+QT_END_NAMESPACE
+
+namespace qbs {
+class Profile;
+class Settings;
+} // namespace qbs
+
+bool isWatcomCompiler(const QString &compilerName);
+void createWatcomProfile(
+ const QFileInfo &compiler, qbs::Settings *settings, QStringView profileName);
+void watcomProbe(qbs::Settings *settings, std::vector<qbs::Profile> &profiles);
+
+#endif // Header guard
diff --git a/src/app/qbs-setup-toolchains/xcodeprobe.cpp b/src/app/qbs-setup-toolchains/xcodeprobe.cpp
index 3ac243028..ea27e4143 100644
--- a/src/app/qbs-setup-toolchains/xcodeprobe.cpp
+++ b/src/app/qbs-setup-toolchains/xcodeprobe.cpp
@@ -45,6 +45,7 @@
#include <logging/translator.h>
#include <tools/architectures.h>
#include <tools/profile.h>
+#include <tools/qttools.h>
#include <tools/settings.h>
#include <QtCore/qstringlist.h>
@@ -62,7 +63,7 @@ using Internal::Tr;
static const QString defaultDeveloperPath =
QStringLiteral("/Applications/Xcode.app/Contents/Developer");
static const std::regex defaultDeveloperPathRegex(
- "^/Applications/Xcode([a-zA-Z0-9 _-]+)\\.app/Contents/Developer$");
+ "^/Applications/Xcode([a-zA-Z0-9 ._-]+)\\.app/Contents/Developer$");
static QString targetOS(const QString &applePlatformName)
{
@@ -94,6 +95,8 @@ static QStringList archList(const QString &applePlatformName)
archs << QStringLiteral("x86");
if (applePlatformName != QStringLiteral("watchsimulator"))
archs << QStringLiteral("x86_64");
+ if (applePlatformName == QStringLiteral("macosx"))
+ archs << QStringLiteral("arm64");
} else if (applePlatformName == QStringLiteral("iphoneos")
|| applePlatformName == QStringLiteral("appletvos")) {
if (applePlatformName != QStringLiteral("appletvos"))
@@ -147,7 +150,7 @@ void XcodeProbe::detectDeveloperPaths()
if (!selectedXcode.waitForFinished(-1) || selectedXcode.exitCode()) {
qbsInfo() << Tr::tr("Could not detect selected Xcode with /usr/bin/xcode-select");
} else {
- QString path = QString::fromLocal8Bit(selectedXcode.readAllStandardOutput());
+ QString path = QString::fromLocal8Bit(selectedXcode.readAllStandardOutput().trimmed());
addDeveloperPath(path);
}
addDeveloperPath(defaultDeveloperPath);
@@ -160,7 +163,7 @@ void XcodeProbe::detectDeveloperPaths()
qbsInfo() << Tr::tr("Could not detect additional Xcode installations with /usr/bin/mdfind");
} else {
const auto paths = QString::fromLocal8Bit(launchServices.readAllStandardOutput())
- .split(QLatin1Char('\n'), QString::SkipEmptyParts);
+ .split(QLatin1Char('\n'), Qt::SkipEmptyParts);
for (const QString &path : paths)
addDeveloperPath(path + QStringLiteral("/Contents/Developer"));
}
@@ -168,15 +171,11 @@ void XcodeProbe::detectDeveloperPaths()
void XcodeProbe::setupDefaultToolchains(const QString &devPath, const QString &xcodeName)
{
- qbsInfo() << Tr::tr("Profile '%1' created for '%2'.").arg(xcodeName).arg(devPath);
+ qbsInfo() << Tr::tr("Profile '%1' created for '%2'.").arg(xcodeName, devPath);
Profile installationProfile(xcodeName, settings);
installationProfile.removeProfile();
- installationProfile.setValue(QStringLiteral("qbs.toolchain"), QStringList()
- << QStringLiteral("xcode")
- << QStringLiteral("clang")
- << QStringLiteral("llvm")
- << QStringLiteral("gcc"));
+ installationProfile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("xcode"));
if (devPath != defaultDeveloperPath)
installationProfile.setValue(QStringLiteral("xcode.developerPath"), devPath);
profiles.push_back(installationProfile);
@@ -189,7 +188,7 @@ void XcodeProbe::setupDefaultToolchains(const QString &devPath, const QString &x
<< QStringLiteral("appletvsimulator")
<< QStringLiteral("watchos")
<< QStringLiteral("watchsimulator");
- for (const QString &platform : qAsConst(platforms)) {
+ for (const QString &platform : std::as_const(platforms)) {
Profile platformProfile(xcodeName + QLatin1Char('-') + platform, settings);
platformProfile.removeProfile();
platformProfile.setBaseProfile(installationProfile.name());
@@ -213,14 +212,15 @@ void XcodeProbe::detectAll()
{
int i = 1;
detectDeveloperPaths();
- for (const QString &developerPath : qAsConst(developerPaths)) {
+ for (const QString &developerPath : std::as_const(developerPaths)) {
QString profileName = QStringLiteral("xcode");
if (developerPath != defaultDeveloperPath) {
const auto devPath = developerPath.toStdString();
std::smatch match;
if (std::regex_match(devPath, match, defaultDeveloperPathRegex))
- profileName += QString::fromStdString(match[1]).toLower().replace(QLatin1Char(' '),
- QLatin1Char('-'));
+ profileName += QString::fromStdString(match[1]).toLower().
+ replace(QLatin1Char(' '), QLatin1Char('-')).
+ replace(QLatin1Char('.'), QLatin1Char('_'));
else
profileName += QString::number(i++);
}
diff --git a/src/app/qbs/CMakeLists.txt b/src/app/qbs/CMakeLists.txt
new file mode 100644
index 000000000..cd9240f98
--- /dev/null
+++ b/src/app/qbs/CMakeLists.txt
@@ -0,0 +1,56 @@
+set(SOURCES
+ application.cpp
+ application.h
+ commandlinefrontend.cpp
+ commandlinefrontend.h
+ consoleprogressobserver.cpp
+ consoleprogressobserver.h
+ ctrlchandler.cpp
+ ctrlchandler.h
+ lspserver.cpp
+ lspserver.h
+ 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
+ )
+
+set(PARSER_SOURCES
+ commandlineoption.cpp
+ commandlineoption.h
+ commandlineoptionpool.cpp
+ commandlineoptionpool.h
+ commandlineparser.cpp
+ commandlineparser.h
+ commandpool.cpp
+ commandpool.h
+ commandtype.h
+ parsercommand.cpp
+ parsercommand.h
+ )
+list_transform_prepend(PARSER_SOURCES parser/)
+
+add_qbs_app(qbs
+ DEFINES
+ "QBS_VERSION=\"${QBS_VERSION}\""
+ "QBS_RELATIVE_LIBEXEC_PATH=\"${QBS_RELATIVE_LIBEXEC_PATH}\""
+ "QBS_RELATIVE_SEARCH_PATH=\"${QBS_RELATIVE_SEARCH_PATH}\""
+ "QBS_RELATIVE_PLUGINS_PATH=\"${QBS_RELATIVE_PLUGINS_PATH}\""
+ DEPENDS
+ qbscore
+ qbsconsolelogger
+ qtclsp
+ Qt${QT_VERSION_MAJOR}::Network
+ SOURCES ${SOURCES} ${PARSER_SOURCES}
+ )
+
+add_dependencies(qbs qbs_cpp_scanner qbs_qt_scanner)
diff --git a/src/app/qbs/commandlinefrontend.cpp b/src/app/qbs/commandlinefrontend.cpp
index f204602a6..200740145 100644
--- a/src/app/qbs/commandlinefrontend.cpp
+++ b/src/app/qbs/commandlinefrontend.cpp
@@ -78,6 +78,7 @@ CommandLineFrontend::CommandLineFrontend(const CommandLineParser &parser, Settin
CommandLineFrontend::~CommandLineFrontend()
{
m_cancelTimer->stop();
+ delete m_observer;
}
// Called from interrupt handler. Don't do anything non-trivial here.
@@ -96,9 +97,9 @@ void CommandLineFrontend::checkCancelStatus()
m_cancelTimer->stop();
if (m_resolveJobs.empty() && m_buildJobs.empty())
std::exit(EXIT_FAILURE);
- for (AbstractJob * const job : qAsConst(m_resolveJobs))
+ for (AbstractJob * const job : std::as_const(m_resolveJobs))
job->cancel();
- for (AbstractJob * const job : qAsConst(m_buildJobs))
+ for (AbstractJob * const job : std::as_const(m_buildJobs))
job->cancel();
break;
case CancelStatusCanceling:
@@ -139,7 +140,7 @@ void CommandLineFrontend::start()
}
if (m_parser.showVersion()) {
- puts(QBS_VERSION);
+ std::puts(QBS_VERSION);
qApp->exit(EXIT_SUCCESS);
return;
}
@@ -151,11 +152,11 @@ void CommandLineFrontend::start()
params.setDryRun(m_parser.dryRun());
params.setForceProbeExecution(m_parser.forceProbesExecution());
params.setWaitLockBuildGraph(m_parser.waitLockBuildGraph());
- params.setFallbackProviderEnabled(!m_parser.disableFallbackProvider());
params.setLogElapsedTime(m_parser.logTime());
params.setSettingsDirectory(m_settings->baseDirectory());
params.setOverrideBuildGraphData(m_parser.command() == ResolveCommandType);
params.setPropertyCheckingMode(ErrorHandlingMode::Strict);
+ params.setDeprecationWarningMode(m_parser.deprecationWarningMode());
if (!m_parser.buildBeforeInstalling() || !m_parser.commandCanResolve())
params.setRestoreBehavior(SetupProjectParameters::RestoreOnly);
const auto buildConfigs = m_parser.buildConfigurations();
@@ -182,6 +183,7 @@ void CommandLineFrontend::start()
params.setConfigurationName(configurationName);
params.setBuildRoot(buildDirectory(profileName));
params.setOverriddenValues(userConfig);
+ params.setMaxJobCount(m_parser.jobCount(profileName));
SetupProjectJob * const job = Project().setupProject(params,
ConsoleLogger::instance().logSink(), this);
connectJob(job);
@@ -347,13 +349,12 @@ CommandLineFrontend::ProductMap CommandLineFrontend::productsToUse() const
ProductMap products;
QStringList productNames;
const bool useAll = m_parser.products().empty();
- for (const Project &project : qAsConst(m_projects)) {
+ for (const Project &project : std::as_const(m_projects)) {
QList<ProductData> &productList = products[project];
const ProjectData projectData = project.projectData();
- const auto products = projectData.allProducts();
- for (const ProductData &product : products) {
- productNames << product.name();
- if (useAll || m_parser.products().contains(product.name())) {
+ for (const ProductData &product : projectData.allProducts()) {
+ productNames << product.fullDisplayName();
+ if (useAll || m_parser.products().contains(product.fullDisplayName())) {
productList.push_back(product);
}
}
@@ -431,7 +432,7 @@ void CommandLineFrontend::handleProjectsResolved()
void CommandLineFrontend::makeClean()
{
if (m_parser.products().empty()) {
- for (const Project &project : qAsConst(m_projects)) {
+ for (const Project &project : std::as_const(m_projects)) {
m_buildJobs << project.cleanAllProducts(m_parser.cleanOptions(project.profile()), this);
}
} else {
@@ -489,9 +490,22 @@ QString CommandLineFrontend::buildDirectory(const QString &profileName) const
}
QString projectName(QFileInfo(m_parser.projectFilePath()).baseName());
+ QString originalBuildDir = buildDir;
buildDir.replace(BuildDirectoryOption::magicProjectString(), projectName);
+ const QString buildDirPlaceHolderMsgTemplate = Tr::tr(
+ "You must provide the path to the project file when using build directory "
+ "placeholder '%1'.");
+ if (buildDir != originalBuildDir && projectName.isEmpty()) {
+ throw ErrorInfo(
+ buildDirPlaceHolderMsgTemplate.arg(BuildDirectoryOption::magicProjectString()));
+ }
QString projectDir(QFileInfo(m_parser.projectFilePath()).path());
+ originalBuildDir = buildDir;
buildDir.replace(BuildDirectoryOption::magicProjectDirString(), projectDir);
+ if (buildDir != originalBuildDir && projectDir.isEmpty()) {
+ throw ErrorInfo(
+ buildDirPlaceHolderMsgTemplate.arg(BuildDirectoryOption::magicProjectDirString()));
+ }
if (!QFileInfo(buildDir).isAbsolute())
buildDir = QDir::currentPath() + QLatin1Char('/') + buildDir;
buildDir = QDir::cleanPath(buildDir);
@@ -503,7 +517,7 @@ void CommandLineFrontend::build()
if (m_parser.products().empty()) {
const Project::ProductSelection productSelection = m_parser.withNonDefaultProducts()
? Project::ProductSelectionWithNonDefault : Project::ProductSelectionDefaultOnly;
- for (const Project &project : qAsConst(m_projects))
+ for (const Project &project : std::as_const(m_projects))
m_buildJobs << project.buildAllProducts(buildOptions(project), productSelection, this);
} else {
const ProductMap &products = productsToUse();
@@ -564,7 +578,7 @@ int CommandLineFrontend::runTarget()
const QString executableFilePath = productToRun.targetExecutable();
if (executableFilePath.isEmpty()) {
throw ErrorInfo(Tr::tr("Cannot run: Product '%1' is not an application.")
- .arg(productToRun.name()));
+ .arg(productToRun.fullDisplayName()));
}
RunEnvironment runEnvironment = m_projects.front().getRunEnvironment(productToRun,
m_parser.installOptions(m_projects.front().profile()),
@@ -609,7 +623,7 @@ void CommandLineFrontend::listProducts()
void CommandLineFrontend::connectBuildJobs()
{
- for (AbstractJob * const job : qAsConst(m_buildJobs))
+ for (AbstractJob * const job : std::as_const(m_buildJobs))
connectBuildJob(job);
}
@@ -646,9 +660,8 @@ ProductData CommandLineFrontend::getTheOneRunnableProduct()
QBS_CHECK(m_projects.size() == 1); // Has been checked earlier.
if (m_parser.products().size() == 1) {
- const auto products = m_projects.front().projectData().allProducts();
- for (const ProductData &p : products) {
- if (p.name() == m_parser.products().constFirst())
+ for (const ProductData &p : m_projects.front().projectData().allProducts()) {
+ if (p.fullDisplayName() == m_parser.products().constFirst())
return p;
}
QBS_CHECK(false);
@@ -656,8 +669,7 @@ ProductData CommandLineFrontend::getTheOneRunnableProduct()
QBS_CHECK(m_parser.products().isEmpty());
QList<ProductData> runnableProducts;
- const auto products = m_projects.front().projectData().allProducts();
- for (const ProductData &p : products) {
+ for (const ProductData &p : m_projects.front().projectData().allProducts()) {
if (p.isRunnable())
runnableProducts.push_back(p);
}
@@ -673,14 +685,8 @@ ProductData CommandLineFrontend::getTheOneRunnableProduct()
ErrorInfo error(Tr::tr("Ambiguous use of command '%1': No product given, but project "
"has more than one runnable product.").arg(m_parser.commandName()));
error.append(Tr::tr("Use the '--products' option with one of the following products:"));
- for (const ProductData &p : qAsConst(runnableProducts)) {
- QString productRepr = QLatin1String("\t") + p.name();
- if (p.profile() != m_projects.front().profile()) {
- productRepr.append(QLatin1String(" [")).append(p.profile())
- .append(QLatin1Char(']'));
- }
- error.append(productRepr);
- }
+ for (const ProductData &p : std::as_const(runnableProducts))
+ error.append(QLatin1String("\t") + p.fullDisplayName());
throw error;
}
diff --git a/src/app/qbs/lspserver.cpp b/src/app/qbs/lspserver.cpp
new file mode 100644
index 000000000..c6cce6706
--- /dev/null
+++ b/src/app/qbs/lspserver.cpp
@@ -0,0 +1,629 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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 "lspserver.h"
+
+#include <api/projectdata.h>
+#include <logging/translator.h>
+#include <lsp/basemessage.h>
+#include <lsp/completion.h>
+#include <lsp/initializemessages.h>
+#include <lsp/jsonrpcmessages.h>
+#include <lsp/messages.h>
+#include <lsp/servercapabilities.h>
+#include <lsp/textsynchronization.h>
+#include <parser/qmljsastvisitor_p.h>
+#include <parser/qmljslexer_p.h>
+#include <parser/qmljsparser_p.h>
+#include <tools/qbsassert.h>
+#include <tools/stlutils.h>
+
+#include <QBuffer>
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QMap>
+
+#include <unordered_map>
+#ifdef Q_OS_WINDOWS
+#include <process.h>
+#else
+#include <unistd.h>
+#endif
+
+namespace qbs::Internal {
+
+static int getPid()
+{
+#ifdef Q_OS_WINDOWS
+ return _getpid();
+#else
+ return getpid();
+#endif
+}
+
+using LspErrorResponse = lsp::ResponseError<std::nullptr_t>;
+using LspErrorCode = LspErrorResponse::ErrorCodes;
+
+class Document {
+public:
+ bool isPositionUpToDate(const CodePosition &pos) const;
+ bool isPositionUpToDate(const lsp::Position &pos) const;
+
+ QString savedContent;
+ QString currentContent;
+};
+
+static CodePosition posFromLspPos(const lsp::Position &pos)
+{
+ return {pos.line() + 1, pos.character() + 1};
+}
+
+static lsp::Position lspPosFromCodeLocation(const CodeLocation &loc)
+{
+ return {loc.line() - 1, loc.column() - 1};
+}
+
+static QString uriToString(const lsp::DocumentUri &uri)
+{
+ return uri.toFilePath([](const lsp::Utils::FilePath &fp) { return fp; });
+}
+
+static int posToOffset(const CodePosition &pos, const QString &doc);
+static int posToOffset(const lsp::Position &pos, const QString &doc)
+{
+ return posToOffset(posFromLspPos(pos), doc);
+}
+
+class AstNodeLocator : public QbsQmlJS::AST::Visitor
+{
+public:
+ AstNodeLocator(int position, QbsQmlJS::AST::UiProgram &ast)
+ : m_position(position)
+ {
+ ast.accept(this);
+ }
+
+ QList<QbsQmlJS::AST::Node *> path() const { return m_path; }
+
+private:
+ bool preVisit(QbsQmlJS::AST::Node *node) override
+ {
+ if (int(node->firstSourceLocation().offset) > m_position)
+ return false;
+ if (int(node->lastSourceLocation().offset) < m_position)
+ return false;
+ m_path << node;
+ return true;
+ }
+
+ const int m_position;
+ QList<QbsQmlJS::AST::Node *> m_path;
+};
+
+class LspServer::Private
+{
+public:
+ void setupConnection();
+ void handleIncomingData();
+ void discardSocket();
+ template<typename T> void sendResponse(const T &msg);
+ void sendErrorResponse(LspErrorCode code, const QString &message);
+ void sendErrorNotification(const QString &message);
+ void sendNoSuchDocumentError(const QString &filePath);
+ void sendMessage(const lsp::JsonObject &msg);
+ void sendMessage(const lsp::JsonRpcMessage &msg);
+ void handleCurrentMessage();
+ void handleInitializeRequest();
+ void handleInitializedNotification();
+ void handleGotoDefinitionRequest();
+ void handleCompletionRequest();
+ void handleShutdownRequest();
+ void handleDidOpenNotification();
+ void handleDidChangeNotification();
+ void handleDidSaveNotification();
+ void handleDidCloseNotification();
+
+ QLocalServer server;
+ QBuffer incomingData;
+ lsp::BaseMessage currentMessage;
+ QJsonObject messageObject;
+ QLocalSocket *socket = nullptr;
+ ProjectData projectData;
+ CodeLinks codeLinks;
+ std::unordered_map<QString, Document> documents;
+
+ enum class State { None, InitRequest, InitNotification, Shutdown };
+ State state = State::None;
+};
+
+LspServer::LspServer() : d(new Private)
+{
+ if (!d->server.listen(QString::fromLatin1("qbs-lsp-%1").arg(getPid()))) {
+ // This is not fatal, qbs main operation can continue.
+ qWarning() << "failed to open lsp socket:" << d->server.errorString();
+ return;
+ }
+ QObject::connect(&d->server, &QLocalServer::newConnection, [this] {
+ d->socket = d->server.nextPendingConnection();
+ d->setupConnection();
+ d->server.close();
+ });
+}
+
+LspServer::~LspServer() { delete d; }
+
+void LspServer::updateProjectData(const ProjectData &projectData, const CodeLinks &codeLinks)
+{
+ d->projectData = projectData;
+ d->codeLinks = codeLinks;
+}
+
+QString LspServer::socketPath() const { return d->server.fullServerName(); }
+
+void LspServer::Private::setupConnection()
+{
+ QBS_ASSERT(socket, return);
+
+ QObject::connect(socket, &QLocalSocket::errorOccurred, [this] { discardSocket(); });
+ QObject::connect(socket, &QLocalSocket::disconnected, [this] { discardSocket(); });
+ QObject::connect(socket, &QLocalSocket::readyRead, [this] { handleIncomingData(); });
+ incomingData.open(QIODevice::ReadWrite | QIODevice::Append);
+ handleIncomingData();
+}
+
+void LspServer::Private::handleIncomingData()
+{
+ const int pos = incomingData.pos();
+ incomingData.write(socket->readAll());
+ incomingData.seek(pos);
+ QString parseError;
+ lsp::BaseMessage::parse(&incomingData, parseError, currentMessage);
+ if (!parseError.isEmpty())
+ return sendErrorResponse(LspErrorResponse::ParseError, parseError);
+ if (currentMessage.isComplete()) {
+ incomingData.buffer().remove(0, incomingData.pos());
+ incomingData.seek(0);
+ handleCurrentMessage();
+ currentMessage = {};
+ messageObject = {};
+ if (socket)
+ handleIncomingData();
+ }
+}
+
+void LspServer::Private::discardSocket()
+{
+ socket->disconnect();
+ socket->deleteLater();
+ socket = nullptr;
+}
+
+template<typename T> void LspServer::Private::sendResponse(const T &msg)
+{
+ lsp::Response<T, std::nullptr_t> response(lsp::MessageId(messageObject.value(lsp::idKey)));
+ response.setResult(msg);
+ sendMessage(response);
+}
+
+void LspServer::Private::sendErrorResponse(LspErrorCode code, const QString &message)
+{
+ lsp::Response<lsp::JsonObject, std::nullptr_t> response(lsp::MessageId(
+ messageObject.value(lsp::idKey)));
+ lsp::ResponseError<std::nullptr_t> error;
+ error.setCode(code);
+ error.setMessage(message);
+ response.setError(error);
+ sendMessage(response);
+}
+
+void LspServer::Private::sendErrorNotification(const QString &message)
+{
+ lsp::ShowMessageParams params;
+ params.setType(lsp::Error);
+ params.setMessage(message);
+ sendMessage(lsp::ShowMessageNotification(params));
+}
+
+void LspServer::Private::sendNoSuchDocumentError(const QString &filePath)
+{
+ sendErrorNotification(Tr::tr("No such document: '%1'").arg(filePath));
+}
+
+void LspServer::Private::sendMessage(const lsp::JsonObject &msg)
+{
+ sendMessage(lsp::JsonRpcMessage(msg));
+}
+
+void LspServer::Private::sendMessage(const lsp::JsonRpcMessage &msg)
+{
+ lsp::BaseMessage baseMsg = msg.toBaseMessage();
+ socket->write(baseMsg.header());
+ socket->write(baseMsg.content);
+}
+
+void LspServer::Private::handleCurrentMessage()
+{
+ messageObject = lsp::JsonRpcMessage(currentMessage).toJsonObject();
+ const QString method = messageObject.value(lsp::methodKey).toString();
+ if (method == QLatin1String("exit"))
+ return discardSocket();
+ if (state == State::Shutdown) {
+ return sendErrorResponse(LspErrorResponse::InvalidRequest,
+ Tr::tr("Method '%1' not allowed after shutdown."));
+ }
+ if (method == "shutdown")
+ return handleShutdownRequest();
+ if (method == QLatin1String("initialize"))
+ return handleInitializeRequest();
+ if (state == State::None) {
+ return sendErrorResponse(LspErrorResponse::ServerNotInitialized,
+ Tr::tr("First message must be initialize request."));
+ }
+ if (method == "initialized")
+ return handleInitializedNotification();
+ if (method == "textDocument/didOpen")
+ return handleDidOpenNotification();
+ if (method == "textDocument/didChange")
+ return handleDidChangeNotification();
+ if (method == "textDocument/didSave")
+ return handleDidSaveNotification();
+ if (method == "textDocument/didClose")
+ return handleDidCloseNotification();
+ if (method == "textDocument/definition")
+ return handleGotoDefinitionRequest();
+ if (method == "textDocument/completion")
+ return handleCompletionRequest();
+
+ sendErrorResponse(LspErrorResponse::MethodNotFound, Tr::tr("This server can do very little."));
+}
+
+void LspServer::Private::handleInitializeRequest()
+{
+ if (state != State::None) {
+ return sendErrorResponse(LspErrorResponse::InvalidRequest,
+ Tr::tr("Received initialize request in initialized state."));
+ }
+ state = State::InitRequest;
+
+ lsp::ServerInfo serverInfo;
+ serverInfo.insert(lsp::nameKey, "qbs");
+ serverInfo.insert(lsp::versionKey, QBS_VERSION);
+ lsp::InitializeResult result;
+ result.insert(lsp::serverInfoKey, serverInfo);
+ lsp::ServerCapabilities capabilities; // TODO: hover
+ capabilities.setDefinitionProvider(true);
+ capabilities.setTextDocumentSync({int(lsp::TextDocumentSyncKind::Incremental)});
+ lsp::ServerCapabilities::CompletionOptions completionOptions;
+ completionOptions.setTriggerCharacters({"."});
+ capabilities.setCompletionProvider(completionOptions);
+ result.setCapabilities(capabilities);
+ sendResponse(result);
+}
+
+void LspServer::Private::handleInitializedNotification()
+{
+ if (state != State::InitRequest) {
+ return sendErrorResponse(LspErrorResponse::InvalidRequest,
+ Tr::tr("Unexpected initialized notification."));
+ }
+ state = State::InitNotification;
+}
+
+void LspServer::Private::handleGotoDefinitionRequest()
+{
+ const lsp::TextDocumentPositionParams params(messageObject.value(lsp::paramsKey));
+ const QString sourceFile = params.textDocument().uri().toLocalFile();
+ const Document *sourceDoc = nullptr;
+ if (const auto it = documents.find(sourceFile); it != documents.end())
+ sourceDoc = &it->second;
+ const auto fileEntry = codeLinks.constFind(sourceFile);
+ if (fileEntry == codeLinks.constEnd())
+ return sendResponse(nullptr);
+ const CodePosition sourcePos = posFromLspPos(params.position());
+ if (sourceDoc && !sourceDoc->isPositionUpToDate(sourcePos))
+ return sendResponse(nullptr);
+ for (auto it = fileEntry->cbegin(); it != fileEntry->cend(); ++it) {
+ if (!it.key().contains(sourcePos))
+ continue;
+ if (sourceDoc && !sourceDoc->isPositionUpToDate(it.key().end()))
+ return sendResponse(nullptr);
+ QList<CodeLocation> targets = it.value();
+ QBS_ASSERT(!targets.isEmpty(), return sendResponse(nullptr));
+ for (auto it = targets.begin(); it != targets.end();) {
+ const Document *targetDoc = nullptr;
+ if (it->filePath() == sourceFile)
+ targetDoc = sourceDoc;
+ else if (const auto docIt = documents.find(it->filePath()); docIt != documents.end())
+ targetDoc = &docIt->second;
+ if (targetDoc && !targetDoc->isPositionUpToDate(CodePosition(it->line(), it->column())))
+ it = targets.erase(it);
+ else
+ ++it;
+ }
+ struct JsonArray : public QJsonArray { void reserve(std::size_t) {}};
+ const auto locations = transformed<JsonArray>(targets,
+ [](const CodeLocation &loc) {
+ const lsp::Position startPos = lspPosFromCodeLocation(loc);
+ const lsp::Position endPos(startPos.line(), startPos.character() + 1);
+ lsp::Location targetLocation;
+ targetLocation.setUri(lsp::DocumentUri::fromProtocol(
+ QUrl::fromLocalFile(loc.filePath()).toString()));
+ targetLocation.setRange({startPos, endPos});
+ return QJsonObject(targetLocation);
+ });
+ if (locations.size() == 1)
+ return sendResponse(locations.first().toObject());
+ return sendResponse(locations);
+ }
+ sendResponse(nullptr);
+}
+
+// We operate under the assumption that the client has basic QML support.
+// Therefore, we only provide completion for qbs modules and their properties.
+// Only a simple prefix match is implemented, with no regard to the contents after the cursor.
+void LspServer::Private::handleCompletionRequest()
+{
+ if (!projectData.isValid())
+ return sendResponse(nullptr);
+
+ const lsp::CompletionParams params(messageObject.value(lsp::paramsKey));
+ const QString sourceFile = params.textDocument().uri().toLocalFile();
+ const Document *sourceDoc = nullptr;
+ if (const auto it = documents.find(sourceFile); it != documents.end())
+ sourceDoc = &it->second;
+ if (!sourceDoc)
+ return sendResponse(nullptr);
+
+ // If there are products corresponding to this file, check only these when looking for modules.
+ // Otherwise, check all products.
+ const QList<ProductData> allProducts = projectData.allProducts();
+ if (allProducts.isEmpty())
+ return sendResponse(nullptr);
+ QList<ProductData> relevantProducts;
+ for (const ProductData &p : allProducts) {
+ if (p.location().filePath() == sourceFile)
+ relevantProducts << p;
+ }
+ if (relevantProducts.isEmpty())
+ relevantProducts = allProducts;
+
+ QString identifierPrefix;
+ QStringList modulePrefixes;
+ const int offset = posToOffset(params.position(), sourceDoc->currentContent) - 1;
+ if (offset < 0 || offset >= sourceDoc->currentContent.length())
+ return sendResponse(nullptr);
+ const auto collectFromRawString = [&] {
+ int currentPos = offset;
+ const auto constructIdentifier = [&] {
+ QString id;
+ while (currentPos >= 0) {
+ const QChar c = sourceDoc->currentContent.at(currentPos);
+ if (!c.isLetterOrNumber() && c != '_')
+ break;
+ id.prepend(c);
+ --currentPos;
+ }
+ return id;
+ };
+ identifierPrefix = constructIdentifier();
+ while (true) {
+ if (currentPos <= 0 || sourceDoc->currentContent.at(currentPos) != '.')
+ return;
+ --currentPos;
+ const QString modulePrefix = constructIdentifier();
+ if (modulePrefix.isEmpty())
+ return;
+ modulePrefixes.prepend(modulePrefix);
+ }
+ };
+
+ // Parse the current file. Note that completion usually happens on invalid code, which
+ // often confuses the parser so much that it yields unusable results. Therefore, we always
+ // gather our input parameters from the raw string. We only use the parse result to skip
+ // completion in contexts where it is undesirable.
+ QbsQmlJS::Engine engine;
+ QbsQmlJS::Lexer lexer(&engine);
+ lexer.setCode(sourceDoc->currentContent, 1);
+ QbsQmlJS::Parser parser(&engine);
+ parser.parse();
+ if (parser.ast()) {
+ AstNodeLocator locator(offset, *parser.ast());
+ const QList<QbsQmlJS::AST::Node *> &astPath = locator.path();
+ if (!astPath.isEmpty()) {
+ switch (astPath.last()->kind) {
+ case QbsQmlJS::AST::Node::Kind_FieldMemberExpression:
+ case QbsQmlJS::AST::Node::Kind_UiObjectDefinition:
+ case QbsQmlJS::AST::Node::Kind_UiQualifiedId:
+ case QbsQmlJS::AST::Node::Kind_UiScriptBinding:
+ break;
+ default:
+ return sendResponse(nullptr);
+ }
+ }
+ }
+
+ collectFromRawString();
+ if (modulePrefixes.isEmpty() && identifierPrefix.isEmpty())
+ return sendResponse(nullptr); // We do not want to start completion from nothing.
+
+ QJsonArray results;
+ QMap<QString, QString> namesAndTypes;
+ for (const ProductData &product : std::as_const(relevantProducts)) {
+ const PropertyMap &moduleProps = product.moduleProperties();
+ const QStringList allModules = moduleProps.allModules();
+ const QString moduleNameOrPrefix = modulePrefixes.join('.');
+
+ // Case 1: Prefixes match a module name. Identifier can only expand to the name
+ // of a module property.
+ // Example: "Qt.core.a^" -> "Qt.core.availableBuildVariants"
+ if (!modulePrefixes.isEmpty() && allModules.contains(moduleNameOrPrefix)) {
+ for (const PropertyMap::PropertyInfo &info :
+ moduleProps.allPropertiesForModule(moduleNameOrPrefix)) {
+ if (info.isBuiltin)
+ continue;
+ if (!identifierPrefix.isEmpty() && !info.name.startsWith(identifierPrefix))
+ continue;
+ namesAndTypes.insert(info.name, info.type);
+ }
+ continue;
+ }
+
+ // Case 2: Isolated identifier. Can only expand to a module name.
+ // Example: "Q^" -> "Qt.core", "Qt.widgets", ...
+ // Case 3: Prefixes match a module prefix. Identifier can only expand to a module name.
+ // Example: "Qt.c^" -> "Qt.core", "Qt.concurrent", ...
+ QString fullPrefix = identifierPrefix;
+ int nameOffset = 0;
+ if (!modulePrefixes.isEmpty()) {
+ fullPrefix.prepend(moduleNameOrPrefix + '.');
+ nameOffset = moduleNameOrPrefix.length() + 1;
+ }
+ for (const QString &module : allModules) {
+ if (module.startsWith(fullPrefix))
+ namesAndTypes.insert(module.mid(nameOffset), {});
+ }
+ }
+
+ for (auto it = namesAndTypes.cbegin(); it != namesAndTypes.cend(); ++it) {
+ lsp::CompletionItem item;
+ item.setLabel(it.key());
+ if (!it.value().isEmpty())
+ item.setDetail(it.value());
+ results.append(QJsonObject(item));
+ };
+ sendResponse(results);
+}
+
+void LspServer::Private::handleShutdownRequest()
+{
+ state = State::Shutdown;
+ sendResponse(nullptr);
+}
+
+void LspServer::Private::handleDidOpenNotification()
+{
+ const lsp::TextDocumentItem docItem = lsp::DidOpenTextDocumentNotification(messageObject)
+ .params().value_or(lsp::DidOpenTextDocumentParams())
+ .textDocument();
+ if (!docItem.isValid())
+ return sendErrorNotification(Tr::tr("Invalid didOpen parameters."));
+ const QString filePath = uriToString(docItem.uri());
+ Document &doc = documents[filePath];
+ doc.savedContent = doc.currentContent = docItem.text();
+}
+
+void LspServer::Private::handleDidChangeNotification()
+{
+ const auto params = lsp::DidChangeTextDocumentNotification(messageObject)
+ .params().value_or(lsp::DidChangeTextDocumentParams());
+ if (!params.isValid())
+ return sendErrorNotification(Tr::tr("Invalid didChange parameters."));
+ const QString filePath = uriToString(params.textDocument().uri());
+ const auto docIt = documents.find(filePath);
+ if (docIt == documents.end())
+ return sendNoSuchDocumentError(filePath);
+ Document &doc = docIt->second;
+ const auto changes = params.contentChanges();
+ for (const auto &change : changes) {
+ const auto range = change.range();
+ if (!range) {
+ doc.currentContent = change.text();
+ continue;
+ }
+ const int startPos = posToOffset(range->start(), doc.currentContent);
+ const int endPos = posToOffset(range->end(), doc.currentContent);
+ if (startPos == -1 || endPos == -1 || startPos > endPos)
+ return sendErrorResponse(LspErrorResponse::InvalidParams, Tr::tr("Invalid range."));
+ doc.currentContent.replace(startPos, endPos - startPos, change.text());
+ }
+}
+
+void LspServer::Private::handleDidSaveNotification()
+{
+ const QString filePath = uriToString(lsp::DidSaveTextDocumentNotification(messageObject)
+ .params().value_or(lsp::DidSaveTextDocumentParams())
+ .textDocument().uri());
+ const auto docIt = documents.find(filePath);
+ if (docIt == documents.end())
+ return sendNoSuchDocumentError(filePath);
+ docIt->second.savedContent = docIt->second.currentContent;
+
+ // TODO: Mark the project data as out of date until the next update(),if the document
+ // is in buildSystemFiles().
+}
+
+void LspServer::Private::handleDidCloseNotification()
+{
+ const QString filePath = uriToString(lsp::DidCloseTextDocumentNotification(messageObject)
+ .params().value_or(lsp::DidCloseTextDocumentParams())
+ .textDocument().uri());
+ const auto docIt = documents.find(filePath);
+ if (docIt == documents.end())
+ return sendNoSuchDocumentError(filePath);
+ documents.erase(docIt);
+}
+
+static int posToOffset(const CodePosition &pos, const QString &doc)
+{
+ int offset = 0;
+ for (int newlines = 0, next = 0; newlines < pos.line() - 1; ++newlines) {
+ offset = doc.indexOf('\n', next);
+ if (offset == -1)
+ return -1;
+ next = offset + 1;
+ }
+ return offset + pos.column();
+}
+
+bool Document::isPositionUpToDate(const CodePosition &pos) const
+{
+ const int origOffset = posToOffset(pos, savedContent);
+ if (origOffset > int(currentContent.size()))
+ return false;
+ return QStringView(currentContent).left(origOffset)
+ == QStringView(savedContent).left(origOffset);
+}
+
+bool Document::isPositionUpToDate(const lsp::Position &pos) const
+{
+ return isPositionUpToDate(posFromLspPos(pos));
+}
+
+} // namespace qbs::Internal
+
diff --git a/src/app/qbs/lspserver.h b/src/app/qbs/lspserver.h
new file mode 100644
index 000000000..566808309
--- /dev/null
+++ b/src/app/qbs/lspserver.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <tools/codelocation.h>
+
+#include <QString>
+
+namespace qbs {
+class ProjectData;
+namespace Internal {
+
+class LspServer
+{
+public:
+ LspServer();
+ ~LspServer();
+
+ void updateProjectData(const ProjectData &projectData, const CodeLinks &codeLinks);
+ QString socketPath() const;
+
+private:
+ class Private;
+ Private * const d;
+};
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/app/qbs/parser/commandlineoption.cpp b/src/app/qbs/parser/commandlineoption.cpp
index a09f36c2c..7572b4e66 100644
--- a/src/app/qbs/parser/commandlineoption.cpp
+++ b/src/app/qbs/parser/commandlineoption.cpp
@@ -334,7 +334,7 @@ void StringListOption::doParse(const QString &representation, QStringList &input
throw ErrorInfo(Tr::tr("Invalid use of option '%1': Argument list must not be empty.\n"
"Usage: %2").arg(representation, description(command())));
}
- for (const QString &element : qAsConst(m_arguments)) {
+ for (const QString &element : std::as_const(m_arguments)) {
if (element.isEmpty()) {
throw ErrorInfo(Tr::tr("Invalid use of option '%1': Argument list must not contain "
"empty elements.\nUsage: %2")
@@ -663,27 +663,51 @@ void CommandEchoModeOption::doParse(const QString &representation, QStringList &
m_echoMode = commandEchoModeFromName(mode);
}
-QString WaitLockOption::description(CommandType command) const
+QString DeprecationWarningsOption::description(CommandType command) const
{
Q_UNUSED(command);
- return Tr::tr("%1\n\tWait indefinitely for other processes to release the build graph lock.\n")
- .arg(longRepresentation());
+ return Tr::tr("%1 <mode>\n"
+ "\tWhat to do when encountering deprecated items or properties.\n"
+ "\tPossible values are '%2'.\n"
+ "\tThe default is '%3'.\n")
+ .arg(longRepresentation(),
+ allDeprecationWarningModeStrings().join(QLatin1String("', '")),
+ deprecationWarningModeName(defaultDeprecationWarningMode()));
}
-QString WaitLockOption::longRepresentation() const
+QString DeprecationWarningsOption::longRepresentation() const
{
- return QStringLiteral("--wait-lock");
+ return QStringLiteral("--deprecation-warnings");
}
-QString DisableFallbackProviderOption::description(CommandType) const
+void DeprecationWarningsOption::doParse(const QString &representation, QStringList &input)
{
- return Tr::tr("%1\n\tDo not fall back to pkg-config if a dependency is not found.\n")
+ const QString mode = getArgument(representation, input);
+ if (mode.isEmpty()) {
+ throw ErrorInfo(Tr::tr("Invalid use of option '%1': No deprecation warning mode given.\n"
+ "Usage: %2")
+ .arg(representation, description(command())));
+ }
+
+ if (!allDeprecationWarningModeStrings().contains(mode)) {
+ throw ErrorInfo(Tr::tr("Invalid use of option '%1': "
+ "Invalid deprecation warning mode '%2' given.\nUsage: %3")
+ .arg(representation, mode, description(command())));
+ }
+
+ m_mode = deprecationWarningModeFromName(mode);
+}
+
+QString WaitLockOption::description(CommandType command) const
+{
+ Q_UNUSED(command);
+ return Tr::tr("%1\n\tWait indefinitely for other processes to release the build graph lock.\n")
.arg(longRepresentation());
}
-QString DisableFallbackProviderOption::longRepresentation() const
+QString WaitLockOption::longRepresentation() const
{
- return QStringLiteral("--no-fallback-module-provider");
+ return QStringLiteral("--wait-lock");
}
QString RunEnvConfigOption::description(CommandType command) const
diff --git a/src/app/qbs/parser/commandlineoption.h b/src/app/qbs/parser/commandlineoption.h
index f8ec1c735..7b8baeae2 100644
--- a/src/app/qbs/parser/commandlineoption.h
+++ b/src/app/qbs/parser/commandlineoption.h
@@ -42,6 +42,7 @@
#include "commandtype.h"
#include <tools/commandechomode.h>
+#include <tools/deprecationwarningmode.h>
#include <tools/joblimits.h>
#include <QtCore/qstringlist.h>
@@ -54,7 +55,9 @@ public:
enum Type {
FileOptionType,
BuildDirectoryOptionType,
- LogLevelOptionType, VerboseOptionType, QuietOptionType,
+ LogLevelOptionType,
+ VerboseOptionType,
+ QuietOptionType,
JobsOptionType,
KeepGoingOptionType,
DryRunOptionType,
@@ -63,7 +66,9 @@ public:
ChangedFilesOptionType,
ProductsOptionType,
NoInstallOptionType,
- InstallRootOptionType, RemoveFirstOptionType, NoBuildOptionType,
+ InstallRootOptionType,
+ RemoveFirstOptionType,
+ NoBuildOptionType,
ForceTimestampCheckOptionType,
ForceOutputCheckOptionType,
BuildNonDefaultOptionType,
@@ -75,7 +80,7 @@ public:
GeneratorOptionType,
WaitLockOptionType,
RunEnvConfigOptionType,
- DisableFallbackProviderType,
+ DeprecationWarningsOptionType,
};
virtual ~CommandLineOption();
@@ -367,6 +372,20 @@ private:
CommandEchoMode m_echoMode = CommandEchoModeInvalid;
};
+class DeprecationWarningsOption : public CommandLineOption
+{
+public:
+ QString description(CommandType command) const override;
+ QString shortRepresentation() const override { return {}; }
+ QString longRepresentation() const override;
+ DeprecationWarningMode mode() const { return m_mode; }
+
+private:
+ void doParse(const QString &representation, QStringList &input) override;
+
+ DeprecationWarningMode m_mode = defaultDeprecationWarningMode();
+};
+
class SettingsDirOption : public CommandLineOption
{
public:
@@ -415,14 +434,6 @@ public:
QString longRepresentation() const override;
};
-class DisableFallbackProviderOption : public OnOffOption
-{
-public:
- QString description(CommandType command) const override;
- QString shortRepresentation() const override { return {}; }
- QString longRepresentation() const override;
-};
-
} // namespace qbs
#endif // QBS_COMMANDLINEOPTION_H
diff --git a/src/app/qbs/parser/commandlineoptionpool.cpp b/src/app/qbs/parser/commandlineoptionpool.cpp
index 63711f623..692c9c737 100644
--- a/src/app/qbs/parser/commandlineoptionpool.cpp
+++ b/src/app/qbs/parser/commandlineoptionpool.cpp
@@ -128,12 +128,12 @@ CommandLineOption *CommandLineOptionPool::getOption(CommandLineOption::Type type
case CommandLineOption::WaitLockOptionType:
option = new WaitLockOption;
break;
- case CommandLineOption::DisableFallbackProviderType:
- option = new DisableFallbackProviderOption;
- break;
case CommandLineOption::RunEnvConfigOptionType:
option = new RunEnvConfigOption;
break;
+ case CommandLineOption::DeprecationWarningsOptionType:
+ option = new DeprecationWarningsOption;
+ break;
default:
qFatal("Unknown option type %d", type);
}
@@ -276,15 +276,15 @@ WaitLockOption *CommandLineOptionPool::waitLockOption() const
return static_cast<WaitLockOption *>(getOption(CommandLineOption::WaitLockOptionType));
}
-DisableFallbackProviderOption *CommandLineOptionPool::disableFallbackProviderOption() const
+RunEnvConfigOption *CommandLineOptionPool::runEnvConfigOption() const
{
- return static_cast<DisableFallbackProviderOption *>(
- getOption(CommandLineOption::DisableFallbackProviderType));
+ return static_cast<RunEnvConfigOption *>(getOption(CommandLineOption::RunEnvConfigOptionType));
}
-RunEnvConfigOption *CommandLineOptionPool::runEnvConfigOption() const
+DeprecationWarningsOption *CommandLineOptionPool::deprecationWarningsOption() const
{
- return static_cast<RunEnvConfigOption *>(getOption(CommandLineOption::RunEnvConfigOptionType));
+ return static_cast<DeprecationWarningsOption *>
+ (getOption(CommandLineOption::DeprecationWarningsOptionType));
}
} // namespace qbs
diff --git a/src/app/qbs/parser/commandlineoptionpool.h b/src/app/qbs/parser/commandlineoptionpool.h
index c7ac263e1..022e9fd09 100644
--- a/src/app/qbs/parser/commandlineoptionpool.h
+++ b/src/app/qbs/parser/commandlineoptionpool.h
@@ -77,8 +77,8 @@ public:
RespectProjectJobLimitsOption *respectProjectJobLimitsOption() const;
GeneratorOption *generatorOption() const;
WaitLockOption *waitLockOption() const;
- DisableFallbackProviderOption *disableFallbackProviderOption() const;
RunEnvConfigOption *runEnvConfigOption() const;
+ DeprecationWarningsOption *deprecationWarningsOption() const;
private:
mutable QHash<CommandLineOption::Type, CommandLineOption *> m_options;
diff --git a/src/app/qbs/parser/commandlineparser.cpp b/src/app/qbs/parser/commandlineparser.cpp
index 052f6b92f..c548cf2b5 100644
--- a/src/app/qbs/parser/commandlineparser.cpp
+++ b/src/app/qbs/parser/commandlineparser.cpp
@@ -58,12 +58,14 @@
#include <tools/qttools.h>
#include <tools/settings.h>
#include <tools/settingsrepresentation.h>
+#include <tools/stlutils.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdir.h>
#include <QtCore/qmap.h>
#include <QtCore/qtextstream.h>
+#include <algorithm>
#include <utility>
#ifdef Q_OS_UNIX
@@ -111,14 +113,9 @@ public:
bool logTime;
};
-CommandLineParser::CommandLineParser() : d(nullptr)
-{
-}
+CommandLineParser::CommandLineParser() = default;
-CommandLineParser::~CommandLineParser()
-{
- delete d;
-}
+CommandLineParser::~CommandLineParser() = default;
void CommandLineParser::printHelp() const
{
@@ -159,17 +156,11 @@ QString CommandLineParser::projectBuildDirectory() const
BuildOptions CommandLineParser::buildOptions(const QString &profile) const
{
- Settings settings(settingsDir());
- Preferences preferences(&settings, profile);
-
- if (d->buildOptions.maxJobCount() <= 0) {
- d->buildOptions.setMaxJobCount(preferences.jobs());
- }
-
+ d->buildOptions.setMaxJobCount(jobCount(profile));
if (d->buildOptions.echoMode() < 0) {
- d->buildOptions.setEchoMode(preferences.defaultEchoMode());
+ Settings settings(settingsDir());
+ d->buildOptions.setEchoMode(Preferences(&settings, profile).defaultEchoMode());
}
-
return d->buildOptions;
}
@@ -206,6 +197,15 @@ InstallOptions CommandLineParser::installOptions(const QString &profile) const
return options;
}
+int CommandLineParser::jobCount(const QString &profile) const
+{
+ if (const int explicitJobCount = d->optionPool.jobsOption()->jobCount(); explicitJobCount > 0)
+ return explicitJobCount;
+
+ Settings settings(settingsDir());
+ return Preferences(&settings, profile).jobs();
+}
+
bool CommandLineParser::forceTimestampCheck() const
{
return d->optionPool.forceTimestampCheckOption()->enabled();
@@ -231,11 +231,6 @@ bool CommandLineParser::waitLockBuildGraph() const
return d->optionPool.waitLockOption()->enabled();
}
-bool CommandLineParser::disableFallbackProvider() const
-{
- return d->optionPool.disableFallbackProviderOption()->enabled();
-}
-
bool CommandLineParser::logTime() const
{
return d->logTime;
@@ -282,6 +277,11 @@ QString CommandLineParser::settingsDir() const
return d->settingsDir();
}
+DeprecationWarningMode CommandLineParser::deprecationWarningMode() const
+{
+ return d->optionPool.deprecationWarningsOption()->mode();
+}
+
QString CommandLineParser::commandName() const
{
return d->command->representation();
@@ -309,8 +309,7 @@ QList<QVariantMap> CommandLineParser::buildConfigurations() const
bool CommandLineParser::parseCommandLine(const QStringList &args)
{
- delete d;
- d = new CommandLineParserPrivate;
+ d = std::make_unique<CommandLineParserPrivate>();
d->commandLine = args;
try {
d->doParse();
@@ -334,7 +333,19 @@ void CommandLineParser::CommandLineParserPrivate::doParse()
} else {
command = commandFromString(commandLine.front());
if (command) {
- commandLine.removeFirst();
+ const QString commandName = commandLine.takeFirst();
+
+ // if the command line contains a `<command>` with
+ // either `-h` or `--help` switch, we transform
+ // it to corresponding `help <command>` instead
+ const QStringList helpSwitches = {QStringLiteral("-h"), QStringLiteral("--help")};
+ if (auto it = std::find_first_of(
+ commandLine.begin(), commandLine.end(),
+ helpSwitches.begin(), helpSwitches.end());
+ it != commandLine.end()) {
+ command = commandPool.getCommand(HelpCommandType);
+ commandLine = QList{commandName}; // keep only command's name
+ }
} else { // No command given.
if (commandLine.front() == QLatin1String("-h")
|| commandLine.front() == QLatin1String("--help")) {
@@ -413,7 +424,7 @@ QString CommandLineParser::CommandLineParserPrivate::generalHelp() const
for (const Command * command : commands)
commandMap.insert(command->representation(), command);
- for (const Command * command : qAsConst(commandMap)) {
+ for (const Command * command : std::as_const(commandMap)) {
help.append(QLatin1String(" ")).append(command->representation());
const QString whitespace
= QString(rhsIndentation - 2 - command->representation().size(), QLatin1Char(' '));
@@ -424,7 +435,7 @@ QString CommandLineParser::CommandLineParserPrivate::generalHelp() const
toolNames.sort();
if (!toolNames.empty()) {
help.append(QLatin1Char('\n')).append(Tr::tr("Auxiliary commands:\n"));
- for (const QString &toolName : qAsConst(toolNames)) {
+ for (const QString &toolName : std::as_const(toolNames)) {
help.append(QLatin1String(" ")).append(toolName);
const QString whitespace = QString(rhsIndentation - 2 - toolName.size(),
QLatin1Char(' '));
@@ -507,7 +518,7 @@ void CommandLineParser::CommandLineParserPrivate::setupBuildConfigurations()
const QVariantMap globalProperties = propertiesPerConfiguration.takeFirst().second;
QList<QVariantMap> buildConfigs;
- for (const PropertyListItem &item : qAsConst(propertiesPerConfiguration)) {
+ for (const PropertyListItem &item : std::as_const(propertiesPerConfiguration)) {
QVariantMap properties = item.second;
for (QVariantMap::ConstIterator globalPropIt = globalProperties.constBegin();
globalPropIt != globalProperties.constEnd(); ++globalPropIt) {
@@ -588,18 +599,15 @@ QString CommandLineParser::CommandLineParserPrivate::propertyName(const QString
// Make fully-qualified, ie "platform" -> "qbs.platform"
if (aCommandLineName.contains(QLatin1Char('.')))
return aCommandLineName;
- else
- return QLatin1String("qbs.") + aCommandLineName;
+ return QLatin1String("qbs.") + aCommandLineName;
}
bool CommandLineParser::CommandLineParserPrivate::checkForExistingBuildConfiguration(
const QList<QVariantMap> &buildConfigs, const QString &configurationName)
{
- for (const QVariantMap &buildConfig : buildConfigs) {
- if (configurationName == getBuildConfigurationName(buildConfig))
- return true;
- }
- return false;
+ return Internal::any_of(buildConfigs, [&configurationName](const auto &buildConfig) {
+ return configurationName == getBuildConfigurationName(buildConfig);
+ });
}
bool CommandLineParser::CommandLineParserPrivate::withNonDefaultProducts() const
diff --git a/src/app/qbs/parser/commandlineparser.h b/src/app/qbs/parser/commandlineparser.h
index d47657b16..4df8829a2 100644
--- a/src/app/qbs/parser/commandlineparser.h
+++ b/src/app/qbs/parser/commandlineparser.h
@@ -41,9 +41,13 @@
#include "commandtype.h"
+#include <tools/deprecationwarningmode.h>
+
#include <QtCore/qstringlist.h>
#include <QtCore/qvariant.h>
+#include <memory>
+
namespace qbs {
class BuildOptions;
class CleanOptions;
@@ -71,12 +75,12 @@ public:
CleanOptions cleanOptions(const QString &profile) const;
GenerateOptions generateOptions() const;
InstallOptions installOptions(const QString &profile) const;
+ int jobCount(const QString &profile) const;
bool forceTimestampCheck() const;
bool forceOutputCheck() const;
bool dryRun() const;
bool forceProbesExecution() const;
bool waitLockBuildGraph() const;
- bool disableFallbackProvider() const;
bool logTime() const;
bool withNonDefaultProducts() const;
bool buildBeforeInstalling() const;
@@ -87,10 +91,11 @@ public:
bool showProgress() const;
bool showVersion() const;
QString settingsDir() const;
+ DeprecationWarningMode deprecationWarningMode() const;
private:
class CommandLineParserPrivate;
- CommandLineParserPrivate *d;
+ std::unique_ptr<CommandLineParserPrivate> d;
};
} // namespace qbs
diff --git a/src/app/qbs/parser/parser.pri b/src/app/qbs/parser/parser.pri
deleted file mode 100644
index f708f1135..000000000
--- a/src/app/qbs/parser/parser.pri
+++ /dev/null
@@ -1,16 +0,0 @@
-SOURCES += \
- $$PWD/commandlineparser.cpp \
- $$PWD/commandpool.cpp \
- $$PWD/commandlineoption.cpp \
- $$PWD/commandlineoptionpool.cpp \
- $$PWD/parsercommand.cpp
-
-HEADERS += \
- $$PWD/commandlineparser.h \
- $$PWD/commandpool.h \
- $$PWD/commandlineoption.h \
- $$PWD/commandlineoptionpool.h \
- $$PWD/commandtype.h \
- $$PWD/parsercommand.h
-
-include(../../../../qbs_version.pri)
diff --git a/src/app/qbs/parser/parsercommand.cpp b/src/app/qbs/parser/parsercommand.cpp
index 799bf5dcf..ef8da9551 100644
--- a/src/app/qbs/parser/parsercommand.cpp
+++ b/src/app/qbs/parser/parsercommand.cpp
@@ -164,7 +164,7 @@ QString Command::supportedOptionsDescription() const
}
QString s = Tr::tr("The possible options are:\n");
- for (const CommandLineOption *option : qAsConst(optionMap))
+ for (const CommandLineOption *option : std::as_const(optionMap))
s += option->description(type());
return s;
}
@@ -199,16 +199,18 @@ QString ResolveCommand::representation() const
static QList<CommandLineOption::Type> resolveOptions()
{
- return {CommandLineOption::FileOptionType,
- CommandLineOption::BuildDirectoryOptionType,
- CommandLineOption::LogLevelOptionType,
- CommandLineOption::VerboseOptionType,
- CommandLineOption::QuietOptionType,
- CommandLineOption::ShowProgressOptionType,
- CommandLineOption::DryRunOptionType,
- CommandLineOption::ForceProbesOptionType,
- CommandLineOption::LogTimeOptionType,
- CommandLineOption::DisableFallbackProviderType};
+ return {
+ CommandLineOption::FileOptionType,
+ CommandLineOption::BuildDirectoryOptionType,
+ CommandLineOption::LogLevelOptionType,
+ CommandLineOption::VerboseOptionType,
+ CommandLineOption::QuietOptionType,
+ CommandLineOption::ShowProgressOptionType,
+ CommandLineOption::DryRunOptionType,
+ CommandLineOption::ForceProbesOptionType,
+ CommandLineOption::LogTimeOptionType,
+ CommandLineOption::DeprecationWarningsOptionType,
+ CommandLineOption::JobsOptionType};
}
QList<CommandLineOption::Type> ResolveCommand::supportedOptions() const
@@ -278,7 +280,6 @@ static QList<CommandLineOption::Type> buildOptions()
<< CommandLineOption::ForceTimestampCheckOptionType
<< CommandLineOption::ForceOutputCheckOptionType
<< CommandLineOption::BuildNonDefaultOptionType
- << CommandLineOption::JobsOptionType
<< CommandLineOption::CommandEchoModeOptionType
<< CommandLineOption::NoInstallOptionType
<< CommandLineOption::RemoveFirstOptionType
diff --git a/src/app/qbs/qbs.pro b/src/app/qbs/qbs.pro
deleted file mode 100644
index 6ce449aad..000000000
--- a/src/app/qbs/qbs.pro
+++ /dev/null
@@ -1,54 +0,0 @@
-include(../app.pri)
-include(parser/parser.pri)
-
-TARGET = qbs
-
-SOURCES += main.cpp \
- ctrlchandler.cpp \
- application.cpp \
- session.cpp \
- sessionpacket.cpp \
- sessionpacketreader.cpp \
- stdinreader.cpp \
- status.cpp \
- consoleprogressobserver.cpp \
- commandlinefrontend.cpp \
- qbstool.cpp
-
-HEADERS += \
- ctrlchandler.h \
- application.h \
- session.h \
- sessionpacket.h \
- sessionpacketreader.h \
- stdinreader.h \
- status.h \
- consoleprogressobserver.h \
- commandlinefrontend.h \
- qbstool.h
-
-include(../../library_dirname.pri)
-isEmpty(QBS_RELATIVE_LIBEXEC_PATH) {
- win32:QBS_RELATIVE_LIBEXEC_PATH=.
- else:QBS_RELATIVE_LIBEXEC_PATH=../libexec/qbs
-}
-isEmpty(QBS_RELATIVE_PLUGINS_PATH):QBS_RELATIVE_PLUGINS_PATH=../$${QBS_LIBRARY_DIRNAME}
-isEmpty(QBS_RELATIVE_SEARCH_PATH):QBS_RELATIVE_SEARCH_PATH=..
-DEFINES += QBS_RELATIVE_LIBEXEC_PATH=\\\"$${QBS_RELATIVE_LIBEXEC_PATH}\\\"
-DEFINES += QBS_RELATIVE_PLUGINS_PATH=\\\"$${QBS_RELATIVE_PLUGINS_PATH}\\\"
-DEFINES += QBS_RELATIVE_SEARCH_PATH=\\\"$${QBS_RELATIVE_SEARCH_PATH}\\\"
-
-CONFIG(static, static|shared) {
- include(../../plugins/qbs_plugin_common.pri)
- LIBS += -L$$qbsPluginDestDir
- scannerPlugins = cpp qt
- for (scannerPlugin, scannerPlugins) {
- include(../../plugins/scanner/$$scannerPlugin/$${scannerPlugin}.pri) \
- include(../../plugins/use_plugin.pri)
- }
- generatorPlugins = clangcompilationdb iarew keiluv makefilegenerator visualstudio
- for (generatorPlugin, generatorPlugins) {
- include(../../plugins/generator/$$generatorPlugin/$${generatorPlugin}.pri) \
- include(../../plugins/use_plugin.pri)
- }
-}
diff --git a/src/app/qbs/qbs.qbs b/src/app/qbs/qbs.qbs
index 530036f3a..52c29e9b2 100644
--- a/src/app/qbs/qbs.qbs
+++ b/src/app/qbs/qbs.qbs
@@ -1,14 +1,17 @@
-import qbs 1.0
import qbs.Utilities
QbsApp {
name: "qbs_app"
- Depends { name: "qbs resources" }
targetName: "qbs"
+
+ Depends { name: "qbs resources" }
+ Depends { name: "qtclsp" }
+ Depends { name: "Qt.network" }
Depends {
condition: Qt.core.staticBuild || qbsbuildconfig.staticBuild
productTypes: ["qbsplugin"]
}
+
cpp.defines: base.concat([
"QBS_VERSION=" + Utilities.cStringQuote(qbsversion.version),
"QBS_RELATIVE_LIBEXEC_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeLibexecPath),
@@ -55,5 +58,13 @@ QbsApp {
"parsercommand.h",
]
}
+ Group {
+ name: "lsp"
+ cpp.defines: outer.filter(function(d) { return d !== "QT_NO_CAST_FROM_ASCII"; })
+ files: [
+ "lspserver.cpp",
+ "lspserver.h",
+ ]
+ }
}
diff --git a/src/app/qbs/session.cpp b/src/app/qbs/session.cpp
index 7e141b4a6..2cdcf2b63 100644
--- a/src/app/qbs/session.cpp
+++ b/src/app/qbs/session.cpp
@@ -39,6 +39,7 @@
#include "session.h"
+#include "lspserver.h"
#include "sessionpacket.h"
#include "sessionpacketreader.h"
@@ -66,7 +67,6 @@
#include <QtCore/qobject.h>
#include <QtCore/qprocess.h>
-#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <memory>
@@ -148,7 +148,7 @@ private:
struct ProductSelection {
ProductSelection(Project::ProductSelection s) : selection(s) {}
- ProductSelection(const QList<ProductData> &p) : products(p) {}
+ ProductSelection(QList<ProductData> p) : products(std::move(p)) {}
Project::ProductSelection selection = Project::ProductSelectionDefaultOnly;
QList<ProductData> products;
@@ -166,6 +166,7 @@ private:
FileUpdateData prepareFileUpdate(const QJsonObject &request);
SessionPacketReader m_packetReader;
+ LspServer m_lspServer;
Project m_project;
ProjectData m_projectData;
SessionLogSink m_logSink;
@@ -186,11 +187,14 @@ Session::Session()
#ifdef Q_OS_WIN32
// Make sure the line feed character appears as itself.
if (_setmode(_fileno(stdout), _O_BINARY) == -1) {
- std::cerr << "Failed to set stdout to binary mode: " << std::strerror(errno) << std::endl;
+ constexpr size_t errmsglen = FILENAME_MAX;
+ char errmsg[errmsglen];
+ strerror_s(errmsg, errmsglen, errno);
+ std::cerr << "Failed to set stdout to binary mode: " << errmsg << std::endl;
qApp->exit(EXIT_FAILURE);
}
#endif
- sendPacket(SessionPacket::helloMessage());
+ sendPacket(SessionPacket::helloMessage(m_lspServer.socketPath()));
connect(&m_logSink, &SessionLogSink::newMessage, this, &Session::sendPacket);
connect(&m_packetReader, &SessionPacketReader::errorOccurred,
this, [](const QString &msg) {
@@ -284,6 +288,7 @@ void Session::setupProject(const QJsonObject &request)
const ProjectData oldProjectData = m_projectData;
m_project = setupJob->project();
m_projectData = m_project.projectData();
+ m_lspServer.updateProjectData(m_projectData, m_project.codeLinks());
QJsonObject reply;
reply.insert(StringConstants::type(), QLatin1String("project-resolved"));
if (success)
@@ -298,7 +303,7 @@ void Session::setupProject(const QJsonObject &request)
void Session::buildProject(const QJsonObject &request)
{
- if (!checkNormalRequestPrerequisites("build-done"))
+ if (!checkNormalRequestPrerequisites("project-built"))
return;
const ProductSelection productSelection = getProductSelection(request);
setLogLevelFromRequest(request);
@@ -396,7 +401,6 @@ void Session::addFiles(const QJsonObject &request)
}
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()) {
@@ -405,7 +409,6 @@ void Session::addFiles(const QJsonObject &request)
failedFiles.push_back(filePath);
}
}
-#endif
QJsonObject reply;
reply.insert(StringConstants::type(), QLatin1String("files-added"));
insertErrorInfoIfNecessary(reply, error);
@@ -432,7 +435,6 @@ void Session::removeFiles(const QJsonObject &request)
}
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()) {
@@ -441,7 +443,6 @@ void Session::removeFiles(const QJsonObject &request)
failedFiles.push_back(filePath);
}
}
-#endif
QJsonObject reply;
reply.insert(StringConstants::type(), QLatin1String("files-removed"));
insertErrorInfoIfNecessary(reply, error);
@@ -555,7 +556,7 @@ void Session::getGeneratedFilesForSources(const QJsonObject &request)
reply.insert(StringConstants::type(), QLatin1String(replyType));
const QJsonArray specs = request.value(StringConstants::productsKey()).toArray();
QJsonArray resultProducts;
- for (const QJsonValue &p : specs) {
+ for (const auto &p : specs) {
const QJsonObject productObject = p.toObject();
const ProductData product = getProductByName(
productObject.value(StringConstants::fullDisplayNameKey()).toString());
@@ -565,7 +566,7 @@ void Session::getGeneratedFilesForSources(const QJsonObject &request)
resultProduct.insert(StringConstants::fullDisplayNameKey(), product.fullDisplayName());
QJsonArray results;
const QJsonArray requests = productObject.value(QLatin1String("requests")).toArray();
- for (const QJsonValue &r : requests) {
+ for (const auto &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")));
@@ -622,10 +623,11 @@ Session::ProductSelection Session::getProductSelection(const QJsonObject &reques
{
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);
+ return {getProductsByName(fromJson<QStringList>(productSelection))};
+ return {
+ productSelection.toString() == QLatin1String("all")
+ ? Project::ProductSelectionWithNonDefault
+ : Project::ProductSelectionDefaultOnly};
}
Session::FileUpdateData Session::prepareFileUpdate(const QJsonObject &request)
@@ -647,15 +649,12 @@ Session::FileUpdateData Session::prepareFileUpdate(const QJsonObject &request)
data.error = tr("Product '%1' not found in project.").arg(productName);
}
const QJsonArray filesArray = request.value(QLatin1String("files")).toArray();
- for (const QJsonValue &v : filesArray)
+ for (const auto &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;
}
diff --git a/src/app/qbs/sessionpacket.cpp b/src/app/qbs/sessionpacket.cpp
index ce9fdaf76..470e27091 100644
--- a/src/app/qbs/sessionpacket.cpp
+++ b/src/app/qbs/sessionpacket.cpp
@@ -82,7 +82,7 @@ SessionPacket::Status SessionPacket::parseInput(QByteArray &input)
QJsonObject SessionPacket::retrievePacket()
{
QBS_ASSERT(isComplete(), return QJsonObject());
- const auto packet = QJsonDocument::fromJson(QByteArray::fromBase64(m_payload)).object();
+ auto packet = QJsonDocument::fromJson(QByteArray::fromBase64(m_payload)).object();
m_payload.clear();
m_expectedPayloadLength = -1;
return packet;
@@ -95,12 +95,13 @@ QByteArray SessionPacket::createPacket(const QJsonObject &packet)
.append(jsonData);
}
-QJsonObject SessionPacket::helloMessage()
+QJsonObject SessionPacket::helloMessage(const QString &lspSocket)
{
return QJsonObject{
{StringConstants::type(), QLatin1String("hello")},
- {QLatin1String("api-level"), 1},
- {QLatin1String("api-compat-level"), 1}
+ {QLatin1String("api-level"), 5},
+ {QLatin1String("api-compat-level"), 2},
+ {QLatin1String("lsp-socket"), lspSocket}
};
}
diff --git a/src/app/qbs/sessionpacket.h b/src/app/qbs/sessionpacket.h
index d919ff340..e77b30b75 100644
--- a/src/app/qbs/sessionpacket.h
+++ b/src/app/qbs/sessionpacket.h
@@ -55,7 +55,7 @@ public:
QJsonObject retrievePacket();
static QByteArray createPacket(const QJsonObject &packet);
- static QJsonObject helloMessage();
+ static QJsonObject helloMessage(const QString &lspSocket);
private:
bool isComplete() const;
diff --git a/src/app/qbs/sessionpacketreader.cpp b/src/app/qbs/sessionpacketreader.cpp
index e99ea01ed..daba30d7e 100644
--- a/src/app/qbs/sessionpacketreader.cpp
+++ b/src/app/qbs/sessionpacketreader.cpp
@@ -42,6 +42,8 @@
#include "sessionpacket.h"
#include "stdinreader.h"
+#include <QPointer>
+
namespace qbs {
namespace Internal {
@@ -64,8 +66,13 @@ 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) {
+ /* Because this SessionPacketReader can be destroyed in the emit packetReceived,
+ * use a `QPointer self(this)` to check whether this instance still exists.
+ * When self evaluates to false, this instance should no longer be referenced,
+ * so the parent QObject and d should no longer be used in any way. */
+ QPointer self(this);
d->incomingData += data;
- while (!d->incomingData.isEmpty()) {
+ while (self && !d->incomingData.isEmpty()) {
switch (d->currentPacket.parseInput(d->incomingData)) {
case SessionPacket::Status::Invalid:
emit errorOccurred(tr("Received invalid input."));
diff --git a/src/app/qbs/status.cpp b/src/app/qbs/status.cpp
index 3dc540ff5..8ee39e46f 100644
--- a/src/app/qbs/status.cpp
+++ b/src/app/qbs/status.cpp
@@ -47,20 +47,20 @@
#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
+#include <QtCore/qregularexpression.h>
#include <QtCore/qstring.h>
-#include <QtCore/qregexp.h>
namespace qbs {
-static QList<QRegExp> createIgnoreList(const QString &projectRootPath)
+static QList<QRegularExpression> createIgnoreList(const QString &projectRootPath)
{
- QList<QRegExp> ignoreRegularExpressionList {
- QRegExp(projectRootPath + QLatin1String("/build.*")),
- QRegExp(QStringLiteral("*.qbs"), Qt::CaseSensitive, QRegExp::Wildcard),
- QRegExp(QStringLiteral("*.pro"), Qt::CaseSensitive, QRegExp::Wildcard),
- QRegExp(QStringLiteral("*Makefile"), Qt::CaseSensitive, QRegExp::Wildcard),
- QRegExp(QStringLiteral("*.so*"), Qt::CaseSensitive, QRegExp::Wildcard),
- QRegExp(QStringLiteral("*.o"), Qt::CaseSensitive, QRegExp::Wildcard)
+ QList<QRegularExpression> ignoreRegularExpressionList {
+ QRegularExpression(QRegularExpression::anchoredPattern(projectRootPath + QLatin1String("/build.*"))),
+ QRegularExpression(QRegularExpression::wildcardToRegularExpression(QStringLiteral("*.qbs"))),
+ QRegularExpression(QRegularExpression::wildcardToRegularExpression(QStringLiteral("*.pro"))),
+ QRegularExpression(QRegularExpression::wildcardToRegularExpression(QStringLiteral("*Makefile"))),
+ QRegularExpression(QRegularExpression::wildcardToRegularExpression(QStringLiteral("*.so*"))),
+ QRegularExpression(QRegularExpression::wildcardToRegularExpression(QStringLiteral("*.o")))
};
QString ignoreFilePath = projectRootPath + QLatin1String("/.qbsignore");
@@ -70,12 +70,13 @@ static QList<QRegExp> createIgnoreList(const QString &projectRootPath)
const QList<QByteArray> ignoreTokenList = ignoreFile.readAll().split('\n');
for (const QByteArray &btoken : ignoreTokenList) {
const QString token = QString::fromLatin1(btoken);
- if (token.left(1) == QLatin1String("/"))
- ignoreRegularExpressionList.push_back(QRegExp(projectRootPath
- + token + QLatin1String(".*"),
- Qt::CaseSensitive, QRegExp::RegExp2));
+ if (token.startsWith(QLatin1String("/")))
+ ignoreRegularExpressionList.push_back(
+ QRegularExpression(QRegularExpression::anchoredPattern(
+ projectRootPath + token + QLatin1String(".*"))));
else if (!token.isEmpty())
- ignoreRegularExpressionList.push_back(QRegExp(token, Qt::CaseSensitive, QRegExp::RegExp2));
+ ignoreRegularExpressionList.push_back(
+ QRegularExpression(QRegularExpression::anchoredPattern(token)));
}
}
@@ -83,7 +84,8 @@ static QList<QRegExp> createIgnoreList(const QString &projectRootPath)
return ignoreRegularExpressionList;
}
-static QStringList allFilesInDirectoryRecursive(const QDir &rootDirecory, const QList<QRegExp> &ignoreRegularExpressionList)
+static QStringList allFilesInDirectoryRecursive(
+ const QDir &rootDirecory, const QList<QRegularExpression> &ignoreRegularExpressionList)
{
QStringList fileList;
@@ -91,8 +93,8 @@ static QStringList allFilesInDirectoryRecursive(const QDir &rootDirecory, const
for (const QFileInfo &fileInfo : fileInfos) {
QString absoluteFilePath = fileInfo.absoluteFilePath();
bool inIgnoreList = false;
- for (const QRegExp &ignoreRegularExpression : ignoreRegularExpressionList) {
- if (ignoreRegularExpression.exactMatch(absoluteFilePath)) {
+ for (const QRegularExpression &ignoreRegularExpression : ignoreRegularExpressionList) {
+ if (ignoreRegularExpression.match(absoluteFilePath).hasMatch()) {
inIgnoreList = true;
break;
}
@@ -112,7 +114,7 @@ static QStringList allFilesInDirectoryRecursive(const QDir &rootDirecory, const
static QStringList allFilesInProject(const QString &projectRootPath)
{
- QList<QRegExp> ignoreRegularExpressionList = createIgnoreList(projectRootPath);
+ QList<QRegularExpression> ignoreRegularExpressionList = createIgnoreList(projectRootPath);
return allFilesInDirectoryRecursive(QDir(projectRootPath), ignoreRegularExpressionList);
}
@@ -120,8 +122,7 @@ static QStringList allFilesInProject(const QString &projectRootPath)
QStringList allFiles(const ProductData &product)
{
QStringList files;
- const auto groups = product.groups();
- for (const GroupData &group : groups)
+ for (const GroupData &group : product.groups())
files += group.allFilePaths();
return files;
}
@@ -134,20 +135,17 @@ int printStatus(const ProjectData &project)
QStringList untrackedFilesInProject = allFilesInProject(projectDirectory);
QStringList missingFiles;
- const auto products = project.allProducts();
- for (const ProductData &product : products) {
+ for (const ProductData &product : project.allProducts()) {
qbsInfo() << "\nProduct: " << product.name()
<< " (" << product.location().filePath() << ":"
<< product.location().line() << ")";
- const auto groups = product.groups();
- for (const GroupData &group : groups) {
+ for (const GroupData &group : product.groups()) {
qbsInfo() << " Group: " << group.name()
<< " (" << group.location().filePath() << ":"
<< group.location().line() << ")";
- QStringList sourceFiles = group.allFilePaths();
- std::sort(sourceFiles.begin(), sourceFiles.end());
- for (const QString &sourceFile : qAsConst(sourceFiles)) {
- if (!QFileInfo(sourceFile).exists())
+ const QStringList sourceFiles = Internal::sorted(group.allFilePaths());
+ for (const QString &sourceFile : sourceFiles) {
+ if (!QFileInfo::exists(sourceFile))
missingFiles.push_back(sourceFile);
qbsInfo() << " " << sourceFile.mid(projectDirectoryPathLength + 1);
untrackedFilesInProject.removeOne(sourceFile);
@@ -156,11 +154,11 @@ int printStatus(const ProjectData &project)
}
qbsInfo() << "\nMissing files:";
- for (const QString &untrackedFile : qAsConst(missingFiles))
+ for (const QString &untrackedFile : std::as_const(missingFiles))
qbsInfo() << " " << untrackedFile.mid(projectDirectoryPathLength + 1);
qbsInfo() << "\nUntracked files:";
- for (const QString &missingFile : qAsConst(untrackedFilesInProject))
+ for (const QString &missingFile : std::as_const(untrackedFilesInProject))
qbsInfo() << " " << missingFile.mid(projectDirectoryPathLength + 1);
return 0;
diff --git a/src/app/qbs/stdinreader.cpp b/src/app/qbs/stdinreader.cpp
index b90abf9d1..4708ff53c 100644
--- a/src/app/qbs/stdinreader.cpp
+++ b/src/app/qbs/stdinreader.cpp
@@ -43,13 +43,14 @@
#include <QtCore/qfile.h>
#include <QtCore/qsocketnotifier.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qtimer.h>
#include <cerrno>
#include <cstring>
#ifdef Q_OS_WIN32
#include <qt_windows.h>
-#include <QtCore/qtimer.h>
#else
#include <fcntl.h>
#endif
@@ -87,6 +88,18 @@ private:
connect(&m_notifier, &QSocketNotifier::activated, this, [this] {
emit dataAvailable(m_stdIn.readAll());
});
+
+ // Neither the aboutToClose() nor the readChannelFinished() signals
+ // are triggering, so we need a timer to check whether the controlling
+ // process disappeared.
+ const auto stdinClosedChecker = new QTimer(this);
+ connect(stdinClosedChecker, &QTimer::timeout, this, [this, stdinClosedChecker] {
+ if (m_stdIn.atEnd()) {
+ stdinClosedChecker->stop();
+ emit errorOccurred(tr("Input channel closed unexpectedly."));
+ }
+ });
+ stdinClosedChecker->start(1000);
}
QFile m_stdIn;
@@ -99,38 +112,183 @@ 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;
+ class FileReaderThread : public QThread
+ {
+ public:
+ FileReaderThread(WindowsStdinReader &parent, HANDLE stdInHandle, HANDLE exitEventHandle)
+ : QThread(&parent), m_stdIn{stdInHandle}, m_exitEvent{exitEventHandle} { }
+ ~FileReaderThread()
+ {
+ wait();
+ CloseHandle(m_exitEvent);
}
- // 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] {
+ void run() override
+ {
+ WindowsStdinReader *r = static_cast<WindowsStdinReader *>(parent());
+
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;
+ while (true) {
+ DWORD bytesRead = 0;
+ if (!ReadFile(m_stdIn, buf, sizeof buf, &bytesRead, nullptr)) {
+ emit r->errorOccurred(tr("Failed to read from input channel."));
+ break;
+ }
+ if (!bytesRead)
+ break;
+ emit r->dataAvailable(QByteArray(buf, bytesRead));
}
- });
- timer->start(10);
+ }
+ private:
+ HANDLE m_stdIn;
+ HANDLE m_exitEvent;
+ };
+
+ class ConsoleReaderThread : public QThread
+ {
+ public:
+ ConsoleReaderThread(WindowsStdinReader &parent, HANDLE stdInHandle, HANDLE exitEventHandle)
+ : QThread(&parent), m_stdIn{stdInHandle}, m_exitEvent{exitEventHandle} { }
+ virtual ~ConsoleReaderThread() override
+ {
+ SetEvent(m_exitEvent);
+ wait();
+ CloseHandle(m_exitEvent);
+ }
+
+ void run() override
+ {
+ WindowsStdinReader *r = static_cast<WindowsStdinReader *>(parent());
+
+ DWORD origConsoleMode;
+ GetConsoleMode(m_stdIn, &origConsoleMode);
+ DWORD consoleMode = ENABLE_PROCESSED_INPUT;
+ SetConsoleMode(m_stdIn, consoleMode);
+
+ HANDLE handles[2] = {m_exitEvent, m_stdIn};
+ char buf[1024];
+ while (true) {
+ auto result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ if (result == WAIT_OBJECT_0)
+ break;
+ INPUT_RECORD consoleInput;
+ DWORD inputsRead = 0;
+ if (!PeekConsoleInputA(m_stdIn, &consoleInput, 1, &inputsRead)) {
+ emit r->errorOccurred(tr("Failed to read from input channel."));
+ break;
+ }
+ if (inputsRead) {
+ if (consoleInput.EventType != KEY_EVENT
+ || !consoleInput.Event.KeyEvent.bKeyDown
+ || !consoleInput.Event.KeyEvent.uChar.AsciiChar) {
+ if (!ReadConsoleInputA(m_stdIn, &consoleInput, 1, &inputsRead)) {
+ emit r->errorOccurred(tr("Failed to read console input."));
+ break;
+ }
+ } else {
+ DWORD bytesRead = 0;
+ if (!ReadConsoleA(m_stdIn, buf, sizeof buf, &bytesRead, nullptr)) {
+ emit r->errorOccurred(tr("Failed to read console."));
+ break;
+ }
+ emit r->dataAvailable(QByteArray(buf, bytesRead));
+ }
+ }
+ }
+ SetConsoleMode(m_stdIn, origConsoleMode);
+ }
+ private:
+ HANDLE m_stdIn;
+ HANDLE m_exitEvent;
+ };
+
+ class PipeReaderThread : public QThread
+ {
+ public:
+ PipeReaderThread(WindowsStdinReader &parent, HANDLE stdInHandle, HANDLE exitEventHandle)
+ : QThread(&parent), m_stdIn{stdInHandle}, m_exitEvent{exitEventHandle} { }
+ virtual ~PipeReaderThread() override
+ {
+ SetEvent(m_exitEvent);
+ wait();
+ CloseHandle(m_exitEvent);
+ }
+
+ void run() override
+ {
+ WindowsStdinReader *r = static_cast<WindowsStdinReader *>(parent());
+
+ OVERLAPPED overlapped = {};
+ overlapped.hEvent = CreateEventA(NULL, TRUE, TRUE, NULL);
+ if (!overlapped.hEvent) {
+ emit r->errorOccurred(StdinReader::tr("Failed to create handle for overlapped event."));
+ return;
+ }
+
+ char buf[1024];
+ DWORD bytesRead;
+ HANDLE handles[2] = {m_exitEvent, overlapped.hEvent};
+ while (true) {
+ bytesRead = 0;
+ auto readResult = ReadFile(m_stdIn, buf, sizeof buf, NULL, &overlapped);
+ if (!readResult) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ emit r->errorOccurred(StdinReader::tr("ReadFile Failed."));
+ break;
+ }
+
+ auto result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ if (result == WAIT_OBJECT_0)
+ break;
+ }
+ if (!GetOverlappedResult(m_stdIn, &overlapped, &bytesRead, FALSE)) {
+ if (GetLastError() != ERROR_HANDLE_EOF)
+ emit r->errorOccurred(StdinReader::tr("Error GetOverlappedResult."));
+ break;
+ }
+ emit r->dataAvailable(QByteArray(buf, bytesRead));
+ }
+ CancelIo(m_stdIn);
+ CloseHandle(overlapped.hEvent);
+ }
+ private:
+ HANDLE m_stdIn;
+ HANDLE m_exitEvent;
+ };
#endif
- }
+ void start() override
+ {
#ifdef Q_OS_WIN32
- HANDLE m_stdinHandle;
+ HANDLE stdInHandle = GetStdHandle(STD_INPUT_HANDLE);
+ if (!stdInHandle) {
+ emit errorOccurred(StdinReader::tr("Failed to create handle for standard input."));
+ return;
+ }
+ HANDLE exitEventHandle = CreateEventA(NULL, TRUE, FALSE, NULL);
+ if (!exitEventHandle) {
+ emit errorOccurred(StdinReader::tr("Failed to create handle for exit event."));
+ return;
+ }
+
+ auto result = GetFileType(stdInHandle);
+ switch (result) {
+ case FILE_TYPE_CHAR:
+ (new ConsoleReaderThread(*this, stdInHandle, exitEventHandle))->start();
+ return;
+ case FILE_TYPE_PIPE:
+ (new PipeReaderThread(*this, stdInHandle, exitEventHandle))->start();
+ return;
+ case FILE_TYPE_DISK:
+ (new FileReaderThread(*this, stdInHandle, exitEventHandle))->start();
+ return;
+ default:
+ emit errorOccurred(StdinReader::tr("Unable to handle unknown input type"));
+ return;
+ }
#endif
+ }
};
StdinReader *StdinReader::create(QObject *parent)
diff --git a/src/app/shared/CMakeLists.txt b/src/app/shared/CMakeLists.txt
new file mode 100644
index 000000000..504db2b5b
--- /dev/null
+++ b/src/app/shared/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(logging)
diff --git a/src/app/shared/logging/CMakeLists.txt b/src/app/shared/logging/CMakeLists.txt
new file mode 100644
index 000000000..f6e4fc135
--- /dev/null
+++ b/src/app/shared/logging/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(SOURCES
+ coloredoutput.cpp
+ coloredoutput.h
+ consolelogger.cpp
+ consolelogger.h
+ )
+
+add_qbs_library(qbsconsolelogger
+ STATIC
+ DEPENDS qbscore
+ SOURCES ${SOURCES}
+ )
diff --git a/src/app/shared/logging/coloredoutput.cpp b/src/app/shared/logging/coloredoutput.cpp
index 0f1bb26da..d5e45cada 100644
--- a/src/app/shared/logging/coloredoutput.cpp
+++ b/src/app/shared/logging/coloredoutput.cpp
@@ -62,15 +62,15 @@ void printfColored(TextColor color, const char *str, ...)
va_end(vl);
}
-void fprintfColored(TextColor color, FILE *file, const char *str, va_list vl)
+void fprintfColored(TextColor color, std::FILE *file, const char *str, va_list vl)
{
#if defined(Q_OS_UNIX)
if (color != TextColorDefault && isatty(fileno(file))) {
unsigned char bright = (color & TextColorBright) >> 3;
- fprintf(file, "\033[%d;%dm", bright, 30 + (color & ~TextColorBright));
- vfprintf(file, str, vl);
- fprintf(stdout, "\033[0m");
- fprintf(stderr, "\033[0m");
+ std::fprintf(file, "\033[%d;%dm", bright, 30 + (color & ~TextColorBright));
+ std::vfprintf(file, str, vl);
+ std::fprintf(stdout, "\033[0m");
+ std::fprintf(stderr, "\033[0m");
} else
#elif defined(Q_OS_WIN32)
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
@@ -83,16 +83,16 @@ void fprintfColored(TextColor color, FILE *file, const char *str, va_list vl)
if (color & TextColorBright)
bgrColor += FOREGROUND_INTENSITY;
SetConsoleTextAttribute(hStdout, (csbiInfo.wAttributes & 0xf0) | bgrColor);
- vfprintf(file, str, vl);
+ std::vfprintf(file, str, vl);
SetConsoleTextAttribute(hStdout, csbiInfo.wAttributes);
} else
#endif
{
- vfprintf(file, str, vl);
+ std::vfprintf(file, str, vl);
}
}
-void fprintfColored(TextColor color, FILE *file, const char *str, ...)
+void fprintfColored(TextColor color, std::FILE *file, const char *str, ...)
{
va_list vl;
va_start(vl, str);
diff --git a/src/app/shared/logging/coloredoutput.h b/src/app/shared/logging/coloredoutput.h
index a7b145ba5..5b98a4ba2 100644
--- a/src/app/shared/logging/coloredoutput.h
+++ b/src/app/shared/logging/coloredoutput.h
@@ -66,8 +66,8 @@ enum TextColor {
void printfColored(TextColor color, const char *str, va_list vl);
void printfColored(TextColor color, const char *str, ...);
-void fprintfColored(TextColor color, FILE *file, const char *str, va_list vl);
-void fprintfColored(TextColor color, FILE *file, const char *str, ...);
+void fprintfColored(TextColor color, std::FILE *file, const char *str, va_list vl);
+void fprintfColored(TextColor color, std::FILE *file, const char *str, ...);
bool terminalSupportsColor();
#endif // QBS_COLOREDOUTPUT_H
diff --git a/src/app/shared/logging/consolelogger.cpp b/src/app/shared/logging/consolelogger.cpp
index 63b7dc9e3..8850ba488 100644
--- a/src/app/shared/logging/consolelogger.cpp
+++ b/src/app/shared/logging/consolelogger.cpp
@@ -64,7 +64,7 @@ void ConsoleLogSink::doPrintMessage(qbs::LoggerLevel level, const QString &messa
if (!m_enabled)
return;
- FILE * const file = level == qbs::LoggerInfo && tag != QStringLiteral("stdErr")
+ std::FILE * const file = level == qbs::LoggerInfo && tag != QStringLiteral("stdErr")
? stdout : stderr;
const QString levelTag = logLevelTag(level);
@@ -84,17 +84,17 @@ void ConsoleLogSink::doPrintMessage(qbs::LoggerLevel level, const QString &messa
static QHash<QString, TextColor> colorTable = setupColorTable();
fprintfWrapper(colorTable.value(tag, TextColorDefault), file, "%s\n",
message.toLocal8Bit().constData());
- fflush(file);
+ std::fflush(file);
}
-void ConsoleLogSink::fprintfWrapper(TextColor color, FILE *file, const char *str, ...)
+void ConsoleLogSink::fprintfWrapper(TextColor color, std::FILE *file, const char *str, ...)
{
va_list vl;
va_start(vl, str);
if (m_coloredOutputEnabled && terminalSupportsColor())
fprintfColored(color, file, str, vl);
else
- vfprintf(file, str, vl);
+ std::vfprintf(file, str, vl);
va_end(vl);
}
diff --git a/src/app/shared/logging/logging.pri b/src/app/shared/logging/logging.pri
deleted file mode 100644
index e24f33e10..000000000
--- a/src/app/shared/logging/logging.pri
+++ /dev/null
@@ -1,2 +0,0 @@
-HEADERS += $$PWD/consolelogger.h $$PWD/coloredoutput.h
-SOURCES += $$PWD/consolelogger.cpp $$PWD/coloredoutput.cpp
diff --git a/src/app/shared/logging/logging.qbs b/src/app/shared/logging/logging.qbs
new file mode 100644
index 000000000..4e0738a4f
--- /dev/null
+++ b/src/app/shared/logging/logging.qbs
@@ -0,0 +1,14 @@
+QbsStaticLibrary {
+ Depends { name: "qbscore" }
+ name: "qbsconsolelogger"
+ files: [
+ "coloredoutput.cpp",
+ "coloredoutput.h",
+ "consolelogger.cpp",
+ "consolelogger.h"
+ ]
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: "."
+ }
+}
diff --git a/src/app/shared/shared.qbs b/src/app/shared/shared.qbs
new file mode 100644
index 000000000..8f9641686
--- /dev/null
+++ b/src/app/shared/shared.qbs
@@ -0,0 +1,5 @@
+Project {
+ references: [
+ "logging/logging.qbs",
+ ]
+}