diff options
Diffstat (limited to 'src/app')
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 ¯o : 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 ¯oDump, 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 = [¯os](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 ¯o : 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", + ] +} |