diff options
Diffstat (limited to 'src/tools')
148 files changed, 16048 insertions, 7499 deletions
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 13a870ac9e..df0d2c7016 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -1,3 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# The configure.cmake here does not get picked up automatically. +# Manually evaluate tool-related features. +include("${CMAKE_CURRENT_SOURCE_DIR}/configure.cmake") +qt_feature_evaluate_features("${CMAKE_CURRENT_SOURCE_DIR}/configure.cmake") + add_subdirectory(uic) if (QT_FEATURE_dbus) add_subdirectory(qdbuscpp2xml) @@ -9,10 +17,17 @@ if (QT_FEATURE_commandlineparser) add_subdirectory(qtpaths) endif() -# Only include the following tools when performing a host build -if(NOT CMAKE_CROSSCOMPILING) +if(QT_FEATURE_androiddeployqt) add_subdirectory(androiddeployqt) - if(QT_FEATURE_gui AND QT_FEATURE_systemsemaphore) + if(QT_FEATURE_gui AND QT_FEATURE_process AND QT_FEATURE_systemsemaphore) add_subdirectory(androidtestrunner) endif() endif() + +if(QT_FEATURE_macdeployqt) + add_subdirectory(macdeployqt) +endif() + +if(QT_FEATURE_windeployqt) + add_subdirectory(windeployqt) +endif() diff --git a/src/tools/androiddeployqt/CMakeLists.txt b/src/tools/androiddeployqt/CMakeLists.txt index 98cd79a334..041d883877 100644 --- a/src/tools/androiddeployqt/CMakeLists.txt +++ b/src/tools/androiddeployqt/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from androiddeployqt.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## androiddeployqt App: @@ -8,24 +9,25 @@ qt_get_tool_target_name(target_name androiddeployqt) qt_internal_add_tool(${target_name} TARGET_DESCRIPTION "Qt Android Deployment Tool" TOOLS_TARGET Core + USER_FACING + INSTALL_VERSIONED_LINK SOURCES main.cpp DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII QT_NO_FOREACH - PUBLIC_LIBRARIES - Qt::Core # special case + QT_NO_QPAIR + LIBRARIES + Qt::Core INCLUDE_DIRECTORIES ../shared ) +qt_internal_return_unless_building_tools() set_target_properties(${target_name} PROPERTIES WIN32_EXECUTABLE FALSE ) -#### Keys ignored in scope 1:.:.:androiddeployqt.pro:<TRUE>: -# _OPTION = "host_build" - ## Scopes: ##################################################################### diff --git a/src/tools/androiddeployqt/doc/src/androiddeployqt.qdoc b/src/tools/androiddeployqt/doc/src/androiddeployqt.qdoc new file mode 100644 index 0000000000..b94d7f5c04 --- /dev/null +++ b/src/tools/androiddeployqt/doc/src/androiddeployqt.qdoc @@ -0,0 +1,95 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page android-deploy-qt-tool.html + \brief An overview of the androiddeployqt tool and how to use it. + \title The androiddeployqt Tool + + \target androiddeployqt + Building an Android package involves many steps, so Qt comes with a tool which + handles the work for you. The steps handled by the androiddeployqt + tool are described in \l{Deploying an Application on Android}. + + \section1 Prerequisites Before Running androiddeployqt + + Before running the tool manually, you need to configure your project with + \c CMake or \c qmake to generate \c Makefiles and a \c JSON file (i.e. + \c{android-<target_name>-deployment-settings.json}) containing important settings + used by \c androiddeployqt. + + \note It is not recommended to modify the androiddeployqt JSON file. + + To prepare the environment for androiddeployqt, configure your project in + a separate directory than your source directory. For more information on + configuring your project, see \l {Building Qt for Android Projects from Command Line}. + + \section1 Command Line Arguments + + The only required command line arguments when running the tool are + \c {--input} and \c {--output}. Other command line arguments are optional but + useful. The list below is available by passing the \c {--help} argument to + androiddeployqt. + + \quotefromfile main.cpp + \skipto Syntax: androiddeployqt --output <destination> [options] + \printuntil --help: Displays this information. + + With a \c project_name, to build the application package with \c androiddeployqt + without deploying it the device, run the following: + + \badcode + androiddeployqt --input <build_dir>/android-project_name-deployment-settings.json \ + --output <build_dir>/android-build + \endcode + + To build and deploy the package to the device: + + \badcode + androiddeployqt --input <build_dir>/android-project_name-deployment-settings.json \ + --output <build_dir>/android-build --install --device <device_serial_id> + \endcode + + \section1 Dependencies Detection + + Qt comes with a number of plugins which are loaded at run-time when they are + needed. These can handle anything from connecting to SQL databases to loading + specific image formats. Detecting plugin dependencies is impossible as the + plugins are loaded at run-time, but androiddeployqt tries to guess such + dependencies based on the Qt dependencies of your application. If the plugin + has any Qt dependencies which are not also dependencies of your application, + it will not be included by default. For instance, in order to ensure that + the SVG image format plugin is included, you will need to add \l {Qt SVG} + module to your project for it to become a dependency of your application: + + \badcode + find_package(Qt6 REQUIRED COMPONENTS Svg) + ... + target_link_libraries(target_name PRIVATE Qt6::Svg) + \endcode + + If you are wondering why a particular plugin is not included automatically, + you can run androiddeployqt with the \c{--verbose} option to get the list of + missing dependencies for each excluded plugin. You can achieve the same in + Qt Creator by ticking the \uicontrol {Verbose output} check box in the + \uicontrol {Projects} > \uicontrol {Build Steps} > \uicontrol {Build Android APK} > + \uicontrol {Advanced Actions}. + + It's also possible to manually specify the dependencies of your application. + For more information, see \l {QT_ANDROID_DEPLOYMENT_DEPENDENCIES} CMake variable. + + \note androiddeployqt scans the QML files of the project to collect the QML imports. + However, if you are loading QML code as a QString from C++ at runtime, that might + not work properly because androiddeployqt won't be aware of it at deploy time. + To remedy that, you can add a dummy QML file that imports such QML modules that + are referenced at runtime. + + \section1 Deployment in Qt Creator + + Qt Creator uses \c androiddeployqt under the hood, and provides easy + and intuitive user interfaces to specify various options. For more information, + see \l{Qt Creator: Deploying Applications to Android Devices}. + + For more information about customizing and deploying a Qt for Android app, + see \l {Deploying an Application on Android}. +*/ diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp index e0e1e2c8bc..6125b405b5 100644 --- a/src/tools/androiddeployqt/main.cpp +++ b/src/tools/androiddeployqt/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QCoreApplication> #include <QStringList> @@ -36,13 +11,18 @@ #include <QDebug> #include <QDataStream> #include <QXmlStreamReader> -#include <QDateTime> #include <QStandardPaths> #include <QUuid> -#include <QDirIterator> +#include <QDirListing> +#include <QElapsedTimer> #include <QRegularExpression> +#include <QSettings> +#include <QHash> +#include <QSet> +#include <QMap> #include <depfile_shared.h> +#include <shellquote_shared.h> #include <algorithm> @@ -58,46 +38,16 @@ #define QT_POPEN_READ "r" #endif -class ActionTimer -{ - qint64 started; -public: - ActionTimer() = default; - void start() - { - started = QDateTime::currentMSecsSinceEpoch(); - } - int elapsed() - { - return int(QDateTime::currentMSecsSinceEpoch() - started); - } -}; +using namespace Qt::StringLiterals; static const bool mustReadOutputAnyway = true; // pclose seems to return the wrong error code unless we read the output static QStringList dependenciesForDepfile; -void deleteRecursively(const QString &dirName) -{ - QDir dir(dirName); - if (!dir.exists()) - return; - - const QFileInfoList entries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - for (const QFileInfo &entry : entries) { - if (entry.isDir()) - deleteRecursively(entry.absoluteFilePath()); - else - QFile::remove(entry.absoluteFilePath()); - } - - QDir().rmdir(dirName); -} - FILE *openProcess(const QString &command) { #if defined(Q_OS_WIN32) - QString processedCommand = QLatin1Char('\"') + command + QLatin1Char('\"'); + QString processedCommand = u'\"' + command + u'\"'; #else const QString& processedCommand = command; #endif @@ -118,6 +68,24 @@ struct QtDependency QString absolutePath; }; +struct QtInstallDirectoryWithTriple +{ + QtInstallDirectoryWithTriple(const QString &dir = QString(), + const QString &t = QString(), + const QHash<QString, QString> &dirs = QHash<QString, QString>() + ) : + qtInstallDirectory(dir), + qtDirectories(dirs), + triple(t), + enabled(false) + {} + + QString qtInstallDirectory; + QHash<QString, QString> qtDirectories; + QString triple; + bool enabled; +}; + struct Options { Options() @@ -128,12 +96,11 @@ struct Options , auxMode(false) , deploymentMechanism(Bundled) , releasePackage(false) - , digestAlg(QLatin1String("SHA-256")) - , sigAlg(QLatin1String("SHA256withRSA")) + , digestAlg("SHA-256"_L1) + , sigAlg("SHA256withRSA"_L1) , internalSf(false) , sectionsOnly(false) , protectedAuthenticationPath(false) - , jarSigner(false) , installApk(false) , uninstallApk(false) , qmlImportScannerBinaryPath() @@ -141,7 +108,8 @@ struct Options enum DeploymentMechanism { - Bundled + Bundled, + Unbundled }; enum TriState { @@ -155,17 +123,28 @@ struct Options bool timing; bool build; bool auxMode; - ActionTimer timer; + bool noRccBundleCleanup = false; + bool copyDependenciesOnly = false; + QElapsedTimer timer; // External tools QString sdkPath; QString sdkBuildToolsVersion; QString ndkPath; + QString ndkVersion; QString jdkPath; // Build paths QString qtInstallDirectory; + QHash<QString, QString> qtDirectories; + QString qtDataDirectory; + QString qtLibsDirectory; + QString qtLibExecsDirectory; + QString qtPluginsDirectory; + QString qtQmlDirectory; + QString qtHostDirectory; std::vector<QString> extraPrefixDirs; + QStringList androidDeployPlugins; // Unlike 'extraPrefixDirs', the 'extraLibraryDirs' key doesn't expect the 'lib' subfolder // when looking for dependencies. std::vector<QString> extraLibraryDirs; @@ -185,7 +164,7 @@ struct Options QString versionName; QString versionCode; QByteArray minSdkVersion{"23"}; - QByteArray targetSdkVersion{"30"}; + QByteArray targetSdkVersion{"34"}; // lib c++ path QString stdCppPath; @@ -193,7 +172,7 @@ struct Options // Build information QString androidPlatform; - QHash<QString, QString> architectures; + QHash<QString, QtInstallDirectoryWithTriple> architectures; QString currentArchitecture; QString toolchainPrefix; QString ndkHost; @@ -203,6 +182,7 @@ struct Options // Package information DeploymentMechanism deploymentMechanism; + QString systemLibsPath; QString packageName; QStringList extraLibs; QHash<QString, QStringList> archExtraLibs; @@ -225,7 +205,6 @@ struct Options bool internalSf; bool sectionsOnly; bool protectedAuthenticationPath; - bool jarSigner; QString apkPath; // Installation information @@ -234,11 +213,19 @@ struct Options QString installLocation; // Per architecture collected information - void clear(const QString &arch) + void setCurrentQtArchitecture(const QString &arch, + const QString &directory, + const QHash<QString, QString> &directories) { currentArchitecture = arch; - } - typedef QPair<QString, QString> BundledFile; + qtInstallDirectory = directory; + qtDataDirectory = directories["qtDataDirectory"_L1]; + qtLibsDirectory = directories["qtLibsDirectory"_L1]; + qtLibExecsDirectory = directories["qtLibExecsDirectory"_L1]; + qtPluginsDirectory = directories["qtPluginsDirectory"_L1]; + qtQmlDirectory = directories["qtQmlDirectory"_L1]; + } + using BundledFile = std::pair<QString, QString>; QHash<QString, QList<BundledFile>> bundledFiles; QHash<QString, QList<QtDependency>> qtDependencies; QHash<QString, QStringList> localLibs; @@ -251,6 +238,7 @@ struct Options // Override qml import scanner path QString qmlImportScannerBinaryPath; + bool qmlSkipImportScanning = false; }; static const QHash<QByteArray, QByteArray> elfArchitectures = { @@ -260,84 +248,52 @@ static const QHash<QByteArray, QByteArray> elfArchitectures = { {"x86_64", "x86_64"} }; -// Copy-pasted from qmake/library/ioutil.cpp -inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16]) +bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies); +bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath, + const QString &moduleUrl); +bool readDependenciesFromElf(Options *options, const QString &fileName, + QSet<QString> *usedDependencies, QSet<QString> *remainingDependencies); + +QString architectureFromName(const QString &name) { - for (int x = arg.length() - 1; x >= 0; --x) { - ushort c = arg.unicode()[x].unicode(); - if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)))) - return true; - } - return false; + QRegularExpression architecture(QStringLiteral("_(armeabi-v7a|arm64-v8a|x86|x86_64).so$")); + auto match = architecture.match(name); + if (!match.hasMatch()) + return {}; + return match.captured(1); } -static QString shellQuoteUnix(const QString &arg) +static QString execSuffixAppended(QString path) { - // Chars that should be quoted (TM). This includes: - static const uchar iqm[] = { - 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, - 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 - }; // 0-32 \'"$`<>|;&(){}*?#!~[] - - if (!arg.length()) - return QLatin1String("\"\""); - - QString ret(arg); - if (hasSpecialChars(ret, iqm)) { - ret.replace(QLatin1Char('\''), QLatin1String("'\\''")); - ret.prepend(QLatin1Char('\'')); - ret.append(QLatin1Char('\'')); - } - return ret; +#if defined(Q_OS_WIN32) + path += ".exe"_L1; +#endif + return path; } -static QString shellQuoteWin(const QString &arg) +static QString batSuffixAppended(QString path) { - // Chars that should be quoted (TM). This includes: - // - control chars & space - // - the shell meta chars "&()<>^| - // - the potential separators ,;= - static const uchar iqm[] = { - 0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78, - 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 - }; - - if (!arg.length()) - return QLatin1String("\"\""); - - QString ret(arg); - if (hasSpecialChars(ret, iqm)) { - // Quotes are escaped and their preceding backslashes are doubled. - // It's impossible to escape anything inside a quoted string on cmd - // level, so the outer quoting must be "suspended". - ret.replace(QRegularExpression(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\"")); - // The argument must not end with a \ since this would be interpreted - // as escaping the quote -- rather put the \ behind the quote: e.g. - // rather use "foo"\ than "foo\" - int i = ret.length(); - while (i > 0 && ret.at(i - 1) == QLatin1Char('\\')) - --i; - ret.insert(i, QLatin1Char('"')); - ret.prepend(QLatin1Char('"')); - } - return ret; +#if defined(Q_OS_WIN32) + path += ".bat"_L1; +#endif + return path; } -static QString shellQuote(const QString &arg) +QString defaultLibexecDir() { - if (QDir::separator() == QLatin1Char('\\')) - return shellQuoteWin(arg); - else - return shellQuoteUnix(arg); +#ifdef Q_OS_WIN32 + return "bin"_L1; +#else + return "libexec"_L1; +#endif } -QString architectureFromName(const QString &name) +static QString llvmReadobjPath(const Options &options) { - QRegularExpression architecture(QStringLiteral("_(armeabi-v7a|arm64-v8a|x86|x86_64).so$")); - auto match = architecture.match(name); - if (!match.hasMatch()) - return {}; - return match.captured(1); + return execSuffixAppended("%1/toolchains/%2/prebuilt/%3/bin/llvm-readobj"_L1 + .arg(options.ndkPath, + options.toolchainPrefix, + options.ndkHost)); } QString fileArchitecture(const Options &options, const QString &path) @@ -346,19 +302,13 @@ QString fileArchitecture(const Options &options, const QString &path) if (!arch.isEmpty()) return arch; - QString readElf = QLatin1String("%1/toolchains/%2/prebuilt/%3/bin/llvm-readobj").arg(options.ndkPath, - options.toolchainPrefix, - options.ndkHost); -#if defined(Q_OS_WIN32) - readElf += QLatin1String(".exe"); -#endif - + QString readElf = llvmReadobjPath(options); if (!QFile::exists(readElf)) { fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf)); return {}; } - readElf = QLatin1String("%1 -needed-libs %2").arg(shellQuote(readElf), shellQuote(path)); + readElf = "%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(path)); FILE *readElfCommand = openProcess(readElf); if (!readElfCommand) { @@ -369,7 +319,6 @@ QString fileArchitecture(const Options &options, const QString &path) char buffer[512]; while (fgets(buffer, sizeof(buffer), readElfCommand) != nullptr) { QByteArray line = QByteArray::fromRawData(buffer, qstrlen(buffer)); - QString library; line = line.trimmed(); if (line.startsWith("Arch: ")) { auto it = elfArchitectures.find(line.mid(6)); @@ -398,7 +347,7 @@ void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir & for (const QFileInfo &src : srcEntries) if (dst.fileName() == src.fileName()) { if (dst.isDir()) - deleteMissingFiles(options, src.absoluteDir(), dst.absoluteDir()); + deleteMissingFiles(options, src.absoluteFilePath(), dst.absoluteFilePath()); found = true; break; } @@ -408,7 +357,7 @@ void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir & fprintf(stdout, "%s not found in %s, removing it.\n", qPrintable(dst.fileName()), qPrintable(srcDir.absolutePath())); if (dst.isDir()) - deleteRecursively(dst.absolutePath()); + QDir{dst.absolutePath()}.removeRecursively(); else QFile::remove(dst.absoluteFilePath()); } @@ -416,7 +365,6 @@ void deleteMissingFiles(const Options &options, const QDir &srcDir, const QDir & fflush(stdout); } - Options parseOptions() { Options options; @@ -424,94 +372,100 @@ Options parseOptions() QStringList arguments = QCoreApplication::arguments(); for (int i=0; i<arguments.size(); ++i) { const QString &argument = arguments.at(i); - if (argument.compare(QLatin1String("--output"), Qt::CaseInsensitive) == 0) { + if (argument.compare("--output"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.outputDirectory = arguments.at(++i).trimmed(); - } else if (argument.compare(QLatin1String("--input"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--input"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.inputFileName = arguments.at(++i); - } else if (argument.compare(QLatin1String("--aab"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--aab"_L1, Qt::CaseInsensitive) == 0) { options.buildAAB = true; options.build = true; - options.jarSigner = true; - } else if (!options.buildAAB && argument.compare(QLatin1String("--no-build"), Qt::CaseInsensitive) == 0) { + } else if (!options.buildAAB && argument.compare("--no-build"_L1, Qt::CaseInsensitive) == 0) { options.build = false; - } else if (argument.compare(QLatin1String("--install"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--install"_L1, Qt::CaseInsensitive) == 0) { options.installApk = true; options.uninstallApk = true; - } else if (argument.compare(QLatin1String("--reinstall"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--reinstall"_L1, Qt::CaseInsensitive) == 0) { options.installApk = true; options.uninstallApk = false; - } else if (argument.compare(QLatin1String("--android-platform"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--android-platform"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.androidPlatform = arguments.at(++i); - } else if (argument.compare(QLatin1String("--help"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--help"_L1, Qt::CaseInsensitive) == 0) { options.helpRequested = true; - } else if (argument.compare(QLatin1String("--verbose"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--verbose"_L1, Qt::CaseInsensitive) == 0) { options.verbose = true; - } else if (argument.compare(QLatin1String("--deployment"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--deployment"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) { options.helpRequested = true; } else { QString deploymentMechanism = arguments.at(++i); - if (deploymentMechanism.compare(QLatin1String("bundled"), Qt::CaseInsensitive) == 0) { + if (deploymentMechanism.compare("bundled"_L1, Qt::CaseInsensitive) == 0) { options.deploymentMechanism = Options::Bundled; + } else if (deploymentMechanism.compare("unbundled"_L1, + Qt::CaseInsensitive) == 0) { + options.deploymentMechanism = Options::Unbundled; } else { fprintf(stderr, "Unrecognized deployment mechanism: %s\n", qPrintable(deploymentMechanism)); options.helpRequested = true; } } - } else if (argument.compare(QLatin1String("--device"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--device"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.installLocation = arguments.at(++i); - } else if (argument.compare(QLatin1String("--release"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--release"_L1, Qt::CaseInsensitive) == 0) { options.releasePackage = true; - } else if (argument.compare(QLatin1String("--jdk"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--jdk"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.jdkPath = arguments.at(++i); - } else if (argument.compare(QLatin1String("--apk"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--apk"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.apkPath = arguments.at(++i); - } else if (argument.compare(QLatin1String("--depfile"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--depfile"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.depFilePath = arguments.at(++i); - } else if (argument.compare(QLatin1String("--builddir"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--builddir"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.buildDirectory = arguments.at(++i); - } else if (argument.compare(QLatin1String("--sign"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--sign"_L1, Qt::CaseInsensitive) == 0) { if (i + 2 >= arguments.size()) { const QString keyStore = qEnvironmentVariable("QT_ANDROID_KEYSTORE_PATH"); const QString storeAlias = qEnvironmentVariable("QT_ANDROID_KEYSTORE_ALIAS"); if (keyStore.isEmpty() || storeAlias.isEmpty()) { options.helpRequested = true; + fprintf(stderr, "Package signing path and alias values are not specified.\n"); } else { fprintf(stdout, "Using package signing path and alias values found from the " "environment variables.\n"); - options.releasePackage = true; options.keyStore = keyStore; options.keyStoreAlias = storeAlias; } - } else { - options.releasePackage = true; + } else if (!arguments.at(i + 1).startsWith("--"_L1) && + !arguments.at(i + 2).startsWith("--"_L1)) { options.keyStore = arguments.at(++i); options.keyStoreAlias = arguments.at(++i); + } else { + options.helpRequested = true; + fprintf(stderr, "Package signing path and alias values are not " + "specified.\n"); } // Do not override if the passwords are provided through arguments @@ -525,58 +479,62 @@ Options parseOptions() "variable.\n"); options.keyPass = qEnvironmentVariable("QT_ANDROID_KEYSTORE_KEY_PASS"); } - } else if (argument.compare(QLatin1String("--storepass"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--storepass"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.keyStorePassword = arguments.at(++i); - } else if (argument.compare(QLatin1String("--storetype"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--storetype"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.storeType = arguments.at(++i); - } else if (argument.compare(QLatin1String("--keypass"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--keypass"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.keyPass = arguments.at(++i); - } else if (argument.compare(QLatin1String("--sigfile"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--sigfile"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.sigFile = arguments.at(++i); - } else if (argument.compare(QLatin1String("--digestalg"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--digestalg"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.digestAlg = arguments.at(++i); - } else if (argument.compare(QLatin1String("--sigalg"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--sigalg"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.sigAlg = arguments.at(++i); - } else if (argument.compare(QLatin1String("--tsa"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--tsa"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.tsaUrl = arguments.at(++i); - } else if (argument.compare(QLatin1String("--tsacert"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--tsacert"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) options.helpRequested = true; else options.tsaCert = arguments.at(++i); - } else if (argument.compare(QLatin1String("--internalsf"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--internalsf"_L1, Qt::CaseInsensitive) == 0) { options.internalSf = true; - } else if (argument.compare(QLatin1String("--sectionsonly"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--sectionsonly"_L1, Qt::CaseInsensitive) == 0) { options.sectionsOnly = true; - } else if (argument.compare(QLatin1String("--protected"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--protected"_L1, Qt::CaseInsensitive) == 0) { options.protectedAuthenticationPath = true; - } else if (argument.compare(QLatin1String("--jarsigner"), Qt::CaseInsensitive) == 0) { - options.jarSigner = true; - } else if (argument.compare(QLatin1String("--aux-mode"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--aux-mode"_L1, Qt::CaseInsensitive) == 0) { options.auxMode = true; - } else if (argument.compare(QLatin1String("--qml-importscanner-binary"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--qml-importscanner-binary"_L1, Qt::CaseInsensitive) == 0) { options.qmlImportScannerBinaryPath = arguments.at(++i).trimmed(); + } else if (argument.compare("--no-rcc-bundle-cleanup"_L1, + Qt::CaseInsensitive) == 0) { + options.noRccBundleCleanup = true; + } else if (argument.compare("--copy-dependencies-only"_L1, + Qt::CaseInsensitive) == 0) { + options.copyDependenciesOnly = true; } } @@ -584,7 +542,7 @@ Options parseOptions() options.helpRequested = true; if (options.inputFileName.isEmpty()) - options.inputFileName = QLatin1String("android-%1-deployment-settings.json").arg(QDir::current().dirName()); + options.inputFileName = "android-%1-deployment-settings.json"_L1.arg(QDir::current().dirName()); options.timing = qEnvironmentVariableIsSet("ANDROIDDEPLOYQT_TIMING_OUTPUT"); @@ -593,112 +551,121 @@ Options parseOptions() options.outputDirectory.clear(); } else { options.outputDirectory = QFileInfo(options.outputDirectory).canonicalFilePath(); - if (!options.outputDirectory.endsWith(QLatin1Char('/'))) - options.outputDirectory += QLatin1Char('/'); + if (!options.outputDirectory.endsWith(u'/')) + options.outputDirectory += u'/'; } return options; } void printHelp() -{// "012345678901234567890123456789012345678901234567890123456789012345678901" - fprintf(stderr, "Syntax: %s --output <destination> [options]\n" - "\n" - " Creates an Android package in the build directory <destination> and\n" - " builds it into an .apk file.\n" - "\n" - " Optional arguments:\n" - " --input <inputfile>: Reads <inputfile> for options generated by\n" - " qmake. A default file name based on the current working\n" - " directory will be used if nothing else is specified.\n" - "\n" - " --deployment <mechanism>: Supported deployment mechanisms:\n" - " bundled (default): Include Qt files in stand-alone package.\n" - "\n" - " --aab: Build an Android App Bundle.\n" - "\n" - " --no-build: Do not build the package, it is useful to just install\n" - " a package previously built.\n" - "\n" - " --install: Installs apk to device/emulator. By default this step is\n" - " not taken. If the application has previously been installed on\n" - " the device, it will be uninstalled first.\n" - "\n" - " --reinstall: Installs apk to device/emulator. By default this step\n" - " is not taken. If the application has previously been installed on\n" - " the device, it will be overwritten, but its data will be left\n" - " intact.\n" - "\n" - " --device [device ID]: Use specified device for deployment. Default\n" - " is the device selected by default by adb.\n" - "\n" - " --android-platform <platform>: Builds against the given android\n" - " platform. By default, the highest available version will be\n" - " used.\n" - "\n" - " --release: Builds a package ready for release. By default, the\n" - " package will be signed with a debug key.\n" - "\n" - " --sign <url/to/keystore> <alias>: Signs the package with the\n" - " specified keystore, alias and store password. Also implies the\n" - " --release option.\n" - " Optional arguments for use with signing:\n" - " --storepass <password>: Keystore password.\n" - " --storetype <type>: Keystore type.\n" - " --keypass <password>: Password for private key (if different\n" - " from keystore password.)\n" - " --sigfile <file>: Name of .SF/.DSA file.\n" - " --digestalg <name>: Name of digest algorithm. Default is\n" - " \"SHA1\".\n" - " --sigalg <name>: Name of signature algorithm. Default is\n" - " \"SHA1withRSA\".\n" - " --tsa <url>: Location of the Time Stamping Authority.\n" - " --tsacert <alias>: Public key certificate for TSA.\n" - " --internalsf: Include the .SF file inside the signature block.\n" - " --sectionsonly: Don't compute hash of entire manifest.\n" - " --protected: Keystore has protected authentication path.\n" - " --jarsigner: Force jarsigner usage, otherwise apksigner will be\n" - " used if available.\n" - "\n" - " NOTE: To conceal the keystore information, the environment variables\n" - " QT_ANDROID_KEYSTORE_PATH, and QT_ANDROID_KEYSTORE_ALIAS are used to\n" - " set the values keysotore and alias respectively.\n" - " Also the environment variables QT_ANDROID_KEYSTORE_STORE_PASS,\n" - " and QT_ANDROID_KEYSTORE_KEY_PASS are used to set the store and key\n" - " passwords respectively. This option needs only the --sign parameter.\n" - "\n" - " --jdk <path/to/jdk>: Used to find the jarsigner tool when used\n" - " in combination with the --release argument. By default,\n" - " an attempt is made to detect the tool using the JAVA_HOME and\n" - " PATH environment variables, in that order.\n" - "\n" - " --qml-import-paths: Specify additional search paths for QML\n" - " imports.\n" - "\n" - " --verbose: Prints out information during processing.\n" - "\n" - " --no-generated-assets-cache: Do not pregenerate the entry list for\n" - " the assets file engine.\n" - "\n" - " --aux-mode: Operate in auxiliary mode. This will only copy the\n" - " dependencies into the build directory and update the XML templates.\n" - " The project will not be built or installed.\n" - "\n" - " --apk <path/where/to/copy/the/apk>: Path where to copy the built apk.\n" - "\n" - " --qml-importscanner-binary <path/to/qmlimportscanner>: Override the\n" - " default qmlimportscanner binary path. By default the\n" - " qmlimportscanner binary is located using the Qt directory\n" - " specified in the input file.\n" - "\n" - " --depfile <path/to/depfile>: Output a dependency file.\n" - "\n" - " --builddir <path/to/build/directory>: build directory. Necessary when\n" - " generating a depfile because ninja requires relative paths.\n" - "\n" - " --help: Displays this information.\n", - qPrintable(QCoreApplication::arguments().at(0)) - ); +{ + fprintf(stderr, R"( +Syntax: androiddeployqt --output <destination> [options] + +Creates an Android package in the build directory <destination> and +builds it into an .apk file. + +Optional arguments: + --input <inputfile>: Reads <inputfile> for options generated by + qmake. A default file name based on the current working + directory will be used if nothing else is specified. + + --deployment <mechanism>: Supported deployment mechanisms: + bundled (default): Includes Qt files in stand-alone package. + unbundled: Assumes native libraries are present on the device + and does not include them in the APK. + + --aab: Build an Android App Bundle. + + --no-build: Do not build the package, it is useful to just install + a package previously built. + + --install: Installs apk to device/emulator. By default this step is + not taken. If the application has previously been installed on + the device, it will be uninstalled first. + + --reinstall: Installs apk to device/emulator. By default this step + is not taken. If the application has previously been installed on + the device, it will be overwritten, but its data will be left + intact. + + --device [device ID]: Use specified device for deployment. Default + is the device selected by default by adb. + + --android-platform <platform>: Builds against the given android + platform. By default, the highest available version will be + used. + + --release: Builds a package ready for release. By default, the + package will be signed with a debug key. + + --sign <url/to/keystore> <alias>: Signs the package with the + specified keystore, alias and store password. + Optional arguments for use with signing: + --storepass <password>: Keystore password. + --storetype <type>: Keystore type. + --keypass <password>: Password for private key (if different + from keystore password.) + --sigfile <file>: Name of .SF/.DSA file. + --digestalg <name>: Name of digest algorithm. Default is + "SHA-256". + --sigalg <name>: Name of signature algorithm. Default is + "SHA256withRSA". + --tsa <url>: Location of the Time Stamping Authority. + --tsacert <alias>: Public key certificate for TSA. + --internalsf: Include the .SF file inside the signature block. + --sectionsonly: Don't compute hash of entire manifest. + --protected: Keystore has protected authentication path. + --jarsigner: Deprecated, ignored. + + NOTE: To conceal the keystore information, the environment variables + QT_ANDROID_KEYSTORE_PATH, and QT_ANDROID_KEYSTORE_ALIAS are used to + set the values keysotore and alias respectively. + Also the environment variables QT_ANDROID_KEYSTORE_STORE_PASS, + and QT_ANDROID_KEYSTORE_KEY_PASS are used to set the store and key + passwords respectively. This option needs only the --sign parameter. + + --jdk <path/to/jdk>: Used to find the jarsigner tool when used + in combination with the --release argument. By default, + an attempt is made to detect the tool using the JAVA_HOME and + PATH environment variables, in that order. + + --qml-import-paths: Specify additional search paths for QML + imports. + + --verbose: Prints out information during processing. + + --no-generated-assets-cache: Do not pregenerate the entry list for + the assets file engine. + + --aux-mode: Operate in auxiliary mode. This will only copy the + dependencies into the build directory and update the XML templates. + The project will not be built or installed. + + --apk <path/where/to/copy/the/apk>: Path where to copy the built apk. + + --qml-importscanner-binary <path/to/qmlimportscanner>: Override the + default qmlimportscanner binary path. By default the + qmlimportscanner binary is located using the Qt directory + specified in the input file. + + --depfile <path/to/depfile>: Output a dependency file. + + --builddir <path/to/build/directory>: build directory. Necessary when + generating a depfile because ninja requires relative paths. + + --no-rcc-bundle-cleanup: skip cleaning rcc bundle directory after + running androiddeployqt. This option simplifies debugging of + the resource bundle content, but it should not be used when deploying + a project, since it litters the 'assets' directory. + + --copy-dependencies-only: resolve application dependencies and stop + deploying process after all libraries and resources that the + application depends on have been copied. + + --help: Displays this information. +)"); } // Since strings compared will all start with the same letters, @@ -709,20 +676,20 @@ bool quasiLexicographicalReverseLessThan(const QFileInfo &fi1, const QFileInfo & QString s1 = fi1.baseName(); QString s2 = fi2.baseName(); - if (s1.length() == s2.length()) + if (s1.size() == s2.size()) return s1 > s2; else - return s1.length() > s2.length(); + return s1.size() > s2.size(); } // Files which contain templates that need to be overwritten by build data should be overwritten every // time. bool alwaysOverwritableFile(const QString &fileName) { - return (fileName.endsWith(QLatin1String("/res/values/libs.xml")) - || fileName.endsWith(QLatin1String("/AndroidManifest.xml")) - || fileName.endsWith(QLatin1String("/res/values/strings.xml")) - || fileName.endsWith(QLatin1String("/src/org/qtproject/qt/android/bindings/QtActivity.java"))); + return (fileName.endsWith("/res/values/libs.xml"_L1) + || fileName.endsWith("/AndroidManifest.xml"_L1) + || fileName.endsWith("/res/values/strings.xml"_L1) + || fileName.endsWith("/src/org/qtproject/qt/android/bindings/QtActivity.java"_L1)); } @@ -776,48 +743,47 @@ QString cleanPackageName(QString packageName) }; for (QChar &c : packageName) { if (!isLegalChar(c)) - c = QLatin1Char('_'); + c = u'_'; } static QStringList keywords; if (keywords.isEmpty()) { - keywords << QLatin1String("abstract") << QLatin1String("continue") << QLatin1String("for") - << QLatin1String("new") << QLatin1String("switch") << QLatin1String("assert") - << QLatin1String("default") << QLatin1String("if") << QLatin1String("package") - << QLatin1String("synchronized") << QLatin1String("boolean") << QLatin1String("do") - << QLatin1String("goto") << QLatin1String("private") << QLatin1String("this") - << QLatin1String("break") << QLatin1String("double") << QLatin1String("implements") - << QLatin1String("protected") << QLatin1String("throw") << QLatin1String("byte") - << QLatin1String("else") << QLatin1String("import") << QLatin1String("public") - << QLatin1String("throws") << QLatin1String("case") << QLatin1String("enum") - << QLatin1String("instanceof") << QLatin1String("return") << QLatin1String("transient") - << QLatin1String("catch") << QLatin1String("extends") << QLatin1String("int") - << QLatin1String("short") << QLatin1String("try") << QLatin1String("char") - << QLatin1String("final") << QLatin1String("interface") << QLatin1String("static") - << QLatin1String("void") << QLatin1String("class") << QLatin1String("finally") - << QLatin1String("long") << QLatin1String("strictfp") << QLatin1String("volatile") - << QLatin1String("const") << QLatin1String("float") << QLatin1String("native") - << QLatin1String("super") << QLatin1String("while"); + keywords << "abstract"_L1 << "continue"_L1 << "for"_L1 + << "new"_L1 << "switch"_L1 << "assert"_L1 + << "default"_L1 << "if"_L1 << "package"_L1 + << "synchronized"_L1 << "boolean"_L1 << "do"_L1 + << "goto"_L1 << "private"_L1 << "this"_L1 + << "break"_L1 << "double"_L1 << "implements"_L1 + << "protected"_L1 << "throw"_L1 << "byte"_L1 + << "else"_L1 << "import"_L1 << "public"_L1 + << "throws"_L1 << "case"_L1 << "enum"_L1 + << "instanceof"_L1 << "return"_L1 << "transient"_L1 + << "catch"_L1 << "extends"_L1 << "int"_L1 + << "short"_L1 << "try"_L1 << "char"_L1 + << "final"_L1 << "interface"_L1 << "static"_L1 + << "void"_L1 << "class"_L1 << "finally"_L1 + << "long"_L1 << "strictfp"_L1 << "volatile"_L1 + << "const"_L1 << "float"_L1 << "native"_L1 + << "super"_L1 << "while"_L1; } // No keywords - int index = -1; - while (index < packageName.length()) { - int next = packageName.indexOf(QLatin1Char('.'), index + 1); + qsizetype index = -1; + while (index < packageName.size()) { + qsizetype next = packageName.indexOf(u'.', index + 1); if (next == -1) - next = packageName.length(); + next = packageName.size(); QString word = packageName.mid(index + 1, next - index - 1); if (!word.isEmpty()) { QChar c = word[0]; - if ((c >= QChar(QLatin1Char('0')) && c<= QChar(QLatin1Char('9'))) - || c == QLatin1Char('_')) { - packageName.insert(index + 1, QLatin1Char('a')); + if ((c >= u'0' && c <= u'9') || c == u'_') { + packageName.insert(index + 1, u'a'); index = next + 1; continue; } } if (keywords.contains(word)) { - packageName.insert(next, QLatin1String("_")); + packageName.insert(next, "_"_L1); index = next + 1; } else { index = next; @@ -829,7 +795,7 @@ QString cleanPackageName(QString packageName) QString detectLatestAndroidPlatform(const QString &sdkPath) { - QDir dir(sdkPath + QLatin1String("/platforms")); + QDir dir(sdkPath + "/platforms"_L1); if (!dir.exists()) { fprintf(stderr, "Directory %s does not exist\n", qPrintable(dir.absolutePath())); return QString(); @@ -843,7 +809,7 @@ QString detectLatestAndroidPlatform(const QString &sdkPath) std::sort(fileInfos.begin(), fileInfos.end(), quasiLexicographicalReverseLessThan); - QFileInfo latestPlatform = fileInfos.first(); + const QFileInfo& latestPlatform = fileInfos.constFirst(); return latestPlatform.baseName(); } @@ -854,14 +820,83 @@ QString packageNameFromAndroidManifest(const QString &androidManifestPath) QXmlStreamReader reader(&androidManifestXml); while (!reader.atEnd()) { reader.readNext(); - if (reader.isStartElement() && reader.name() == QLatin1String("manifest")) - return cleanPackageName( - reader.attributes().value(QLatin1String("package")).toString()); + if (reader.isStartElement() && reader.name() == "manifest"_L1) + return cleanPackageName(reader.attributes().value("package"_L1).toString()); } } return {}; } +bool parseCmakeBoolean(const QJsonValue &value) +{ + const QString stringValue = value.toString(); + return (stringValue.compare(QString::fromUtf8("true"), Qt::CaseInsensitive) + || stringValue.compare(QString::fromUtf8("on"), Qt::CaseInsensitive) + || stringValue.compare(QString::fromUtf8("yes"), Qt::CaseInsensitive) + || stringValue.compare(QString::fromUtf8("y"), Qt::CaseInsensitive) + || stringValue.toInt() > 0); +} + +bool readInputFileDirectory(Options *options, QJsonObject &jsonObject, const QString keyName) +{ + const QJsonValue qtDirectory = jsonObject.value(keyName); + if (qtDirectory.isUndefined()) { + for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) { + if (keyName == "qtDataDirectory"_L1) { + options->architectures[it.key()].qtDirectories[keyName] = "."_L1; + break; + } else if (keyName == "qtLibsDirectory"_L1) { + options->architectures[it.key()].qtDirectories[keyName] = "lib"_L1; + break; + } else if (keyName == "qtLibExecsDirectory"_L1) { + options->architectures[it.key()].qtDirectories[keyName] = defaultLibexecDir(); + break; + } else if (keyName == "qtPluginsDirectory"_L1) { + options->architectures[it.key()].qtDirectories[keyName] = "plugins"_L1; + break; + } else if (keyName == "qtQmlDirectory"_L1) { + options->architectures[it.key()].qtDirectories[keyName] = "qml"_L1; + break; + } + } + return true; + } + + if (qtDirectory.isObject()) { + const QJsonObject object = qtDirectory.toObject(); + for (auto it = object.constBegin(); it != object.constEnd(); ++it) { + if (it.value().isUndefined()) { + fprintf(stderr, + "Invalid '%s' record in deployment settings: %s\n", + qPrintable(keyName), + qPrintable(it.value().toString())); + return false; + } + if (it.value().isNull()) + continue; + if (!options->architectures.contains(it.key())) { + fprintf(stderr, "Architecture %s unknown (%s).", qPrintable(it.key()), + qPrintable(options->architectures.keys().join(u','))); + return false; + } + options->architectures[it.key()].qtDirectories[keyName] = it.value().toString(); + } + } else if (qtDirectory.isString()) { + // Format for Qt < 6 or when using the tool with Qt >= 6 but in single arch. + // We assume Qt > 5.14 where all architectures are in the same directory. + const QString directory = qtDirectory.toString(); + options->architectures["arm64-v8a"_L1].qtDirectories[keyName] = directory; + options->architectures["armeabi-v7a"_L1].qtDirectories[keyName] = directory; + options->architectures["x86"_L1].qtDirectories[keyName] = directory; + options->architectures["x86_64"_L1].qtDirectories[keyName] = directory; + } else { + fprintf(stderr, "Invalid format for %s in json file %s.\n", + qPrintable(keyName), qPrintable(options->inputFileName)); + return false; + } + return true; +} + bool readInputFile(Options *options) { QFile file(options->inputFileName); @@ -880,7 +915,7 @@ bool readInputFile(Options *options) QJsonObject jsonObject = jsonDocument.object(); { - QJsonValue sdkPath = jsonObject.value(QLatin1String("sdk")); + QJsonValue sdkPath = jsonObject.value("sdk"_L1); if (sdkPath.isUndefined()) { fprintf(stderr, "No SDK path in json file %s\n", qPrintable(options->inputFileName)); return false; @@ -893,7 +928,7 @@ bool readInputFile(Options *options) if (options->androidPlatform.isEmpty()) return false; } else { - if (!QDir(options->sdkPath + QLatin1String("/platforms/") + options->androidPlatform).exists()) { + if (!QDir(options->sdkPath + "/platforms/"_L1 + options->androidPlatform).exists()) { fprintf(stderr, "Warning: Android platform '%s' does not exist in SDK.\n", qPrintable(options->androidPlatform)); } @@ -902,22 +937,74 @@ bool readInputFile(Options *options) { - const QJsonValue value = jsonObject.value(QLatin1String("sdkBuildToolsRevision")); + const QJsonValue value = jsonObject.value("sdkBuildToolsRevision"_L1); if (!value.isUndefined()) options->sdkBuildToolsVersion = value.toString(); } { - const QJsonValue qtInstallDirectory = jsonObject.value(QLatin1String("qt")); + const QJsonValue qtInstallDirectory = jsonObject.value("qt"_L1); if (qtInstallDirectory.isUndefined()) { fprintf(stderr, "No Qt directory in json file %s\n", qPrintable(options->inputFileName)); return false; } - options->qtInstallDirectory = qtInstallDirectory.toString(); + + if (qtInstallDirectory.isObject()) { + const QJsonObject object = qtInstallDirectory.toObject(); + for (auto it = object.constBegin(); it != object.constEnd(); ++it) { + if (it.value().isUndefined()) { + fprintf(stderr, + "Invalid 'qt' record in deployment settings: %s\n", + qPrintable(it.value().toString())); + return false; + } + if (it.value().isNull()) + continue; + options->architectures.insert(it.key(), + QtInstallDirectoryWithTriple(it.value().toString())); + } + } else if (qtInstallDirectory.isString()) { + // Format for Qt < 6 or when using the tool with Qt >= 6 but in single arch. + // We assume Qt > 5.14 where all architectures are in the same directory. + const QString directory = qtInstallDirectory.toString(); + QtInstallDirectoryWithTriple qtInstallDirectoryWithTriple(directory); + options->architectures.insert("arm64-v8a"_L1, qtInstallDirectoryWithTriple); + options->architectures.insert("armeabi-v7a"_L1, qtInstallDirectoryWithTriple); + options->architectures.insert("x86"_L1, qtInstallDirectoryWithTriple); + options->architectures.insert("x86_64"_L1, qtInstallDirectoryWithTriple); + // In Qt < 6 rcc and qmlimportscanner are installed in the host and install directories + // In Qt >= 6 rcc and qmlimportscanner are only installed in the host directory + // So setting the "qtHostDir" is not necessary with Qt < 6. + options->qtHostDirectory = directory; + } else { + fprintf(stderr, "Invalid format for Qt install prefixes in json file %s.\n", + qPrintable(options->inputFileName)); + return false; + } + } + + if (!readInputFileDirectory(options, jsonObject, "qtDataDirectory"_L1) || + !readInputFileDirectory(options, jsonObject, "qtLibsDirectory"_L1) || + !readInputFileDirectory(options, jsonObject, "qtLibExecsDirectory"_L1) || + !readInputFileDirectory(options, jsonObject, "qtPluginsDirectory"_L1) || + !readInputFileDirectory(options, jsonObject, "qtQmlDirectory"_L1)) + return false; + + { + const QJsonValue qtHostDirectory = jsonObject.value("qtHostDir"_L1); + if (!qtHostDirectory.isUndefined()) { + if (qtHostDirectory.isString()) { + options->qtHostDirectory = qtHostDirectory.toString(); + } else { + fprintf(stderr, "Invalid format for Qt host directory in json file %s.\n", + qPrintable(options->inputFileName)); + return false; + } + } } { - const auto extraPrefixDirs = jsonObject.value(QLatin1String("extraPrefixDirs")).toArray(); + const auto extraPrefixDirs = jsonObject.value("extraPrefixDirs"_L1).toArray(); options->extraPrefixDirs.reserve(extraPrefixDirs.size()); for (const QJsonValue prefix : extraPrefixDirs) { options->extraPrefixDirs.push_back(prefix.toString()); @@ -925,7 +1012,12 @@ bool readInputFile(Options *options) } { - const auto extraLibraryDirs = jsonObject.value(QLatin1String("extraLibraryDirs")).toArray(); + const auto androidDeployPlugins = jsonObject.value("android-deploy-plugins"_L1).toString(); + options->androidDeployPlugins = androidDeployPlugins.split(";"_L1, Qt::SkipEmptyParts); + } + + { + const auto extraLibraryDirs = jsonObject.value("extraLibraryDirs"_L1).toArray(); options->extraLibraryDirs.reserve(extraLibraryDirs.size()); for (const QJsonValue path : extraLibraryDirs) { options->extraLibraryDirs.push_back(path.toString()); @@ -933,13 +1025,13 @@ bool readInputFile(Options *options) } { - const QJsonValue androidSourcesDirectory = jsonObject.value(QLatin1String("android-package-source-directory")); + const QJsonValue androidSourcesDirectory = jsonObject.value("android-package-source-directory"_L1); if (!androidSourcesDirectory.isUndefined()) options->androidSourceDirectory = androidSourcesDirectory.toString(); } { - const QJsonValue applicationArguments = jsonObject.value(QLatin1String("android-application-arguments")); + const QJsonValue applicationArguments = jsonObject.value("android-application-arguments"_L1); if (!applicationArguments.isUndefined()) options->applicationArguments = applicationArguments.toString(); else @@ -947,7 +1039,7 @@ bool readInputFile(Options *options) } { - const QJsonValue androidVersionName = jsonObject.value(QLatin1String("android-version-name")); + const QJsonValue androidVersionName = jsonObject.value("android-version-name"_L1); if (!androidVersionName.isUndefined()) options->versionName = androidVersionName.toString(); else @@ -955,7 +1047,7 @@ bool readInputFile(Options *options) } { - const QJsonValue androidVersionCode = jsonObject.value(QLatin1String("android-version-code")); + const QJsonValue androidVersionCode = jsonObject.value("android-version-code"_L1); if (!androidVersionCode.isUndefined()) options->versionCode = androidVersionCode.toString(); else @@ -963,19 +1055,19 @@ bool readInputFile(Options *options) } { - const QJsonValue ver = jsonObject.value(QLatin1String("android-min-sdk-version")); + const QJsonValue ver = jsonObject.value("android-min-sdk-version"_L1); if (!ver.isUndefined()) options->minSdkVersion = ver.toString().toUtf8(); } { - const QJsonValue ver = jsonObject.value(QLatin1String("android-target-sdk-version")); + const QJsonValue ver = jsonObject.value("android-target-sdk-version"_L1); if (!ver.isUndefined()) options->targetSdkVersion = ver.toString().toUtf8(); } { - const QJsonObject targetArchitectures = jsonObject.value(QLatin1String("architectures")).toObject(); + const QJsonObject targetArchitectures = jsonObject.value("architectures"_L1).toObject(); if (targetArchitectures.isEmpty()) { fprintf(stderr, "No target architecture defined in json file.\n"); return false; @@ -987,21 +1079,36 @@ bool readInputFile(Options *options) } if (it.value().isNull()) continue; - options->architectures.insert(it.key(), it.value().toString()); + if (!options->architectures.contains(it.key())) { + fprintf(stderr, "Architecture %s unknown (%s).", qPrintable(it.key()), + qPrintable(options->architectures.keys().join(u','))); + return false; + } + options->architectures[it.key()].triple = it.value().toString(); + options->architectures[it.key()].enabled = true; } } { - const QJsonValue ndk = jsonObject.value(QLatin1String("ndk")); + const QJsonValue ndk = jsonObject.value("ndk"_L1); if (ndk.isUndefined()) { fprintf(stderr, "No NDK path defined in json file.\n"); return false; } options->ndkPath = ndk.toString(); + const QString ndkPropertiesPath = options->ndkPath + QStringLiteral("/source.properties"); + const QSettings settings(ndkPropertiesPath, QSettings::IniFormat); + const QString ndkVersion = settings.value(QStringLiteral("Pkg.Revision")).toString(); + if (ndkVersion.isEmpty()) { + fprintf(stderr, "Couldn't retrieve the NDK version from \"%s\".\n", + qPrintable(ndkPropertiesPath)); + return false; + } + options->ndkVersion = ndkVersion; } { - const QJsonValue toolchainPrefix = jsonObject.value(QLatin1String("toolchain-prefix")); + const QJsonValue toolchainPrefix = jsonObject.value("toolchain-prefix"_L1); if (toolchainPrefix.isUndefined()) { fprintf(stderr, "No toolchain prefix defined in json file.\n"); return false; @@ -1010,7 +1117,7 @@ bool readInputFile(Options *options) } { - const QJsonValue ndkHost = jsonObject.value(QLatin1String("ndk-host")); + const QJsonValue ndkHost = jsonObject.value("ndk-host"_L1); if (ndkHost.isUndefined()) { fprintf(stderr, "No NDK host defined in json file.\n"); return false; @@ -1019,19 +1126,41 @@ bool readInputFile(Options *options) } { - const QJsonValue extraLibs = jsonObject.value(QLatin1String("android-extra-libs")); + const QJsonValue extraLibs = jsonObject.value("android-extra-libs"_L1); if (!extraLibs.isUndefined()) - options->extraLibs = extraLibs.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); + options->extraLibs = extraLibs.toString().split(u',', Qt::SkipEmptyParts); + } + + { + const QJsonValue qmlSkipImportScanning = jsonObject.value("qml-skip-import-scanning"_L1); + if (!qmlSkipImportScanning.isUndefined()) + options->qmlSkipImportScanning = qmlSkipImportScanning.toBool(); } { - const QJsonValue extraPlugins = jsonObject.value(QLatin1String("android-extra-plugins")); + const QJsonValue extraPlugins = jsonObject.value("android-extra-plugins"_L1); if (!extraPlugins.isUndefined()) - options->extraPlugins = extraPlugins.toString().split(QLatin1Char(',')); + options->extraPlugins = extraPlugins.toString().split(u','); } { - const QJsonValue stdcppPath = jsonObject.value(QLatin1String("stdcpp-path")); + const QJsonValue systemLibsPath = + jsonObject.value("android-system-libs-prefix"_L1); + if (!systemLibsPath.isUndefined()) + options->systemLibsPath = systemLibsPath.toString(); + } + + { + const QJsonValue noDeploy = jsonObject.value("android-no-deploy-qt-libs"_L1); + if (!noDeploy.isUndefined()) { + bool useUnbundled = parseCmakeBoolean(noDeploy); + options->deploymentMechanism = useUnbundled ? Options::Unbundled : + Options::Bundled; + } + } + + { + const QJsonValue stdcppPath = jsonObject.value("stdcpp-path"_L1); if (stdcppPath.isUndefined()) { fprintf(stderr, "No stdcpp-path defined in json file.\n"); return false; @@ -1040,7 +1169,7 @@ bool readInputFile(Options *options) } { - const QJsonValue qmlRootPath = jsonObject.value(QLatin1String("qml-root-path")); + const QJsonValue qmlRootPath = jsonObject.value("qml-root-path"_L1); if (qmlRootPath.isString()) { options->rootPaths.push_back(qmlRootPath.toString()); } else if (qmlRootPath.isArray()) { @@ -1050,30 +1179,30 @@ bool readInputFile(Options *options) options->rootPaths.push_back(path.toString()); } } else { - options->rootPaths.push_back(options->inputFileName); + options->rootPaths.push_back(QFileInfo(options->inputFileName).absolutePath()); } } { - const QJsonValue qmlImportPaths = jsonObject.value(QLatin1String("qml-import-paths")); + const QJsonValue qmlImportPaths = jsonObject.value("qml-import-paths"_L1); if (!qmlImportPaths.isUndefined()) - options->qmlImportPaths = qmlImportPaths.toString().split(QLatin1Char(',')); + options->qmlImportPaths = qmlImportPaths.toString().split(u','); } { - const QJsonValue qmlImportScannerBinaryPath = jsonObject.value(QLatin1String("qml-importscanner-binary")); + const QJsonValue qmlImportScannerBinaryPath = jsonObject.value("qml-importscanner-binary"_L1); if (!qmlImportScannerBinaryPath.isUndefined()) options->qmlImportScannerBinaryPath = qmlImportScannerBinaryPath.toString(); } { - const QJsonValue rccBinaryPath = jsonObject.value(QLatin1String("rcc-binary")); + const QJsonValue rccBinaryPath = jsonObject.value("rcc-binary"_L1); if (!rccBinaryPath.isUndefined()) options->rccBinaryPath = rccBinaryPath.toString(); } { - const QJsonValue applicationBinary = jsonObject.value(QLatin1String("application-binary")); + const QJsonValue applicationBinary = jsonObject.value("application-binary"_L1); if (applicationBinary.isUndefined()) { fprintf(stderr, "No application binary defined in json file.\n"); return false; @@ -1081,7 +1210,9 @@ bool readInputFile(Options *options) options->applicationBinary = applicationBinary.toString(); if (options->build) { for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) { - auto appBinaryPath = QLatin1String("%1/libs/%2/lib%3_%2.so").arg(options->outputDirectory, it.key(), options->applicationBinary); + if (!it->enabled) + continue; + auto appBinaryPath = "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, it.key(), options->applicationBinary); if (!QFile::exists(appBinaryPath)) { fprintf(stderr, "Cannot find application binary in build dir %s.\n", qPrintable(appBinaryPath)); return false; @@ -1091,21 +1222,21 @@ bool readInputFile(Options *options) } { - const QJsonValue deploymentDependencies = jsonObject.value(QLatin1String("deployment-dependencies")); + using ItFlag = QDirListing::IteratorFlag; + const QJsonValue deploymentDependencies = jsonObject.value("deployment-dependencies"_L1); if (!deploymentDependencies.isUndefined()) { QString deploymentDependenciesString = deploymentDependencies.toString(); - const auto dependencies = QStringView{deploymentDependenciesString}.split(QLatin1Char(',')); + const auto dependencies = QStringView{deploymentDependenciesString}.split(u','); for (const auto &dependency : dependencies) { QString path = options->qtInstallDirectory + QChar::fromLatin1('/'); path += dependency; if (QFileInfo(path).isDir()) { - QDirIterator iterator(path, QDirIterator::Subdirectories); - while (iterator.hasNext()) { - if (iterator.nextFileInfo().isFile()) { - QString subPath = iterator.filePath(); + for (const auto &dirEntry : QDirListing(path, ItFlag::Recursive)) { + if (dirEntry.isFile()) { + const QString subPath = dirEntry.filePath(); auto arch = fileArchitecture(*options, subPath); if (!arch.isEmpty()) { - options->qtDependencies[arch].append(QtDependency(subPath.mid(options->qtInstallDirectory.length() + 1), + options->qtDependencies[arch].append(QtDependency(subPath.mid(options->qtInstallDirectory.size() + 1), subPath)); } else if (options->verbose) { fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(subPath)); @@ -1114,34 +1245,51 @@ bool readInputFile(Options *options) } } } else { - auto arch = fileArchitecture(*options, path); - if (!arch.isEmpty()) { - options->qtDependencies[arch].append(QtDependency(dependency.toString(), path)); - } else if (options->verbose) { - fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(path)); - fflush(stderr); + auto qtDependency = [options](const QStringView &dependency, + const QString &arch) { + const auto installDir = options->architectures[arch].qtInstallDirectory; + const auto absolutePath = "%1/%2"_L1.arg(installDir, dependency.toString()); + return QtDependency(dependency.toString(), absolutePath); + }; + + if (dependency.endsWith(QLatin1String(".so"))) { + auto arch = fileArchitecture(*options, path); + if (!arch.isEmpty()) { + options->qtDependencies[arch].append(qtDependency(dependency, arch)); + } else if (options->verbose) { + fprintf(stderr, "Skipping \"%s\", unknown architecture\n", qPrintable(path)); + fflush(stderr); + } + } else { + for (auto arch : options->architectures.keys()) + options->qtDependencies[arch].append(qtDependency(dependency, arch)); } } } } } { - const QJsonValue qrcFiles = jsonObject.value(QLatin1String("qrcFiles")); - options->qrcFiles = qrcFiles.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); + const QJsonValue qrcFiles = jsonObject.value("qrcFiles"_L1); + options->qrcFiles = qrcFiles.toString().split(u',', Qt::SkipEmptyParts); } { - const QJsonValue zstdCompressionFlag = jsonObject.value(QLatin1String("zstdCompression")); + const QJsonValue zstdCompressionFlag = jsonObject.value("zstdCompression"_L1); if (zstdCompressionFlag.isBool()) { options->isZstdCompressionEnabled = zstdCompressionFlag.toBool(); } } - options->packageName = packageNameFromAndroidManifest(options->androidSourceDirectory + QLatin1String("/AndroidManifest.xml")); + options->packageName = packageNameFromAndroidManifest(options->androidSourceDirectory + "/AndroidManifest.xml"_L1); if (options->packageName.isEmpty()) - options->packageName = cleanPackageName(QLatin1String("org.qtproject.example.%1").arg(options->applicationBinary)); + options->packageName = cleanPackageName("org.qtproject.example.%1"_L1.arg(options->applicationBinary)); return true; } +bool isDeployment(const Options *options, Options::DeploymentMechanism deployment) +{ + return options->deploymentMechanism == deployment; +} + bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, const Options &options, bool forceOverwrite = false) { const QFileInfoList entries = sourceDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); @@ -1153,7 +1301,7 @@ bool copyFiles(const QDir &sourceDirectory, const QDir &destinationDirectory, co return false; } - if (!copyFiles(dir, QDir(destinationDirectory.path() + QLatin1Char('/') + dir.dirName()), options, forceOverwrite)) + if (!copyFiles(dir, QDir(destinationDirectory.path() + u'/' + dir.dirName()), options, forceOverwrite)) return false; } else { QString destination = destinationDirectory.absoluteFilePath(entry.fileName()); @@ -1169,8 +1317,8 @@ void cleanTopFolders(const Options &options, const QDir &srcDir, const QString & { const auto dirs = srcDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs); for (const QFileInfo &dir : dirs) { - if (dir.fileName() != QLatin1String("libs")) - deleteMissingFiles(options, dir.absoluteDir(), QDir(dstDir + dir.fileName())); + if (dir.fileName() != "libs"_L1) + deleteMissingFiles(options, dir.absoluteFilePath(), QDir(dstDir + dir.fileName())); } } @@ -1179,13 +1327,15 @@ void cleanAndroidFiles(const Options &options) if (!options.androidSourceDirectory.isEmpty()) cleanTopFolders(options, QDir(options.androidSourceDirectory), options.outputDirectory); - cleanTopFolders(options, QDir(options.qtInstallDirectory + QLatin1String("/src/android/templates")), + cleanTopFolders(options, + QDir(options.qtInstallDirectory + u'/' + + options.qtDataDirectory + "/src/android/templates"_L1), options.outputDirectory); } bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, const QString &outDirPrefix = QString()) { - QDir sourceDirectory(options.qtInstallDirectory + androidTemplate); + QDir sourceDirectory(options.qtInstallDirectory + u'/' + options.qtDataDirectory + androidTemplate); if (!sourceDirectory.exists()) { fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath())); return false; @@ -1203,7 +1353,8 @@ bool copyAndroidTemplate(const Options &options, const QString &androidTemplate, bool copyGradleTemplate(const Options &options) { - QDir sourceDirectory(options.qtInstallDirectory + QLatin1String("/src/3rdparty/gradle")); + QDir sourceDirectory(options.qtInstallDirectory + u'/' + + options.qtDataDirectory + "/src/3rdparty/gradle"_L1); if (!sourceDirectory.exists()) { fprintf(stderr, "Cannot find template directory %s\n", qPrintable(sourceDirectory.absolutePath())); return false; @@ -1226,7 +1377,7 @@ bool copyAndroidTemplate(const Options &options) if (!copyGradleTemplate(options)) return false; - if (!copyAndroidTemplate(options, QLatin1String("/src/android/templates"))) + if (!copyAndroidTemplate(options, "/src/android/templates"_L1)) return false; return true; @@ -1254,8 +1405,16 @@ bool copyAndroidExtraLibs(Options *options) if (options->extraLibs.isEmpty()) return true; - if (options->verbose) - fprintf(stdout, "Copying %zd external libraries to package.\n", size_t(options->extraLibs.size())); + if (options->verbose) { + switch (options->deploymentMechanism) { + case Options::Bundled: + fprintf(stdout, "Copying %zd external libraries to package.\n", size_t(options->extraLibs.size())); + break; + case Options::Unbundled: + fprintf(stdout, "Skip copying of external libraries.\n"); + break; + }; + } for (const QString &extraLib : options->extraLibs) { QFileInfo extraLibInfo(extraLib); @@ -1268,19 +1427,21 @@ bool copyAndroidExtraLibs(Options *options) fprintf(stdout, "Skipping \"%s\", architecture mismatch.\n", qPrintable(extraLib)); continue; } - if (!extraLibInfo.fileName().startsWith(QLatin1String("lib")) || extraLibInfo.suffix() != QLatin1String("so")) { + if (!extraLibInfo.fileName().startsWith("lib"_L1) || extraLibInfo.suffix() != "so"_L1) { fprintf(stderr, "The file name of external library %s must begin with \"lib\" and end with the suffix \".so\".\n", qPrintable(extraLib)); return false; } QString destinationFile(options->outputDirectory - + QLatin1String("/libs/") + + "/libs/"_L1 + options->currentArchitecture - + QLatin1Char('/') + + u'/' + extraLibInfo.fileName()); - if (!copyFileIfNewer(extraLib, destinationFile, *options)) + if (isDeployment(options, Options::Bundled) + && !copyFileIfNewer(extraLib, destinationFile, *options)) { return false; + } options->archExtraLibs[options->currentArchitecture] += extraLib; } @@ -1318,18 +1479,21 @@ bool copyAndroidExtraResources(Options *options) } QDir resourceDir(extraResource); - QString assetsDir = options->outputDirectory + QLatin1String("/assets/") + resourceDir.dirName() + QLatin1Char('/'); - QString libsDir = options->outputDirectory + QLatin1String("/libs/") + options->currentArchitecture + QLatin1Char('/'); + QString assetsDir = options->outputDirectory + "/assets/"_L1 + + resourceDir.dirName() + u'/'; + QString libsDir = options->outputDirectory + "/libs/"_L1 + options->currentArchitecture + u'/'; const QStringList files = allFilesInside(resourceDir, resourceDir); for (const QString &resourceFile : files) { QString originFile(resourceDir.filePath(resourceFile)); QString destinationFile; - if (!resourceFile.endsWith(QLatin1String(".so"))) { + if (!resourceFile.endsWith(".so"_L1)) { destinationFile = assetsDir + resourceFile; } else { - if (!checkArchitecture(*options, originFile)) + if (isDeployment(options, Options::Unbundled) + || !checkArchitecture(*options, originFile)) { continue; + } destinationFile = libsDir + resourceFile; options->archExtraPlugins[options->currentArchitecture] += resourceFile; } @@ -1362,7 +1526,7 @@ bool updateFile(const QString &fileName, const QHash<QString, QString> &replacem forever { int index = contents.indexOf(it.key().toUtf8()); if (index >= 0) { - contents.replace(index, it.key().length(), it.value().toUtf8()); + contents.replace(index, it.key().size(), it.value().toUtf8()); hasReplacements = true; } else { break; @@ -1390,7 +1554,7 @@ bool updateLibsXml(Options *options) if (options->verbose) fprintf(stdout, " -- res/values/libs.xml\n"); - QString fileName = options->outputDirectory + QLatin1String("/res/values/libs.xml"); + QString fileName = options->outputDirectory + "/res/values/libs.xml"_L1; if (!QFile::exists(fileName)) { fprintf(stderr, "Cannot find %s in prepared packaged. This file is required.\n", qPrintable(fileName)); return false; @@ -1401,23 +1565,40 @@ bool updateLibsXml(Options *options) QString extraLibs; for (auto it = options->architectures.constBegin(); it != options->architectures.constEnd(); ++it) { - QString libsPath = QLatin1String("libs/") + it.key() + QLatin1Char('/'); + if (!it->enabled) + continue; - qtLibs += QLatin1String(" <item>%1;%2</item>\n").arg(it.key(), options->stdCppName); + qtLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), options->stdCppName); for (const Options::BundledFile &bundledFile : options->bundledFiles[it.key()]) { - if (bundledFile.second.startsWith(QLatin1String("lib/"))) { + if (bundledFile.second.startsWith("lib/lib"_L1)) { + if (!bundledFile.second.endsWith(".so"_L1)) { + fprintf(stderr, + "The bundled library %s doesn't end with .so. Android only supports " + "versionless libraries ending with the .so suffix.\n", + qPrintable(bundledFile.second)); + return false; + } QString s = bundledFile.second.mid(sizeof("lib/lib") - 1); s.chop(sizeof(".so") - 1); - qtLibs += QLatin1String(" <item>%1;%2</item>\n").arg(it.key(), s); + qtLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), s); } } if (!options->archExtraLibs[it.key()].isEmpty()) { for (const QString &extraLib : options->archExtraLibs[it.key()]) { QFileInfo extraLibInfo(extraLib); - QString name = extraLibInfo.fileName().mid(sizeof("lib") - 1); - name.chop(sizeof(".so") - 1); - extraLibs += QLatin1String(" <item>%1;%2</item>\n").arg(it.key(), name); + if (extraLibInfo.fileName().startsWith("lib"_L1)) { + if (!extraLibInfo.fileName().endsWith(".so"_L1)) { + fprintf(stderr, + "The library %s doesn't end with .so. Android only supports " + "versionless libraries ending with the .so suffix.\n", + qPrintable(extraLibInfo.fileName())); + return false; + } + QString name = extraLibInfo.fileName().mid(sizeof("lib") - 1); + name.chop(sizeof(".so") - 1); + extraLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), name); + } } } @@ -1427,25 +1608,21 @@ bool updateLibsXml(Options *options) if (localLibs.isEmpty()) { QString plugin; for (const QtDependency &qtDependency : options->qtDependencies[it.key()]) { - if (qtDependency.relativePath.endsWith(QLatin1String("libqtforandroid.so")) - || qtDependency.relativePath.endsWith(QLatin1String("libqtforandroidGL.so"))) { - if (!plugin.isEmpty() && plugin != qtDependency.relativePath) { - fprintf(stderr, "Both platform plugins libqtforandroid.so and libqtforandroidGL.so included in package. Please include only one.\n"); - return false; - } - + if (qtDependency.relativePath.contains("libplugins_platforms_qtforandroid_"_L1)) plugin = qtDependency.relativePath; - } - if (qtDependency.relativePath.contains(QLatin1String("libQt5OpenGL")) - || qtDependency.relativePath.contains(QLatin1String("libQt5Quick"))) { + + if (qtDependency.relativePath.contains( + QString::asprintf("libQt%dOpenGL", QT_VERSION_MAJOR)) + || qtDependency.relativePath.contains( + QString::asprintf("libQt%dQuick", QT_VERSION_MAJOR))) { options->usesOpenGL |= true; - break; } } if (plugin.isEmpty()) { fflush(stdout); - fprintf(stderr, "No platform plugin, neither libqtforandroid.so or libqtforandroidGL.so, included in package. Please include one.\n"); + fprintf(stderr, "No platform plugin (libplugins_platforms_qtforandroid.so) included" + " in the deployment. Make sure the app links to Qt Gui library.\n"); fflush(stderr); return false; } @@ -1457,10 +1634,10 @@ bool updateLibsXml(Options *options) // remove all paths for (auto &lib : localLibs) { - if (lib.endsWith(QLatin1String(".so"))) - lib = lib.mid(lib.lastIndexOf(QLatin1Char('/')) + 1); + if (lib.endsWith(".so"_L1)) + lib = lib.mid(lib.lastIndexOf(u'/') + 1); } - allLocalLibs += QLatin1String(" <item>%1;%2</item>\n").arg(it.key(), localLibs.join(QLatin1Char(':'))); + allLocalLibs += " <item>%1;%2</item>\n"_L1.arg(it.key(), localLibs.join(u':')); } options->initClasses.removeDuplicates(); @@ -1469,14 +1646,15 @@ bool updateLibsXml(Options *options) replacements[QStringLiteral("<!-- %%INSERT_QT_LIBS%% -->")] += qtLibs.trimmed(); replacements[QStringLiteral("<!-- %%INSERT_LOCAL_LIBS%% -->")] = allLocalLibs.trimmed(); replacements[QStringLiteral("<!-- %%INSERT_EXTRA_LIBS%% -->")] = extraLibs.trimmed(); - const QString initClasses = options->initClasses.join(QLatin1Char(':')); + const QString initClasses = options->initClasses.join(u':'); replacements[QStringLiteral("<!-- %%INSERT_INIT_CLASSES%% -->")] = initClasses; - // Bundle and use libs from the apk because currently we don't have a way avoid - // duplicating them. - replacements[QStringLiteral("<!-- %%BUNDLE_LOCAL_QT_LIBS%% -->")] = QLatin1String("1"); - replacements[QStringLiteral("<!-- %%USE_LOCAL_QT_LIBS%% -->")] = QLatin1String("1"); - + // Set BUNDLE_LOCAL_QT_LIBS based on the deployment used + replacements[QStringLiteral("<!-- %%BUNDLE_LOCAL_QT_LIBS%% -->")] + = isDeployment(options, Options::Unbundled) ? "0"_L1 : "1"_L1; + replacements[QStringLiteral("<!-- %%USE_LOCAL_QT_LIBS%% -->")] = "1"_L1; + replacements[QStringLiteral("<!-- %%SYSTEM_LIBS_PREFIX%% -->")] = + isDeployment(options, Options::Unbundled) ? options->systemLibsPath : QStringLiteral(""); if (!updateFile(fileName, replacements)) return false; @@ -1492,7 +1670,7 @@ bool updateStringsXml(const Options &options) QHash<QString, QString> replacements; replacements[QStringLiteral("<!-- %%INSERT_APP_NAME%% -->")] = options.applicationBinary; - QString fileName = options.outputDirectory + QLatin1String("/res/values/strings.xml"); + QString fileName = options.outputDirectory + "/res/values/strings.xml"_L1; if (!QFile::exists(fileName)) { if (options.verbose) fprintf(stdout, " -- Create strings.xml since it's missing.\n"); @@ -1524,22 +1702,22 @@ bool updateAndroidManifest(Options &options) replacements[QStringLiteral("-- %%INSERT_APP_LIB_NAME%% --")] = options.applicationBinary; replacements[QStringLiteral("-- %%INSERT_VERSION_NAME%% --")] = options.versionName; replacements[QStringLiteral("-- %%INSERT_VERSION_CODE%% --")] = options.versionCode; - replacements[QStringLiteral("package=\"org.qtproject.example\"")] = QLatin1String("package=\"%1\"").arg(options.packageName); + replacements[QStringLiteral("package=\"org.qtproject.example\"")] = "package=\"%1\""_L1.arg(options.packageName); QString permissions; - for (const QString &permission : qAsConst(options.permissions)) - permissions += QLatin1String(" <uses-permission android:name=\"%1\" />\n").arg(permission); + for (const QString &permission : std::as_const(options.permissions)) + permissions += " <uses-permission android:name=\"%1\" />\n"_L1.arg(permission); replacements[QStringLiteral("<!-- %%INSERT_PERMISSIONS -->")] = permissions.trimmed(); QString features; - for (const QString &feature : qAsConst(options.features)) - features += QLatin1String(" <uses-feature android:name=\"%1\" android:required=\"false\" />\n").arg(feature); + for (const QString &feature : std::as_const(options.features)) + features += " <uses-feature android:name=\"%1\" android:required=\"false\" />\n"_L1.arg(feature); if (options.usesOpenGL) - features += QLatin1String(" <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />"); + features += " <uses-feature android:glEsVersion=\"0x00020000\" android:required=\"true\" />"_L1; replacements[QStringLiteral("<!-- %%INSERT_FEATURES -->")] = features.trimmed(); - QString androidManifestPath = options.outputDirectory + QLatin1String("/AndroidManifest.xml"); + QString androidManifestPath = options.outputDirectory + "/AndroidManifest.xml"_L1; if (!updateFile(androidManifestPath, replacements)) return false; @@ -1557,28 +1735,27 @@ bool updateAndroidManifest(Options &options) reader.readNext(); if (reader.isStartElement()) { - if (reader.name() == QLatin1String("manifest")) { - if (!reader.attributes().hasAttribute(QLatin1String("package"))) { + if (reader.name() == "manifest"_L1) { + if (!reader.attributes().hasAttribute("package"_L1)) { fprintf(stderr, "Invalid android manifest file: %s\n", qPrintable(androidManifestPath)); return false; } - options.packageName = reader.attributes().value(QLatin1String("package")).toString(); - } else if (reader.name() == QLatin1String("uses-sdk")) { - if (reader.attributes().hasAttribute(QLatin1String("android:minSdkVersion"))) - if (reader.attributes().value(QLatin1String("android:minSdkVersion")).toInt() < 23) { + options.packageName = reader.attributes().value("package"_L1).toString(); + } else if (reader.name() == "uses-sdk"_L1) { + if (reader.attributes().hasAttribute("android:minSdkVersion"_L1)) + if (reader.attributes().value("android:minSdkVersion"_L1).toInt() < 23) { fprintf(stderr, "Invalid minSdkVersion version, minSdkVersion must be >= 23\n"); return false; } - } else if ((reader.name() == QLatin1String("application") || - reader.name() == QLatin1String("activity")) && - reader.attributes().hasAttribute(QLatin1String("android:label")) && - reader.attributes().value(QLatin1String("android:label")) == QLatin1String("@string/app_name")) { + } else if ((reader.name() == "application"_L1 || + reader.name() == "activity"_L1) && + reader.attributes().hasAttribute("android:label"_L1) && + reader.attributes().value("android:label"_L1) == "@string/app_name"_L1) { checkOldAndroidLabelString = true; - } else if (reader.name() == QLatin1String("meta-data")) { - const auto name = reader.attributes().value(QLatin1String("android:name")); - const auto value = reader.attributes().value(QLatin1String("android:value")); - if (name == QLatin1String("android.app.lib_name") - && value.contains(QLatin1Char(' '))) { + } else if (reader.name() == "meta-data"_L1) { + const auto name = reader.attributes().value("android:name"_L1); + const auto value = reader.attributes().value("android:value"_L1); + if (name == "android.app.lib_name"_L1 && value.contains(u' ')) { fprintf(stderr, "The Activity's android.app.lib_name should not contain" " spaces.\n"); return false; @@ -1621,20 +1798,40 @@ static QString absoluteFilePath(const Options *options, const QString &relativeF // Use extraLibraryDirs as the extra library lookup folder if it is expected to find a file in // any $prefix/lib folder. // Library directories from a build tree(extraLibraryDirs) have the higher priority. - if (relativeFileName.startsWith(QLatin1String("lib/"))) { + if (relativeFileName.startsWith("lib/"_L1)) { for (const auto &dir : options->extraLibraryDirs) { - const QString path = dir + QLatin1Char('/') + relativeFileName.mid(sizeof("lib/") - 1); + const QString path = dir + u'/' + relativeFileName.mid(sizeof("lib/") - 1); if (QFile::exists(path)) return path; } } for (const auto &prefix : options->extraPrefixDirs) { - const QString path = prefix + QLatin1Char('/') + relativeFileName; + const QString path = prefix + u'/' + relativeFileName; if (QFile::exists(path)) return path; } - return options->qtInstallDirectory + QLatin1Char('/') + relativeFileName; + + if (relativeFileName.endsWith("-android-dependencies.xml"_L1)) { + for (const auto &dir : options->extraLibraryDirs) { + const QString path = dir + u'/' + relativeFileName; + if (QFile::exists(path)) + return path; + } + return options->qtInstallDirectory + u'/' + options->qtLibsDirectory + + u'/' + relativeFileName; + } + + if (relativeFileName.startsWith("jar/"_L1)) { + return options->qtInstallDirectory + u'/' + options->qtDataDirectory + + u'/' + relativeFileName; + } + + if (relativeFileName.startsWith("lib/"_L1)) { + return options->qtInstallDirectory + u'/' + options->qtLibsDirectory + + u'/' + relativeFileName.mid(sizeof("lib/") - 1); + } + return options->qtInstallDirectory + u'/' + relativeFileName; } QList<QtDependency> findFilesRecursively(const Options &options, const QFileInfo &info, const QString &rootPath) @@ -1656,19 +1853,63 @@ QList<QtDependency> findFilesRecursively(const Options &options, const QFileInfo return ret; } else { - return QList<QtDependency>() << QtDependency(info.absoluteFilePath().mid(rootPath.length()), info.absoluteFilePath()); + return QList<QtDependency>() << QtDependency(info.absoluteFilePath().mid(rootPath.size()), info.absoluteFilePath()); } } QList<QtDependency> findFilesRecursively(const Options &options, const QString &fileName) { + // We try to find the fileName in extraPrefixDirs first. The function behaves differently + // depending on what the fileName points to. If fileName is a file then we try to find the + // first occurrence in extraPrefixDirs and return this file. If fileName is directory function + // iterates over it and looks for deployment artifacts in each 'extraPrefixDirs' entry. + // Also we assume that if the fileName is recognized as a directory once it will be directory + // for every 'extraPrefixDirs' entry. + QList<QtDependency> deps; for (const auto &prefix : options.extraPrefixDirs) { - QFileInfo info(prefix + QLatin1Char('/') + fileName); - if (info.exists()) - return findFilesRecursively(options, info, prefix + QLatin1Char('/')); + QFileInfo info(prefix + u'/' + fileName); + if (info.exists()) { + if (info.isDir()) + deps.append(findFilesRecursively(options, info, prefix + u'/')); + else + return findFilesRecursively(options, info, prefix + u'/'); + } + } + + // Usually android deployment settings contain Qt install directory in extraPrefixDirs. + if (std::find(options.extraPrefixDirs.begin(), options.extraPrefixDirs.end(), + options.qtInstallDirectory) == options.extraPrefixDirs.end()) { + QFileInfo info(options.qtInstallDirectory + "/"_L1 + fileName); + QFileInfo rootPath(options.qtInstallDirectory + "/"_L1); + deps.append(findFilesRecursively(options, info, rootPath.absolutePath())); + } + return deps; +} + +void readDependenciesFromFiles(Options *options, const QList<QtDependency> &files, + QSet<QString> &usedDependencies, + QSet<QString> &remainingDependencies) +{ + for (const QtDependency &fileName : files) { + if (usedDependencies.contains(fileName.absolutePath)) + continue; + + if (fileName.absolutePath.endsWith(".so"_L1)) { + if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies, + &remainingDependencies)) { + fprintf(stdout, "Skipping file dependency: %s\n", + qPrintable(fileName.relativePath)); + continue; + } + } + usedDependencies.insert(fileName.absolutePath); + + if (options->verbose) { + fprintf(stdout, "Appending file dependency: %s\n", qPrintable(fileName.relativePath)); + } + + options->qtDependencies[options->currentArchitecture].append(fileName); } - QFileInfo info(options.qtInstallDirectory + QLatin1Char('/') + fileName); - return findFilesRecursively(options, info, options.qtInstallDirectory + QLatin1Char('/')); } bool readAndroidDependencyXml(Options *options, @@ -1676,7 +1917,7 @@ bool readAndroidDependencyXml(Options *options, QSet<QString> *usedDependencies, QSet<QString> *remainingDependencies) { - QString androidDependencyName = absoluteFilePath(options, QLatin1String("/lib/%1-android-dependencies.xml").arg(moduleName)); + QString androidDependencyName = absoluteFilePath(options, "%1-android-dependencies.xml"_L1.arg(moduleName)); QFile androidDependencyFile(androidDependencyName); if (androidDependencyFile.exists()) { @@ -1693,35 +1934,27 @@ bool readAndroidDependencyXml(Options *options, reader.readNext(); if (reader.isStartElement()) { - if (reader.name() == QLatin1String("bundled")) { - if (!reader.attributes().hasAttribute(QLatin1String("file"))) { + if (reader.name() == "bundled"_L1) { + if (!reader.attributes().hasAttribute("file"_L1)) { fprintf(stderr, "Invalid android dependency file: %s\n", qPrintable(androidDependencyName)); return false; } - QString file = reader.attributes().value(QLatin1String("file")).toString(); + QString file = reader.attributes().value("file"_L1).toString(); - // Special case, since this is handled by qmlimportscanner instead - if (!options->rootPaths.empty() - && (file == QLatin1String("qml") || file == QLatin1String("qml/"))) + if (reader.attributes().hasAttribute("type"_L1) + && reader.attributes().value("type"_L1) == "plugin_dir"_L1 + && !options->androidDeployPlugins.isEmpty()) { continue; + } const QList<QtDependency> fileNames = findFilesRecursively(*options, file); - for (const QtDependency &fileName : fileNames) { - if (usedDependencies->contains(fileName.absolutePath)) - continue; - - usedDependencies->insert(fileName.absolutePath); - - if (options->verbose) - fprintf(stdout, "Appending dependency from xml: %s\n", qPrintable(fileName.relativePath)); - - options->qtDependencies[options->currentArchitecture].append(fileName); - } - } else if (reader.name() == QLatin1String("jar")) { - int bundling = reader.attributes().value(QLatin1String("bundling")).toInt(); - QString fileName = QDir::cleanPath(reader.attributes().value(QLatin1String("file")).toString()); - if (bundling == (options->deploymentMechanism == Options::Bundled)) { + readDependenciesFromFiles(options, fileNames, *usedDependencies, + *remainingDependencies); + } else if (reader.name() == "jar"_L1) { + int bundling = reader.attributes().value("bundling"_L1).toInt(); + QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString()); + if (bundling) { QtDependency dependency(fileName, absoluteFilePath(options, fileName)); if (!usedDependencies->contains(dependency.absolutePath)) { options->qtDependencies[options->currentArchitecture].append(dependency); @@ -1729,13 +1962,13 @@ bool readAndroidDependencyXml(Options *options, } } - if (reader.attributes().hasAttribute(QLatin1String("initClass"))) { - options->initClasses.append(reader.attributes().value(QLatin1String("initClass")).toString()); + if (reader.attributes().hasAttribute("initClass"_L1)) { + options->initClasses.append(reader.attributes().value("initClass"_L1).toString()); } - } else if (reader.name() == QLatin1String("lib")) { - QString fileName = QDir::cleanPath(reader.attributes().value(QLatin1String("file")).toString()); - if (reader.attributes().hasAttribute(QLatin1String("replaces"))) { - QString replaces = reader.attributes().value(QLatin1String("replaces")).toString(); + } else if (reader.name() == "lib"_L1) { + QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString()); + if (reader.attributes().hasAttribute("replaces"_L1)) { + QString replaces = reader.attributes().value("replaces"_L1).toString(); for (int i=0; i<options->localLibs.size(); ++i) { if (options->localLibs[options->currentArchitecture].at(i) == replaces) { options->localLibs[options->currentArchitecture][i] = fileName; @@ -1745,14 +1978,14 @@ bool readAndroidDependencyXml(Options *options, } else if (!fileName.isEmpty()) { options->localLibs[options->currentArchitecture].append(fileName); } - if (fileName.endsWith(QLatin1String(".so")) && checkArchitecture(*options, fileName)) { + if (fileName.endsWith(".so"_L1) && checkArchitecture(*options, fileName)) { remainingDependencies->insert(fileName); } - } else if (reader.name() == QLatin1String("permission")) { - QString name = reader.attributes().value(QLatin1String("name")).toString(); + } else if (reader.name() == "permission"_L1) { + QString name = reader.attributes().value("name"_L1).toString(); options->permissions.append(name); - } else if (reader.name() == QLatin1String("feature")) { - QString name = reader.attributes().value(QLatin1String("name")).toString(); + } else if (reader.name() == "feature"_L1) { + QString name = reader.attributes().value("name"_L1).toString(); options->features.append(name); } } @@ -1773,19 +2006,13 @@ bool readAndroidDependencyXml(Options *options, QStringList getQtLibsFromElf(const Options &options, const QString &fileName) { - QString readElf = QLatin1String("%1/toolchains/%2/prebuilt/%3/bin/llvm-readobj").arg(options.ndkPath, - options.toolchainPrefix, - options.ndkHost); -#if defined(Q_OS_WIN32) - readElf += QLatin1String(".exe"); -#endif - + QString readElf = llvmReadobjPath(options); if (!QFile::exists(readElf)) { fprintf(stderr, "Command does not exist: %s\n", qPrintable(readElf)); return QStringList(); } - readElf = QLatin1String("%1 -needed-libs %2").arg(shellQuote(readElf), shellQuote(fileName)); + readElf = "%1 --needed-libs %2"_L1.arg(shellQuote(readElf), shellQuote(fileName)); FILE *readElfCommand = openProcess(readElf); if (!readElfCommand) { @@ -1807,6 +2034,7 @@ QStringList getQtLibsFromElf(const Options &options, const QString &fileName) if (it == elfArchitectures.constEnd() || *it != options.currentArchitecture.toLatin1()) { if (options.verbose) fprintf(stdout, "Skipping \"%s\", architecture mismatch\n", qPrintable(fileName)); + pclose(readElfCommand); return {}; } } @@ -1816,7 +2044,7 @@ QStringList getQtLibsFromElf(const Options &options, const QString &fileName) if (!line.startsWith("lib")) continue; library = QString::fromLatin1(line); - QString libraryName = QLatin1String("lib/") + library; + QString libraryName = "lib/"_L1 + library; if (QFile::exists(absoluteFilePath(&options, libraryName))) ret += libraryName; } @@ -1860,7 +2088,7 @@ bool readDependenciesFromElf(Options *options, dependenciesToCheck.append(dependency); } - for (const QString &dependency : qAsConst(dependenciesToCheck)) { + for (const QString &dependency : std::as_const(dependenciesToCheck)) { QString qtBaseName = dependency.mid(sizeof("lib/lib") - 1); qtBaseName = qtBaseName.left(qtBaseName.size() - (sizeof(".so") - 1)); if (!readAndroidDependencyXml(options, qtBaseName, usedDependencies, remainingDependencies)) { @@ -1871,41 +2099,38 @@ bool readDependenciesFromElf(Options *options, return true; } -QString defaultLibexecDir() -{ -#ifdef Q_OS_WIN32 - return QStringLiteral("bin"); -#else - return QStringLiteral("libexec"); -#endif -} - -bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies); -bool checkQmlFileInRootPaths(const Options *options, const QString &absolutePath); - bool scanImports(Options *options, QSet<QString> *usedDependencies) { if (options->verbose) fprintf(stdout, "Scanning for QML imports.\n"); QString qmlImportScanner; - if (!options->qmlImportScannerBinaryPath.isEmpty()) + if (!options->qmlImportScannerBinaryPath.isEmpty()) { qmlImportScanner = options->qmlImportScannerBinaryPath; - else - qmlImportScanner = options->qtInstallDirectory + QLatin1String("/bin/qmlimportscanner"); -#if defined(Q_OS_WIN32) - qmlImportScanner += QLatin1String(".exe"); -#endif + } else { + qmlImportScanner = execSuffixAppended(options->qtLibExecsDirectory + + "/qmlimportscanner"_L1); + } QStringList importPaths; - importPaths += shellQuote(options->qtInstallDirectory + QLatin1String("/qml")); + // In Conan's case, qtInstallDirectory will point only to qtbase installed files, which + // lacks a qml directory. We don't want to pass it as an import path if it doesn't exist + // because it will cause qmlimportscanner to fail. + // This also covers the case when only qtbase is installed in a regular Qt build. + const QString mainImportPath = options->qtInstallDirectory + u'/' + options->qtQmlDirectory; + if (QFile::exists(mainImportPath)) + importPaths += shellQuote(mainImportPath); + + // These are usually provided by CMake in the deployment json file from paths specified + // in CMAKE_FIND_ROOT_PATH. They might not have qml modules. for (const QString &prefix : options->extraPrefixDirs) - if (QDir().exists(prefix + QLatin1String("/qml"))) - importPaths += shellQuote(prefix + QLatin1String("/qml")); + if (QFile::exists(prefix + "/qml"_L1)) + importPaths += shellQuote(prefix + "/qml"_L1); - for (const QString &qmlImportPath : qAsConst(options->qmlImportPaths)) { - if (QDir().exists(qmlImportPath)) { + // These are provided by both CMake and qmake. + for (const QString &qmlImportPath : std::as_const(options->qmlImportPaths)) { + if (QFile::exists(qmlImportPath)) { importPaths += shellQuote(qmlImportPath); } else { fprintf(stderr, "Warning: QML import path %s does not exist.\n", @@ -1932,7 +2157,7 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies) if (!QFile::exists(qmlImportScanner)) { fprintf(stderr, "%s: qmlimportscanner not found at %s\n", - qmlImportExists ? QLatin1String("Error").data() : QLatin1String("Warning").data(), + qmlImportExists ? "Error"_L1.data() : "Warning"_L1.data(), qPrintable(qmlImportScanner)); return true; } @@ -1940,23 +2165,23 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies) for (auto rootPath : options->rootPaths) { rootPath = QFileInfo(rootPath).absoluteFilePath(); - if (!rootPath.endsWith(QLatin1Char('/'))) - rootPath += QLatin1Char('/'); + if (!rootPath.endsWith(u'/')) + rootPath += u'/'; // After checking for qml folder imports we can add rootPath if (!rootPath.isEmpty()) importPaths += shellQuote(rootPath); - qmlImportScanner += QLatin1String(" -rootPath %1").arg(shellQuote(rootPath)); + qmlImportScanner += " -rootPath %1"_L1.arg(shellQuote(rootPath)); } if (!options->qrcFiles.isEmpty()) { - qmlImportScanner += QLatin1String(" -qrcFiles"); + qmlImportScanner += " -qrcFiles"_L1; for (const QString &qrcFile : options->qrcFiles) - qmlImportScanner += QLatin1Char(' ') + shellQuote(qrcFile); + qmlImportScanner += u' ' + shellQuote(qrcFile); } - qmlImportScanner += QLatin1String(" -importPath %1").arg(importPaths.join(QLatin1Char(' '))); + qmlImportScanner += " -importPath %1"_L1.arg(importPaths.join(u' ')); if (options->verbose) { fprintf(stdout, "Running qmlimportscanner with the following command: %s\n", @@ -1977,6 +2202,7 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies) QJsonDocument jsonDocument = QJsonDocument::fromJson(output); if (jsonDocument.isNull()) { fprintf(stderr, "Invalid json output from qmlimportscanner.\n"); + pclose(qmlImportScannerCommand); return false; } @@ -1985,17 +2211,18 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies) QJsonValue value = jsonArray.at(i); if (!value.isObject()) { fprintf(stderr, "Invalid format of qmlimportscanner output.\n"); + pclose(qmlImportScannerCommand); return false; } QJsonObject object = value.toObject(); - QString path = object.value(QLatin1String("path")).toString(); + QString path = object.value("path"_L1).toString(); if (path.isEmpty()) { fprintf(stderr, "Warning: QML import could not be resolved in any of the import paths: %s\n", - qPrintable(object.value(QLatin1String("name")).toString())); + qPrintable(object.value("name"_L1).toString())); } else { if (options->verbose) - fprintf(stdout, " -- Adding '%s' as QML dependency\n", path.toLocal8Bit().constData()); + fprintf(stdout, " -- Adding '%s' as QML dependency\n", qPrintable(path)); QFileInfo info(path); @@ -2007,24 +2234,22 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies) } QString absolutePath = info.absolutePath(); - if (!absolutePath.endsWith(QLatin1Char('/'))) - absolutePath += QLatin1Char('/'); + if (!absolutePath.endsWith(u'/')) + absolutePath += u'/'; - if (checkQmlFileInRootPaths(options, absolutePath)) { + const QUrl url(object.value("name"_L1).toString()); + + const QString moduleUrlPath = u"/"_s + url.toString().replace(u'.', u'/'); + if (checkCanImportFromRootPaths(options, info.absolutePath(), moduleUrlPath)) { if (options->verbose) fprintf(stdout, " -- Skipping because path is in QML root path.\n"); continue; } QString importPathOfThisImport; - for (const QString &importPath : qAsConst(importPaths)) { -#if defined(Q_OS_WIN32) - Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; -#else - Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; -#endif + for (const QString &importPath : std::as_const(importPaths)) { QString cleanImportPath = QDir::cleanPath(importPath); - if (info.absoluteFilePath().startsWith(cleanImportPath, caseSensitivity)) { + if (QFile::exists(cleanImportPath + moduleUrlPath)) { importPathOfThisImport = importPath; break; } @@ -2032,43 +2257,83 @@ bool scanImports(Options *options, QSet<QString> *usedDependencies) if (importPathOfThisImport.isEmpty()) { fprintf(stderr, "Import found outside of import paths: %s.\n", qPrintable(info.absoluteFilePath())); + pclose(qmlImportScannerCommand); return false; } - QDir dir(importPathOfThisImport); - importPathOfThisImport = dir.absolutePath() + QLatin1Char('/'); - - const QList<QtDependency> fileNames = findFilesRecursively(*options, info, importPathOfThisImport); - for (QtDependency fileName : fileNames) { - if (usedDependencies->contains(fileName.absolutePath)) - continue; - - usedDependencies->insert(fileName.absolutePath); - + importPathOfThisImport = QDir(importPathOfThisImport).absolutePath() + u'/'; + QList<QtDependency> qmlImportsDependencies; + auto collectQmlDependency = [&usedDependencies, &qmlImportsDependencies, + &importPathOfThisImport](const QString &filePath) { + if (!usedDependencies->contains(filePath)) { + usedDependencies->insert(filePath); + qmlImportsDependencies += QtDependency( + "qml/"_L1 + filePath.mid(importPathOfThisImport.size()), + filePath); + } + }; + + QString plugin = object.value("plugin"_L1).toString(); + bool pluginIsOptional = object.value("pluginIsOptional"_L1).toBool(); + QFileInfo pluginFileInfo = QFileInfo( + path + u'/' + "lib"_L1 + plugin + u'_' + + options->currentArchitecture + ".so"_L1); + QString pluginFilePath = pluginFileInfo.absoluteFilePath(); + QSet<QString> remainingDependencies; + if (pluginFileInfo.exists() && checkArchitecture(*options, pluginFilePath) + && readDependenciesFromElf(options, pluginFilePath, usedDependencies, + &remainingDependencies)) { + collectQmlDependency(pluginFilePath); + } else if (!pluginIsOptional) { if (options->verbose) - fprintf(stdout, " -- Appending dependency found by qmlimportscanner: %s\n", qPrintable(fileName.absolutePath)); + fprintf(stdout, " -- Skipping because the required plugin is missing.\n"); + continue; + } - // Put all imports in default import path in assets - fileName.relativePath.prepend(QLatin1String("qml/")); - options->qtDependencies[options->currentArchitecture].append(fileName); + QFileInfo qmldirFileInfo = QFileInfo(path + u'/' + "qmldir"_L1); + if (qmldirFileInfo.exists()) { + collectQmlDependency(qmldirFileInfo.absoluteFilePath()); + } - if (fileName.absolutePath.endsWith(QLatin1String(".so")) && checkArchitecture(*options, fileName.absolutePath)) { - QSet<QString> remainingDependencies; - if (!readDependenciesFromElf(options, fileName.absolutePath, usedDependencies, &remainingDependencies)) - return false; + QString prefer = object.value("prefer"_L1).toString(); + // If the preferred location of Qml files points to the Qt resources, this means + // that all Qml files has been embedded into plugin and we should not copy them to the + // android rcc bundle + if (!prefer.startsWith(":/"_L1)) { + QVariantList qmlFiles = + object.value("components"_L1).toArray().toVariantList(); + qmlFiles.append(object.value("scripts"_L1).toArray().toVariantList()); + bool qmlFilesMissing = false; + for (const auto &qmlFileEntry : qmlFiles) { + QFileInfo fileInfo(qmlFileEntry.toString()); + if (!fileInfo.exists()) { + qmlFilesMissing = true; + break; + } + collectQmlDependency(fileInfo.absoluteFilePath()); + } + if (qmlFilesMissing) { + if (options->verbose) + fprintf(stdout, + " -- Skipping because the required qml files are missing.\n"); + continue; } } + + options->qtDependencies[options->currentArchitecture].append(qmlImportsDependencies); } } + pclose(qmlImportScannerCommand); return true; } -bool checkQmlFileInRootPaths(const Options *options, const QString &absolutePath) +bool checkCanImportFromRootPaths(const Options *options, const QString &absolutePath, + const QString &moduleUrlPath) { for (auto rootPath : options->rootPaths) { - if (absolutePath.startsWith(rootPath)) + if ((rootPath + moduleUrlPath) == absolutePath) return true; } return false; @@ -2097,8 +2362,8 @@ bool runCommand(const Options &options, const QString &command) bool createRcc(const Options &options) { - auto assetsDir = QLatin1String("%1/assets").arg(options.outputDirectory); - if (!QDir{QLatin1String("%1/android_rcc_bundle").arg(assetsDir)}.exists()) { + auto assetsDir = "%1/assets"_L1.arg(options.outputDirectory); + if (!QDir{"%1/android_rcc_bundle"_L1.arg(assetsDir)}.exists()) { fprintf(stdout, "Skipping createRCC\n"); return true; } @@ -2111,43 +2376,40 @@ bool createRcc(const Options &options) if (!options.rccBinaryPath.isEmpty()) { rcc = options.rccBinaryPath; } else { - rcc = options.qtInstallDirectory + QLatin1Char('/') + defaultLibexecDir() - + QLatin1String("/rcc"); + rcc = execSuffixAppended(options.qtLibExecsDirectory + "/rcc"_L1); } -#if defined(Q_OS_WIN32) - rcc += QLatin1String(".exe"); -#endif - if (!QFile::exists(rcc)) { fprintf(stderr, "rcc not found: %s\n", qPrintable(rcc)); return false; } auto currentDir = QDir::currentPath(); - if (!QDir::setCurrent(QLatin1String("%1/android_rcc_bundle").arg(assetsDir))) { - fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable(QLatin1String("%1/android_rcc_bundle").arg(assetsDir))); + if (!QDir::setCurrent("%1/android_rcc_bundle"_L1.arg(assetsDir))) { + fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable("%1/android_rcc_bundle"_L1.arg(assetsDir))); return false; } - bool res = runCommand(options, QLatin1String("%1 --project -o %2").arg(rcc, shellQuote(QLatin1String("%1/android_rcc_bundle.qrc").arg(assetsDir)))); + bool res = runCommand(options, "%1 --project -o %2"_L1.arg(rcc, shellQuote("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir)))); if (!res) return false; - QLatin1String noZstd; + QLatin1StringView noZstd; if (!options.isZstdCompressionEnabled) - noZstd = QLatin1String("--no-zstd"); + noZstd = "--no-zstd"_L1; - QFile::rename(QLatin1String("%1/android_rcc_bundle.qrc").arg(assetsDir), QLatin1String("%1/android_rcc_bundle/android_rcc_bundle.qrc").arg(assetsDir)); + QFile::rename("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir), "%1/android_rcc_bundle/android_rcc_bundle.qrc"_L1.arg(assetsDir)); - res = runCommand(options, QLatin1String("%1 %2 %3 --binary -o %4 android_rcc_bundle.qrc").arg(rcc, shellQuote(QLatin1String("--root=/android_rcc_bundle/")), - noZstd, - shellQuote(QLatin1String("%1/android_rcc_bundle.rcc").arg(assetsDir)))); + res = runCommand(options, "%1 %2 %3 --binary -o %4 android_rcc_bundle.qrc"_L1.arg(rcc, shellQuote("--root=/android_rcc_bundle/"_L1), + noZstd, + shellQuote("%1/android_rcc_bundle.rcc"_L1.arg(assetsDir)))); if (!QDir::setCurrent(currentDir)) { fprintf(stderr, "Cannot set current dir to: %s\n", qPrintable(currentDir)); return false; } - QFile::remove(QLatin1String("%1/android_rcc_bundle.qrc").arg(assetsDir)); - QDir{QLatin1String("%1/android_rcc_bundle").arg(assetsDir)}.removeRecursively(); + if (!options.noRccBundleCleanup) { + QFile::remove("%1/android_rcc_bundle.qrc"_L1.arg(assetsDir)); + QDir{"%1/android_rcc_bundle"_L1.arg(assetsDir)}.removeRecursively(); + } return res; } @@ -2167,9 +2429,17 @@ bool readDependencies(Options *options) QSet<QString> remainingDependencies; // Add dependencies of application binary first - if (!readDependenciesFromElf(options, QLatin1String("%1/libs/%2/lib%3_%2.so").arg(options->outputDirectory, options->currentArchitecture, options->applicationBinary), &usedDependencies, &remainingDependencies)) + if (!readDependenciesFromElf(options, "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, options->currentArchitecture, options->applicationBinary), &usedDependencies, &remainingDependencies)) return false; + QList<QtDependency> pluginDeps; + for (const auto &pluginPath : options->androidDeployPlugins) { + pluginDeps.append(findFilesRecursively(*options, QFileInfo(pluginPath), + options->qtInstallDirectory + "/"_L1)); + } + + readDependenciesFromFiles(options, pluginDeps, usedDependencies, remainingDependencies); + while (!remainingDependencies.isEmpty()) { QSet<QString>::iterator start = remainingDependencies.begin(); QString fileName = absoluteFilePath(options, *start); @@ -2183,7 +2453,7 @@ bool readDependencies(Options *options) } else { fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n", qPrintable(fileName), - qPrintable(unmetDependencies.join(QLatin1Char(',')))); + qPrintable(unmetDependencies.join(u','))); } } @@ -2193,18 +2463,17 @@ bool readDependencies(Options *options) if (!goodToCopy(options, absoluteFilePath(options, *it), &unmetDependencies)) { fprintf(stdout, "Skipping %s due to unmet dependencies: %s\n", qPrintable(*it), - qPrintable(unmetDependencies.join(QLatin1Char(',')))); + qPrintable(unmetDependencies.join(u','))); it = options->localLibs[options->currentArchitecture].erase(it); } else { ++it; } } - if ((!options->rootPaths.empty() || options->qrcFiles.isEmpty()) && - !scanImports(options, &usedDependencies)) - return false; - - return true; + if (options->qmlSkipImportScanning + || (options->rootPaths.empty() && options->qrcFiles.isEmpty())) + return true; + return scanImports(options, &usedDependencies); } bool containsApplicationBinary(Options *options) @@ -2215,18 +2484,17 @@ bool containsApplicationBinary(Options *options) if (options->verbose) fprintf(stdout, "Checking if application binary is in package.\n"); - QFileInfo applicationBinary(options->applicationBinary); - QString applicationFileName = QLatin1String("lib%1_%2.so").arg(options->applicationBinary, - options->currentArchitecture); + QString applicationFileName = "lib%1_%2.so"_L1.arg(options->applicationBinary, + options->currentArchitecture); - QString applicationPath = QLatin1String("%1/libs/%2/%3").arg(options->outputDirectory, - options->currentArchitecture, - applicationFileName); + QString applicationPath = "%1/libs/%2/%3"_L1.arg(options->outputDirectory, + options->currentArchitecture, + applicationFileName); if (!QFile::exists(applicationPath)) { #if defined(Q_OS_WIN32) - QLatin1String makeTool("mingw32-make"); // Only Mingw host builds supported on Windows currently + const auto makeTool = "mingw32-make"_L1; // Only Mingw host builds supported on Windows currently #else - QLatin1String makeTool("make"); + const auto makeTool = "make"_L1; #endif fprintf(stderr, "Application binary is not in output directory: %s. Please run '%s install INSTALL_ROOT=%s' first.\n", qPrintable(applicationFileName), @@ -2239,20 +2507,16 @@ bool containsApplicationBinary(Options *options) FILE *runAdb(const Options &options, const QString &arguments) { - QString adb = options.sdkPath + QLatin1String("/platform-tools/adb"); -#if defined(Q_OS_WIN32) - adb += QLatin1String(".exe"); -#endif - + QString adb = execSuffixAppended(options.sdkPath + "/platform-tools/adb"_L1); if (!QFile::exists(adb)) { fprintf(stderr, "Cannot find adb tool: %s\n", qPrintable(adb)); return 0; } QString installOption; if (!options.installLocation.isEmpty()) - installOption = QLatin1String(" -s ") + shellQuote(options.installLocation); + installOption = " -s "_L1 + shellQuote(options.installLocation); - adb = QLatin1String("%1%2 %3").arg(shellQuote(adb), installOption, arguments); + adb = "%1%2 %3"_L1.arg(shellQuote(adb), installOption, arguments); if (options.verbose) fprintf(stdout, "Running command \"%s\"\n", adb.toLocal8Bit().constData()); @@ -2268,7 +2532,7 @@ FILE *runAdb(const Options &options, const QString &arguments) bool goodToCopy(const Options *options, const QString &file, QStringList *unmetDependencies) { - if (!file.endsWith(QLatin1String(".so"))) + if (!file.endsWith(".so"_L1)) return true; if (!checkArchitecture(*options, file)) @@ -2291,7 +2555,11 @@ bool copyQtFiles(Options *options) if (options->verbose) { switch (options->deploymentMechanism) { case Options::Bundled: - fprintf(stdout, "Copying %zd dependencies from Qt into package.\n", size_t(options->qtDependencies.size())); + fprintf(stdout, "Copying %zd dependencies from Qt into package.\n", size_t(options->qtDependencies[options->currentArchitecture].size())); + break; + case Options::Unbundled: + fprintf(stdout, "Copying dependencies from Qt into the package build folder," + "skipping native libraries.\n"); break; }; } @@ -2300,23 +2568,19 @@ bool copyQtFiles(Options *options) return true; - QString libsDirectory = QLatin1String("libs/"); + QString libsDirectory = "libs/"_L1; // Copy other Qt dependencies - auto assetsDestinationDirectory = QLatin1String("assets/android_rcc_bundle/"); - for (const QtDependency &qtDependency : qAsConst(options->qtDependencies[options->currentArchitecture])) { + auto assetsDestinationDirectory = "assets/android_rcc_bundle/"_L1; + for (const QtDependency &qtDependency : std::as_const(options->qtDependencies[options->currentArchitecture])) { QString sourceFileName = qtDependency.absolutePath; QString destinationFileName; - - if (qtDependency.relativePath.endsWith(QLatin1String(".so"))) { - QString garbledFileName; - if (QDir::fromNativeSeparators(qtDependency.relativePath).startsWith(QLatin1String("lib/"))) { - garbledFileName = qtDependency.relativePath.mid(sizeof("lib/") - 1); - } else { - garbledFileName = qtDependency.relativePath.mid(qtDependency.relativePath.lastIndexOf(QLatin1Char('/')) + 1); - } - destinationFileName = libsDirectory + options->currentArchitecture + QLatin1Char('/') + garbledFileName; - } else if (QDir::fromNativeSeparators(qtDependency.relativePath).startsWith(QLatin1String("jar/"))) { + bool isSharedLibrary = qtDependency.relativePath.endsWith(".so"_L1); + if (isSharedLibrary) { + QString garbledFileName = qtDependency.relativePath.mid( + qtDependency.relativePath.lastIndexOf(u'/') + 1); + destinationFileName = libsDirectory + options->currentArchitecture + u'/' + garbledFileName; + } else if (QDir::fromNativeSeparators(qtDependency.relativePath).startsWith("jar/"_L1)) { destinationFileName = libsDirectory + qtDependency.relativePath.mid(sizeof("jar/") - 1); } else { destinationFileName = assetsDestinationDirectory + qtDependency.relativePath; @@ -2337,19 +2601,18 @@ bool copyQtFiles(Options *options) } else { fprintf(stdout, " -- Skipping %s. It has unmet dependencies: %s.\n", qPrintable(sourceFileName), - qPrintable(unmetDependencies.join(QLatin1Char(',')))); + qPrintable(unmetDependencies.join(u','))); } continue; } - if (options->deploymentMechanism == Options::Bundled + if ((isDeployment(options, Options::Bundled) || !isSharedLibrary) && !copyFileIfNewer(sourceFileName, - options->outputDirectory + QLatin1Char('/') + destinationFileName, + options->outputDirectory + u'/' + destinationFileName, *options)) { return false; } - - options->bundledFiles[options->currentArchitecture] += qMakePair(destinationFileName, qtDependency.relativePath); + options->bundledFiles[options->currentArchitecture] += std::make_pair(destinationFileName, qtDependency.relativePath); } return true; @@ -2359,7 +2622,7 @@ QStringList getLibraryProjectsInOutputFolder(const Options &options) { QStringList ret; - QFile file(options.outputDirectory + QLatin1String("/project.properties")); + QFile file(options.outputDirectory + "/project.properties"_L1); if (file.open(QIODevice::ReadOnly)) { while (!file.atEnd()) { QByteArray line = file.readLine().trimmed(); @@ -2368,7 +2631,7 @@ QStringList getLibraryProjectsInOutputFolder(const Options &options) if (equalSignIndex >= 0) { QString path = QString::fromLocal8Bit(line.mid(equalSignIndex + 1)); - QFileInfo info(options.outputDirectory + QLatin1Char('/') + path); + QFileInfo info(options.outputDirectory + u'/' + path); if (QDir::isRelativePath(path) && info.exists() && info.isDir() @@ -2383,63 +2646,6 @@ QStringList getLibraryProjectsInOutputFolder(const Options &options) return ret; } -bool createAndroidProject(const Options &options) -{ - if (options.verbose) - fprintf(stdout, "Running Android tool to create package definition.\n"); - - QString androidToolExecutable = options.sdkPath + QLatin1String("/tools/android"); -#if defined(Q_OS_WIN32) - androidToolExecutable += QLatin1String(".bat"); -#endif - - if (!QFile::exists(androidToolExecutable)) { - fprintf(stderr, "Cannot find Android tool: %s\n", qPrintable(androidToolExecutable)); - return false; - } - - QString androidTool = QLatin1String("%1 update project --path %2 --target %3 --name QtApp") - .arg(shellQuote(androidToolExecutable)) - .arg(shellQuote(options.outputDirectory)) - .arg(shellQuote(options.androidPlatform)); - - if (options.verbose) - fprintf(stdout, " -- Command: %s\n", qPrintable(androidTool)); - - FILE *androidToolCommand = openProcess(androidTool); - if (androidToolCommand == 0) { - fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool)); - return false; - } - - pclose(androidToolCommand); - - // If the project has subprojects inside the current folder, we need to also run android update on these. - const QStringList libraryProjects = getLibraryProjectsInOutputFolder(options); - for (const QString &libraryProject : libraryProjects) { - if (options.verbose) - fprintf(stdout, "Updating subproject %s\n", qPrintable(libraryProject)); - - androidTool = QLatin1String("%1 update lib-project --path %2 --target %3") - .arg(shellQuote(androidToolExecutable)) - .arg(shellQuote(libraryProject)) - .arg(shellQuote(options.androidPlatform)); - - if (options.verbose) - fprintf(stdout, " -- Command: %s\n", qPrintable(androidTool)); - - FILE *androidToolCommand = popen(androidTool.toLocal8Bit().constData(), QT_POPEN_READ); - if (androidToolCommand == 0) { - fprintf(stderr, "Cannot run command '%s'\n", qPrintable(androidTool)); - return false; - } - - pclose(androidToolCommand); - } - - return true; -} - QString findInPath(const QString &fileName) { const QString path = QString::fromLocal8Bit(qgetenv("PATH")); @@ -2451,9 +2657,9 @@ QString findInPath(const QString &fileName) const QStringList paths = path.split(separator); for (const QString &path : paths) { - QFileInfo fileInfo(path + QLatin1Char('/') + fileName); + QFileInfo fileInfo(path + u'/' + fileName); if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable()) - return path + QLatin1Char('/') + fileName; + return path + u'/' + fileName; } return QString(); @@ -2483,15 +2689,16 @@ static GradleProperties readGradleProperties(const QString &path) static bool mergeGradleProperties(const QString &path, GradleProperties properties) { - QFile::remove(path + QLatin1Char('~')); - QFile::rename(path, path + QLatin1Char('~')); + const QString oldPathStr = path + u'~'; + QFile::remove(oldPathStr); + QFile::rename(path, oldPathStr); QFile file(path); if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) { fprintf(stderr, "Can't open file: %s for writing\n", qPrintable(file.fileName())); return false; } - QFile oldFile(path + QLatin1Char('~')); + QFile oldFile(oldPathStr); if (oldFile.open(QIODevice::ReadOnly)) { while (!oldFile.atEnd()) { QByteArray line(oldFile.readLine()); @@ -2504,9 +2711,10 @@ static bool mergeGradleProperties(const QString &path, GradleProperties properti continue; } } - file.write(line); + file.write(line.trimmed() + '\n'); } oldFile.close(); + QFile::remove(oldPathStr); } for (GradleProperties::const_iterator it = properties.begin(); it != properties.end(); ++it) @@ -2520,11 +2728,11 @@ static bool mergeGradleProperties(const QString &path, GradleProperties properti void checkAndWarnGradleLongPaths(const QString &outputDirectory) { QStringList longFileNames; - QDirIterator it(outputDirectory, QStringList(QStringLiteral("*.java")), QDir::Files, - QDirIterator::Subdirectories); - while (it.hasNext()) { - if (it.next().size() >= MAX_PATH) - longFileNames.append(it.next()); + using F = QDirListing::IteratorFlag; + for (const auto &dirEntry : QDirListing(outputDirectory, QStringList(u"*.java"_s), + QDir::Files, F::Recursive)) { + if (dirEntry.size() >= MAX_PATH) + longFileNames.append(dirEntry.filePath()); } if (!longFileNames.isEmpty()) { @@ -2532,38 +2740,110 @@ void checkAndWarnGradleLongPaths(const QString &outputDirectory) "The maximum path length that can be processed by Gradle on Windows is %d characters.\n" "Consider moving your project to reduce its path length.\n" "The following files have too long paths:\n%s.\n", - MAX_PATH, qPrintable(longFileNames.join(QLatin1Char('\n')))); + MAX_PATH, qPrintable(longFileNames.join(u'\n'))); } } #endif +struct GradleFlags { + bool setsLegacyPackaging = false; + bool usesIntegerCompileSdkVersion = false; +}; + +GradleFlags gradleBuildFlags(const QString &path) +{ + GradleFlags flags; + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) + return flags; + + auto isComment = [](const QByteArray &line) { + const auto trimmed = line.trimmed(); + return trimmed.startsWith("//") || trimmed.startsWith('*') || trimmed.startsWith("/*"); + }; + + const auto lines = file.readAll().split('\n'); + for (const auto &line : lines) { + if (isComment(line)) + continue; + if (line.contains("useLegacyPackaging")) { + flags.setsLegacyPackaging = true; + } else if (line.contains("compileSdkVersion androidCompileSdkVersion.toInteger()")) { + flags.usesIntegerCompileSdkVersion = true; + } + } + + return flags; +} + bool buildAndroidProject(const Options &options) { GradleProperties localProperties; localProperties["sdk.dir"] = QDir::fromNativeSeparators(options.sdkPath).toUtf8(); - localProperties["ndk.dir"] = QDir::fromNativeSeparators(options.ndkPath).toUtf8(); - - if (!mergeGradleProperties(options.outputDirectory + QLatin1String("local.properties"), localProperties)) + const QString localPropertiesPath = options.outputDirectory + "local.properties"_L1; + if (!mergeGradleProperties(localPropertiesPath, localProperties)) return false; - QString gradlePropertiesPath = options.outputDirectory + QLatin1String("gradle.properties"); + const QString gradlePropertiesPath = options.outputDirectory + "gradle.properties"_L1; GradleProperties gradleProperties = readGradleProperties(gradlePropertiesPath); - gradleProperties["android.bundle.enableUncompressedNativeLibs"] = "false"; + + const QString gradleBuildFilePath = options.outputDirectory + "build.gradle"_L1; + GradleFlags gradleFlags = gradleBuildFlags(gradleBuildFilePath); + if (!gradleFlags.setsLegacyPackaging) + gradleProperties["android.bundle.enableUncompressedNativeLibs"] = "false"; + gradleProperties["buildDir"] = "build"; - gradleProperties["qt5AndroidDir"] = (options.qtInstallDirectory + QLatin1String("/src/android/java")).toUtf8(); - gradleProperties["androidCompileSdkVersion"] = options.androidPlatform.split(QLatin1Char('-')).last().toLocal8Bit(); + gradleProperties["qtAndroidDir"] = + (options.qtInstallDirectory + u'/' + options.qtDataDirectory + + "/src/android/java"_L1) + .toUtf8(); + // The following property "qt5AndroidDir" is only for compatibility. + // Projects using a custom build.gradle file may use this variable. + // ### Qt7: Remove the following line + gradleProperties["qt5AndroidDir"] = + (options.qtInstallDirectory + u'/' + options.qtDataDirectory + + "/src/android/java"_L1) + .toUtf8(); + + QByteArray sdkPlatformVersion; + // Provide the integer version only if build.gradle explicitly converts to Integer, + // to avoid regression to existing projects that build for sdk platform of form android-xx. + if (gradleFlags.usesIntegerCompileSdkVersion) { + const QByteArray tmp = options.androidPlatform.split(u'-').last().toLocal8Bit(); + bool ok; + tmp.toInt(&ok); + if (ok) { + sdkPlatformVersion = tmp; + } else { + fprintf(stderr, "Warning: Gradle expects SDK platform version to be an integer, " + "but the set version is not convertible to an integer."); + } + } + + if (sdkPlatformVersion.isEmpty()) + sdkPlatformVersion = options.androidPlatform.toLocal8Bit(); + + gradleProperties["androidCompileSdkVersion"] = sdkPlatformVersion; gradleProperties["qtMinSdkVersion"] = options.minSdkVersion; gradleProperties["qtTargetSdkVersion"] = options.targetSdkVersion; + gradleProperties["androidNdkVersion"] = options.ndkVersion.toUtf8(); if (gradleProperties["androidBuildToolsVersion"].isEmpty()) gradleProperties["androidBuildToolsVersion"] = options.sdkBuildToolsVersion.toLocal8Bit(); - + QString abiList; + for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) { + if (!it->enabled) + continue; + if (abiList.size()) + abiList.append(u","); + abiList.append(it.key()); + } + gradleProperties["qtTargetAbiList"] = abiList.toLocal8Bit();// armeabi-v7a or arm64-v8a or ... if (!mergeGradleProperties(gradlePropertiesPath, gradleProperties)) return false; -#if defined(Q_OS_WIN32) - QString gradlePath(options.outputDirectory + QLatin1String("gradlew.bat")); -#else - QString gradlePath(options.outputDirectory + QLatin1String("gradlew")); + QString gradlePath = batSuffixAppended(options.outputDirectory + "gradlew"_L1); +#ifndef Q_OS_WIN32 { QFile f(gradlePath); if (!f.setPermissions(f.permissions() | QFileDevice::ExeUser)) @@ -2577,12 +2857,12 @@ bool buildAndroidProject(const Options &options) return false; } - QString commandLine = QLatin1String("%1 %2").arg(shellQuote(gradlePath), options.releasePackage ? QLatin1String(" assembleRelease") : QLatin1String(" assembleDebug")); + QString commandLine = "%1 %2"_L1.arg(shellQuote(gradlePath), options.releasePackage ? " assembleRelease"_L1 : " assembleDebug"_L1); if (options.buildAAB) - commandLine += QLatin1String(" bundle"); + commandLine += " bundle"_L1; if (options.verbose) - commandLine += QLatin1String(" --info"); + commandLine += " --info"_L1; FILE *gradleCommand = openProcess(commandLine); if (gradleCommand == 0) { @@ -2622,7 +2902,7 @@ bool uninstallApk(const Options &options) fprintf(stdout, "Uninstalling old Android package %s if present.\n", qPrintable(options.packageName)); - FILE *adbCommand = runAdb(options, QLatin1String(" uninstall ") + shellQuote(options.packageName)); + FILE *adbCommand = runAdb(options, " uninstall "_L1 + shellQuote(options.packageName)); if (adbCommand == 0) return false; @@ -2653,32 +2933,32 @@ enum PackageType { QString packagePath(const Options &options, PackageType pt) { QString path(options.outputDirectory); - path += QLatin1String("/build/outputs/%1/").arg(pt >= UnsignedAPK ? QStringLiteral("apk") : QStringLiteral("bundle")); - QString buildType(options.releasePackage ? QLatin1String("release/") : QLatin1String("debug/")); + path += "/build/outputs/%1/"_L1.arg(pt >= UnsignedAPK ? QStringLiteral("apk") : QStringLiteral("bundle")); + QString buildType(options.releasePackage ? "release/"_L1 : "debug/"_L1); if (QDir(path + buildType).exists()) path += buildType; - path += QDir(options.outputDirectory).dirName() + QLatin1Char('-'); + path += QDir(options.outputDirectory).dirName() + u'-'; if (options.releasePackage) { - path += QLatin1String("release-"); + path += "release-"_L1; if (pt >= UnsignedAPK) { if (pt == UnsignedAPK) - path += QLatin1String("un"); - path += QLatin1String("signed.apk"); + path += "un"_L1; + path += "signed.apk"_L1; } else { path.chop(1); - path += QLatin1String(".aab"); + path += ".aab"_L1; } } else { - path += QLatin1String("debug"); + path += "debug"_L1; if (pt >= UnsignedAPK) { if (pt == SignedAPK) - path += QLatin1String("-signed"); - path += QLatin1String(".apk"); + path += "-signed"_L1; + path += ".apk"_L1; } else { - path += QLatin1String(".aab"); + path += ".aab"_L1; } } - return shellQuote(path); + return path; } bool installApk(const Options &options) @@ -2691,10 +2971,9 @@ bool installApk(const Options &options) if (options.verbose) fprintf(stdout, "Installing Android package to device.\n"); - FILE *adbCommand = runAdb(options, - QLatin1String(" install -r ") + FILE *adbCommand = runAdb(options, " install -r "_L1 + packagePath(options, options.keyStore.isEmpty() ? UnsignedAPK - : SignedAPK)); + : SignedAPK)); if (adbCommand == 0) return false; @@ -2726,10 +3005,14 @@ bool copyPackage(const Options &options) bool copyStdCpp(Options *options) { + if (isDeployment(options, Options::Unbundled)) + return true; if (options->verbose) fprintf(stdout, "Copying STL library\n"); - QString stdCppPath = QLatin1String("%1/%2/lib%3.so").arg(options->stdCppPath, options->architectures[options->currentArchitecture], options->stdCppName); + const QString triple = options->architectures[options->currentArchitecture].triple; + const QString stdCppPath = "%1/%2/lib%3.so"_L1.arg(options->stdCppPath, triple, + options->stdCppName); if (!QFile::exists(stdCppPath)) { fprintf(stderr, "STL library does not exist at %s\n", qPrintable(stdCppPath)); fflush(stdout); @@ -2737,13 +3020,29 @@ bool copyStdCpp(Options *options) return false; } - const QString destinationFile = QLatin1String("%1/libs/%2/lib%3.so").arg(options->outputDirectory, - options->currentArchitecture, - options->stdCppName); + const QString destinationFile = "%1/libs/%2/lib%3.so"_L1.arg(options->outputDirectory, + options->currentArchitecture, + options->stdCppName); return copyFileIfNewer(stdCppPath, destinationFile, *options); } -bool jarSignerSignPackage(const Options &options) +static QString zipalignPath(const Options &options, bool *ok) +{ + *ok = true; + QString zipAlignTool = execSuffixAppended(options.sdkPath + "/tools/zipalign"_L1); + if (!QFile::exists(zipAlignTool)) { + zipAlignTool = execSuffixAppended(options.sdkPath + "/build-tools/"_L1 + + options.sdkBuildToolsVersion + "/zipalign"_L1); + if (!QFile::exists(zipAlignTool)) { + fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool)); + *ok = false; + } + } + + return zipAlignTool; +} + +bool signAAB(const Options &options) { if (options.verbose) fprintf(stdout, "Signing Android package.\n"); @@ -2753,61 +3052,55 @@ bool jarSignerSignPackage(const Options &options) if (jdkPath.isEmpty()) jdkPath = QString::fromLocal8Bit(qgetenv("JAVA_HOME")); -#if defined(Q_OS_WIN32) - QString jarSignerTool = QLatin1String("jarsigner.exe"); -#else - QString jarSignerTool = QLatin1String("jarsigner"); -#endif - - if (jdkPath.isEmpty() || !QFile::exists(jdkPath + QLatin1String("/bin/") + jarSignerTool)) + QString jarSignerTool = execSuffixAppended("jarsigner"_L1); + if (jdkPath.isEmpty() || !QFile::exists(jdkPath + "/bin/"_L1 + jarSignerTool)) jarSignerTool = findInPath(jarSignerTool); else - jarSignerTool = jdkPath + QLatin1String("/bin/") + jarSignerTool; + jarSignerTool = jdkPath + "/bin/"_L1 + jarSignerTool; if (!QFile::exists(jarSignerTool)) { fprintf(stderr, "Cannot find jarsigner in JAVA_HOME or PATH. Please use --jdk option to pass in the correct path to JDK.\n"); return false; } - jarSignerTool = QLatin1String("%1 -sigalg %2 -digestalg %3 -keystore %4") + jarSignerTool = "%1 -sigalg %2 -digestalg %3 -keystore %4"_L1 .arg(shellQuote(jarSignerTool), shellQuote(options.sigAlg), shellQuote(options.digestAlg), shellQuote(options.keyStore)); if (!options.keyStorePassword.isEmpty()) - jarSignerTool += QLatin1String(" -storepass %1").arg(shellQuote(options.keyStorePassword)); + jarSignerTool += " -storepass %1"_L1.arg(shellQuote(options.keyStorePassword)); if (!options.storeType.isEmpty()) - jarSignerTool += QLatin1String(" -storetype %1").arg(shellQuote(options.storeType)); + jarSignerTool += " -storetype %1"_L1.arg(shellQuote(options.storeType)); if (!options.keyPass.isEmpty()) - jarSignerTool += QLatin1String(" -keypass %1").arg(shellQuote(options.keyPass)); + jarSignerTool += " -keypass %1"_L1.arg(shellQuote(options.keyPass)); if (!options.sigFile.isEmpty()) - jarSignerTool += QLatin1String(" -sigfile %1").arg(shellQuote(options.sigFile)); + jarSignerTool += " -sigfile %1"_L1.arg(shellQuote(options.sigFile)); if (!options.signedJar.isEmpty()) - jarSignerTool += QLatin1String(" -signedjar %1").arg(shellQuote(options.signedJar)); + jarSignerTool += " -signedjar %1"_L1.arg(shellQuote(options.signedJar)); if (!options.tsaUrl.isEmpty()) - jarSignerTool += QLatin1String(" -tsa %1").arg(shellQuote(options.tsaUrl)); + jarSignerTool += " -tsa %1"_L1.arg(shellQuote(options.tsaUrl)); if (!options.tsaCert.isEmpty()) - jarSignerTool += QLatin1String(" -tsacert %1").arg(shellQuote(options.tsaCert)); + jarSignerTool += " -tsacert %1"_L1.arg(shellQuote(options.tsaCert)); if (options.internalSf) - jarSignerTool += QLatin1String(" -internalsf"); + jarSignerTool += " -internalsf"_L1; if (options.sectionsOnly) - jarSignerTool += QLatin1String(" -sectionsonly"); + jarSignerTool += " -sectionsonly"_L1; if (options.protectedAuthenticationPath) - jarSignerTool += QLatin1String(" -protected"); + jarSignerTool += " -protected"_L1; - auto signPackage = [&](const QString &file) { + auto jarSignPackage = [&](const QString &file) { fprintf(stdout, "Signing file %s\n", qPrintable(file)); fflush(stdout); - QString command = jarSignerTool + QLatin1String(" %1 %2") - .arg(file) - .arg(shellQuote(options.keyStoreAlias)); + QString command = jarSignerTool + " %1 %2"_L1.arg(shellQuote(file)) + .arg(shellQuote(options.keyStoreAlias)); FILE *jarSignerCommand = openProcess(command); if (jarSignerCommand == 0) { @@ -2831,81 +3124,22 @@ bool jarSignerSignPackage(const Options &options) return true; }; - if (!signPackage(packagePath(options, UnsignedAPK))) - return false; - if (options.buildAAB && !signPackage(packagePath(options, AAB))) - return false; - - QString zipAlignTool = options.sdkPath + QLatin1String("/tools/zipalign"); -#if defined(Q_OS_WIN32) - zipAlignTool += QLatin1String(".exe"); -#endif - - if (!QFile::exists(zipAlignTool)) { - zipAlignTool = options.sdkPath + QLatin1String("/build-tools/") + options.sdkBuildToolsVersion + QLatin1String("/zipalign"); -#if defined(Q_OS_WIN32) - zipAlignTool += QLatin1String(".exe"); -#endif - if (!QFile::exists(zipAlignTool)) { - fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool)); - return false; - } - } - - zipAlignTool = QLatin1String("%1%2 -f 4 %3 %4") - .arg(shellQuote(zipAlignTool), - options.verbose ? QLatin1String(" -v") : QLatin1String(), - packagePath(options, UnsignedAPK), - packagePath(options, SignedAPK)); - - FILE *zipAlignCommand = openProcess(zipAlignTool); - if (zipAlignCommand == 0) { - fprintf(stderr, "Couldn't run zipalign.\n"); - return false; - } - - char buffer[512]; - while (fgets(buffer, sizeof(buffer), zipAlignCommand) != 0) - fprintf(stdout, "%s", buffer); - - int errorCode = pclose(zipAlignCommand); - if (errorCode != 0) { - fprintf(stderr, "zipalign command failed.\n"); - if (!options.verbose) - fprintf(stderr, " -- Run with --verbose for more information.\n"); + if (options.buildAAB && !jarSignPackage(packagePath(options, AAB))) return false; - } - - return QFile::remove(packagePath(options, UnsignedAPK)); + return true; } bool signPackage(const Options &options) { - QString apksignerTool = options.sdkPath + QLatin1String("/build-tools/") + options.sdkBuildToolsVersion + QLatin1String("/apksigner"); -#if defined(Q_OS_WIN32) - apksignerTool += QLatin1String(".bat"); -#endif - - if (options.jarSigner || !QFile::exists(apksignerTool)) - return jarSignerSignPackage(options); - - // APKs signed with apksigner must not be changed after they're signed, therefore we need to zipalign it before we sign it. + const QString apksignerTool = batSuffixAppended(options.sdkPath + "/build-tools/"_L1 + + options.sdkBuildToolsVersion + "/apksigner"_L1); + // APKs signed with apksigner must not be changed after they're signed, + // therefore we need to zipalign it before we sign it. - QString zipAlignTool = options.sdkPath + QLatin1String("/tools/zipalign"); -#if defined(Q_OS_WIN32) - zipAlignTool += QLatin1String(".exe"); -#endif - - if (!QFile::exists(zipAlignTool)) { - zipAlignTool = options.sdkPath + QLatin1String("/build-tools/") + options.sdkBuildToolsVersion + QLatin1String("/zipalign"); -#if defined(Q_OS_WIN32) - zipAlignTool += QLatin1String(".exe"); -#endif - if (!QFile::exists(zipAlignTool)) { - fprintf(stderr, "zipalign tool not found: %s\n", qPrintable(zipAlignTool)); - return false; - } - } + bool ok; + QString zipAlignTool = zipalignPath(options, &ok); + if (!ok) + return false; auto zipalignRunner = [](const QString &zipAlignCommandLine) { FILE *zipAlignCommand = openProcess(zipAlignCommandLine); @@ -2921,10 +3155,11 @@ bool signPackage(const Options &options) return pclose(zipAlignCommand) == 0; }; - const QString verifyZipAlignCommandLine = QLatin1String("%1%2 -c 4 %3") - .arg(shellQuote(zipAlignTool), - options.verbose ? QLatin1String(" -v") : QLatin1String(), - packagePath(options, UnsignedAPK)); + const QString verifyZipAlignCommandLine = + "%1%2 -c 4 %3"_L1 + .arg(shellQuote(zipAlignTool), + options.verbose ? " -v"_L1 : QLatin1StringView(), + shellQuote(packagePath(options, UnsignedAPK))); if (zipalignRunner(verifyZipAlignCommandLine)) { if (options.verbose) @@ -2941,11 +3176,12 @@ bool signPackage(const Options &options) if (options.verbose) fprintf(stdout, "APK not aligned, aligning it for signing.\n"); - const QString zipAlignCommandLine = QLatin1String("%1%2 -f 4 %3 %4") - .arg(shellQuote(zipAlignTool), - options.verbose ? QLatin1String(" -v") : QLatin1String(), - packagePath(options, UnsignedAPK), - packagePath(options, SignedAPK)); + const QString zipAlignCommandLine = + "%1%2 -f 4 %3 %4"_L1 + .arg(shellQuote(zipAlignTool), + options.verbose ? " -v"_L1 : QLatin1StringView(), + shellQuote(packagePath(options, UnsignedAPK)), + shellQuote(packagePath(options, SignedAPK))); if (!zipalignRunner(zipAlignCommandLine)) { fprintf(stderr, "zipalign command failed.\n"); @@ -2955,23 +3191,22 @@ bool signPackage(const Options &options) } } - QString apkSignCommand = QLatin1String("%1 sign --ks %2") + QString apkSignCommand = "%1 sign --ks %2"_L1 .arg(shellQuote(apksignerTool), shellQuote(options.keyStore)); if (!options.keyStorePassword.isEmpty()) - apkSignCommand += QLatin1String(" --ks-pass pass:%1").arg(shellQuote(options.keyStorePassword)); + apkSignCommand += " --ks-pass pass:%1"_L1.arg(shellQuote(options.keyStorePassword)); if (!options.keyStoreAlias.isEmpty()) - apkSignCommand += QLatin1String(" --ks-key-alias %1").arg(shellQuote(options.keyStoreAlias)); + apkSignCommand += " --ks-key-alias %1"_L1.arg(shellQuote(options.keyStoreAlias)); if (!options.keyPass.isEmpty()) - apkSignCommand += QLatin1String(" --key-pass pass:%1").arg(shellQuote(options.keyPass)); + apkSignCommand += " --key-pass pass:%1"_L1.arg(shellQuote(options.keyPass)); if (options.verbose) - apkSignCommand += QLatin1String(" --verbose"); + apkSignCommand += " --verbose"_L1; - apkSignCommand += QLatin1String(" %1") - .arg(packagePath(options, SignedAPK)); + apkSignCommand += " %1"_L1.arg(shellQuote(packagePath(options, SignedAPK))); auto apkSignerRunner = [](const QString &command, bool verbose) { FILE *apkSigner = openProcess(command); @@ -2998,8 +3233,12 @@ bool signPackage(const Options &options) if (!apkSignerRunner(apkSignCommand, options.verbose)) return false; - const QString apkVerifyCommand = QLatin1String("%1 verify --verbose %2") - .arg(shellQuote(apksignerTool), packagePath(options, SignedAPK)); + const QString apkVerifyCommand = + "%1 verify --verbose %2"_L1 + .arg(shellQuote(apksignerTool), shellQuote(packagePath(options, SignedAPK))); + + if (options.buildAAB && !signAAB(options)) + return false; // Verify the package and remove the unsigned apk return apkSignerRunner(apkVerifyCommand, true) && QFile::remove(packagePath(options, UnsignedAPK)); @@ -3018,7 +3257,7 @@ enum ErrorCode CannotCopyAndroidExtraLibs = 10, CannotCopyAndroidSources = 11, CannotUpdateAndroidFiles = 12, - CannotCreateAndroidProject = 13, + CannotCreateAndroidProject = 13, // Not used anymore CannotBuildAndroidProject = 14, CannotSignPackage = 15, CannotInstallApk = 16, @@ -3032,12 +3271,19 @@ bool writeDependencyFile(const Options &options) if (options.verbose) fprintf(stdout, "Writing dependency file.\n"); - QFile depFile(options.depFilePath); - - QString relativeApkPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath); + QString relativeTargetPath; + if (options.copyDependenciesOnly) { + // When androiddeploy Qt is running in copyDependenciesOnly mode we need to use + // the timestamp file as the target to collect dependencies. + QString timestampAbsPath = QFileInfo(options.depFilePath).absolutePath() + "/timestamp"_L1; + relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(timestampAbsPath); + } else { + relativeTargetPath = QDir(options.buildDirectory).relativeFilePath(options.apkPath); + } + QFile depFile(options.depFilePath); if (depFile.open(QIODevice::WriteOnly)) { - depFile.write(escapeAndEncodeDependencyPath(relativeApkPath)); + depFile.write(escapeAndEncodeDependencyPath(relativeTargetPath)); depFile.write(": "); for (const auto &file : dependenciesForDepfile) { @@ -3066,10 +3312,9 @@ int main(int argc, char *argv[]) return CannotReadInputFile; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Read input file\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Read input file\n", options.timer.nsecsElapsed()); fprintf(stdout, -// "012345678901234567890123456789012345678901234567890123456789012345678901" "Generating Android Package\n" " Input file: %s\n" " Output directory: %s\n" @@ -3085,61 +3330,81 @@ int main(int argc, char *argv[]) : "No" ); - if (options.build && !options.auxMode) { - cleanAndroidFiles(options); - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Cleaned Android file\n", options.timer.elapsed()); + bool androidTemplatetCopied = false; - if (!copyAndroidTemplate(options)) - return CannotCopyAndroidTemplate; + for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) { + if (!it->enabled) + continue; + options.setCurrentQtArchitecture(it.key(), + it.value().qtInstallDirectory, + it.value().qtDirectories); - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied Android template\n", options.timer.elapsed()); - } + // All architectures have a copy of the gradle files but only one set needs to be copied. + if (!androidTemplatetCopied && options.build && !options.copyDependenciesOnly) { + cleanAndroidFiles(options); + if (Q_UNLIKELY(options.timing)) + fprintf(stdout, "[TIMING] %lld ns: Cleaned Android file\n", options.timer.nsecsElapsed()); - for (auto it = options.architectures.constBegin(); it != options.architectures.constEnd(); ++it) { - options.clear(it.key()); + if (!copyAndroidTemplate(options)) + return CannotCopyAndroidTemplate; + + if (Q_UNLIKELY(options.timing)) + fprintf(stdout, "[TIMING] %lld ns: Copied Android template\n", options.timer.nsecsElapsed()); + androidTemplatetCopied = true; + } if (!readDependencies(&options)) return CannotReadDependencies; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Read dependencies\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Read dependencies\n", options.timer.nsecsElapsed()); if (!copyQtFiles(&options)) return CannotCopyQtFiles; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied Qt files\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Copied Qt files\n", options.timer.nsecsElapsed()); if (!copyAndroidExtraLibs(&options)) return CannotCopyAndroidExtraLibs; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied extra libs\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ms: Copied extra libs\n", options.timer.nsecsElapsed()); if (!copyAndroidExtraResources(&options)) return CannotCopyAndroidExtraResources; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied extra resources\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Copied extra resources\n", options.timer.nsecsElapsed()); - if (!options.auxMode) { - if (!copyStdCpp(&options)) - return CannotCopyGnuStl; - - if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied GNU STL\n", options.timer.elapsed()); - } + if (!copyStdCpp(&options)) + return CannotCopyGnuStl; - if (!containsApplicationBinary(&options)) + if (Q_UNLIKELY(options.timing)) + fprintf(stdout, "[TIMING] %lld ns: Copied GNU STL\n", options.timer.nsecsElapsed()); + + // If Unbundled deployment is used, remove app lib as we don't want it packaged inside the APK + if (options.deploymentMechanism == Options::Unbundled) { + QString appLibPath = "%1/libs/%2/lib%3_%2.so"_L1. + arg(options.outputDirectory, + options.currentArchitecture, + options.applicationBinary); + QFile::remove(appLibPath); + } else if (!containsApplicationBinary(&options)) { return CannotFindApplicationBinary; + } if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Checked for application binary\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Checked for application binary\n", options.timer.nsecsElapsed()); if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Bundled Qt libs\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Bundled Qt libs\n", options.timer.nsecsElapsed()); + } + + if (options.copyDependenciesOnly) { + if (!options.depFilePath.isEmpty()) + writeDependencyFile(options); + return 0; } if (!createRcc(options)) @@ -3151,28 +3416,27 @@ int main(int argc, char *argv[]) return 0; } - if (options.build) { if (!copyAndroidSources(options)) return CannotCopyAndroidSources; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Copied android sources\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Copied android sources\n", options.timer.nsecsElapsed()); if (!updateAndroidFiles(options)) return CannotUpdateAndroidFiles; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Updated files\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Updated files\n", options.timer.nsecsElapsed()); if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Created project\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Created project\n", options.timer.nsecsElapsed()); if (!buildAndroidProject(options)) return CannotBuildAndroidProject; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Built project\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Built project\n", options.timer.nsecsElapsed()); if (!options.keyStore.isEmpty() && !signPackage(options)) return CannotSignPackage; @@ -3181,14 +3445,14 @@ int main(int argc, char *argv[]) return CannotCopyApk; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Signed package\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Signed package\n", options.timer.nsecsElapsed()); } if (options.installApk && !installApk(options)) return CannotInstallApk; if (Q_UNLIKELY(options.timing)) - fprintf(stdout, "[TIMING] %d ms: Installed APK\n", options.timer.elapsed()); + fprintf(stdout, "[TIMING] %lld ns: Installed APK\n", options.timer.nsecsElapsed()); if (!options.depFilePath.isEmpty()) writeDependencyFile(options); diff --git a/src/tools/androidtestrunner/CMakeLists.txt b/src/tools/androidtestrunner/CMakeLists.txt index c53b9125ca..7935753cff 100644 --- a/src/tools/androidtestrunner/CMakeLists.txt +++ b/src/tools/androidtestrunner/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from androidtestrunner.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## androidtestrunner App: @@ -14,16 +15,14 @@ qt_internal_add_tool(${target_name} QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII QT_NO_FOREACH - PUBLIC_LIBRARIES - Qt::Gui + LIBRARIES + Qt::Core ) +qt_internal_return_unless_building_tools() set_target_properties(${target_name} PROPERTIES WIN32_EXECUTABLE FALSE ) -#### Keys ignored in scope 1:.:.:androidtestrunner.pro:<TRUE>: -# _OPTION = "host_build" - ## Scopes: ##################################################################### diff --git a/src/tools/androidtestrunner/main.cpp b/src/tools/androidtestrunner/main.cpp index 3e59148cff..baad678ec0 100644 --- a/src/tools/androidtestrunner/main.cpp +++ b/src/tools/androidtestrunner/main.cpp @@ -1,203 +1,200 @@ -/**************************************************************************** -** -** Copyright (C) 2019 BogDan Vatra <bogdan@kde.org> -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QCoreApplication> -#include <QDir> -#include <QHash> -#include <QRegularExpression> -#include <QSystemSemaphore> -#include <QXmlStreamReader> - -#include <algorithm> -#include <chrono> +// Copyright (C) 2019 BogDan Vatra <bogdan@kde.org> +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtCore/QCoreApplication> +#include <QtCore/QDeadlineTimer> +#include <QtCore/QDir> +#include <QtCore/QHash> +#include <QtCore/QProcess> +#include <QtCore/QProcessEnvironment> +#include <QtCore/QRegularExpression> +#include <QtCore/QSystemSemaphore> +#include <QtCore/QThread> +#include <QtCore/QXmlStreamReader> + +#include <atomic> +#include <csignal> #include <functional> -#include <thread> - -#ifdef Q_CC_MSVC -#define popen _popen -#define QT_POPEN_READ "rb" -#define pclose _pclose +#if defined(Q_OS_WIN32) +#include <process.h> #else -#define QT_POPEN_READ "r" +#include <unistd.h> #endif -static auto junitChecker = [](const QByteArray &data) -> bool { +using namespace Qt::StringLiterals; + +static bool checkJunit(const QByteArray &data) { QXmlStreamReader reader{data}; while (!reader.atEnd()) { reader.readNext(); - if (reader.isStartElement() && reader.name() == QStringLiteral("testcase") && - reader.attributes().value(QStringLiteral("result")).toString() == QStringLiteral("fail")) { + + if (!reader.isStartElement()) + continue; + + if (reader.name() == "error"_L1) return false; + + const QString type = reader.attributes().value("type"_L1).toString(); + if (reader.name() == "failure"_L1) { + if (type == "fail"_L1 || type == "xpass"_L1) + return false; } } + + // Fail if there's an error after reading through all the xml output + return !reader.hasError(); +} + +static bool checkTxt(const QByteArray &data) { + if (data.indexOf("\nFAIL! : "_L1) >= 0) + return false; + if (data.indexOf("\nXPASS : "_L1) >= 0) + return false; + // Look for "********* Finished testing of tst_QTestName *********" + static const QRegularExpression testTail("\\*+ +Finished testing of .+ +\\*+"_L1); + return testTail.match(QLatin1StringView(data)).hasMatch(); +} + +static bool checkCsv(const QByteArray &data) { + // The csv format is only suitable for benchmarks, + // so this is not much useful to determine test failure/success. + // FIXME: warn the user early on about this. + Q_UNUSED(data); return true; -}; +} + +static bool checkXml(const QByteArray &data) { + QXmlStreamReader reader{data}; + while (!reader.atEnd()) { + reader.readNext(); + const QString type = reader.attributes().value("type"_L1).toString(); + const bool isIncident = (reader.name() == "Incident"_L1); + if (reader.isStartElement() && isIncident) { + if (type == "fail"_L1 || type == "xpass"_L1) + return false; + } + } + + // Fail if there's an error after reading through all the xml output + return !reader.hasError(); +} + +static bool checkLightxml(const QByteArray &data) { + // lightxml intentionally skips the root element, which technically makes it + // not valid XML. We'll add that ourselves for the purpose of validation. + QByteArray newData = data; + newData.prepend("<root>"); + newData.append("</root>"); + return checkXml(newData); +} + +static bool checkTeamcity(const QByteArray &data) { + if (data.indexOf("' message='Failure! |[Loc: ") >= 0) + return false; + const QList<QByteArray> lines = data.trimmed().split('\n'); + if (lines.isEmpty()) + return false; + return lines.last().startsWith("##teamcity[testSuiteFinished "_L1); +} + +static bool checkTap(const QByteArray &data) { + // This will still report blacklisted fails because QTest with TAP + // is not putting any data about that. + if (data.indexOf("\nnot ok ") >= 0) + return false; + + static const QRegularExpression testTail("ok [0-9]* - cleanupTestCase\\(\\)"_L1); + return testTail.match(QLatin1StringView(data)).hasMatch(); +} struct Options { bool helpRequested = false; bool verbose = false; bool skipAddInstallRoot = false; - std::chrono::seconds timeout{300}; // 5minutes + int timeoutSecs = 600; // 10 minutes QString buildPath; - QString adbCommand{QStringLiteral("adb")}; + QString adbCommand{"adb"_L1}; QString makeCommand; QString package; QString activity; QStringList testArgsList; QHash<QString, QString> outFiles; - QString testArgs; + QStringList amStarttestArgs; QString apkPath; - QHash<QString, std::function<bool(const QByteArray &)>> checkFiles = { - {QStringLiteral("txt"), [](const QByteArray &data) -> bool { - return data.indexOf("\nFAIL! : ") < 0; - }}, - {QStringLiteral("csv"), [](const QByteArray &/*data*/) -> bool { - // It seems csv is broken - return true; - }}, - {QStringLiteral("xml"), [](const QByteArray &data) -> bool { - QXmlStreamReader reader{data}; - while (!reader.atEnd()) { - reader.readNext(); - if (reader.isStartElement() && reader.name() == QStringLiteral("Incident") && - reader.attributes().value(QStringLiteral("type")).toString() == QStringLiteral("fail")) { - return false; - } - } - return true; - }}, - {QStringLiteral("lightxml"), [](const QByteArray &data) -> bool { - return data.indexOf("\n<Incident type=\"fail\" ") < 0; - }}, - {QStringLiteral("xunitxml"), junitChecker}, - {QStringLiteral("junitxml"), junitChecker}, - {QStringLiteral("teamcity"), [](const QByteArray &data) -> bool { - return data.indexOf("' message='Failure! |[Loc: ") < 0; - }}, - {QStringLiteral("tap"), [](const QByteArray &data) -> bool { - return data.indexOf("\nnot ok ") < 0; - }}, + QString ndkStackPath; + bool showLogcatOutput = false; + const QHash<QString, std::function<bool(const QByteArray &)>> checkFiles = { + {"txt"_L1, checkTxt}, + {"csv"_L1, checkCsv}, + {"xml"_L1, checkXml}, + {"lightxml"_L1, checkLightxml}, + {"xunitxml"_L1, checkJunit}, + {"junitxml"_L1, checkJunit}, + {"teamcity"_L1, checkTeamcity}, + {"tap"_L1, checkTap}, }; }; static Options g_options; -static bool execCommand(const QString &command, QByteArray *output = nullptr, bool verbose = false) +struct TestInfo +{ + int sdkVersion = -1; + int pid = -1; + + std::atomic<bool> isPackageInstalled { false }; + std::atomic<bool> isTestRunnerInterrupted { false }; +}; + +static TestInfo g_testInfo; + +static bool execCommand(const QString &program, const QStringList &args, + QByteArray *output = nullptr, bool verbose = false) { - if (verbose) - fprintf(stdout, "Execute %s.\n", command.toUtf8().constData()); - FILE *process = popen(command.toUtf8().constData(), QT_POPEN_READ); + const auto command = program + " "_L1 + args.join(u' '); - if (!process) { - fprintf(stderr, "Cannot execute command %s.\n", qPrintable(command)); + if (verbose && g_options.verbose) + qDebug("Execute %s.", command.toUtf8().constData()); + + QProcess process; + process.start(program, args); + if (!process.waitForStarted()) { + qCritical("Cannot execute command %s.", qPrintable(command)); return false; } - char buffer[512]; - while (fgets(buffer, sizeof(buffer), process)) { - if (output) - output->append(buffer); - if (verbose) - fprintf(stdout, "%s", buffer); - } - return pclose(process) == 0; -} -// Copy-pasted from qmake/library/ioutil.cpp -inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16]) -{ - for (int x = arg.length() - 1; x >= 0; --x) { - ushort c = arg.unicode()[x].unicode(); - if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)))) - return true; + // If the command is not adb, for example, make or ninja, it can take more that + // QProcess::waitForFinished() 30 secs, so for that use a higher timeout. + const int FinishTimeout = program.endsWith("adb"_L1) ? 30000 : g_options.timeoutSecs * 1000; + if (!process.waitForFinished(FinishTimeout)) { + qCritical("Execution of command %s timed out.", qPrintable(command)); + return false; } - return false; -} -static QString shellQuoteUnix(const QString &arg) -{ - // Chars that should be quoted (TM). This includes: - static const uchar iqm[] = { - 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, - 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 - }; // 0-32 \'"$`<>|;&(){}*?#!~[] + const auto stdOut = process.readAllStandardOutput(); + if (output) + output->append(stdOut); - if (!arg.length()) - return QStringLiteral("\"\""); + if (verbose && g_options.verbose) + qDebug() << stdOut.constData(); - QString ret(arg); - if (hasSpecialChars(ret, iqm)) { - ret.replace(QLatin1Char('\''), QStringLiteral("'\\''")); - ret.prepend(QLatin1Char('\'')); - ret.append(QLatin1Char('\'')); - } - return ret; + return process.exitCode() == 0; } -static QString shellQuoteWin(const QString &arg) +static bool execAdbCommand(const QStringList &args, QByteArray *output = nullptr, + bool verbose = true) { - // Chars that should be quoted (TM). This includes: - // - control chars & space - // - the shell meta chars "&()<>^| - // - the potential separators ,;= - static const uchar iqm[] = { - 0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78, - 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 - }; - - if (!arg.length()) - return QStringLiteral("\"\""); - - QString ret(arg); - if (hasSpecialChars(ret, iqm)) { - // Quotes are escaped and their preceding backslashes are doubled. - // It's impossible to escape anything inside a quoted string on cmd - // level, so the outer quoting must be "suspended". - ret.replace(QRegularExpression(QStringLiteral("(\\\\*)\"")), QStringLiteral("\"\\1\\1\\^\"\"")); - // The argument must not end with a \ since this would be interpreted - // as escaping the quote -- rather put the \ behind the quote: e.g. - // rather use "foo"\ than "foo\" - int i = ret.length(); - while (i > 0 && ret.at(i - 1) == QLatin1Char('\\')) - --i; - ret.insert(i, QLatin1Char('"')); - ret.prepend(QLatin1Char('"')); - } - return ret; + return execCommand(g_options.adbCommand, args, output, verbose); } -static QString shellQuote(const QString &arg) +static bool execCommand(const QString &command, QByteArray *output = nullptr, bool verbose = true) { - if (QDir::separator() == QLatin1Char('\\')) - return shellQuoteWin(arg); - else - return shellQuoteUnix(arg); + auto args = QProcess::splitCommand(command); + const auto program = args.first(); + args.removeOne(program); + return execCommand(program, args, output, verbose); } static bool parseOptions() @@ -206,43 +203,50 @@ static bool parseOptions() int i = 1; for (; i < arguments.size(); ++i) { const QString &argument = arguments.at(i); - if (argument.compare(QStringLiteral("--adb"), Qt::CaseInsensitive) == 0) { + if (argument.compare("--adb"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) g_options.helpRequested = true; else g_options.adbCommand = arguments.at(++i); - } else if (argument.compare(QStringLiteral("--path"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--path"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) g_options.helpRequested = true; else g_options.buildPath = arguments.at(++i); - } else if (argument.compare(QStringLiteral("--make"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--make"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) g_options.helpRequested = true; else g_options.makeCommand = arguments.at(++i); - } else if (argument.compare(QStringLiteral("--apk"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--apk"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) g_options.helpRequested = true; else g_options.apkPath = arguments.at(++i); - } else if (argument.compare(QStringLiteral("--activity"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--activity"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) g_options.helpRequested = true; else g_options.activity = arguments.at(++i); - } else if (argument.compare(QStringLiteral("--skip-install-root"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--skip-install-root"_L1, Qt::CaseInsensitive) == 0) { g_options.skipAddInstallRoot = true; - } else if (argument.compare(QStringLiteral("--timeout"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--show-logcat"_L1, Qt::CaseInsensitive) == 0) { + g_options.showLogcatOutput = true; + } else if (argument.compare("--ndk-stack"_L1, Qt::CaseInsensitive) == 0) { + if (i + 1 == arguments.size()) + g_options.helpRequested = true; + else + g_options.ndkStackPath = arguments.at(++i); + } else if (argument.compare("--timeout"_L1, Qt::CaseInsensitive) == 0) { if (i + 1 == arguments.size()) g_options.helpRequested = true; else - g_options.timeout = std::chrono::seconds{arguments.at(++i).toInt()}; - } else if (argument.compare(QStringLiteral("--help"), Qt::CaseInsensitive) == 0) { + g_options.timeoutSecs = arguments.at(++i).toInt(); + } else if (argument.compare("--help"_L1, Qt::CaseInsensitive) == 0) { g_options.helpRequested = true; - } else if (argument.compare(QStringLiteral("--verbose"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--verbose"_L1, Qt::CaseInsensitive) == 0) { g_options.verbose = true; - } else if (argument.compare(QStringLiteral("--"), Qt::CaseInsensitive) == 0) { + } else if (argument.compare("--"_L1, Qt::CaseInsensitive) == 0) { ++i; break; } else { @@ -257,17 +261,24 @@ static bool parseOptions() QString serial = qEnvironmentVariable("ANDROID_DEVICE_SERIAL"); if (!serial.isEmpty()) - g_options.adbCommand += QStringLiteral(" -s %1").arg(serial); + g_options.adbCommand += " -s %1"_L1.arg(serial); + + if (g_options.ndkStackPath.isEmpty()) { + const QString ndkPath = qEnvironmentVariable("ANDROID_NDK_ROOT"); + const QString ndkStackPath = ndkPath + QDir::separator() + "ndk-stack"_L1; + if (QFile::exists(ndkStackPath)) + g_options.ndkStackPath = ndkStackPath; + } + return true; } static void printHelp() -{// "012345678901234567890123456789012345678901234567890123456789012345678901" - fprintf(stderr, "Syntax: %s <options> -- [TESTARGS] \n" +{ + qWarning( "Syntax: %s <options> -- [TESTARGS] \n" "\n" - " Creates an Android package in a temp directory <destination> and\n" - " runs it on the default emulator/device or on the one specified by\n" - " \"ANDROID_DEVICE_SERIAL\" environment variable.\n" + " Runs an Android test on the default emulator/device or on the one\n" + " specified by \"ANDROID_DEVICE_SERIAL\" environment variable.\n" "\n" " Mandatory arguments:\n" " --path <path>: The path where androiddeployqt builds the android package.\n" @@ -277,7 +288,7 @@ static void printHelp() "\n" " Optional arguments:\n" " --make <make cmd>: make command, needed to install the qt library.\n" - " For Qt 5.14+ this can be \"make apk\".\n" + " For Qt 6, this can be \"cmake --build . --target <target>_make_apk\".\n" "\n" " --adb <adb cmd>: The Android ADB command. If missing the one from\n" " $PATH will be used.\n" @@ -285,15 +296,20 @@ static void printHelp() " --activity <acitvity>: The Activity to run. If missing the first\n" " activity from AndroidManifest.qml file will be used.\n" "\n" - " --timeout <seconds>: Timeout to run the test. Default is 5 minutes.\n" + " --timeout <seconds>: Timeout to run the test. Default is 10 minutes.\n" "\n" " --skip-install-root: Do not append INSTALL_ROOT=... to the make command.\n" "\n" + " --show-logcat: Print Logcat output to stdout.\n" + "\n" + " --ndk-stack: Path to ndk-stack tool that symbolizes crash stacktraces.\n" + " By default, ANDROID_NDK_ROOT env var is used to deduce the tool path.\n" + "\n" " -- Arguments that will be passed to the test application.\n" "\n" " --verbose: Prints out information during processing.\n" "\n" - " --help: Displays this information.\n\n", + " --help: Displays this information.\n", qPrintable(QCoreApplication::arguments().at(0)) ); } @@ -305,8 +321,8 @@ static QString packageNameFromAndroidManifest(const QString &androidManifestPath QXmlStreamReader reader(&androidManifestXml); while (!reader.atEnd()) { reader.readNext(); - if (reader.isStartElement() && reader.name() == QStringLiteral("manifest")) - return reader.attributes().value(QStringLiteral("package")).toString(); + if (reader.isStartElement() && reader.name() == "manifest"_L1) + return reader.attributes().value("package"_L1).toString(); } } return {}; @@ -319,8 +335,8 @@ static QString activityFromAndroidManifest(const QString &androidManifestPath) QXmlStreamReader reader(&androidManifestXml); while (!reader.atEnd()) { reader.readNext(); - if (reader.isStartElement() && reader.name() == QStringLiteral("activity")) - return reader.attributes().value(QStringLiteral("android:name")).toString(); + if (reader.isStartElement() && reader.name() == "activity"_L1) + return reader.attributes().value("android:name"_L1).toString(); } } return {}; @@ -329,26 +345,26 @@ static QString activityFromAndroidManifest(const QString &androidManifestPath) static void setOutputFile(QString file, QString format) { if (file.isEmpty()) - file = QStringLiteral("-"); + file = "-"_L1; if (format.isEmpty()) - format = QStringLiteral("txt"); + format = "txt"_L1; g_options.outFiles[format] = file; } static bool parseTestArgs() { - QRegularExpression oldFormats{QStringLiteral("^-(txt|csv|xunitxml|junitxml|xml|lightxml|teamcity|tap)$")}; - QRegularExpression newLoggingFormat{QStringLiteral("^(.*),(txt|csv|xunitxml|junitxml|xml|lightxml|teamcity|tap)$")}; + QRegularExpression oldFormats{"^-(txt|csv|xunitxml|junitxml|xml|lightxml|teamcity|tap)$"_L1}; + QRegularExpression newLoggingFormat{"^(.*),(txt|csv|xunitxml|junitxml|xml|lightxml|teamcity|tap)$"_L1}; QString file; QString logType; - QString unhandledArgs; + QStringList unhandledArgs; for (int i = 0; i < g_options.testArgsList.size(); ++i) { const QString &arg = g_options.testArgsList[i].trimmed(); - if (arg == QStringLiteral("--")) + if (arg == "--"_L1) continue; - if (arg == QStringLiteral("-o")) { + if (arg == "-o"_L1) { if (i >= g_options.testArgsList.size() - 1) return false; // missing file argument @@ -365,68 +381,157 @@ static bool parseTestArgs() if (match.hasMatch()) { logType = match.capturedTexts().at(1); } else { - unhandledArgs += QStringLiteral(" %1").arg(arg); + // Use triple literal quotes so that QProcess::splitCommand() in androidjnimain.cpp + // keeps quotes characters inside the string. + QString quotedArg = QString(arg).replace("\""_L1, "\\\"\\\"\\\""_L1); + // Escape single quotes so they don't interfere with the shell command, + // and so they get passed to the app as single quote inside the string. + quotedArg.replace("'"_L1, "\'"_L1); + // Add escaped double quote character so that args with spaces are treated as one. + unhandledArgs << " \\\"%1\\\""_L1.arg(quotedArg); } } } if (g_options.outFiles.isEmpty() || !file.isEmpty() || !logType.isEmpty()) setOutputFile(file, logType); + QString testAppArgs; for (const auto &format : g_options.outFiles.keys()) - g_options.testArgs += QStringLiteral(" -o output.%1,%1").arg(format); + testAppArgs += "-o output.%1,%1 "_L1.arg(format); + + testAppArgs += unhandledArgs.join(u' ').trimmed(); + testAppArgs = "\"%1\""_L1.arg(testAppArgs.trimmed()); + const QString activityName = "%1/%2"_L1.arg(g_options.package).arg(g_options.activity); + + // Pass over any testlib env vars if set + QString testEnvVars; + const QStringList envVarsList = QProcessEnvironment::systemEnvironment().toStringList(); + for (const QString &var : envVarsList) { + if (var.startsWith("QTEST_"_L1)) + testEnvVars += "%1 "_L1.arg(var); + } + + if (!testEnvVars.isEmpty()) { + testEnvVars = QString::fromUtf8(testEnvVars.trimmed().toUtf8().toBase64()); + testEnvVars = "-e extraenvvars \"%4\""_L1.arg(testEnvVars); + } + + g_options.amStarttestArgs = { "shell"_L1, "am"_L1, "start"_L1, + "-n"_L1, activityName, + "-e"_L1, "applicationArguments"_L1, testAppArgs, + testEnvVars + }; - g_options.testArgs += unhandledArgs; - g_options.testArgs = QStringLiteral("shell am start -e applicationArguments \\\"%1\\\" -n %2/%3").arg(shellQuote(g_options.testArgs.trimmed()), - g_options.package, - g_options.activity); return true; } -static bool isRunning() { +static bool obtainPid() { QByteArray output; - if (!execCommand(QStringLiteral("%1 shell \"ps | grep ' %2'\"").arg(g_options.adbCommand, - shellQuote(g_options.package)), &output)) { + const QStringList psArgs = { "shell"_L1, "ps | grep ' %1'"_L1.arg(g_options.package) }; + if (!execAdbCommand(psArgs, &output, false)) + return false; + const QList<QByteArray> lines = output.split(u'\n'); + if (lines.size() < 1) return false; + + QList<QByteArray> columns = lines.first().simplified().replace(u'\t', u' ').split(u' '); + if (columns.size() < 3) + return false; + + if (g_testInfo.pid == -1) { + bool ok = false; + int pid = columns.at(1).toInt(&ok); + if (ok) + g_testInfo.pid = pid; } - return output.indexOf(QLatin1String(" " + g_options.package.toUtf8())) > -1; + + return true; } -static bool waitToFinish() +static bool isRunning() { + QByteArray output; + const QStringList psArgs = { "shell"_L1, "ps | grep ' %1'"_L1.arg(g_options.package) }; + if (!execAdbCommand(psArgs, &output, false)) + return false; + + return output.indexOf(QLatin1StringView(" " + g_options.package.toUtf8())) > -1; +} + +static void waitForStartedAndFinished() { - using clock = std::chrono::system_clock; - auto start = clock::now(); - // wait to start - while (!isRunning()) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - if ((clock::now() - start) > std::chrono::seconds{10}) - return false; - } + // wait to start and set PID + QDeadlineTimer startDeadline(10000); + do { + if (obtainPid()) + break; + QThread::msleep(100); + } while (!startDeadline.hasExpired() && !g_testInfo.isTestRunnerInterrupted.load()); // Wait to finish - while (isRunning()) { - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - if ((clock::now() - start) > g_options.timeout) - return false; - } - return true; + QDeadlineTimer finishedDeadline(g_options.timeoutSecs * 1000); + do { + if (!isRunning()) + break; + QThread::msleep(250); + } while (!finishedDeadline.hasExpired() && !g_testInfo.isTestRunnerInterrupted.load()); + + if (finishedDeadline.hasExpired()) + qWarning() << "Timed out while waiting for the test to finish"; } +static void obtainSdkVersion() +{ + // SDK version is necessary, as in SDK 23 pidof is broken, so we cannot obtain the pid. + // Also, Logcat cannot filter by pid in SDK 23, so we don't offer the --show-logcat option. + QByteArray output; + const QStringList versionArgs = { "shell"_L1, "getprop"_L1, "ro.build.version.sdk"_L1 }; + execAdbCommand(versionArgs, &output, false); + bool ok = false; + int sdkVersion = output.toInt(&ok); + if (ok) + g_testInfo.sdkVersion = sdkVersion; + else + qCritical() << "Unable to obtain the SDK version of the target."; +} static bool pullFiles() { bool ret = true; + QByteArray userId; + // adb get-current-user command is available starting from API level 26. + if (g_testInfo.sdkVersion >= 26) { + const QStringList userIdArgs = {"shell"_L1, "cmd"_L1, "activity"_L1, "get-current-user"_L1}; + if (!execAdbCommand(userIdArgs, &userId, false)) { + qCritical() << "Error: failed to retrieve the user ID"; + return false; + } + } else { + userId = "0"; + } + for (auto it = g_options.outFiles.constBegin(); it != g_options.outFiles.end(); ++it) { + // Get only stdout from cat and get rid of stderr and fail later if the output is empty + const QString outSuffix = it.key(); + const QString catCmd = "cat files/output.%1 2> /dev/null"_L1.arg(outSuffix); + const QStringList fullCatArgs = { "shell"_L1, "run-as %1 --user %2 %3"_L1.arg( + g_options.package, QString::fromUtf8(userId.simplified()), catCmd) }; + QByteArray output; - if (!execCommand(QStringLiteral("%1 shell run-as %2 cat files/output.%3") - .arg(g_options.adbCommand, g_options.package, it.key()), &output)) { + if (!execAdbCommand(fullCatArgs, &output, false)) { + qCritical() << "Error: failed to retrieve the test's output.%1 file."_L1.arg(outSuffix); return false; } - auto checkerIt = g_options.checkFiles.find(it.key()); - ret = ret && checkerIt != g_options.checkFiles.end() && checkerIt.value()(output); - if (it.value() == QStringLiteral("-")){ - fprintf(stdout, "%s", output.constData()); - fflush(stdout); + + if (output.isEmpty()) { + qCritical() << "Error: the test's output.%1 is empty."_L1.arg(outSuffix); + return false; + } + + auto checkerIt = g_options.checkFiles.find(outSuffix); + ret &= (checkerIt != g_options.checkFiles.end() && checkerIt.value()(output)); + if (it.value() == "-"_L1) { + qDebug() << output.constData(); } else { QFile out{it.value()}; if (!out.open(QIODevice::WriteOnly)) @@ -437,21 +542,171 @@ static bool pullFiles() return ret; } -struct RunnerLocker +void printLogcat(const QString &formattedTime) { - RunnerLocker() - { - runner.acquire(); + QStringList logcatArgs = { "logcat"_L1 }; + if (g_testInfo.sdkVersion <= 23 || g_testInfo.pid == -1) + logcatArgs << "-t"_L1 << formattedTime; + else + logcatArgs << "-d"_L1 << "--pid=%1"_L1.arg(QString::number(g_testInfo.pid)); + + QByteArray logcat; + if (!execAdbCommand(logcatArgs, &logcat, false)) { + qCritical() << "Error: failed to fetch logcat of the test"; + return; + } + + if (logcat.isEmpty()) { + qWarning() << "The retrieved logcat is empty"; + return; + } + + qDebug() << "****** Begin logcat output ******"; + qDebug().noquote() << logcat; + qDebug() << "****** End logcat output ******"; +} + +static QString getDeviceABI() +{ + const QStringList abiArgs = { "shell"_L1, "getprop"_L1, "ro.product.cpu.abi"_L1 }; + QByteArray abi; + if (!execAdbCommand(abiArgs, &abi, false)) { + qWarning() << "Warning: failed to get the device abi, fallback to first libs dir"; + return {}; + } + + return QString::fromUtf8(abi.simplified()); +} + +void printLogcatCrashBuffer(const QString &formattedTime) +{ + bool useNdkStack = false; + auto libsPath = "%1/libs/"_L1.arg(g_options.buildPath); + + if (!g_options.ndkStackPath.isEmpty()) { + QString abi = getDeviceABI(); + if (abi.isEmpty()) { + QStringList subDirs = QDir(libsPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot); + if (!subDirs.isEmpty()) + abi = subDirs.first(); + } + + if (!abi.isEmpty()) { + libsPath += abi; + useNdkStack = true; + } else { + qWarning() << "Warning: failed to get the libs abi, ndk-stack cannot be used."; + } + } else { + qWarning() << "Warning: ndk-stack path not provided and couldn't be deduced " + "using the ANDROID_NDK_ROOT environment variable."; + } + + QProcess adbCrashProcess; + QProcess ndkStackProcess; + + if (useNdkStack) { + adbCrashProcess.setStandardOutputProcess(&ndkStackProcess); + ndkStackProcess.start(g_options.ndkStackPath, { "-sym"_L1, libsPath }); + } + + const QStringList adbCrashArgs = { "logcat"_L1, "-b"_L1, "crash"_L1, "-t"_L1, formattedTime }; + adbCrashProcess.start(g_options.adbCommand, adbCrashArgs); + + if (!adbCrashProcess.waitForStarted()) { + qCritical() << "Error: failed to run adb logcat crash command."; + return; + } + + if (useNdkStack && !ndkStackProcess.waitForStarted()) { + qCritical() << "Error: failed to run ndk-stack command."; + return; + } + + if (!adbCrashProcess.waitForFinished()) { + qCritical() << "Error: adb command timed out."; + return; } - ~RunnerLocker() + + if (useNdkStack && !ndkStackProcess.waitForFinished()) { + qCritical() << "Error: ndk-stack command timed out."; + return; + } + + const QByteArray crash = useNdkStack ? ndkStackProcess.readAllStandardOutput() + : adbCrashProcess.readAllStandardOutput(); + if (crash.isEmpty()) { + qWarning() << "The retrieved crash logcat is empty"; + return; + } + + qDebug() << "****** Begin logcat crash buffer output ******"; + qDebug().noquote() << crash; + qDebug() << "****** End logcat crash buffer output ******"; +} + +static QString getCurrentTimeString() +{ + const QString timeFormat = (g_testInfo.sdkVersion <= 23) ? + "%m-%d %H:%M:%S.000"_L1 : "%Y-%m-%d %H:%M:%S.%3N"_L1; + + QStringList dateArgs = { "shell"_L1, "date"_L1, "+'%1'"_L1.arg(timeFormat) }; + QByteArray output; + if (!execAdbCommand(dateArgs, &output, false)) { + qWarning() << "Date/time adb command failed"; + return {}; + } + + return QString::fromUtf8(output.simplified()); +} + +static bool uninstallTestPackage() +{ + return execAdbCommand({ "uninstall"_L1, g_options.package }, nullptr); +} + +struct TestRunnerSystemSemaphore +{ + TestRunnerSystemSemaphore() { } + ~TestRunnerSystemSemaphore() { release(); } + + void acquire() { isAcquired.store(semaphore.acquire()); } + + void release() { - runner.release(); + bool expected = true; + // NOTE: There's still could be tiny time gap between the compare_exchange_strong() call + // and release() call where the thread could be interrupted, if that's ever an issue, + // this code could be checked and improved further. + if (isAcquired.compare_exchange_strong(expected, false)) + isAcquired.store(!semaphore.release()); } - QSystemSemaphore runner{QStringLiteral("androidtestrunner"), 1, QSystemSemaphore::Open}; + + std::atomic<bool> isAcquired { false }; + QSystemSemaphore semaphore { QSystemSemaphore::platformSafeKey(u"androidtestrunner"_s), + 1, QSystemSemaphore::Open }; }; +TestRunnerSystemSemaphore testRunnerLock; + +void sigHandler(int signal) +{ + std::signal(signal, SIG_DFL); + testRunnerLock.release(); + // Ideally we shouldn't be doing such calls from a signal handler, + // and we can't use QSocketNotifier because this tool doesn't spin + // a main event loop. Since, there's no other alternative to do this, + // let's do the cleanup anyway. + if (!g_testInfo.isPackageInstalled.load()) + _exit(-1); + g_testInfo.isTestRunnerInterrupted.store(true); +} + int main(int argc, char *argv[]) { + std::signal(SIGINT, sigHandler); + std::signal(SIGTERM, sigHandler); + QCoreApplication a(argc, argv); if (!parseOptions()) { printHelp(); @@ -459,41 +714,33 @@ int main(int argc, char *argv[]) } if (g_options.makeCommand.isEmpty()) { - fprintf(stderr, - "It is required to provide a make command with the \"--make\" parameter " - "to generate the apk.\n"); + qCritical() << "It is required to provide a make command with the \"--make\" parameter " + "to generate the apk."; return 1; } if (!execCommand(g_options.makeCommand, nullptr, true)) { if (!g_options.skipAddInstallRoot) { // we need to run make INSTALL_ROOT=path install to install the application file(s) first - if (!execCommand(QStringLiteral("%1 INSTALL_ROOT=%2 install") - .arg(g_options.makeCommand, QDir::toNativeSeparators(g_options.buildPath)), nullptr, g_options.verbose)) { + if (!execCommand("%1 INSTALL_ROOT=%2 install"_L1.arg(g_options.makeCommand, + QDir::toNativeSeparators(g_options.buildPath)), nullptr)) { return 1; } } else { - if (!execCommand(QStringLiteral("%1") - .arg(g_options.makeCommand), nullptr, g_options.verbose)) { + if (!execCommand(g_options.makeCommand, nullptr)) return 1; - } } } if (!QFile::exists(g_options.apkPath)) { - fprintf(stderr, - "No apk \"%s\" found after running the make command. Check the provided path and " - "the make command.\n", - qPrintable(g_options.apkPath)); + qCritical("No apk \"%s\" found after running the make command. " + "Check the provided path and the make command.", + qPrintable(g_options.apkPath)); return 1; } - RunnerLocker lock; // do not install or run packages while another test is running - if (!execCommand(QStringLiteral("%1 install -r -g %2") - .arg(g_options.adbCommand, g_options.apkPath), nullptr, g_options.verbose)) { - return 1; - } + obtainSdkVersion(); - QString manifest = g_options.buildPath + QStringLiteral("/AndroidManifest.xml"); + QString manifest = g_options.buildPath + "/AndroidManifest.xml"_L1; g_options.package = packageNameFromAndroidManifest(manifest); if (g_options.activity.isEmpty()) g_options.activity = activityFromAndroidManifest(manifest); @@ -502,13 +749,42 @@ int main(int argc, char *argv[]) if (!parseTestArgs()) return 1; + // do not install or run packages while another test is running + testRunnerLock.acquire(); + + const QStringList installArgs = { "install"_L1, "-r"_L1, "-g"_L1, g_options.apkPath }; + g_testInfo.isPackageInstalled.store(execAdbCommand(installArgs, nullptr)); + if (!g_testInfo.isPackageInstalled) + return 1; + + const QString formattedTime = getCurrentTimeString(); + // start the tests - bool res = execCommand(QStringLiteral("%1 %2").arg(g_options.adbCommand, g_options.testArgs), - nullptr, g_options.verbose) && waitToFinish(); - if (res) - res &= pullFiles(); - res &= execCommand(QStringLiteral("%1 uninstall %2").arg(g_options.adbCommand, g_options.package), - nullptr, g_options.verbose); - fflush(stdout); - return res ? 0 : 1; + bool success = execAdbCommand(g_options.amStarttestArgs, nullptr); + + waitForStartedAndFinished(); + + if (success) { + success &= pullFiles(); + if (g_options.showLogcatOutput) + printLogcat(formattedTime); + } + + // If we have a failure, attempt to print both logcat and the crash buffer which + // includes the crash stacktrace that is not included in the default logcat. + if (!success) { + printLogcat(formattedTime); + printLogcatCrashBuffer(formattedTime); + } + + success &= uninstallTestPackage(); + + testRunnerLock.release(); + + if (g_testInfo.isTestRunnerInterrupted.load()) { + qCritical() << "The androidtestrunner was interrupted and the was test cleaned up."; + return 1; + } + + return success ? 0 : 1; } diff --git a/src/tools/bootstrap/CMakeLists.txt b/src/tools/bootstrap/CMakeLists.txt index 393ef7e1c2..93e826fa22 100644 --- a/src/tools/bootstrap/CMakeLists.txt +++ b/src/tools/bootstrap/CMakeLists.txt @@ -1,29 +1,27 @@ -# Generated from bootstrap.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## Bootstrap Module: ##################################################################### -# special case begin # The bootstrap library has a few manual tweaks compared to other # libraries. qt_add_library(Bootstrap STATIC) -# special case end + +qt_internal_add_sync_header_dependencies(Bootstrap Core) + qt_internal_extend_target(Bootstrap SOURCES - ../../corelib/global/qendian.cpp + ../../corelib/global/qassert.cpp ../../corelib/global/qfloat16.cpp - ../../corelib/global/qglobal.cpp ../../corelib/global/qlogging.cpp ../../corelib/global/qmalloc.cpp - ../../corelib/global/qnumeric.cpp - ../../corelib/global/qoperatingsystemversion.cpp - ../../corelib/global/qrandom.cpp + ../../corelib/global/qtenvironmentvariables.cpp ../../corelib/io/qabstractfileengine.cpp ../../corelib/io/qbuffer.cpp ../../corelib/io/qdebug.cpp ../../corelib/io/qdir.cpp - ../../corelib/io/qdiriterator.cpp ../../corelib/io/qfile.cpp ../../corelib/io/qfiledevice.cpp ../../corelib/io/qfileinfo.cpp @@ -32,32 +30,14 @@ qt_internal_extend_target(Bootstrap ../../corelib/io/qfsfileengine.cpp ../../corelib/io/qfsfileengine_iterator.cpp ../../corelib/io/qiodevice.cpp - ../../corelib/io/qipaddress.cpp - ../../corelib/io/qloggingcategory.cpp - ../../corelib/io/qloggingregistry.cpp - ../../corelib/io/qresource.cpp - ../../corelib/io/qsavefile.cpp ../../corelib/io/qstandardpaths.cpp - ../../corelib/io/qtemporarydir.cpp - ../../corelib/io/qtemporaryfile.cpp - ../../corelib/io/qurl.cpp - ../../corelib/io/qurlidna.cpp - ../../corelib/io/qurlquery.cpp - ../../corelib/io/qurlrecode.cpp ../../corelib/kernel/qcoreapplication.cpp - ../../corelib/kernel/qcoreglobaldata.cpp - ../../corelib/kernel/qiterable.cpp - ../../corelib/kernel/qmetacontainer.cpp ../../corelib/kernel/qmetatype.cpp - ../../corelib/kernel/qsharedmemory.cpp ../../corelib/kernel/qsystemerror.cpp - ../../corelib/kernel/qsystemsemaphore.cpp - ../../corelib/kernel/qvariant.cpp ../../corelib/plugin/quuid.cpp ../../corelib/serialization/qcborcommon.cpp ../../corelib/serialization/qcborstreamwriter.cpp ../../corelib/serialization/qcborvalue.cpp - ../../corelib/serialization/qdatastream.cpp ../../corelib/serialization/qjsonarray.cpp ../../corelib/serialization/qjsoncbor.cpp ../../corelib/serialization/qjsondocument.cpp @@ -66,12 +46,10 @@ qt_internal_extend_target(Bootstrap ../../corelib/serialization/qjsonvalue.cpp ../../corelib/serialization/qjsonwriter.cpp ../../corelib/serialization/qtextstream.cpp - ../../corelib/serialization/qxmlstream.cpp - ../../corelib/serialization/qxmlstreamgrammar.cpp - ../../corelib/serialization/qxmlutils.cpp ../../corelib/text/qbytearray.cpp ../../corelib/text/qbytearraylist.cpp ../../corelib/text/qbytearraymatcher.cpp + ../../corelib/text/qlatin1stringmatcher.cpp ../../corelib/text/qlocale.cpp ../../corelib/text/qlocale_tools.cpp ../../corelib/text/qregularexpression.cpp @@ -83,29 +61,24 @@ qt_internal_extend_target(Bootstrap ../../corelib/time/qcalendar.cpp ../../corelib/time/qdatetime.cpp ../../corelib/time/qgregoriancalendar.cpp + ../../corelib/time/qlocaltime.cpp ../../corelib/time/qromancalendar.cpp + ../../corelib/time/qtimezone.cpp ../../corelib/tools/qarraydata.cpp - ../../corelib/tools/qbitarray.cpp ../../corelib/tools/qcommandlineoption.cpp ../../corelib/tools/qcommandlineparser.cpp ../../corelib/tools/qcryptographichash.cpp ../../corelib/tools/qhash.cpp - ../../corelib/tools/qline.cpp - ../../corelib/tools/qpoint.cpp - ../../corelib/tools/qrect.cpp ../../corelib/tools/qringbuffer.cpp - ../../corelib/tools/qsize.cpp - ../../corelib/tools/qversionnumber.cpp - ../../xml/dom/qdom.cpp DEFINES HAVE_CONFIG_H QT_TYPESAFE_FLAGS - PUBLIC_DEFINES # special case - QT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} # special case - QT_VERSION_MINOR=${PROJECT_VERSION_MINOR} # special case - QT_VERSION_PATCH=${PROJECT_VERSION_PATCH} # special case - QT_VERSION_STR="${PROJECT_VERSION}" # special case - QT_USE_QSTRINGBUILDER # special case + PUBLIC_DEFINES + QT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} + QT_VERSION_MINOR=${PROJECT_VERSION_MINOR} + QT_VERSION_PATCH=${PROJECT_VERSION_PATCH} + QT_VERSION_STR="${PROJECT_VERSION}" + QT_USE_QSTRINGBUILDER QT_BOOTSTRAPPED QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII @@ -113,20 +86,14 @@ qt_internal_extend_target(Bootstrap INCLUDE_DIRECTORIES .. ../../3rdparty/tinycbor/src - PUBLIC_INCLUDE_DIRECTORIES # special case - $<TARGET_PROPERTY:Core,INCLUDE_DIRECTORIES> # special case - $<TARGET_PROPERTY:Xml,INCLUDE_DIRECTORIES> # special case - PUBLIC_LIBRARIES # special case - Qt::Platform # special case + PUBLIC_INCLUDE_DIRECTORIES + $<TARGET_PROPERTY:Core,INCLUDE_DIRECTORIES> + ../../corelib/global + PUBLIC_LIBRARIES + Qt::Platform + NO_UNITY_BUILD ) -#### Keys ignored in scope 1:.:.:bootstrap.pro:<TRUE>: -# INSTALLS = "lib" -# MODULE_CONFIG = "gc_binaries" -# MODULE_INCNAME = "QtCore" "QtXml" -# _OPTION = "host_build" -# lib.CONFIG = "dummy_install" - ## Scopes: ##################################################################### @@ -136,26 +103,24 @@ qt_internal_extend_target(Bootstrap CONDITION UNIX ../../corelib/io/qfilesystemiterator_unix.cpp ../../corelib/io/qfsfileengine_unix.cpp ../../corelib/kernel/qcore_unix.cpp - ../../corelib/kernel/qsharedmemory_posix.cpp - ../../corelib/kernel/qsharedmemory_systemv.cpp - ../../corelib/kernel/qsharedmemory_unix.cpp - ../../corelib/kernel/qsystemsemaphore_posix.cpp - ../../corelib/kernel/qsystemsemaphore_systemv.cpp - ../../corelib/kernel/qsystemsemaphore_unix.cpp ) +if(APPLE) + set_source_files_properties(../../corelib/io/qfilesystemengine_unix.cpp PROPERTIES LANGUAGE OBJCXX) + qt_internal_extend_target(Bootstrap CONDITION + PUBLIC_LIBRARIES ${FWUniformTypeIdentifiers} + ) +endif() qt_internal_extend_target(Bootstrap CONDITION WIN32 SOURCES - ../../corelib/global/qoperatingsystemversion_win.cpp ../../corelib/io/qfilesystemengine_win.cpp ../../corelib/io/qfilesystemiterator_win.cpp ../../corelib/io/qfsfileengine_win.cpp ../../corelib/io/qstandardpaths_win.cpp ../../corelib/kernel/qcoreapplication_win.cpp - ../../corelib/kernel/qsharedmemory_win.cpp - ../../corelib/kernel/qsystemsemaphore_win.cpp ../../corelib/kernel/qwinregistry.cpp ../../corelib/plugin/qsystemlibrary.cpp + ../../corelib/kernel/qfunctions_win.cpp PUBLIC_LIBRARIES advapi32 netapi32 @@ -166,10 +131,12 @@ qt_internal_extend_target(Bootstrap CONDITION WIN32 qt_internal_extend_target(Bootstrap CONDITION APPLE SOURCES + ../../corelib/global/qoperatingsystemversion.cpp ../../corelib/global/qoperatingsystemversion_darwin.mm ../../corelib/kernel/qcore_foundation.mm ../../corelib/kernel/qcore_mac.mm ../../corelib/kernel/qcoreapplication_mac.cpp + ../../corelib/tools/qversionnumber.cpp PUBLIC_LIBRARIES ${FWFoundation} ) @@ -197,6 +164,7 @@ qt_internal_extend_target(Bootstrap CONDITION CMAKE_CROSSCOMPILING OR NOT QT_FEA ../../3rdparty/pcre2/src/pcre2.h ../../3rdparty/pcre2/src/pcre2_auto_possess.c ../../3rdparty/pcre2/src/pcre2_chartables.c + ../../3rdparty/pcre2/src/pcre2_chkdint.c ../../3rdparty/pcre2/src/pcre2_compile.c ../../3rdparty/pcre2/src/pcre2_config.c ../../3rdparty/pcre2/src/pcre2_context.c @@ -229,7 +197,7 @@ qt_internal_extend_target(Bootstrap CONDITION CMAKE_CROSSCOMPILING OR NOT QT_FEA DEFINES PCRE2_CODE_UNIT_WIDTH=16 PCRE2_DISABLE_JIT - PUBLIC_INCLUDE_DIRECTORIES # special case + PUBLIC_INCLUDE_DIRECTORIES $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../3rdparty/pcre2/src> ) @@ -243,41 +211,11 @@ qt_internal_extend_target(Bootstrap CONDITION QT_FEATURE_system_pcre2 AND NOT CM WrapPCRE2::WrapPCRE2 ) -qt_internal_extend_target(Bootstrap CONDITION CMAKE_CROSSCOMPILING OR NOT QT_FEATURE_system_zlib - SOURCES - ../../3rdparty/zlib/src/adler32.c - ../../3rdparty/zlib/src/compress.c - ../../3rdparty/zlib/src/crc32.c - ../../3rdparty/zlib/src/deflate.c - ../../3rdparty/zlib/src/gzclose.c - ../../3rdparty/zlib/src/gzlib.c - ../../3rdparty/zlib/src/gzread.c - ../../3rdparty/zlib/src/gzwrite.c - ../../3rdparty/zlib/src/infback.c - ../../3rdparty/zlib/src/inffast.c - ../../3rdparty/zlib/src/inflate.c - ../../3rdparty/zlib/src/inftrees.c - ../../3rdparty/zlib/src/trees.c - ../../3rdparty/zlib/src/uncompr.c - ../../3rdparty/zlib/src/zutil.c - INCLUDE_DIRECTORIES - ../../3rdparty/zlib/src -) - -qt_internal_extend_target(Bootstrap CONDITION QT_FEATURE_system_zlib AND NOT CMAKE_CROSSCOMPILING - LIBRARIES - WrapZLIB::WrapZLIB -) - qt_internal_extend_target(Bootstrap CONDITION MINGW AND WIN32 PUBLIC_LIBRARIES uuid ) -#### Keys ignored in scope 22:.:../../3rdparty/pcre2:../../3rdparty/pcre2/pcre2.pri:QT_FEATURE_intelcet: -# QMAKE_CFLAGS = "$$QMAKE_CFLAGS_SHSTK" - -# special case begin target_link_libraries(Bootstrap PRIVATE PlatformCommonInternal) qt_internal_apply_gc_binaries(Bootstrap PUBLIC) set_target_properties(Bootstrap PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF) @@ -286,6 +224,9 @@ qt_set_msvc_cplusplus_options(Bootstrap PUBLIC) qt_set_common_target_properties(Bootstrap) qt_internal_apply_intel_cet(Bootstrap PUBLIC) +if(WARNINGS_ARE_ERRORS) + qt_internal_set_warnings_are_errors_flags(Bootstrap PRIVATE) +endif() qt_internal_extend_target(Bootstrap CONDITION MSVC DEFINES _CRT_SECURE_NO_WARNINGS @@ -295,4 +236,3 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.20.0" AND QT_FEATURE_debug_and_release set_property(TARGET Bootstrap PROPERTY EXCLUDE_FROM_ALL "$<NOT:$<CONFIG:${QT_MULTI_CONFIG_FIRST_CONFIG}>>") endif() -# special case end diff --git a/src/tools/cmake_automoc_parser/CMakeLists.txt b/src/tools/cmake_automoc_parser/CMakeLists.txt index b5c4aba89c..a58c9c9ff1 100644 --- a/src/tools/cmake_automoc_parser/CMakeLists.txt +++ b/src/tools/cmake_automoc_parser/CMakeLists.txt @@ -1,20 +1,24 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + ##################################################################### ## moc Tool: ##################################################################### qt_get_tool_target_name(target_name cmake_automoc_parser) qt_internal_add_tool(${target_name} - BOOTSTRAP + CORE_LIBRARY Bootstrap + TARGET_DESCRIPTION "Qt CMake AUTOMOC Parser" INSTALL_DIR "${INSTALL_LIBEXECDIR}" - TOOLS_TARGET Core # special case + TOOLS_TARGET Core SOURCES main.cpp DEFINES QT_MOC QT_NO_CAST_FROM_ASCII QT_NO_CAST_FROM_BYTEARRAY - QT_NO_COMPRESS QT_NO_FOREACH INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} ) +qt_internal_return_unless_building_tools() diff --git a/src/tools/cmake_automoc_parser/main.cpp b/src/tools/cmake_automoc_parser/main.cpp index 6d0214638e..de484b184b 100644 --- a/src/tools/cmake_automoc_parser/main.cpp +++ b/src/tools/cmake_automoc_parser/main.cpp @@ -1,35 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QtCore/qglobal.h> #include <cstdio> #include <cstdlib> +#include <limits> #include <qcommandlineoption.h> #include <qcommandlineparser.h> @@ -46,9 +22,12 @@ #include <qset.h> #include <qstring.h> #include <qstack.h> +#include <qdatastream.h> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + using AutoGenHeaderMap = QMap<QString, QString>; using AutoGenSourcesList = QList<QString>; @@ -73,9 +52,9 @@ static bool readAutogenInfoJson(AutoGenHeaderMap &headers, AutoGenSourcesList &s } QJsonObject rootObject = doc.object(); - QJsonValue headersValue = rootObject.value(QLatin1String("HEADERS")); - QJsonValue sourcesValue = rootObject.value(QLatin1String("SOURCES")); - QJsonValue headerExtValue = rootObject.value(QLatin1String("HEADER_EXTENSIONS")); + QJsonValue headersValue = rootObject.value("HEADERS"_L1); + QJsonValue sourcesValue = rootObject.value("SOURCES"_L1); + QJsonValue headerExtValue = rootObject.value("HEADER_EXTENSIONS"_L1); if (!headersValue.isArray() || !sourcesValue.isArray() || !headerExtValue.isArray()) { fprintf(stderr, @@ -152,20 +131,22 @@ static bool readParseCache(ParseCacheMap &entries, const QString &parseCacheFile // .... QTextStream textStream(&file); - const QString mmcKey = QString(QLatin1String(" mmc:")); - const QString miuKey = QString(QLatin1String(" miu:")); - const QString uicKey = QString(QLatin1String(" uic:")); - const QString midKey = QString(QLatin1String(" mid:")); - const QString mdpKey = QString(QLatin1String(" mdp:")); - const QString udpKey = QString(QLatin1String(" udp:")); + const QString mmcKey = QString(" mmc:"_L1); + const QString miuKey = QString(" miu:"_L1); + const QString uicKey = QString(" uic:"_L1); + const QString midKey = QString(" mid:"_L1); + const QString mdpKey = QString(" mdp:"_L1); + const QString udpKey = QString(" udp:"_L1); QString line; bool mmc_key_found = false; while (textStream.readLineInto(&line)) { - if (!line.startsWith(QLatin1Char(' '))) { + if (!line.startsWith(u' ')) { if (!mocEntries.isEmpty() || mmc_key_found || !mocIncludes.isEmpty()) { entries.insert(source, ParseCacheEntry { std::move(mocEntries), std::move(mocIncludes) }); source.clear(); + mocEntries = {}; + mocIncludes = {}; mmc_key_found = false; } source = line; @@ -194,38 +175,46 @@ static bool readParseCache(ParseCacheMap &entries, const QString &parseCacheFile return true; } -static bool readJsonFiles(QList<QString> &entries, const QString &filePath) +static bool writeJsonFiles(const QList<QString> &fileList, const QString &fileListFilePath, + const QString ×tampFilePath) { - - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - fprintf(stderr, "Could not open: %s\n", qPrintable(filePath)); + QFile timestampFile(timestampFilePath); + if (!timestampFile.open(QIODevice::ReadWrite)) { + fprintf(stderr, "Could not open: %s\n", qPrintable(timestampFilePath)); return false; } - QTextStream textStream(&file); - QString line; - while (textStream.readLineInto(&line)) { - entries.push_back(line); + qint64 timestamp = std::numeric_limits<qint64>::min(); + if (timestampFile.size() == sizeof(timestamp)) + timestampFile.read(reinterpret_cast<char *>(×tamp), sizeof(timestamp)); + + // Check if any of the metatype json files produced by automoc is newer than the last file + // processed by cmake_automoc parser + for (const auto &jsonFile : fileList) { + const qint64 jsonFileLastModified = + QFileInfo(jsonFile).lastModified(QTimeZone::UTC).toMSecsSinceEpoch(); + if (jsonFileLastModified > timestamp) { + timestamp = jsonFileLastModified; + } } - file.close(); - return true; -} -static bool writeJsonFiles(const QList<QString> &fileList, const QString &fileListFilePath) -{ - QFile file(fileListFilePath); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - fprintf(stderr, "Could not open: %s\n", qPrintable(fileListFilePath)); - return false; - } + if (timestamp != std::numeric_limits<qint64>::min() || !QFile::exists(fileListFilePath)) { + QFile file(fileListFilePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + fprintf(stderr, "Could not open: %s\n", qPrintable(fileListFilePath)); + return false; + } - QTextStream textStream(&file); - for (const auto &file : fileList) { - textStream << file << Qt::endl; - } + QTextStream textStream(&file); + for (const auto &jsonFile : fileList) { + textStream << jsonFile << Qt::endl; + } + textStream.flush(); - file.close(); + // Update the timestamp according the newest json file timestamp. + timestampFile.resize(0); + timestampFile.write(reinterpret_cast<char *>(×tamp), sizeof(timestamp)); + } return true; } @@ -270,6 +259,13 @@ int main(int argc, char **argv) QStringLiteral("Set this option when using CMake with a multi-config generator")); parser.addOption(isMultiConfigOption); + QCommandLineOption timestampFilePathOption(QStringLiteral("timestamp-file-path")); + timestampFilePathOption.setDescription( + QStringLiteral("The path to a timestamp file that determines whether the output" + " file needs to be updated.")); + timestampFilePathOption.setValueName(QStringLiteral("timestamp file")); + parser.addOption(timestampFilePathOption); + QStringList arguments = QCoreApplication::arguments(); parser.process(arguments); @@ -323,7 +319,7 @@ int main(int argc, char **argv) const QString base = fileInfo.path() + fileInfo.completeBaseName(); // 1a) erase header for (const auto &ext : headerExtList) { - const QString headerPath = base + QLatin1Char('.') + ext; + const QString headerPath = base + u'.' + ext; auto it = autoGenHeaders.find(headerPath); if (it != autoGenHeaders.end()) { autoGenHeaders.erase(it); @@ -331,19 +327,18 @@ int main(int argc, char **argv) } } // Add extra moc files - for (const auto &mocFile : it.value().mocFiles) { - jsonFileList.push_back(dir.filePath(mocFile) + QLatin1String(".json")); - } + for (const auto &mocFile : it.value().mocFiles) + jsonFileList.push_back(dir.filePath(mocFile) + ".json"_L1); // Add main moc files for (const auto &mocFile : it.value().mocIncludes) { - jsonFileList.push_back(dir.filePath(mocFile) + QLatin1String(".json")); + jsonFileList.push_back(dir.filePath(mocFile) + ".json"_L1); // 1b) Locate this header and delete it constexpr int mocKeyLen = 4; // length of "moc_" const QString headerBaseName = QFileInfo(mocFile.right(mocFile.size() - mocKeyLen)).completeBaseName(); bool breakFree = false; for (auto &ext : headerExtList) { - const QString headerSuffix = headerBaseName + QLatin1Char('.') + ext; + const QString headerSuffix = headerBaseName + u'.' + ext; for (auto it = autoGenHeaders.begin(); it != autoGenHeaders.end(); ++it) { if (it.key().endsWith(headerSuffix) && QFileInfo(it.key()).completeBaseName() == headerBaseName) { @@ -369,8 +364,7 @@ int main(int argc, char **argv) const QString pathPrefix = !isMultiConfig ? QStringLiteral("../") : QString(); - const QString jsonPath = - dir.filePath(pathPrefix + mapIt.value() + QLatin1String(".json")); + const QString jsonPath = dir.filePath(pathPrefix + mapIt.value() + ".json"_L1); jsonFileList.push_back(jsonPath); } @@ -378,19 +372,9 @@ int main(int argc, char **argv) jsonFileList.sort(); // Read Previous file list (if any) - const QString fileListFilePath = parser.value(outputFileOption); - QList<QString> previousList; - QFile prev_file(fileListFilePath); - - // Only try to open file if it exists to avoid error messages - if (prev_file.exists()) { - (void)readJsonFiles(previousList, fileListFilePath); - } - - if (previousList != jsonFileList || !QFile(fileListFilePath).exists()) { - if (!writeJsonFiles(jsonFileList, fileListFilePath)) { - return EXIT_FAILURE; - } + if (!writeJsonFiles(jsonFileList, parser.value(outputFileOption), + parser.value(timestampFilePathOption))) { + return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/src/tools/configure.cmake b/src/tools/configure.cmake new file mode 100644 index 0000000000..f813b727ba --- /dev/null +++ b/src/tools/configure.cmake @@ -0,0 +1,35 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_feature("androiddeployqt" PRIVATE + SECTION "Deployment" + LABEL "Android deployment tool" + PURPOSE "The Android deployment tool automates the process of creating Android packages." + CONDITION NOT CMAKE_CROSSCOMPILING AND QT_FEATURE_regularexpression AND QT_FEATURE_settings) + +qt_feature("macdeployqt" PRIVATE + SECTION "Deployment" + LABEL "macOS deployment tool" + PURPOSE "The Mac deployment tool automates the process of creating a deployable application bundle that contains the Qt libraries as private frameworks." + AUTODETECT CMAKE_HOST_APPLE + CONDITION MACOS AND QT_FEATURE_thread) + +qt_feature("windeployqt" PRIVATE + SECTION "Deployment" + LABEL "Windows deployment tool" + PURPOSE "The Windows deployment tool is designed to automate the process of creating a deployable folder containing the Qt-related dependencies (libraries, QML imports, plugins, and translations) required to run the application from that folder. The folder can be easily bundled into an installation package." + AUTODETECT CMAKE_HOST_WIN32 + CONDITION WIN32) + +qt_feature("qmake" PRIVATE + PURPOSE "The qmake tool helps simplify the build process for development projects across different platforms." + CONDITION QT_FEATURE_settings AND QT_FEATURE_alloca AND + (QT_FEATURE_alloca_malloc_h OR NOT WIN32) AND QT_FEATURE_cborstreamwriter AND + QT_FEATURE_datestring AND QT_FEATURE_regularexpression AND QT_FEATURE_temporaryfile) + +qt_configure_add_summary_section(NAME "Core tools") +qt_configure_add_summary_entry(ARGS "androiddeployqt") +qt_configure_add_summary_entry(ARGS "macdeployqt") +qt_configure_add_summary_entry(ARGS "windeployqt") +qt_configure_add_summary_entry(ARGS "qmake") +qt_configure_end_summary_section() diff --git a/src/tools/macdeployqt/CMakeLists.txt b/src/tools/macdeployqt/CMakeLists.txt new file mode 100644 index 0000000000..2e01ec90b4 --- /dev/null +++ b/src/tools/macdeployqt/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_FEATURE_macdeployqt) + return() +endif() +add_subdirectory(macdeployqt) diff --git a/src/tools/macdeployqt/macdeployqt/CMakeLists.txt b/src/tools/macdeployqt/macdeployqt/CMakeLists.txt new file mode 100644 index 0000000000..6cd66adaa7 --- /dev/null +++ b/src/tools/macdeployqt/macdeployqt/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## macdeployqt Tool: +##################################################################### + +qt_get_tool_target_name(target_name macdeployqt) +qt_internal_add_tool(${target_name} + TOOLS_TARGET Core + USER_FACING + INSTALL_VERSIONED_LINK + TARGET_DESCRIPTION "Qt macOS Deployment Tool" + SOURCES + ../shared/shared.cpp + main.cpp + DEFINES + QT_NO_FOREACH + LIBRARIES + ${FWCoreFoundation} +) +qt_internal_return_unless_building_tools() diff --git a/src/tools/macdeployqt/macdeployqt/main.cpp b/src/tools/macdeployqt/macdeployqt/main.cpp new file mode 100644 index 0000000000..8b6c476fa5 --- /dev/null +++ b/src/tools/macdeployqt/macdeployqt/main.cpp @@ -0,0 +1,253 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include <QCoreApplication> +#include <QDir> +#include <QLibraryInfo> + +#include "../shared/shared.h" + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + QString appBundlePath; + if (argc > 1) + appBundlePath = QString::fromLocal8Bit(argv[1]); + + if (argc < 2 || appBundlePath.startsWith("-")) { + qDebug() << "Usage: macdeployqt app-bundle [options]"; + qDebug() << ""; + qDebug() << "Options:"; + qDebug() << " -verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug"; + qDebug() << " -no-plugins : Skip plugin deployment"; + qDebug() << " -dmg : Create a .dmg disk image"; + qDebug() << " -no-strip : Don't run 'strip' on the binaries"; + qDebug() << " -use-debug-libs : Deploy with debug versions of frameworks and plugins (implies -no-strip)"; + qDebug() << " -executable=<path> : Let the given executable use the deployed frameworks too"; + qDebug() << " -qmldir=<path> : Scan for QML imports in the given path"; + qDebug() << " -qmlimport=<path> : Add the given path to the QML module search locations"; + qDebug() << " -always-overwrite : Copy files even if the target file exists"; + qDebug() << " -codesign=<ident> : Run codesign with the given identity on all executables"; + qDebug() << " -hardened-runtime : Enable Hardened Runtime when code signing"; + qDebug() << " -timestamp : Include a secure timestamp when code signing (requires internet connection)"; + qDebug() << " -sign-for-notarization=<ident>: Activate the necessary options for notarization (requires internet connection)"; + qDebug() << " -appstore-compliant : Skip deployment of components that use private API"; + qDebug() << " -libpath=<path> : Add the given path to the library search path"; + qDebug() << " -fs=<filesystem> : Set the filesystem used for the .dmg disk image (defaults to HFS+)"; + qDebug() << ""; + qDebug() << "macdeployqt takes an application bundle as input and makes it"; + qDebug() << "self-contained by copying in the Qt frameworks and plugins that"; + qDebug() << "the application uses."; + qDebug() << ""; + qDebug() << "Plugins related to a framework are copied in with the"; + qDebug() << "framework. The accessibility, image formats, and text codec"; + qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified."; + qDebug() << ""; + qDebug() << "Qt plugins may use private API and will cause the app to be"; + qDebug() << "rejected from the Mac App store. MacDeployQt will print a warning"; + qDebug() << "when known incompatible plugins are deployed. Use -appstore-compliant "; + qDebug() << "to skip these plugins. Currently two SQL plugins are known to"; + qDebug() << "be incompatible: qsqlodbc and qsqlpsql."; + qDebug() << ""; + qDebug() << "See the \"Deploying Applications on OS X\" topic in the"; + qDebug() << "documentation for more information about deployment on OS X."; + + return 1; + } + + appBundlePath = QDir::cleanPath(appBundlePath); + + if (!QDir(appBundlePath).exists()) { + qDebug() << "Error: Could not find app bundle" << appBundlePath; + return 1; + } + + bool plugins = true; + bool dmg = false; + QByteArray filesystem("HFS+"); + bool useDebugLibs = false; + extern bool runStripEnabled; + extern bool alwaysOwerwriteEnabled; + extern QStringList librarySearchPath; + QStringList additionalExecutables; + bool qmldirArgumentUsed = false; + QStringList qmlDirs; + QStringList qmlImportPaths; + extern bool runCodesign; + extern QString codesignIdentiy; + extern bool hardenedRuntime; + extern bool appstoreCompliant; + extern bool deployFramework; + extern bool secureTimestamp; + + for (int i = 2; i < argc; ++i) { + QByteArray argument = QByteArray(argv[i]); + if (argument == QByteArray("-no-plugins")) { + LogDebug() << "Argument found:" << argument; + plugins = false; + } else if (argument == QByteArray("-dmg")) { + LogDebug() << "Argument found:" << argument; + dmg = true; + } else if (argument == QByteArray("-no-strip")) { + LogDebug() << "Argument found:" << argument; + runStripEnabled = false; + } else if (argument == QByteArray("-use-debug-libs")) { + LogDebug() << "Argument found:" << argument; + useDebugLibs = true; + runStripEnabled = false; + } else if (argument.startsWith(QByteArray("-verbose"))) { + LogDebug() << "Argument found:" << argument; + int index = argument.indexOf("="); + bool ok = false; + int number = argument.mid(index+1).toInt(&ok); + if (!ok) + LogError() << "Could not parse verbose level"; + else + logLevel = number; + } else if (argument.startsWith(QByteArray("-executable"))) { + LogDebug() << "Argument found:" << argument; + int index = argument.indexOf('='); + if (index == -1) + LogError() << "Missing executable path"; + else + additionalExecutables << argument.mid(index+1); + } else if (argument.startsWith(QByteArray("-qmldir"))) { + LogDebug() << "Argument found:" << argument; + qmldirArgumentUsed = true; + int index = argument.indexOf('='); + if (index == -1) + LogError() << "Missing qml directory path"; + else + qmlDirs << argument.mid(index+1); + } else if (argument.startsWith(QByteArray("-qmlimport"))) { + LogDebug() << "Argument found:" << argument; + int index = argument.indexOf('='); + if (index == -1) + LogError() << "Missing qml import path"; + else + qmlImportPaths << argument.mid(index+1); + } else if (argument.startsWith(QByteArray("-libpath"))) { + LogDebug() << "Argument found:" << argument; + int index = argument.indexOf('='); + if (index == -1) + LogError() << "Missing library search path"; + else + librarySearchPath << argument.mid(index+1); + } else if (argument == QByteArray("-always-overwrite")) { + LogDebug() << "Argument found:" << argument; + alwaysOwerwriteEnabled = true; + } else if (argument.startsWith(QByteArray("-codesign"))) { + LogDebug() << "Argument found:" << argument; + int index = argument.indexOf("="); + if (index < 0 || index >= argument.size()) { + LogError() << "Missing code signing identity"; + } else { + runCodesign = true; + codesignIdentiy = argument.mid(index+1); + } + } else if (argument.startsWith(QByteArray("-sign-for-notarization"))) { + LogDebug() << "Argument found:" << argument; + int index = argument.indexOf("="); + if (index < 0 || index >= argument.size()) { + LogError() << "Missing code signing identity"; + } else { + runCodesign = true; + hardenedRuntime = true; + secureTimestamp = true; + codesignIdentiy = argument.mid(index+1); + } + } else if (argument.startsWith(QByteArray("-hardened-runtime"))) { + LogDebug() << "Argument found:" << argument; + hardenedRuntime = true; + } else if (argument.startsWith(QByteArray("-timestamp"))) { + LogDebug() << "Argument found:" << argument; + secureTimestamp = true; + } else if (argument == QByteArray("-appstore-compliant")) { + LogDebug() << "Argument found:" << argument; + appstoreCompliant = true; + + // Undocumented option, may not work as intended + } else if (argument == QByteArray("-deploy-framework")) { + LogDebug() << "Argument found:" << argument; + deployFramework = true; + + } else if (argument.startsWith(QByteArray("-fs"))) { + LogDebug() << "Argument found:" << argument; + int index = argument.indexOf('='); + if (index == -1) + LogError() << "Missing filesystem type"; + else + filesystem = argument.mid(index+1); + } else if (argument.startsWith("-")) { + LogError() << "Unknown argument" << argument << "\n"; + return 1; + } + } + + DeploymentInfo deploymentInfo = deployQtFrameworks(appBundlePath, additionalExecutables, useDebugLibs); + + if (deploymentInfo.isDebug) + useDebugLibs = true; + + if (deployFramework && deploymentInfo.isFramework) + fixupFramework(appBundlePath); + + // Convenience: Look for .qml files in the current directory if no -qmldir specified. + if (qmlDirs.isEmpty()) { + QDir dir; + if (!dir.entryList(QStringList() << QStringLiteral("*.qml")).isEmpty()) { + qmlDirs += QStringLiteral("."); + } + } + + if (!qmlDirs.isEmpty()) { + bool ok = deployQmlImports(appBundlePath, deploymentInfo, qmlDirs, qmlImportPaths); + if (!ok && qmldirArgumentUsed) + return 1; // exit if the user explicitly asked for qml import deployment + + // Update deploymentInfo.deployedFrameworks - the QML imports + // may have brought in extra frameworks as dependencies. + deploymentInfo.deployedFrameworks += findAppFrameworkNames(appBundlePath); + deploymentInfo.deployedFrameworks = + QSet<QString>(deploymentInfo.deployedFrameworks.begin(), + deploymentInfo.deployedFrameworks.end()).values(); + } + + // Handle plugins + if (plugins) { + // Set the plugins search directory + deploymentInfo.pluginPath = QLibraryInfo::path(QLibraryInfo::PluginsPath); + + // Sanity checks + if (deploymentInfo.pluginPath.isEmpty()) { + LogError() << "Missing Qt plugins path\n"; + return 1; + } + + if (!QDir(deploymentInfo.pluginPath).exists()) { + LogError() << "Plugins path does not exist" << deploymentInfo.pluginPath << "\n"; + return 1; + } + + // Deploy plugins + Q_ASSERT(!deploymentInfo.pluginPath.isEmpty()); + if (!deploymentInfo.pluginPath.isEmpty()) { + LogNormal(); + deployPlugins(appBundlePath, deploymentInfo, useDebugLibs); + createQtConf(appBundlePath); + } + } + + if (runStripEnabled) + stripAppBinary(appBundlePath); + + if (runCodesign) + codesign(codesignIdentiy, appBundlePath); + + if (dmg) { + LogNormal(); + createDiskImage(appBundlePath, filesystem); + } + + return 0; +} diff --git a/src/tools/macdeployqt/shared/shared.cpp b/src/tools/macdeployqt/shared/shared.cpp new file mode 100644 index 0000000000..6ff269b36d --- /dev/null +++ b/src/tools/macdeployqt/shared/shared.cpp @@ -0,0 +1,1606 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include <QCoreApplication> +#include <QString> +#include <QStringList> +#include <QDebug> +#include <iostream> +#include <utility> +#include <QProcess> +#include <QDir> +#include <QSet> +#include <QVariant> +#include <QVariantMap> +#include <QStack> +#include <QDirIterator> +#include <QLibraryInfo> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonValue> +#include <QRegularExpression> +#include "shared.h" + +#ifdef Q_OS_DARWIN +#include <CoreFoundation/CoreFoundation.h> +#endif + +bool runStripEnabled = true; +bool alwaysOwerwriteEnabled = false; +bool runCodesign = false; +QStringList librarySearchPath; +QString codesignIdentiy; +QString extraEntitlements; +bool hardenedRuntime = false; +bool secureTimestamp = false; +bool appstoreCompliant = false; +int logLevel = 1; +bool deployFramework = false; + +using std::cout; +using std::endl; +using namespace Qt::StringLiterals; + +bool operator==(const FrameworkInfo &a, const FrameworkInfo &b) +{ + return ((a.frameworkPath == b.frameworkPath) && (a.binaryPath == b.binaryPath)); +} + +QDebug operator<<(QDebug debug, const FrameworkInfo &info) +{ + debug << "Framework name" << info.frameworkName << "\n"; + debug << "Framework directory" << info.frameworkDirectory << "\n"; + debug << "Framework path" << info.frameworkPath << "\n"; + debug << "Binary directory" << info.binaryDirectory << "\n"; + debug << "Binary name" << info.binaryName << "\n"; + debug << "Binary path" << info.binaryPath << "\n"; + debug << "Version" << info.version << "\n"; + debug << "Install name" << info.installName << "\n"; + debug << "Deployed install name" << info.deployedInstallName << "\n"; + debug << "Source file Path" << info.sourceFilePath << "\n"; + debug << "Framework Destination Directory (relative to bundle)" << info.frameworkDestinationDirectory << "\n"; + debug << "Binary Destination Directory (relative to bundle)" << info.binaryDestinationDirectory << "\n"; + + return debug; +} + +const QString bundleFrameworkDirectory = "Contents/Frameworks"; + +inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info) +{ + debug << "Application bundle path" << info.path << "\n"; + debug << "Binary path" << info.binaryPath << "\n"; + debug << "Additional libraries" << info.libraryPaths << "\n"; + return debug; +} + +bool copyFilePrintStatus(const QString &from, const QString &to) +{ + if (QFile::exists(to)) { + if (alwaysOwerwriteEnabled) { + QFile(to).remove(); + } else { + qDebug() << "File exists, skip copy:" << to; + return false; + } + } + + if (QFile::copy(from, to)) { + QFile dest(to); + dest.setPermissions(dest.permissions() | QFile::WriteOwner | QFile::WriteUser); + LogNormal() << " copied:" << from; + LogNormal() << " to" << to; + + // The source file might not have write permissions set. Set the + // write permission on the target file to make sure we can use + // install_name_tool on it later. + QFile toFile(to); + if (toFile.permissions() & QFile::WriteOwner) + return true; + + if (!toFile.setPermissions(toFile.permissions() | QFile::WriteOwner)) { + LogError() << "Failed to set u+w permissions on target file: " << to; + return false; + } + + return true; + } else { + LogError() << "file copy failed from" << from; + LogError() << " to" << to; + return false; + } +} + +bool linkFilePrintStatus(const QString &file, const QString &link) +{ + if (QFile::exists(link)) { + if (QFile(link).symLinkTarget().isEmpty()) + LogError() << link << "exists but it's a file."; + else + LogNormal() << "Symlink exists, skipping:" << link; + return false; + } else if (QFile::link(file, link)) { + LogNormal() << " symlink" << link; + LogNormal() << " points to" << file; + return true; + } else { + LogError() << "failed to symlink" << link; + LogError() << " to" << file; + return false; + } +} + +void patch_debugInInfoPlist(const QString &infoPlistPath) +{ + // Older versions of qmake may have the "_debug" binary as + // the value for CFBundleExecutable. Remove it. + QFile infoPlist(infoPlistPath); + infoPlist.open(QIODevice::ReadOnly); + QByteArray contents = infoPlist.readAll(); + infoPlist.close(); + infoPlist.open(QIODevice::WriteOnly | QIODevice::Truncate); + contents.replace("_debug", ""); // surely there are no legit uses of "_debug" in an Info.plist + infoPlist.write(contents); +} + +OtoolInfo findDependencyInfo(const QString &binaryPath) +{ + OtoolInfo info; + info.binaryPath = binaryPath; + + LogDebug() << "Using otool:"; + LogDebug() << " inspecting" << binaryPath; + QProcess otool; + otool.start("otool", QStringList() << "-L" << binaryPath); + otool.waitForFinished(-1); + + if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) { + LogError() << otool.readAllStandardError(); + return info; + } + + static const QRegularExpression regexp(QStringLiteral( + "^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), " + "current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\)$")); + + QString output = otool.readAllStandardOutput(); + QStringList outputLines = output.split("\n", Qt::SkipEmptyParts); + if (outputLines.size() < 2) { + LogError() << "Could not parse otool output:" << output; + return info; + } + + outputLines.removeFirst(); // remove line containing the binary path + if (binaryPath.contains(".framework/") || binaryPath.endsWith(".dylib")) { + const auto match = regexp.match(outputLines.constFirst()); + if (match.hasMatch()) { + QString installname = match.captured(1); + if (QFileInfo(binaryPath).fileName() == QFileInfo(installname).fileName()) { + info.installName = installname; + info.compatibilityVersion = QVersionNumber::fromString(match.captured(2)); + info.currentVersion = QVersionNumber::fromString(match.captured(3)); + outputLines.removeFirst(); + } else { + info.installName = binaryPath; + } + } else { + LogDebug() << "Could not parse otool output line:" << outputLines.constFirst(); + outputLines.removeFirst(); + } + } + + for (const QString &outputLine : outputLines) { + const auto match = regexp.match(outputLine); + if (match.hasMatch()) { + if (match.captured(1) == info.installName) + continue; // Another arch reference to the same binary + DylibInfo dylib; + dylib.binaryPath = match.captured(1); + dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2)); + dylib.currentVersion = QVersionNumber::fromString(match.captured(3)); + info.dependencies << dylib; + } else { + LogDebug() << "Could not parse otool output line:" << outputLine; + } + } + + return info; +} + +FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs) +{ + FrameworkInfo info; + QString trimmed = line.trimmed(); + + if (trimmed.isEmpty()) + return info; + + // Don't deploy system libraries. + if (trimmed.startsWith("/System/Library/") || + (trimmed.startsWith("/usr/lib/") && trimmed.contains("libQt") == false) // exception for libQtuitools and libQtlucene + || trimmed.startsWith("@executable_path") || trimmed.startsWith("@loader_path")) + return info; + + // Resolve rpath relative libraries. + if (trimmed.startsWith("@rpath/")) { + QString rpathRelativePath = trimmed.mid(QStringLiteral("@rpath/").length()); + bool foundInsideBundle = false; + for (const QString &rpath : std::as_const(rpaths)) { + QString path = QDir::cleanPath(rpath + "/" + rpathRelativePath); + // Skip paths already inside the bundle. + if (!appBundlePath.isEmpty()) { + if (QDir::isAbsolutePath(appBundlePath)) { + if (path.startsWith(QDir::cleanPath(appBundlePath) + "/")) { + foundInsideBundle = true; + continue; + } + } else { + if (path.startsWith(QDir::cleanPath(QDir::currentPath() + "/" + appBundlePath) + "/")) { + foundInsideBundle = true; + continue; + } + } + } + // Try again with substituted rpath. + FrameworkInfo resolvedInfo = parseOtoolLibraryLine(path, appBundlePath, rpaths, useDebugLibs); + if (!resolvedInfo.frameworkName.isEmpty() && QFile::exists(resolvedInfo.frameworkPath)) { + resolvedInfo.rpathUsed = rpath; + resolvedInfo.installName = trimmed; + return resolvedInfo; + } + } + if (!rpaths.isEmpty() && !foundInsideBundle) { + LogError() << "Cannot resolve rpath" << trimmed; + LogError() << " using" << rpaths; + } + return info; + } + + enum State {QtPath, FrameworkName, DylibName, Version, FrameworkBinary, End}; + State state = QtPath; + int part = 0; + QString name; + QString qtPath; + QString suffix = useDebugLibs ? "_debug" : ""; + + // Split the line into [Qt-path]/lib/qt[Module].framework/Versions/[Version]/ + QStringList parts = trimmed.split("/"); + while (part < parts.count()) { + const QString currentPart = parts.at(part).simplified(); + ++part; + if (currentPart == "") + continue; + + if (state == QtPath) { + // Check for library name part + if (part < parts.count() && parts.at(part).contains(".dylib")) { + info.frameworkDirectory += "/" + QString(qtPath + currentPart + "/").simplified(); + state = DylibName; + continue; + } else if (part < parts.count() && parts.at(part).endsWith(".framework")) { + info.frameworkDirectory += "/" + QString(qtPath + "lib/").simplified(); + state = FrameworkName; + continue; + } else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package. + QStringList partsCopy = parts; + partsCopy.removeLast(); + for (QString &path : librarySearchPath) { + if (!path.endsWith("/")) + path += '/'; + QString nameInPath = path + parts.join(u'/'); + if (QFile::exists(nameInPath)) { + info.frameworkDirectory = path + partsCopy.join(u'/'); + break; + } + } + if (currentPart.contains(".framework")) { + if (info.frameworkDirectory.isEmpty()) + info.frameworkDirectory = "/Library/Frameworks/" + partsCopy.join(u'/'); + if (!info.frameworkDirectory.endsWith("/")) + info.frameworkDirectory += "/"; + state = FrameworkName; + --part; + continue; + } else if (currentPart.contains(".dylib")) { + if (info.frameworkDirectory.isEmpty()) + info.frameworkDirectory = "/usr/lib/" + partsCopy.join(u'/'); + if (!info.frameworkDirectory.endsWith("/")) + info.frameworkDirectory += "/"; + state = DylibName; + --part; + continue; + } + } + qtPath += (currentPart + "/"); + + } if (state == FrameworkName) { + // remove ".framework" + name = currentPart; + name.chop(QString(".framework").length()); + info.isDylib = false; + info.frameworkName = currentPart; + state = Version; + ++part; + continue; + } if (state == DylibName) { + name = currentPart; + info.isDylib = true; + info.frameworkName = name; + info.binaryName = name.contains(suffix) ? name : name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.')); + info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName; + info.frameworkPath = info.frameworkDirectory + info.binaryName; + info.sourceFilePath = info.frameworkPath; + info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/"; + info.binaryDestinationDirectory = info.frameworkDestinationDirectory; + info.binaryDirectory = info.frameworkDirectory; + info.binaryPath = info.frameworkPath; + state = End; + ++part; + continue; + } else if (state == Version) { + info.version = currentPart; + info.binaryDirectory = "Versions/" + info.version; + info.frameworkPath = info.frameworkDirectory + info.frameworkName; + info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName; + info.binaryDestinationDirectory = info.frameworkDestinationDirectory + "/" + info.binaryDirectory; + state = FrameworkBinary; + } else if (state == FrameworkBinary) { + info.binaryName = currentPart.contains(suffix) ? currentPart : currentPart + suffix; + info.binaryPath = "/" + info.binaryDirectory + "/" + info.binaryName; + info.deployedInstallName = "@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath; + info.sourceFilePath = info.frameworkPath + info.binaryPath; + state = End; + } else if (state == End) { + break; + } + } + + if (!info.sourceFilePath.isEmpty() && QFile::exists(info.sourceFilePath)) { + info.installName = findDependencyInfo(info.sourceFilePath).installName; + if (info.installName.startsWith("@rpath/")) + info.deployedInstallName = info.installName; + } + + return info; +} + +QString findAppBinary(const QString &appBundlePath) +{ + QString binaryPath; + +#ifdef Q_OS_DARWIN + CFStringRef bundlePath = appBundlePath.toCFString(); + CFURLRef bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath, + kCFURLPOSIXPathStyle, true); + CFRelease(bundlePath); + CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, bundleURL); + if (bundle) { + CFURLRef executableURL = CFBundleCopyExecutableURL(bundle); + if (executableURL) { + CFURLRef absoluteExecutableURL = CFURLCopyAbsoluteURL(executableURL); + if (absoluteExecutableURL) { + CFStringRef executablePath = CFURLCopyFileSystemPath(absoluteExecutableURL, + kCFURLPOSIXPathStyle); + if (executablePath) { + binaryPath = QString::fromCFString(executablePath); + CFRelease(executablePath); + } + CFRelease(absoluteExecutableURL); + } + CFRelease(executableURL); + } + CFRelease(bundle); + } + CFRelease(bundleURL); +#endif + + if (QFile::exists(binaryPath)) + return binaryPath; + LogError() << "Could not find bundle binary for" << appBundlePath; + return QString(); +} + +QStringList findAppFrameworkNames(const QString &appBundlePath) +{ + QStringList frameworks; + + // populate the frameworks list with QtFoo.framework etc, + // as found in /Contents/Frameworks/ + QString searchPath = appBundlePath + "/Contents/Frameworks/"; + QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"), + QDir::Dirs | QDir::NoSymLinks); + while (iter.hasNext()) { + iter.next(); + frameworks << iter.fileInfo().fileName(); + } + + return frameworks; +} + +QStringList findAppFrameworkPaths(const QString &appBundlePath) +{ + QStringList frameworks; + QString searchPath = appBundlePath + "/Contents/Frameworks/"; + QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"), + QDir::Dirs | QDir::NoSymLinks); + while (iter.hasNext()) { + iter.next(); + frameworks << iter.fileInfo().filePath(); + } + + return frameworks; +} + +QStringList findAppLibraries(const QString &appBundlePath) +{ + QStringList result; + // dylibs + QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*.dylib") << QString::fromLatin1("*.so"), + QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories); + while (iter.hasNext()) { + iter.next(); + result << iter.fileInfo().filePath(); + } + return result; +} + +QStringList findAppBundleFiles(const QString &appBundlePath, bool absolutePath = false) +{ + QStringList result; + + QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*"), + QDir::Files, QDirIterator::Subdirectories); + + while (iter.hasNext()) { + iter.next(); + if (iter.fileInfo().isSymLink()) + continue; + result << (absolutePath ? iter.fileInfo().absoluteFilePath() : iter.fileInfo().filePath()); + } + + return result; +} + +QString findEntitlementsFile(const QString& path) +{ + QDirIterator iter(path, QStringList() << QString::fromLatin1("*.entitlements"), + QDir::Files, QDirIterator::Subdirectories); + + while (iter.hasNext()) { + iter.next(); + if (iter.fileInfo().isSymLink()) + continue; + + //return the first entitlements file - only one is used for signing anyway + return iter.fileInfo().absoluteFilePath(); + } + + return QString(); +} + +QList<FrameworkInfo> getQtFrameworks(const QList<DylibInfo> &dependencies, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs) +{ + QList<FrameworkInfo> libraries; + for (const DylibInfo &dylibInfo : dependencies) { + FrameworkInfo info = parseOtoolLibraryLine(dylibInfo.binaryPath, appBundlePath, rpaths, useDebugLibs); + if (info.frameworkName.isEmpty() == false) { + LogDebug() << "Adding framework:"; + LogDebug() << info; + libraries.append(info); + } + } + return libraries; +} + +QString resolveDyldPrefix(const QString &path, const QString &loaderPath, const QString &executablePath) +{ + if (path.startsWith("@")) { + if (path.startsWith(QStringLiteral("@executable_path/"))) { + // path relative to bundle executable dir + if (QDir::isAbsolutePath(executablePath)) { + return QDir::cleanPath(QFileInfo(executablePath).path() + path.mid(QStringLiteral("@executable_path").length())); + } else { + return QDir::cleanPath(QDir::currentPath() + "/" + + QFileInfo(executablePath).path() + path.mid(QStringLiteral("@executable_path").length())); + } + } else if (path.startsWith(QStringLiteral("@loader_path"))) { + // path relative to loader dir + if (QDir::isAbsolutePath(loaderPath)) { + return QDir::cleanPath(QFileInfo(loaderPath).path() + path.mid(QStringLiteral("@loader_path").length())); + } else { + return QDir::cleanPath(QDir::currentPath() + "/" + + QFileInfo(loaderPath).path() + path.mid(QStringLiteral("@loader_path").length())); + } + } else { + LogError() << "Unexpected prefix" << path; + } + } + return path; +} + +QList<QString> getBinaryRPaths(const QString &path, bool resolve = true, QString executablePath = QString()) +{ + QList<QString> rpaths; + + QProcess otool; + otool.start("otool", QStringList() << "-l" << path); + otool.waitForFinished(); + + if (otool.exitCode() != 0) { + LogError() << otool.readAllStandardError(); + } + + if (resolve && executablePath.isEmpty()) { + executablePath = path; + } + + QString output = otool.readAllStandardOutput(); + QStringList outputLines = output.split("\n"); + + for (auto i = outputLines.cbegin(), end = outputLines.cend(); i != end; ++i) { + if (i->contains("cmd LC_RPATH") && ++i != end && + i->contains("cmdsize") && ++i != end) { + const QString &rpathCmd = *i; + int pathStart = rpathCmd.indexOf("path "); + int pathEnd = rpathCmd.indexOf(" ("); + if (pathStart >= 0 && pathEnd >= 0 && pathStart < pathEnd) { + QString rpath = rpathCmd.mid(pathStart + 5, pathEnd - pathStart - 5); + if (resolve) { + rpaths << resolveDyldPrefix(rpath, path, executablePath); + } else { + rpaths << rpath; + } + } + } + } + + return rpaths; +} + +QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs) +{ + const OtoolInfo info = findDependencyInfo(path); + QList<QString> allRPaths = rpaths + getBinaryRPaths(path); + allRPaths.removeDuplicates(); + return getQtFrameworks(info.dependencies, appBundlePath, allRPaths, useDebugLibs); +} + +QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs) +{ + QList<FrameworkInfo> result; + QSet<QString> existing; + for (const QString &path : paths) { + for (const FrameworkInfo &info : getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) { + if (!existing.contains(info.frameworkPath)) { // avoid duplicates + existing.insert(info.frameworkPath); + result << info; + } + } + } + return result; +} + +QStringList getBinaryDependencies(const QString executablePath, + const QString &path, + const QList<QString> &additionalBinariesContainingRpaths) +{ + QStringList binaries; + + const auto dependencies = findDependencyInfo(path).dependencies; + + bool rpathsLoaded = false; + QList<QString> rpaths; + + // return bundle-local dependencies. (those starting with @executable_path) + for (const DylibInfo &info : dependencies) { + QString trimmedLine = info.binaryPath; + if (trimmedLine.startsWith("@executable_path/")) { + QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length())); + if (binary != path) + binaries.append(binary); + } else if (trimmedLine.startsWith("@loader_path/")) { + QString binary = QDir::cleanPath(QFileInfo(path).path() + "/" + trimmedLine.mid(QStringLiteral("@loader_path/").length())); + if (binary != path) + binaries.append(binary); + } else if (trimmedLine.startsWith("@rpath/")) { + if (!rpathsLoaded) { + rpaths = getBinaryRPaths(path, true, executablePath); + for (const QString &binaryPath : additionalBinariesContainingRpaths) + rpaths += getBinaryRPaths(binaryPath, true); + rpaths.removeDuplicates(); + rpathsLoaded = true; + } + bool resolved = false; + for (const QString &rpath : std::as_const(rpaths)) { + QString binary = QDir::cleanPath(rpath + "/" + trimmedLine.mid(QStringLiteral("@rpath/").length())); + LogDebug() << "Checking for" << binary; + if (QFile::exists(binary)) { + binaries.append(binary); + resolved = true; + break; + } + } + if (!resolved && !rpaths.isEmpty()) { + LogError() << "Cannot resolve rpath" << trimmedLine; + LogError() << " using" << rpaths; + } + } + } + + return binaries; +} + +// copies everything _inside_ sourcePath to destinationPath +bool recursiveCopy(const QString &sourcePath, const QString &destinationPath, + const QRegularExpression &ignoreRegExp = QRegularExpression()) +{ + const QDir sourceDir(sourcePath); + if (!sourceDir.exists()) + return false; + QDir().mkpath(destinationPath); + + LogNormal() << "copy:" << sourcePath << destinationPath; + + const QStringList files = sourceDir.entryList(QStringList() << "*", QDir::Files | QDir::NoDotAndDotDot); + const bool hasValidRegExp = ignoreRegExp.isValid() && ignoreRegExp.pattern().length() > 0; + for (const QString &file : files) { + if (hasValidRegExp && ignoreRegExp.match(file).hasMatch()) + continue; + const QString fileSourcePath = sourcePath + "/" + file; + const QString fileDestinationPath = destinationPath + "/" + file; + copyFilePrintStatus(fileSourcePath, fileDestinationPath); + } + + const QStringList subdirs = sourceDir.entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &dir : subdirs) { + recursiveCopy(sourcePath + "/" + dir, destinationPath + "/" + dir); + } + return true; +} + +void recursiveCopyAndDeploy(const QString &appBundlePath, const QList<QString> &rpaths, const QString &sourcePath, const QString &destinationPath) +{ + QDir().mkpath(destinationPath); + + LogNormal() << "copy:" << sourcePath << destinationPath; + const bool isDwarfPath = sourcePath.endsWith("DWARF"); + + const QDir sourceDir(sourcePath); + + const QStringList files = sourceDir.entryList(QStringList() << QStringLiteral("*"), QDir::Files | QDir::NoDotAndDotDot); + for (const QString &file : files) { + const QString fileSourcePath = sourcePath + u'/' + file; + + if (file.endsWith("_debug.dylib")) { + continue; // Skip debug versions + } else if (!isDwarfPath && file.endsWith(QStringLiteral(".dylib"))) { + // App store code signing rules forbids code binaries in Contents/Resources/, + // which poses a problem for deploying mixed .qml/.dylib Qt Quick imports. + // Solve this by placing the dylibs in Contents/PlugIns/quick, and then + // creting a symlink to there from the Qt Quick import in Contents/Resources/. + // + // Example: + // MyApp.app/Contents/Resources/qml/QtQuick/Controls/libqtquickcontrolsplugin.dylib -> + // ../../../../PlugIns/quick/libqtquickcontrolsplugin.dylib + // + + // The .dylib destination path: + QString fileDestinationDir = appBundlePath + QStringLiteral("/Contents/PlugIns/quick/"); + QDir().mkpath(fileDestinationDir); + QString fileDestinationPath = fileDestinationDir + file; + + // The .dylib symlink destination path: + QString linkDestinationPath = destinationPath + u'/' + file; + + // The (relative) link; with a correct number of "../"'s. + QString linkPath = QStringLiteral("PlugIns/quick/") + file; + int cdupCount = linkDestinationPath.count(QStringLiteral("/")) - appBundlePath.count(QStringLiteral("/")); + for (int i = 0; i < cdupCount - 2; ++i) + linkPath.prepend("../"); + + if (copyFilePrintStatus(fileSourcePath, fileDestinationPath)) { + linkFilePrintStatus(linkPath, linkDestinationPath); + + runStrip(fileDestinationPath); + bool useDebugLibs = false; + bool useLoaderPath = false; + QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, appBundlePath, rpaths, useDebugLibs); + deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath); + } + } else { + QString fileDestinationPath = destinationPath + u'/' + file; + copyFilePrintStatus(fileSourcePath, fileDestinationPath); + } + } + + const QStringList subdirs = sourceDir.entryList(QStringList() << QStringLiteral("*"), QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &dir : subdirs) { + recursiveCopyAndDeploy(appBundlePath, rpaths, sourcePath + u'/' + dir, destinationPath + u'/' + dir); + } +} + +QString copyDylib(const FrameworkInfo &framework, const QString path) +{ + if (!QFile::exists(framework.sourceFilePath)) { + LogError() << "no file at" << framework.sourceFilePath; + return QString(); + } + + // Construct destination paths. The full path typically looks like + // MyApp.app/Contents/Frameworks/libfoo.dylib + QString dylibDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory; + QString dylibDestinationBinaryPath = dylibDestinationDirectory + u'/' + framework.binaryName; + + // Create destination directory + if (!QDir().mkpath(dylibDestinationDirectory)) { + LogError() << "could not create destination directory" << dylibDestinationDirectory; + return QString(); + } + + // Return if the dylib has already been deployed + if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled) + return dylibDestinationBinaryPath; + + // Copy dylib binary + copyFilePrintStatus(framework.sourceFilePath, dylibDestinationBinaryPath); + return dylibDestinationBinaryPath; +} + +QString copyFramework(const FrameworkInfo &framework, const QString path) +{ + if (!QFile::exists(framework.sourceFilePath)) { + LogError() << "no file at" << framework.sourceFilePath; + return QString(); + } + + // Construct destination paths. The full path typically looks like + // MyApp.app/Contents/Frameworks/Foo.framework/Versions/5/QtFoo + QString frameworkDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory; + QString frameworkBinaryDestinationDirectory = frameworkDestinationDirectory + u'/' + framework.binaryDirectory; + QString frameworkDestinationBinaryPath = frameworkBinaryDestinationDirectory + u'/' + framework.binaryName; + + // Return if the framework has aleardy been deployed + if (QDir(frameworkDestinationDirectory).exists() && !alwaysOwerwriteEnabled) + return QString(); + + // Create destination directory + if (!QDir().mkpath(frameworkBinaryDestinationDirectory)) { + LogError() << "could not create destination directory" << frameworkBinaryDestinationDirectory; + return QString(); + } + + // Now copy the framework. Some parts should be left out (headers/, .prl files). + // Some parts should be included (Resources/, symlink structure). We want this + // function to make as few assumptions about the framework as possible while at + // the same time producing a codesign-compatible framework. + + // Copy framework binary + copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath); + + // Copy Resources/, Libraries/ and Helpers/ + const QString resourcesSourcePath = framework.frameworkPath + "/Resources"; + const QString resourcesDestinationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Resources"; + // Ignore *.prl files that are in the Resources directory + recursiveCopy(resourcesSourcePath, resourcesDestinationPath, + QRegularExpression("\\A(?:[^/]*\\.prl)\\z")); + const QString librariesSourcePath = framework.frameworkPath + "/Libraries"; + const QString librariesDestinationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Libraries"; + bool createdLibraries = recursiveCopy(librariesSourcePath, librariesDestinationPath); + const QString helpersSourcePath = framework.frameworkPath + "/Helpers"; + const QString helpersDestinationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Helpers"; + bool createdHelpers = recursiveCopy(helpersSourcePath, helpersDestinationPath); + + // Create symlink structure. Links at the framework root point to Versions/Current/ + // which again points to the actual version: + // QtFoo.framework/QtFoo -> Versions/Current/QtFoo + // QtFoo.framework/Resources -> Versions/Current/Resources + // QtFoo.framework/Versions/Current -> 5 + linkFilePrintStatus("Versions/Current/" + framework.binaryName, frameworkDestinationDirectory + "/" + framework.binaryName); + linkFilePrintStatus("Versions/Current/Resources", frameworkDestinationDirectory + "/Resources"); + if (createdLibraries) + linkFilePrintStatus("Versions/Current/Libraries", frameworkDestinationDirectory + "/Libraries"); + if (createdHelpers) + linkFilePrintStatus("Versions/Current/Helpers", frameworkDestinationDirectory + "/Helpers"); + linkFilePrintStatus(framework.version, frameworkDestinationDirectory + "/Versions/Current"); + + // Correct Info.plist location for frameworks produced by older versions of qmake + // Contents/Info.plist should be Versions/5/Resources/Info.plist + const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist"; + const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist"; + if (QFile::exists(legacyInfoPlistPath)) { + copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath); + patch_debugInInfoPlist(correctInfoPlistPath); + } + return frameworkDestinationBinaryPath; +} + +void runInstallNameTool(QStringList options) +{ + QProcess installNametool; + installNametool.start("install_name_tool", options); + installNametool.waitForFinished(); + if (installNametool.exitCode() != 0) { + LogError() << installNametool.readAllStandardError(); + LogError() << installNametool.readAllStandardOutput(); + } +} + +void changeIdentification(const QString &id, const QString &binaryPath) +{ + LogDebug() << "Using install_name_tool:"; + LogDebug() << " change identification in" << binaryPath; + LogDebug() << " to" << id; + runInstallNameTool(QStringList() << "-id" << id << binaryPath); +} + +void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework, const QStringList &binaryPaths, bool useLoaderPath) +{ + const QString absBundlePath = QFileInfo(bundlePath).absoluteFilePath(); + for (const QString &binary : binaryPaths) { + QString deployedInstallName; + if (useLoaderPath) { + deployedInstallName = "@loader_path/"_L1 + + QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + u'/' + framework.binaryDestinationDirectory + u'/' + framework.binaryName); + } else { + deployedInstallName = framework.deployedInstallName; + } + changeInstallName(framework.installName, deployedInstallName, binary); + // Workaround for the case when the library ID name is a symlink, while the dependencies + // specified using the canonical path to the library (QTBUG-56814) + QFileInfo fileInfo= QFileInfo(framework.installName); + QString canonicalInstallName = fileInfo.canonicalFilePath(); + if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) { + changeInstallName(canonicalInstallName, deployedInstallName, binary); + // some libraries' inner dependencies (such as ffmpeg, nettle) use symbol link (QTBUG-100093) + QString innerDependency = fileInfo.canonicalPath() + "/" + fileInfo.fileName(); + if (innerDependency != canonicalInstallName && innerDependency != framework.installName) { + changeInstallName(innerDependency, deployedInstallName, binary); + } + } + } +} + +void addRPath(const QString &rpath, const QString &binaryPath) +{ + runInstallNameTool(QStringList() << "-add_rpath" << rpath << binaryPath); +} + +void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QString &binaryPath, bool useLoaderPath) +{ + const QString absFrameworksPath = QFileInfo(bundlePath).absoluteFilePath() + + "/Contents/Frameworks"_L1; + const QString relativeFrameworkPath = QFileInfo(binaryPath).absoluteDir().relativeFilePath(absFrameworksPath); + const QString loaderPathToFrameworks = "@loader_path/"_L1 + relativeFrameworkPath; + bool rpathToFrameworksFound = false; + QStringList args; + QList<QString> binaryRPaths = getBinaryRPaths(binaryPath, false); + for (const QString &rpath : std::as_const(binaryRPaths)) { + if (rpath == "@executable_path/../Frameworks" || + rpath == loaderPathToFrameworks) { + rpathToFrameworksFound = true; + continue; + } + if (rpaths.contains(resolveDyldPrefix(rpath, binaryPath, binaryPath))) { + if (!args.contains(rpath)) + args << "-delete_rpath" << rpath; + } + } + if (!args.length()) { + return; + } + if (!rpathToFrameworksFound) { + if (!useLoaderPath) { + args << "-add_rpath" << "@executable_path/../Frameworks"; + } else { + args << "-add_rpath" << loaderPathToFrameworks; + } + } + LogDebug() << "Using install_name_tool:"; + LogDebug() << " change rpaths in" << binaryPath; + LogDebug() << " using" << args; + runInstallNameTool(QStringList() << args << binaryPath); +} + +void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QStringList &binaryPaths, bool useLoaderPath) +{ + for (const QString &binary : binaryPaths) { + deployRPaths(bundlePath, rpaths, binary, useLoaderPath); + } +} + +void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath) +{ + LogDebug() << "Using install_name_tool:"; + LogDebug() << " in" << binaryPath; + LogDebug() << " change reference" << oldName; + LogDebug() << " to" << newName; + runInstallNameTool(QStringList() << "-change" << oldName << newName << binaryPath); +} + +void runStrip(const QString &binaryPath) +{ + if (runStripEnabled == false) + return; + + LogDebug() << "Using strip:"; + LogDebug() << " stripped" << binaryPath; + QProcess strip; + strip.start("strip", QStringList() << "-x" << binaryPath); + strip.waitForFinished(); + if (strip.exitCode() != 0) { + LogError() << strip.readAllStandardError(); + LogError() << strip.readAllStandardOutput(); + } +} + +void stripAppBinary(const QString &bundlePath) +{ + runStrip(findAppBinary(bundlePath)); +} + +bool DeploymentInfo::containsModule(const QString &module, const QString &libInFix) const +{ + // Check for framework first + if (deployedFrameworks.contains("Qt"_L1 + module + libInFix + ".framework"_L1)) + return true; + // Check for dylib + const QRegularExpression dylibRegExp("libQt[0-9]+"_L1 + + module + libInFix + + (isDebug ? "_debug" : "") + + ".[0-9]+.dylib"_L1); + return deployedFrameworks.filter(dylibRegExp).size() > 0; +} + +/* + Deploys the listed frameworks into an app bundle. + The frameworks are searched for dependencies, which are also deployed. + (deploying Qt3Support will also deploy QtNetwork and QtSql for example.) + Returns a DeploymentInfo structure containing the Qt path used and a + a list of actually deployed frameworks. +*/ +DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks, + const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, + bool useLoaderPath) +{ + LogNormal(); + LogNormal() << "Deploying Qt frameworks found inside:" << binaryPaths; + QStringList copiedFrameworks; + DeploymentInfo deploymentInfo; + deploymentInfo.useLoaderPath = useLoaderPath; + deploymentInfo.isFramework = bundlePath.contains(".framework"); + deploymentInfo.isDebug = false; + QList<QString> rpathsUsed; + + while (frameworks.isEmpty() == false) { + const FrameworkInfo framework = frameworks.takeFirst(); + copiedFrameworks.append(framework.frameworkName); + + // If a single dependency has the _debug suffix, we treat that as + // the whole deployment being a debug deployment, including deploying + // the debug version of plugins. + if (framework.isDebugLibrary()) + deploymentInfo.isDebug = true; + + if (deploymentInfo.qtPath.isNull()) + deploymentInfo.qtPath = QLibraryInfo::path(QLibraryInfo::PrefixPath); + + if (framework.frameworkDirectory.startsWith(bundlePath)) { + LogError() << framework.frameworkName << "already deployed, skipping."; + continue; + } + + if (!framework.rpathUsed.isEmpty() && !rpathsUsed.contains(framework.rpathUsed)) { + rpathsUsed.append(framework.rpathUsed); + } + + // Copy the framework/dylib to the app bundle. + const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath) + : copyFramework(framework, bundlePath); + + // Install_name_tool the new id into the binaries + changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath); + + // Skip the rest if already was deployed. + if (deployedBinaryPath.isNull()) + continue; + + runStrip(deployedBinaryPath); + + // Install_name_tool it a new id. + if (!framework.rpathUsed.length()) { + changeIdentification(framework.deployedInstallName, deployedBinaryPath); + } + + // Check for framework dependencies + QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs); + + for (const FrameworkInfo &dependency : dependencies) { + if (dependency.rpathUsed.isEmpty()) { + changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath); + } else { + rpathsUsed.append(dependency.rpathUsed); + } + + // Deploy framework if necessary. + if (copiedFrameworks.contains(dependency.frameworkName) == false && frameworks.contains(dependency) == false) { + frameworks.append(dependency); + } + } + } + deploymentInfo.deployedFrameworks = copiedFrameworks; + deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath); + deploymentInfo.rpathsUsed += rpathsUsed; + deploymentInfo.rpathsUsed.removeDuplicates(); + return deploymentInfo; +} + +DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs) +{ + ApplicationBundleInfo applicationBundle; + applicationBundle.path = appBundlePath; + applicationBundle.binaryPath = findAppBinary(appBundlePath); + applicationBundle.libraryPaths = findAppLibraries(appBundlePath); + QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths + << additionalExecutables; + + QList<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true); + allLibraryPaths.append(QLibraryInfo::path(QLibraryInfo::LibrariesPath)); + allLibraryPaths.removeDuplicates(); + + QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs); + if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) { + LogWarning(); + LogWarning() << "Could not find any external Qt frameworks to deploy in" << appBundlePath; + LogWarning() << "Perhaps macdeployqt was already used on" << appBundlePath << "?"; + LogWarning() << "If so, you will need to rebuild" << appBundlePath << "before trying again."; + return DeploymentInfo(); + } else { + return deployQtFrameworks(frameworks, applicationBundle.path, allBinaryPaths, useDebugLibs, !additionalExecutables.isEmpty()); + } +} + +QString getLibInfix(const QStringList &deployedFrameworks) +{ + QString libInfix; + for (const QString &framework : deployedFrameworks) { + if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework")) && + !framework.contains(QStringLiteral("5Compat"))) { + Q_ASSERT(framework.length() >= 16); + // 16 == "QtCore" + ".framework" + const int lengthOfLibInfix = framework.length() - 16; + if (lengthOfLibInfix) + libInfix = framework.mid(6, lengthOfLibInfix); + break; + } + } + return libInfix; +} + +void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pluginSourcePath, + const QString pluginDestinationPath, DeploymentInfo deploymentInfo, bool useDebugLibs) +{ + LogNormal() << "Deploying plugins from" << pluginSourcePath; + + if (!pluginSourcePath.contains(deploymentInfo.pluginPath)) + return; + + // Plugin white list: + QStringList pluginList; + + const auto addPlugins = [&pluginSourcePath,&pluginList,useDebugLibs](const QString &subDirectory, + const std::function<bool(QString)> &predicate = std::function<bool(QString)>()) { + const QStringList libs = QDir(pluginSourcePath + u'/' + subDirectory) + .entryList({QStringLiteral("*.dylib")}); + for (const QString &lib : libs) { + if (lib.endsWith(QStringLiteral("_debug.dylib")) != useDebugLibs) + continue; + if (!predicate || predicate(lib)) + pluginList.append(subDirectory + u'/' + lib); + } + }; + + // Platform plugin: + addPlugins(QStringLiteral("platforms"), [](const QString &lib) { + // Ignore minimal and offscreen platform plugins + if (!lib.contains(QStringLiteral("cocoa"))) + return false; + return true; + }); + + // Cocoa print support + addPlugins(QStringLiteral("printsupport")); + + // Styles + addPlugins(QStringLiteral("styles")); + + // Check if Qt was configured with -libinfix + const QString libInfix = getLibInfix(deploymentInfo.deployedFrameworks); + + // Network + if (deploymentInfo.containsModule("Network", libInfix)) { + addPlugins(QStringLiteral("tls")); + addPlugins(QStringLiteral("networkinformation")); + } + + // All image formats (svg if QtSvg is used) + const bool usesSvg = deploymentInfo.containsModule("Svg", libInfix); + addPlugins(QStringLiteral("imageformats"), [usesSvg](const QString &lib) { + if (lib.contains(QStringLiteral("qsvg")) && !usesSvg) + return false; + return true; + }); + + addPlugins(QStringLiteral("iconengines")); + + // Platforminputcontext plugins if QtGui is in use + if (deploymentInfo.containsModule("Gui", libInfix)) { + addPlugins(QStringLiteral("platforminputcontexts"), [&addPlugins](const QString &lib) { + // Deploy the virtual keyboard plugins if we have deployed virtualkeyboard + if (lib.startsWith(QStringLiteral("libqtvirtualkeyboard"))) + addPlugins(QStringLiteral("virtualkeyboard")); + return true; + }); + } + + // Sql plugins if QtSql is in use + if (deploymentInfo.containsModule("Sql", libInfix)) { + addPlugins(QStringLiteral("sqldrivers"), [](const QString &lib) { + if (lib.startsWith(QStringLiteral("libqsqlodbc")) || lib.startsWith(QStringLiteral("libqsqlpsql"))) { + LogWarning() << "Plugin" << lib << "uses private API and is not Mac App store compliant."; + if (appstoreCompliant) { + LogWarning() << "Skip plugin" << lib; + return false; + } + } + return true; + }); + } + + // WebView plugins if QtWebView is in use + if (deploymentInfo.containsModule("WebView", libInfix)) { + addPlugins(QStringLiteral("webview"), [](const QString &lib) { + if (lib.startsWith(QStringLiteral("libqtwebview_webengine"))) { + LogWarning() << "Plugin" << lib << "uses QtWebEngine and is not Mac App store compliant."; + if (appstoreCompliant) { + LogWarning() << "Skip plugin" << lib; + return false; + } + } + return true; + }); + } + + static const std::map<QString, std::vector<QString>> map { + {QStringLiteral("Multimedia"), {QStringLiteral("multimedia")}}, + {QStringLiteral("3DRender"), {QStringLiteral("sceneparsers"), QStringLiteral("geometryloaders"), QStringLiteral("renderers")}}, + {QStringLiteral("3DQuickRender"), {QStringLiteral("renderplugins")}}, + {QStringLiteral("Positioning"), {QStringLiteral("position")}}, + {QStringLiteral("Location"), {QStringLiteral("geoservices")}}, + {QStringLiteral("TextToSpeech"), {QStringLiteral("texttospeech")}} + }; + + for (const auto &it : map) { + if (deploymentInfo.containsModule(it.first, libInfix)) { + for (const auto &pluginType : it.second) { + addPlugins(pluginType); + } + } + } + + for (const QString &plugin : pluginList) { + QString sourcePath = pluginSourcePath + "/" + plugin; + const QString destinationPath = pluginDestinationPath + "/" + plugin; + QDir dir; + dir.mkpath(QFileInfo(destinationPath).path()); + + if (copyFilePrintStatus(sourcePath, destinationPath)) { + runStrip(destinationPath); + QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs); + deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath); + } + } +} + +void createQtConf(const QString &appBundlePath) +{ + // Set Plugins and imports paths. These are relative to App.app/Contents. + QByteArray contents = "[Paths]\n" + "Plugins = PlugIns\n" + "Imports = Resources/qml\n" + "QmlImports = Resources/qml\n"; + + QString filePath = appBundlePath + "/Contents/Resources/"; + QString fileName = filePath + "qt.conf"; + + QDir().mkpath(filePath); + + QFile qtconf(fileName); + if (qtconf.exists() && !alwaysOwerwriteEnabled) { + LogWarning(); + LogWarning() << fileName << "already exists, will not overwrite."; + LogWarning() << "To make sure the plugins are loaded from the correct location,"; + LogWarning() << "please make sure qt.conf contains the following lines:"; + LogWarning() << "[Paths]"; + LogWarning() << " Plugins = PlugIns"; + return; + } + + qtconf.open(QIODevice::WriteOnly); + if (qtconf.write(contents) != -1) { + LogNormal() << "Created configuration file:" << fileName; + LogNormal() << "This file sets the plugin search path to" << appBundlePath + "/Contents/PlugIns"; + } +} + +void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs) +{ + ApplicationBundleInfo applicationBundle; + applicationBundle.path = appBundlePath; + applicationBundle.binaryPath = findAppBinary(appBundlePath); + + const QString pluginDestinationPath = appBundlePath + "/" + "Contents/PlugIns"; + deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs); +} + +void deployQmlImport(const QString &appBundlePath, const QList<QString> &rpaths, const QString &importSourcePath, const QString &importName) +{ + QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName; + + // Skip already deployed imports. This can happen in cases like "QtQuick.Controls.Styles", + // where deploying QtQuick.Controls will also deploy the "Styles" sub-import. + if (QDir().exists(importDestinationPath)) + return; + + recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath); +} + +static bool importLessThan(const QVariant &v1, const QVariant &v2) +{ + QVariantMap import1 = v1.toMap(); + QVariantMap import2 = v2.toMap(); + QString path1 = import1["path"].toString(); + QString path2 = import2["path"].toString(); + return path1 < path2; +} + +// Scan qml files in qmldirs for import statements, deploy used imports from QmlImportsPath to Contents/Resources/qml. +bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths) +{ + LogNormal() << ""; + LogNormal() << "Deploying QML imports "; + LogNormal() << "Application QML file path(s) is" << qmlDirs; + LogNormal() << "QML module search path(s) is" << qmlImportPaths; + + // Use qmlimportscanner from QLibraryInfo::LibraryExecutablesPath + QString qmlImportScannerPath = + QDir::cleanPath(QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath) + + "/qmlimportscanner"); + + // Fallback: Look relative to the macdeployqt binary + if (!QFile::exists(qmlImportScannerPath)) + qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner"; + + // Verify that we found a qmlimportscanner binary + if (!QFile::exists(qmlImportScannerPath)) { + LogError() << "qmlimportscanner not found at" << qmlImportScannerPath; + LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner"; + return false; + } + + // build argument list for qmlimportsanner: "-rootPath foo/ -rootPath bar/ -importPath path/to/qt/qml" + // ("rootPath" points to a directory containing app qml, "importPath" is where the Qt imports are installed) + QStringList argumentList; + for (const QString &qmlDir : qmlDirs) { + argumentList.append("-rootPath"); + argumentList.append(qmlDir); + } + for (const QString &importPath : qmlImportPaths) + argumentList << "-importPath" << importPath; + QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath); + argumentList.append( "-importPath"); + argumentList.append(qmlImportsPath); + + // run qmlimportscanner + QProcess qmlImportScanner; + qmlImportScanner.start(qmlImportScannerPath, argumentList); + if (!qmlImportScanner.waitForStarted()) { + LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString(); + return false; + } + qmlImportScanner.waitForFinished(-1); + + // log qmlimportscanner errors + qmlImportScanner.setReadChannel(QProcess::StandardError); + QByteArray errors = qmlImportScanner.readAll(); + if (!errors.isEmpty()) { + LogWarning() << "QML file parse error (deployment will continue):"; + LogWarning() << errors; + } + + // parse qmlimportscanner json + qmlImportScanner.setReadChannel(QProcess::StandardOutput); + QByteArray json = qmlImportScanner.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(json); + if (!doc.isArray()) { + LogError() << "qmlimportscanner output error. Expected json array, got:"; + LogError() << json; + return false; + } + + // sort imports to deploy a module before its sub-modules (otherwise + // deployQmlImports can consider the module deployed if it has already + // deployed one of its sub-module) + QVariantList array = doc.array().toVariantList(); + std::sort(array.begin(), array.end(), importLessThan); + + // deploy each import + for (const QVariant &importValue : array) { + QVariantMap import = importValue.toMap(); + QString name = import["name"].toString(); + QString path = import["path"].toString(); + QString type = import["type"].toString(); + + LogNormal() << "Deploying QML import" << name; + + // Skip imports with missing info - path will be empty if the import is not found. + if (name.isEmpty() || path.isEmpty()) { + LogNormal() << " Skip import: name or path is empty"; + LogNormal() << ""; + continue; + } + + // Deploy module imports only, skip directory (local/remote) and js imports. These + // should be deployed as a part of the application build. + if (type != QStringLiteral("module")) { + LogNormal() << " Skip non-module import"; + LogNormal() << ""; + continue; + } + + // Create the destination path from the name + // and version (grabbed from the source path) + // ### let qmlimportscanner provide this. + name.replace(u'.', u'/'); + int secondTolast = path.length() - 2; + QString version = path.mid(secondTolast); + if (version.startsWith(u'.')) + name.append(version); + + deployQmlImport(appBundlePath, deploymentInfo.rpathsUsed, path, name); + LogNormal() << ""; + } + return true; +} + +void codesignFile(const QString &identity, const QString &filePath) +{ + if (!runCodesign) + return; + + QString codeSignLogMessage = "codesign"; + if (hardenedRuntime) + codeSignLogMessage += ", enable hardened runtime"; + if (secureTimestamp) + codeSignLogMessage += ", include secure timestamp"; + LogNormal() << codeSignLogMessage << filePath; + + QStringList codeSignOptions = { "--preserve-metadata=identifier,entitlements", "--force", "-s", + identity, filePath }; + if (hardenedRuntime) + codeSignOptions << "-o" << "runtime"; + + if (secureTimestamp) + codeSignOptions << "--timestamp"; + + if (!extraEntitlements.isEmpty()) + codeSignOptions << "--entitlements" << extraEntitlements; + + QProcess codesign; + codesign.start("codesign", codeSignOptions); + codesign.waitForFinished(-1); + + QByteArray err = codesign.readAllStandardError(); + if (codesign.exitCode() > 0) { + LogError() << "Codesign signing error:"; + LogError() << err; + } else if (!err.isEmpty()) { + LogDebug() << err; + } +} + +QSet<QString> codesignBundle(const QString &identity, + const QString &appBundlePath, + QList<QString> additionalBinariesContainingRpaths) +{ + // Code sign all binaries in the app bundle. This needs to + // be done inside-out, e.g sign framework dependencies + // before the main app binary. The codesign tool itself has + // a "--deep" option to do this, but usage when signing is + // not recommended: "Signing with --deep is for emergency + // repairs and temporary adjustments only." + + LogNormal() << ""; + LogNormal() << "Signing" << appBundlePath << "with identity" << identity; + + QStack<QString> pendingBinaries; + QSet<QString> pendingBinariesSet; + QSet<QString> signedBinaries; + + // Create the root code-binary set. This set consists of the application + // executable(s) and the plugins. + QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath(); + QString rootBinariesPath = appBundleAbsolutePath + "/Contents/MacOS/"; + QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() << "*", QDir::Files); + for (const QString &binary : foundRootBinaries) { + QString binaryPath = rootBinariesPath + binary; + pendingBinaries.push(binaryPath); + pendingBinariesSet.insert(binaryPath); + additionalBinariesContainingRpaths.append(binaryPath); + } + + bool getAbsoltuePath = true; + QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath + "/Contents/PlugIns/", getAbsoltuePath); + for (const QString &binary : foundPluginBinaries) { + pendingBinaries.push(binary); + pendingBinariesSet.insert(binary); + } + + // Add frameworks for processing. + QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath); + for (const QString &frameworkPath : frameworkPaths) { + + // Prioritise first to sign any additional inner bundles found in the Helpers folder (e.g + // used by QtWebEngine). + QDirIterator helpersIterator(frameworkPath, QStringList() << QString::fromLatin1("Helpers"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories); + while (helpersIterator.hasNext()) { + helpersIterator.next(); + QString helpersPath = helpersIterator.filePath(); + QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() << "*.app", QDir::Dirs); + for (const QString &innerBundleName : innerBundleNames) + signedBinaries += codesignBundle(identity, + helpersPath + "/" + innerBundleName, + additionalBinariesContainingRpaths); + } + + // Also make sure to sign any libraries that will not be found by otool because they + // are not linked and won't be seen as a dependency. + QDirIterator librariesIterator(frameworkPath, QStringList() << QString::fromLatin1("Libraries"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories); + while (librariesIterator.hasNext()) { + librariesIterator.next(); + QString librariesPath = librariesIterator.filePath(); + QStringList bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath); + for (const QString &binary : bundleFiles) { + pendingBinaries.push(binary); + pendingBinariesSet.insert(binary); + } + } + } + + // Sign all binaries; use otool to find and sign dependencies first. + while (!pendingBinaries.isEmpty()) { + QString binary = pendingBinaries.pop(); + if (signedBinaries.contains(binary)) + continue; + + // Check if there are unsigned dependencies, sign these first. + QStringList dependencies = getBinaryDependencies(rootBinariesPath, binary, + additionalBinariesContainingRpaths); + dependencies = QSet<QString>(dependencies.begin(), dependencies.end()) + .subtract(signedBinaries) + .subtract(pendingBinariesSet) + .values(); + + if (!dependencies.isEmpty()) { + pendingBinaries.push(binary); + pendingBinariesSet.insert(binary); + int dependenciesSkipped = 0; + for (const QString &dependency : std::as_const(dependencies)) { + // Skip dependencies that are outside the current app bundle, because this might + // cause a codesign error if the current bundle is part of the dependency (e.g. + // a bundle is part of a framework helper, and depends on that framework). + // The dependencies will be taken care of after the current bundle is signed. + if (!dependency.startsWith(appBundleAbsolutePath)) { + ++dependenciesSkipped; + LogNormal() << "Skipping outside dependency: " << dependency; + continue; + } + pendingBinaries.push(dependency); + pendingBinariesSet.insert(dependency); + } + + // If all dependencies were skipped, make sure the binary is actually signed, instead + // of going into an infinite loop. + if (dependenciesSkipped == dependencies.size()) { + pendingBinaries.pop(); + } else { + continue; + } + } + + // Look for an entitlements file in the bundle to include when signing + extraEntitlements = findEntitlementsFile(appBundleAbsolutePath + "/Contents/Resources/"); + + // All dependencies are signed, now sign this binary. + codesignFile(identity, binary); + signedBinaries.insert(binary); + pendingBinariesSet.remove(binary); + } + + LogNormal() << "Finished codesigning " << appBundlePath << "with identity" << identity; + + // Verify code signature + QProcess codesign; + codesign.start("codesign", QStringList() << "--deep" << "-v" << appBundlePath); + codesign.waitForFinished(-1); + QByteArray err = codesign.readAllStandardError(); + if (codesign.exitCode() > 0) { + LogError() << "codesign verification error:"; + LogError() << err; + } else if (!err.isEmpty()) { + LogDebug() << err; + } + + return signedBinaries; +} + +void codesign(const QString &identity, const QString &appBundlePath) { + codesignBundle(identity, appBundlePath, QList<QString>()); +} + +void createDiskImage(const QString &appBundlePath, const QString &filesystemType) +{ + QString appBaseName = appBundlePath; + appBaseName.chop(4); // remove ".app" from end + + QString dmgName = appBaseName + ".dmg"; + + QFile dmg(dmgName); + + if (dmg.exists() && alwaysOwerwriteEnabled) + dmg.remove(); + + if (dmg.exists()) { + LogNormal() << "Disk image already exists, skipping .dmg creation for" << dmg.fileName(); + } else { + LogNormal() << "Creating disk image (.dmg) for" << appBundlePath; + } + + LogNormal() << "Image will use" << filesystemType; + + // More dmg options can be found in the hdiutil man page. + QStringList options = QStringList() + << "create" << dmgName + << "-srcfolder" << appBundlePath + << "-format" << "UDZO" + << "-fs" << filesystemType + << "-volname" << appBaseName; + + QProcess hdutil; + hdutil.start("hdiutil", options); + hdutil.waitForFinished(-1); + if (hdutil.exitCode() != 0) { + LogError() << "Bundle creation error:" << hdutil.readAllStandardError(); + } +} + +void fixupFramework(const QString &frameworkName) +{ + // Expected framework name looks like "Foo.framework" + QStringList parts = frameworkName.split("."); + if (parts.count() < 2) { + LogError() << "fixupFramework: Unexpected framework name" << frameworkName; + return; + } + + // Assume framework binary path is Foo.framework/Foo + QString frameworkBinary = frameworkName + QStringLiteral("/") + parts[0]; + + // Xcode expects to find Foo.framework/Versions/A when code + // signing, while qmake typically generates numeric versions. + // Create symlink to the actual version in the framework. + linkFilePrintStatus("Current", frameworkName + "/Versions/A"); + + // Set up @rpath structure. + changeIdentification("@rpath/" + frameworkBinary, frameworkBinary); + addRPath("@loader_path/../../Contents/Frameworks/", frameworkBinary); +} diff --git a/src/tools/macdeployqt/shared/shared.h b/src/tools/macdeployqt/shared/shared.h new file mode 100644 index 0000000000..33384e868a --- /dev/null +++ b/src/tools/macdeployqt/shared/shared.h @@ -0,0 +1,119 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef MAC_DEPLOMYMENT_SHARED_H +#define MAC_DEPLOMYMENT_SHARED_H + +#include <QString> +#include <QStringList> +#include <QDebug> +#include <QSet> +#include <QVersionNumber> + +extern int logLevel; +#define LogError() if (logLevel < 0) {} else qDebug() << "ERROR:" +#define LogWarning() if (logLevel < 1) {} else qDebug() << "WARNING:" +#define LogNormal() if (logLevel < 2) {} else qDebug() << "Log:" +#define LogDebug() if (logLevel < 3) {} else qDebug() << "Log:" + +extern bool runStripEnabled; + +class FrameworkInfo +{ +public: + bool isDylib; + QString frameworkDirectory; + QString frameworkName; + QString frameworkPath; + QString binaryDirectory; + QString binaryName; + QString binaryPath; + QString rpathUsed; + QString version; + QString installName; + QString deployedInstallName; + QString sourceFilePath; + QString frameworkDestinationDirectory; + QString binaryDestinationDirectory; + + bool isDebugLibrary() const + { + if (isDylib) + return binaryName.contains(QStringLiteral("_debug.")); + else + return binaryName.endsWith(QStringLiteral("_debug")); + } +}; + +class DylibInfo +{ +public: + QString binaryPath; + QVersionNumber currentVersion; + QVersionNumber compatibilityVersion; +}; + +class OtoolInfo +{ +public: + QString installName; + QString binaryPath; + QVersionNumber currentVersion; + QVersionNumber compatibilityVersion; + QList<DylibInfo> dependencies; +}; + +bool operator==(const FrameworkInfo &a, const FrameworkInfo &b); +QDebug operator<<(QDebug debug, const FrameworkInfo &info); + +class ApplicationBundleInfo +{ + public: + QString path; + QString binaryPath; + QStringList libraryPaths; +}; + +class DeploymentInfo +{ +public: + QString qtPath; + QString pluginPath; + QStringList deployedFrameworks; + QList<QString> rpathsUsed; + bool useLoaderPath; + bool isFramework; + bool isDebug; + + bool containsModule(const QString &module, const QString &libInFix) const; +}; + +inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info); + +OtoolInfo findDependencyInfo(const QString &binaryPath); +FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs); +QString findAppBinary(const QString &appBundlePath); +QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs); +QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs); +QString copyFramework(const FrameworkInfo &framework, const QString path); +DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs); +DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath); +void createQtConf(const QString &appBundlePath); +void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs); +bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths); +void changeIdentification(const QString &id, const QString &binaryPath); +void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath); +void runStrip(const QString &binaryPath); +void stripAppBinary(const QString &bundlePath); +QString findAppBinary(const QString &appBundlePath); +QStringList findAppFrameworkNames(const QString &appBundlePath); +QStringList findAppFrameworkPaths(const QString &appBundlePath); +void codesignFile(const QString &identity, const QString &filePath); +QSet<QString> codesignBundle(const QString &identity, + const QString &appBundlePath, + QList<QString> additionalBinariesContainingRpaths); +void codesign(const QString &identity, const QString &appBundlePath); +void createDiskImage(const QString &appBundlePath, const QString &filesystemType); +void fixupFramework(const QString &appBundlePath); + + +#endif diff --git a/src/tools/moc/CMakeLists.txt b/src/tools/moc/CMakeLists.txt index 88dce045f8..b98b7ab4e9 100644 --- a/src/tools/moc/CMakeLists.txt +++ b/src/tools/moc/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from moc.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## moc Tool: @@ -6,10 +7,11 @@ qt_get_tool_target_name(target_name moc) qt_internal_add_tool(${target_name} - BOOTSTRAP + TRY_RUN + CORE_LIBRARY Bootstrap TARGET_DESCRIPTION "Qt Meta Object Compiler" INSTALL_DIR "${INSTALL_LIBEXECDIR}" - TOOLS_TARGET Core # special case + TOOLS_TARGET Core SOURCES cbordevice.h collectjson.cpp collectjson.h @@ -19,7 +21,6 @@ qt_internal_add_tool(${target_name} outputrevision.h parser.cpp parser.h preprocessor.cpp preprocessor.h - # qdatetime_p.h special case remove symbols.h token.cpp token.h utils.h @@ -27,17 +28,15 @@ qt_internal_add_tool(${target_name} QT_MOC QT_NO_CAST_FROM_ASCII QT_NO_CAST_FROM_BYTEARRAY - QT_NO_COMPRESS QT_NO_FOREACH + QT_NO_QPAIR + QT_USE_NODISCARD_FILE_OPEN INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} ../../3rdparty/tinycbor/src ../shared ) - -#### Keys ignored in scope 1:.:.:moc.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "Qt Meta Object Compiler" -# _OPTION = "host_build" +qt_internal_return_unless_building_tools() ## Scopes: ##################################################################### diff --git a/src/tools/moc/cbordevice.h b/src/tools/moc/cbordevice.h index dbfc537dd2..7668e4c0be 100644 --- a/src/tools/moc/cbordevice.h +++ b/src/tools/moc/cbordevice.h @@ -1,34 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2018 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef CBORDEVICE_H #define CBORDEVICE_H +#include <QtCore/qtypes.h> + #include <memory> #include <stdio.h> diff --git a/src/tools/moc/collectjson.cpp b/src/tools/moc/collectjson.cpp index 6577a3216b..d542e2abc4 100644 --- a/src/tools/moc/collectjson.cpp +++ b/src/tools/moc/collectjson.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <qfile.h> #include <qjsonarray.h> @@ -85,7 +60,7 @@ int collectJson(const QStringList &jsonFiles, const QString &outputFile, bool sk QStringList jsonFilesSorted = jsonFiles; jsonFilesSorted.sort(); - for (const QString &jsonFile : qAsConst(jsonFilesSorted)) { + for (const QString &jsonFile : std::as_const(jsonFilesSorted)) { QFile f(jsonFile); if (!f.open(QIODevice::ReadOnly)) { fprintf(stderr, "Error opening %s for reading\n", qPrintable(jsonFile)); diff --git a/src/tools/moc/collectjson.h b/src/tools/moc/collectjson.h index 3a33952a54..b16ae61519 100644 --- a/src/tools/moc/collectjson.h +++ b/src/tools/moc/collectjson.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef COLLECTJSON_H #define COLLECTJSON_H diff --git a/src/tools/moc/generator.cpp b/src/tools/moc/generator.cpp index a21e44ba8f..02e9ef178a 100644 --- a/src/tools/moc/generator.cpp +++ b/src/tools/moc/generator.cpp @@ -1,32 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Copyright (C) 2019 Olivier Goffart <ogoffart@woboq.com> -** Copyright (C) 2018 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2019 Olivier Goffart <ogoffart@woboq.com> +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "generator.h" #include "cbordevice.h" @@ -48,6 +23,8 @@ QT_BEGIN_NAMESPACE +using namespace QtMiscUtils; + uint nameToBuiltinType(const QByteArray &name) { if (name.isEmpty()) @@ -80,11 +57,12 @@ QT_FOR_EACH_STATIC_TYPE(RETURN_METATYPENAME_STRING) return nullptr; } - Generator::Generator(ClassDef *classDef, const QList<QByteArray> &metaTypes, + Generator::Generator(Moc *moc, ClassDef *classDef, const QList<QByteArray> &metaTypes, const QHash<QByteArray, QByteArray> &knownQObjectClasses, const QHash<QByteArray, QByteArray> &knownGadgets, FILE *outfile, bool requireCompleteTypes) - : out(outfile), + : parser(moc), + out(outfile), cdef(classDef), metaTypes(metaTypes), knownQObjectClasses(knownQObjectClasses), @@ -92,32 +70,53 @@ QT_FOR_EACH_STATIC_TYPE(RETURN_METATYPENAME_STRING) requireCompleteTypes(requireCompleteTypes) { if (cdef->superclassList.size()) - purestSuperClass = cdef->superclassList.constFirst().first; + purestSuperClass = cdef->superclassList.constFirst().classname; } -static inline int lengthOfEscapeSequence(const QByteArray &s, int i) +static inline qsizetype lengthOfEscapeSequence(const QByteArray &s, qsizetype i) { - if (s.at(i) != '\\' || i >= s.length() - 1) + if (s.at(i) != '\\' || i >= s.size() - 1) return 1; - const int startPos = i; + const qsizetype startPos = i; ++i; char ch = s.at(i); if (ch == 'x') { ++i; - while (i < s.length() && is_hex_char(s.at(i))) + while (i < s.size() && isHexDigit(s.at(i))) ++i; - } else if (is_octal_char(ch)) { + } else if (isOctalDigit(ch)) { while (i < startPos + 4 - && i < s.length() - && is_octal_char(s.at(i))) { + && i < s.size() + && isOctalDigit(s.at(i))) { ++i; } } else { // single character escape sequence - i = qMin(i + 1, s.length()); + i = qMin(i + 1, s.size()); } return i - startPos; } +// Prints \a s to \a out, breaking it into lines of at most ColumnWidth. The +// opening and closing quotes are NOT included (it's up to the caller). +static void printStringWithIndentation(FILE *out, const QByteArray &s) +{ + static constexpr int ColumnWidth = 72; + const qsizetype len = s.size(); + qsizetype idx = 0; + + do { + qsizetype spanLen = qMin(ColumnWidth - 2, len - idx); + // don't cut escape sequences at the end of a line + const qsizetype backSlashPos = s.lastIndexOf('\\', idx + spanLen - 1); + if (backSlashPos >= idx) { + const qsizetype escapeLen = lengthOfEscapeSequence(s, backSlashPos); + spanLen = qBound(spanLen, backSlashPos + escapeLen - idx, len - idx); + } + fprintf(out, "\n \"%.*s\"", int(spanLen), s.constData() + idx); + idx += spanLen; + } while (idx < len); +} + void Generator::strreg(const QByteArray &s) { if (!strings.contains(s)) @@ -126,7 +125,7 @@ void Generator::strreg(const QByteArray &s) int Generator::stridx(const QByteArray &s) { - int i = strings.indexOf(s); + int i = int(strings.indexOf(s)); Q_ASSERT_X(i != -1, Q_FUNC_INFO, "We forgot to register some strings"); return i; } @@ -137,8 +136,8 @@ int Generator::stridx(const QByteArray &s) static int aggregateParameterCount(const QList<FunctionDef> &list) { int sum = 0; - for (int i = 0; i < list.count(); ++i) - sum += list.at(i).arguments.count() + 1; // +1 for return type + for (const FunctionDef &def : list) + sum += int(def.arguments.size()) + 1; // +1 for return type return sum; } @@ -175,14 +174,14 @@ bool Generator::registerableMetaType(const QByteArray &propertyType) #undef STREAM_1ARG_TEMPLATE ; for (const QByteArray &oneArgTemplateType : oneArgTemplates) { - QByteArray ba = oneArgTemplateType + "<"; + const QByteArray ba = oneArgTemplateType + "<"; if (propertyType.startsWith(ba) && propertyType.endsWith(">")) { - const int argumentSize = propertyType.size() - oneArgTemplateType.size() - 1 + const qsizetype argumentSize = propertyType.size() - ba.size() // The closing '>' - 1 // templates inside templates have an extra whitespace char to strip. - (propertyType.at(propertyType.size() - 2) == ' ' ? 1 : 0 ); - const QByteArray templateArg = propertyType.mid(oneArgTemplateType.size() + 1, argumentSize); + const QByteArray templateArg = propertyType.sliced(ba.size(), argumentSize); return isBuiltinType(templateArg) || registerableMetaType(templateArg); } } @@ -195,12 +194,28 @@ static bool qualifiedNameEquals(const QByteArray &qualifiedName, const QByteArra { if (qualifiedName == name) return true; - int index = qualifiedName.indexOf("::"); + const qsizetype index = qualifiedName.indexOf("::"); if (index == -1) return false; return qualifiedNameEquals(qualifiedName.mid(index+2), name); } +static QByteArray generateQualifiedClassNameIdentifier(const QByteArray &identifier) +{ + QByteArray qualifiedClassNameIdentifier = identifier; + + // Remove ':'s in the name, but be sure not to create any illegal + // identifiers in the process. (Don't replace with '_', because + // that will create problems with things like NS_::_class.) + qualifiedClassNameIdentifier.replace("::", "SCOPE"); + + // Also, avoid any leading/trailing underscores (we'll concatenate + // the generated name with other prefixes/suffixes, and these latter + // may already include an underscore, leading to two underscores) + qualifiedClassNameIdentifier = "CLASS" + qualifiedClassNameIdentifier + "ENDCLASS"; + return qualifiedClassNameIdentifier; +} + void Generator::generateCode() { bool isQObject = (cdef->classname == "QObject"); @@ -209,8 +224,7 @@ void Generator::generateCode() // filter out undeclared enumerators and sets { QList<EnumDef> enumList; - for (int i = 0; i < cdef->enumList.count(); ++i) { - EnumDef def = cdef->enumList.at(i); + for (EnumDef def : std::as_const(cdef->enumList)) { if (cdef->enumDeclarations.contains(def.name)) { enumList += def; } @@ -237,128 +251,58 @@ void Generator::generateCode() registerPropertyStrings(); registerEnumStrings(); - QByteArray qualifiedClassNameIdentifier = cdef->qualified; - qualifiedClassNameIdentifier.replace(':', '_'); + const bool hasStaticMetaCall = + (cdef->hasQObject || !cdef->methodList.isEmpty() + || !cdef->propertyList.isEmpty() || !cdef->constructorList.isEmpty()); + + const QByteArray qualifiedClassNameIdentifier = generateQualifiedClassNameIdentifier(cdef->qualified); + + // ensure the qt_meta_stringdata_XXXX_t type is local + fprintf(out, "namespace {\n"); // -// Build stringdata struct +// Build the strings using QtMocHelpers::StringData // - const int constCharArraySizeLimit = 65535; - fprintf(out, "struct qt_meta_stringdata_%s_t {\n", qualifiedClassNameIdentifier.constData()); - fprintf(out, " const uint offsetsAndSize[%d];\n", int(strings.size()*2)); - { - int stringDataLength = 0; - int stringDataCounter = 0; - for (int i = 0; i < strings.size(); ++i) { - int thisLength = strings.at(i).length() + 1; - stringDataLength += thisLength; - if (stringDataLength / constCharArraySizeLimit) { - // save previous stringdata and start computing the next one. - fprintf(out, " char stringdata%d[%d];\n", stringDataCounter++, stringDataLength - thisLength); - stringDataLength = thisLength; - } - } - fprintf(out, " char stringdata%d[%d];\n", stringDataCounter, stringDataLength); - } - fprintf(out, "};\n"); - - // Macro that expands into a QByteArrayData. The offset member is - // calculated from 1) the offset of the actual characters in the - // stringdata.stringdata member, and 2) the stringdata.data index of the - // QByteArrayData being defined. This calculation relies on the - // QByteArrayData::data() implementation returning simply "this + offset". - fprintf(out, "#define QT_MOC_LITERAL(ofs, len) \\\n" - " uint(offsetof(qt_meta_stringdata_%s_t, stringdata0) + ofs), len \n", - qualifiedClassNameIdentifier.constData()); - - fprintf(out, "static const qt_meta_stringdata_%s_t qt_meta_stringdata_%s = {\n", + fprintf(out, "\n#ifdef QT_MOC_HAS_STRINGDATA\n" + "struct qt_meta_stringdata_%s_t {};\n" + "constexpr auto qt_meta_stringdata_%s = QtMocHelpers::stringData(", qualifiedClassNameIdentifier.constData(), qualifiedClassNameIdentifier.constData()); - fprintf(out, " {\n"); { - int idx = 0; - for (int i = 0; i < strings.size(); ++i) { - const QByteArray &str = strings.at(i); - fprintf(out, "QT_MOC_LITERAL(%d, %d)", idx, int(str.length())); - if (i != strings.size() - 1) - fputc(',', out); - const QByteArray comment = str.length() > 32 ? str.left(29) + "..." : str; - fprintf(out, " // \"%s\"\n", comment.size() ? comment.constData() : ""); - idx += str.length() + 1; - for (int j = 0; j < str.length(); ++j) { - if (str.at(j) == '\\') { - int cnt = lengthOfEscapeSequence(str, j) - 1; - idx -= cnt; - j += cnt; - } - } + char comma = 0; + for (const QByteArray &str : strings) { + if (comma) + fputc(comma, out); + printStringWithIndentation(out, str); + comma = ','; } - fprintf(out, "\n },\n"); } - -// -// Build stringdata array -// - fprintf(out, " \""); - int col = 0; - int len = 0; - int stringDataLength = 0; - for (int i = 0; i < strings.size(); ++i) { - QByteArray s = strings.at(i); - len = s.length(); - stringDataLength += len + 1; - if (stringDataLength >= constCharArraySizeLimit) { - fprintf(out, "\",\n \""); - stringDataLength = len + 1; - col = 0; - } else if (i) - fputs("\\0", out); // add \0 at the end of each string - - if (col && col + len >= 72) { - fprintf(out, "\"\n \""); - col = 0; - } else if (len && s.at(0) >= '0' && s.at(0) <= '9') { - fprintf(out, "\"\""); - len += 2; - } - int idx = 0; - while (idx < s.length()) { - if (idx > 0) { - col = 0; - fprintf(out, "\"\n \""); - } - int spanLen = qMin(70, s.length() - idx); - // don't cut escape sequences at the end of a line - int backSlashPos = s.lastIndexOf('\\', idx + spanLen - 1); - if (backSlashPos >= idx) { - int escapeLen = lengthOfEscapeSequence(s, backSlashPos); - spanLen = qBound(spanLen, backSlashPos + escapeLen - idx, s.length() - idx); - } - fprintf(out, "%.*s", spanLen, s.constData() + idx); - idx += spanLen; - col += spanLen; - } - col += len + 2; - } - -// Terminate stringdata struct - fprintf(out, "\"\n};\n"); - fprintf(out, "#undef QT_MOC_LITERAL\n\n"); + fprintf(out, "\n);\n" + "#else // !QT_MOC_HAS_STRINGDATA\n"); + fprintf(out, "#error \"qtmochelpers.h not found or too old.\"\n"); + fprintf(out, "#endif // !QT_MOC_HAS_STRINGDATA\n"); + fprintf(out, "} // unnamed namespace\n\n"); // // build the data array // int index = MetaObjectPrivateFieldCount; - fprintf(out, "static const uint qt_meta_data_%s[] = {\n", qualifiedClassNameIdentifier.constData()); + fprintf(out, "Q_CONSTINIT static const uint qt_meta_data_%s[] = {\n", qualifiedClassNameIdentifier.constData()); fprintf(out, "\n // content:\n"); fprintf(out, " %4d, // revision\n", int(QMetaObjectPrivate::OutputRevision)); fprintf(out, " %4d, // classname\n", stridx(cdef->qualified)); - fprintf(out, " %4d, %4d, // classinfo\n", int(cdef->classInfoList.count()), int(cdef->classInfoList.count() ? index : 0)); - index += cdef->classInfoList.count() * 2; + fprintf(out, " %4d, %4d, // classinfo\n", int(cdef->classInfoList.size()), int(cdef->classInfoList.size() ? index : 0)); + index += cdef->classInfoList.size() * 2; + + qsizetype methodCount = 0; + if (qAddOverflow(cdef->signalList.size(), cdef->slotList.size(), &methodCount) + || qAddOverflow(cdef->methodList.size(), methodCount, &methodCount)) { + parser->error("internal limit exceeded: the total number of member functions" + " (including signals and slots) is too big."); + } - int methodCount = cdef->signalList.count() + cdef->slotList.count() + cdef->methodList.count(); - fprintf(out, " %4d, %4d, // methods\n", methodCount, methodCount ? index : 0); + fprintf(out, " %4" PRIdQSIZETYPE ", %4d, // methods\n", methodCount, methodCount ? index : 0); index += methodCount * QMetaObjectPrivate::IntsPerMethod; if (cdef->revisionedMethods) index += methodCount; @@ -369,16 +313,17 @@ void Generator::generateCode() + aggregateParameterCount(cdef->constructorList); index += totalParameterCount * 2 // types and parameter names - methodCount // return "parameters" don't have names - - cdef->constructorList.count(); // "this" parameters don't have names + - int(cdef->constructorList.size()); // "this" parameters don't have names - fprintf(out, " %4d, %4d, // properties\n", int(cdef->propertyList.count()), int(cdef->propertyList.count() ? index : 0)); - index += cdef->propertyList.count() * QMetaObjectPrivate::IntsPerProperty; - fprintf(out, " %4d, %4d, // enums/sets\n", int(cdef->enumList.count()), cdef->enumList.count() ? index : 0); + fprintf(out, " %4d, %4d, // properties\n", int(cdef->propertyList.size()), int(cdef->propertyList.size() ? index : 0)); + index += cdef->propertyList.size() * QMetaObjectPrivate::IntsPerProperty; + fprintf(out, " %4d, %4d, // enums/sets\n", int(cdef->enumList.size()), cdef->enumList.size() ? index : 0); int enumsIndex = index; - for (int i = 0; i < cdef->enumList.count(); ++i) - index += 5 + (cdef->enumList.at(i).values.count() * 2); - fprintf(out, " %4d, %4d, // constructors\n", isConstructible ? int(cdef->constructorList.count()) : 0, + for (const EnumDef &def : std::as_const(cdef->enumList)) + index += QMetaObjectPrivate::IntsPerEnum + (def.values.size() * 2); + + fprintf(out, " %4d, %4d, // constructors\n", isConstructible ? int(cdef->constructorList.size()) : 0, isConstructible ? index : 0); int flags = 0; @@ -388,7 +333,7 @@ void Generator::generateCode() flags |= PropertyAccessInStaticMetaCall; } fprintf(out, " %4d, // flags\n", flags); - fprintf(out, " %4d, // signalCount\n", int(cdef->signalList.count())); + fprintf(out, " %4d, // signalCount\n", int(cdef->signalList.size())); // @@ -396,8 +341,14 @@ void Generator::generateCode() // generateClassInfos(); - // all property metatypes, + 1 for the type of the current class itself - int initialMetaTypeOffset = cdef->propertyList.count() + 1; + qsizetype propEnumCount = 0; + // all property metatypes + all enum metatypes + 1 for the type of the current class itself + if (qAddOverflow(cdef->propertyList.size(), cdef->enumList.size(), &propEnumCount) + || qAddOverflow(propEnumCount, qsizetype(1), &propEnumCount) + || propEnumCount >= std::numeric_limits<int>::max()) { + parser->error("internal limit exceeded: number of property and enum metatypes is too big."); + } + int initialMetaTypeOffset = int(propEnumCount); // // Build signals array first, otherwise the signal indices would be wrong @@ -454,30 +405,20 @@ void Generator::generateCode() fprintf(out, "\n 0 // eod\n};\n\n"); // -// Generate internal qt_static_metacall() function -// - const bool hasStaticMetaCall = - (cdef->hasQObject || !cdef->methodList.isEmpty() - || !cdef->propertyList.isEmpty() || !cdef->constructorList.isEmpty()); - if (hasStaticMetaCall) - generateStaticMetacall(); - -// // Build extra array // QList<QByteArray> extraList; QMultiHash<QByteArray, QByteArray> knownExtraMetaObject(knownGadgets); knownExtraMetaObject.unite(knownQObjectClasses); - for (int i = 0; i < cdef->propertyList.count(); ++i) { - const PropertyDef &p = cdef->propertyList.at(i); + for (const PropertyDef &p : std::as_const(cdef->propertyList)) { if (isBuiltinType(p.type)) continue; if (p.type.contains('*') || p.type.contains('<') || p.type.contains('>')) continue; - int s = p.type.lastIndexOf("::"); + const qsizetype s = p.type.lastIndexOf("::"); if (s <= 0) continue; @@ -488,7 +429,7 @@ void Generator::generateCode() QByteArray thisScope = cdef->qualified; do { - int s = thisScope.lastIndexOf("::"); + const qsizetype s = thisScope.lastIndexOf("::"); thisScope = thisScope.left(s); QByteArray currentScope = thisScope.isEmpty() ? unqualifiedScope : thisScope + "::" + unqualifiedScope; scopeIt = knownExtraMetaObject.constFind(currentScope); @@ -514,7 +455,7 @@ void Generator::generateCode() for (auto it = cdef->enumDeclarations.keyBegin(), end = cdef->enumDeclarations.keyEnd(); it != end; ++it) { const QByteArray &enumKey = *it; - int s = enumKey.lastIndexOf("::"); + const qsizetype s = enumKey.lastIndexOf("::"); if (s > 0) { QByteArray scope = enumKey.left(s); if (scope != "Qt" && !qualifiedNameEquals(cdef->qualified, scope) && !extraList.contains(scope)) @@ -527,28 +468,29 @@ void Generator::generateCode() // if (!extraList.isEmpty()) { - fprintf(out, "static const QMetaObject::SuperData qt_meta_extradata_%s[] = {\n", + fprintf(out, "Q_CONSTINIT static const QMetaObject::SuperData qt_meta_extradata_%s[] = {\n", qualifiedClassNameIdentifier.constData()); - for (int i = 0; i < extraList.count(); ++i) { - fprintf(out, " QMetaObject::SuperData::link<%s::staticMetaObject>(),\n", extraList.at(i).constData()); - } + for (const QByteArray &ba : std::as_const(extraList)) + fprintf(out, " QMetaObject::SuperData::link<%s::staticMetaObject>(),\n", ba.constData()); + fprintf(out, " nullptr\n};\n\n"); } // // Finally create and initialize the static meta object // - fprintf(out, "const QMetaObject %s::staticMetaObject = { {\n", cdef->qualified.constData()); + fprintf(out, "Q_CONSTINIT const QMetaObject %s::staticMetaObject = { {\n", + cdef->qualified.constData()); if (isQObject) fprintf(out, " nullptr,\n"); else if (cdef->superclassList.size() && !cdef->hasQGadget && !cdef->hasQNamespace) // for qobject, we know the super class must have a static metaobject fprintf(out, " QMetaObject::SuperData::link<%s::staticMetaObject>(),\n", purestSuperClass.constData()); else if (cdef->superclassList.size()) // for gadgets we need to query at compile time for it - fprintf(out, " QtPrivate::MetaObjectForType<%s>::value(),\n", purestSuperClass.constData()); + fprintf(out, " QtPrivate::MetaObjectForType<%s>::value,\n", purestSuperClass.constData()); else fprintf(out, " nullptr,\n"); - fprintf(out, " qt_meta_stringdata_%s.offsetsAndSize,\n" + fprintf(out, " qt_meta_stringdata_%s.offsetsAndSizes,\n" " qt_meta_data_%s,\n", qualifiedClassNameIdentifier.constData(), qualifiedClassNameIdentifier.constData()); if (hasStaticMetaCall) @@ -561,63 +503,75 @@ void Generator::generateCode() else fprintf(out, " qt_meta_extradata_%s,\n", qualifiedClassNameIdentifier.constData()); - bool needsComma = false; + const char *comma = ""; const bool requireCompleteness = requireCompleteTypes || cdef->requireCompleteMethodTypes; + auto stringForType = [requireCompleteness](const QByteArray &type, bool forceComplete) -> QByteArray { + const char *forceCompleteType = forceComplete ? ", std::true_type>" : ", std::false_type>"; + if (requireCompleteness) + return type; + return "QtPrivate::TypeAndForceComplete<" % type % forceCompleteType; + }; if (!requireCompleteness) { - fprintf(out, "qt_incomplete_metaTypeArray<qt_meta_stringdata_%s_t\n", qualifiedClassNameIdentifier.constData()); - needsComma = true; + fprintf(out, " qt_incomplete_metaTypeArray<qt_meta_stringdata_%s_t", qualifiedClassNameIdentifier.constData()); + comma = ","; } else { - fprintf(out, "qt_metaTypeArray<\n"); + fprintf(out, " qt_metaTypeArray<"); } // metatypes for properties - for (int i = 0; i < cdef->propertyList.count(); ++i) { - const PropertyDef &p = cdef->propertyList.at(i); - if (requireCompleteness) - fprintf(out, "%s%s", needsComma ? ", " : "", p.type.data()); - else - fprintf(out, "%sQtPrivate::TypeAndForceComplete<%s, std::true_type>", needsComma ? ", " : "", p.type.data()); - needsComma = true; + for (const PropertyDef &p : std::as_const(cdef->propertyList)) { + fprintf(out, "%s\n // property '%s'\n %s", + comma, p.name.constData(), stringForType(p.type, true).constData()); + comma = ","; + } + + // metatypes for enums + for (const EnumDef &e : std::as_const(cdef->enumList)) { + fprintf(out, "%s\n // enum '%s'\n %s", + comma, e.name.constData(), stringForType(e.qualifiedType(cdef), true).constData()); + comma = ","; } + // type name for the Q_OJBECT/GADGET itself, void for namespaces auto ownType = !cdef->hasQNamespace ? cdef->classname.data() : "void"; - if (requireCompleteness) - fprintf(out, "%s%s", needsComma ? ", " : "", ownType); - else - fprintf(out, "%sQtPrivate::TypeAndForceComplete<%s, std::true_type>", needsComma ? ", " : "", ownType); + fprintf(out, "%s\n // Q_OBJECT / Q_GADGET\n %s", + comma, stringForType(ownType, true).constData()); + comma = ","; // metatypes for all exposed methods - // no need to check for needsComma any longer, as we always need one due to the classname being present - for (const QList<FunctionDef> &methodContainer : - { cdef->signalList, cdef->slotList, cdef->methodList }) { - for (int i = 0; i< methodContainer.count(); ++i) { - const FunctionDef& fdef = methodContainer.at(i); - if (requireCompleteness) - fprintf(out, ", %s", fdef.type.name.data()); - else - fprintf(out, ", QtPrivate::TypeAndForceComplete<%s, std::false_type>", fdef.type.name.data()); - for (const auto &argument: fdef.arguments) { - if (requireCompleteness) - fprintf(out, ", %s", argument.type.name.data()); - else - fprintf(out, ", QtPrivate::TypeAndForceComplete<%s, std::false_type>", argument.type.name.data()); - } + // because we definitely printed something above, this section doesn't need comma control + const auto allMethods = {&cdef->signalList, &cdef->slotList, &cdef->methodList}; + for (const QList<FunctionDef> *methodContainer : allMethods) { + for (const FunctionDef &fdef : *methodContainer) { + fprintf(out, ",\n // method '%s'\n %s", + fdef.name.constData(), stringForType(fdef.type.name, false).constData()); + for (const auto &argument: fdef.arguments) + fprintf(out, ",\n %s", stringForType(argument.type.name, false).constData()); } - fprintf(out, "\n"); } - for (int i = 0; i< cdef->constructorList.count(); ++i) { - const FunctionDef& fdef = cdef->constructorList.at(i); + + // but constructors have no return types, so this needs comma control again + for (const FunctionDef &fdef : std::as_const(cdef->constructorList)) { + if (fdef.arguments.isEmpty()) + continue; + + fprintf(out, "%s\n // constructor '%s'", comma, fdef.name.constData()); + comma = ""; for (const auto &argument: fdef.arguments) { - if (requireCompleteness) - fprintf(out, ", %s", argument.type.name.data()); - else - fprintf(out, ", QtPrivate::TypeAndForceComplete<%s, std::false_type>", argument.type.name.data()); + fprintf(out, "%s\n %s", comma, + stringForType(argument.type.name, false).constData()); + comma = ","; } } - fprintf(out, "\n"); - fprintf(out, ">,\n"); + fprintf(out, "\n >,\n"); fprintf(out, " nullptr\n} };\n\n"); +// +// Generate internal qt_static_metacall() function +// + if (hasStaticMetaCall) + generateStaticMetacall(); + if (!cdef->hasQObject) return; @@ -633,18 +587,24 @@ void Generator::generateCode() fprintf(out, " if (!strcmp(_clname, qt_meta_stringdata_%s.stringdata0))\n" " return static_cast<void*>(this);\n", qualifiedClassNameIdentifier.constData()); - for (int i = 1; i < cdef->superclassList.size(); ++i) { // for all superclasses but the first one - if (cdef->superclassList.at(i).second == FunctionDef::Private) - continue; - const char *cname = cdef->superclassList.at(i).first.constData(); - fprintf(out, " if (!strcmp(_clname, \"%s\"))\n return static_cast< %s*>(this);\n", - cname, cname); + + // for all superclasses but the first one + if (cdef->superclassList.size() > 1) { + auto it = cdef->superclassList.cbegin() + 1; + const auto end = cdef->superclassList.cend(); + for (; it != end; ++it) { + if (it->access == FunctionDef::Private) + continue; + const char *cname = it->classname.constData(); + fprintf(out, " if (!strcmp(_clname, \"%s\"))\n return static_cast< %s*>(this);\n", + cname, cname); + } } - for (int i = 0; i < cdef->interfaceList.size(); ++i) { - const QList<ClassDef::Interface> &iface = cdef->interfaceList.at(i); - for (int j = 0; j < iface.size(); ++j) { + + for (const QList<ClassDef::Interface> &iface : std::as_const(cdef->interfaceList)) { + for (qsizetype j = 0; j < iface.size(); ++j) { fprintf(out, " if (!strcmp(_clname, %s))\n return ", iface.at(j).interfaceId.constData()); - for (int k = j; k >= 0; --k) + for (qsizetype k = j; k >= 0; --k) fprintf(out, "static_cast< %s*>(", iface.at(k).className.constData()); fprintf(out, "this%s;\n", QByteArray(j + 1, ')').constData()); } @@ -665,8 +625,8 @@ void Generator::generateCode() // // Generate internal signal functions // - for (int signalindex = 0; signalindex < cdef->signalList.size(); ++signalindex) - generateSignal(&cdef->signalList[signalindex], signalindex); + for (int signalindex = 0; signalindex < int(cdef->signalList.size()); ++signalindex) + generateSignal(&cdef->signalList.at(signalindex), signalindex); // // Generate plugin meta data @@ -677,12 +637,29 @@ void Generator::generateCode() // Generate function to make sure the non-class signals exist in the parent classes // if (!cdef->nonClassSignalList.isEmpty()) { - fprintf(out, "// If you get a compile error in this function it can be because either\n"); - fprintf(out, "// a) You are using a NOTIFY signal that does not exist. Fix it.\n"); - fprintf(out, "// b) You are using a NOTIFY signal that does exist (in a parent class) but has a non-empty parameter list. This is a moc limitation.\n"); - fprintf(out, "[[maybe_unused]] static void checkNotifySignalValidity_%s(%s *t) {\n", qualifiedClassNameIdentifier.constData(), cdef->qualified.constData()); - for (const QByteArray &nonClassSignal : qAsConst(cdef->nonClassSignalList)) - fprintf(out, " t->%s();\n", nonClassSignal.constData()); + fprintf(out, "namespace CheckNotifySignalValidity_%s {\n", qualifiedClassNameIdentifier.constData()); + for (const QByteArray &nonClassSignal : std::as_const(cdef->nonClassSignalList)) { + const auto propertyIt = std::find_if(cdef->propertyList.constBegin(), + cdef->propertyList.constEnd(), + [&nonClassSignal](const PropertyDef &p) { + return nonClassSignal == p.notify; + }); + // must find something, otherwise checkProperties wouldn't have inserted an entry into nonClassSignalList + Q_ASSERT(propertyIt != cdef->propertyList.constEnd()); + fprintf(out, "template<typename T> using has_nullary_%s = decltype(std::declval<T>().%s());\n", + nonClassSignal.constData(), + nonClassSignal.constData()); + const auto &propertyType = propertyIt->type; + fprintf(out, "template<typename T> using has_unary_%s = decltype(std::declval<T>().%s(std::declval<%s>()));\n", + nonClassSignal.constData(), + nonClassSignal.constData(), + propertyType.constData()); + fprintf(out, "static_assert(qxp::is_detected_v<has_nullary_%s, %s> || qxp::is_detected_v<has_unary_%s, %s>,\n" + " \"NOTIFY signal %s does not exist in class (or is private in its parent)\");\n", + nonClassSignal.constData(), cdef->qualified.constData(), + nonClassSignal.constData(), cdef->qualified.constData(), + nonClassSignal.constData()); + } fprintf(out, "}\n"); } } @@ -690,8 +667,7 @@ void Generator::generateCode() void Generator::registerClassInfoStrings() { - for (int i = 0; i < cdef->classInfoList.size(); ++i) { - const ClassInfoDef &c = cdef->classInfoList.at(i); + for (const ClassInfoDef &c : std::as_const(cdef->classInfoList)) { strreg(c.name); strreg(c.value); } @@ -704,25 +680,19 @@ void Generator::generateClassInfos() fprintf(out, "\n // classinfo: key, value\n"); - for (int i = 0; i < cdef->classInfoList.size(); ++i) { - const ClassInfoDef &c = cdef->classInfoList.at(i); + for (const ClassInfoDef &c : std::as_const(cdef->classInfoList)) fprintf(out, " %4d, %4d,\n", stridx(c.name), stridx(c.value)); - } } void Generator::registerFunctionStrings(const QList<FunctionDef> &list) { - for (int i = 0; i < list.count(); ++i) { - const FunctionDef &f = list.at(i); - + for (const FunctionDef &f : list) { strreg(f.name); if (!isBuiltinType(f.normalizedType)) strreg(f.normalizedType); strreg(f.tag); - int argsCount = f.arguments.count(); - for (int j = 0; j < argsCount; ++j) { - const ArgumentDef &a = f.arguments.at(j); + for (const ArgumentDef &a : f.arguments) { if (!isBuiltinType(a.normalizedType)) strreg(a.normalizedType); strreg(a.name); @@ -743,9 +713,7 @@ void Generator::generateFunctions(const QList<FunctionDef> &list, const char *fu return; fprintf(out, "\n // %ss: name, argc, parameters, tag, flags, initial metatype offsets\n", functype); - for (int i = 0; i < list.count(); ++i) { - const FunctionDef &f = list.at(i); - + for (const FunctionDef &f : list) { QByteArray comment; uint flags = type; if (f.access == FunctionDef::Private) { @@ -780,7 +748,7 @@ void Generator::generateFunctions(const QList<FunctionDef> &list, const char *fu comment.append(" | MethodIsConst "); } - int argc = f.arguments.count(); + const int argc = int(f.arguments.size()); fprintf(out, " %4d, %4d, %4d, %4d, 0x%02x, %4d /* %s */,\n", stridx(f.name), argc, paramsIndex, stridx(f.tag), flags, initialMetatypeOffset, comment.constData()); @@ -792,12 +760,10 @@ void Generator::generateFunctions(const QList<FunctionDef> &list, const char *fu void Generator::generateFunctionRevisions(const QList<FunctionDef> &list, const char *functype) { - if (list.count()) + if (list.size()) fprintf(out, "\n // %ss: revision\n", functype); - for (int i = 0; i < list.count(); ++i) { - const FunctionDef &f = list.at(i); + for (const FunctionDef &f : list) fprintf(out, " %4d,\n", f.revision); - } } void Generator::generateFunctionParameters(const QList<FunctionDef> &list, const char *functype) @@ -805,25 +771,22 @@ void Generator::generateFunctionParameters(const QList<FunctionDef> &list, const if (list.isEmpty()) return; fprintf(out, "\n // %ss: parameters\n", functype); - for (int i = 0; i < list.count(); ++i) { - const FunctionDef &f = list.at(i); + for (const FunctionDef &f : list) { fprintf(out, " "); // Types - int argsCount = f.arguments.count(); - for (int j = -1; j < argsCount; ++j) { - if (j > -1) - fputc(' ', out); - const QByteArray &typeName = (j < 0) ? f.normalizedType : f.arguments.at(j).normalizedType; - generateTypeInfo(typeName, /*allowEmptyName=*/f.isConstructor); + const bool allowEmptyName = f.isConstructor; + generateTypeInfo(f.normalizedType, allowEmptyName); + fputc(',', out); + for (const ArgumentDef &arg : f.arguments) { + fputc(' ', out); + generateTypeInfo(arg.normalizedType, allowEmptyName); fputc(',', out); } // Parameter names - for (int j = 0; j < argsCount; ++j) { - const ArgumentDef &arg = f.arguments.at(j); + for (const ArgumentDef &arg : f.arguments) fprintf(out, " %4d,", stridx(arg.name)); - } fprintf(out, "\n"); } @@ -856,8 +819,7 @@ void Generator::generateTypeInfo(const QByteArray &typeName, bool allowEmptyName void Generator::registerPropertyStrings() { - for (int i = 0; i < cdef->propertyList.count(); ++i) { - const PropertyDef &p = cdef->propertyList.at(i); + for (const PropertyDef &p : std::as_const(cdef->propertyList)) { strreg(p.name); if (!isBuiltinType(p.type)) strreg(p.type); @@ -870,10 +832,9 @@ void Generator::generateProperties() // Create meta data // - if (cdef->propertyList.count()) + if (cdef->propertyList.size()) fprintf(out, "\n // properties: name, type, flags\n"); - for (int i = 0; i < cdef->propertyList.count(); ++i) { - const PropertyDef &p = cdef->propertyList.at(i); + for (const PropertyDef &p : std::as_const(cdef->propertyList)) { uint flags = Invalid; if (!isBuiltinType(p.type)) flags |= EnumOrFlag; @@ -917,7 +878,7 @@ void Generator::generateProperties() int notifyId = p.notifyId; if (p.notifyId < -1) { // signal is in parent class - const int indexInStrings = strings.indexOf(p.notify); + const int indexInStrings = int(strings.indexOf(p.notify)); notifyId = indexInStrings | IsUnresolvedSignal; } fprintf(out, ", 0x%.8x, uint(%d), %d,\n", flags, notifyId, p.revision); @@ -926,13 +887,12 @@ void Generator::generateProperties() void Generator::registerEnumStrings() { - for (int i = 0; i < cdef->enumList.count(); ++i) { - const EnumDef &e = cdef->enumList.at(i); + for (const EnumDef &e : std::as_const(cdef->enumList)) { strreg(e.name); if (!e.enumName.isNull()) strreg(e.enumName); - for (int j = 0; j < e.values.count(); ++j) - strreg(e.values.at(j)); + for (const QByteArray &val : e.values) + strreg(val); } } @@ -942,9 +902,9 @@ void Generator::generateEnums(int index) return; fprintf(out, "\n // enums: name, alias, flags, count, data\n"); - index += 5 * cdef->enumList.count(); + index += QMetaObjectPrivate::IntsPerEnum * cdef->enumList.size(); int i; - for (i = 0; i < cdef->enumList.count(); ++i) { + for (i = 0; i < cdef->enumList.size(); ++i) { const EnumDef &e = cdef->enumList.at(i); int flags = 0; if (cdef->enumDeclarations.value(e.name)) @@ -955,16 +915,14 @@ void Generator::generateEnums(int index) stridx(e.name), e.enumName.isNull() ? stridx(e.name) : stridx(e.enumName), flags, - int(e.values.count()), + int(e.values.size()), index); - index += e.values.count() * 2; + index += e.values.size() * 2; } fprintf(out, "\n // enum data: key, value\n"); - for (i = 0; i < cdef->enumList.count(); ++i) { - const EnumDef &e = cdef->enumList.at(i); - for (int j = 0; j < e.values.count(); ++j) { - const QByteArray &val = e.values.at(j); + for (const EnumDef &e : std::as_const(cdef->enumList)) { + for (const QByteArray &val : e.values) { QByteArray code = cdef->qualified.constData(); if (e.isEnumClass) code += "::" + (e.enumName.isNull() ? e.name : e.enumName); @@ -1022,8 +980,6 @@ void Generator::generateMetacall() } if (cdef->propertyList.size()) { - - fprintf(out, "\n#ifndef QT_NO_PROPERTIES\n "); if (needElse) fprintf(out, "else "); fprintf(out, @@ -1031,8 +987,7 @@ void Generator::generateMetacall() " || _c == QMetaObject::ResetProperty || _c == QMetaObject::BindableProperty\n" " || _c == QMetaObject::RegisterPropertyMetaType) {\n" " qt_static_metacall(this, _c, _id, _a);\n" - " _id -= %d;\n }", int(cdef->propertyList.count())); - fprintf(out, "\n#endif // QT_NO_PROPERTIES"); + " _id -= %d;\n }", int(cdef->propertyList.size())); } if (methodList.size() || cdef->propertyList.size()) fprintf(out, "\n "); @@ -1040,10 +995,11 @@ void Generator::generateMetacall() } +// ### Qt 7 (6.x?): remove QMultiMap<QByteArray, int> Generator::automaticPropertyMetaTypesHelper() { QMultiMap<QByteArray, int> automaticPropertyMetaTypes; - for (int i = 0; i < cdef->propertyList.size(); ++i) { + for (int i = 0; i < int(cdef->propertyList.size()); ++i) { const QByteArray propertyType = cdef->propertyList.at(i).type; if (registerableMetaType(propertyType) && !isBuiltinType(propertyType)) automaticPropertyMetaTypes.insert(propertyType, i); @@ -1057,7 +1013,7 @@ Generator::methodsWithAutomaticTypesHelper(const QList<FunctionDef> &methodList) QMap<int, QMultiMap<QByteArray, int> > methodsWithAutomaticTypes; for (int i = 0; i < methodList.size(); ++i) { const FunctionDef &f = methodList.at(i); - for (int j = 0; j < f.arguments.count(); ++j) { + for (int j = 0; j < f.arguments.size(); ++j) { const QByteArray argType = f.arguments.at(j).normalizedType; if (registerableMetaType(argType) && !isBuiltinType(argType)) methodsWithAutomaticTypes[i].insert(argType, j); @@ -1074,33 +1030,46 @@ void Generator::generateStaticMetacall() bool needElse = false; bool isUsed_a = false; + const auto generateCtorArguments = [&](int ctorindex) { + const FunctionDef &f = cdef->constructorList.at(ctorindex); + Q_ASSERT(!f.isPrivateSignal); // That would be a strange ctor indeed + int offset = 1; + + const auto begin = f.arguments.cbegin(); + const auto end = f.arguments.cend(); + for (auto it = begin; it != end; ++it) { + const ArgumentDef &a = *it; + if (it != begin) + fprintf(out, ","); + fprintf(out, "(*reinterpret_cast<%s>(_a[%d]))", + a.typeNameForCast.constData(), offset++); + } + }; + if (!cdef->constructorList.isEmpty()) { fprintf(out, " if (_c == QMetaObject::CreateInstance) {\n"); fprintf(out, " switch (_id) {\n"); - for (int ctorindex = 0; ctorindex < cdef->constructorList.count(); ++ctorindex) { + const int ctorend = int(cdef->constructorList.size()); + for (int ctorindex = 0; ctorindex < ctorend; ++ctorindex) { fprintf(out, " case %d: { %s *_r = new %s(", ctorindex, cdef->classname.constData(), cdef->classname.constData()); - const FunctionDef &f = cdef->constructorList.at(ctorindex); - int offset = 1; - - int argsCount = f.arguments.count(); - for (int j = 0; j < argsCount; ++j) { - const ArgumentDef &a = f.arguments.at(j); - if (j) - fprintf(out, ","); - fprintf(out, "(*reinterpret_cast< %s>(_a[%d]))", a.typeNameForCast.constData(), offset++); - } - if (f.isPrivateSignal) { - if (argsCount > 0) - fprintf(out, ", "); - fprintf(out, "%s", QByteArray("QPrivateSignal()").constData()); - } + generateCtorArguments(ctorindex); fprintf(out, ");\n"); fprintf(out, " if (_a[0]) *reinterpret_cast<%s**>(_a[0]) = _r; } break;\n", (cdef->hasQGadget || cdef->hasQNamespace) ? "void" : "QObject"); } fprintf(out, " default: break;\n"); fprintf(out, " }\n"); + fprintf(out, " } else if (_c == QMetaObject::ConstructInPlace) {\n"); + fprintf(out, " switch (_id) {\n"); + for (int ctorindex = 0; ctorindex < ctorend; ++ctorindex) { + fprintf(out, " case %d: { new (_a[0]) %s(", + ctorindex, cdef->classname.constData()); + generateCtorArguments(ctorindex); + fprintf(out, "); } break;\n"); + } + fprintf(out, " default: break;\n"); + fprintf(out, " }\n"); fprintf(out, " }"); needElse = true; isUsed_a = true; @@ -1142,16 +1111,17 @@ void Generator::generateStaticMetacall() if (f.isRawSlot) { fprintf(out, "QMethodRawArguments{ _a }"); } else { - int argsCount = f.arguments.count(); - for (int j = 0; j < argsCount; ++j) { - const ArgumentDef &a = f.arguments.at(j); - if (j) + const auto begin = f.arguments.cbegin(); + const auto end = f.arguments.cend(); + for (auto it = begin; it != end; ++it) { + const ArgumentDef &a = *it; + if (it != begin) fprintf(out, ","); fprintf(out, "(*reinterpret_cast< %s>(_a[%d]))",a.typeNameForCast.constData(), offset++); isUsed_a = true; } if (f.isPrivateSignal) { - if (argsCount > 0) + if (!f.arguments.isEmpty()) fprintf(out, ", "); fprintf(out, "%s", "QPrivateSignal()"); } @@ -1204,7 +1174,7 @@ void Generator::generateStaticMetacall() fprintf(out, " else if (_c == QMetaObject::IndexOfMethod) {\n"); fprintf(out, " int *result = reinterpret_cast<int *>(_a[0]);\n"); bool anythingUsed = false; - for (int methodindex = 0; methodindex < cdef->signalList.size(); ++methodindex) { + for (int methodindex = 0; methodindex < int(cdef->signalList.size()); ++methodindex) { const FunctionDef &f = cdef->signalList.at(methodindex); if (f.wasCloned || !f.inPrivateClass.isEmpty() || f.isStatic) continue; @@ -1212,15 +1182,16 @@ void Generator::generateStaticMetacall() fprintf(out, " {\n"); fprintf(out, " using _t = %s (%s::*)(",f.type.rawName.constData() , cdef->classname.constData()); - int argsCount = f.arguments.count(); - for (int j = 0; j < argsCount; ++j) { - const ArgumentDef &a = f.arguments.at(j); - if (j) + const auto begin = f.arguments.cbegin(); + const auto end = f.arguments.cend(); + for (auto it = begin; it != end; ++it) { + const ArgumentDef &a = *it; + if (it != begin) fprintf(out, ", "); fprintf(out, "%s", QByteArray(a.type.name + ' ' + a.rightType).constData()); } if (f.isPrivateSignal) { - if (argsCount > 0) + if (!f.arguments.isEmpty()) fprintf(out, ", "); fprintf(out, "%s", "QPrivateSignal"); } @@ -1228,7 +1199,7 @@ void Generator::generateStaticMetacall() fprintf(out, ") const;\n"); else fprintf(out, ");\n"); - fprintf(out, " if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&%s::%s)) {\n", + fprintf(out, " if (_t _q_method = &%s::%s; *reinterpret_cast<_t *>(_a[1]) == _q_method) {\n", cdef->classname.constData(), f.name.constData()); fprintf(out, " *result = %d;\n", methodindex); fprintf(out, " return;\n"); @@ -1260,7 +1231,7 @@ void Generator::generateStaticMetacall() fprintf(out, " *reinterpret_cast<int*>(_a[0]) = qRegisterMetaType< %s >(); break;\n", lastKey.constData()); } fprintf(out, " }\n"); - fprintf(out, " }\n"); + fprintf(out, " } "); isUsed_a = true; needElse = true; } @@ -1271,8 +1242,7 @@ void Generator::generateStaticMetacall() bool needSet = false; bool needReset = false; bool hasBindableProperties = false; - for (int i = 0; i < cdef->propertyList.size(); ++i) { - const PropertyDef &p = cdef->propertyList.at(i); + for (const PropertyDef &p : std::as_const(cdef->propertyList)) { needGet |= !p.read.isEmpty() || !p.member.isEmpty(); if (!p.read.isEmpty() || !p.member.isEmpty()) needTempVarForGet |= (p.gspec != PropertyDef::PointerSpec @@ -1282,10 +1252,8 @@ void Generator::generateStaticMetacall() needReset |= !p.reset.isEmpty(); hasBindableProperties |= !p.bind.isEmpty(); } - fprintf(out, "\n#ifndef QT_NO_PROPERTIES\n "); - if (needElse) - fprintf(out, "else "); + fprintf(out, " else "); fprintf(out, "if (_c == QMetaObject::ReadProperty) {\n"); auto setupMemberAccess = [this]() { @@ -1305,7 +1273,7 @@ void Generator::generateStaticMetacall() if (needTempVarForGet) fprintf(out, " void *_v = _a[0];\n"); fprintf(out, " switch (_id) {\n"); - for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) { + for (int propindex = 0; propindex < int(cdef->propertyList.size()); ++propindex) { const PropertyDef &p = cdef->propertyList.at(propindex); if (p.read.isEmpty() && p.member.isEmpty()) continue; @@ -1323,6 +1291,9 @@ void Generator::generateStaticMetacall() else if (cdef->enumDeclarations.value(p.type, false)) fprintf(out, " case %d: *reinterpret_cast<int*>(_v) = QFlag(%s%s()); break;\n", propindex, prefix.constData(), p.read.constData()); + else if (p.read == "default") + fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s().value(); break;\n", + propindex, p.type.constData(), prefix.constData(), p.bind.constData()); else if (!p.read.isEmpty()) fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s(); break;\n", propindex, p.type.constData(), prefix.constData(), p.read.constData()); @@ -1343,7 +1314,7 @@ void Generator::generateStaticMetacall() setupMemberAccess(); fprintf(out, " void *_v = _a[0];\n"); fprintf(out, " switch (_id) {\n"); - for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) { + for (int propindex = 0; propindex < int(cdef->propertyList.size()); ++propindex) { const PropertyDef &p = cdef->propertyList.at(propindex); if (p.constant) continue; @@ -1356,6 +1327,12 @@ void Generator::generateStaticMetacall() if (cdef->enumDeclarations.value(p.type, false)) { fprintf(out, " case %d: %s%s(QFlag(*reinterpret_cast<int*>(_v))); break;\n", propindex, prefix.constData(), p.write.constData()); + } else if (p.write == "default") { + fprintf(out, " case %d: {\n", propindex); + fprintf(out, " %s%s().setValue(*reinterpret_cast< %s*>(_v));\n", + prefix.constData(), p.bind.constData(), p.type.constData()); + fprintf(out, " break;\n"); + fprintf(out, " }\n"); } else if (!p.write.isEmpty()) { fprintf(out, " case %d: %s%s(*reinterpret_cast< %s*>(_v)); break;\n", propindex, prefix.constData(), p.write.constData(), p.type.constData()); @@ -1390,15 +1367,15 @@ void Generator::generateStaticMetacall() if (needReset) { setupMemberAccess(); fprintf(out, " switch (_id) {\n"); - for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) { + for (int propindex = 0; propindex < int(cdef->propertyList.size()); ++propindex) { const PropertyDef &p = cdef->propertyList.at(propindex); - if (!p.reset.endsWith(')')) + if (p.reset.isEmpty()) continue; QByteArray prefix = "_t->"; if (p.inPrivateClass.size()) { prefix += p.inPrivateClass + "->"; } - fprintf(out, " case %d: %s%s; break;\n", + fprintf(out, " case %d: %s%s(); break;\n", propindex, prefix.constData(), p.reset.constData()); } fprintf(out, " default: break;\n"); @@ -1411,7 +1388,7 @@ void Generator::generateStaticMetacall() if (hasBindableProperties) { setupMemberAccess(); fprintf(out, " switch (_id) {\n"); - for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) { + for (int propindex = 0; propindex < int(cdef->propertyList.size()); ++propindex) { const PropertyDef &p = cdef->propertyList.at(propindex); if (p.bind.isEmpty()) continue; @@ -1428,7 +1405,6 @@ void Generator::generateStaticMetacall() fprintf(out, " }\n"); } fprintf(out, " }"); - fprintf(out, "\n#endif // QT_NO_PROPERTIES"); needElse = true; } @@ -1445,10 +1421,10 @@ void Generator::generateStaticMetacall() if (!isUsed_a) fprintf(out, " (void)_a;\n"); - fprintf(out, "}\n\n"); + fprintf(out, "}\n"); } -void Generator::generateSignal(FunctionDef *def,int index) +void Generator::generateSignal(const FunctionDef *def, int index) { if (def->wasCloned || def->isAbstract) return; @@ -1472,9 +1448,11 @@ void Generator::generateSignal(FunctionDef *def,int index) } int offset = 1; - for (int j = 0; j < def->arguments.count(); ++j) { - const ArgumentDef &a = def->arguments.at(j); - if (j) + const auto begin = def->arguments.cbegin(); + const auto end = def->arguments.cend(); + for (auto it = begin; it != end; ++it) { + const ArgumentDef &a = *it; + if (it != begin) fputs(", ", out); if (a.type.name.size()) fputs(a.type.name.constData(), out); @@ -1505,7 +1483,7 @@ void Generator::generateSignal(FunctionDef *def,int index) } int i; for (i = 1; i < offset; ++i) - if (i <= def->arguments.count() && def->arguments.at(i - 1).type.isVolatile) + if (i <= def->arguments.size() && def->arguments.at(i - 1).type.isVolatile) fprintf(out, ", const_cast<void*>(reinterpret_cast<const volatile void*>(std::addressof(_t%d)))", i); else fprintf(out, ", const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t%d)))", i); @@ -1564,8 +1542,7 @@ static CborError jsonValueToCbor(CborEncoder *parent, const QJsonValue &v) return cbor_encode_double(parent, d); } } - Q_UNREACHABLE(); - return CborUnknownError; + Q_UNREACHABLE_RETURN(CborUnknownError); } void Generator::generatePluginMetaData() @@ -1573,62 +1550,76 @@ void Generator::generatePluginMetaData() if (cdef->pluginData.iid.isEmpty()) return; - fprintf(out, "\nQT_PLUGIN_METADATA_SECTION\n" - "static constexpr unsigned char qt_pluginMetaData_%s[] = {\n" - " 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!',\n" - " // metadata version, Qt version, architectural requirements\n" - " 0, QT_VERSION_MAJOR, QT_VERSION_MINOR, qPluginArchRequirements(),", - cdef->classname.constData()); + auto outputCborData = [this]() { + CborDevice dev(out); + CborEncoder enc; + cbor_encoder_init_writer(&enc, CborDevice::callback, &dev); + CborEncoder map; + cbor_encoder_create_map(&enc, &map, CborIndefiniteLength); - CborDevice dev(out); - CborEncoder enc; - cbor_encoder_init_writer(&enc, CborDevice::callback, &dev); - - CborEncoder map; - cbor_encoder_create_map(&enc, &map, CborIndefiniteLength); + dev.nextItem("\"IID\""); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::IID)); + cbor_encode_text_string(&map, cdef->pluginData.iid.constData(), cdef->pluginData.iid.size()); - dev.nextItem("\"IID\""); - cbor_encode_int(&map, int(QtPluginMetaDataKeys::IID)); - cbor_encode_text_string(&map, cdef->pluginData.iid.constData(), cdef->pluginData.iid.size()); + dev.nextItem("\"className\""); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::ClassName)); + cbor_encode_text_string(&map, cdef->classname.constData(), cdef->classname.size()); - dev.nextItem("\"className\""); - cbor_encode_int(&map, int(QtPluginMetaDataKeys::ClassName)); - cbor_encode_text_string(&map, cdef->classname.constData(), cdef->classname.size()); - - QJsonObject o = cdef->pluginData.metaData.object(); - if (!o.isEmpty()) { - dev.nextItem("\"MetaData\""); - cbor_encode_int(&map, int(QtPluginMetaDataKeys::MetaData)); - jsonObjectToCbor(&map, o); - } + QJsonObject o = cdef->pluginData.metaData.object(); + if (!o.isEmpty()) { + dev.nextItem("\"MetaData\""); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::MetaData)); + jsonObjectToCbor(&map, o); + } - if (!cdef->pluginData.uri.isEmpty()) { - dev.nextItem("\"URI\""); - cbor_encode_int(&map, int(QtPluginMetaDataKeys::URI)); - cbor_encode_text_string(&map, cdef->pluginData.uri.constData(), cdef->pluginData.uri.size()); - } + if (!cdef->pluginData.uri.isEmpty()) { + dev.nextItem("\"URI\""); + cbor_encode_int(&map, int(QtPluginMetaDataKeys::URI)); + cbor_encode_text_string(&map, cdef->pluginData.uri.constData(), cdef->pluginData.uri.size()); + } - // Add -M args from the command line: - for (auto it = cdef->pluginData.metaArgs.cbegin(), end = cdef->pluginData.metaArgs.cend(); it != end; ++it) { - const QJsonArray &a = it.value(); - QByteArray key = it.key().toUtf8(); - dev.nextItem(QByteArray("command-line \"" + key + "\"").constData()); - cbor_encode_text_string(&map, key.constData(), key.size()); - jsonArrayToCbor(&map, a); - } + // Add -M args from the command line: + for (auto it = cdef->pluginData.metaArgs.cbegin(), end = cdef->pluginData.metaArgs.cend(); it != end; ++it) { + const QJsonArray &a = it.value(); + QByteArray key = it.key().toUtf8(); + dev.nextItem(QByteArray("command-line \"" + key + "\"").constData()); + cbor_encode_text_string(&map, key.constData(), key.size()); + jsonArrayToCbor(&map, a); + } - // Close the CBOR map manually - dev.nextItem(); - cbor_encoder_close_container(&enc, &map); - fputs("\n};\n", out); + // Close the CBOR map manually + dev.nextItem(); + cbor_encoder_close_container(&enc, &map); + }; // 'Use' all namespaces. - int pos = cdef->qualified.indexOf("::"); + qsizetype pos = cdef->qualified.indexOf("::"); for ( ; pos != -1 ; pos = cdef->qualified.indexOf("::", pos + 2) ) fprintf(out, "using namespace %s;\n", cdef->qualified.left(pos).constData()); - fprintf(out, "QT_MOC_EXPORT_PLUGIN(%s, %s)\n\n", + + fputs("\n#ifdef QT_MOC_EXPORT_PLUGIN_V2", out); + + // Qt 6.3+ output + fprintf(out, "\nstatic constexpr unsigned char qt_pluginMetaDataV2_%s[] = {", + cdef->classname.constData()); + outputCborData(); + fprintf(out, "\n};\nQT_MOC_EXPORT_PLUGIN_V2(%s, %s, qt_pluginMetaDataV2_%s)\n", + cdef->qualified.constData(), cdef->classname.constData(), cdef->classname.constData()); + + // compatibility with Qt 6.0-6.2 + fprintf(out, "#else\nQT_PLUGIN_METADATA_SECTION\n" + "Q_CONSTINIT static constexpr unsigned char qt_pluginMetaData_%s[] = {\n" + " 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!',\n" + " // metadata version, Qt version, architectural requirements\n" + " 0, QT_VERSION_MAJOR, QT_VERSION_MINOR, qPluginArchRequirements(),", + cdef->classname.constData()); + outputCborData(); + fprintf(out, "\n};\nQT_MOC_EXPORT_PLUGIN(%s, %s)\n" + "#endif // QT_MOC_EXPORT_PLUGIN_V2\n", cdef->qualified.constData(), cdef->classname.constData()); + + fputs("\n", out); } QT_WARNING_DISABLE_GCC("-Wunused-function") diff --git a/src/tools/moc/generator.h b/src/tools/moc/generator.h index 35128cb543..2d4d69ca05 100644 --- a/src/tools/moc/generator.h +++ b/src/tools/moc/generator.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef GENERATOR_H #define GENERATOR_H @@ -35,16 +10,19 @@ QT_BEGIN_NAMESPACE class Generator { + Moc *parser = nullptr; FILE *out; ClassDef *cdef; QList<uint> meta_data; public: - Generator(ClassDef *classDef, const QList<QByteArray> &metaTypes, + Generator(Moc *moc, ClassDef *classDef, const QList<QByteArray> &metaTypes, const QHash<QByteArray, QByteArray> &knownQObjectClasses, const QHash<QByteArray, QByteArray> &knownGadgets, FILE *outfile = nullptr, bool requireCompleteTypes = false); void generateCode(); + qsizetype registeredStringsCount() { return strings.size(); }; + private: bool registerableMetaType(const QByteArray &propertyType); void registerClassInfoStrings(); @@ -62,7 +40,7 @@ private: void generateProperties(); void generateMetacall(); void generateStaticMetacall(); - void generateSignal(FunctionDef *def, int index); + void generateSignal(const FunctionDef *def, int index); void generatePluginMetaData(); QMultiMap<QByteArray, int> automaticPropertyMetaTypesHelper(); QMap<int, QMultiMap<QByteArray, int>> diff --git a/src/tools/moc/keywords.cpp b/src/tools/moc/keywords.cpp index cc7d747f5b..6a1f58490f 100644 --- a/src/tools/moc/keywords.cpp +++ b/src/tools/moc/keywords.cpp @@ -1,41 +1,16 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // auto generated // DO NOT EDIT. static const short keyword_trans[][128] = { - {0,0,0,0,0,0,0,0,0,579,576,0,0,0,0,0, + {0,0,0,0,0,0,0,0,0,618,615,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 579,252,577,580,8,38,239,578,25,26,236,234,30,235,27,237, + 618,252,616,619,8,38,239,617,25,26,236,234,30,235,27,237, 22,22,22,22,22,22,22,22,22,22,34,41,23,39,24,43, 0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,21,8,8,8,8,8,8,8,8,8,31,582,32,238,8, + 8,21,8,8,8,8,8,8,8,8,8,31,621,32,238,8, 0,1,2,3,4,5,6,7,8,9,8,8,10,11,12,13, 14,8,15,16,17,18,19,20,8,8,8,36,245,37,248,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -116,7 +91,7 @@ static const short keyword_trans[][128] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,290,222,0,0,497,0,0,0, + 0,0,0,0,0,0,0,0,290,222,0,0,524,0,0,0, 0,0,0,0,55,0,0,330,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -155,7 +130,7 @@ static const short keyword_trans[][128] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,521,0,0,0,0,0,0,0,0,0,0,357, + 0,0,0,0,401,0,0,0,0,0,0,0,0,0,0,357, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -177,7 +152,7 @@ static const short keyword_trans[][128] = { {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,42,0,0,0,28,0, - 585,585,585,585,585,585,585,585,585,585,0,0,0,0,0,0, + 624,624,624,624,624,624,624,624,624,624,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -336,7 +311,7 @@ static const short keyword_trans[][128] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,584,0,0,0,0,583, + 0,0,0,0,0,0,0,0,0,0,623,0,0,0,0,622, 0,0,0,0,0,0,0,0,0,0,0,0,0,258,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -372,29 +347,29 @@ static const short keyword_trans[][128] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,494,0,0,0,300,0,0,0,0,0,0,0,0,0,0, + 0,521,0,0,0,300,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,475,424,408,416,380,0,484,0,0,0,565,364,358, - 386,0,557,472,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,502,451,435,443,380,0,511,0,0,0,604,364,358, + 393,0,596,499,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,394,0,0,0, - 0,0,387,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,421,0,0,0, + 0,0,394,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,511,0,0,0,0,0,388, + 0,0,0,0,0,0,0,0,0,538,0,0,0,0,0,395, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, @@ -402,48 +377,64 @@ static const short keyword_trans[][128] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,403,0,0,0,0,0,0,0,0,0,0,0,548,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,412,0,0,0,0,0,0,0,0,0,0,0,413, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,582,0,0,0,0,0,415, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,420,0,0,0,0,0,0,0,0,0,0,0,421, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,454,432,0,0,437,0,0,0,446,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,439,0,0,0,0,0,0,0,0,0,0,0,440, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,540,0,473,0,0,0,501,0,0,507,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,447,0,0,0,0,0,0,0,0,0,0,0,448, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,481,459,0,0,464,0,0,0,473,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,486,0,533,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,565,0,500,0,0,0,528,0,0,534,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 549,0,0,517,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,513,0,558,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 574,0,0,544,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} }; @@ -841,85 +832,85 @@ static const struct {CHARACTER, 0, 71, 383, CHARACTER}, {CHARACTER, 0, 69, 384, CHARACTER}, {CHARACTER, 0, 84, 385, CHARACTER}, - {Q_GADGET_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 44, 0, 0, CHARACTER}, - {CHARACTER, 45, 0, 0, CHARACTER}, + {Q_GADGET_TOKEN, 0, 95, 386, CHARACTER}, + {CHARACTER, 0, 69, 387, CHARACTER}, + {CHARACTER, 0, 88, 388, CHARACTER}, {CHARACTER, 0, 80, 389, CHARACTER}, - {CHARACTER, 0, 69, 390, CHARACTER}, + {CHARACTER, 0, 79, 390, CHARACTER}, {CHARACTER, 0, 82, 391, CHARACTER}, {CHARACTER, 0, 84, 392, CHARACTER}, - {CHARACTER, 0, 89, 393, CHARACTER}, + {Q_GADGET_EXPORT_TOKEN, 0, 0, 0, CHARACTER}, + {CHARACTER, 44, 0, 0, CHARACTER}, + {CHARACTER, 45, 0, 0, CHARACTER}, + {CHARACTER, 0, 80, 396, CHARACTER}, + {CHARACTER, 0, 69, 397, CHARACTER}, + {CHARACTER, 0, 82, 398, CHARACTER}, + {CHARACTER, 0, 84, 399, CHARACTER}, + {CHARACTER, 0, 89, 400, CHARACTER}, {Q_PROPERTY_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 85, 395, CHARACTER}, - {CHARACTER, 0, 71, 396, CHARACTER}, - {CHARACTER, 0, 73, 397, CHARACTER}, - {CHARACTER, 0, 78, 398, CHARACTER}, - {CHARACTER, 0, 95, 399, CHARACTER}, - {CHARACTER, 0, 77, 400, CHARACTER}, - {CHARACTER, 0, 69, 401, CHARACTER}, - {CHARACTER, 0, 84, 402, CHARACTER}, - {CHARACTER, 0, 65, 403, CHARACTER}, - {CHARACTER, 0, 68, 404, CHARACTER}, - {CHARACTER, 0, 65, 405, CHARACTER}, - {CHARACTER, 0, 84, 406, CHARACTER}, - {CHARACTER, 0, 65, 407, CHARACTER}, - {Q_PLUGIN_METADATA_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 78, 409, CHARACTER}, + {CHARACTER, 0, 95, 402, CHARACTER}, + {CHARACTER, 46, 0, 0, CHARACTER}, + {CHARACTER, 0, 78, 404, CHARACTER}, + {CHARACTER, 0, 79, 405, CHARACTER}, + {CHARACTER, 0, 78, 406, CHARACTER}, + {CHARACTER, 0, 89, 407, CHARACTER}, + {CHARACTER, 0, 77, 408, CHARACTER}, + {CHARACTER, 0, 79, 409, CHARACTER}, {CHARACTER, 0, 85, 410, CHARACTER}, - {CHARACTER, 0, 77, 411, CHARACTER}, - {Q_ENUM_TOKEN, 46, 0, 0, CHARACTER}, + {CHARACTER, 0, 83, 411, CHARACTER}, + {CHARACTER, 0, 95, 412, CHARACTER}, + {CHARACTER, 0, 80, 413, CHARACTER}, + {CHARACTER, 0, 82, 414, CHARACTER}, + {CHARACTER, 47, 0, 0, CHARACTER}, + {CHARACTER, 0, 80, 416, CHARACTER}, + {CHARACTER, 0, 69, 417, CHARACTER}, + {CHARACTER, 0, 82, 418, CHARACTER}, + {CHARACTER, 0, 84, 419, CHARACTER}, + {CHARACTER, 0, 89, 420, CHARACTER}, + {QT_ANONYMOUS_PROPERTY_TOKEN, 0, 0, 0, CHARACTER}, + {CHARACTER, 0, 85, 422, CHARACTER}, + {CHARACTER, 0, 71, 423, CHARACTER}, + {CHARACTER, 0, 73, 424, CHARACTER}, + {CHARACTER, 0, 78, 425, CHARACTER}, + {CHARACTER, 0, 95, 426, CHARACTER}, + {CHARACTER, 0, 77, 427, CHARACTER}, + {CHARACTER, 0, 69, 428, CHARACTER}, + {CHARACTER, 0, 84, 429, CHARACTER}, + {CHARACTER, 0, 65, 430, CHARACTER}, + {CHARACTER, 0, 68, 431, CHARACTER}, + {CHARACTER, 0, 65, 432, CHARACTER}, + {CHARACTER, 0, 84, 433, CHARACTER}, + {CHARACTER, 0, 65, 434, CHARACTER}, + {Q_PLUGIN_METADATA_TOKEN, 0, 0, 0, CHARACTER}, + {CHARACTER, 0, 78, 436, CHARACTER}, + {CHARACTER, 0, 85, 437, CHARACTER}, + {CHARACTER, 0, 77, 438, CHARACTER}, + {Q_ENUM_TOKEN, 48, 0, 0, CHARACTER}, {Q_ENUMS_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 78, 414, CHARACTER}, - {CHARACTER, 0, 83, 415, CHARACTER}, + {CHARACTER, 0, 78, 441, CHARACTER}, + {CHARACTER, 0, 83, 442, CHARACTER}, {Q_ENUM_NS_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 76, 417, CHARACTER}, - {CHARACTER, 0, 65, 418, CHARACTER}, - {CHARACTER, 0, 71, 419, CHARACTER}, - {Q_FLAG_TOKEN, 47, 0, 0, CHARACTER}, + {CHARACTER, 0, 76, 444, CHARACTER}, + {CHARACTER, 0, 65, 445, CHARACTER}, + {CHARACTER, 0, 71, 446, CHARACTER}, + {Q_FLAG_TOKEN, 49, 0, 0, CHARACTER}, {Q_FLAGS_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 78, 422, CHARACTER}, - {CHARACTER, 0, 83, 423, CHARACTER}, + {CHARACTER, 0, 78, 449, CHARACTER}, + {CHARACTER, 0, 83, 450, CHARACTER}, {Q_FLAG_NS_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 69, 425, CHARACTER}, - {CHARACTER, 0, 67, 426, CHARACTER}, - {CHARACTER, 0, 76, 427, CHARACTER}, - {CHARACTER, 0, 65, 428, CHARACTER}, - {CHARACTER, 0, 82, 429, CHARACTER}, - {CHARACTER, 0, 69, 430, CHARACTER}, - {CHARACTER, 0, 95, 431, CHARACTER}, - {CHARACTER, 48, 0, 0, CHARACTER}, - {CHARACTER, 0, 76, 433, CHARACTER}, - {CHARACTER, 0, 65, 434, CHARACTER}, - {CHARACTER, 0, 71, 435, CHARACTER}, - {CHARACTER, 0, 83, 436, CHARACTER}, - {Q_DECLARE_FLAGS_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 78, 438, CHARACTER}, - {CHARACTER, 0, 84, 439, CHARACTER}, - {CHARACTER, 0, 69, 440, CHARACTER}, - {CHARACTER, 0, 82, 441, CHARACTER}, - {CHARACTER, 0, 70, 442, CHARACTER}, - {CHARACTER, 0, 65, 443, CHARACTER}, - {CHARACTER, 0, 67, 444, CHARACTER}, - {CHARACTER, 0, 69, 445, CHARACTER}, - {Q_DECLARE_INTERFACE_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 69, 447, CHARACTER}, - {CHARACTER, 0, 84, 448, CHARACTER}, - {CHARACTER, 0, 65, 449, CHARACTER}, - {CHARACTER, 0, 84, 450, CHARACTER}, - {CHARACTER, 0, 89, 451, CHARACTER}, - {CHARACTER, 0, 80, 452, CHARACTER}, - {CHARACTER, 0, 69, 453, CHARACTER}, - {Q_DECLARE_METATYPE_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 88, 455, CHARACTER}, - {CHARACTER, 0, 84, 456, CHARACTER}, + {CHARACTER, 0, 69, 452, CHARACTER}, + {CHARACTER, 0, 67, 453, CHARACTER}, + {CHARACTER, 0, 76, 454, CHARACTER}, + {CHARACTER, 0, 65, 455, CHARACTER}, + {CHARACTER, 0, 82, 456, CHARACTER}, {CHARACTER, 0, 69, 457, CHARACTER}, - {CHARACTER, 0, 78, 458, CHARACTER}, - {CHARACTER, 0, 83, 459, CHARACTER}, - {CHARACTER, 0, 73, 460, CHARACTER}, - {CHARACTER, 0, 79, 461, CHARACTER}, - {CHARACTER, 0, 78, 462, CHARACTER}, - {CHARACTER, 0, 95, 463, CHARACTER}, - {CHARACTER, 0, 73, 464, CHARACTER}, + {CHARACTER, 0, 95, 458, CHARACTER}, + {CHARACTER, 50, 0, 0, CHARACTER}, + {CHARACTER, 0, 76, 460, CHARACTER}, + {CHARACTER, 0, 65, 461, CHARACTER}, + {CHARACTER, 0, 71, 462, CHARACTER}, + {CHARACTER, 0, 83, 463, CHARACTER}, + {Q_DECLARE_FLAGS_TOKEN, 0, 0, 0, CHARACTER}, {CHARACTER, 0, 78, 465, CHARACTER}, {CHARACTER, 0, 84, 466, CHARACTER}, {CHARACTER, 0, 69, 467, CHARACTER}, @@ -927,116 +918,155 @@ static const struct {CHARACTER, 0, 70, 469, CHARACTER}, {CHARACTER, 0, 65, 470, CHARACTER}, {CHARACTER, 0, 67, 471, CHARACTER}, - {CHARACTER, 0, 69, 445, CHARACTER}, - {CHARACTER, 49, 0, 0, CHARACTER}, - {CHARACTER, 0, 84, 474, CHARACTER}, - {CHARACTER, 0, 83, 420, CHARACTER}, - {CHARACTER, 0, 76, 476, CHARACTER}, - {CHARACTER, 0, 65, 477, CHARACTER}, - {CHARACTER, 0, 83, 478, CHARACTER}, - {CHARACTER, 0, 83, 479, CHARACTER}, - {CHARACTER, 0, 73, 480, CHARACTER}, - {CHARACTER, 0, 78, 481, CHARACTER}, - {CHARACTER, 0, 70, 482, CHARACTER}, - {CHARACTER, 0, 79, 483, CHARACTER}, - {Q_CLASSINFO_TOKEN, 0, 0, 0, CHARACTER}, + {CHARACTER, 0, 69, 472, CHARACTER}, + {Q_DECLARE_INTERFACE_TOKEN, 0, 0, 0, CHARACTER}, + {CHARACTER, 0, 69, 474, CHARACTER}, + {CHARACTER, 0, 84, 475, CHARACTER}, + {CHARACTER, 0, 65, 476, CHARACTER}, + {CHARACTER, 0, 84, 477, CHARACTER}, + {CHARACTER, 0, 89, 478, CHARACTER}, + {CHARACTER, 0, 80, 479, CHARACTER}, + {CHARACTER, 0, 69, 480, CHARACTER}, + {Q_DECLARE_METATYPE_TOKEN, 0, 0, 0, CHARACTER}, + {CHARACTER, 0, 88, 482, CHARACTER}, + {CHARACTER, 0, 84, 483, CHARACTER}, + {CHARACTER, 0, 69, 484, CHARACTER}, {CHARACTER, 0, 78, 485, CHARACTER}, - {CHARACTER, 50, 0, 0, CHARACTER}, - {CHARACTER, 0, 69, 487, CHARACTER}, - {CHARACTER, 0, 82, 488, CHARACTER}, - {CHARACTER, 0, 70, 489, CHARACTER}, - {CHARACTER, 0, 65, 490, CHARACTER}, - {CHARACTER, 0, 67, 491, CHARACTER}, - {CHARACTER, 0, 69, 492, CHARACTER}, - {CHARACTER, 0, 83, 493, CHARACTER}, + {CHARACTER, 0, 83, 486, CHARACTER}, + {CHARACTER, 0, 73, 487, CHARACTER}, + {CHARACTER, 0, 79, 488, CHARACTER}, + {CHARACTER, 0, 78, 489, CHARACTER}, + {CHARACTER, 0, 95, 490, CHARACTER}, + {CHARACTER, 0, 73, 491, CHARACTER}, + {CHARACTER, 0, 78, 492, CHARACTER}, + {CHARACTER, 0, 84, 493, CHARACTER}, + {CHARACTER, 0, 69, 494, CHARACTER}, + {CHARACTER, 0, 82, 495, CHARACTER}, + {CHARACTER, 0, 70, 496, CHARACTER}, + {CHARACTER, 0, 65, 497, CHARACTER}, + {CHARACTER, 0, 67, 498, CHARACTER}, + {CHARACTER, 0, 69, 472, CHARACTER}, + {CHARACTER, 51, 0, 0, CHARACTER}, + {CHARACTER, 0, 84, 501, CHARACTER}, + {CHARACTER, 0, 83, 447, CHARACTER}, + {CHARACTER, 0, 76, 503, CHARACTER}, + {CHARACTER, 0, 65, 504, CHARACTER}, + {CHARACTER, 0, 83, 505, CHARACTER}, + {CHARACTER, 0, 83, 506, CHARACTER}, + {CHARACTER, 0, 73, 507, CHARACTER}, + {CHARACTER, 0, 78, 508, CHARACTER}, + {CHARACTER, 0, 70, 509, CHARACTER}, + {CHARACTER, 0, 79, 510, CHARACTER}, + {Q_CLASSINFO_TOKEN, 0, 0, 0, CHARACTER}, + {CHARACTER, 0, 78, 512, CHARACTER}, + {CHARACTER, 52, 0, 0, CHARACTER}, + {CHARACTER, 0, 69, 514, CHARACTER}, + {CHARACTER, 0, 82, 515, CHARACTER}, + {CHARACTER, 0, 70, 516, CHARACTER}, + {CHARACTER, 0, 65, 517, CHARACTER}, + {CHARACTER, 0, 67, 518, CHARACTER}, + {CHARACTER, 0, 69, 519, CHARACTER}, + {CHARACTER, 0, 83, 520, CHARACTER}, {Q_INTERFACES_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 108, 495, CHARACTER}, - {CHARACTER, 0, 115, 496, CHARACTER}, + {CHARACTER, 0, 108, 522, CHARACTER}, + {CHARACTER, 0, 115, 523, CHARACTER}, {SIGNALS, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 111, 498, CHARACTER}, - {CHARACTER, 0, 116, 499, CHARACTER}, - {CHARACTER, 0, 115, 500, CHARACTER}, + {CHARACTER, 0, 111, 525, CHARACTER}, + {CHARACTER, 0, 116, 526, CHARACTER}, + {CHARACTER, 0, 115, 527, CHARACTER}, {SLOTS, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 71, 502, CHARACTER}, - {CHARACTER, 0, 78, 503, CHARACTER}, - {CHARACTER, 0, 65, 504, CHARACTER}, - {CHARACTER, 0, 76, 505, CHARACTER}, - {Q_SIGNAL_TOKEN, 0, 83, 506, CHARACTER}, + {CHARACTER, 0, 71, 529, CHARACTER}, + {CHARACTER, 0, 78, 530, CHARACTER}, + {CHARACTER, 0, 65, 531, CHARACTER}, + {CHARACTER, 0, 76, 532, CHARACTER}, + {Q_SIGNAL_TOKEN, 0, 83, 533, CHARACTER}, {Q_SIGNALS_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 79, 508, CHARACTER}, - {CHARACTER, 0, 84, 509, CHARACTER}, - {Q_SLOT_TOKEN, 0, 83, 510, CHARACTER}, + {CHARACTER, 0, 79, 535, CHARACTER}, + {CHARACTER, 0, 84, 536, CHARACTER}, + {Q_SLOT_TOKEN, 0, 83, 537, CHARACTER}, {Q_SLOTS_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 86, 512, CHARACTER}, - {CHARACTER, 0, 65, 513, CHARACTER}, - {CHARACTER, 0, 84, 514, CHARACTER}, - {CHARACTER, 0, 69, 515, CHARACTER}, - {CHARACTER, 0, 95, 516, CHARACTER}, - {CHARACTER, 51, 0, 0, CHARACTER}, - {CHARACTER, 0, 76, 518, CHARACTER}, - {CHARACTER, 0, 79, 519, CHARACTER}, - {CHARACTER, 0, 84, 520, CHARACTER}, + {CHARACTER, 0, 86, 539, CHARACTER}, + {CHARACTER, 0, 65, 540, CHARACTER}, + {CHARACTER, 0, 84, 541, CHARACTER}, + {CHARACTER, 0, 69, 542, CHARACTER}, + {CHARACTER, 0, 95, 543, CHARACTER}, + {CHARACTER, 53, 0, 0, CHARACTER}, + {CHARACTER, 0, 76, 545, CHARACTER}, + {CHARACTER, 0, 79, 546, CHARACTER}, + {CHARACTER, 0, 84, 547, CHARACTER}, {Q_PRIVATE_SLOT_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 95, 522, CHARACTER}, - {CHARACTER, 0, 77, 523, CHARACTER}, - {CHARACTER, 0, 79, 524, CHARACTER}, - {CHARACTER, 0, 67, 525, CHARACTER}, - {CHARACTER, 0, 95, 526, CHARACTER}, - {CHARACTER, 0, 67, 527, CHARACTER}, - {CHARACTER, 0, 79, 528, CHARACTER}, - {CHARACTER, 0, 77, 529, CHARACTER}, - {CHARACTER, 0, 80, 530, CHARACTER}, - {CHARACTER, 0, 65, 531, CHARACTER}, - {CHARACTER, 0, 84, 532, CHARACTER}, + {CHARACTER, 0, 79, 549, CHARACTER}, + {CHARACTER, 0, 67, 550, CHARACTER}, + {CHARACTER, 0, 95, 551, CHARACTER}, + {CHARACTER, 0, 67, 552, CHARACTER}, + {CHARACTER, 0, 79, 553, CHARACTER}, + {CHARACTER, 0, 77, 554, CHARACTER}, + {CHARACTER, 0, 80, 555, CHARACTER}, + {CHARACTER, 0, 65, 556, CHARACTER}, + {CHARACTER, 0, 84, 557, CHARACTER}, {Q_MOC_COMPAT_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 79, 534, CHARACTER}, - {CHARACTER, 0, 75, 535, CHARACTER}, - {CHARACTER, 0, 65, 536, CHARACTER}, - {CHARACTER, 0, 66, 537, CHARACTER}, - {CHARACTER, 0, 76, 538, CHARACTER}, - {CHARACTER, 0, 69, 539, CHARACTER}, + {CHARACTER, 0, 79, 559, CHARACTER}, + {CHARACTER, 0, 75, 560, CHARACTER}, + {CHARACTER, 0, 65, 561, CHARACTER}, + {CHARACTER, 0, 66, 562, CHARACTER}, + {CHARACTER, 0, 76, 563, CHARACTER}, + {CHARACTER, 0, 69, 564, CHARACTER}, {Q_INVOKABLE_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 82, 541, CHARACTER}, - {CHARACTER, 0, 73, 542, CHARACTER}, - {CHARACTER, 0, 80, 543, CHARACTER}, - {CHARACTER, 0, 84, 544, CHARACTER}, - {CHARACTER, 0, 65, 545, CHARACTER}, - {CHARACTER, 0, 66, 546, CHARACTER}, - {CHARACTER, 0, 76, 547, CHARACTER}, - {CHARACTER, 0, 69, 548, CHARACTER}, + {CHARACTER, 0, 82, 566, CHARACTER}, + {CHARACTER, 0, 73, 567, CHARACTER}, + {CHARACTER, 0, 80, 568, CHARACTER}, + {CHARACTER, 0, 84, 569, CHARACTER}, + {CHARACTER, 0, 65, 570, CHARACTER}, + {CHARACTER, 0, 66, 571, CHARACTER}, + {CHARACTER, 0, 76, 572, CHARACTER}, + {CHARACTER, 0, 69, 573, CHARACTER}, {Q_SCRIPTABLE_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 82, 550, CHARACTER}, - {CHARACTER, 0, 79, 551, CHARACTER}, - {CHARACTER, 0, 80, 552, CHARACTER}, - {CHARACTER, 0, 69, 553, CHARACTER}, - {CHARACTER, 0, 82, 554, CHARACTER}, - {CHARACTER, 0, 84, 555, CHARACTER}, - {CHARACTER, 0, 89, 556, CHARACTER}, + {CHARACTER, 0, 82, 575, CHARACTER}, + {CHARACTER, 0, 79, 576, CHARACTER}, + {CHARACTER, 0, 80, 577, CHARACTER}, + {CHARACTER, 0, 69, 578, CHARACTER}, + {CHARACTER, 0, 82, 579, CHARACTER}, + {CHARACTER, 0, 84, 580, CHARACTER}, + {CHARACTER, 0, 89, 581, CHARACTER}, {Q_PRIVATE_PROPERTY_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 69, 558, CHARACTER}, - {CHARACTER, 0, 86, 559, CHARACTER}, - {CHARACTER, 0, 73, 560, CHARACTER}, - {CHARACTER, 0, 83, 561, CHARACTER}, - {CHARACTER, 0, 73, 562, CHARACTER}, - {CHARACTER, 0, 79, 563, CHARACTER}, - {CHARACTER, 0, 78, 564, CHARACTER}, + {CHARACTER, 0, 86, 583, CHARACTER}, + {CHARACTER, 0, 65, 584, CHARACTER}, + {CHARACTER, 0, 84, 585, CHARACTER}, + {CHARACTER, 0, 69, 586, CHARACTER}, + {CHARACTER, 0, 95, 587, CHARACTER}, + {CHARACTER, 0, 80, 588, CHARACTER}, + {CHARACTER, 0, 82, 589, CHARACTER}, + {CHARACTER, 0, 79, 590, CHARACTER}, + {CHARACTER, 0, 80, 591, CHARACTER}, + {CHARACTER, 0, 69, 592, CHARACTER}, + {CHARACTER, 0, 82, 593, CHARACTER}, + {CHARACTER, 0, 84, 594, CHARACTER}, + {CHARACTER, 0, 89, 595, CHARACTER}, + {QT_ANONYMOUS_PRIVATE_PROPERTY_TOKEN, 0, 0, 0, CHARACTER}, + {CHARACTER, 0, 69, 597, CHARACTER}, + {CHARACTER, 0, 86, 598, CHARACTER}, + {CHARACTER, 0, 73, 599, CHARACTER}, + {CHARACTER, 0, 83, 600, CHARACTER}, + {CHARACTER, 0, 73, 601, CHARACTER}, + {CHARACTER, 0, 79, 602, CHARACTER}, + {CHARACTER, 0, 78, 603, CHARACTER}, {Q_REVISION_TOKEN, 0, 0, 0, CHARACTER}, - {CHARACTER, 0, 79, 566, CHARACTER}, - {CHARACTER, 0, 67, 567, CHARACTER}, - {CHARACTER, 0, 95, 568, CHARACTER}, - {CHARACTER, 0, 73, 569, CHARACTER}, - {CHARACTER, 0, 78, 570, CHARACTER}, - {CHARACTER, 0, 67, 571, CHARACTER}, - {CHARACTER, 0, 76, 572, CHARACTER}, - {CHARACTER, 0, 85, 573, CHARACTER}, - {CHARACTER, 0, 68, 574, CHARACTER}, - {CHARACTER, 0, 69, 575, CHARACTER}, + {CHARACTER, 0, 79, 605, CHARACTER}, + {CHARACTER, 0, 67, 606, CHARACTER}, + {CHARACTER, 0, 95, 607, CHARACTER}, + {CHARACTER, 0, 73, 608, CHARACTER}, + {CHARACTER, 0, 78, 609, CHARACTER}, + {CHARACTER, 0, 67, 610, CHARACTER}, + {CHARACTER, 0, 76, 611, CHARACTER}, + {CHARACTER, 0, 85, 612, CHARACTER}, + {CHARACTER, 0, 68, 613, CHARACTER}, + {CHARACTER, 0, 69, 614, CHARACTER}, {Q_MOC_INCLUDE_TOKEN, 0, 0, 0, CHARACTER}, {NEWLINE, 0, 0, 0, NOTOKEN}, {QUOTE, 0, 0, 0, NOTOKEN}, {SINGLEQUOTE, 0, 0, 0, NOTOKEN}, {WHITESPACE, 0, 0, 0, NOTOKEN}, - {HASH, 0, 35, 581, HASH}, + {HASH, 0, 35, 620, HASH}, {PP_HASHHASH, 0, 0, 0, NOTOKEN}, {BACKSLASH, 0, 0, 0, NOTOKEN}, {CPP_COMMENT, 0, 0, 0, NOTOKEN}, diff --git a/src/tools/moc/main.cpp b/src/tools/moc/main.cpp index 3e98fbf2b8..bb51352519 100644 --- a/src/tools/moc/main.cpp +++ b/src/tools/moc/main.cpp @@ -1,31 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2016 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <depfile_shared.h> #include "preprocessor.h" @@ -48,6 +23,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + /* This function looks at two file names and returns the name of the infile with a path relative to outfile. @@ -153,7 +130,7 @@ static QStringList argumentsFromCommandLineAndFile(const QStringList &arguments, allArguments.reserve(arguments.size()); for (const QString &argument : arguments) { // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it - if (argument.startsWith(QLatin1Char('@'))) { + if (argument.startsWith(u'@')) { QString optionsFile = argument; optionsFile.remove(0, 1); if (optionsFile.isEmpty()) { @@ -306,6 +283,10 @@ int runMoc(int argc, char **argv) jsonOption.setDescription(QStringLiteral("In addition to generating C++ code, create a machine-readable JSON file in a file that matches the output file and an extra .json extension.")); parser.addOption(jsonOption); + QCommandLineOption debugIncludesOption(QStringLiteral("debug-includes")); + debugIncludesOption.setDescription(QStringLiteral("Display debug messages of each considered include path.")); + parser.addOption(debugIncludesOption); + QCommandLineOption collectOption(QStringLiteral("collect-json")); collectOption.setDescription(QStringLiteral("Instead of processing C++ code, collect previously generated JSON output into a single file.")); parser.addOption(collectOption); @@ -349,8 +330,8 @@ int runMoc(int argc, char **argv) if (parser.isSet(collectOption)) return collectJson(files, output, hasOptionFiles); - if (files.count() > 1) { - error(qPrintable(QLatin1String("Too many input files specified: '") + files.join(QLatin1String("' '")) + QLatin1Char('\''))); + if (files.size() > 1) { + error(qPrintable("Too many input files specified: '"_L1 + files.join("' '"_L1) + u'\'')); parser.showHelp(1); } else if (!files.isEmpty()) { filename = files.first(); @@ -358,6 +339,7 @@ int runMoc(int argc, char **argv) const bool ignoreConflictingOptions = parser.isSet(ignoreConflictsOption); pp.preprocessOnly = parser.isSet(preprocessOption); + pp.setDebugIncludes(parser.isSet(debugIncludesOption)); if (parser.isSet(noIncludeOption)) { moc.noInclude = true; autoInclude = false; @@ -385,7 +367,7 @@ int runMoc(int argc, char **argv) for (const QString &path : includePaths) pp.includes += Preprocessor::IncludePath(QFile::encodeName(path)); QString compilerFlavor = parser.value(compilerFlavorOption); - if (compilerFlavor.isEmpty() || compilerFlavor == QLatin1String("unix")) { + if (compilerFlavor.isEmpty() || compilerFlavor == "unix"_L1) { // traditional Unix compilers use both CPATH and CPLUS_INCLUDE_PATH // $CPATH feeds to #include <...> and #include "...", whereas // CPLUS_INCLUDE_PATH is equivalent to GCC's -isystem, so we parse later @@ -395,14 +377,14 @@ int runMoc(int argc, char **argv) const auto cplus_include_path = qgetenv("CPLUS_INCLUDE_PATH").split(QDir::listSeparator().toLatin1()); for (const QByteArray &p : cplus_include_path) pp.includes += Preprocessor::IncludePath(p); - } else if (compilerFlavor == QLatin1String("msvc")) { + } else if (compilerFlavor == "msvc"_L1) { // MSVC uses one environment variable: INCLUDE const auto include = qgetenv("INCLUDE").split(QDir::listSeparator().toLatin1()); for (const QByteArray &p : include) pp.includes += Preprocessor::IncludePath(p); } else { - error(qPrintable(QLatin1String("Unknown compiler flavor '") + compilerFlavor + - QLatin1String("'; valid values are: msvc, unix."))); + error(qPrintable("Unknown compiler flavor '"_L1 + compilerFlavor + + "'; valid values are: msvc, unix."_L1)); parser.showHelp(1); } @@ -417,7 +399,7 @@ int runMoc(int argc, char **argv) for (const QString &arg : defines) { QByteArray name = arg.toLocal8Bit(); QByteArray value("1"); - int eq = name.indexOf('='); + const qsizetype eq = name.indexOf('='); if (eq >= 0) { value = name.mid(eq + 1); name = name.left(eq); @@ -441,16 +423,16 @@ int runMoc(int argc, char **argv) pp.macros.remove(macro); } const QStringList noNotesCompatValues = parser.values(noNotesWarningsCompatOption); - if (parser.isSet(noNotesOption) || noNotesCompatValues.contains(QLatin1String("n"))) + if (parser.isSet(noNotesOption) || noNotesCompatValues.contains("n"_L1)) moc.displayNotes = false; - if (parser.isSet(noWarningsOption) || noNotesCompatValues.contains(QLatin1String("w"))) + if (parser.isSet(noWarningsOption) || noNotesCompatValues.contains("w"_L1)) moc.displayWarnings = moc.displayNotes = false; if (autoInclude) { - int spos = filename.lastIndexOf(QDir::separator()); - int ppos = filename.lastIndexOf(QLatin1Char('.')); + qsizetype spos = filename.lastIndexOf(QDir::separator()); + qsizetype ppos = filename.lastIndexOf(u'.'); // spos >= -1 && ppos > spos => ppos >= 0 - moc.noInclude = (ppos > spos && filename.at(ppos + 1).toLower() != QLatin1Char('h')); + moc.noInclude = (ppos > spos && filename.at(ppos + 1).toLower() != u'h'); } if (defaultInclude) { if (moc.includePath.isEmpty()) { @@ -467,11 +449,14 @@ int runMoc(int argc, char **argv) if (filename.isEmpty()) { filename = QStringLiteral("standard input"); - in.open(stdin, QIODevice::ReadOnly); + if (!in.open(stdin, QIODevice::ReadOnly)) { + fprintf(stderr, "moc: cannot open standard input: %s\n", qPrintable(in.errorString())); + return 1; + } } else { in.setFileName(filename); if (!in.open(QIODevice::ReadOnly)) { - fprintf(stderr, "moc: %s: No such file\n", qPrintable(filename)); + fprintf(stderr, "moc: cannot open %s: %s\n", qPrintable(filename), qPrintable(in.errorString())); return 1; } moc.filename = filename.toLocal8Bit(); @@ -479,13 +464,13 @@ int runMoc(int argc, char **argv) const auto metadata = parser.values(metadataOption); for (const QString &md : metadata) { - int split = md.indexOf(QLatin1Char('=')); + qsizetype split = md.indexOf(u'='); QString key = md.left(split); QString value = md.mid(split + 1); if (split == -1 || key.isEmpty() || value.isEmpty()) { error("missing key or value for option '-M'"); - } else if (key.indexOf(QLatin1Char('.')) != -1) { + } else if (key.indexOf(u'.') != -1) { // Don't allow keys with '.' for now, since we might need this // format later for more advanced meta data API error("A key cannot contain the letter '.' for option '-M'"); @@ -499,6 +484,17 @@ int runMoc(int argc, char **argv) moc.currentFilenames.push(filename.toLocal8Bit()); moc.includes = pp.includes; + if (Q_UNLIKELY(parser.isSet(debugIncludesOption))) { + fprintf(stderr, "debug-includes: include search list:\n"); + + for (auto &includePath : pp.includes) { + fprintf(stderr, "debug-includes: '%s' framework: %d \n", + includePath.path.constData(), + includePath.isFrameworkPath); + } + fprintf(stderr, "debug-includes: end of search list.\n"); + } + // 1. preprocess const auto includeFiles = parser.values(includeOption); QStringList validIncludesFiles; @@ -543,12 +539,15 @@ int runMoc(int argc, char **argv) if (!out) #endif { - fprintf(stderr, "moc: Cannot create %s\n", QFile::encodeName(output).constData()); + const auto fopen_errno = errno; + fprintf(stderr, "moc: Cannot create %s. Error: %s\n", + QFile::encodeName(output).constData(), + strerror(fopen_errno)); return 1; } if (parser.isSet(jsonOption)) { - const QString jsonOutputFileName = output + QLatin1String(".json"); + const QString jsonOutputFileName = output + ".json"_L1; FILE *f; #if defined(_MSC_VER) if (_wfopen_s(&f, reinterpret_cast<const wchar_t *>(jsonOutputFileName.utf16()), L"w") != 0) @@ -556,9 +555,12 @@ int runMoc(int argc, char **argv) f = fopen(QFile::encodeName(jsonOutputFileName).constData(), "w"); if (!f) #endif - fprintf(stderr, "moc: Cannot create JSON output file %s. %s\n", + { + const auto fopen_errno = errno; + fprintf(stderr, "moc: Cannot create JSON output file %s. Error: %s\n", QFile::encodeName(jsonOutputFileName).constData(), - strerror(errno)); + strerror(fopen_errno)); + } jsonOutput.reset(f); } } else { // use stdout @@ -589,7 +591,7 @@ int runMoc(int argc, char **argv) if (parser.isSet(depFilePathOption)) { depOutputFileName = parser.value(depFilePathOption); } else if (outputToFile) { - depOutputFileName = output + QLatin1String(".d"); + depOutputFileName = output + ".d"_L1; } else { fprintf(stderr, "moc: Writing to stdout, but no depfile path specified.\n"); } @@ -603,9 +605,12 @@ int runMoc(int argc, char **argv) depFileHandleRaw = fopen(QFile::encodeName(depOutputFileName).constData(), "w"); if (!depFileHandleRaw) #endif - fprintf(stderr, "moc: Cannot create dep output file '%s'. %s\n", + { + const auto fopen_errno = errno; + fprintf(stderr, "moc: Cannot create dep output file '%s'. Error: %s\n", QFile::encodeName(depOutputFileName).constData(), - strerror(errno)); + strerror(fopen_errno)); + } depFileHandle.reset(depFileHandleRaw); if (!depFileHandle.isNull()) { diff --git a/src/tools/moc/moc.cpp b/src/tools/moc/moc.cpp index 56440129c2..3cbe331f14 100644 --- a/src/tools/moc/moc.cpp +++ b/src/tools/moc/moc.cpp @@ -1,31 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Copyright (C) 2019 Olivier Goffart <ogoffart@woboq.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2019 Olivier Goffart <ogoffart@woboq.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "moc.h" #include "generator.h" @@ -43,12 +18,23 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + // only moc needs this function static QByteArray normalizeType(const QByteArray &ba) { return ba.size() ? normalizeTypeInternal(ba.constBegin(), ba.constEnd()) : ba; } +const QByteArray &Moc::toFullyQualified(const QByteArray &name) const noexcept +{ + if (auto it = knownQObjectClasses.find(name); it != knownQObjectClasses.end()) + return it.value(); + if (auto it = knownGadgets.find(name); it != knownGadgets.end()) + return it.value(); + return name; +} + bool Moc::parseClassHead(ClassDef *def) { // figure out whether this is a class declaration, or only a @@ -63,6 +49,9 @@ bool Moc::parseClassHead(ClassDef *def) return false; } while (token); + // support attributes like "class [[deprecated]]] name" + skipCxxAttributes(); + if (!test(IDENTIFIER)) // typedef struct { ... } return false; QByteArray name = lexem(); @@ -107,17 +96,17 @@ bool Moc::parseClassHead(ClassDef *def) else test(PUBLIC); test(VIRTUAL); - const QByteArray type = parseType().name; + const Type type = parseType(); // ignore the 'class Foo : BAR(Baz)' case if (test(LPAREN)) { until(RPAREN); } else { - def->superclassList += qMakePair(type, access); + def->superclassList.push_back({type.name, toFullyQualified(type.name), access}); } } while (test(COMMA)); if (!def->superclassList.isEmpty() - && knownGadgets.contains(def->superclassList.constFirst().first)) { + && knownGadgets.contains(def->superclassList.constFirst().classname)) { // Q_GADGET subclasses are treated as Q_GADGETs knownGadgets.insert(def->classname, def->qualified); knownGadgets.insert(def->qualified, def->qualified); @@ -263,7 +252,7 @@ bool Moc::parseEnum(EnumDef *def) } if (test(COLON)) { // C++11 strongly typed enum // enum Foo : unsigned long { ... }; - parseType(); //ignore the result + def->type = normalizeType(parseType().name); } if (!test(LBRACE)) return false; @@ -320,7 +309,7 @@ void Moc::parseFunctionArguments(FunctionDef *def) arg.rightType += lexem(); } arg.normalizedType = normalizeType(QByteArray(arg.type.name + ' ' + arg.rightType)); - arg.typeNameForCast = normalizeType(QByteArray(noRef(arg.type.name) + "(*)" + arg.rightType)); + arg.typeNameForCast = QByteArray("std::add_pointer_t<"+arg.normalizedType+">"); if (test(EQ)) arg.isDefault = true; def->arguments += arg; @@ -338,6 +327,9 @@ void Moc::parseFunctionArguments(FunctionDef *def) def->arguments.removeLast(); def->isRawSlot = true; } + + if (Q_UNLIKELY(def->arguments.size() >= std::numeric_limits<int>::max())) + error("number of function arguments exceeds std::numeric_limits<int>::max()"); } bool Moc::testFunctionAttribute(FunctionDef *def) @@ -388,7 +380,7 @@ QTypeRevision Moc::parseRevision() revisionString.remove(0, 1); revisionString.chop(1); const QList<QByteArray> majorMinor = revisionString.split(','); - switch (majorMinor.length()) { + switch (majorMinor.size()) { case 1: { bool ok = false; const int revision = revisionString.toInt(&ok); @@ -429,8 +421,7 @@ bool Moc::parseFunction(FunctionDef *def, bool inMacro) def->isVirtual = false; def->isStatic = false; //skip modifiers and attributes - while (test(INLINE) || (test(STATIC) && (def->isStatic = true) == true) || - (test(VIRTUAL) && (def->isVirtual = true) == true) //mark as virtual + while (testForFunctionModifiers(def) || skipCxxAttributes() || testFunctionAttribute(def) || testFunctionRevision(def)) {} bool templateFunction = (lookup() == TEMPLATE); def->type = parseType(); @@ -441,31 +432,29 @@ bool Moc::parseFunction(FunctionDef *def, bool inMacro) error(); } bool scopedFunctionName = false; - if (test(LPAREN)) { - def->name = def->type.name; - scopedFunctionName = def->type.isScoped; - def->type = Type("int"); - } else { - Type tempType = parseType();; - while (!tempType.name.isEmpty() && lookup() != LPAREN) { - if (testFunctionAttribute(def->type.firstToken, def)) - ; // fine - else if (def->type.firstToken == Q_SIGNALS_TOKEN) - error(); - else if (def->type.firstToken == Q_SLOTS_TOKEN) - error(); - else { - if (!def->tag.isEmpty()) - def->tag += ' '; - def->tag += def->type.name; - } - def->type = tempType; - tempType = parseType(); + // we might have modifiers and attributes after a tag + // note that testFunctionAttribute is handled further below, + // and revisions and attributes must come first + while (testForFunctionModifiers(def)) {} + Type tempType = parseType(); + while (!tempType.name.isEmpty() && lookup() != LPAREN) { + if (testFunctionAttribute(def->type.firstToken, def)) + ; // fine + else if (def->type.firstToken == Q_SIGNALS_TOKEN) + error(); + else if (def->type.firstToken == Q_SLOTS_TOKEN) + error(); + else { + if (!def->tag.isEmpty()) + def->tag += ' '; + def->tag += def->type.name; } - next(LPAREN, "Not a signal or slot declaration"); - def->name = tempType.name; - scopedFunctionName = tempType.isScoped; + def->type = tempType; + tempType = parseType(); } + next(LPAREN, "Not a signal or slot declaration"); + def->name = tempType.name; + scopedFunctionName = tempType.isScoped; if (!test(RPAREN)) { parseFunctionArguments(def); @@ -529,14 +518,20 @@ bool Moc::parseFunction(FunctionDef *def, bool inMacro) return true; } +bool Moc::testForFunctionModifiers(FunctionDef *def) +{ + return test(EXPLICIT) || test(INLINE) || + (test(STATIC) && (def->isStatic = true)) || + (test(VIRTUAL) && (def->isVirtual = true)); +} + // like parseFunction, but never aborts with an error bool Moc::parseMaybeFunction(const ClassDef *cdef, FunctionDef *def) { def->isVirtual = false; def->isStatic = false; //skip modifiers and attributes - while (test(EXPLICIT) || test(INLINE) || (test(STATIC) && (def->isStatic = true) == true) || - (test(VIRTUAL) && (def->isVirtual = true) == true) //mark as virtual + while (testForFunctionModifiers(def) || skipCxxAttributes() || testFunctionAttribute(def) || testFunctionRevision(def)) {} bool tilde = test(TILDE); def->type = parseType(); @@ -551,10 +546,15 @@ bool Moc::parseMaybeFunction(const ClassDef *cdef, FunctionDef *def) def->isConstructor = !tilde; def->type = Type(); } else { - def->type = Type("int"); + // missing type name? => Skip + return false; } } else { - Type tempType = parseType();; + // ### TODO: The condition before testForFunctionModifiers shoulnd't be necessary, + // but otherwise we end up with misparses + if (def->isSlot || def->isSignal || def->isInvokable) + while (testForFunctionModifiers(def)) {} + Type tempType = parseType(); while (!tempType.name.isEmpty() && lookup() != LPAREN) { if (testFunctionAttribute(def->type.firstToken, def)) ; // fine @@ -601,6 +601,63 @@ bool Moc::parseMaybeFunction(const ClassDef *cdef, FunctionDef *def) return true; } +inline void handleDefaultArguments(QList<FunctionDef> *functionList, FunctionDef &function) +{ + // support a function with a default argument by pretending there is an + // overload without the argument (the original function is the overload with + // all arguments present) + while (function.arguments.size() > 0 && function.arguments.constLast().isDefault) { + function.wasCloned = true; + function.arguments.removeLast(); + *functionList += function; + } +} + +void Moc::prependNamespaces(BaseDef &def, const QList<NamespaceDef> &namespaceList) const +{ + auto it = namespaceList.crbegin(); + const auto rend = namespaceList.crend(); + for (; it != rend; ++it) { + if (inNamespace(&*it)) + def.qualified.prepend(it->classname + "::"); + } +} + +void Moc::checkListSizes(const ClassDef &def) +{ + if (Q_UNLIKELY(def.nonClassSignalList.size() > std::numeric_limits<int>::max())) + error("number of signals defined in parent class(es) exceeds " + "std::numeric_limits<int>::max()."); + + if (Q_UNLIKELY(def.propertyList.size() > std::numeric_limits<int>::max())) + error("number of bindable properties exceeds std::numeric_limits<int>::max()."); + + if (Q_UNLIKELY(def.classInfoList.size() > std::numeric_limits<int>::max())) + error("number of times Q_CLASSINFO macro is used exceeds " + "std::numeric_limits<int>::max()."); + + if (Q_UNLIKELY(def.enumList.size() > std::numeric_limits<int>::max())) + error("number of enumerations exceeds std::numeric_limits<int>::max()."); + + if (Q_UNLIKELY(def.superclassList.size() > std::numeric_limits<int>::max())) + error("number of super classes exceeds std::numeric_limits<int>::max()."); + + if (Q_UNLIKELY(def.constructorList.size() > std::numeric_limits<int>::max())) + error("number of constructor parameters exceeds std::numeric_limits<int>::max()."); + + if (Q_UNLIKELY(def.signalList.size() > std::numeric_limits<int>::max())) + error("number of signals exceeds std::numeric_limits<int>::max()."); + + if (Q_UNLIKELY(def.slotList.size() > std::numeric_limits<int>::max())) + error("number of declared slots exceeds std::numeric_limits<int>::max()."); + + if (Q_UNLIKELY(def.methodList.size() > std::numeric_limits<int>::max())) + error("number of methods exceeds std::numeric_limits<int>::max()."); + + if (Q_UNLIKELY(def.publicList.size() > std::numeric_limits<int>::max())) + error("number of public functions declared in this class exceeds " + "std::numeric_limits<int>::max()."); +} void Moc::parse() { @@ -610,11 +667,17 @@ void Moc::parse() Token t = next(); switch (t) { case NAMESPACE: { - int rewind = index; + qsizetype rewind = index; if (test(IDENTIFIER)) { QByteArray nsName = lexem(); QByteArrayList nested; while (test(SCOPE)) { + /* treat (C++20's) namespace A::inline B {} as A::B + this is mostly to not break compilation when encountering such + a construct in a header; the interaction of Qt's meta-macros with + inline namespaces is still rather poor. + */ + test(INLINE); next(IDENTIFIER); nested.append(nsName); nsName = lexem(); @@ -636,11 +699,8 @@ void Moc::parse() def.end = index; index = def.begin + 1; - for (int i = namespaceList.size() - 1; i >= 0; --i) { - if (inNamespace(&namespaceList.at(i))) { - def.qualified.prepend(namespaceList.at(i).classname + "::"); - } - } + prependNamespaces(def, namespaceList); + for (const QByteArray &ns : nested) { NamespaceDef parentNs; parentNs.classname = ns; @@ -655,8 +715,10 @@ void Moc::parse() switch (next()) { case NAMESPACE: if (test(IDENTIFIER)) { - while (test(SCOPE)) + while (test(SCOPE)) { + test(INLINE); // ignore inline namespaces next(IDENTIFIER); + } if (test(EQ)) { // namespace Foo = Bar::Baz; until(SEMIC); @@ -770,6 +832,12 @@ void Moc::parse() case Q_OBJECT_TOKEN: def.hasQObject = true; break; + case Q_GADGET_EXPORT_TOKEN: + next(LPAREN); + while (test(IDENTIFIER)) + {} + next(RPAREN); + Q_FALLTHROUGH(); case Q_GADGET_TOKEN: def.hasQGadget = true; break; @@ -780,9 +848,7 @@ void Moc::parse() if (!def.hasQObject && !def.hasQGadget) continue; - for (int i = namespaceList.size() - 1; i >= 0; --i) - if (inNamespace(&namespaceList.at(i))) - def.qualified.prepend(namespaceList.at(i).classname + "::"); + prependNamespaces(def, namespaceList); QHash<QByteArray, QByteArray> &classHash = def.hasQObject ? knownQObjectClasses : knownGadgets; classHash.insert(def.classname, def.qualified); @@ -795,10 +861,9 @@ void Moc::parse() continue; ClassDef def; if (parseClassHead(&def)) { + prependNamespaces(def, namespaceList); + FunctionDef::Access access = FunctionDef::Private; - for (int i = namespaceList.size() - 1; i >= 0; --i) - if (inNamespace(&namespaceList.at(i))) - def.qualified.prepend(namespaceList.at(i).classname + "::"); while (inClass(&def) && hasNext()) { switch ((t = next())) { case PRIVATE: @@ -847,13 +912,22 @@ void Moc::parse() if (def.classname != "Qt" && def.classname != "QObject" && def.superclassList.isEmpty()) error("Class contains Q_OBJECT macro but does not inherit from QObject"); break; + case Q_GADGET_EXPORT_TOKEN: + next(LPAREN); + while (test(IDENTIFIER)) + {} + next(RPAREN); + Q_FALLTHROUGH(); case Q_GADGET_TOKEN: def.hasQGadget = true; if (templateClass) error("Template classes not supported by Q_GADGET"); break; case Q_PROPERTY_TOKEN: - parseProperty(&def); + parseProperty(&def, Named); + break; + case QT_ANONYMOUS_PROPERTY_TOKEN: + parseProperty(&def, Anonymous); break; case Q_PLUGIN_METADATA_TOKEN: parsePluginData(&def); @@ -888,7 +962,10 @@ void Moc::parse() parseSlotInPrivate(&def, access); break; case Q_PRIVATE_PROPERTY_TOKEN: - parsePrivateProperty(&def); + parsePrivateProperty(&def, Named); + break; + case QT_ANONYMOUS_PRIVATE_PROPERTY_TOKEN: + parsePrivateProperty(&def, Anonymous); break; case ENUM: { EnumDef enumDef; @@ -901,16 +978,12 @@ void Moc::parse() default: FunctionDef funcDef; funcDef.access = access; - int rewind = index--; + qsizetype rewind = index--; if (parseMaybeFunction(&def, &funcDef)) { if (funcDef.isConstructor) { if ((access == FunctionDef::Public) && funcDef.isInvokable) { def.constructorList += funcDef; - while (funcDef.arguments.size() > 0 && funcDef.arguments.constLast().isDefault) { - funcDef.wasCloned = true; - funcDef.arguments.removeLast(); - def.constructorList += funcDef; - } + handleDefaultArguments(&def.constructorList, funcDef); } } else if (funcDef.isDestructor) { // don't care about destructors @@ -919,29 +992,17 @@ void Moc::parse() def.publicList += funcDef; if (funcDef.isSlot) { def.slotList += funcDef; - while (funcDef.arguments.size() > 0 && funcDef.arguments.constLast().isDefault) { - funcDef.wasCloned = true; - funcDef.arguments.removeLast(); - def.slotList += funcDef; - } + handleDefaultArguments(&def.slotList, funcDef); if (funcDef.revision > 0) ++def.revisionedMethods; } else if (funcDef.isSignal) { def.signalList += funcDef; - while (funcDef.arguments.size() > 0 && funcDef.arguments.constLast().isDefault) { - funcDef.wasCloned = true; - funcDef.arguments.removeLast(); - def.signalList += funcDef; - } + handleDefaultArguments(&def.signalList, funcDef); if (funcDef.revision > 0) ++def.revisionedMethods; } else if (funcDef.isInvokable) { def.methodList += funcDef; - while (funcDef.arguments.size() > 0 && funcDef.arguments.constLast().isDefault) { - funcDef.wasCloned = true; - funcDef.arguments.removeLast(); - def.methodList += funcDef; - } + handleDefaultArguments(&def.methodList, funcDef); if (funcDef.revision > 0) ++def.revisionedMethods; } @@ -966,16 +1027,20 @@ void Moc::parse() if (!def.pluginData.iid.isEmpty()) def.pluginData.metaArgs = metaArgs; - checkSuperClasses(&def); + if (def.hasQObject && !def.superclassList.isEmpty()) + checkSuperClasses(&def); + checkProperties(&def); + checkListSizes(def); + classList += def; QHash<QByteArray, QByteArray> &classHash = def.hasQObject ? knownQObjectClasses : knownGadgets; classHash.insert(def.classname, def.qualified); classHash.insert(def.qualified, def.qualified); } } - for (const auto &n : qAsConst(namespaceList)) { + for (const auto &n : std::as_const(namespaceList)) { if (!n.hasQNamespace) continue; ClassDef def; @@ -988,8 +1053,10 @@ void Moc::parse() if (it != classList.end()) { it->classInfoList += def.classInfoList; + Q_ASSERT(it->classInfoList.size() <= std::numeric_limits<int>::max()); it->enumDeclarations.insert(def.enumDeclarations); it->enumList += def.enumList; + Q_ASSERT(it->enumList.size() <= std::numeric_limits<int>::max()); it->flagAliases.insert(def.flagAliases); } else { knownGadgets.insert(def.classname, def.qualified); @@ -1067,25 +1134,27 @@ static QByteArrayList requiredQtContainers(const QList<ClassDef> &classes) void Moc::generate(FILE *out, FILE *jsonOutput) { - QByteArray fn = filename; - int i = filename.length()-1; - while (i > 0 && filename.at(i - 1) != '/' && filename.at(i - 1) != '\\') - --i; // skip path - if (i >= 0) - fn = filename.mid(i); + QByteArrayView fn = QByteArrayView(filename); + + auto isSlash = [](char ch) { return ch == '/' || ch == '\\'; }; + auto rit = std::find_if(fn.crbegin(), fn.crend(), isSlash); + if (rit != fn.crend()) + fn = fn.last(rit - fn.crbegin()); + fprintf(out, "/****************************************************************************\n" "** Meta object code from reading C++ file '%s'\n**\n" , fn.constData()); fprintf(out, "** Created by: The Qt Meta Object Compiler version %d (Qt %s)\n**\n" , mocOutputRevision, QT_VERSION_STR); fprintf(out, "** WARNING! All changes made in this file will be lost!\n" "*****************************************************************************/\n\n"); - fprintf(out, "#include <memory>\n"); // For std::addressof + // include header(s) of user class definitions at _first_ to allow + // for preprocessor definitions possibly affecting standard headers. + // see https://codereview.qt-project.org/c/qt/qtbase/+/445937 if (!noInclude) { if (includePath.size() && !includePath.endsWith('/')) includePath += '/'; - for (int i = 0; i < includeFiles.size(); ++i) { - QByteArray inc = includeFiles.at(i); - if (inc.at(0) != '<' && inc.at(0) != '"') { + for (QByteArray inc : std::as_const(includeFiles)) { + if (!inc.isEmpty() && inc.at(0) != '<' && inc.at(0) != '"') { if (includePath.size() && includePath != "./") inc.prepend(includePath); inc = '\"' + inc + '\"'; @@ -1096,7 +1165,6 @@ void Moc::generate(FILE *out, FILE *jsonOutput) if (classList.size() && classList.constFirst().classname == "Qt") fprintf(out, "#include <QtCore/qobject.h>\n"); - fprintf(out, "#include <QtCore/qbytearray.h>\n"); // For QByteArrayData fprintf(out, "#include <QtCore/qmetatype.h>\n"); // For QMetaType::Type if (mustIncludeQPluginH) fprintf(out, "#include <QtCore/qplugin.h>\n"); @@ -1105,6 +1173,10 @@ void Moc::generate(FILE *out, FILE *jsonOutput) for (const QByteArray &qtContainer : qtContainers) fprintf(out, "#include <QtCore/%s>\n", qtContainer.constData()); + fprintf(out, "\n#include <QtCore/qtmochelpers.h>\n"); + + fprintf(out, "\n#include <memory>\n\n"); // For std::addressof + fprintf(out, "\n#include <QtCore/qxptype_traits.h>\n"); // is_detected fprintf(out, "#if !defined(Q_MOC_OUTPUT_REVISION)\n" "#error \"The header file '%s' doesn't include <QObject>.\"\n", fn.constData()); @@ -1115,32 +1187,44 @@ void Moc::generate(FILE *out, FILE *jsonOutput) " much.)\"\n", QT_VERSION_STR); fprintf(out, "#endif\n\n"); - fprintf(out, "QT_BEGIN_MOC_NAMESPACE\n"); +#if QT_VERSION <= QT_VERSION_CHECK(7, 0, 0) + fprintf(out, "#ifndef Q_CONSTINIT\n" + "#define Q_CONSTINIT\n" + "#endif\n\n"); +#endif + fprintf(out, "QT_WARNING_PUSH\n"); fprintf(out, "QT_WARNING_DISABLE_DEPRECATED\n"); + fprintf(out, "QT_WARNING_DISABLE_GCC(\"-Wuseless-cast\")\n"); fputs("", out); - for (i = 0; i < classList.size(); ++i) { - Generator generator(&classList[i], metaTypes, knownQObjectClasses, knownGadgets, out, requireCompleteTypes); + for (ClassDef &def : classList) { + Generator generator(this, &def, metaTypes, knownQObjectClasses, knownGadgets, out, + requireCompleteTypes); generator.generateCode(); + + // generator.generateCode() should have already registered all strings + if (Q_UNLIKELY(generator.registeredStringsCount() >= std::numeric_limits<int>::max())) { + error("internal limit exceeded: number of parsed strings is too big."); + exit(EXIT_FAILURE); + } } fputs("", out); fprintf(out, "QT_WARNING_POP\n"); - fprintf(out, "QT_END_MOC_NAMESPACE\n"); if (jsonOutput) { QJsonObject mocData; - mocData[QLatin1String("outputRevision")] = mocOutputRevision; - mocData[QLatin1String("inputFile")] = QLatin1String(fn.constData()); + mocData["outputRevision"_L1] = mocOutputRevision; + mocData["inputFile"_L1] = QLatin1StringView(fn.constData()); QJsonArray classesJsonFormatted; - for (const ClassDef &cdef: qAsConst(classList)) + for (const ClassDef &cdef: std::as_const(classList)) classesJsonFormatted.append(cdef.toJson()); if (!classesJsonFormatted.isEmpty()) - mocData[QLatin1String("classes")] = classesJsonFormatted; + mocData["classes"_L1] = classesJsonFormatted; QJsonDocument jsonDoc(mocData); fputs(jsonDoc.toJson().constData(), jsonOutput); @@ -1185,11 +1269,7 @@ void Moc::parseSlots(ClassDef *def, FunctionDef::Access access) ++def->revisionedMethods; } def->slotList += funcDef; - while (funcDef.arguments.size() > 0 && funcDef.arguments.constLast().isDefault) { - funcDef.wasCloned = true; - funcDef.arguments.removeLast(); - def->slotList += funcDef; - } + handleDefaultArguments(&def->slotList, funcDef); } } @@ -1233,15 +1313,11 @@ void Moc::parseSignals(ClassDef *def) ++def->revisionedMethods; } def->signalList += funcDef; - while (funcDef.arguments.size() > 0 && funcDef.arguments.constLast().isDefault) { - funcDef.wasCloned = true; - funcDef.arguments.removeLast(); - def->signalList += funcDef; - } + handleDefaultArguments(&def->signalList, funcDef); } } -void Moc::createPropertyDef(PropertyDef &propDef, int propertyIndex) +void Moc::createPropertyDef(PropertyDef &propDef, int propertyIndex, Moc::PropertyMode mode) { propDef.location = index; propDef.relativeIndex = propertyIndex; @@ -1271,8 +1347,10 @@ void Moc::createPropertyDef(PropertyDef &propDef, int propertyIndex) propDef.type = type; - next(); - propDef.name = lexem(); + if (mode == Moc::Named) { + next(); + propDef.name = lexem(); + } parsePropertyAttributes(propDef); } @@ -1289,7 +1367,8 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef) }; while (test(IDENTIFIER)) { - const QByteArray l = lexem(); + const Symbol &lsym = symbol(); + const QByteArray l = lsym.lexem(); if (l[0] == 'C' && l == "CONSTANT") { propDef.constant = true; continue; @@ -1312,11 +1391,15 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef) QByteArray v, v2; if (test(LPAREN)) { v = lexemUntil(RPAREN); - v = v.mid(1, v.length() - 2); // removes the '(' and ')' + v = v.mid(1, v.size() - 2); // removes the '(' and ')' } else if (test(INTEGER_LITERAL)) { v = lexem(); if (l != "REVISION") - error(1); + error(lsym); + } else if (test(DEFAULT)) { + v = lexem(); + if (l != "READ" && l != "WRITE") + error(lsym); } else { next(IDENTIFIER); v = lexem(); @@ -1330,21 +1413,21 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef) if (l == "MEMBER") propDef.member = v; else - error(2); + error(lsym); break; case 'R': if (l == "READ") propDef.read = v; else if (l == "RESET") - propDef.reset = v + v2; + propDef.reset = v; else if (l == "REVISION") { bool ok = false; const int minor = v.toInt(&ok); if (!ok || !QTypeRevision::isValidSegment(minor)) - error(1); + error(lsym); propDef.revision = QTypeRevision::fromMinorVersion(minor).toEncodedVersion<int>(); } else - error(2); + error(lsym); break; case 'S': if (l == "SCRIPTABLE") { @@ -1354,27 +1437,27 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef) propDef.stored = v + v2; checkIsFunction(propDef.stored, "STORED"); } else - error(2); + error(lsym); break; - case 'W': if (l != "WRITE") error(2); + case 'W': if (l != "WRITE") error(lsym); propDef.write = v; break; - case 'B': if (l != "BINDABLE") error(2); + case 'B': if (l != "BINDABLE") error(lsym); propDef.bind = v; break; - case 'D': if (l != "DESIGNABLE") error(2); + case 'D': if (l != "DESIGNABLE") error(lsym); propDef.designable = v + v2; checkIsFunction(propDef.designable, "DESIGNABLE"); break; - case 'N': if (l != "NOTIFY") error(2); + case 'N': if (l != "NOTIFY") error(lsym); propDef.notify = v; break; - case 'U': if (l != "USER") error(2); + case 'U': if (l != "USER") error(lsym); propDef.user = v + v2; checkIsFunction(propDef.user, "USER"); break; default: - error(2); + error(lsym); } } if (propDef.constant && !propDef.write.isNull()) { @@ -1395,13 +1478,25 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef) propDef.constant = false; warning(msg.constData()); } + if (propDef.read == "default" && propDef.bind.isNull()) { + const QByteArray msg = "Property declaration " + propDef.name + + " is not BINDable but default-READable. READ will be ignored."; + propDef.read = ""; + warning(msg.constData()); + } + if (propDef.write == "default" && propDef.bind.isNull()) { + const QByteArray msg = "Property declaration " + propDef.name + + " is not BINDable but default-WRITEable. WRITE will be ignored."; + propDef.write = ""; + warning(msg.constData()); + } } -void Moc::parseProperty(ClassDef *def) +void Moc::parseProperty(ClassDef *def, Moc::PropertyMode mode) { next(LPAREN); PropertyDef propDef; - createPropertyDef(propDef, int(def->propertyList.size())); + createPropertyDef(propDef, int(def->propertyList.size()), mode); next(RPAREN); def->propertyList += propDef; @@ -1422,9 +1517,11 @@ void Moc::parsePluginData(ClassDef *def) } else if (l == "FILE") { next(STRING_LITERAL); QByteArray metaDataFile = unquotedLexem(); - QFileInfo fi(QFileInfo(QString::fromLocal8Bit(currentFilenames.top().constData())).dir(), QString::fromLocal8Bit(metaDataFile.constData())); - for (int j = 0; j < includes.size() && !fi.exists(); ++j) { - const IncludePath &p = includes.at(j); + QFileInfo fi(QFileInfo(QString::fromLocal8Bit(currentFilenames.top())).dir(), + QString::fromLocal8Bit(metaDataFile)); + for (const IncludePath &p : std::as_const(includes)) { + if (fi.exists()) + break; if (p.isFrameworkPath) continue; @@ -1487,7 +1584,7 @@ QByteArray Moc::parsePropertyAccessor() return accessor; } -void Moc::parsePrivateProperty(ClassDef *def) +void Moc::parsePrivateProperty(ClassDef *def, Moc::PropertyMode mode) { next(LPAREN); PropertyDef propDef; @@ -1495,7 +1592,7 @@ void Moc::parsePrivateProperty(ClassDef *def) next(COMMA); - createPropertyDef(propDef, int(def->propertyList.size())); + createPropertyDef(propDef, int(def->propertyList.size()), mode); def->propertyList += propDef; } @@ -1593,7 +1690,7 @@ void Moc::parseInterfaces(ClassDef *def) } } // resolve from classnames to interface ids - for (int i = 0; i < iface.count(); ++i) { + for (qsizetype i = 0; i < iface.size(); ++i) { const QByteArray iid = interface2IdMap.value(iface.at(i).className); if (iid.isEmpty()) error("Undefined interface"); @@ -1662,11 +1759,7 @@ void Moc::parseSlotInPrivate(ClassDef *def, FunctionDef::Access access) funcDef.access = access; parseFunction(&funcDef, true); def->slotList += funcDef; - while (funcDef.arguments.size() > 0 && funcDef.arguments.constLast().isDefault) { - funcDef.wasCloned = true; - funcDef.arguments.removeLast(); - def->slotList += funcDef; - } + handleDefaultArguments(&def->slotList, funcDef); if (funcDef.revision > 0) ++def->revisionedMethods; @@ -1674,7 +1767,7 @@ void Moc::parseSlotInPrivate(ClassDef *def, FunctionDef::Access access) QByteArray Moc::lexemUntil(Token target) { - int from = index; + qsizetype from = index; until(target); QByteArray s; while (from <= index) { @@ -1708,9 +1801,9 @@ bool Moc::until(Token target) { } //when searching commas within the default argument, we should take care of template depth (anglecount) - // unfortunatelly, we do not have enough semantic information to know if '<' is the operator< or + // unfortunately, we do not have enough semantic information to know if '<' is the operator< or // the beginning of a template type. so we just use heuristics. - int possible = -1; + qsizetype possible = -1; while (index < symbols.size()) { Token t = symbols.at(index++).token; @@ -1774,7 +1867,8 @@ bool Moc::until(Token target) { void Moc::checkSuperClasses(ClassDef *def) { - const QByteArray firstSuperclass = def->superclassList.value(0).first; + Q_ASSERT(!def->superclassList.isEmpty()); + const QByteArray &firstSuperclass = def->superclassList.at(0).classname; if (!knownQObjectClasses.contains(firstSuperclass)) { // enable once we /require/ include paths @@ -1789,8 +1883,18 @@ void Moc::checkSuperClasses(ClassDef *def) #endif return; } - for (int i = 1; i < def->superclassList.count(); ++i) { - const QByteArray superClass = def->superclassList.at(i).first; + + auto isRegisteredInterface = [&def](QByteArrayView super) { + auto matchesSuperClass = [&super](const auto &ifaces) { + return !ifaces.isEmpty() && ifaces.first().className == super; + }; + return std::any_of(def->interfaceList.cbegin(), def->interfaceList.cend(), matchesSuperClass); + }; + + const auto end = def->superclassList.cend(); + auto it = def->superclassList.cbegin() + 1; + for (; it != end; ++it) { + const QByteArray &superClass = it->classname; if (knownQObjectClasses.contains(superClass)) { const QByteArray msg = "Class " @@ -1804,14 +1908,7 @@ void Moc::checkSuperClasses(ClassDef *def) } if (interface2IdMap.contains(superClass)) { - bool registeredInterface = false; - for (int i = 0; i < def->interfaceList.count(); ++i) - if (def->interfaceList.at(i).constFirst().className == superClass) { - registeredInterface = true; - break; - } - - if (!registeredInterface) { + if (!isRegisteredInterface(superClass)) { const QByteArray msg = "Class " + def->classname @@ -1829,34 +1926,30 @@ void Moc::checkSuperClasses(ClassDef *def) void Moc::checkProperties(ClassDef *cdef) { // - // specify get function, for compatibiliy we accept functions + // specify get function, for compatibility we accept functions // returning pointers, or const char * for QByteArray. // - QDuplicateTracker<QByteArray> definedProperties(cdef->propertyList.count()); - for (int i = 0; i < cdef->propertyList.count(); ++i) { - PropertyDef &p = cdef->propertyList[i]; + QDuplicateTracker<QByteArray> definedProperties(cdef->propertyList.size()); + auto hasNoAttributes = [&](const PropertyDef &p) { if (definedProperties.hasSeen(p.name)) { QByteArray msg = "The property '" + p.name + "' is defined multiple times in class " + cdef->classname + "."; warning(msg.constData()); } if (p.read.isEmpty() && p.member.isEmpty() && p.bind.isEmpty()) { - const int rewind = index; - if (p.location >= 0) - index = p.location; QByteArray msg = "Property declaration " + p.name + " has neither an associated QProperty<> member" ", nor a READ accessor function nor an associated MEMBER variable. The property will be invalid."; - warning(msg.constData()); - index = rewind; - if (p.write.isEmpty()) { - cdef->propertyList.removeAt(i); - --i; - } - continue; + const auto &sym = p.location >= 0 ? symbolAt(p.location) : Symbol(); + warning(sym, msg.constData()); + if (p.write.isEmpty()) + return true; } + return false; + }; + cdef->propertyList.removeIf(hasNoAttributes); - for (int j = 0; j < cdef->publicList.count(); ++j) { - const FunctionDef &f = cdef->publicList.at(j); + for (PropertyDef &p : cdef->propertyList) { + for (const FunctionDef &f : std::as_const(cdef->publicList)) { if (f.name != p.read) continue; if (!f.isConst) // get functions must be const @@ -1882,7 +1975,7 @@ void Moc::checkProperties(ClassDef *cdef) } if (!p.notify.isEmpty()) { int notifyId = -1; - for (int j = 0; j < cdef->signalList.count(); ++j) { + for (int j = 0; j < int(cdef->signalList.size()); ++j) { const FunctionDef &f = cdef->signalList.at(j); if (f.name != p.notify) { continue; @@ -1893,12 +1986,12 @@ void Moc::checkProperties(ClassDef *cdef) } p.notifyId = notifyId; if (notifyId == -1) { - int index = cdef->nonClassSignalList.indexOf(p.notify); + const int index = int(cdef->nonClassSignalList.indexOf(p.notify)); if (index == -1) { cdef->nonClassSignalList << p.notify; - p.notifyId = -1 - cdef->nonClassSignalList.count(); + p.notifyId = int(-1 - cdef->nonClassSignalList.size()); } else { - p.notifyId = -2 - index; + p.notifyId = int(-2 - index); } } } @@ -1908,19 +2001,19 @@ void Moc::checkProperties(ClassDef *cdef) QJsonObject ClassDef::toJson() const { QJsonObject cls; - cls[QLatin1String("className")] = QString::fromUtf8(classname.constData()); - cls[QLatin1String("qualifiedClassName")] = QString::fromUtf8(qualified.constData()); + cls["className"_L1] = QString::fromUtf8(classname.constData()); + cls["qualifiedClassName"_L1] = QString::fromUtf8(qualified.constData()); QJsonArray classInfos; - for (const auto &info: qAsConst(classInfoList)) { + for (const auto &info: std::as_const(classInfoList)) { QJsonObject infoJson; - infoJson[QLatin1String("name")] = QString::fromUtf8(info.name); - infoJson[QLatin1String("value")] = QString::fromUtf8(info.value); + infoJson["name"_L1] = QString::fromUtf8(info.name); + infoJson["value"_L1] = QString::fromUtf8(info.value); classInfos.append(infoJson); } if (classInfos.size()) - cls[QLatin1String("classInfos")] = classInfos; + cls["classInfos"_L1] = classInfos; const auto appendFunctions = [&cls](const QString &type, const QList<FunctionDef> &funcs) { QJsonArray jsonFuncs; @@ -1932,59 +2025,59 @@ QJsonObject ClassDef::toJson() const cls[type] = jsonFuncs; }; - appendFunctions(QLatin1String("signals"), signalList); - appendFunctions(QLatin1String("slots"), slotList); - appendFunctions(QLatin1String("constructors"), constructorList); - appendFunctions(QLatin1String("methods"), methodList); + appendFunctions("signals"_L1, signalList); + appendFunctions("slots"_L1, slotList); + appendFunctions("constructors"_L1, constructorList); + appendFunctions("methods"_L1, methodList); QJsonArray props; - for (const PropertyDef &propDef: qAsConst(propertyList)) + for (const PropertyDef &propDef: std::as_const(propertyList)) props.append(propDef.toJson()); if (!props.isEmpty()) - cls[QLatin1String("properties")] = props; + cls["properties"_L1] = props; if (hasQObject) - cls[QLatin1String("object")] = true; + cls["object"_L1] = true; if (hasQGadget) - cls[QLatin1String("gadget")] = true; + cls["gadget"_L1] = true; if (hasQNamespace) - cls[QLatin1String("namespace")] = true; + cls["namespace"_L1] = true; QJsonArray superClasses; - for (const auto &super: qAsConst(superclassList)) { - const auto name = super.first; - const auto access = super.second; + for (const auto &super: std::as_const(superclassList)) { QJsonObject superCls; - superCls[QLatin1String("name")] = QString::fromUtf8(name); - FunctionDef::accessToJson(&superCls, access); + superCls["name"_L1] = QString::fromUtf8(super.classname); + if (super.classname != super.qualified) + superCls["fullyQualifiedName"_L1] = QString::fromUtf8(super.qualified); + FunctionDef::accessToJson(&superCls, super.access); superClasses.append(superCls); } if (!superClasses.isEmpty()) - cls[QLatin1String("superClasses")] = superClasses; + cls["superClasses"_L1] = superClasses; QJsonArray enums; - for (const EnumDef &enumDef: qAsConst(enumList)) + for (const EnumDef &enumDef: std::as_const(enumList)) enums.append(enumDef.toJson(*this)); if (!enums.isEmpty()) - cls[QLatin1String("enums")] = enums; + cls["enums"_L1] = enums; QJsonArray ifaces; for (const QList<Interface> &ifaceList : interfaceList) { QJsonArray jsonList; for (const Interface &iface: ifaceList) { QJsonObject ifaceJson; - ifaceJson[QLatin1String("id")] = QString::fromUtf8(iface.interfaceId); - ifaceJson[QLatin1String("className")] = QString::fromUtf8(iface.className); + ifaceJson["id"_L1] = QString::fromUtf8(iface.interfaceId); + ifaceJson["className"_L1] = QString::fromUtf8(iface.className); jsonList.append(ifaceJson); } ifaces.append(jsonList); } if (!ifaces.isEmpty()) - cls[QLatin1String("interfaces")] = ifaces; + cls["interfaces"_L1] = ifaces; return cls; } @@ -1992,22 +2085,25 @@ QJsonObject ClassDef::toJson() const QJsonObject FunctionDef::toJson() const { QJsonObject fdef; - fdef[QLatin1String("name")] = QString::fromUtf8(name); + fdef["name"_L1] = QString::fromUtf8(name); if (!tag.isEmpty()) - fdef[QLatin1String("tag")] = QString::fromUtf8(tag); - fdef[QLatin1String("returnType")] = QString::fromUtf8(normalizedType); + fdef["tag"_L1] = QString::fromUtf8(tag); + fdef["returnType"_L1] = QString::fromUtf8(normalizedType); QJsonArray args; for (const ArgumentDef &arg: arguments) args.append(arg.toJson()); if (!args.isEmpty()) - fdef[QLatin1String("arguments")] = args; + fdef["arguments"_L1] = args; accessToJson(&fdef, access); if (revision > 0) - fdef[QLatin1String("revision")] = revision; + fdef["revision"_L1] = revision; + + if (wasCloned) + fdef["isCloned"_L1] = true; return fdef; } @@ -2015,30 +2111,30 @@ QJsonObject FunctionDef::toJson() const void FunctionDef::accessToJson(QJsonObject *obj, FunctionDef::Access acs) { switch (acs) { - case Private: (*obj)[QLatin1String("access")] = QLatin1String("private"); break; - case Public: (*obj)[QLatin1String("access")] = QLatin1String("public"); break; - case Protected: (*obj)[QLatin1String("access")] = QLatin1String("protected"); break; + case Private: (*obj)["access"_L1] = "private"_L1; break; + case Public: (*obj)["access"_L1] = "public"_L1; break; + case Protected: (*obj)["access"_L1] = "protected"_L1; break; } } QJsonObject ArgumentDef::toJson() const { QJsonObject arg; - arg[QLatin1String("type")] = QString::fromUtf8(normalizedType); + arg["type"_L1] = QString::fromUtf8(normalizedType); if (!name.isEmpty()) - arg[QLatin1String("name")] = QString::fromUtf8(name); + arg["name"_L1] = QString::fromUtf8(name); return arg; } QJsonObject PropertyDef::toJson() const { QJsonObject prop; - prop[QLatin1String("name")] = QString::fromUtf8(name); - prop[QLatin1String("type")] = QString::fromUtf8(type); + prop["name"_L1] = QString::fromUtf8(name); + prop["type"_L1] = QString::fromUtf8(type); const auto jsonify = [&prop](const char *str, const QByteArray &member) { if (!member.isEmpty()) - prop[QLatin1String(str)] = QString::fromUtf8(member); + prop[QLatin1StringView(str)] = QString::fromUtf8(member); }; jsonify("member", member); @@ -2057,7 +2153,7 @@ QJsonObject PropertyDef::toJson() const value = false; else value = QString::fromUtf8(boolOrString); // function name to query at run-time - prop[QLatin1String(str)] = value; + prop[QLatin1StringView(str)] = value; }; jsonifyBoolOrString("designable", designable); @@ -2065,12 +2161,12 @@ QJsonObject PropertyDef::toJson() const jsonifyBoolOrString("stored", stored); jsonifyBoolOrString("user", user); - prop[QLatin1String("constant")] = constant; - prop[QLatin1String("final")] = final; - prop[QLatin1String("required")] = required; - prop[QLatin1String("index")] = relativeIndex; + prop["constant"_L1] = constant; + prop["final"_L1] = final; + prop["required"_L1] = required; + prop["index"_L1] = relativeIndex; if (revision > 0) - prop[QLatin1String("revision")] = revision; + prop["revision"_L1] = revision; return prop; } @@ -2078,19 +2174,40 @@ QJsonObject PropertyDef::toJson() const QJsonObject EnumDef::toJson(const ClassDef &cdef) const { QJsonObject def; - def[QLatin1String("name")] = QString::fromUtf8(name); + def["name"_L1] = QString::fromUtf8(name); if (!enumName.isEmpty()) - def[QLatin1String("alias")] = QString::fromUtf8(enumName); - def[QLatin1String("isFlag")] = cdef.enumDeclarations.value(name); - def[QLatin1String("isClass")] = isEnumClass; + def["alias"_L1] = QString::fromUtf8(enumName); + if (!type.isEmpty()) + def["type"_L1] = QString::fromUtf8(type); + def["isFlag"_L1] = cdef.enumDeclarations.value(name); + def["isClass"_L1] = isEnumClass; QJsonArray valueArr; for (const QByteArray &value: values) valueArr.append(QString::fromUtf8(value)); if (!valueArr.isEmpty()) - def[QLatin1String("values")] = valueArr; + def["values"_L1] = valueArr; return def; } +QByteArray EnumDef::qualifiedType(const ClassDef *cdef) const +{ + if (name == cdef->classname) { + // The name of the enclosing namespace is the same as the enum class name + if (cdef->qualified.contains("::")) { + // QTBUG-112996, fully qualify by using cdef->qualified to disambiguate enum + // class name and enclosing namespace, e.g.: + // namespace A { namespace B { Q_NAMESPACE; enum class B { }; Q_ENUM_NS(B) } } + return cdef->qualified % "::" % name; + } else { + // Just "B"; otherwise the compiler complains about the type "B::B" inside + // "B::staticMetaObject" in the generated code; e.g.: + // namespace B { Q_NAMESPACE; enum class B { }; Q_ENUM_NS(B) } + return name; + } + } + return cdef->classname % "::" % name; +} + QT_END_NAMESPACE diff --git a/src/tools/moc/moc.h b/src/tools/moc/moc.h index 1c2d6624fa..c1759fb0a3 100644 --- a/src/tools/moc/moc.h +++ b/src/tools/moc/moc.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef MOC_H #define MOC_H @@ -32,13 +7,13 @@ #include "parser.h" #include <qstringlist.h> #include <qmap.h> -#include <qpair.h> #include <qjsondocument.h> #include <qjsonarray.h> #include <qjsonobject.h> -#include <qversionnumber.h> +#include <qtyperevision.h> #include <stdio.h> -#include <ctype.h> + +#include <private/qtools_p.h> QT_BEGIN_NAMESPACE @@ -67,10 +42,12 @@ struct EnumDef { QByteArray name; QByteArray enumName; + QByteArray type; QList<QByteArray> values; bool isEnumClass; // c++11 enum class EnumDef() : isEnumClass(false) {} QJsonObject toJson(const ClassDef &cdef) const; + QByteArray qualifiedType(const ClassDef *cdef) const; }; Q_DECLARE_TYPEINFO(EnumDef, Q_RELOCATABLE_TYPE); @@ -126,8 +103,10 @@ Q_DECLARE_TYPEINFO(FunctionDef, Q_RELOCATABLE_TYPE); struct PropertyDef { bool stdCppSet() const { + if (name.isEmpty()) + return false; QByteArray s("set"); - s += toupper(name[0]); + s += QtMiscUtils::toAsciiUpper(name[0]); s += name.mid(1); return (s == write); } @@ -142,7 +121,7 @@ struct PropertyDef bool required = false; int relativeIndex = -1; // property index in current metaobject - int location = -1; // token index, used for error reporting + qsizetype location = -1; // token index, used for error reporting QJsonObject toJson() const; }; @@ -172,12 +151,19 @@ struct BaseDef { QMap<QByteArray, bool> enumDeclarations; QList<EnumDef> enumList; QMap<QByteArray, QByteArray> flagAliases; - int begin = 0; - int end = 0; + qsizetype begin = 0; + qsizetype end = 0; +}; + +struct SuperClass { + QByteArray classname; + QByteArray qualified; + FunctionDef::Access access; }; +Q_DECLARE_TYPEINFO(SuperClass, Q_RELOCATABLE_TYPE); struct ClassDef : BaseDef { - QList<QPair<QByteArray, FunctionDef::Access>> superclassList; + QList<SuperClass> superclassList; struct Interface { @@ -221,6 +207,8 @@ Q_DECLARE_TYPEINFO(NamespaceDef, Q_RELOCATABLE_TYPE); class Moc : public Parser { public: + enum PropertyMode { Named, Anonymous }; + Moc() : noInclude(false), mustIncludeQPluginH(false), requireCompleteTypes(false) {} @@ -253,6 +241,10 @@ public: return index > def->begin && index < def->end - 1; } + const QByteArray &toFullyQualified(const QByteArray &name) const noexcept; + + void prependNamespaces(BaseDef &def, const QList<NamespaceDef> &namespaceList) const; + Type parseType(); bool parseEnum(EnumDef *def); @@ -262,9 +254,11 @@ public: void parseSlots(ClassDef *def, FunctionDef::Access access); void parseSignals(ClassDef *def); - void parseProperty(ClassDef *def); + void parseProperty(ClassDef *def, PropertyMode mode); void parsePluginData(ClassDef *def); - void createPropertyDef(PropertyDef &def, int propertyIndex); + + void createPropertyDef(PropertyDef &def, int propertyIndex, PropertyMode mode); + void parsePropertyAttributes(PropertyDef &propDef); void parseEnumOrFlag(BaseDef *def, bool isFlag); void parseFlag(BaseDef *def); @@ -277,7 +271,7 @@ public: void parseMocInclude(); void parseSlotInPrivate(ClassDef *def, FunctionDef::Access access); QByteArray parsePropertyAccessor(); - void parsePrivateProperty(ClassDef *def); + void parsePrivateProperty(ClassDef *def, PropertyMode mode); void parseFunctionArguments(FunctionDef *def); @@ -295,14 +289,17 @@ public: void checkSuperClasses(ClassDef *def); void checkProperties(ClassDef* cdef); + bool testForFunctionModifiers(FunctionDef *def); + + void checkListSizes(const ClassDef &def); }; inline QByteArray noRef(const QByteArray &type) { if (type.endsWith('&')) { if (type.endsWith("&&")) - return type.left(type.length()-2); - return type.left(type.length()-1); + return type.left(type.size()-2); + return type.left(type.size()-1); } return type; } diff --git a/src/tools/moc/outputrevision.h b/src/tools/moc/outputrevision.h index e8d2d1b1b9..3bc5a872ed 100644 --- a/src/tools/moc/outputrevision.h +++ b/src/tools/moc/outputrevision.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef OUTPUTREVISION_H #define OUTPUTREVISION_H diff --git a/src/tools/moc/parser.cpp b/src/tools/moc/parser.cpp index 4b67e3a99a..1cfb8ce486 100644 --- a/src/tools/moc/parser.cpp +++ b/src/tools/moc/parser.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "parser.h" #include "utils.h" @@ -33,42 +8,80 @@ QT_BEGIN_NAMESPACE -#ifdef USE_LEXEM_STORE -Symbol::LexemStore Symbol::lexemStore; -#endif - static const char *error_msg = nullptr; +/*! \internal + Base implementation for printing diagnostic messages. + + For example: + "/path/to/file:line:column: error: %s\n" + '%s' is replaced by \a msg. (Currently "column" is always 1). + + If sym.lineNum is -1, the line and column parts aren't printed: + "/path/to/file: error: %s\n" + + \a formatStringSuffix specifies the type of the message e.g.: + "error: %s\n" + "warning: %s\n" + "note: %s\n" + "Parse error at %s\n" (from defaultErrorMsg()) +*/ +void Parser::printMsg(QByteArrayView formatStringSuffix, QByteArrayView msg, const Symbol &sym) +{ + if (sym.lineNum != -1) { #ifdef Q_CC_MSVC -#define ErrorFormatString "%s(%d:%d): " + QByteArray formatString = "%s(%d:%d): " + formatStringSuffix; #else -#define ErrorFormatString "%s:%d:%d: " + QByteArray formatString = "%s:%d:%d: " + formatStringSuffix; #endif + fprintf(stderr, formatString.constData(), + currentFilenames.top().constData(), sym.lineNum, 1, msg.data()); + } else { + QByteArray formatString = "%s: " + formatStringSuffix; + fprintf(stderr, formatString.constData(), + currentFilenames.top().constData(), msg.data()); + } +} + +void Parser::defaultErrorMsg(const Symbol &sym) +{ + if (sym.lineNum != -1) + printMsg("error: Parse error at \"%s\"\n", sym.lexem().data(), sym); + else + printMsg("error: could not parse file\n", "", sym); +} -void Parser::error(int rollback) { - index -= rollback; - error(); +void Parser::error(const Symbol &sym) +{ + defaultErrorMsg(sym); + exit(EXIT_FAILURE); } -void Parser::error(const char *msg) { + +void Parser::error(const char *msg) +{ if (msg || error_msg) - fprintf(stderr, ErrorFormatString "error: %s\n", - currentFilenames.top().constData(), symbol().lineNum, 1, msg?msg:error_msg); + printMsg("error: %s\n", + msg ? msg : error_msg, + index > 0 ? symbol() : Symbol{}); else - fprintf(stderr, ErrorFormatString "error: Parse error at \"%s\"\n", - currentFilenames.top().constData(), symbol().lineNum, 1, symbol().lexem().data()); + defaultErrorMsg(symbol()); + exit(EXIT_FAILURE); } +void Parser::warning(const Symbol &sym, QByteArrayView msg) +{ + if (displayWarnings) + printMsg("warning: %s\n", msg, sym); +} + void Parser::warning(const char *msg) { - if (displayWarnings && msg) - fprintf(stderr, ErrorFormatString "warning: %s\n", - currentFilenames.top().constData(), qMax(0, index > 0 ? symbol().lineNum : 0), 1, msg); + warning(index > 0 ? symbol() : Symbol{}, msg); } void Parser::note(const char *msg) { if (displayNotes && msg) - fprintf(stderr, ErrorFormatString "note: %s\n", - currentFilenames.top().constData(), qMax(0, index > 0 ? symbol().lineNum : 0), 1, msg); + printMsg("note: %s\n", msg, index > 0 ? symbol() : Symbol{}); } QT_END_NAMESPACE diff --git a/src/tools/moc/parser.h b/src/tools/moc/parser.h index f014d7e6b5..6fe982a1ce 100644 --- a/src/tools/moc/parser.h +++ b/src/tools/moc/parser.h @@ -1,35 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef PARSER_H #define PARSER_H #include "symbols.h" +#include <QtCore/qbytearrayview.h> #include <stack> @@ -40,7 +16,7 @@ class Parser public: Parser():index(0), displayWarnings(true), displayNotes(true) {} Symbols symbols; - int index; + qsizetype index; bool displayWarnings; bool displayNotes; @@ -68,11 +44,15 @@ public: inline QByteArray lexem() { return symbols.at(index-1).lexem();} inline QByteArray unquotedLexem() { return symbols.at(index-1).unquotedLexem();} inline const Symbol &symbol() { return symbols.at(index-1);} + inline const Symbol &symbolAt(qsizetype idx) { return symbols.at(idx); } - Q_NORETURN void error(int rollback); + Q_NORETURN void error(const Symbol &symbol); Q_NORETURN void error(const char *msg = nullptr); void warning(const char * = nullptr); + void warning(const Symbol &sym, QByteArrayView msg); void note(const char * = nullptr); + void defaultErrorMsg(const Symbol &sym); + void printMsg(QByteArrayView formatStringSuffix, QByteArrayView msg, const Symbol &sym); }; @@ -87,7 +67,7 @@ inline bool Parser::test(Token token) inline Token Parser::lookup(int k) { - const int l = index - 1 + k; + const qsizetype l = index - 1 + k; return l < symbols.size() ? symbols.at(l).token : NOTOKEN; } diff --git a/src/tools/moc/ppkeywords.cpp b/src/tools/moc/ppkeywords.cpp index b94abf8cd8..dc13f3db95 100644 --- a/src/tools/moc/ppkeywords.cpp +++ b/src/tools/moc/ppkeywords.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // auto generated // DO NOT EDIT. diff --git a/src/tools/moc/preprocessor.cpp b/src/tools/moc/preprocessor.cpp index c6e84c0913..11ea8d417e 100644 --- a/src/tools/moc/preprocessor.cpp +++ b/src/tools/moc/preprocessor.cpp @@ -1,31 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2014 Olivier Goffart <ogoffart@woboq.org> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2014 Olivier Goffart <ogoffart@woboq.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "preprocessor.h" #include "utils.h" @@ -33,9 +8,12 @@ #include <qfile.h> #include <qdir.h> #include <qfileinfo.h> +#include <qvarlengtharray.h> QT_BEGIN_NAMESPACE +using namespace QtMiscUtils; + #include "ppkeywords.cpp" #include "keywords.cpp" @@ -236,7 +214,9 @@ Symbols Preprocessor::tokenize(const QByteArray& input, int lineNum, Preprocesso data -= 2; break; case DIGIT: - while (is_digit_char(*data) || *data == '\'') + { + bool hasSeenTokenSeparator = false;; + while (isAsciiDigit(*data) || (hasSeenTokenSeparator = *data == '\'')) ++data; if (!*data || *data != '.') { token = INTEGER_LITERAL; @@ -245,22 +225,30 @@ Symbols Preprocessor::tokenize(const QByteArray& input, int lineNum, Preprocesso || *data == 'b' || *data == 'B') && *lexem == '0') { ++data; - while (is_hex_char(*data) || *data == '\'') + while (isHexDigit(*data) || (hasSeenTokenSeparator = *data == '\'')) + ++data; + } else if (*data == 'L') // TODO: handle other suffixes + ++data; + if (!hasSeenTokenSeparator) { + while (is_ident_char(*data)) { ++data; + token = IDENTIFIER; + } } break; } token = FLOATING_LITERAL; ++data; Q_FALLTHROUGH(); + } case FLOATING_LITERAL: - while (is_digit_char(*data) || *data == '\'') + while (isAsciiDigit(*data) || *data == '\'') ++data; if (*data == '+' || *data == '-') ++data; if (*data == 'e' || *data == 'E') { ++data; - while (is_digit_char(*data) || *data == '\'') + while (isAsciiDigit(*data) || *data == '\'') ++data; } if (*data == 'f' || *data == 'F' @@ -339,16 +327,7 @@ Symbols Preprocessor::tokenize(const QByteArray& input, int lineNum, Preprocesso continue; //ignore } } -#ifdef USE_LEXEM_STORE - if (!Preprocessor::preprocessOnly - && token != IDENTIFIER - && token != STRING_LITERAL - && token != FLOATING_LITERAL - && token != INTEGER_LITERAL) - symbols += Symbol(lineNum, token); - else -#endif - symbols += Symbol(lineNum, token, input, lexem-begin, data-lexem); + symbols += Symbol(lineNum, token, input, lexem-begin, data-lexem); } else { // Preprocessor @@ -414,7 +393,7 @@ Symbols Preprocessor::tokenize(const QByteArray& input, int lineNum, Preprocesso token = PP_CHARACTER_LITERAL; break; case PP_DIGIT: - while (is_digit_char(*data) || *data == '\'') + while (isAsciiDigit(*data) || *data == '\'') ++data; if (!*data || *data != '.') { token = PP_INTEGER_LITERAL; @@ -422,22 +401,23 @@ Symbols Preprocessor::tokenize(const QByteArray& input, int lineNum, Preprocesso (*data == 'x' || *data == 'X') && *lexem == '0') { ++data; - while (is_hex_char(*data) || *data == '\'') + while (isHexDigit(*data) || *data == '\'') ++data; - } + } else if (*data == 'L') // TODO: handle other suffixes + ++data; break; } token = PP_FLOATING_LITERAL; ++data; Q_FALLTHROUGH(); case PP_FLOATING_LITERAL: - while (is_digit_char(*data) || *data == '\'') + while (isAsciiDigit(*data) || *data == '\'') ++data; if (*data == '+' || *data == '-') ++data; if (*data == 'e' || *data == 'E') { ++data; - while (is_digit_char(*data) || *data == '\'') + while (isAsciiDigit(*data) || *data == '\'') ++data; } if (*data == 'f' || *data == 'F' @@ -519,22 +499,14 @@ Symbols Preprocessor::tokenize(const QByteArray& input, int lineNum, Preprocesso } if (mode == PreparePreprocessorStatement) continue; -#ifdef USE_LEXEM_STORE - if (token != PP_IDENTIFIER - && token != PP_STRING_LITERAL - && token != PP_FLOATING_LITERAL - && token != PP_INTEGER_LITERAL) - symbols += Symbol(lineNum, token); - else -#endif - symbols += Symbol(lineNum, token, input, lexem-begin, data-lexem); + symbols += Symbol(lineNum, token, input, lexem-begin, data-lexem); } } symbols += Symbol(); // eof symbol return symbols; } -void Preprocessor::macroExpand(Symbols *into, Preprocessor *that, const Symbols &toExpand, int &index, +void Preprocessor::macroExpand(Symbols *into, Preprocessor *that, const Symbols &toExpand, qsizetype &index, int lineNum, bool one, const QSet<QByteArray> &excludeSymbols) { SymbolStack symbols; @@ -642,19 +614,22 @@ Symbols Preprocessor::macroExpandIdentifier(Preprocessor *that, SymbolStack &sym HashHash } mode = Normal; - for (int i = 0; i < macro.symbols.size(); ++i) { - const Symbol &s = macro.symbols.at(i); + const auto end = macro.symbols.cend(); + auto it = macro.symbols.cbegin(); + const auto lastSym = std::prev(macro.symbols.cend(), !macro.symbols.isEmpty() ? 1 : 0); + for (; it != end; ++it) { + const Symbol &s = *it; if (s.token == HASH || s.token == PP_HASHHASH) { mode = (s.token == HASH ? Hash : HashHash); continue; } - int index = macro.arguments.indexOf(s); + const qsizetype index = macro.arguments.indexOf(s); if (mode == Normal) { if (index >= 0 && index < arguments.size()) { // each argument undoergoes macro expansion if it's not used as part of a # or ## - if (i == macro.symbols.size() - 1 || macro.symbols.at(i + 1).token != PP_HASHHASH) { + if (it == lastSym || std::next(it)->token != PP_HASHHASH) { Symbols arg = arguments.at(index); - int idx = 1; + qsizetype idx = 1; macroExpand(&expansion, that, arg, idx, lineNum, false, symbols.excludeSymbols()); } else { expansion += arguments.at(index); @@ -673,9 +648,9 @@ Symbols Preprocessor::macroExpandIdentifier(Preprocessor *that, SymbolStack &sym const Symbols &arg = arguments.at(index); QByteArray stringified; - for (int i = 0; i < arg.size(); ++i) { - stringified += arg.at(i).lexem(); - } + for (const Symbol &sym : arg) + stringified += sym.lexem(); + stringified.replace('"', "\\\""); stringified.prepend('"'); stringified.append('"'); @@ -709,8 +684,8 @@ Symbols Preprocessor::macroExpandIdentifier(Preprocessor *that, SymbolStack &sym if (index >= 0 && index < arguments.size()) { const Symbols &arg = arguments.at(index); - for (int i = 1; i < arg.size(); ++i) - expansion += arg.at(i); + if (!arg.isEmpty()) + expansion.append(arg.cbegin() + 1, arg.cend()); } } mode = Normal; @@ -951,7 +926,11 @@ int PP_Expression::primary_expression() test(PP_RPAREN); } else { next(); - value = lexem().toInt(nullptr, 0); + const QByteArray &lex = lexem(); + auto lexView = QByteArrayView(lex); + if (lex.endsWith('L')) + lexView.chop(1); + value = lexView.toInt(nullptr, 0); } return value; } @@ -990,7 +969,7 @@ static void mergeStringLiterals(Symbols *_symbols) for (Symbols::iterator i = symbols.begin(); i != symbols.end(); ++i) { if (i->token == STRING_LITERAL) { Symbols::Iterator mergeSymbol = i; - int literalsLength = mergeSymbol->len; + qsizetype literalsLength = mergeSymbol->len; while (++i != symbols.end() && i->token == STRING_LITERAL) literalsLength += i->len - 2; // no quotes @@ -1004,7 +983,7 @@ static void mergeStringLiterals(Symbols *_symbols) for (Symbols::iterator j = mergeSymbol + 1; j != i; ++j) mergeSymbolLexem.append(j->lex.constData() + j->from + 1, j->len - 2); // append j->unquotedLexem() mergeSymbolLexem.append('"'); - mergeSymbol->len = mergeSymbol->lex.length(); + mergeSymbol->len = mergeSymbol->lex.size(); mergeSymbol->from = 0; i = symbols.erase(mergeSymbol + 1, i); } @@ -1015,13 +994,21 @@ static void mergeStringLiterals(Symbols *_symbols) } static QByteArray searchIncludePaths(const QList<Parser::IncludePath> &includepaths, - const QByteArray &include) + const QByteArray &include, + const bool debugIncludes) { QFileInfo fi; - for (int j = 0; j < includepaths.size() && !fi.exists(); ++j) { - const Parser::IncludePath &p = includepaths.at(j); + + if (Q_UNLIKELY(debugIncludes)) { + fprintf(stderr, "debug-includes: searching for '%s'\n", include.constData()); + } + + for (const Parser::IncludePath &p : includepaths) { + if (fi.exists()) + break; + if (p.isFrameworkPath) { - const int slashPos = include.indexOf('/'); + const qsizetype slashPos = include.indexOf('/'); if (slashPos == -1) continue; fi.setFile(QString::fromLocal8Bit(p.path + '/' + include.left(slashPos) + ".framework/Headers/"), @@ -1029,6 +1016,12 @@ static QByteArray searchIncludePaths(const QList<Parser::IncludePath> &includepa } else { fi.setFile(QString::fromLocal8Bit(p.path), QString::fromLocal8Bit(include)); } + + if (Q_UNLIKELY(debugIncludes)) { + const auto candidate = fi.filePath().toLocal8Bit(); + fprintf(stderr, "debug-includes: considering '%s'\n", candidate.constData()); + } + // try again, maybe there's a file later in the include paths with the same name // (186067) if (fi.isDir()) { @@ -1037,9 +1030,20 @@ static QByteArray searchIncludePaths(const QList<Parser::IncludePath> &includepa } } - if (!fi.exists() || fi.isDir()) + if (!fi.exists() || fi.isDir()) { + if (Q_UNLIKELY(debugIncludes)) { + fprintf(stderr, "debug-includes: can't find '%s'\n", include.constData()); + } return QByteArray(); - return fi.canonicalFilePath().toLocal8Bit(); + } + + const auto result = fi.canonicalFilePath().toLocal8Bit(); + + if (Q_UNLIKELY(debugIncludes)) { + fprintf(stderr, "debug-includes: found '%s'\n", result.constData()); + } + + return result; } QByteArray Preprocessor::resolveInclude(const QByteArray &include, const QByteArray &relativeTo) @@ -1053,7 +1057,11 @@ QByteArray Preprocessor::resolveInclude(const QByteArray &include, const QByteAr auto it = nonlocalIncludePathResolutionCache.find(include); if (it == nonlocalIncludePathResolutionCache.end()) - it = nonlocalIncludePathResolutionCache.insert(include, searchIncludePaths(includes, include)); + it = nonlocalIncludePathResolutionCache.insert(include, + searchIncludePaths( + includes, + include, + debugIncludes)); return it.value(); } @@ -1096,7 +1104,7 @@ void Preprocessor::preprocess(const QByteArray &filename, Symbols &preprocessed) continue; Symbols saveSymbols = symbols; - int saveIndex = index; + qsizetype saveIndex = index; // phase 1: get rid of backslash-newlines input = cleaned(input); @@ -1131,14 +1139,14 @@ void Preprocessor::preprocess(const QByteArray &filename, Symbols &preprocessed) } else { macro.isFunction = false; } - int start = index; + qsizetype start = index; until(PP_NEWLINE); macro.symbols.reserve(index - start - 1); // remove whitespace where there shouldn't be any: // Before and after the macro, after a # and around ## Token lastToken = HASH; // skip shitespace at the beginning - for (int i = start; i < index - 1; ++i) { + for (qsizetype i = start; i < index - 1; ++i) { Token token = symbols.at(i).token; if (token == WHITESPACE) { if (lastToken == PP_HASH || lastToken == HASH || @@ -1281,7 +1289,7 @@ void Preprocessor::parseDefineArguments(Macro *m) if (!test(PP_RPAREN)) error("missing ')' in macro argument list"); break; - } else if (!is_identifier(l.constData(), l.length())) { + } else if (!is_identifier(l.constData(), l.size())) { error("Unexpected character in macro argument list."); } } @@ -1318,4 +1326,10 @@ void Preprocessor::until(Token t) ; } +void Preprocessor::setDebugIncludes(bool value) +{ + debugIncludes = value; +} + + QT_END_NAMESPACE diff --git a/src/tools/moc/preprocessor.h b/src/tools/moc/preprocessor.h index 39f56d6e92..3509e83dce 100644 --- a/src/tools/moc/preprocessor.h +++ b/src/tools/moc/preprocessor.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef PREPROCESSOR_H #define PREPROCESSOR_H @@ -45,11 +20,7 @@ struct Macro Symbols symbols; }; -#ifdef USE_LEXEM_STORE -typedef QByteArray MacroName; -#else typedef SubArray MacroName; -#endif typedef QHash<MacroName, Macro> Macros; class QFile; @@ -73,18 +44,22 @@ public: void substituteUntilNewline(Symbols &substituted); static Symbols macroExpandIdentifier(Preprocessor *that, SymbolStack &symbols, int lineNum, QByteArray *macroName); - static void macroExpand(Symbols *into, Preprocessor *that, const Symbols &toExpand, int &index, int lineNum, bool one, - const QSet<QByteArray> &excludeSymbols = QSet<QByteArray>()); + static void macroExpand(Symbols *into, Preprocessor *that, const Symbols &toExpand, + qsizetype &index, int lineNum, bool one, + const QSet<QByteArray> &excludeSymbols = QSet<QByteArray>()); int evaluateCondition(); enum TokenizeMode { TokenizeCpp, TokenizePreprocessor, PreparePreprocessorStatement, TokenizePreprocessorStatement, TokenizeInclude, PrepareDefine, TokenizeDefine }; static Symbols tokenize(const QByteArray &input, int lineNum = 1, TokenizeMode mode = TokenizeCpp); + void setDebugIncludes(bool value); + private: void until(Token); void preprocess(const QByteArray &filename, Symbols &preprocessed); + bool debugIncludes = false; }; QT_END_NAMESPACE diff --git a/src/tools/moc/symbols.h b/src/tools/moc/symbols.h index bbb1312cdc..869f7c793f 100644 --- a/src/tools/moc/symbols.h +++ b/src/tools/moc/symbols.h @@ -1,111 +1,62 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2013 Olivier Goffart <ogoffart@woboq.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef SYMBOLS_H #define SYMBOLS_H #include "token.h" #include <qdebug.h> -#include <qhash.h> +#include <qhashfunctions.h> #include <qlist.h> #include <qstack.h> #include <qstring.h> +#include <qset.h> QT_BEGIN_NAMESPACE -//#define USE_LEXEM_STORE - struct SubArray { - inline SubArray():from(0),len(-1){} + inline SubArray() = default; inline SubArray(const QByteArray &a):array(a),from(0), len(a.size()){} inline SubArray(const char *s):array(s),from(0) { len = array.size(); } - inline SubArray(const QByteArray &a, int from, int len):array(a), from(from), len(len){} + SubArray(const QByteArray &a, qsizetype from, qsizetype len) + : array(a), from(from), len(len) + { + } QByteArray array; - int from, len; + qsizetype from = 0; + qsizetype len = -1; inline bool operator==(const SubArray &other) const { if (len != other.len) return false; - for (int i = 0; i < len; ++i) - if (array.at(from + i) != other.array.at(other.from + i)) - return false; - return true; + const auto begin = array.cbegin() + from; + const auto end = begin + len; + const auto other_begin = other.array.cbegin() + other.from; + return std::equal(begin, end, other_begin); } }; -inline size_t qHash(const SubArray &key) +inline size_t qHash(const SubArray &key, size_t seed = 0) { - return qHash(QLatin1String(key.array.constData() + key.from, key.len)); + return qHash(QLatin1StringView(key.array.constData() + key.from, key.len), seed); } struct Symbol { - -#ifdef USE_LEXEM_STORE - typedef QHash<SubArray, QHashDummyValue> LexemStore; - static LexemStore lexemStore; - - inline Symbol() : lineNum(-1),token(NOTOKEN){} - inline Symbol(int lineNum, Token token): - lineNum(lineNum), token(token){} - inline Symbol(int lineNum, Token token, const QByteArray &lexem): - lineNum(lineNum), token(token),lex(lexem){} - inline Symbol(int lineNum, Token token, const QByteArray &lexem, int from, int len): - lineNum(lineNum), token(token){ - LexemStore::const_iterator it = lexemStore.constFind(SubArray(lexem, from, len)); - - if (it != lexemStore.constEnd()) { - lex = it.key().array; - } else { - lex = lexem.mid(from, len); - lexemStore.insert(lex, QHashDummyValue()); - } + inline Symbol() = default; + inline Symbol(int lineNum, Token token) : lineNum(lineNum), token(token) { } + inline Symbol(int lineNum, Token token, const QByteArray &lexem) + : lineNum(lineNum), token(token), lex(lexem), len(lex.size()) + { } - int lineNum; - Token token; - inline QByteArray unquotedLexem() const { return lex.mid(1, lex.length()-2); } - inline QByteArray lexem() const { return lex; } - inline operator QByteArray() const { return lex; } - QByteArray lex; - -#else - - inline Symbol() : lineNum(-1),token(NOTOKEN), from(0),len(-1) {} - inline Symbol(int lineNum, Token token): - lineNum(lineNum), token(token), from(0), len(-1) {} - inline Symbol(int lineNum, Token token, const QByteArray &lexem): - lineNum(lineNum), token(token), lex(lexem), from(0) { len = lex.size(); } - inline Symbol(int lineNum, Token token, const QByteArray &lexem, int from, int len): - lineNum(lineNum), token(token),lex(lexem),from(from), len(len){} - int lineNum; - Token token; + Symbol(int lineNum, Token token, const QByteArray &lexem, qsizetype from, qsizetype len) + : lineNum(lineNum), token(token), lex(lexem), from(from), len(len) + { + } + int lineNum = -1; + Token token = NOTOKEN; inline QByteArray lexem() const { return lex.mid(from, len); } inline QByteArray unquotedLexem() const { return lex.mid(from+1, len-2); } inline operator SubArray() const { return SubArray(lex, from, len); } @@ -114,9 +65,8 @@ struct Symbol return SubArray(lex, from, len) == SubArray(o.lex, o.from, o.len); } QByteArray lex; - int from, len; - -#endif + qsizetype from = 0; + qsizetype len = -1; }; Q_DECLARE_TYPEINFO(Symbol, Q_RELOCATABLE_TYPE); @@ -126,7 +76,7 @@ struct SafeSymbols { Symbols symbols; QByteArray expandedMacro; QSet<QByteArray> excludedSymbols; - int index; + qsizetype index; }; Q_DECLARE_TYPEINFO(SafeSymbols, Q_RELOCATABLE_TYPE); @@ -151,13 +101,13 @@ public: inline QByteArray lexem() const { return symbol().lexem(); } inline QByteArray unquotedLexem() { return symbol().unquotedLexem(); } - bool dontReplaceSymbol(const QByteArray &name); - QSet<QByteArray> excludeSymbols(); + bool dontReplaceSymbol(const QByteArray &name) const; + QSet<QByteArray> excludeSymbols() const; }; inline bool SymbolStack::test(Token token) { - int stackPos = size() - 1; + qsizetype stackPos = size() - 1; while (stackPos >= 0 && at(stackPos).index >= at(stackPos).symbols.size()) --stackPos; if (stackPos < 0) @@ -169,21 +119,20 @@ inline bool SymbolStack::test(Token token) return false; } -inline bool SymbolStack::dontReplaceSymbol(const QByteArray &name) +inline bool SymbolStack::dontReplaceSymbol(const QByteArray &name) const { - for (int i = 0; i < size(); ++i) { - if (name == at(i).expandedMacro || at(i).excludedSymbols.contains(name)) - return true; - } - return false; + auto matchesName = [&name](const SafeSymbols &sf) { + return name == sf.expandedMacro || sf.excludedSymbols.contains(name); + }; + return std::any_of(cbegin(), cend(), matchesName); } -inline QSet<QByteArray> SymbolStack::excludeSymbols() +inline QSet<QByteArray> SymbolStack::excludeSymbols() const { QSet<QByteArray> set; - for (int i = 0; i < size(); ++i) { - set << at(i).expandedMacro; - set += at(i).excludedSymbols; + for (const SafeSymbols &sf : *this) { + set << sf.expandedMacro; + set += sf.excludedSymbols; } return set; } diff --git a/src/tools/moc/token.cpp b/src/tools/moc/token.cpp index cf1aa102c7..bfefaff57b 100644 --- a/src/tools/moc/token.cpp +++ b/src/tools/moc/token.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "token.h" diff --git a/src/tools/moc/token.h b/src/tools/moc/token.h index c11ec6a38c..a70808370d 100644 --- a/src/tools/moc/token.h +++ b/src/tools/moc/token.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef TOKEN_H #define TOKEN_H @@ -154,9 +129,11 @@ QT_BEGIN_NAMESPACE F(RETURN) \ F(Q_OBJECT_TOKEN) \ F(Q_GADGET_TOKEN) \ + F(Q_GADGET_EXPORT_TOKEN) \ F(Q_NAMESPACE_TOKEN) \ F(Q_NAMESPACE_EXPORT_TOKEN) \ F(Q_PROPERTY_TOKEN) \ + F(QT_ANONYMOUS_PROPERTY_TOKEN) \ F(Q_PLUGIN_METADATA_TOKEN) \ F(Q_ENUMS_TOKEN) \ F(Q_ENUM_TOKEN) \ @@ -178,6 +155,7 @@ QT_BEGIN_NAMESPACE F(Q_INVOKABLE_TOKEN) \ F(Q_SCRIPTABLE_TOKEN) \ F(Q_PRIVATE_PROPERTY_TOKEN) \ + F(QT_ANONYMOUS_PRIVATE_PROPERTY_TOKEN) \ F(Q_REVISION_TOKEN) \ F(Q_MOC_INCLUDE_TOKEN) \ F(SPECIAL_TREATMENT_MARK) \ diff --git a/src/tools/moc/util/generate.sh b/src/tools/moc/util/generate.sh index 5460d28924..6be06e5a91 100755 --- a/src/tools/moc/util/generate.sh +++ b/src/tools/moc/util/generate.sh @@ -1,37 +1,12 @@ #!/bin/sh -############################################################################# -## -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is the build configuration utility of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:GPL-EXCEPT$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 3 as published by the Free Software -## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 set -ex qmake make -cat licenseheader.txt > ../keywords.cpp -cat licenseheader.txt > ../ppkeywords.cpp +cat licenseheader.cpp.in > ../keywords.cpp +cat licenseheader.cpp.in > ../ppkeywords.cpp ./generate_keywords >> ../keywords.cpp ./generate_keywords preprocessor >> ../ppkeywords.cpp diff --git a/src/tools/moc/util/generate_keywords.cpp b/src/tools/moc/util/generate_keywords.cpp index 28c5eeff38..a6c85af9f1 100644 --- a/src/tools/moc/util/generate_keywords.cpp +++ b/src/tools/moc/util/generate_keywords.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <stdio.h> #include <string.h> #include <qbytearray.h> @@ -216,7 +191,9 @@ static const Keyword keywords[] = { { "Q_NAMESPACE", "Q_NAMESPACE_TOKEN" }, { "Q_NAMESPACE_EXPORT", "Q_NAMESPACE_EXPORT_TOKEN" }, { "Q_GADGET", "Q_GADGET_TOKEN" }, + { "Q_GADGET_EXPORT", "Q_GADGET_EXPORT_TOKEN" }, { "Q_PROPERTY", "Q_PROPERTY_TOKEN" }, + { "QT_ANONYMOUS_PROPERTY", "QT_ANONYMOUS_PROPERTY_TOKEN" }, { "Q_PLUGIN_METADATA", "Q_PLUGIN_METADATA_TOKEN" }, { "Q_ENUMS", "Q_ENUMS_TOKEN" }, { "Q_ENUM", "Q_ENUM_TOKEN" }, @@ -242,6 +219,7 @@ static const Keyword keywords[] = { { "Q_SLOT", "Q_SLOT_TOKEN" }, { "Q_SCRIPTABLE", "Q_SCRIPTABLE_TOKEN" }, { "Q_PRIVATE_PROPERTY", "Q_PRIVATE_PROPERTY_TOKEN" }, + { "QT_ANONYMOUS_PRIVATE_PROPERTY", "QT_ANONYMOUS_PRIVATE_PROPERTY_TOKEN" }, { "Q_REVISION", "Q_REVISION_TOKEN" }, { "Q_MOC_INCLUDE", "Q_MOC_INCLUDE_TOKEN" }, { "\n", "NEWLINE" }, diff --git a/src/tools/moc/util/generate_keywords.pro b/src/tools/moc/util/generate_keywords.pro new file mode 100644 index 0000000000..e29738c18a --- /dev/null +++ b/src/tools/moc/util/generate_keywords.pro @@ -0,0 +1,5 @@ +CONFIG -= moc +CONFIG += cmdline +QT = core + +SOURCES += generate_keywords.cpp diff --git a/src/tools/moc/util/licenseheader.cpp.in b/src/tools/moc/util/licenseheader.cpp.in new file mode 100644 index 0000000000..42958a66f5 --- /dev/null +++ b/src/tools/moc/util/licenseheader.cpp.in @@ -0,0 +1,3 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + diff --git a/src/tools/moc/util/licenseheader.txt b/src/tools/moc/util/licenseheader.txt deleted file mode 100644 index b2b02f82eb..0000000000 --- a/src/tools/moc/util/licenseheader.txt +++ /dev/null @@ -1,28 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - diff --git a/src/tools/moc/utils.h b/src/tools/moc/utils.h index 4cb1d90345..0b0d70f462 100644 --- a/src/tools/moc/utils.h +++ b/src/tools/moc/utils.h @@ -1,35 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef UTILS_H #define UTILS_H #include <QtCore/qglobal.h> +#include <private/qtools_p.h> + +#include <algorithm> QT_BEGIN_NAMESPACE @@ -45,49 +23,20 @@ inline bool is_space(char s) inline bool is_ident_start(char s) { - return ((s >= 'a' && s <= 'z') - || (s >= 'A' && s <= 'Z') - || s == '_' || s == '$' - ); + using namespace QtMiscUtils; + return isAsciiLower(s) || isAsciiUpper(s) || s == '_' || s == '$'; } inline bool is_ident_char(char s) { - return ((s >= 'a' && s <= 'z') - || (s >= 'A' && s <= 'Z') - || (s >= '0' && s <= '9') - || s == '_' || s == '$' - ); + return QtMiscUtils::isAsciiLetterOrNumber(s) || s == '_' || s == '$'; } -inline bool is_identifier(const char *s, int len) +inline bool is_identifier(const char *s, qsizetype len) { if (len < 1) return false; - if (!is_ident_start(*s)) - return false; - for (int i = 1; i < len; ++i) - if (!is_ident_char(s[i])) - return false; - return true; -} - -inline bool is_digit_char(char s) -{ - return (s >= '0' && s <= '9'); -} - -inline bool is_octal_char(char s) -{ - return (s >= '0' && s <= '7'); -} - -inline bool is_hex_char(char s) -{ - return ((s >= 'a' && s <= 'f') - || (s >= 'A' && s <= 'F') - || (s >= '0' && s <= '9') - ); + return std::all_of(s, s + len, is_ident_char); } inline const char *skipQuote(const char *data) diff --git a/src/tools/qdbuscpp2xml/CMakeLists.txt b/src/tools/qdbuscpp2xml/CMakeLists.txt index 865995755b..781c1835bc 100644 --- a/src/tools/qdbuscpp2xml/CMakeLists.txt +++ b/src/tools/qdbuscpp2xml/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from qdbuscpp2xml.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## qdbuscpp2xml Tool: @@ -6,8 +7,10 @@ qt_get_tool_target_name(target_name qdbuscpp2xml) qt_internal_add_tool(${target_name} + TRY_RUN + TRY_RUN_FLAGS "-V" TARGET_DESCRIPTION "Qt D-Bus C++ to XML Compiler" - TOOLS_TARGET DBus # special case + TOOLS_TARGET DBus SOURCES ../moc/cbordevice.h ../moc/collectjson.cpp ../moc/collectjson.h @@ -26,24 +29,8 @@ qt_internal_add_tool(${target_name} INCLUDE_DIRECTORIES ../moc ../moc/../../3rdparty/tinycbor/src - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::DBusPrivate - # COMPILE_OPTIONS # special case - # "$$QT_HOST_CFLAGS_DBUS" - # QMAKE_TARGET_DESCRIPTION = "Qt D-Bus C++ to XML Compiler" - # _LOADED = "qt_tool" - # _OPTION = "host_build" ) - -#### Keys ignored in scope 1:.:.:qdbuscpp2xml.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "Qt D-Bus C++ to XML Compiler" -# _OPTION = "host_build" - -## Scopes: -##################################################################### - -# special case begin -# qt_internal_extend_target(qdbuscpp2xml CONDITION force_bootstrap [...]) -# qt_internal_extend_target(qdbuscpp2xml CONDITION NOT force_bootstrap [...]) -# special case end +qt_internal_return_unless_building_tools() diff --git a/src/tools/qdbuscpp2xml/qdbuscpp2xml.cpp b/src/tools/qdbuscpp2xml/qdbuscpp2xml.cpp index fad92994f9..3b7d73894b 100644 --- a/src/tools/qdbuscpp2xml/qdbuscpp2xml.cpp +++ b/src/tools/qdbuscpp2xml/qdbuscpp2xml.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <qbuffer.h> #include <qbytearray.h> @@ -40,6 +15,9 @@ #include <qdbusconnection.h> // for the Export* flags #include <private/qdbusconnection_p.h> // for the qDBusCheckAsyncTag +#include <private/qdbusmetatype_p.h> // for QDBusMetaTypeId::init() + +using namespace Qt::StringLiterals; // copied from dbus-protocol.h: static const char docTypeHeader[] = @@ -60,7 +38,7 @@ static const char docTypeHeader[] = #define PROGRAMNAME "qdbuscpp2xml" #define PROGRAMVERSION "0.2" -#define PROGRAMCOPYRIGHT "Copyright (C) 2021 The Qt Company Ltd." +#define PROGRAMCOPYRIGHT QT_COPYRIGHT static QString outputFile; static int flags; @@ -95,7 +73,7 @@ int qDBusParametersForMethod(const FunctionDef &mm, QList<QMetaType> &metaTypes, static inline QString typeNameToXml(const char *typeName) { - QString plain = QLatin1String(typeName); + QString plain = QLatin1StringView(typeName); return plain.toHtmlEscaped(); } @@ -133,13 +111,13 @@ static QString addFunction(const FunctionDef &mm, bool isSignal = false) { qWarning() << qPrintable(errorMsg); return QString(); // invalid form } - if (isSignal && inputCount + 1 != types.count()) + if (isSignal && inputCount + 1 != types.size()) return QString(); // signal with output arguments? if (isSignal && types.at(inputCount) == QDBusMetaTypeId::message()) return QString(); // signal with QDBusMessage argument? bool isScriptable = mm.isScriptable; - for (int j = 1; j < types.count(); ++j) { + for (qsizetype j = 1; j < types.size(); ++j) { // input parameter for a slot or output for a signal if (types.at(j) == QDBusMetaTypeId::message()) { isScriptable = true; @@ -155,14 +133,14 @@ static QString addFunction(const FunctionDef &mm, bool isSignal = false) { const char *signature = QDBusMetaType::typeToSignature(QMetaType(types.at(j))); xml += QString::fromLatin1(" <arg %1type=\"%2\" direction=\"%3\"/>\n") .arg(name, - QLatin1String(signature), - isOutput ? QLatin1String("out") : QLatin1String("in")); + QLatin1StringView(signature), + isOutput ? "out"_L1 : "in"_L1); // do we need to describe this argument? if (!QDBusMetaType::signatureToMetaType(signature).isValid()) { const char *typeName = QMetaType(types.at(j)).name(); xml += QString::fromLatin1(" <annotation name=\"org.qtproject.QtDBus.QtTypeName.%1%2\" value=\"%3\"/>\n") - .arg(isOutput ? QLatin1String("Out") : QLatin1String("In")) + .arg(isOutput ? "Out"_L1 : "In"_L1) .arg(isOutput && !isSignal ? j - inputCount : j - 1) .arg(typeNameToXml(typeName)); } @@ -180,12 +158,10 @@ static QString addFunction(const FunctionDef &mm, bool isSignal = false) { if (qDBusCheckAsyncTag(mm.tag.constData())) // add the no-reply annotation - xml += QLatin1String(" <annotation name=\"" ANNOTATION_NO_WAIT "\"" - " value=\"true\"/>\n"); + xml += " <annotation name=\"" ANNOTATION_NO_WAIT "\" value=\"true\"/>\n"_L1; QString retval = xml; - retval += QString::fromLatin1(" </%1>\n") - .arg(isSignal ? QLatin1String("signal") : QLatin1String("method")); + retval += QString::fromLatin1(" </%1>\n").arg(isSignal ? "signal"_L1 : "method"_L1); return retval; } @@ -209,6 +185,8 @@ static QString generateInterfaceXml(const ClassDef *mo) access |= 1; if (!mp.write.isEmpty()) access |= 2; + if (!mp.member.isEmpty()) + access |= 3; int typeId = QMetaType::fromName(mp.type).id(); if (!typeId) { @@ -221,15 +199,15 @@ static QString generateInterfaceXml(const ClassDef *mo) continue; retval += QString::fromLatin1(" <property name=\"%1\" type=\"%2\" access=\"%3\"") - .arg(QLatin1String(mp.name), - QLatin1String(signature), - QLatin1String(accessvalues[access])); + .arg(QLatin1StringView(mp.name), + QLatin1StringView(signature), + QLatin1StringView(accessvalues[access])); if (!QDBusMetaType::signatureToMetaType(signature).isValid()) { retval += QString::fromLatin1(">\n <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"%3\"/>\n </property>\n") .arg(typeNameToXml(mp.type.constData())); } else { - retval += QLatin1String("/>\n"); + retval += "/>\n"_L1; } } } @@ -272,17 +250,17 @@ QString qDBusInterfaceFromClassDef(const ClassDef *mo) if (cid.name == QCLASSINFO_DBUS_INTERFACE) return QString::fromUtf8(cid.value); } - interface = QLatin1String(mo->classname); - interface.replace(QLatin1String("::"), QLatin1String(".")); + interface = QLatin1StringView(mo->classname); + interface.replace("::"_L1, "."_L1); - if (interface.startsWith(QLatin1String("QDBus"))) { - interface.prepend(QLatin1String("org.qtproject.QtDBus.")); - } else if (interface.startsWith(QLatin1Char('Q')) && - interface.length() >= 2 && interface.at(1).isUpper()) { + if (interface.startsWith("QDBus"_L1)) { + interface.prepend("org.qtproject.QtDBus."_L1); + } else if (interface.startsWith(u'Q') && + interface.size() >= 2 && interface.at(1).isUpper()) { // assume it's Qt - interface.prepend(QLatin1String("local.org.qtproject.Qt.")); + interface.prepend("local.org.qtproject.Qt."_L1); } else { - interface.prepend(QLatin1String("local.")); + interface.prepend("local."_L1); } return interface; @@ -358,16 +336,16 @@ static std::deque<CustomType> s_customTypes; static void parseCmdLine(QStringList &arguments) { flags = 0; - for (int i = 0; i < arguments.count(); ++i) { + for (qsizetype i = 0; i < arguments.size(); ++i) { const QString arg = arguments.at(i); - if (arg == QLatin1String("--help")) + if (arg == "--help"_L1) showHelp(); - if (!arg.startsWith(QLatin1Char('-'))) + if (!arg.startsWith(u'-')) continue; - char c = arg.count() == 2 ? arg.at(1).toLatin1() : char(0); + char c = arg.size() == 2 ? arg.at(1).toLatin1() : char(0); switch (c) { case 'P': flags |= QDBusConnection::ExportNonScriptableProperties; @@ -398,13 +376,13 @@ static void parseCmdLine(QStringList &arguments) break; case 't': - if (arguments.count() < i + 2) { + if (arguments.size() < i + 2) { printf("-t expects a type=dbustype argument\n"); exit(1); } else { const QByteArray arg = arguments.takeAt(i + 1).toUtf8(); // lastIndexOf because the C++ type could contain '=' while the DBus type can't - const int separator = arg.lastIndexOf('='); + const qsizetype separator = arg.lastIndexOf('='); if (separator == -1) { printf("-t expects a type=dbustype argument, but no '=' was found\n"); exit(1); @@ -419,7 +397,7 @@ static void parseCmdLine(QStringList &arguments) break; case 'o': - if (arguments.count() < i + 2 || arguments.at(i + 1).startsWith(QLatin1Char('-'))) { + if (arguments.size() < i + 2 || arguments.at(i + 1).startsWith(u'-')) { printf("-o expects a filename\n"); exit(1); } @@ -454,18 +432,30 @@ int main(int argc, char **argv) args.append(QString::fromLocal8Bit(argv[n])); parseCmdLine(args); - QList<ClassDef> classes; + QDBusMetaTypeId::init(); - for (int i = 0; i < args.count(); ++i) { - const QString arg = args.at(i); + QList<ClassDef> classes; - if (arg.startsWith(QLatin1Char('-'))) + if (args.isEmpty()) + args << u"-"_s; + for (const auto &arg: std::as_const(args)) { + if (arg.startsWith(u'-') && arg.size() > 1) continue; - QFile f(arg); - if (!f.open(QIODevice::ReadOnly|QIODevice::Text)) { + QFile f; + bool fileIsOpen; + QString fileName; + if (arg == u'-') { + fileName = "stdin"_L1; + fileIsOpen = f.open(stdin, QIODevice::ReadOnly | QIODevice::Text); + } else { + fileName = arg; + f.setFileName(arg); + fileIsOpen = f.open(QIODevice::ReadOnly | QIODevice::Text); + } + if (!fileIsOpen) { fprintf(stderr, PROGRAMNAME ": could not open '%s': %s\n", - qPrintable(arg), qPrintable(f.errorString())); + qPrintable(fileName), qPrintable(f.errorString())); return 1; } @@ -491,11 +481,15 @@ int main(int argc, char **argv) QFile output; if (outputFile.isEmpty()) { - output.open(stdout, QIODevice::WriteOnly); + if (!output.open(stdout, QIODevice::WriteOnly)) { + fprintf(stderr, PROGRAMNAME ": could not open standard output: %s\n", + qPrintable(output.errorString())); + return 1; + } } else { output.setFileName(outputFile); if (!output.open(QIODevice::WriteOnly)) { - fprintf(stderr, PROGRAMNAME ": could not open output file '%s': %s", + fprintf(stderr, PROGRAMNAME ": could not open output file '%s': %s\n", qPrintable(outputFile), qPrintable(output.errorString())); return 1; } @@ -503,7 +497,7 @@ int main(int argc, char **argv) output.write(docTypeHeader); output.write("<node>\n"); - for (const ClassDef &cdef : qAsConst(classes)) { + for (const ClassDef &cdef : std::as_const(classes)) { QString xml = qDBusGenerateClassDefXml(&cdef); output.write(std::move(xml).toLocal8Bit()); } diff --git a/src/tools/qdbusxml2cpp/CMakeLists.txt b/src/tools/qdbusxml2cpp/CMakeLists.txt index 37327b34db..0da22ebfc6 100644 --- a/src/tools/qdbusxml2cpp/CMakeLists.txt +++ b/src/tools/qdbusxml2cpp/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from qdbusxml2cpp.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## qdbusxml2cpp Tool: @@ -6,32 +7,16 @@ qt_get_tool_target_name(target_name qdbusxml2cpp) qt_internal_add_tool(${target_name} + TRY_RUN TARGET_DESCRIPTION "Qt D-Bus XML to C++ Compiler" - TOOLS_TARGET DBus # special case + TOOLS_TARGET DBus SOURCES qdbusxml2cpp.cpp DEFINES QT_NO_CAST_FROM_ASCII QT_NO_FOREACH - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::DBusPrivate - # COMPILE_OPTIONS # special case - # "$$QT_HOST_CFLAGS_DBUS" - # QMAKE_TARGET_DESCRIPTION = "Qt D-Bus XML to C++ Compiler" - # _LOADED = "qt_tool" - # _OPTION = "host_build" ) - -#### Keys ignored in scope 1:.:.:qdbusxml2cpp.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "Qt D-Bus XML to C++ Compiler" -# _OPTION = "host_build" - -## Scopes: -##################################################################### - -# special case begin -# qt_internal_extend_target(qdbusxml2cpp CONDITION NOT force_bootstrap [...]) -# qt_internal_extend_target(qdbusxml2cpp CONDITION NOT QT_FEATURE_commandlineparser AND NOT force_bootstrap [...]) -# qt_internal_extend_target(qdbusxml2cpp CONDITION force_bootstrap [...]) -# special case end +qt_internal_return_unless_building_tools() diff --git a/src/tools/qdbusxml2cpp/qdbusxml2cpp.cpp b/src/tools/qdbusxml2cpp/qdbusxml2cpp.cpp index bf079995d7..579604286c 100644 --- a/src/tools/qdbusxml2cpp/qdbusxml2cpp.cpp +++ b/src/tools/qdbusxml2cpp/qdbusxml2cpp.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <qbytearray.h> #include <qcommandlineparser.h> @@ -46,21 +21,75 @@ #define PROGRAMNAME "qdbusxml2cpp" #define PROGRAMVERSION "0.8" -#define PROGRAMCOPYRIGHT "Copyright (C) 2021 The Qt Company Ltd." +#define PROGRAMCOPYRIGHT QT_COPYRIGHT #define ANNOTATION_NO_WAIT "org.freedesktop.DBus.Method.NoReply" -static QString globalClassName; -static QString parentClassName; -static QString proxyFile; -static QString adaptorFile; -static QString inputFile; -static bool skipNamespaces; -static bool verbose; -static bool includeMocs; -static QString commandLine; -static QStringList includes; -static QStringList wantedInterfaces; +using namespace Qt::StringLiterals; + +class QDBusXmlToCpp final +{ +public: + int run(const QCoreApplication &app); + +private: + class DiagnosticsReporter final : public QDBusIntrospection::DiagnosticsReporter + { + public: + void setFileName(const QString &fileName) { m_fileName = fileName; } + bool hadErrors() const { return m_hadErrors; } + + void warning(const QDBusIntrospection::SourceLocation &location, const char *msg, + ...) override; + void error(const QDBusIntrospection::SourceLocation &location, const char *msg, + ...) override; + void note(const QDBusIntrospection::SourceLocation &location, const char *msg, ...) + Q_ATTRIBUTE_FORMAT_PRINTF(3, 4); + + private: + QString m_fileName; + bool m_hadErrors = false; + + void report(const QDBusIntrospection::SourceLocation &location, const char *msg, va_list ap, + const char *severity); + }; + + enum ClassType { Proxy, Adaptor }; + + void writeAdaptor(const QString &filename, const QDBusIntrospection::Interfaces &interfaces); + void writeProxy(const QString &filename, const QDBusIntrospection::Interfaces &interfaces); + + QDBusIntrospection::Interfaces readInput(); + void cleanInterfaces(QDBusIntrospection::Interfaces &interfaces); + QTextStream &writeHeader(QTextStream &ts, bool changesWillBeLost); + QString classNameForInterface(const QString &interface, ClassType classType); + QByteArray qtTypeName(const QDBusIntrospection::SourceLocation &location, + const QString &signature, + const QDBusIntrospection::Annotations &annotations, + qsizetype paramId = -1, const char *direction = "Out"); + void + writeArgList(QTextStream &ts, const QStringList &argNames, + const QDBusIntrospection::Annotations &annotations, + const QDBusIntrospection::Arguments &inputArgs, + const QDBusIntrospection::Arguments &outputArgs = QDBusIntrospection::Arguments()); + void writeSignalArgList(QTextStream &ts, const QStringList &argNames, + const QDBusIntrospection::Annotations &annotations, + const QDBusIntrospection::Arguments &outputArgs); + QString propertyGetter(const QDBusIntrospection::Property &property); + QString propertySetter(const QDBusIntrospection::Property &property); + + QString globalClassName; + QString parentClassName; + QString inputFile; + bool skipNamespaces = false; + bool includeMocs = false; + QString commandLine; + QStringList includes; + QStringList globalIncludes; + QStringList wantedInterfaces; + + DiagnosticsReporter reporter; +}; static const char includeList[] = "#include <QtCore/QByteArray>\n" @@ -73,30 +102,71 @@ static const char includeList[] = static const char forwardDeclarations[] = "#include <QtCore/qcontainerfwd.h>\n"; -static QDBusIntrospection::Interfaces readInput() +void QDBusXmlToCpp::DiagnosticsReporter::warning(const QDBusIntrospection::SourceLocation &location, + const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + report(location, msg, ap, "warning"); + va_end(ap); +} + +void QDBusXmlToCpp::DiagnosticsReporter::error(const QDBusIntrospection::SourceLocation &location, + const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + report(location, msg, ap, "error"); + va_end(ap); + m_hadErrors = true; +} + +void QDBusXmlToCpp::DiagnosticsReporter::note(const QDBusIntrospection::SourceLocation &location, + const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + report(location, msg, ap, "note"); + va_end(ap); + m_hadErrors = true; +} + +void QDBusXmlToCpp::DiagnosticsReporter::report(const QDBusIntrospection::SourceLocation &location, + const char *msg, va_list ap, const char *severity) +{ + fprintf(stderr, "%s:%lld:%lld: %s: ", qPrintable(m_fileName), + (long long int)location.lineNumber, (long long int)location.columnNumber + 1, severity); + vfprintf(stderr, msg, ap); +} + +QDBusIntrospection::Interfaces QDBusXmlToCpp::readInput() { QFile input(inputFile); - if (inputFile.isEmpty() || inputFile == QLatin1String("-")) { - input.open(stdin, QIODevice::ReadOnly); + if (inputFile.isEmpty() || inputFile == "-"_L1) { + reporter.setFileName("<standard input>"_L1); + if (!input.open(stdin, QIODevice::ReadOnly)) { + fprintf(stderr, PROGRAMNAME ": could not open standard input: %s\n", + qPrintable(input.errorString())); + exit(1); + } } else { - input.open(QIODevice::ReadOnly); + reporter.setFileName(inputFile); + if (!input.open(QIODevice::ReadOnly)) { + fprintf(stderr, PROGRAMNAME ": could not open input file '%s': %s\n", + qPrintable(inputFile), qPrintable(input.errorString())); + exit(1); + } } QByteArray data = input.readAll(); + auto interfaces = QDBusIntrospection::parseInterfaces(QString::fromUtf8(data), &reporter); + if (reporter.hadErrors()) + exit(1); - // check if the input is already XML - data = data.trimmed(); - if (data.startsWith("<!DOCTYPE ") || data.startsWith("<?xml") || - data.startsWith("<node") || data.startsWith("<interface")) - // already XML - return QDBusIntrospection::parseInterfaces(QString::fromUtf8(data)); - - fprintf(stderr, "%s: Cannot process input: '%s'. Stop.\n", - PROGRAMNAME, qPrintable(inputFile)); - exit(1); + return interfaces; } -static void cleanInterfaces(QDBusIntrospection::Interfaces &interfaces) +void QDBusXmlToCpp::cleanInterfaces(QDBusIntrospection::Interfaces &interfaces) { if (!wantedInterfaces.isEmpty()) { QDBusIntrospection::Interfaces::Iterator it = interfaces.begin(); @@ -108,18 +178,33 @@ static void cleanInterfaces(QDBusIntrospection::Interfaces &interfaces) } } +static bool isSupportedSuffix(QStringView suffix) +{ + const QLatin1StringView candidates[] = { + "h"_L1, + "cpp"_L1, + "cc"_L1 + }; + + for (auto candidate : candidates) + if (suffix == candidate) + return true; + + return false; +} + // produce a header name from the file name static QString header(const QString &name) { - QStringList parts = name.split(QLatin1Char(':')); - QString retval = parts.first(); + QStringList parts = name.split(u':'); + QString retval = parts.front(); - if (retval.isEmpty() || retval == QLatin1String("-")) + if (retval.isEmpty() || retval == "-"_L1) return retval; - if (!retval.endsWith(QLatin1String(".h")) && !retval.endsWith(QLatin1String(".cpp")) && - !retval.endsWith(QLatin1String(".cc"))) - retval.append(QLatin1String(".h")); + QFileInfo header{retval}; + if (!isSupportedSuffix(header.suffix())) + retval.append(".h"_L1); return retval; } @@ -127,15 +212,15 @@ static QString header(const QString &name) // produce a cpp name from the file name static QString cpp(const QString &name) { - QStringList parts = name.split(QLatin1Char(':')); - QString retval = parts.last(); + QStringList parts = name.split(u':'); + QString retval = parts.back(); - if (retval.isEmpty() || retval == QLatin1String("-")) + if (retval.isEmpty() || retval == "-"_L1) return retval; - if (!retval.endsWith(QLatin1String(".h")) && !retval.endsWith(QLatin1String(".cpp")) && - !retval.endsWith(QLatin1String(".cc"))) - retval.append(QLatin1String(".cpp")); + QFileInfo source{retval}; + if (!isSupportedSuffix(source.suffix())) + retval.append(".cpp"_L1); return retval; } @@ -143,44 +228,78 @@ static QString cpp(const QString &name) // produce a moc name from the file name static QString moc(const QString &name) { - QString retval = header(name); - if (retval.isEmpty()) - return retval; + QString retval; + const QStringList fileNames = name.split(u':'); + + if (fileNames.size() == 1) { + QFileInfo fi{fileNames.front()}; + if (isSupportedSuffix(fi.suffix())) { + // Generates a file that contains the header and the implementation: include "filename.moc" + retval += fi.completeBaseName(); + retval += ".moc"_L1; + } else { + // Separate source and header files are generated: include "moc_filename.cpp" + retval += "moc_"_L1; + retval += fi.fileName(); + retval += ".cpp"_L1; + } + } else { + QString headerName = fileNames.front(); + QString sourceName = fileNames.back(); + + if (sourceName.isEmpty() || sourceName == "-"_L1) { + // If only a header is generated, don't include anything + } else if (headerName.isEmpty() || headerName == "-"_L1) { + // If only source file is generated: include "moc_sourcename.cpp" + QFileInfo source{sourceName}; + + retval += "moc_"_L1; + retval += source.completeBaseName(); + retval += ".cpp"_L1; + + fprintf(stderr, "warning: no header name is provided, assuming it to be \"%s\"\n", + qPrintable(source.completeBaseName() + ".h"_L1)); + } else { + // Both source and header generated: include "moc_headername.cpp" + QFileInfo header{headerName}; + + retval += "moc_"_L1; + retval += header.completeBaseName(); + retval += ".cpp"_L1; + } + } - retval.truncate(retval.length() - 1); // drop the h in .h - retval += QLatin1String("moc"); return retval; } -static QTextStream &writeHeader(QTextStream &ts, bool changesWillBeLost) +QTextStream &QDBusXmlToCpp::writeHeader(QTextStream &ts, bool changesWillBeLost) { - ts << "/*" << Qt::endl - << " * This file was generated by " PROGRAMNAME " version " PROGRAMVERSION << Qt::endl - << " * Command line was: " << commandLine << Qt::endl - << " *" << Qt::endl - << " * " PROGRAMNAME " is " PROGRAMCOPYRIGHT << Qt::endl - << " *" << Qt::endl - << " * This is an auto-generated file." << Qt::endl; + ts << "/*\n" + " * This file was generated by " PROGRAMNAME " version " PROGRAMVERSION "\n" + " * Command line was: " << commandLine << "\n" + " *\n" + " * " PROGRAMNAME " is " PROGRAMCOPYRIGHT "\n" + " *\n" + " * This is an auto-generated file.\n"; if (changesWillBeLost) - ts << " * Do not edit! All changes made to it will be lost." << Qt::endl; + ts << " * Do not edit! All changes made to it will be lost.\n"; else - ts << " * This file may have been hand-edited. Look for HAND-EDIT comments" << Qt::endl - << " * before re-generating it." << Qt::endl; + ts << " * This file may have been hand-edited. Look for HAND-EDIT comments\n" + " * before re-generating it.\n"; - ts << " */" << Qt::endl - << Qt::endl; + ts << " */\n\n"; return ts; } -enum ClassType { Proxy, Adaptor }; -static QString classNameForInterface(const QString &interface, ClassType classType) +QString QDBusXmlToCpp::classNameForInterface(const QString &interface, + QDBusXmlToCpp::ClassType classType) { if (!globalClassName.isEmpty()) return globalClassName; - const auto parts = QStringView{interface}.split(QLatin1Char('.')); + const auto parts = QStringView{interface}.split(u'.'); QString retval; if (classType == Proxy) { @@ -193,50 +312,46 @@ static QString classNameForInterface(const QString &interface, ClassType classTy } if (classType == Proxy) - retval += QLatin1String("Interface"); + retval += "Interface"_L1; else - retval += QLatin1String("Adaptor"); + retval += "Adaptor"_L1; return retval; } -// ### Qt6 Remove the two isSignal ifs -// They are only here because before signal arguments where previously searched as "In" so to maintain compatibility -// we first search for "Out" and if not found we search for "In" -static QByteArray qtTypeName(const QString &signature, const QDBusIntrospection::Annotations &annotations, int paramId = -1, const char *direction = "Out", bool isSignal = false) +QByteArray QDBusXmlToCpp::qtTypeName(const QDBusIntrospection::SourceLocation &location, + const QString &signature, + const QDBusIntrospection::Annotations &annotations, + qsizetype paramId, const char *direction) { int type = QDBusMetaType::signatureToMetaType(signature.toLatin1()).id(); if (type == QMetaType::UnknownType) { - QString annotationName = QString::fromLatin1("org.qtproject.QtDBus.QtTypeName"); + QString annotationName = u"org.qtproject.QtDBus.QtTypeName"_s; if (paramId >= 0) - annotationName += QString::fromLatin1(".%1%2").arg(QLatin1String(direction)).arg(paramId); - QString qttype = annotations.value(annotationName); + annotationName += ".%1%2"_L1.arg(QLatin1StringView(direction)).arg(paramId); + auto annotation = annotations.value(annotationName); + QString qttype = annotation.value; if (!qttype.isEmpty()) return std::move(qttype).toLatin1(); - QString oldAnnotationName = QString::fromLatin1("com.trolltech.QtDBus.QtTypeName"); + QString oldAnnotationName = u"com.trolltech.QtDBus.QtTypeName"_s; if (paramId >= 0) - oldAnnotationName += QString::fromLatin1(".%1%2").arg(QLatin1String(direction)).arg(paramId); - qttype = annotations.value(oldAnnotationName); + oldAnnotationName += ".%1%2"_L1.arg(QLatin1StringView(direction)).arg(paramId); + annotation = annotations.value(oldAnnotationName); + qttype = annotation.value; if (qttype.isEmpty()) { - if (!isSignal || qstrcmp(direction, "Out") == 0) { - fprintf(stderr, "%s: Got unknown type `%s' processing '%s'\n", - PROGRAMNAME, qPrintable(signature), qPrintable(inputFile)); - fprintf(stderr, "You should add <annotation name=\"%s\" value=\"<type>\"/> to the XML description\n", - qPrintable(annotationName)); - } - - if (isSignal) - return qtTypeName(signature, annotations, paramId, "In", isSignal); + reporter.error(location, "unknown type `%s'\n", qPrintable(signature)); + reporter.note(location, "you should add <annotation name=\"%s\" value=\"<type>\"/>\n", + qPrintable(annotationName)); exit(1); } - fprintf(stderr, "%s: Warning: deprecated annotation '%s' found while processing '%s'; " - "suggest updating to '%s'\n", - PROGRAMNAME, qPrintable(oldAnnotationName), qPrintable(inputFile), - qPrintable(annotationName)); + reporter.warning(annotation.location, "deprecated annotation '%s' found\n", + qPrintable(oldAnnotationName)); + reporter.note(annotation.location, "suggest updating to '%s'\n", + qPrintable(annotationName)); return std::move(qttype).toLatin1(); } @@ -245,23 +360,23 @@ static QByteArray qtTypeName(const QString &signature, const QDBusIntrospection: static QString nonConstRefArg(const QByteArray &arg) { - return QLatin1String(arg + " &"); + return QLatin1StringView(arg) + " &"_L1; } static QString templateArg(const QByteArray &arg) { if (!arg.endsWith('>')) - return QLatin1String(arg); + return QLatin1StringView(arg); - return QLatin1String(arg + ' '); + return QLatin1StringView(arg) + " "_L1; } static QString constRefArg(const QByteArray &arg) { if (!arg.startsWith('Q')) - return QLatin1String(arg + ' '); + return QLatin1StringView(arg) + " "_L1; else - return QString( QLatin1String("const %1 &") ).arg( QLatin1String(arg) ); + return "const %1 &"_L1.arg(QLatin1StringView(arg)); } static QStringList makeArgNames(const QDBusIntrospection::Arguments &inputArgs, @@ -269,45 +384,45 @@ static QStringList makeArgNames(const QDBusIntrospection::Arguments &inputArgs, QDBusIntrospection::Arguments()) { QStringList retval; - const int numInputArgs = inputArgs.count(); - const int numOutputArgs = outputArgs.count(); + const qsizetype numInputArgs = inputArgs.size(); + const qsizetype numOutputArgs = outputArgs.size(); retval.reserve(numInputArgs + numOutputArgs); - for (int i = 0; i < numInputArgs; ++i) { + for (qsizetype i = 0; i < numInputArgs; ++i) { const QDBusIntrospection::Argument &arg = inputArgs.at(i); QString name = arg.name; if (name.isEmpty()) - name = QString( QLatin1String("in%1") ).arg(i); + name = u"in%1"_s.arg(i); else - name.replace(QLatin1Char('-'), QLatin1Char('_')); + name.replace(u'-', u'_'); while (retval.contains(name)) - name += QLatin1String("_"); + name += "_"_L1; retval << name; } - for (int i = 0; i < numOutputArgs; ++i) { + for (qsizetype i = 0; i < numOutputArgs; ++i) { const QDBusIntrospection::Argument &arg = outputArgs.at(i); QString name = arg.name; if (name.isEmpty()) - name = QString( QLatin1String("out%1") ).arg(i); + name = u"out%1"_s.arg(i); else - name.replace(QLatin1Char('-'), QLatin1Char('_')); + name.replace(u'-', u'_'); while (retval.contains(name)) - name += QLatin1String("_"); + name += "_"_L1; retval << name; } return retval; } -static void writeArgList(QTextStream &ts, const QStringList &argNames, - const QDBusIntrospection::Annotations &annotations, - const QDBusIntrospection::Arguments &inputArgs, - const QDBusIntrospection::Arguments &outputArgs = QDBusIntrospection::Arguments()) +void QDBusXmlToCpp::writeArgList(QTextStream &ts, const QStringList &argNames, + const QDBusIntrospection::Annotations &annotations, + const QDBusIntrospection::Arguments &inputArgs, + const QDBusIntrospection::Arguments &outputArgs) { // input args: bool first = true; - int argPos = 0; - for (int i = 0; i < inputArgs.count(); ++i) { + qsizetype argPos = 0; + for (qsizetype i = 0; i < inputArgs.size(); ++i) { const QDBusIntrospection::Argument &arg = inputArgs.at(i); - QString type = constRefArg(qtTypeName(arg.type, annotations, i, "In")); + QString type = constRefArg(qtTypeName(arg.location, arg.type, annotations, i, "In")); if (!first) ts << ", "; @@ -319,26 +434,26 @@ static void writeArgList(QTextStream &ts, const QStringList &argNames, // output args // yes, starting from 1 - for (int i = 1; i < outputArgs.count(); ++i) { + for (qsizetype i = 1; i < outputArgs.size(); ++i) { const QDBusIntrospection::Argument &arg = outputArgs.at(i); if (!first) ts << ", "; - ts << nonConstRefArg(qtTypeName(arg.type, annotations, i, "Out")) + ts << nonConstRefArg(qtTypeName(arg.location, arg.type, annotations, i, "Out")) << argNames.at(argPos++); first = false; } } -static void writeSignalArgList(QTextStream &ts, const QStringList &argNames, - const QDBusIntrospection::Annotations &annotations, - const QDBusIntrospection::Arguments &outputArgs) +void QDBusXmlToCpp::writeSignalArgList(QTextStream &ts, const QStringList &argNames, + const QDBusIntrospection::Annotations &annotations, + const QDBusIntrospection::Arguments &outputArgs) { bool first = true; - int argPos = 0; - for (int i = 0; i < outputArgs.count(); ++i) { + qsizetype argPos = 0; + for (qsizetype i = 0; i < outputArgs.size(); ++i) { const QDBusIntrospection::Argument &arg = outputArgs.at(i); - QString type = constRefArg(qtTypeName(arg.type, annotations, i, "Out", true /* isSignal */)); + QString type = constRefArg(qtTypeName(arg.location, arg.type, annotations, i, "Out")); if (!first) ts << ", "; @@ -347,49 +462,49 @@ static void writeSignalArgList(QTextStream &ts, const QStringList &argNames, } } -static QString propertyGetter(const QDBusIntrospection::Property &property) +QString QDBusXmlToCpp::propertyGetter(const QDBusIntrospection::Property &property) { - QString getter = property.annotations.value(QLatin1String("org.qtproject.QtDBus.PropertyGetter")); - if (!getter.isEmpty()) - return getter; - - getter = property.annotations.value(QLatin1String("com.trolltech.QtDBus.propertyGetter")); - if (!getter.isEmpty()) { - fprintf(stderr, "%s: Warning: deprecated annotation 'com.trolltech.QtDBus.propertyGetter' found" - " while processing '%s';" - " suggest updating to 'org.qtproject.QtDBus.PropertyGetter'\n", - PROGRAMNAME, qPrintable(inputFile)); - return getter; + auto annotation = property.annotations.value("org.qtproject.QtDBus.PropertyGetter"_L1); + if (!annotation.value.isEmpty()) + return annotation.value; + + annotation = property.annotations.value("com.trolltech.QtDBus.propertyGetter"_L1); + if (!annotation.value.isEmpty()) { + reporter.warning(annotation.location, + "deprecated annotation 'com.trolltech.QtDBus.propertyGetter' found\n"); + reporter.note(annotation.location, + "suggest updating to 'org.qtproject.QtDBus.PropertyGetter'\n"); + return annotation.value; } - getter = property.name; + QString getter = property.name; getter[0] = getter[0].toLower(); return getter; } -static QString propertySetter(const QDBusIntrospection::Property &property) +QString QDBusXmlToCpp::propertySetter(const QDBusIntrospection::Property &property) { - QString setter = property.annotations.value(QLatin1String("org.qtproject.QtDBus.PropertySetter")); - if (!setter.isEmpty()) - return setter; - - setter = property.annotations.value(QLatin1String("com.trolltech.QtDBus.propertySetter")); - if (!setter.isEmpty()) { - fprintf(stderr, "%s: Warning: deprecated annotation 'com.trolltech.QtDBus.propertySetter' found" - " while processing '%s';" - " suggest updating to 'org.qtproject.QtDBus.PropertySetter'\n", - PROGRAMNAME, qPrintable(inputFile)); - return setter; + auto annotation = property.annotations.value("org.qtproject.QtDBus.PropertySetter"_L1); + if (!annotation.value.isEmpty()) + return annotation.value; + + annotation = property.annotations.value("com.trolltech.QtDBus.propertySetter"_L1); + if (!annotation.value.isEmpty()) { + reporter.warning(annotation.location, + "deprecated annotation 'com.trolltech.QtDBus.propertySetter' found\n"); + reporter.note(annotation.location, + "suggest updating to 'org.qtproject.QtDBus.PropertySetter'\n"); + return annotation.value; } - setter = QLatin1String("set") + property.name; + QString setter = "set"_L1 + property.name; setter[3] = setter[3].toUpper(); return setter; } static QString methodName(const QDBusIntrospection::Method &method) { - QString name = method.annotations.value(QStringLiteral("org.qtproject.QtDBus.MethodName")); + QString name = method.annotations.value(u"org.qtproject.QtDBus.MethodName"_s).value; if (!name.isEmpty()) return name; @@ -399,17 +514,17 @@ static QString methodName(const QDBusIntrospection::Method &method) static QString stringify(const QString &data) { QString retval; - int i; - for (i = 0; i < data.length(); ++i) { - retval += QLatin1Char('\"'); - for ( ; i < data.length() && data[i] != QLatin1Char('\n') && data[i] != QLatin1Char('\r'); ++i) - if (data[i] == QLatin1Char('\"')) - retval += QLatin1String("\\\""); + qsizetype i; + for (i = 0; i < data.size(); ++i) { + retval += u'\"'; + for ( ; i < data.size() && data[i] != u'\n' && data[i] != u'\r'; ++i) + if (data[i] == u'\"') + retval += "\\\""_L1; else retval += data[i]; - if (i+1 < data.length() && data[i] == QLatin1Char('\r') && data[i+1] == QLatin1Char('\n')) + if (i+1 < data.size() && data[i] == u'\r' && data[i+1] == u'\n') i++; - retval += QLatin1String("\\n\"\n"); + retval += "\\n\"\n"_L1; } return retval; } @@ -420,7 +535,7 @@ static bool openFile(const QString &fileName, QFile &file) return false; bool isOk = false; - if (fileName == QLatin1String("-")) { + if (fileName == "-"_L1) { isOk = file.open(stdout, QIODevice::WriteOnly | QIODevice::Text); } else { file.setFileName(fileName); @@ -433,7 +548,8 @@ static bool openFile(const QString &fileName, QFile &file) return isOk; } -static void writeProxy(const QString &filename, const QDBusIntrospection::Interfaces &interfaces) +void QDBusXmlToCpp::writeProxy(const QString &filename, + const QDBusIntrospection::Interfaces &interfaces) { // open the file QString headerName = header(filename); @@ -451,85 +567,83 @@ static void writeProxy(const QString &filename, const QDBusIntrospection::Interf // include guards: QString includeGuard; - if (!headerName.isEmpty() && headerName != QLatin1String("-")) { - includeGuard = headerName.toUpper().replace(QLatin1Char('.'), QLatin1Char('_')); - int pos = includeGuard.lastIndexOf(QLatin1Char('/')); + if (!headerName.isEmpty() && headerName != "-"_L1) { + includeGuard = headerName.toUpper().replace(u'.', u'_'); + qsizetype pos = includeGuard.lastIndexOf(u'/'); if (pos != -1) includeGuard = includeGuard.mid(pos + 1); } else { - includeGuard = QLatin1String("QDBUSXML2CPP_PROXY"); + includeGuard = u"QDBUSXML2CPP_PROXY"_s; } - includeGuard = QString(QLatin1String("%1")) - .arg(includeGuard); - hs << "#ifndef " << includeGuard << Qt::endl - << "#define " << includeGuard << Qt::endl - << Qt::endl; + + hs << "#ifndef " << includeGuard << "\n" + "#define " << includeGuard << "\n\n"; // include our stuff: - hs << "#include <QtCore/QObject>" << Qt::endl + hs << "#include <QtCore/QObject>\n" << includeList; #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) - hs << "#include <QtDBus/QtDBus>" << Qt::endl; + hs << "#include <QtDBus/QtDBus>\n"; #else - hs << "#include <QtDBus/QDBusAbstractInterface>" << Qt::endl; - hs << "#include <QtDBus/QDBusPendingReply>" << Qt::endl; + hs << "#include <QtDBus/QDBusAbstractInterface>\n" + "#include <QtDBus/QDBusPendingReply>\n"; #endif - for (const QString &include : qAsConst(includes)) { - hs << "#include \"" << include << "\"" << Qt::endl; + for (const QString &include : std::as_const(includes)) { + hs << "#include \"" << include << "\"\n"; + if (headerName.isEmpty()) + cs << "#include \"" << include << "\"\n"; + } + + for (const QString &include : std::as_const(globalIncludes)) { + hs << "#include <" << include << ">\n"; if (headerName.isEmpty()) - cs << "#include \"" << include << "\"" << Qt::endl; + cs << "#include <" << include << ">\n"; } - hs << Qt::endl; + hs << "\n"; if (cppName != headerName) { - if (!headerName.isEmpty() && headerName != QLatin1String("-")) - cs << "#include \"" << headerName << "\"" << Qt::endl << Qt::endl; + if (!headerName.isEmpty() && headerName != "-"_L1) + cs << "#include \"" << headerName << "\"\n\n"; } for (const QDBusIntrospection::Interface *interface : interfaces) { QString className = classNameForInterface(interface->name, Proxy); // comment: - hs << "/*" << Qt::endl - << " * Proxy class for interface " << interface->name << Qt::endl - << " */" << Qt::endl; - cs << "/*" << Qt::endl - << " * Implementation of interface class " << className << Qt::endl - << " */" << Qt::endl - << Qt::endl; + hs << "/*\n" + " * Proxy class for interface " << interface->name << "\n" + " */\n"; + cs << "/*\n" + " * Implementation of interface class " << className << "\n" + " */\n\n"; // class header: - hs << "class " << className << ": public QDBusAbstractInterface" << Qt::endl - << "{" << Qt::endl - << " Q_OBJECT" << Qt::endl; + hs << "class " << className << ": public QDBusAbstractInterface\n" + "{\n" + " Q_OBJECT\n"; // the interface name - hs << "public:" << Qt::endl - << " static inline const char *staticInterfaceName()" << Qt::endl - << " { return \"" << interface->name << "\"; }" << Qt::endl - << Qt::endl; + hs << "public:\n" + " static inline const char *staticInterfaceName()\n" + " { return \"" << interface->name << "\"; }\n\n"; // constructors/destructors: - hs << "public:" << Qt::endl - << " " << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr);" << Qt::endl - << Qt::endl - << " ~" << className << "();" << Qt::endl - << Qt::endl; - cs << className << "::" << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)" << Qt::endl - << " : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)" << Qt::endl - << "{" << Qt::endl - << "}" << Qt::endl - << Qt::endl - << className << "::~" << className << "()" << Qt::endl - << "{" << Qt::endl - << "}" << Qt::endl - << Qt::endl; + hs << "public:\n" + " " << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr);\n\n" + " ~" << className << "();\n\n"; + cs << className << "::" << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)\n" + " : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)\n" + "{\n" + "}\n\n" + << className << "::~" << className << "()\n" + "{\n" + "}\n\n"; // properties: for (const QDBusIntrospection::Property &property : interface->properties) { - QByteArray type = qtTypeName(property.type, property.annotations); + QByteArray type = qtTypeName(property.location, property.type, property.annotations); QString getter = propertyGetter(property); QString setter = propertySetter(property); @@ -537,7 +651,7 @@ static void writeProxy(const QString &filename, const QDBusIntrospection::Interf // getter: if (property.access != QDBusIntrospection::Property::Write) - // it's readble + // it's readable hs << " READ " << getter; // setter @@ -545,48 +659,53 @@ static void writeProxy(const QString &filename, const QDBusIntrospection::Interf // it's writeable hs << " WRITE " << setter; - hs << ")" << Qt::endl; + hs << ")\n"; // getter: if (property.access != QDBusIntrospection::Property::Write) { - hs << " inline " << type << " " << getter << "() const" << Qt::endl - << " { return qvariant_cast< " << type << " >(property(\"" - << property.name << "\")); }" << Qt::endl; + hs << " inline " << type << " " << getter << "() const\n" + " { return qvariant_cast< " << type << " >(property(\"" + << property.name << "\")); }\n"; } // setter: if (property.access != QDBusIntrospection::Property::Read) { - hs << " inline void " << setter << "(" << constRefArg(type) << "value)" << Qt::endl - << " { setProperty(\"" << property.name - << "\", QVariant::fromValue(value)); }" << Qt::endl; + hs << " inline void " << setter << "(" << constRefArg(type) << "value)\n" + " { setProperty(\"" << property.name + << "\", QVariant::fromValue(value)); }\n"; } - hs << Qt::endl; + hs << "\n"; } // methods: - hs << "public Q_SLOTS: // METHODS" << Qt::endl; + hs << "public Q_SLOTS: // METHODS\n"; for (const QDBusIntrospection::Method &method : interface->methods) { - bool isDeprecated = method.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) == QLatin1String("true"); - bool isNoReply = - method.annotations.value(QLatin1String(ANNOTATION_NO_WAIT)) == QLatin1String("true"); + bool isDeprecated = method.annotations.value("org.freedesktop.DBus.Deprecated"_L1).value + == "true"_L1; + bool isNoReply = method.annotations.value(ANNOTATION_NO_WAIT ""_L1).value == "true"_L1; if (isNoReply && !method.outputArgs.isEmpty()) { - fprintf(stderr, "%s: warning while processing '%s': method %s in interface %s is marked 'no-reply' but has output arguments.\n", - PROGRAMNAME, qPrintable(inputFile), qPrintable(method.name), - qPrintable(interface->name)); + reporter.warning(method.location, + "method %s in interface %s is marked 'no-reply' but has output " + "arguments.\n", + qPrintable(method.name), qPrintable(interface->name)); continue; } - hs << " inline " - << (isDeprecated ? "Q_DECL_DEPRECATED " : ""); + if (isDeprecated) + hs << " Q_DECL_DEPRECATED "; + else + hs << " "; if (isNoReply) { - hs << "Q_NOREPLY void "; + hs << "Q_NOREPLY inline void "; } else { - hs << "QDBusPendingReply<"; - for (int i = 0; i < method.outputArgs.count(); ++i) + hs << "inline QDBusPendingReply<"; + for (qsizetype i = 0; i < method.outputArgs.size(); ++i) hs << (i > 0 ? ", " : "") - << templateArg(qtTypeName(method.outputArgs.at(i).type, method.annotations, i, "Out")); + << templateArg(qtTypeName(method.outputArgs.at(i).location, + method.outputArgs.at(i).type, method.annotations, + i, "Out")); hs << "> "; } @@ -595,75 +714,77 @@ static void writeProxy(const QString &filename, const QDBusIntrospection::Interf QStringList argNames = makeArgNames(method.inputArgs); writeArgList(hs, argNames, method.annotations, method.inputArgs); - hs << ")" << Qt::endl - << " {" << Qt::endl - << " QList<QVariant> argumentList;" << Qt::endl; + hs << ")\n" + " {\n" + " QList<QVariant> argumentList;\n"; if (!method.inputArgs.isEmpty()) { hs << " argumentList"; - for (int argPos = 0; argPos < method.inputArgs.count(); ++argPos) + for (qsizetype argPos = 0; argPos < method.inputArgs.size(); ++argPos) hs << " << QVariant::fromValue(" << argNames.at(argPos) << ')'; - hs << ";" << Qt::endl; + hs << ";\n"; } if (isNoReply) hs << " callWithArgumentList(QDBus::NoBlock, " - << "QStringLiteral(\"" << method.name << "\"), argumentList);" << Qt::endl; + "QStringLiteral(\"" << method.name << "\"), argumentList);\n"; else hs << " return asyncCallWithArgumentList(QStringLiteral(\"" - << method.name << "\"), argumentList);" << Qt::endl; + << method.name << "\"), argumentList);\n"; // close the function: - hs << " }" << Qt::endl; + hs << " }\n"; - if (method.outputArgs.count() > 1) { + if (method.outputArgs.size() > 1) { // generate the old-form QDBusReply methods with multiple incoming parameters - hs << " inline " - << (isDeprecated ? "Q_DECL_DEPRECATED " : "") - << "QDBusReply<" - << templateArg(qtTypeName(method.outputArgs.first().type, method.annotations, 0, "Out")) << "> "; + hs << (isDeprecated ? " Q_DECL_DEPRECATED " : " ") << "inline QDBusReply<" + << templateArg(qtTypeName(method.outputArgs.first().location, + method.outputArgs.first().type, method.annotations, 0, + "Out")) + << "> "; hs << method.name << "("; QStringList argNames = makeArgNames(method.inputArgs, method.outputArgs); writeArgList(hs, argNames, method.annotations, method.inputArgs, method.outputArgs); - hs << ")" << Qt::endl - << " {" << Qt::endl - << " QList<QVariant> argumentList;" << Qt::endl; + hs << ")\n" + " {\n" + " QList<QVariant> argumentList;\n"; - int argPos = 0; + qsizetype argPos = 0; if (!method.inputArgs.isEmpty()) { hs << " argumentList"; - for (argPos = 0; argPos < method.inputArgs.count(); ++argPos) + for (argPos = 0; argPos < method.inputArgs.size(); ++argPos) hs << " << QVariant::fromValue(" << argNames.at(argPos) << ')'; - hs << ";" << Qt::endl; + hs << ";\n"; } hs << " QDBusMessage reply = callWithArgumentList(QDBus::Block, " - << "QStringLiteral(\"" << method.name << "\"), argumentList);" << Qt::endl; + "QStringLiteral(\"" << method.name << "\"), argumentList);\n"; argPos++; - hs << " if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == " - << method.outputArgs.count() << ") {" << Qt::endl; + hs << " if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() == " + << method.outputArgs.size() << ") {\n"; // yes, starting from 1 - for (int i = 1; i < method.outputArgs.count(); ++i) + for (qsizetype i = 1; i < method.outputArgs.size(); ++i) hs << " " << argNames.at(argPos++) << " = qdbus_cast<" - << templateArg(qtTypeName(method.outputArgs.at(i).type, method.annotations, i, "Out")) - << ">(reply.arguments().at(" << i << "));" << Qt::endl; - hs << " }" << Qt::endl - << " return reply;" << Qt::endl - << " }" << Qt::endl; + << templateArg(qtTypeName(method.outputArgs.at(i).location, + method.outputArgs.at(i).type, method.annotations, + i, "Out")) + << ">(reply.arguments().at(" << i << "));\n"; + hs << " }\n" + " return reply;\n" + " }\n"; } - hs << Qt::endl; + hs << "\n"; } - hs << "Q_SIGNALS: // SIGNALS" << Qt::endl; + hs << "Q_SIGNALS: // SIGNALS\n"; for (const QDBusIntrospection::Signal &signal : interface->signals_) { hs << " "; - if (signal.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) == - QLatin1String("true")) + if (signal.annotations.value("org.freedesktop.DBus.Deprecated"_L1).value == "true"_L1) hs << "Q_DECL_DEPRECATED "; hs << "void " << signal.name << "("; @@ -671,12 +792,11 @@ static void writeProxy(const QString &filename, const QDBusIntrospection::Interf QStringList argNames = makeArgNames(signal.outputArgs); writeSignalArgList(hs, argNames, signal.annotations, signal.outputArgs); - hs << ");" << Qt::endl; // finished for header + hs << ");\n"; // finished for header } // close the class: - hs << "};" << Qt::endl - << Qt::endl; + hs << "};\n\n"; } if (!skipNamespaces) { @@ -687,28 +807,28 @@ static void writeProxy(const QString &filename, const QDBusIntrospection::Interf QStringList current; QString name; if (it != interfaces.constEnd()) { - current = it->constData()->name.split(QLatin1Char('.')); + current = it->constData()->name.split(u'.'); name = current.takeLast(); } - int i = 0; - while (i < current.count() && i < last.count() && current.at(i) == last.at(i)) + qsizetype i = 0; + while (i < current.size() && i < last.size() && current.at(i) == last.at(i)) ++i; // i parts matched - // close last.arguments().count() - i namespaces: - for (int j = i; j < last.count(); ++j) - hs << QString((last.count() - j - 1 + i) * 2, QLatin1Char(' ')) << "}" << Qt::endl; + // close last.arguments().size() - i namespaces: + for (qsizetype j = i; j < last.size(); ++j) + hs << QString((last.size() - j - 1 + i) * 2, u' ') << "}\n"; - // open current.arguments().count() - i namespaces - for (int j = i; j < current.count(); ++j) - hs << QString(j * 2, QLatin1Char(' ')) << "namespace " << current.at(j) << " {" << Qt::endl; + // open current.arguments().size() - i namespaces + for (qsizetype j = i; j < current.size(); ++j) + hs << QString(j * 2, u' ') << "namespace " << current.at(j) << " {\n"; // add this class: if (!name.isEmpty()) { - hs << QString(current.count() * 2, QLatin1Char(' ')) + hs << QString(current.size() * 2, u' ') << "using " << name << " = ::" << classNameForInterface(it->constData()->name, Proxy) - << ";" << Qt::endl; + << ";\n"; } if (it == interfaces.constEnd()) @@ -719,12 +839,12 @@ static void writeProxy(const QString &filename, const QDBusIntrospection::Interf } // close the include guard - hs << "#endif" << Qt::endl; + hs << "#endif\n"; QString mocName = moc(filename); if (includeMocs && !mocName.isEmpty()) - cs << Qt::endl - << "#include \"" << mocName << "\"" << Qt::endl; + cs << "\n" + "#include \"" << mocName << "\"\n"; cs.flush(); hs.flush(); @@ -744,7 +864,8 @@ static void writeProxy(const QString &filename, const QDBusIntrospection::Interf } } -static void writeAdaptor(const QString &filename, const QDBusIntrospection::Interfaces &interfaces) +void QDBusXmlToCpp::writeAdaptor(const QString &filename, + const QDBusIntrospection::Interfaces &interfaces) { // open the file QString headerName = header(filename); @@ -762,103 +883,102 @@ static void writeAdaptor(const QString &filename, const QDBusIntrospection::Inte // include guards: QString includeGuard; - if (!headerName.isEmpty() && headerName != QLatin1String("-")) { - includeGuard = headerName.toUpper().replace(QLatin1Char('.'), QLatin1Char('_')); - int pos = includeGuard.lastIndexOf(QLatin1Char('/')); + if (!headerName.isEmpty() && headerName != "-"_L1) { + includeGuard = headerName.toUpper().replace(u'.', u'_'); + qsizetype pos = includeGuard.lastIndexOf(u'/'); if (pos != -1) includeGuard = includeGuard.mid(pos + 1); } else { - includeGuard = QLatin1String("QDBUSXML2CPP_ADAPTOR"); + includeGuard = u"QDBUSXML2CPP_ADAPTOR"_s; } - includeGuard = QString(QLatin1String("%1")) - .arg(includeGuard); - hs << "#ifndef " << includeGuard << Qt::endl - << "#define " << includeGuard << Qt::endl - << Qt::endl; + + hs << "#ifndef " << includeGuard << "\n" + "#define " << includeGuard << "\n\n"; // include our stuff: - hs << "#include <QtCore/QObject>" << Qt::endl; + hs << "#include <QtCore/QObject>\n"; if (cppName == headerName) - hs << "#include <QtCore/QMetaObject>" << Qt::endl - << "#include <QtCore/QVariant>" << Qt::endl; + hs << "#include <QtCore/QMetaObject>\n" + "#include <QtCore/QVariant>\n"; #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) - hs << "#include <QtDBus/QtDBus>" << Qt::endl; + hs << "#include <QtDBus/QtDBus>\n"; #else - hs << "#include <QtDBus/QDBusAbstractAdaptor>" << Qt::endl; - hs << "#include <QtDBus/QDBusObjectPath>" << Qt::endl; + hs << "#include <QtDBus/QDBusAbstractAdaptor>\n" + "#include <QtDBus/QDBusObjectPath>\n"; #endif - for (const QString &include : qAsConst(includes)) { - hs << "#include \"" << include << "\"" << Qt::endl; + for (const QString &include : std::as_const(includes)) { + hs << "#include \"" << include << "\"\n"; + if (headerName.isEmpty()) + cs << "#include \"" << include << "\"\n"; + } + + for (const QString &include : std::as_const(globalIncludes)) { + hs << "#include <" << include << ">\n"; if (headerName.isEmpty()) - cs << "#include \"" << include << "\"" << Qt::endl; + cs << "#include <" << include << ">\n"; } if (cppName != headerName) { - if (!headerName.isEmpty() && headerName != QLatin1String("-")) - cs << "#include \"" << headerName << "\"" << Qt::endl; + if (!headerName.isEmpty() && headerName != "-"_L1) + cs << "#include \"" << headerName << "\"\n"; - cs << "#include <QtCore/QMetaObject>" << Qt::endl + cs << "#include <QtCore/QMetaObject>\n" << includeList - << Qt::endl; + << "\n"; hs << forwardDeclarations; } else { hs << includeList; } - hs << Qt::endl; + hs << "\n"; QString parent = parentClassName; if (parentClassName.isEmpty()) - parent = QLatin1String("QObject"); + parent = u"QObject"_s; for (const QDBusIntrospection::Interface *interface : interfaces) { QString className = classNameForInterface(interface->name, Adaptor); // comment: - hs << "/*" << Qt::endl - << " * Adaptor class for interface " << interface->name << Qt::endl - << " */" << Qt::endl; - cs << "/*" << Qt::endl - << " * Implementation of adaptor class " << className << Qt::endl - << " */" << Qt::endl - << Qt::endl; + hs << "/*\n" + " * Adaptor class for interface " << interface->name << "\n" + " */\n"; + cs << "/*\n" + " * Implementation of adaptor class " << className << "\n" + " */\n\n"; // class header: - hs << "class " << className << ": public QDBusAbstractAdaptor" << Qt::endl - << "{" << Qt::endl - << " Q_OBJECT" << Qt::endl - << " Q_CLASSINFO(\"D-Bus Interface\", \"" << interface->name << "\")" << Qt::endl - << " Q_CLASSINFO(\"D-Bus Introspection\", \"\"" << Qt::endl + hs << "class " << className << ": public QDBusAbstractAdaptor\n" + "{\n" + " Q_OBJECT\n" + " Q_CLASSINFO(\"D-Bus Interface\", \"" << interface->name << "\")\n" + " Q_CLASSINFO(\"D-Bus Introspection\", \"\"\n" << stringify(interface->introspection) - << " \"\")" << Qt::endl - << "public:" << Qt::endl - << " " << className << "(" << parent << " *parent);" << Qt::endl - << " virtual ~" << className << "();" << Qt::endl - << Qt::endl; + << " \"\")\n" + "public:\n" + " " << className << "(" << parent << " *parent);\n" + " ~" << className << "() override;\n\n"; if (!parentClassName.isEmpty()) - hs << " inline " << parent << " *parent() const" << Qt::endl - << " { return static_cast<" << parent << " *>(QObject::parent()); }" << Qt::endl - << Qt::endl; + hs << " inline " << parent << " *parent() const\n" + " { return static_cast<" << parent << " *>(QObject::parent()); }\n\n"; // constructor/destructor - cs << className << "::" << className << "(" << parent << " *parent)" << Qt::endl - << " : QDBusAbstractAdaptor(parent)" << Qt::endl - << "{" << Qt::endl - << " // constructor" << Qt::endl - << " setAutoRelaySignals(true);" << Qt::endl - << "}" << Qt::endl - << Qt::endl - << className << "::~" << className << "()" << Qt::endl - << "{" << Qt::endl - << " // destructor" << Qt::endl - << "}" << Qt::endl - << Qt::endl; - - hs << "public: // PROPERTIES" << Qt::endl; + cs << className << "::" << className << "(" << parent << " *parent)\n" + " : QDBusAbstractAdaptor(parent)\n" + "{\n" + " // constructor\n" + " setAutoRelaySignals(true);\n" + "}\n\n" + << className << "::~" << className << "()\n" + "{\n" + " // destructor\n" + "}\n\n"; + + hs << "public: // PROPERTIES\n"; for (const QDBusIntrospection::Property &property : interface->properties) { - QByteArray type = qtTypeName(property.type, property.annotations); + QByteArray type = qtTypeName(property.location, property.type, property.annotations); QString constRefType = constRefArg(type); QString getter = propertyGetter(property); QString setter = propertySetter(property); @@ -868,52 +988,47 @@ static void writeAdaptor(const QString &filename, const QDBusIntrospection::Inte hs << " READ " << getter; if (property.access != QDBusIntrospection::Property::Read) hs << " WRITE " << setter; - hs << ")" << Qt::endl; + hs << ")\n"; // getter: if (property.access != QDBusIntrospection::Property::Write) { - hs << " " << type << " " << getter << "() const;" << Qt::endl; + hs << " " << type << " " << getter << "() const;\n"; cs << type << " " - << className << "::" << getter << "() const" << Qt::endl - << "{" << Qt::endl - << " // get the value of property " << property.name << Qt::endl - << " return qvariant_cast< " << type <<" >(parent()->property(\"" << property.name << "\"));" << Qt::endl - << "}" << Qt::endl - << Qt::endl; + << className << "::" << getter << "() const\n" + "{\n" + " // get the value of property " << property.name << "\n" + " return qvariant_cast< " << type <<" >(parent()->property(\"" << property.name << "\"));\n" + "}\n\n"; } // setter if (property.access != QDBusIntrospection::Property::Read) { - hs << " void " << setter << "(" << constRefType << "value);" << Qt::endl; - cs << "void " << className << "::" << setter << "(" << constRefType << "value)" << Qt::endl - << "{" << Qt::endl - << " // set the value of property " << property.name << Qt::endl - << " parent()->setProperty(\"" << property.name << "\", QVariant::fromValue(value"; - if (constRefType.contains(QLatin1String("QDBusVariant"))) + hs << " void " << setter << "(" << constRefType << "value);\n"; + cs << "void " << className << "::" << setter << "(" << constRefType << "value)\n" + "{\n" + " // set the value of property " << property.name << "\n" + " parent()->setProperty(\"" << property.name << "\", QVariant::fromValue(value"; + if (constRefType.contains("QDBusVariant"_L1)) cs << ".variant()"; - cs << "));" << Qt::endl - << "}" << Qt::endl - << Qt::endl; + cs << "));\n" + "}\n\n"; } - hs << Qt::endl; + hs << "\n"; } - hs << "public Q_SLOTS: // METHODS" << Qt::endl; + hs << "public Q_SLOTS: // METHODS\n"; for (const QDBusIntrospection::Method &method : interface->methods) { - bool isNoReply = - method.annotations.value(QLatin1String(ANNOTATION_NO_WAIT)) == QLatin1String("true"); + bool isNoReply = method.annotations.value(ANNOTATION_NO_WAIT ""_L1).value == "true"_L1; if (isNoReply && !method.outputArgs.isEmpty()) { - fprintf(stderr, "%s: warning while processing '%s': method %s in interface %s is marked 'no-reply' but has output arguments.\n", - PROGRAMNAME, qPrintable(inputFile), qPrintable(method.name), qPrintable(interface->name)); + reporter.warning(method.location, + "method %s in interface %s is marked 'no-reply' but has output " + "arguments.\n", + qPrintable(method.name), qPrintable(interface->name)); continue; } hs << " "; - if (method.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) == - QLatin1String("true")) - hs << "Q_DECL_DEPRECATED "; - QByteArray returnType; if (isNoReply) { hs << "Q_NOREPLY void "; @@ -922,7 +1037,9 @@ static void writeAdaptor(const QString &filename, const QDBusIntrospection::Inte hs << "void "; cs << "void "; } else { - returnType = qtTypeName(method.outputArgs.first().type, method.annotations, 0, "Out"); + returnType = + qtTypeName(method.outputArgs.first().location, + method.outputArgs.first().type, method.annotations, 0, "Out"); hs << returnType << " "; cs << returnType << " "; } @@ -935,46 +1052,42 @@ static void writeAdaptor(const QString &filename, const QDBusIntrospection::Inte writeArgList(hs, argNames, method.annotations, method.inputArgs, method.outputArgs); writeArgList(cs, argNames, method.annotations, method.inputArgs, method.outputArgs); - hs << ");" << Qt::endl; // finished for header - cs << ")" << Qt::endl - << "{" << Qt::endl - << " // handle method call " << interface->name << "." << methodName(method) << Qt::endl; + hs << ");\n"; // finished for header + cs << ")\n" + "{\n" + " // handle method call " << interface->name << "." << methodName(method) << "\n"; // make the call bool usingInvokeMethod = false; - if (parentClassName.isEmpty() && method.inputArgs.count() <= 10 - && method.outputArgs.count() <= 1) + if (parentClassName.isEmpty() && method.inputArgs.size() <= 10 + && method.outputArgs.size() <= 1) usingInvokeMethod = true; if (usingInvokeMethod) { // we are using QMetaObject::invokeMethod if (!returnType.isEmpty()) - cs << " " << returnType << " " << argNames.at(method.inputArgs.count()) - << ";" << Qt::endl; + cs << " " << returnType << " " << argNames.at(method.inputArgs.size()) + << ";\n"; static const char invoke[] = " QMetaObject::invokeMethod(parent(), \""; cs << invoke << name << "\""; if (!method.outputArgs.isEmpty()) cs << ", Q_RETURN_ARG(" - << qtTypeName(method.outputArgs.at(0).type, method.annotations, - 0, "Out") - << ", " - << argNames.at(method.inputArgs.count()) - << ")"; + << qtTypeName(method.outputArgs.at(0).location, method.outputArgs.at(0).type, + method.annotations, 0, "Out") + << ", " << argNames.at(method.inputArgs.size()) << ")"; - for (int i = 0; i < method.inputArgs.count(); ++i) + for (qsizetype i = 0; i < method.inputArgs.size(); ++i) cs << ", Q_ARG(" - << qtTypeName(method.inputArgs.at(i).type, method.annotations, - i, "In") - << ", " - << argNames.at(i) - << ")"; + << qtTypeName(method.inputArgs.at(i).location, method.inputArgs.at(i).type, + method.annotations, i, "In") + << ", " << argNames.at(i) << ")"; - cs << ");" << Qt::endl; + cs << ");\n"; if (!returnType.isEmpty()) - cs << " return " << argNames.at(method.inputArgs.count()) << ";" << Qt::endl; + cs << " return " << argNames.at(method.inputArgs.size()) << ";\n"; } else { if (parentClassName.isEmpty()) cs << " //"; @@ -990,51 +1103,44 @@ static void writeAdaptor(const QString &filename, const QDBusIntrospection::Inte cs << "parent()->"; cs << name << "("; - int argPos = 0; + qsizetype argPos = 0; bool first = true; - for (int i = 0; i < method.inputArgs.count(); ++i) { + for (qsizetype i = 0; i < method.inputArgs.size(); ++i) { cs << (first ? "" : ", ") << argNames.at(argPos++); first = false; } ++argPos; // skip retval, if any - for (int i = 1; i < method.outputArgs.count(); ++i) { + for (qsizetype i = 1; i < method.outputArgs.size(); ++i) { cs << (first ? "" : ", ") << argNames.at(argPos++); first = false; } - cs << ");" << Qt::endl; + cs << ");\n"; } - cs << "}" << Qt::endl - << Qt::endl; + cs << "}\n\n"; } - hs << "Q_SIGNALS: // SIGNALS" << Qt::endl; + hs << "Q_SIGNALS: // SIGNALS\n"; for (const QDBusIntrospection::Signal &signal : interface->signals_) { - hs << " "; - if (signal.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) == - QLatin1String("true")) - hs << "Q_DECL_DEPRECATED "; - - hs << "void " << signal.name << "("; + hs << " void " << signal.name << "("; QStringList argNames = makeArgNames(signal.outputArgs); writeSignalArgList(hs, argNames, signal.annotations, signal.outputArgs); - hs << ");" << Qt::endl; // finished for header + hs << ");\n"; // finished for header } // close the class: - hs << "};" << Qt::endl - << Qt::endl; + hs << "};\n\n"; } // close the include guard - hs << "#endif" << Qt::endl; + hs << "#endif\n"; QString mocName = moc(filename); if (includeMocs && !mocName.isEmpty()) - cs << Qt::endl - << "#include \"" << mocName << "\"" << Qt::endl; + cs << "\n" + "#include \"" << mocName << "\"\n"; cs.flush(); hs.flush(); @@ -1054,70 +1160,73 @@ static void writeAdaptor(const QString &filename, const QDBusIntrospection::Inte } } -int main(int argc, char **argv) +int QDBusXmlToCpp::run(const QCoreApplication &app) { - QCoreApplication app(argc, argv); - QCoreApplication::setApplicationName(QStringLiteral(PROGRAMNAME)); - QCoreApplication::setApplicationVersion(QStringLiteral(PROGRAMVERSION)); - QCommandLineParser parser; - parser.setApplicationDescription(QLatin1String( + parser.setApplicationDescription( "Produces the C++ code to implement the interfaces defined in the input file.\n\n" "If the file name given to the options -a and -p does not end in .cpp or .h, the\n" "program will automatically append the suffixes and produce both files.\n" "You can also use a colon (:) to separate the header name from the source file\n" "name, as in '-a filename_p.h:filename.cpp'.\n\n" "If you pass a dash (-) as the argument to either -p or -a, the output is written\n" - "to the standard output.")); + "to the standard output."_L1); parser.addHelpOption(); parser.addVersionOption(); - parser.addPositionalArgument(QStringLiteral("xml-or-xml-file"), QStringLiteral("XML file to use.")); - parser.addPositionalArgument(QStringLiteral("interfaces"), QStringLiteral("List of interfaces to use."), - QStringLiteral("[interfaces ...]")); + parser.addPositionalArgument(u"xml-or-xml-file"_s, u"XML file to use."_s); + parser.addPositionalArgument(u"interfaces"_s, u"List of interfaces to use."_s, + u"[interfaces ...]"_s); - QCommandLineOption adapterCodeOption(QStringList() << QStringLiteral("a") << QStringLiteral("adaptor"), - QStringLiteral("Write the adaptor code to <filename>"), QStringLiteral("filename")); + QCommandLineOption adapterCodeOption(QStringList{u"a"_s, u"adaptor"_s}, + u"Write the adaptor code to <filename>"_s, u"filename"_s); parser.addOption(adapterCodeOption); - QCommandLineOption classNameOption(QStringList() << QStringLiteral("c") << QStringLiteral("classname"), - QStringLiteral("Use <classname> as the class name for the generated classes"), QStringLiteral("classname")); + QCommandLineOption classNameOption(QStringList{u"c"_s, u"classname"_s}, + u"Use <classname> as the class name for the generated classes. " + u"This option can only be used when processing a single interface."_s, + u"classname"_s); parser.addOption(classNameOption); - QCommandLineOption addIncludeOption(QStringList() << QStringLiteral("i") << QStringLiteral("include"), - QStringLiteral("Add #include to the output"), QStringLiteral("filename")); + QCommandLineOption addIncludeOption(QStringList{u"i"_s, u"include"_s}, + u"Add #include \"filename\" to the output"_s, u"filename"_s); parser.addOption(addIncludeOption); - QCommandLineOption adapterParentOption(QStringLiteral("l"), - QStringLiteral("When generating an adaptor, use <classname> as the parent class"), QStringLiteral("classname")); + QCommandLineOption addGlobalIncludeOption(QStringList{u"I"_s, u"global-include"_s}, + u"Add #include <filename> to the output"_s, u"filename"_s); + parser.addOption(addGlobalIncludeOption); + + QCommandLineOption adapterParentOption(u"l"_s, + u"When generating an adaptor, use <classname> as the parent class"_s, u"classname"_s); parser.addOption(adapterParentOption); - QCommandLineOption mocIncludeOption(QStringList() << QStringLiteral("m") << QStringLiteral("moc"), - QStringLiteral("Generate #include \"filename.moc\" statements in the .cpp files")); + QCommandLineOption mocIncludeOption(QStringList{u"m"_s, u"moc"_s}, + u"Generate #include \"filename.moc\" statements in the .cpp files"_s); parser.addOption(mocIncludeOption); - QCommandLineOption noNamespaceOption(QStringList() << QStringLiteral("N") << QStringLiteral("no-namespaces"), - QStringLiteral("Don't use namespaces")); + QCommandLineOption noNamespaceOption(QStringList{u"N"_s, u"no-namespaces"_s}, + u"Don't use namespaces"_s); parser.addOption(noNamespaceOption); - QCommandLineOption proxyCodeOption(QStringList() << QStringLiteral("p") << QStringLiteral("proxy"), - QStringLiteral("Write the proxy code to <filename>"), QStringLiteral("filename")); + QCommandLineOption proxyCodeOption(QStringList{u"p"_s, u"proxy"_s}, + u"Write the proxy code to <filename>"_s, u"filename"_s); parser.addOption(proxyCodeOption); - QCommandLineOption verboseOption(QStringList() << QStringLiteral("V") << QStringLiteral("verbose"), - QStringLiteral("Be verbose.")); + QCommandLineOption verboseOption(QStringList{u"V"_s, u"verbose"_s}, + u"Be verbose."_s); parser.addOption(verboseOption); parser.process(app); - adaptorFile = parser.value(adapterCodeOption); + QString adaptorFile = parser.value(adapterCodeOption); globalClassName = parser.value(classNameOption); includes = parser.values(addIncludeOption); + globalIncludes = parser.values(addGlobalIncludeOption); parentClassName = parser.value(adapterParentOption); includeMocs = parser.isSet(mocIncludeOption); skipNamespaces = parser.isSet(noNamespaceOption); - proxyFile = parser.value(proxyCodeOption); - verbose = parser.isSet(verboseOption); + QString proxyFile = parser.value(proxyCodeOption); + bool verbose = parser.isSet(verboseOption); wantedInterfaces = parser.positionalArguments(); if (!wantedInterfaces.isEmpty()) { @@ -1131,15 +1240,19 @@ int main(int argc, char **argv) } if (verbose) - QLoggingCategory::setFilterRules(QStringLiteral("dbus.parser.debug=true")); + QLoggingCategory::setFilterRules(u"dbus.parser.debug=true"_s); QDBusIntrospection::Interfaces interfaces = readInput(); cleanInterfaces(interfaces); + if (!globalClassName.isEmpty() && interfaces.count() != 1) { + qCritical("Option -c/--classname can only be used with a single interface.\n"); + return 1; + } + QStringList args = app.arguments(); args.removeFirst(); - commandLine = QLatin1String(PROGRAMNAME " "); - commandLine += args.join(QLatin1Char(' ')); + commandLine = PROGRAMNAME " "_L1 + args.join(u' '); if (!proxyFile.isEmpty() || adaptorFile.isEmpty()) writeProxy(proxyFile, interfaces); @@ -1150,3 +1263,11 @@ int main(int argc, char **argv) return 0; } +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName(QStringLiteral(PROGRAMNAME)); + QCoreApplication::setApplicationVersion(QStringLiteral(PROGRAMVERSION)); + + return QDBusXmlToCpp().run(app); +} diff --git a/src/tools/qlalr/CMakeLists.txt b/src/tools/qlalr/CMakeLists.txt index 1b28154695..da8b351889 100644 --- a/src/tools/qlalr/CMakeLists.txt +++ b/src/tools/qlalr/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from qlalr.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## qlalr Tool: @@ -8,7 +9,7 @@ qt_get_tool_target_name(target_name qlalr) qt_internal_add_tool(${target_name} TARGET_DESCRIPTION "Qt Look Ahead LR Parser Generator" INSTALL_DIR "${INSTALL_LIBEXECDIR}" - TOOLS_TARGET Core # special case + TOOLS_TARGET Core SOURCES compress.cpp compress.h cppgenerator.cpp cppgenerator.h @@ -20,11 +21,10 @@ qt_internal_add_tool(${target_name} recognizer.cpp recognizer.h DEFINES QT_NO_FOREACH - PUBLIC_LIBRARIES - Qt::Core # special case + QT_NO_QPAIR + QT_USE_NODISCARD_FILE_OPEN + LIBRARIES + Qt::Core + Qt::CorePrivate ) - -#### Keys ignored in scope 1:.:.:qlalr.pro:<TRUE>: -# OTHER_FILES = "lalr.g" -# QMAKE_TARGET_DESCRIPTION = "Qt Look Ahead LR Parser Generator" -# _OPTION = "host_build" +qt_internal_return_unless_building_tools() diff --git a/src/tools/qlalr/compress.cpp b/src/tools/qlalr/compress.cpp index 18f9fb4b58..6ee083f7e9 100644 --- a/src/tools/qlalr/compress.cpp +++ b/src/tools/qlalr/compress.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "compress.h" @@ -164,7 +139,7 @@ void Compress::operator () (int *table, int row_count, int column_count) #ifndef QLALR_NO_CHECK_SORTED_TABLE int previous_zeros = INT_MAX; - for (const UncompressedRow &row : qAsConst(sortedTable)) + for (const UncompressedRow &row : std::as_const(sortedTable)) { int zeros = row.count (0); @@ -176,7 +151,7 @@ void Compress::operator () (int *table, int row_count, int column_count) index.fill (-999999, row_count); - for (const UncompressedRow &row : qAsConst(sortedTable)) + for (const UncompressedRow &row : std::as_const(sortedTable)) { int first_token = std::distance (row.begin (), row.beginNonZeros ()); QList<int>::iterator pos = info.begin(); @@ -186,7 +161,7 @@ void Compress::operator () (int *table, int row_count, int column_count) if (pos == info.begin ()) { // try to find a perfect match - QList<int>::iterator pm = std::search(&*pos, &*info.end(), row.beginNonZeros(), + QList<int>::iterator pm = std::search(pos, info.end(), row.beginNonZeros(), row.endNonZeros(), _PerfectMatch()); if (pm != info.end ()) @@ -196,7 +171,7 @@ void Compress::operator () (int *table, int row_count, int column_count) } } - pos = std::search (&*pos, &*info.end (), row.beginNonZeros (), row.endNonZeros (), _Fit ()); + pos = std::search (pos, info.end (), row.beginNonZeros (), row.endNonZeros (), _Fit ()); if (pos == info.end ()) break; @@ -251,7 +226,7 @@ void Compress::operator () (int *table, int row_count, int column_count) } #if 0 - for (const UncompressedRow &row : qAsConst(sortedTable)) + for (const UncompressedRow &row : std::as_const(sortedTable)) { int i = row.index (); Q_ASSERT (i < sortedTable.size ()); diff --git a/src/tools/qlalr/compress.h b/src/tools/qlalr/compress.h index 84b804e234..99f790348a 100644 --- a/src/tools/qlalr/compress.h +++ b/src/tools/qlalr/compress.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the utils of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef COMPRESS_H #define COMPRESS_H diff --git a/src/tools/qlalr/cppgenerator.cpp b/src/tools/qlalr/cppgenerator.cpp index 7efe94a5c2..fd56de106d 100644 --- a/src/tools/qlalr/cppgenerator.cpp +++ b/src/tools/qlalr/cppgenerator.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cppgenerator.h" @@ -35,9 +10,12 @@ #include <QtCore/qtextstream.h> #include <QtCore/qfile.h> #include <QtCore/qmap.h> +#include <QtCore/private/qconfig_p.h> #include <iterator> +using namespace Qt::StringLiterals; + namespace { void generateSeparator(int i, QTextStream &out) @@ -64,40 +42,15 @@ void generateList(const QList<int> &list, QTextStream &out) QString CppGenerator::copyrightHeader() const { - return QLatin1String( - "/****************************************************************************\n" - "**\n" - "** Copyright (C) 2016 The Qt Company Ltd.\n" - "** Contact: https://www.qt.io/licensing/\n" - "**\n" - "** This file is part of the Qt Toolkit.\n" - "**\n" - "** $QT_BEGIN_LICENSE:GPL-EXCEPT$\n" - "** Commercial License Usage\n" - "** Licensees holding valid commercial Qt licenses may use this file in\n" - "** accordance with the commercial license agreement provided with the\n" - "** Software or, alternatively, in accordance with the terms contained in\n" - "** a written agreement between you and The Qt Company. For licensing terms\n" - "** and conditions see https://www.qt.io/terms-conditions. For further\n" - "** information use the contact form at https://www.qt.io/contact-us.\n" - "**\n" - "** GNU General Public License Usage\n" - "** Alternatively, this file may be used under the terms of the GNU\n" - "** General Public License version 3 as published by the Free Software\n" - "** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT\n" - "** included in the packaging of this file. Please review the following\n" - "** information to ensure the GNU General Public License requirements will\n" - "** be met: https://www.gnu.org/licenses/gpl-3.0.html.\n" - "**\n" - "** $QT_END_LICENSE$\n" - "**\n" - "****************************************************************************/\n" - "\n"); + return + "// " QT_COPYRIGHT "\n" + "// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0\n" + "\n"_L1; } QString CppGenerator::privateCopyrightHeader() const { - return QLatin1String( + return "//\n" "// W A R N I N G\n" "// -------------\n" @@ -107,12 +60,12 @@ QString CppGenerator::privateCopyrightHeader() const "// version without notice, or even be removed.\n" "//\n" "// We mean it.\n" - "//\n"); + "//\n"_L1; } QString CppGenerator::startIncludeGuard(const QString &fileName) { - const QString normalized(QString(fileName).replace(QLatin1Char('.'), QLatin1Char('_')).toUpper()); + const QString normalized(QString(fileName).replace(u'.', u'_').toUpper()); return QString::fromLatin1("#ifndef %1\n" "#define %2\n").arg(normalized, normalized); @@ -120,7 +73,7 @@ QString CppGenerator::startIncludeGuard(const QString &fileName) QString CppGenerator::endIncludeGuard(const QString &fileName) { - const QString normalized(QString(fileName).replace(QLatin1Char('.'), QLatin1Char('_')).toUpper()); + const QString normalized(QString(fileName).replace(u'.', u'_').toUpper()); return QString::fromLatin1("#endif // %1\n").arg(normalized); } @@ -240,7 +193,15 @@ void CppGenerator::operator () () { if (shift_reduce_conflict_count != grammar.expected_shift_reduce || reduce_reduce_conflict_count != grammar.expected_reduce_reduce) - qerr() << "*** Conflicts: " << shift_reduce_conflict_count << " shift/reduce, " << reduce_reduce_conflict_count << " reduce/reduce" << Qt::endl; + { + qerr() << "*** Conflicts: " << shift_reduce_conflict_count << " shift/reduce, " << reduce_reduce_conflict_count << " reduce/reduce" << Qt::endl; + if (warnings_are_errors) + { + qerr() << "qlalr: error: warning occurred, treating as error due to " + "--exit-on-warn." << Qt::endl; + exit(2); + } + } if (verbose) qout() << Qt::endl << "*** Conflicts: " << shift_reduce_conflict_count << " shift/reduce, " << reduce_reduce_conflict_count << " reduce/reduce" << Qt::endl @@ -262,12 +223,20 @@ void CppGenerator::operator () () } auto rule = grammar.rules.begin(); - for (int i = 0; i < used_rules.count (); ++i, ++rule) + for (int i = 0; i < used_rules.size(); ++i, ++rule) { if (! used_rules.testBit (i)) { if (rule != grammar.goal) - qerr() << "*** Warning: Rule ``" << *rule << "'' is useless!" << Qt::endl; + { + qerr() << "*** Warning: Rule ``" << *rule << "'' is useless!" << Qt::endl; + if (warnings_are_errors) + { + qerr() << "qlalr: error: warning occurred, treating as error due to " + "--exit-on-warn." << Qt::endl; + exit(2); + } + } } } @@ -374,12 +343,17 @@ void CppGenerator::operator () () } // default behaviour - QString declFileName = grammar.table_name.toLower () + QLatin1String("_p.h"); - QString bitsFileName = grammar.table_name.toLower () + QLatin1String(".cpp"); + QString declFileName = grammar.table_name.toLower () + "_p.h"_L1; + QString bitsFileName = grammar.table_name.toLower () + ".cpp"_L1; { // decls... QFile f (declFileName); - f.open (QFile::WriteOnly); + if (! f.open (QFile::WriteOnly)) + { + fprintf (stderr, "*** cannot create %s: %s\n", + qPrintable(declFileName), qPrintable(f.errorString())); + return; + } QTextStream out (&f); QString prot = declFileName.toUpper ().replace (QLatin1Char ('.'), QLatin1Char ('_')); @@ -411,7 +385,12 @@ void CppGenerator::operator () () { // bits... QFile f (bitsFileName); - f.open (QFile::WriteOnly); + if (! f.open (QFile::WriteOnly)) + { + fprintf (stderr, "*** cannot create %s: %s\n", + qPrintable(bitsFileName), qPrintable(f.errorString())); + return; + } QTextStream out (&f); // copyright headers must come first, otherwise the headers tests will fail @@ -432,7 +411,12 @@ void CppGenerator::operator () () if (! grammar.decl_file_name.isEmpty ()) { QFile f (grammar.decl_file_name); - f.open (QFile::WriteOnly); + if (! f.open (QFile::WriteOnly)) + { + fprintf (stderr, "*** cannot create %s: %s\n", + qPrintable(grammar.decl_file_name), qPrintable(f.errorString())); + return; + } QTextStream out (&f); out << p.decls(); } @@ -440,7 +424,12 @@ void CppGenerator::operator () () if (! grammar.impl_file_name.isEmpty ()) { QFile f (grammar.impl_file_name); - f.open (QFile::WriteOnly); + if (! f.open (QFile::WriteOnly)) + { + fprintf (stderr, "*** cannot create %s: %s\n", + qPrintable(grammar.impl_file_name), qPrintable(f.errorString())); + return; + } QTextStream out (&f); out << p.impls(); } @@ -448,9 +437,9 @@ void CppGenerator::operator () () QString CppGenerator::debugInfoProt() const { - QString prot = QLatin1String("QLALR_NO_"); + QString prot = "QLALR_NO_"_L1; prot += grammar.table_name.toUpper(); - prot += QLatin1String("_DEBUG_INFO"); + prot += "_DEBUG_INFO"_L1; return prot; } @@ -461,16 +450,16 @@ void CppGenerator::generateDecl (QTextStream &out) << "public:" << Qt::endl << " enum VariousConstants {" << Qt::endl; - for (const Name &t : qAsConst(grammar.terminals)) + for (const Name &t : std::as_const(grammar.terminals)) { QString name = *t; int value = std::distance (grammar.names.begin (), t); - if (name == QLatin1String ("$end")) - name = QLatin1String ("EOF_SYMBOL"); + if (name == "$end"_L1) + name = "EOF_SYMBOL"_L1; - else if (name == QLatin1String ("$accept")) - name = QLatin1String ("ACCEPT_SYMBOL"); + else if (name == "$accept"_L1) + name = "ACCEPT_SYMBOL"_L1; else name.prepend (grammar.token_prefix); diff --git a/src/tools/qlalr/cppgenerator.h b/src/tools/qlalr/cppgenerator.h index a83ec101fd..66ae781be4 100644 --- a/src/tools/qlalr/cppgenerator.h +++ b/src/tools/qlalr/cppgenerator.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef CPPGENERATOR_H #define CPPGENERATOR_H @@ -45,7 +20,8 @@ public: aut (aut), verbose (verbose), debug_info (false), - copyright (false) {} + copyright (false), + warnings_are_errors(false) {} void operator () (); @@ -54,6 +30,8 @@ public: void setCopyright (bool t) { copyright = t; } + void setWarningsAreErrors (bool e) { warnings_are_errors = e; } + private: void generateDecl (QTextStream &out); void generateImpl (QTextStream &out); @@ -76,6 +54,7 @@ private: int non_terminal_count; bool debug_info; bool copyright; + bool warnings_are_errors; Compress compressed_action; Compress compressed_goto; QList<int> count; diff --git a/src/tools/qlalr/dotgraph.cpp b/src/tools/qlalr/dotgraph.cpp index 1d479af2b2..75bd304367 100644 --- a/src/tools/qlalr/dotgraph.cpp +++ b/src/tools/qlalr/dotgraph.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the utils of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "dotgraph.h" diff --git a/src/tools/qlalr/dotgraph.h b/src/tools/qlalr/dotgraph.h index 2d4f1cd2fc..42d4527c46 100644 --- a/src/tools/qlalr/dotgraph.h +++ b/src/tools/qlalr/dotgraph.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the utils of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef DOTGRAPH_H #define DOTGRAPH_H diff --git a/src/tools/qlalr/examples/dummy-xml/ll/dummy-xml-ll.cpp b/src/tools/qlalr/examples/dummy-xml/ll/dummy-xml-ll.cpp index 7bf1506b4a..24399d3786 100644 --- a/src/tools/qlalr/examples/dummy-xml/ll/dummy-xml-ll.cpp +++ b/src/tools/qlalr/examples/dummy-xml/ll/dummy-xml-ll.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include <cstdlib> #include <cstdio> diff --git a/src/tools/qlalr/examples/dummy-xml/xml.g b/src/tools/qlalr/examples/dummy-xml/xml.g index 43c1fb169f..59472dc219 100644 --- a/src/tools/qlalr/examples/dummy-xml/xml.g +++ b/src/tools/qlalr/examples/dummy-xml/xml.g @@ -1,30 +1,5 @@ ----------------------------------------------------------------------------- --- -- Copyright (C) 2016 The Qt Company Ltd. --- Contact: https://www.qt.io/licensing/ --- --- This file is part of the QtCore module of the Qt Toolkit. --- --- $QT_BEGIN_LICENSE:GPL-EXCEPT$ --- Commercial License Usage --- Licensees holding valid commercial Qt licenses may use this file in --- accordance with the commercial license agreement provided with the --- Software or, alternatively, in accordance with the terms contained in --- a written agreement between you and The Qt Company. For licensing terms --- and conditions see https://www.qt.io/terms-conditions. For further --- information use the contact form at https://www.qt.io/contact-us. --- --- GNU General Public License Usage --- Alternatively, this file may be used under the terms of the GNU --- General Public License version 3 as published by the Free Software --- Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT --- included in the packaging of this file. Please review the following --- information to ensure the GNU General Public License requirements will --- be met: https://www.gnu.org/licenses/gpl-3.0.html. --- --- $QT_END_LICENSE$ --- ----------------------------------------------------------------------------- +-- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause %parser XMLTable diff --git a/src/tools/qlalr/examples/glsl/build.sh b/src/tools/qlalr/examples/glsl/build.sh index 5bd0faab07..d43889bf72 100644 --- a/src/tools/qlalr/examples/glsl/build.sh +++ b/src/tools/qlalr/examples/glsl/build.sh @@ -1,31 +1,6 @@ #!/bin/sh -############################################################################# -## -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is the build configuration utility of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:GPL-EXCEPT$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 3 as published by the Free Software -## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# +# Copyright (C) 2016 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause ${FLEX-flex} -oglsl-lex.incl glsl-lex.l ${QLALR-qlalr} glsl.g diff --git a/src/tools/qlalr/examples/glsl/glsl-lex.l b/src/tools/qlalr/examples/glsl/glsl-lex.l index c92aad296b..f3f9bb4f50 100644 --- a/src/tools/qlalr/examples/glsl/glsl-lex.l +++ b/src/tools/qlalr/examples/glsl/glsl-lex.l @@ -1,32 +1,5 @@ - -%{ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR tool of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include <cassert> #define YY_DECL int GLSLParser::nextToken() diff --git a/src/tools/qlalr/examples/glsl/glsl.g b/src/tools/qlalr/examples/glsl/glsl.g index 66a2c73656..223e90284d 100644 --- a/src/tools/qlalr/examples/glsl/glsl.g +++ b/src/tools/qlalr/examples/glsl/glsl.g @@ -1,30 +1,5 @@ ----------------------------------------------------------------------------- --- -- Copyright (C) 2016 The Qt Company Ltd. --- Contact: https://www.qt.io/licensing/ --- --- This file is part of the QtCore module of the Qt Toolkit. --- --- $QT_BEGIN_LICENSE:GPL-EXCEPT$ --- Commercial License Usage --- Licensees holding valid commercial Qt licenses may use this file in --- accordance with the commercial license agreement provided with the --- Software or, alternatively, in accordance with the terms contained in --- a written agreement between you and The Qt Company. For licensing terms --- and conditions see https://www.qt.io/terms-conditions. For further --- information use the contact form at https://www.qt.io/contact-us. --- --- GNU General Public License Usage --- Alternatively, this file may be used under the terms of the GNU --- General Public License version 3 as published by the Free Software --- Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT --- included in the packaging of this file. Please review the following --- information to ensure the GNU General Public License requirements will --- be met: https://www.gnu.org/licenses/gpl-3.0.html. --- --- $QT_END_LICENSE$ --- ----------------------------------------------------------------------------- +-- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause %parser GLSLParserTable %merged_output glsl.cpp diff --git a/src/tools/qlalr/examples/lambda/lambda.g b/src/tools/qlalr/examples/lambda/lambda.g index 2aa3c128e9..d4fd01ed4c 100644 --- a/src/tools/qlalr/examples/lambda/lambda.g +++ b/src/tools/qlalr/examples/lambda/lambda.g @@ -1,30 +1,5 @@ ----------------------------------------------------------------------------- --- -- Copyright (C) 2016 The Qt Company Ltd. --- Contact: https://www.qt.io/licensing/ --- --- This file is part of the QtCore module of the Qt Toolkit. --- --- $QT_BEGIN_LICENSE:GPL-EXCEPT$ --- Commercial License Usage --- Licensees holding valid commercial Qt licenses may use this file in --- accordance with the commercial license agreement provided with the --- Software or, alternatively, in accordance with the terms contained in --- a written agreement between you and The Qt Company. For licensing terms --- and conditions see https://www.qt.io/terms-conditions. For further --- information use the contact form at https://www.qt.io/contact-us. --- --- GNU General Public License Usage --- Alternatively, this file may be used under the terms of the GNU --- General Public License version 3 as published by the Free Software --- Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT --- included in the packaging of this file. Please review the following --- information to ensure the GNU General Public License requirements will --- be met: https://www.gnu.org/licenses/gpl-3.0.html. --- --- $QT_END_LICENSE$ --- ----------------------------------------------------------------------------- +-- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -- lambda calculus diff --git a/src/tools/qlalr/examples/lambda/main.cpp b/src/tools/qlalr/examples/lambda/main.cpp index 6198cc8538..c6a2695493 100644 --- a/src/tools/qlalr/examples/lambda/main.cpp +++ b/src/tools/qlalr/examples/lambda/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "lambda.h" diff --git a/src/tools/qlalr/examples/qparser/calc.g b/src/tools/qlalr/examples/qparser/calc.g index 8524c1ca2c..2be9fd55c0 100644 --- a/src/tools/qlalr/examples/qparser/calc.g +++ b/src/tools/qlalr/examples/qparser/calc.g @@ -1,30 +1,5 @@ ----------------------------------------------------------------------------- --- -- Copyright (C) 2016 The Qt Company Ltd. --- Contact: https://www.qt.io/licensing/ --- --- This file is part of the QtCore module of the Qt Toolkit. --- --- $QT_BEGIN_LICENSE:GPL-EXCEPT$ --- Commercial License Usage --- Licensees holding valid commercial Qt licenses may use this file in --- accordance with the commercial license agreement provided with the --- Software or, alternatively, in accordance with the terms contained in --- a written agreement between you and The Qt Company. For licensing terms --- and conditions see https://www.qt.io/terms-conditions. For further --- information use the contact form at https://www.qt.io/contact-us. --- --- GNU General Public License Usage --- Alternatively, this file may be used under the terms of the GNU --- General Public License version 3 as published by the Free Software --- Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT --- included in the packaging of this file. Please review the following --- information to ensure the GNU General Public License requirements will --- be met: https://www.gnu.org/licenses/gpl-3.0.html. --- --- $QT_END_LICENSE$ --- ----------------------------------------------------------------------------- +-- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause %parser calc_grammar %decl calc_parser.h diff --git a/src/tools/qlalr/examples/qparser/calc.l b/src/tools/qlalr/examples/qparser/calc.l index fe542680a8..0f42987758 100644 --- a/src/tools/qlalr/examples/qparser/calc.l +++ b/src/tools/qlalr/examples/qparser/calc.l @@ -1,34 +1,5 @@ - -%option noyywrap - -%{ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR tool of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "calc_parser.h" #include <cstdlib> diff --git a/src/tools/qlalr/examples/qparser/qparser.cpp b/src/tools/qlalr/examples/qparser/qparser.cpp index c173436a9c..354a778458 100644 --- a/src/tools/qlalr/examples/qparser/qparser.cpp +++ b/src/tools/qlalr/examples/qparser/qparser.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "qparser.h" diff --git a/src/tools/qlalr/examples/qparser/qparser.h b/src/tools/qlalr/examples/qparser/qparser.h index 7700562d55..80643616c4 100644 --- a/src/tools/qlalr/examples/qparser/qparser.h +++ b/src/tools/qlalr/examples/qparser/qparser.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef QPARSER_H #define QPARSER_H diff --git a/src/tools/qlalr/grammar.cpp b/src/tools/qlalr/grammar.cpp index 2ab6504bf8..d4d8916c6d 100644 --- a/src/tools/qlalr/grammar.cpp +++ b/src/tools/qlalr/grammar.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // This file was generated by qlalr - DO NOT EDIT! #include "grammar_p.h" diff --git a/src/tools/qlalr/grammar_p.h b/src/tools/qlalr/grammar_p.h index 8482f9bfe1..b6fa6b6353 100644 --- a/src/tools/qlalr/grammar_p.h +++ b/src/tools/qlalr/grammar_p.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // // W A R N I N G diff --git a/src/tools/qlalr/lalr.cpp b/src/tools/qlalr/lalr.cpp index 853465c272..51f1d94a40 100644 --- a/src/tools/qlalr/lalr.cpp +++ b/src/tools/qlalr/lalr.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the utils of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "lalr.h" @@ -39,6 +14,8 @@ #define QLALR_NO_DEBUG_INCLUDES #define QLALR_NO_DEBUG_LOOKAHEADS +using namespace Qt::StringLiterals; + QT_BEGIN_NAMESPACE QTextStream &qerr() { @@ -162,24 +139,24 @@ State::State (Grammar *g): { } -QPair<ItemPointer, bool> State::insert (const Item &item) +std::pair<ItemPointer, bool> State::insert(const Item &item) { ItemPointer it = std::find (kernel.begin (), kernel.end (), item); if (it != kernel.end ()) - return qMakePair (it, false); + return {it, false}; - return qMakePair (kernel.insert (it, item), true); + return {kernel.insert(it, item), true}; } -QPair<ItemPointer, bool> State::insertClosure (const Item &item) +std::pair<ItemPointer, bool> State::insertClosure(const Item &item) { ItemPointer it = std::find (closure.begin (), closure.end (), item); if (it != closure.end ()) - return qMakePair (it, false); + return {it, false}; - return qMakePair (closure.insert (it, item), true); + return {closure.insert (it, item), true}; } @@ -194,11 +171,11 @@ Grammar::Grammar (): current_prec = 0; current_assoc = NonAssoc; - table_name = QLatin1String ("parser_table"); + table_name = "parser_table"_L1; tk_end = intern ("$end"); terminals.insert (tk_end); - spells.insert (tk_end, QLatin1String("end of file")); + spells.insert (tk_end, "end of file"_L1); /*tk_error= terminals.insert (intern ("error"))*/; } @@ -319,14 +296,14 @@ void Automaton::buildNullables () #endif } -QPair<StatePointer, bool> Automaton::internState (const State &state) +std::pair<StatePointer, bool> Automaton::internState(const State &state) { StatePointer it = std::find (states.begin (), states.end (), state); if (it != states.end ()) - return qMakePair (it, false); + return {it, false}; - return qMakePair (states.insert (it, state), true); + return {states.insert (it, state), true}; } struct _Bucket @@ -374,7 +351,7 @@ void Automaton::closure (StatePointer state) if (_M_grammar->isNonTerminal (*item->dot)) { - const auto range = qAsConst(_M_grammar->rule_map).equal_range(*item->dot); + const auto range = std::as_const(_M_grammar->rule_map).equal_range(*item->dot); for (auto it = range.first; it != range.second; ++it) { const RulePointer &rule = *it; @@ -382,7 +359,7 @@ void Automaton::closure (StatePointer state) ii.rule = rule; ii.dot = rule->rhs.begin (); - QPair<ItemPointer, bool> r = state->insertClosure (ii); + std::pair<ItemPointer, bool> r = state->insertClosure(ii); if (r.second) working_list.push (r.first); @@ -394,7 +371,7 @@ void Automaton::closure (StatePointer state) for (bucket_map_type::iterator bucket = buckets.begin (); bucket != buckets.end (); ++bucket) { - QPair<StatePointer, bool> r = internState (bucket->toState (this)); + std::pair<StatePointer, bool> r = internState(bucket->toState(this)); StatePointer target = r.first; @@ -422,7 +399,7 @@ void Automaton::buildLookbackSets () if (! _M_grammar->isNonTerminal (A)) continue; - const auto range = qAsConst(_M_grammar->rule_map).equal_range(A); + const auto range = std::as_const(_M_grammar->rule_map).equal_range(A); for (auto it = range.first; it != range.second; ++it) { const RulePointer &rule = *it; @@ -617,7 +594,7 @@ void Automaton::buildIncludesDigraph () if (! _M_grammar->isNonTerminal (name)) continue; - const auto range = qAsConst(_M_grammar->rule_map).equal_range(name); + const auto range = std::as_const(_M_grammar->rule_map).equal_range(name); for (auto it = range.first; it != range.second; ++it) { const RulePointer &rule = *it; @@ -719,7 +696,7 @@ void Automaton::buildLookaheads () { for (ItemPointer item = p->closure.begin (); item != p->closure.end (); ++item) { - const auto range = qAsConst(lookbacks).equal_range(item); + const auto range = std::as_const(lookbacks).equal_range(item); for (auto it = range.first; it != range.second; ++it) { const Lookback &lookback = *it; diff --git a/src/tools/qlalr/lalr.g b/src/tools/qlalr/lalr.g index 3c1d1d3ebd..71d0054feb 100644 --- a/src/tools/qlalr/lalr.g +++ b/src/tools/qlalr/lalr.g @@ -1,30 +1,5 @@ ------------------------------------------------------------------------------ --- -- Copyright (C) 2016 The Qt Company Ltd. --- Contact: https://www.qt.io/licensing/ --- --- This file is part of the QLALR project on Qt Labs. --- --- $QT_BEGIN_LICENSE:GPL-EXCEPT$ --- Commercial License Usage --- Licensees holding valid commercial Qt licenses may use this file in --- accordance with the commercial license agreement provided with the --- Software or, alternatively, in accordance with the terms contained in --- a written agreement between you and The Qt Company. For licensing terms --- and conditions see https://www.qt.io/terms-conditions. For further --- information use the contact form at https://www.qt.io/contact-us. --- --- GNU General Public License Usage --- Alternatively, this file may be used under the terms of the GNU --- General Public License version 3 as published by the Free Software --- Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT --- included in the packaging of this file. Please review the following --- information to ensure the GNU General Public License requirements will --- be met: https://www.gnu.org/licenses/gpl-3.0.html. --- --- $QT_END_LICENSE$ --- ------------------------------------------------------------------------------ +-- SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 %parser grammar @@ -61,33 +36,8 @@ %start Specification -/:/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR project on Qt Labs. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +/:// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "$header" @@ -126,7 +76,7 @@ protected: // scanner { ch = *_M_currentChar++; - if (ch == QLatin1Char('\n')) + if (ch == u'\n') ++_M_line; } else @@ -162,33 +112,8 @@ protected: }; :/ -/./**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR project on Qt Labs. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +/.// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "recognizer.h" @@ -198,6 +123,8 @@ protected: #include <cstring> #include <cctype> +using namespace Qt::StringLiterals; + Recognizer::Recognizer (Grammar *grammar, bool no_lines): tos(0), stack_size(0), @@ -247,9 +174,9 @@ int Recognizer::nextToken() { inp(); // skip " text.clear (); - while (! ch.isNull () && ch != QLatin1Char ('"')) + while (! ch.isNull () && ch != u'"') { - if (ch == QLatin1Char ('\\')) + if (ch == u'\\') { text += ch; inp(); @@ -258,7 +185,7 @@ int Recognizer::nextToken() inp (); } - if (ch == QLatin1Char ('"')) + if (ch == u'"') inp (); else qerr() << _M_input_file << ":" << _M_line << ": Warning. Expected `\"'" << Qt::endl; @@ -267,11 +194,11 @@ int Recognizer::nextToken() return (token = STRING_LITERAL); } - else if (ch.isLetterOrNumber () || ch == QLatin1Char ('_')) + else if (ch.isLetterOrNumber () || ch == u'_') { text.clear (); do { text += ch; inp (); } - while (ch.isLetterOrNumber () || ch == QLatin1Char ('_') || ch == QLatin1Char ('.')); + while (ch.isLetterOrNumber () || ch == u'_' || ch == u'.'); _M_current_value = text; return (token = ID); } @@ -284,33 +211,33 @@ int Recognizer::nextToken() while (ch.isSpace ()); do { text += ch; inp (); } - while (ch.isLetterOrNumber () || ch == QLatin1Char ('_') || ch == QLatin1Char ('-')); + while (ch.isLetterOrNumber () || ch == u'_' || ch == u'-'); - if (text == QLatin1String("token_prefix")) + if (text == "token_prefix"_L1) return (token = TOKEN_PREFIX); - else if (text == QLatin1String("merged_output")) + else if (text == "merged_output"_L1) return (token = MERGED_OUTPUT); - else if (text == QLatin1String("token")) + else if (text == "token"_L1) return (token = TOKEN); - else if (text == QLatin1String("start")) + else if (text == "start"_L1) return (token = START); - else if (text == QLatin1String("parser")) + else if (text == "parser"_L1) return (token = PARSER); - else if (text == QLatin1String("decl")) + else if (text == "decl"_L1) return (token = DECL_FILE); - else if (text == QLatin1String("impl")) + else if (text == "impl"_L1) return (token = IMPL_FILE); - else if (text == QLatin1String("expect")) + else if (text == "expect"_L1) return (token = EXPECT); - else if (text == QLatin1String("expect-rr")) + else if (text == "expect-rr"_L1) return (token = EXPECT_RR); - else if (text == QLatin1String("left")) + else if (text == "left"_L1) return (token = LEFT); - else if (text == QLatin1String("right")) + else if (text == "right"_L1) return (token = RIGHT); - else if (text == QLatin1String("nonassoc")) + else if (text == "nonassoc"_L1) return (token = NONASSOC); - else if (text == QLatin1String("prec")) + else if (text == "prec"_L1) return (token = PREC); else { @@ -322,30 +249,30 @@ int Recognizer::nextToken() inp (); - if (token == '-' && ch == QLatin1Char ('-')) + if (token == '-' && ch == u'-') { do { inp (); } - while (! ch.isNull () && ch != QLatin1Char ('\n')); + while (! ch.isNull () && ch != u'\n'); goto Lagain; } - else if (token == ':' && ch == QLatin1Char (':')) + else if (token == ':' && ch == u':') { inp (); - if (ch != QLatin1Char ('=')) + if (ch != u'=') return (token = ERROR); inp (); return (token = COLON); } - else if (token == '/' && ch == QLatin1Char (':')) + else if (token == '/' && ch == u':') { _M_action_line = _M_line; text.clear (); if (! _M_no_lines) - text += QLatin1String("\n#line ") + QString::number(_M_action_line) + - QLatin1String(" \"") + QDir::fromNativeSeparators(_M_input_file) + QLatin1String("\"\n"); + text += "\n#line "_L1 + QString::number(_M_action_line) + + " \""_L1 + QDir::fromNativeSeparators(_M_input_file) + "\"\n"_L1; inp (); // skip ':' forever @@ -355,13 +282,13 @@ int Recognizer::nextToken() token = ch.unicode (); inp (); - if (token == ':' && ch == QLatin1Char ('/')) + if (token == ':' && ch == u'/') break; text += QLatin1Char (token); } - if (ch != QLatin1Char ('/')) + if (ch != u'/') return (token = ERROR); inp (); @@ -372,18 +299,18 @@ int Recognizer::nextToken() return (token = DECL); } else - text += QLatin1String (":/"); + text += ":/"_L1; } } - else if (token == '/' && ch == QLatin1Char ('.')) + else if (token == '/' && ch == u'.') { _M_action_line = _M_line; text.clear (); if (! _M_no_lines) - text += QLatin1String("\n#line ") + QString::number(_M_action_line) + - QLatin1String(" \"") + QDir::fromNativeSeparators(_M_input_file) + QLatin1String("\"\n"); + text += "\n#line "_L1 + QString::number(_M_action_line) + + " \""_L1 + QDir::fromNativeSeparators(_M_input_file) + "\"\n"_L1; inp (); // skip ':' @@ -394,13 +321,13 @@ int Recognizer::nextToken() token = ch.unicode (); inp (); - if (token == '.' && ch == QLatin1Char ('/')) + if (token == '.' && ch == u'/') break; text += QLatin1Char (token); } - if (ch != QLatin1Char ('/')) + if (ch != u'/') return (token = ERROR); inp (); @@ -411,7 +338,7 @@ int Recognizer::nextToken() return (token = IMPL); } else - text += QLatin1String ("./"); + text += "./"_L1; } } diff --git a/src/tools/qlalr/lalr.h b/src/tools/qlalr/lalr.h index 5be44fea67..efa0a91a39 100644 --- a/src/tools/qlalr/lalr.h +++ b/src/tools/qlalr/lalr.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the utils of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef LALR_H #define LALR_H @@ -34,7 +9,6 @@ #include <QtCore/qmap.h> #include <QtCore/qstring.h> #include <QtCore/qtextstream.h> -#include <QtCore/qpair.h> #include <algorithm> #include <functional> @@ -142,8 +116,8 @@ public: inline bool operator != (const State &other) const { return kernel != other.kernel; } - QPair<ItemPointer, bool> insert (const Item &item); - QPair<ItemPointer, bool> insertClosure (const Item &item); + std::pair<ItemPointer, bool> insert(const Item &item); + std::pair<ItemPointer, bool> insertClosure(const Item &item); public: // attributes ItemList kernel; @@ -168,7 +142,7 @@ public: public: static iterator get (_Tp data); - QPair<edge_iterator, bool> insertEdge (iterator other) const; + std::pair<edge_iterator, bool> insertEdge(iterator other) const; inline edge_iterator begin () const { return outs.begin (); } @@ -223,15 +197,15 @@ typename Node<_Tp>::iterator Node<_Tp>::get (_Tp data) } template <typename _Tp> -QPair<typename std::list<typename Node<_Tp>::iterator>::iterator, bool> Node<_Tp>::insertEdge(typename Node<_Tp>::iterator other) const +std::pair<typename std::list<typename Node<_Tp>::iterator>::iterator, bool> Node<_Tp>::insertEdge(typename Node<_Tp>::iterator other) const { edge_iterator it = std::find (outs.begin (), outs.end (), other); if (it != outs.end ()) - return qMakePair (it, false); + return {it, false}; other->root = false; - return qMakePair (outs.insert (outs.end (), other), true); + return {outs.insert (outs.end (), other), true}; } ///////////////////////////////////////////////////////////// @@ -337,7 +311,7 @@ class Automaton public: Automaton (Grammar *g); - QPair<StatePointer, bool> internState (const State &state); + std::pair<StatePointer, bool> internState (const State &state); typedef Node<Read> ReadsGraph; typedef ReadsGraph::iterator ReadNode; diff --git a/src/tools/qlalr/main.cpp b/src/tools/qlalr/main.cpp index 6a57c7aa7a..04ae54d986 100644 --- a/src/tools/qlalr/main.cpp +++ b/src/tools/qlalr/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the utils of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "lalr.h" #include "dotgraph.h" @@ -42,6 +17,8 @@ #define QLALR_NO_DEBUG_TABLE #define QLALR_NO_DEBUG_DOT +using namespace Qt::StringLiterals; + static void help_me () { qerr() << "Usage: qlalr [options] [input file name]" << Qt::endl @@ -51,7 +28,8 @@ static void help_me () << " --no-debug\t\tno debug information" << Qt::endl << " --no-lines\t\tno #line directives" << Qt::endl << " --dot\t\t\tgenerate a graph" << Qt::endl - << " --qt\t\tadd the Qt copyright header and Qt-specific types and macros" << Qt::endl + << " --qt\t\t\tadd the Qt copyright header and Qt-specific types and macros" << Qt::endl + << " --exit-on-warn\texit with status code 2 on warning" << Qt::endl << Qt::endl; exit (0); } @@ -65,28 +43,32 @@ int main (int argc, char *argv[]) bool no_lines = false; bool debug_info = true; bool qt_copyright = false; + bool warnings_are_errors = false; QString file_name; const QStringList args = app.arguments().mid(1); for (const QString &arg : args) { - if (arg == QLatin1String ("-h") || arg == QLatin1String ("--help")) + if (arg == "-h"_L1 || arg == "--help"_L1) help_me (); - else if (arg == QLatin1String ("-v") || arg == QLatin1String ("--verbose")) + else if (arg == "-v"_L1 || arg == "--verbose"_L1) generate_report = true; - else if (arg == QLatin1String ("--dot")) + else if (arg == "--dot"_L1) generate_dot = true; - else if (arg == QLatin1String ("--no-lines")) + else if (arg == "--no-lines"_L1) no_lines = true; - else if (arg == QLatin1String ("--no-debug")) + else if (arg == "--no-debug"_L1) debug_info = false; - else if (arg == QLatin1String ("--qt")) + else if (arg == "--qt"_L1) qt_copyright = true; + else if (arg == "--exit-on-warn"_L1) + warnings_are_errors = true; + else if (file_name.isEmpty ()) file_name = arg; @@ -127,6 +109,7 @@ int main (int argc, char *argv[]) CppGenerator gen (p, grammar, aut, generate_report); gen.setDebugInfo (debug_info); gen.setCopyright (qt_copyright); + gen.setWarningsAreErrors (warnings_are_errors); gen (); if (generate_dot) @@ -150,19 +133,19 @@ QString Recognizer::expand (const QString &text) const if (_M_grammar->start != _M_grammar->names.end ()) { - code = code.replace (QLatin1String("$start_id"), QString::number (std::distance (_M_grammar->names.begin (), _M_grammar->start))); - code = code.replace (QLatin1String("$start"), *_M_grammar->start); + code = code.replace ("$start_id"_L1, QString::number (std::distance (_M_grammar->names.begin (), _M_grammar->start))); + code = code.replace ("$start"_L1, *_M_grammar->start); } - code = code.replace (QLatin1String("$header"), _M_grammar->table_name.toLower () + QLatin1String("_p.h")); + code = code.replace ("$header"_L1, _M_grammar->table_name.toLower () + "_p.h"_L1); - code = code.replace (QLatin1String("$table"), _M_grammar->table_name); - code = code.replace (QLatin1String("$parser"), _M_grammar->table_name); + code = code.replace ("$table"_L1, _M_grammar->table_name); + code = code.replace ("$parser"_L1, _M_grammar->table_name); if (_M_current_rule != _M_grammar->rules.end ()) { - code = code.replace (QLatin1String("$rule_number"), QString::number (std::distance (_M_grammar->rules.begin (), _M_current_rule))); - code = code.replace (QLatin1String("$rule"), *_M_current_rule->lhs); + code = code.replace ("$rule_number"_L1, QString::number (std::distance (_M_grammar->rules.begin (), _M_current_rule))); + code = code.replace ("$rule"_L1, *_M_current_rule->lhs); } return code; diff --git a/src/tools/qlalr/parsetable.cpp b/src/tools/qlalr/parsetable.cpp index 9e71acebb4..c144b6f7a6 100644 --- a/src/tools/qlalr/parsetable.cpp +++ b/src/tools/qlalr/parsetable.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the utils of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "parsetable.h" diff --git a/src/tools/qlalr/parsetable.h b/src/tools/qlalr/parsetable.h index 2bf477011b..9d3a97026b 100644 --- a/src/tools/qlalr/parsetable.h +++ b/src/tools/qlalr/parsetable.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the utils of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef PARSETABLE_H #define PARSETABLE_H diff --git a/src/tools/qlalr/recognizer.cpp b/src/tools/qlalr/recognizer.cpp index 760a094460..65862f7b02 100644 --- a/src/tools/qlalr/recognizer.cpp +++ b/src/tools/qlalr/recognizer.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR project on Qt Labs. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "recognizer.h" @@ -34,6 +9,8 @@ #include <cstring> #include <cctype> +using namespace Qt::StringLiterals; + Recognizer::Recognizer (Grammar *grammar, bool no_lines): tos(0), stack_size(0), @@ -83,9 +60,9 @@ int Recognizer::nextToken() { inp(); // skip " text.clear (); - while (! ch.isNull () && ch != QLatin1Char ('"')) + while (!ch.isNull () && ch != u'"') { - if (ch == QLatin1Char ('\\')) + if (ch == u'\\') { text += ch; inp(); @@ -94,7 +71,7 @@ int Recognizer::nextToken() inp (); } - if (ch == QLatin1Char ('"')) + if (ch == u'"') inp (); else qerr() << _M_input_file << ":" << _M_line << ": Warning. Expected `\"'" << Qt::endl; @@ -103,11 +80,11 @@ int Recognizer::nextToken() return (token = STRING_LITERAL); } - else if (ch.isLetterOrNumber () || ch == QLatin1Char ('_')) + else if (ch.isLetterOrNumber () || ch == u'_') { text.clear (); do { text += ch; inp (); } - while (ch.isLetterOrNumber () || ch == QLatin1Char ('_') || ch == QLatin1Char ('.')); + while (ch.isLetterOrNumber () || ch == u'_' || ch == u'.'); _M_current_value = text; return (token = ID); } @@ -120,33 +97,33 @@ int Recognizer::nextToken() while (ch.isSpace ()); do { text += ch; inp (); } - while (ch.isLetterOrNumber () || ch == QLatin1Char ('_') || ch == QLatin1Char ('-')); + while (ch.isLetterOrNumber () || ch == u'_' || ch == u'-'); - if (text == QLatin1String("token_prefix")) + if (text == "token_prefix"_L1) return (token = TOKEN_PREFIX); - else if (text == QLatin1String("merged_output")) + else if (text == "merged_output"_L1) return (token = MERGED_OUTPUT); - else if (text == QLatin1String("token")) + else if (text == "token"_L1) return (token = TOKEN); - else if (text == QLatin1String("start")) + else if (text == "start"_L1) return (token = START); - else if (text == QLatin1String("parser")) + else if (text == "parser"_L1) return (token = PARSER); - else if (text == QLatin1String("decl")) + else if (text == "decl"_L1) return (token = DECL_FILE); - else if (text == QLatin1String("impl")) + else if (text == "impl"_L1) return (token = IMPL_FILE); - else if (text == QLatin1String("expect")) + else if (text == "expect"_L1) return (token = EXPECT); - else if (text == QLatin1String("expect-rr")) + else if (text == "expect-rr"_L1) return (token = EXPECT_RR); - else if (text == QLatin1String("left")) + else if (text == "left"_L1) return (token = LEFT); - else if (text == QLatin1String("right")) + else if (text == "right"_L1) return (token = RIGHT); - else if (text == QLatin1String("nonassoc")) + else if (text == "nonassoc"_L1) return (token = NONASSOC); - else if (text == QLatin1String("prec")) + else if (text == "prec"_L1) return (token = PREC); else { @@ -158,30 +135,30 @@ int Recognizer::nextToken() inp (); - if (token == '-' && ch == QLatin1Char ('-')) + if (token == '-' && ch == u'-') { do { inp (); } - while (! ch.isNull () && ch != QLatin1Char ('\n')); + while (!ch.isNull () && ch != u'\n'); goto Lagain; } - else if (token == ':' && ch == QLatin1Char (':')) + else if (token == ':' && ch == u':') { inp (); - if (ch != QLatin1Char ('=')) + if (ch != u'=') return (token = ERROR); inp (); return (token = COLON); } - else if (token == '/' && ch == QLatin1Char (':')) + else if (token == '/' && ch == u':') { _M_action_line = _M_line; text.clear (); if (! _M_no_lines) - text += QLatin1String("\n#line ") + QString::number(_M_action_line) + - QLatin1String(" \"") + QDir::fromNativeSeparators(_M_input_file) + QLatin1String("\"\n"); + text += "\n#line "_L1 + QString::number(_M_action_line) + + " \""_L1 + QDir::fromNativeSeparators(_M_input_file) + "\"\n"_L1; inp (); // skip ':' forever @@ -191,13 +168,13 @@ int Recognizer::nextToken() token = ch.unicode (); inp (); - if (token == ':' && ch == QLatin1Char ('/')) + if (token == ':' && ch == u'/') break; text += QLatin1Char (token); } - if (ch != QLatin1Char ('/')) + if (ch != u'/') return (token = ERROR); inp (); @@ -208,18 +185,18 @@ int Recognizer::nextToken() return (token = DECL); } else - text += QLatin1String (":/"); + text += ":/"_L1; } } - else if (token == '/' && ch == QLatin1Char ('.')) + else if (token == '/' && ch == u'.') { _M_action_line = _M_line; text.clear (); if (! _M_no_lines) - text += QLatin1String("\n#line ") + QString::number(_M_action_line) + - QLatin1String(" \"") + QDir::fromNativeSeparators(_M_input_file) + QLatin1String("\"\n"); + text += "\n#line "_L1 + QString::number(_M_action_line) + + " \""_L1 + QDir::fromNativeSeparators(_M_input_file) + "\"\n"_L1; inp (); // skip ':' @@ -230,13 +207,13 @@ int Recognizer::nextToken() token = ch.unicode (); inp (); - if (token == '.' && ch == QLatin1Char ('/')) + if (token == '.' && ch == u'/') break; text += QLatin1Char (token); } - if (ch != QLatin1Char ('/')) + if (ch != u'/') return (token = ERROR); inp (); @@ -247,7 +224,7 @@ int Recognizer::nextToken() return (token = IMPL); } else - text += QLatin1String (""); + text += ""_L1; } } diff --git a/src/tools/qlalr/recognizer.h b/src/tools/qlalr/recognizer.h index 001de3bcf1..31d606e657 100644 --- a/src/tools/qlalr/recognizer.h +++ b/src/tools/qlalr/recognizer.h @@ -1,30 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QLALR project on Qt Labs. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef RECOGNIZER_H +#define RECOGNIZER_H #include "grammar_p.h" @@ -63,7 +41,7 @@ protected: // scanner { ch = *_M_currentChar++; - if (ch == QLatin1Char('\n')) + if (ch == u'\n') ++_M_line; } else @@ -97,3 +75,5 @@ protected: QString _M_current_value; bool _M_no_lines; }; + +#endif // RECOGNIZER_H diff --git a/src/tools/qtpaths/CMakeLists.txt b/src/tools/qtpaths/CMakeLists.txt index 77b16f5f1e..d64caeb3c2 100644 --- a/src/tools/qtpaths/CMakeLists.txt +++ b/src/tools/qtpaths/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from qtpaths.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## qtpaths App: @@ -6,15 +7,16 @@ qt_get_tool_target_name(target_name qtpaths) qt_internal_add_tool(${target_name} + TRY_RUN TARGET_DESCRIPTION "Qt tool that provides the standard paths of the Qt framework" TOOLS_TARGET Core INSTALL_VERSIONED_LINK SOURCES qtpaths.cpp DEFINES - QT_NO_FOREACH QTPATHS_VERSION_STR="2.0" ) +qt_internal_return_unless_building_tools() ## Scopes: ##################################################################### @@ -22,7 +24,6 @@ qt_internal_add_tool(${target_name} qt_internal_extend_target(${target_name} CONDITION QT_FEATURE_settings LIBRARIES QtLibraryInfo - Qt::CorePrivate ) if(WIN32 AND TARGET ${target_name}) diff --git a/src/tools/qtpaths/qtpaths.cpp b/src/tools/qtpaths/qtpaths.cpp index 667b594b67..71f9fe4349 100644 --- a/src/tools/qtpaths/qtpaths.cpp +++ b/src/tools/qtpaths/qtpaths.cpp @@ -1,52 +1,5 @@ -/**************************************************************************** - * * - ** Copyright (C) 2016 Sune Vuorela <sune@kde.org> - ** Contact: http://www.qt-project.org/ - ** - ** This file is part of the tools applications of the Qt Toolkit. - ** - ** $QT_BEGIN_LICENSE:BSD$ - ** 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. - ** - ** BSD License Usage - ** Alternatively, you may use this file under the terms of the BSD license - ** as follows: - ** - ** "Redistribution and use in source and binary forms, with or without - ** modification, are permitted provided that the following conditions are - ** met: - ** * Redistributions of source code must retain the above copyright - ** notice, this list of conditions and the following disclaimer. - ** * Redistributions in binary form must reproduce the above copyright - ** notice, this list of conditions and the following disclaimer in - ** the documentation and/or other materials provided with the - ** distribution. - ** * Neither the name of The Qt Company Ltd nor the names of its - ** contributors may be used to endorse or promote products derived - ** from this software without specific prior written permission. - ** - ** - ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." - ** - ** $QT_END_LICENSE$ - ** - ****************************************************************************/ +// Copyright (C) 2016 Sune Vuorela <sune@kde.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QCoreApplication> #include <QCommandLineParser> @@ -108,9 +61,6 @@ static const StringEnum lookupTableData[] = { { "ApplicationsLocation", QStandardPaths::ApplicationsLocation, false }, { "CacheLocation", QStandardPaths::CacheLocation, true }, { "ConfigLocation", QStandardPaths::ConfigLocation, false }, -#if QT_VERSION < QT_VERSION_CHECK(6,0,0) - { "DataLocation", QStandardPaths::DataLocation, true }, -#endif { "DesktopLocation", QStandardPaths::DesktopLocation, false }, { "DocumentsLocation", QStandardPaths::DocumentsLocation, false }, { "DownloadLocation", QStandardPaths::DownloadLocation, false }, @@ -118,11 +68,15 @@ static const StringEnum lookupTableData[] = { { "GenericCacheLocation", QStandardPaths::GenericCacheLocation, false }, { "GenericConfigLocation", QStandardPaths::GenericConfigLocation, false }, { "GenericDataLocation", QStandardPaths::GenericDataLocation, false }, + { "GenericStateLocation", QStandardPaths::GenericStateLocation, false }, { "HomeLocation", QStandardPaths::HomeLocation, false }, { "MoviesLocation", QStandardPaths::MoviesLocation, false }, { "MusicLocation", QStandardPaths::MusicLocation, false }, { "PicturesLocation", QStandardPaths::PicturesLocation, false }, + { "PublicShareLocation", QStandardPaths::PublicShareLocation, false }, { "RuntimeLocation", QStandardPaths::RuntimeLocation, false }, + { "StateLocation", QStandardPaths::StateLocation, true }, + { "TemplatesLocation", QStandardPaths::TemplatesLocation, false }, { "TempLocation", QStandardPaths::TempLocation, false } }; @@ -145,7 +99,7 @@ static QStringList types() static const StringEnum &parseLocationOrError(const QString &locationString) { for (const StringEnum &se : lookupTableData) - if (locationString == QLatin1String(se.stringvalue)) + if (locationString == QLatin1StringView(se.stringvalue)) return se; QString message = QStringLiteral("Unknown location: %1"); @@ -168,6 +122,7 @@ static QString searchStringOrError(QCommandLineParser *parser) int main(int argc, char **argv) { + QString qtconfManualPath; QCoreApplication app(argc, argv); app.setApplicationVersion(QTPATHS_VERSION_STR); @@ -265,13 +220,14 @@ int main(int argc, char **argv) #if QT_CONFIG(settings) if (parser.isSet(qtconf)) { - QLibraryInfoPrivate::qtconfManualPath = parser.value(qtconf); + qtconfManualPath = parser.value(qtconf); + QLibraryInfoPrivate::setQtconfManualPath(&qtconfManualPath); } #endif QStringList results; if (parser.isSet(qtversion)) { - QString qtversionstring = QString::fromLatin1(qVersion()); + QString qtversionstring = QString::fromLatin1(QT_VERSION_STR); results << qtversionstring; } @@ -295,6 +251,10 @@ int main(int argc, char **argv) results << typesList.join('\n'); } + QT_WARNING_PUSH +#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU >= 1300 && Q_CC_GNU < 1500 + QT_WARNING_DISABLE_GCC("-Wdangling-reference") +#endif if (parser.isSet(display)) { const StringEnum &location = parseLocationOrError(parser.value(display)); QString text = QStandardPaths::displayName(location.enumvalue); @@ -346,6 +306,7 @@ int main(int argc, char **argv) QStringList paths = QStandardPaths::locateAll(location.enumvalue, searchitem, QStandardPaths::LocateFile); results << location.mapName(paths.join(pathsep)); } + QT_WARNING_POP #if !QT_CONFIG(settings) if (parser.isSet(query) || parser.isSet(qtconf) || parser.isSet(queryformat)) { @@ -383,7 +344,7 @@ int main(int argc, char **argv) if (results.isEmpty()) { parser.showHelp(); } else if (results.size() == 1) { - const QString &item = results.first(); + const QString &item = results.constFirst(); message(item); if (item.isEmpty()) return EXIT_FAILURE; diff --git a/src/tools/qvkgen/CMakeLists.txt b/src/tools/qvkgen/CMakeLists.txt index 75ea64fda6..0f68968fd3 100644 --- a/src/tools/qvkgen/CMakeLists.txt +++ b/src/tools/qvkgen/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from qvkgen.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## qvkgen Tool: @@ -8,13 +9,10 @@ qt_get_tool_target_name(target_name qvkgen) qt_internal_add_tool(${target_name} TARGET_DESCRIPTION "Qt Vulkan Header Generator" INSTALL_DIR "${INSTALL_LIBEXECDIR}" - TOOLS_TARGET Gui # special case + TOOLS_TARGET Gui SOURCES qvkgen.cpp - PUBLIC_LIBRARIES - Qt::Core # special case + LIBRARIES + Qt::Core ) - -#### Keys ignored in scope 1:.:.:qvkgen.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "Qt Vulkan Header Generator" -# _OPTION = "host_build" +qt_internal_return_unless_building_tools() diff --git a/src/tools/qvkgen/qvkgen.cpp b/src/tools/qvkgen/qvkgen.cpp index 798cbb7932..3ef7aa56f6 100644 --- a/src/tools/qvkgen/qvkgen.cpp +++ b/src/tools/qvkgen/qvkgen.cpp @@ -1,42 +1,19 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QtCore/qcoreapplication.h> #include <QtCore/qfile.h> #include <QtCore/qfileinfo.h> #include <QtCore/qlist.h> +#include <QtCore/qmap.h> #include <QtCore/qxmlstream.h> // generate wrappers for core functions from the following versions static const QStringList VERSIONS = { QStringLiteral("VK_VERSION_1_0"), // must be the first and always present QStringLiteral("VK_VERSION_1_1"), - QStringLiteral("VK_VERSION_1_2") + QStringLiteral("VK_VERSION_1_2"), + QStringLiteral("VK_VERSION_1_3") }; class VkSpecParser @@ -234,11 +211,11 @@ VkSpecParser::TypedName VkSpecParser::parseParamOrProto(const QString &tag) } else { auto text = m_reader.text().trimmed(); if (!text.isEmpty()) { - if (text.startsWith(QLatin1Char('['))) { + if (text.startsWith(u'[')) { t.typeSuffix += text; } else { if (!t.type.isEmpty()) - t.type += QLatin1Char(' '); + t.type += u' '; t.type += text; } } @@ -266,7 +243,7 @@ QString funcSig(const VkSpecParser::Command &c, const char *className = nullptr) (className ? className : ""), (className ? "::" : ""), qPrintable(c.cmd.name))); if (!c.args.isEmpty()) { - s += QLatin1Char('('); + s += u'('; bool first = true; for (const VkSpecParser::TypedName &a : c.args) { if (!first) @@ -274,10 +251,10 @@ QString funcSig(const VkSpecParser::Command &c, const char *className = nullptr) else first = false; s += QString::asprintf("%s%s%s%s", qPrintable(a.type), - (a.type.endsWith(QLatin1Char('*')) ? "" : " "), + (a.type.endsWith(u'*') ? "" : " "), qPrintable(a.name), qPrintable(a.typeSuffix)); } - s += QLatin1Char(')'); + s += u')'; } return s; } @@ -291,7 +268,7 @@ QString funcCall(const VkSpecParser::Command &c, int idx) qPrintable(c.cmd.name), idx); if (!c.args.isEmpty()) { - s += QLatin1Char('('); + s += u'('; bool first = true; for (const VkSpecParser::TypedName &a : c.args) { if (!first) @@ -300,7 +277,7 @@ QString funcCall(const VkSpecParser::Command &c, int idx) first = false; s += a.name; } - s += QLatin1Char(')'); + s += u')'; } return s; } @@ -348,9 +325,13 @@ bool genVulkanFunctionsH(const QList<VkSpecParser::Command> &commands, "#ifndef QVULKANFUNCTIONS_H\n" "#define QVULKANFUNCTIONS_H\n" "\n" +"#if 0\n" +"#pragma qt_no_master_include\n" +"#endif\n" +"\n" "#include <QtGui/qtguiglobal.h>\n" "\n" -"#if QT_CONFIG(vulkan) || defined(Q_CLANG_QDOC)\n" +"#if QT_CONFIG(vulkan) || defined(Q_QDOC)\n" "\n" "#ifndef VK_NO_PROTOTYPES\n" "#define VK_NO_PROTOTYPES\n" @@ -395,7 +376,7 @@ bool genVulkanFunctionsH(const QList<VkSpecParser::Command> &commands, "\n" "QT_END_NAMESPACE\n" "\n" -"#endif // QT_CONFIG(vulkan) || defined(Q_CLANG_QDOC)\n" +"#endif // QT_CONFIG(vulkan) || defined(Q_QDOC)\n" "\n" "#endif // QVULKANFUNCTIONS_H\n"; @@ -509,22 +490,25 @@ bool genVulkanFunctionsPC(const QList<VkSpecParser::Command> &commands, return false; } - static const char *s = + static const char s[] = "%s\n" "#include \"qvulkanfunctions_p.h\"\n" "#include \"qvulkaninstance.h\"\n" "\n" +"#include <QtCore/private/qoffsetstringarray_p.h>\n" +"\n" "QT_BEGIN_NAMESPACE\n" "\n%s" "QVulkanFunctionsPrivate::QVulkanFunctionsPrivate(QVulkanInstance *inst)\n" "{\n" -" static const char *funcNames[] = {\n" +" static constexpr auto funcNames = qOffsetStringArray(\n" "%s\n" -" };\n" -" for (int i = 0; i < %d; ++i) {\n" -" m_funcs[i] = inst->getInstanceProcAddr(funcNames[i]);\n" +" );\n" +" static_assert(std::extent_v<decltype(m_funcs)> == size_t(funcNames.count()));\n" +" for (int i = 0; i < funcNames.count(); ++i) {\n" +" m_funcs[i] = inst->getInstanceProcAddr(funcNames.at(i));\n" " if (i < %d && !m_funcs[i])\n" -" qWarning(\"QVulkanFunctions: Failed to resolve %%s\", funcNames[i]);\n" +" qWarning(\"QVulkanFunctions: Failed to resolve %%s\", funcNames.at(i));\n" " }\n" "}\n" "\n%s" @@ -532,13 +516,14 @@ bool genVulkanFunctionsPC(const QList<VkSpecParser::Command> &commands, "{\n" " QVulkanFunctions *f = inst->functions();\n" " Q_ASSERT(f);\n\n" -" static const char *funcNames[] = {\n" +" static constexpr auto funcNames = qOffsetStringArray(\n" "%s\n" -" };\n" -" for (int i = 0; i < %d; ++i) {\n" -" m_funcs[i] = f->vkGetDeviceProcAddr(device, funcNames[i]);\n" +" );\n" +" static_assert(std::extent_v<decltype(m_funcs)> == size_t(funcNames.count()));\n" +" for (int i = 0; i < funcNames.count(); ++i) {\n" +" m_funcs[i] = f->vkGetDeviceProcAddr(device, funcNames.at(i));\n" " if (i < %d && !m_funcs[i])\n" -" qWarning(\"QVulkanDeviceFunctions: Failed to resolve %%s\", funcNames[i]);\n" +" qWarning(\"QVulkanDeviceFunctions: Failed to resolve %%s\", funcNames.at(i));\n" " }\n" "}\n" "\n" @@ -547,9 +532,7 @@ bool genVulkanFunctionsPC(const QList<VkSpecParser::Command> &commands, QString devCmdWrapperStr; QString instCmdWrapperStr; int devIdx = 0; - int devCount = 0; int instIdx = 0; - int instCount = 0; QString devCmdNamesStr; QString instCmdNamesStr; int vulkan10DevCount = 0; @@ -575,11 +558,6 @@ bool genVulkanFunctionsPC(const QList<VkSpecParser::Command> &commands, *dst += QStringLiteral(" \""); *dst += c.cmd.name; *dst += QStringLiteral("\",\n"); - - if (c.deviceLevel) - devCount += 1; - else - instCount += 1; } if (version == QStringLiteral("VK_VERSION_1_0")) { vulkan10InstCount = instIdx; @@ -589,17 +567,17 @@ bool genVulkanFunctionsPC(const QList<VkSpecParser::Command> &commands, devCmdWrapperStr += "#endif\n\n"; } - if (devCmdNamesStr.count() > 2) - devCmdNamesStr = devCmdNamesStr.left(devCmdNamesStr.count() - 2); - if (instCmdNamesStr.count() > 2) - instCmdNamesStr = instCmdNamesStr.left(instCmdNamesStr.count() - 2); + if (devCmdNamesStr.size() > 2) + devCmdNamesStr.chop(2); + if (instCmdNamesStr.size() > 2) + instCmdNamesStr.chop(2); const QString str = QString::asprintf(s, preamble.get(licHeaderFn).constData(), instCmdWrapperStr.toUtf8().constData(), - instCmdNamesStr.toUtf8().constData(), instCount, vulkan10InstCount, + instCmdNamesStr.toUtf8().constData(), vulkan10InstCount, devCmdWrapperStr.toUtf8().constData(), - devCmdNamesStr.toUtf8().constData(), devCount, vulkan10DevCount); + devCmdNamesStr.toUtf8().constData(), vulkan10DevCount); f.write(str.toUtf8()); @@ -633,7 +611,7 @@ int main(int argc, char **argv) QStringLiteral("vkGetInstanceProcAddr"), QStringLiteral("vkEnumerateInstanceVersion") }; - for (int i = 0; i < commands.count(); ++i) { + for (int i = 0; i < commands.size(); ++i) { if (ignoredFuncs.contains(commands[i].cmd.name)) commands.remove(i--); } diff --git a/src/tools/rcc/CMakeLists.txt b/src/tools/rcc/CMakeLists.txt index 30fa8f4824..35a72c43fe 100644 --- a/src/tools/rcc/CMakeLists.txt +++ b/src/tools/rcc/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from rcc.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## rcc Tool: @@ -6,9 +7,10 @@ qt_get_tool_target_name(target_name rcc) qt_internal_add_tool(${target_name} + TRY_RUN TARGET_DESCRIPTION "Qt Resource Compiler" INSTALL_DIR "${INSTALL_LIBEXECDIR}" - TOOLS_TARGET Core # special case + TOOLS_TARGET Core SOURCES main.cpp rcc.cpp rcc.h @@ -16,25 +18,16 @@ qt_internal_add_tool(${target_name} QT_NO_CAST_FROM_ASCII QT_NO_FOREACH QT_RCC + QT_USE_NODISCARD_FILE_OPEN INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} ) - -#### Keys ignored in scope 1:.:.:rcc.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "Qt Resource Compiler" -# _OPTION = "host_build" +qt_internal_return_unless_building_tools() ## Scopes: ##################################################################### -qt_internal_extend_target(${target_name} CONDITION QT_FEATURE_zstd AND NOT CMAKE_CROSSCOMPILING - DEFINES - QT_FEATURE_zstd=1 +qt_internal_extend_target(${target_name} CONDITION QT_FEATURE_zstd LIBRARIES - ZSTD::ZSTD -) - -qt_internal_extend_target(${target_name} CONDITION CMAKE_CROSSCOMPILING OR NOT QT_FEATURE_zstd - DEFINES - QT_FEATURE_zstd=-1 + WrapZSTD::WrapZSTD ) diff --git a/src/tools/rcc/main.cpp b/src/tools/rcc/main.cpp index eca7991280..03709ccbd4 100644 --- a/src/tools/rcc/main.cpp +++ b/src/tools/rcc/main.cpp @@ -1,31 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2018 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <rcc.h> @@ -49,6 +24,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + void dumpRecursive(const QDir &dir, QTextStream &out) { const QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot @@ -57,9 +34,9 @@ void dumpRecursive(const QDir &dir, QTextStream &out) if (entry.isDir()) { dumpRecursive(entry.filePath(), out); } else { - out << QLatin1String("<file>") + out << "<file>"_L1 << entry.filePath() - << QLatin1String("</file>\n"); + << "</file>\n"_L1; } } } @@ -69,7 +46,7 @@ int createProject(const QString &outFileName) QDir currentDir = QDir::current(); QString currentDirName = currentDir.dirName(); if (currentDirName.isEmpty()) - currentDirName = QLatin1String("root"); + currentDirName = "root"_L1; QFile file; bool isOk = false; @@ -87,14 +64,14 @@ int createProject(const QString &outFileName) } QTextStream out(&file); - out << QLatin1String("<!DOCTYPE RCC><RCC version=\"1.0\">\n" - "<qresource>\n"); + out << "<!DOCTYPE RCC><RCC version=\"1.0\">\n" + "<qresource>\n"_L1; - // use "." as dir to get relative file pathes - dumpRecursive(QDir(QLatin1String(".")), out); + // use "." as dir to get relative file paths + dumpRecursive(QDir("."_L1), out); - out << QLatin1String("</qresource>\n" - "</RCC>\n"); + out << "</qresource>\n" + "</RCC>\n"_L1; return 0; } @@ -105,11 +82,11 @@ QString makefileEscape(const QString &filepath) // Always use forward slashes QString result = QDir::cleanPath(filepath); // Spaces are escaped with a backslash - result.replace(QLatin1Char(' '), QLatin1String("\\ ")); + result.replace(u' ', "\\ "_L1); // Pipes are escaped with a backslash - result.replace(QLatin1Char('|'), QLatin1String("\\|")); + result.replace(u'|', "\\|"_L1); // Dollars are escaped with a dollar - result.replace(QLatin1Char('$'), QLatin1String("$$")); + result.replace(u'$', "$$"_L1); return result; } @@ -118,16 +95,16 @@ void writeDepFile(QIODevice &iodev, const QStringList &depsList, const QString & { QTextStream out(&iodev); out << qPrintable(makefileEscape(targetName)); - out << QLatin1Char(':'); + out << QChar(u':'); // Write depfile for (int i = 0; i < depsList.size(); ++i) { - out << QLatin1Char(' '); + out << QChar(u' '); out << qPrintable(makefileEscape(depsList.at(i))); } - out << QLatin1Char('\n'); + out << QChar(u'\n'); } int runRcc(int argc, char *argv[]) @@ -139,7 +116,7 @@ int runRcc(int argc, char *argv[]) // If you use this code as an example for a translated app, make sure to translate the strings. QCommandLineParser parser; parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); - parser.setApplicationDescription(QLatin1String("Qt Resource Compiler version " QT_VERSION_STR)); + parser.setApplicationDescription("Qt Resource Compiler version " QT_VERSION_STR ""_L1); parser.addHelpOption(); parser.addVersionOption(); @@ -233,9 +210,9 @@ int runRcc(int argc, char *argv[]) bool ok = false; formatVersion = parser.value(formatVersionOption).toUInt(&ok); if (!ok) { - errorMsg = QLatin1String("Invalid format version specified"); + errorMsg = "Invalid format version specified"_L1; } else if (formatVersion < 1 || formatVersion > 3) { - errorMsg = QLatin1String("Unsupported format version specified"); + errorMsg = "Unsupported format version specified"_L1; } } @@ -244,19 +221,22 @@ int runRcc(int argc, char *argv[]) library.setInitName(parser.value(nameOption)); if (parser.isSet(rootOption)) { library.setResourceRoot(QDir::cleanPath(parser.value(rootOption))); - if (library.resourceRoot().isEmpty() - || library.resourceRoot().at(0) != QLatin1Char('/')) - errorMsg = QLatin1String("Root must start with a /"); + if (library.resourceRoot().isEmpty() || library.resourceRoot().at(0) != u'/') + errorMsg = "Root must start with a /"_L1; } if (parser.isSet(compressionAlgoOption)) library.setCompressionAlgorithm(RCCResourceLibrary::parseCompressionAlgorithm(parser.value(compressionAlgoOption), &errorMsg)); - if (formatVersion < 3 && library.compressionAlgorithm() == RCCResourceLibrary::CompressionAlgorithm::Zstd) - errorMsg = QLatin1String("Zstandard compression requires format version 3 or higher"); - if (parser.isSet(nocompressOption)) - library.setCompressionAlgorithm(RCCResourceLibrary::CompressionAlgorithm::None); if (parser.isSet(noZstdOption)) library.setNoZstd(true); + if (library.compressionAlgorithm() == RCCResourceLibrary::CompressionAlgorithm::Zstd) { + if (formatVersion < 3) + errorMsg = "Zstandard compression requires format version 3 or higher"_L1; + if (library.noZstd()) + errorMsg = "--compression-algo=zstd and --no-zstd both specified."_L1; + } + if (parser.isSet(nocompressOption)) + library.setCompressionAlgorithm(RCCResourceLibrary::CompressionAlgorithm::None); if (parser.isSet(compressOption) && errorMsg.isEmpty()) { int level = library.parseCompressionLevel(library.compressionAlgorithm(), parser.value(compressOption), &errorMsg); library.setCompressLevel(level); @@ -267,25 +247,25 @@ int runRcc(int argc, char *argv[]) library.setFormat(RCCResourceLibrary::Binary); if (parser.isSet(generatorOption)) { auto value = parser.value(generatorOption); - if (value == QLatin1String("cpp")) { + if (value == "cpp"_L1) { library.setFormat(RCCResourceLibrary::C_Code); - } else if (value == QLatin1String("python")) { + } else if (value == "python"_L1) { library.setFormat(RCCResourceLibrary::Python_Code); - } else if (value == QLatin1String("python2")) { // ### fixme Qt 7: remove + } else if (value == "python2"_L1) { // ### fixme Qt 7: remove qWarning("Format python2 is no longer supported, defaulting to python."); library.setFormat(RCCResourceLibrary::Python_Code); } else { - errorMsg = QLatin1String("Invalid generator: ") + value; + errorMsg = "Invalid generator: "_L1 + value; } } if (parser.isSet(passOption)) { - if (parser.value(passOption) == QLatin1String("1")) + if (parser.value(passOption) == "1"_L1) library.setFormat(RCCResourceLibrary::Pass1); - else if (parser.value(passOption) == QLatin1String("2")) + else if (parser.value(passOption) == "2"_L1) library.setFormat(RCCResourceLibrary::Pass2); else - errorMsg = QLatin1String("Pass number must be 1 or 2"); + errorMsg = "Pass number must be 1 or 2"_L1; } if (parser.isSet(namespaceOption)) library.setUseNameSpace(!library.useNameSpace()); @@ -298,7 +278,7 @@ int runRcc(int argc, char *argv[]) const QStringList filenamesIn = parser.positionalArguments(); for (const QString &file : filenamesIn) { - if (file == QLatin1String("-")) + if (file == "-"_L1) continue; else if (!QFile::exists(file)) { qWarning("%s: File does not exist '%s'", argv[0], qPrintable(file)); @@ -323,7 +303,8 @@ int runRcc(int argc, char *argv[]) return 1; } QFile errorDevice; - errorDevice.open(stderr, QIODevice::WriteOnly|QIODevice::Text); + if (!errorDevice.open(stderr, QIODevice::WriteOnly|QIODevice::Text)) + return 1; if (library.verbose()) errorDevice.write("Qt resource compiler\n"); @@ -350,7 +331,7 @@ int runRcc(int argc, char *argv[]) } - if (outFilename.isEmpty() || outFilename == QLatin1String("-")) { + if (outFilename.isEmpty() || outFilename == "-"_L1) { #ifdef Q_OS_WIN // Make sure fwrite to stdout doesn't do LF->CRLF if (library.format() == RCCResourceLibrary::Binary) @@ -361,7 +342,12 @@ int runRcc(int argc, char *argv[]) mode &= ~QIODevice::Text; #endif // Q_OS_WIN // using this overload close() only flushes. - out.open(stdout, mode); + if (!out.open(stdout, mode)) { + const QString msg = QString::fromLatin1("Unable to open standard output for writing: %1\n") + .arg(out.errorString()); + errorDevice.write(msg.toUtf8()); + return 1; + } } else { out.setFileName(outFilename); if (!out.open(mode)) { @@ -398,7 +384,7 @@ int runRcc(int argc, char *argv[]) QFile depout; depout.setFileName(depFilename); - if (outFilename.isEmpty() || outFilename == QLatin1String("-")) { + if (outFilename.isEmpty() || outFilename == "-"_L1) { const QString msg = QString::fromUtf8("Unable to write depfile when outputting to stdout!\n"); errorDevice.write(msg.toUtf8()); return 1; diff --git a/src/tools/rcc/rcc.cpp b/src/tools/rcc/rcc.cpp index 1825c77b53..444e9c4ae5 100644 --- a/src/tools/rcc/rcc.cpp +++ b/src/tools/rcc/rcc.cpp @@ -1,31 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2018 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "rcc.h" @@ -33,7 +8,7 @@ #include <qdatetime.h> #include <qdebug.h> #include <qdir.h> -#include <qdiriterator.h> +#include <qdirlisting.h> #include <qfile.h> #include <qiodevice.h> #include <qlocale.h> @@ -50,6 +25,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + enum { CONSTANT_USENAMESPACE = 1, CONSTANT_COMPRESSLEVEL_DEFAULT = -1, @@ -58,14 +35,6 @@ enum { CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 }; -#if QT_CONFIG(zstd) && QT_VERSION >= QT_VERSION_CHECK(6,0,0) -# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zstd -#elif !defined(QT_NO_COMPRESS) -# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zlib -#else -# define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::None -#endif - void RCCResourceLibrary::write(const char *str, int len) { int n = m_out.size(); @@ -106,15 +75,18 @@ public: CompressedZstd = 0x04 }; - RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(), - QLocale::Language language = QLocale::C, - QLocale::Territory territory = QLocale::AnyTerritory, - uint flags = NoFlags, - RCCResourceLibrary::CompressionAlgorithm compressAlgo = CONSTANT_COMPRESSALGO_DEFAULT, - int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT, - int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT, - bool noZstd = false); + + RCCFileInfo() = default; + RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language, + QLocale::Territory territory, uint flags, + RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, + int compressThreshold, bool noZstd, bool isEmpty); + ~RCCFileInfo(); + RCCFileInfo(const RCCFileInfo &) = delete; + RCCFileInfo &operator=(const RCCFileInfo &) = delete; + RCCFileInfo(RCCFileInfo &&) = default; + RCCFileInfo &operator=(RCCFileInfo &&other) = delete; QString resourceName() const; @@ -123,41 +95,40 @@ public: qint64 writeDataName(RCCResourceLibrary &, qint64 offset); void writeDataInfo(RCCResourceLibrary &lib); - int m_flags; + int m_flags = NoFlags; + QLocale::Language m_language = QLocale::C; + QLocale::Territory m_territory = QLocale::AnyTerritory; QString m_name; - QLocale::Language m_language; - QLocale::Territory m_territory; QFileInfo m_fileInfo; - RCCFileInfo *m_parent; + RCCFileInfo *m_parent = nullptr; QMultiHash<QString, RCCFileInfo *> m_children; - RCCResourceLibrary::CompressionAlgorithm m_compressAlgo; - int m_compressLevel; - int m_compressThreshold; - - qint64 m_nameOffset; - qint64 m_dataOffset; - qint64 m_childOffset; - bool m_noZstd; + + RCCResourceLibrary::CompressionAlgorithm m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Best; + int m_compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT; + int m_compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT; + bool m_noZstd = false; + bool m_isEmpty = false; + + qint64 m_nameOffset = 0; + qint64 m_dataOffset = 0; + qint64 m_childOffset = 0; }; -RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, - QLocale::Language language, QLocale::Territory territory, uint flags, - RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, int compressThreshold, - bool noZstd) +RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language, + QLocale::Territory territory, uint flags, + RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, + int compressThreshold, bool noZstd, bool isEmpty) + : m_flags(flags), + m_language(language), + m_territory(territory), + m_name(name), + m_fileInfo(fileInfo), + m_compressAlgo(compressAlgo), + m_compressLevel(compressLevel), + m_compressThreshold(compressThreshold), + m_noZstd(noZstd), + m_isEmpty(isEmpty) { - m_name = name; - m_fileInfo = fileInfo; - m_language = language; - m_territory = territory; - m_flags = flags; - m_parent = nullptr; - m_nameOffset = 0; - m_dataOffset = 0; - m_childOffset = 0; - m_compressAlgo = compressAlgo; - m_compressLevel = compressLevel; - m_compressThreshold = compressThreshold; - m_noZstd = noZstd; } RCCFileInfo::~RCCFileInfo() @@ -169,8 +140,9 @@ QString RCCFileInfo::resourceName() const { QString resource = m_name; for (RCCFileInfo *p = m_parent; p; p = p->m_parent) - resource = resource.prepend(p->m_name + QLatin1Char('/')); - return QLatin1Char(':') + resource; + resource = resource.prepend(p->m_name + u'/'); + resource.prepend(u':'); + return resource; } void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) @@ -229,7 +201,7 @@ void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) if (lib.formatVersion() >= 2) { // last modified time stamp - const QDateTime lastModified = m_fileInfo.lastModified(); + const QDateTime lastModified = m_fileInfo.lastModified(QTimeZone::UTC); quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0); static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong(); if (sourceDate != 0) @@ -256,14 +228,18 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, //capture the offset m_dataOffset = offset; + QByteArray data; - //find the data to be written - QFile file(m_fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) { - *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); - return 0; + if (!m_isEmpty) { + //find the data to be written + QFile file(m_fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) { + *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); + return 0; + } + + data = file.readAll(); } - QByteArray data = file.readAll(); // Check if compression is useful for this file if (data.size() != 0) { @@ -402,7 +378,7 @@ qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) } // write the length - lib.writeNumber2(m_name.length()); + lib.writeNumber2(m_name.size()); if (text || pass1) lib.writeString("\n "); else if (python) @@ -419,14 +395,14 @@ qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) // write the m_name const QChar *unicode = m_name.unicode(); - for (int i = 0; i < m_name.length(); ++i) { + for (int i = 0; i < m_name.size(); ++i) { lib.writeNumber2(unicode[i].unicode()); if ((text || pass1) && i % 16 == 0) lib.writeString("\n "); else if (python && i % 16 == 0) lib.writeString("\\\n"); } - offset += m_name.length()*2; + offset += m_name.size()*2; // done if (text || pass1) @@ -445,14 +421,15 @@ qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) /////////////////////////////////////////////////////////// RCCResourceLibrary::Strings::Strings() : - TAG_RCC(QLatin1String("RCC")), - TAG_RESOURCE(QLatin1String("qresource")), - TAG_FILE(QLatin1String("file")), - ATTRIBUTE_LANG(QLatin1String("lang")), - ATTRIBUTE_PREFIX(QLatin1String("prefix")), - ATTRIBUTE_ALIAS(QLatin1String("alias")), - ATTRIBUTE_THRESHOLD(QLatin1String("threshold")), - ATTRIBUTE_COMPRESS(QLatin1String("compress")), + TAG_RCC("RCC"_L1), + TAG_RESOURCE("qresource"_L1), + TAG_FILE("file"_L1), + ATTRIBUTE_LANG("lang"_L1), + ATTRIBUTE_PREFIX("prefix"_L1), + ATTRIBUTE_ALIAS("alias"_L1), + ATTRIBUTE_EMPTY("empty"_L1), + ATTRIBUTE_THRESHOLD("threshold"_L1), + ATTRIBUTE_COMPRESS("compress"_L1), ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm")) { } @@ -461,7 +438,7 @@ RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion) : m_root(nullptr), m_format(C_Code), m_verbose(false), - m_compressionAlgo(CONSTANT_COMPRESSALGO_DEFAULT), + m_compressionAlgo(CompressionAlgorithm::Best), m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), m_treeOffset(0), @@ -495,11 +472,22 @@ enum RCCXmlTag { }; Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE); +static bool parseBoolean(QStringView value, QString *errorMsg) +{ + if (value.compare("true"_L1, Qt::CaseInsensitive) == 0) + return true; + if (value.compare("false"_L1, Qt::CaseInsensitive) == 0) + return false; + + *errorMsg = QString::fromLatin1("Invalid value for boolean attribute: '%1'").arg(value); + return false; +} + bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, const QString &fname, QString currentPath, bool listMode) { Q_ASSERT(m_errorDevice); - const QChar slash = QLatin1Char('/'); + const QChar slash = u'/'; if (!currentPath.isEmpty() && !currentPath.endsWith(slash)) currentPath += slash; @@ -510,6 +498,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, QLocale::Language language = QLocale::c().language(); QLocale::Territory territory = QLocale::c().territory(); QString alias; + bool empty = false; auto compressAlgo = m_compressionAlgo; int compressLevel = m_compressLevel; int compressThreshold = m_compressThreshold; @@ -520,12 +509,12 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, case QXmlStreamReader::StartElement: if (reader.name() == m_strings.TAG_RCC) { if (!tokens.isEmpty()) - reader.raiseError(QLatin1String("expected <RCC> tag")); + reader.raiseError("expected <RCC> tag"_L1); else tokens.push(RccTag); } else if (reader.name() == m_strings.TAG_RESOURCE) { if (tokens.isEmpty() || tokens.top() != RccTag) { - reader.raiseError(QLatin1String("unexpected <RESOURCE> tag")); + reader.raiseError("unexpected <RESOURCE> tag"_L1); } else { tokens.push(ResourceTag); @@ -537,7 +526,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString(); QLocale lang = QLocale(attribute); language = lang.language(); - if (2 == attribute.length()) { + if (2 == attribute.size()) { // Language only territory = QLocale::AnyTerritory; } else { @@ -555,7 +544,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, } } else if (reader.name() == m_strings.TAG_FILE) { if (tokens.isEmpty() || tokens.top() != ResourceTag) { - reader.raiseError(QLatin1String("unexpected <FILE> tag")); + reader.raiseError("unexpected <FILE> tag"_L1); } else { tokens.push(FileTag); @@ -569,6 +558,11 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, compressThreshold = m_compressThreshold; QString errorString; + if (attributes.hasAttribute(m_strings.ATTRIBUTE_EMPTY)) + empty = parseBoolean(attributes.value(m_strings.ATTRIBUTE_EMPTY), &errorString); + else + empty = false; + if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO)) compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString); if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) { @@ -587,7 +581,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, reader.raiseError(errorString); } } else { - reader.raiseError(QString(QLatin1String("unexpected tag: %1")).arg(reader.name().toString())); + reader.raiseError("unexpected tag: %1"_L1.arg(reader.name().toString())); } break; @@ -596,17 +590,17 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, if (!tokens.isEmpty() && tokens.top() == RccTag) tokens.pop(); else - reader.raiseError(QLatin1String("unexpected closing tag")); + reader.raiseError("unexpected closing tag"_L1); } else if (reader.name() == m_strings.TAG_RESOURCE) { if (!tokens.isEmpty() && tokens.top() == ResourceTag) tokens.pop(); else - reader.raiseError(QLatin1String("unexpected closing tag")); + reader.raiseError("unexpected closing tag"_L1); } else if (reader.name() == m_strings.TAG_FILE) { if (!tokens.isEmpty() && tokens.top() == FileTag) tokens.pop(); else - reader.raiseError(QLatin1String("unexpected closing tag")); + reader.raiseError("unexpected closing tag"_L1); } break; @@ -614,7 +608,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, if (reader.isWhitespace()) break; if (tokens.isEmpty() || tokens.top() != FileTag) { - reader.raiseError(QLatin1String("unexpected text")); + reader.raiseError("unexpected text"_L1); } else { QString fileName = reader.text().toString(); if (fileName.isEmpty()) { @@ -626,7 +620,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, alias = fileName; alias = QDir::cleanPath(alias); - while (alias.startsWith(QLatin1String("../"))) + while (alias.startsWith("../"_L1)) alias.remove(0, 3); alias = QDir::cleanPath(m_resourceRoot) + prefix + alias; @@ -640,13 +634,12 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, alias += slash; QStringList filePaths; - QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); - while (it.hasNext()) { - it.next(); - if (it.fileName() == QLatin1String(".") - || it.fileName() == QLatin1String("..")) + using F = QDirListing::IteratorFlag; + for (const auto &entry : QDirListing(dir, F::FollowSymlinks | F::Recursive)) { + const QString &fileName = entry.fileName(); + if (fileName == "."_L1 || fileName == ".."_L1) continue; - filePaths.append(it.filePath()); + filePaths.emplace_back(entry.filePath()); } // make rcc output deterministic @@ -660,7 +653,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, child.isDir() ? RCCFileInfo::Directory : RCCFileInfo::NoFlags, compressAlgo, compressLevel, compressThreshold, - m_noZstd)); + m_noZstd, empty)); if (!arc) m_failedResources.push_back(child.fileName()); } @@ -675,7 +668,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, compressAlgo, compressLevel, compressThreshold, - m_noZstd) + m_noZstd, empty) ); if (!arc) m_failedResources.push_back(absFileName); @@ -714,15 +707,15 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, m_errorDevice->write(msg.toUtf8()); if (!listMode && m_format == Binary) { // create dummy entry, otherwise loading with QResource will crash - m_root = new RCCFileInfo(QString(), QFileInfo(), - QLocale::C, QLocale::AnyTerritory, RCCFileInfo::Directory); + m_root = new RCCFileInfo{}; + m_root->m_flags = RCCFileInfo::Directory; } } return true; } -bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) +bool RCCResourceLibrary::addFile(const QString &alias, RCCFileInfo file) { Q_ASSERT(m_errorDevice); if (file.m_fileInfo.size() > 0xffffffff) { @@ -730,17 +723,21 @@ bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) m_errorDevice->write(msg.toUtf8()); return false; } - if (!m_root) - m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyTerritory, RCCFileInfo::Directory); + if (!m_root) { + m_root = new RCCFileInfo{}; + m_root->m_flags = RCCFileInfo::Directory; + } RCCFileInfo *parent = m_root; - const QStringList nodes = alias.split(QLatin1Char('/')); + const QStringList nodes = alias.split(u'/'); for (int i = 1; i < nodes.size()-1; ++i) { const QString node = nodes.at(i); if (node.isEmpty()) continue; if (!parent->m_children.contains(node)) { - RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyTerritory, RCCFileInfo::Directory); + RCCFileInfo *s = new RCCFileInfo{}; + s->m_name = node; + s->m_flags = RCCFileInfo::Directory; s->m_parent = parent; parent->m_children.insert(node, s); parent = s; @@ -750,14 +747,14 @@ bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) } const QString filename = nodes.at(nodes.size()-1); - RCCFileInfo *s = new RCCFileInfo(file); + RCCFileInfo *s = new RCCFileInfo(std::move(file)); s->m_parent = parent; auto cbegin = parent->m_children.constFind(filename); auto cend = parent->m_children.constEnd(); for (auto it = cbegin; it != cend; ++it) { if (it.key() == filename && it.value()->m_language == s->m_language && it.value()->m_territory == s->m_territory) { - for (const QString &name : qAsConst(m_fileNames)) { + for (const QString &name : std::as_const(m_fileNames)) { qWarning("%s: Warning: potential duplicate alias detected: '%s'", qPrintable(name), qPrintable(filename)); } @@ -793,8 +790,8 @@ bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice) QFile fileIn; QString fname = m_fileNames.at(i); QString pwd; - if (fname == QLatin1String("-")) { - fname = QLatin1String("(stdin)"); + if (fname == "-"_L1) { + fname = "(stdin)"_L1; pwd = QDir::currentPath(); fileIn.setFileName(fname); if (!fileIn.open(stdin, QIODevice::ReadOnly)) { @@ -845,7 +842,7 @@ QStringList RCCResourceLibrary::dataFiles() const // Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) { - const QChar slash = QLatin1Char('/'); + const QChar slash = u'/'; const auto cend = m_root->m_children.constEnd(); for (auto it = m_root->m_children.constBegin(); it != cend; ++it) { const RCCFileInfo *child = it.value(); @@ -862,27 +859,27 @@ RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap( { ResourceDataFileMap rc; if (m_root) - resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')), rc); + resourceDataFileMapRecursion(m_root, QString(u':'), rc); return rc; } RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg) { - if (value == QLatin1String("best")) + if (value == "best"_L1) return CompressionAlgorithm::Best; - if (value == QLatin1String("zlib")) { + if (value == "zlib"_L1) { #ifdef QT_NO_COMPRESS - *errorMsg = QLatin1String("zlib support not compiled in"); + *errorMsg = "zlib support not compiled in"_L1; #else return CompressionAlgorithm::Zlib; #endif - } else if (value == QLatin1String("zstd")) { + } else if (value == "zstd"_L1) { #if QT_CONFIG(zstd) return CompressionAlgorithm::Zstd; #else - *errorMsg = QLatin1String("Zstandard support not compiled in"); + *errorMsg = "Zstandard support not compiled in"_L1; #endif - } else if (value != QLatin1String("none")) { + } else if (value != "none"_L1) { *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'").arg(value); } @@ -932,13 +929,17 @@ bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIO m_errorDevice->write("No data signature found\n"); return false; } + + if (c != pattern[i]) { + for (int k = 0; k < i; ++k) + outDevice.putChar(pattern[k]); + i = 0; + } + if (c == pattern[i]) { ++i; } else { - for (int k = 0; k < i; ++k) - outDevice.putChar(pattern[k]); outDevice.putChar(c); - i = 0; } } @@ -1097,6 +1098,10 @@ bool RCCResourceLibrary::writeHeader() writeString("\n**\n"); writeString("** WARNING! All changes made in this file will be lost!\n"); writeString( "*****************************************************************************/\n\n"); + writeString("#ifdef _MSC_VER\n" + "// disable informational message \"function ... selected for automatic inline expansion\"\n" + "#pragma warning (disable: 4711)\n" + "#endif\n\n"); break; case Python_Code: writeString("# Resource object code (Python 3)\n"); @@ -1348,7 +1353,7 @@ bool RCCResourceLibrary::writeInitializer() //write("\nQT_BEGIN_NAMESPACE\n"); QString initNameStr = m_initName; if (!initNameStr.isEmpty()) { - initNameStr.prepend(QLatin1Char('_')); + initNameStr.prepend(u'_'); auto isAsciiLetterOrNumber = [] (QChar c) -> bool { ushort ch = c.unicode(); return (ch >= '0' && ch <= '9') || @@ -1358,7 +1363,7 @@ bool RCCResourceLibrary::writeInitializer() }; for (QChar &c : initNameStr) { if (!isAsciiLetterOrNumber(c)) - c = QLatin1Char('_'); + c = u'_'; } } QByteArray initName = initNameStr.toLatin1(); @@ -1377,7 +1382,9 @@ bool RCCResourceLibrary::writeInitializer() "# define QT_RCC_MANGLE_NAMESPACE(name) name\n" "#endif\n\n"); - writeString("#ifdef QT_NAMESPACE\n" + writeString("#if defined(QT_INLINE_NAMESPACE)\n" + "inline namespace QT_NAMESPACE {\n" + "#elif defined(QT_NAMESPACE)\n" "namespace QT_NAMESPACE {\n" "#endif\n\n"); } @@ -1478,6 +1485,11 @@ bool RCCResourceLibrary::writeInitializer() writeString(" return 1;\n"); writeString("}\n\n"); + // -Wexit-time-destructors was added to clang 3.0.0 in 2011. + writeString("#ifdef __clang__\n" + "# pragma clang diagnostic push\n" + "# pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n" + "#endif\n\n"); writeString("namespace {\n" " struct initializer {\n"); @@ -1490,7 +1502,12 @@ bool RCCResourceLibrary::writeInitializer() " ~initializer() { " + cleanResources + "(); }\n"); } writeString(" } dummy;\n" - "}\n"); + "}\n\n"); + + writeString("#ifdef __clang__\n" + "# pragma clang diagnostic pop\n" + "#endif\n"); + } else if (m_format == Binary) { int i = 4; diff --git a/src/tools/rcc/rcc.h b/src/tools/rcc/rcc.h index b6fcb21f5f..60af1c67cf 100644 --- a/src/tools/rcc/rcc.h +++ b/src/tools/rcc/rcc.h @@ -1,31 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Copyright (C) 2018 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc_p.h) @@ -121,13 +96,14 @@ private: const QString ATTRIBUTE_LANG; const QString ATTRIBUTE_PREFIX; const QString ATTRIBUTE_ALIAS; + const QString ATTRIBUTE_EMPTY; const QString ATTRIBUTE_THRESHOLD; const QString ATTRIBUTE_COMPRESS; const QString ATTRIBUTE_COMPRESSALGO; }; friend class RCCFileInfo; void reset(); - bool addFile(const QString &alias, const RCCFileInfo &file); + bool addFile(const QString &alias, RCCFileInfo file); bool interpretResourceFile(QIODevice *inputDevice, const QString &file, QString currentPath = QString(), bool listMode = false); bool writeHeader(); diff --git a/src/tools/shared/depfile_shared.h b/src/tools/shared/depfile_shared.h index 9fb9bbb863..411a7a1110 100644 --- a/src/tools/shared/depfile_shared.h +++ b/src/tools/shared/depfile_shared.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef QTBASE_DEPFILE_SHARED_H #define QTBASE_DEPFILE_SHARED_H diff --git a/src/tools/shared/shellquote_shared.h b/src/tools/shared/shellquote_shared.h new file mode 100644 index 0000000000..7a9ab691da --- /dev/null +++ b/src/tools/shared/shellquote_shared.h @@ -0,0 +1,82 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SHELLQUOTE_SHARED_H +#define SHELLQUOTE_SHARED_H + +#include <QDir> +#include <QRegularExpression> +#include <QString> + +// Copy-pasted from qmake/library/ioutil.cpp +inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16]) +{ + for (int x = arg.size() - 1; x >= 0; --x) { + ushort c = arg.unicode()[x].unicode(); + if ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)))) + return true; + } + return false; +} + +static QString shellQuoteUnix(const QString &arg) +{ + // Chars that should be quoted (TM). This includes: + static const uchar iqm[] = { + 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, + 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 + }; // 0-32 \'"$`<>|;&(){}*?#!~[] + + if (!arg.size()) + return QLatin1String("\"\""); + + QString ret(arg); + if (hasSpecialChars(ret, iqm)) { + ret.replace(QLatin1Char('\''), QLatin1String("'\\''")); + ret.prepend(QLatin1Char('\'')); + ret.append(QLatin1Char('\'')); + } + return ret; +} + +static QString shellQuoteWin(const QString &arg) +{ + // Chars that should be quoted (TM). This includes: + // - control chars & space + // - the shell meta chars "&()<>^| + // - the potential separators ,;= + static const uchar iqm[] = { + 0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 + }; + + if (!arg.size()) + return QLatin1String("\"\""); + + QString ret(arg); + if (hasSpecialChars(ret, iqm)) { + // Quotes are escaped and their preceding backslashes are doubled. + // It's impossible to escape anything inside a quoted string on cmd + // level, so the outer quoting must be "suspended". + ret.replace(QRegularExpression(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\"")); + // The argument must not end with a \ since this would be interpreted + // as escaping the quote -- rather put the \ behind the quote: e.g. + // rather use "foo"\ than "foo\" + int i = ret.size(); + while (i > 0 && ret.at(i - 1) == QLatin1Char('\\')) + --i; + ret.insert(i, QLatin1Char('"')); + ret.prepend(QLatin1Char('"')); + } + return ret; +} + +static QString shellQuote(const QString &arg) +{ + if (QDir::separator() == QLatin1Char('\\')) + return shellQuoteWin(arg); + else + return shellQuoteUnix(arg); +} + +#endif // SHELLQUOTE_SHARED_H diff --git a/src/tools/syncqt/CMakeLists.txt b/src/tools/syncqt/CMakeLists.txt new file mode 100644 index 0000000000..b3ab091aa4 --- /dev/null +++ b/src/tools/syncqt/CMakeLists.txt @@ -0,0 +1,80 @@ +if(NOT QT_INTERNAL_AVOID_OVERRIDING_SYNCQT_CONFIG) + qt_internal_get_configs_for_flag_manipulation(configs) + qt_internal_remove_known_optimization_flags(LANGUAGES CXX CONFIGS ${configs}) + # The /RTC1 flag is present in the default DEBUG flags list and contradicts -O2 but is not + # removed by qt_internal_remove_known_optimization_flags + qt_internal_remove_compiler_flags("/RTC1" LANGUAGES CXX CONFIGS ${configs}) + qt_internal_get_optimize_full_flags(optimize_full_flags) + qt_internal_add_compiler_flags(LANGUAGES CXX CONFIGS ${configs} FLAGS "${optimize_full_flags}") + + if(MSVC) + qt_internal_add_compiler_flags(LANGUAGES CXX CONFIGS ${configs} FLAGS "/EHsc") + else() + qt_internal_add_compiler_flags(LANGUAGES CXX CONFIGS ${configs} FLAGS "-fexceptions") + endif() + + # Replace all linker flags with those we use in the RelWithDebInfo configuration + list(REMOVE_ITEM configs RELWITHDEBINFO) + foreach(config IN LISTS configs) + set(CMAKE_EXE_LINKER_FLAGS_${config} "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}") + endforeach() + qt_internal_skip_sanitizer() +endif() + +set(compile_definitions + QT_VERSION_STR="${PROJECT_VERSION}" + QT_VERSION_MAJOR=${PROJECT_VERSION_MAJOR} + QT_VERSION_MINOR=${PROJECT_VERSION_MINOR} + QT_VERSION_PATCH=${PROJECT_VERSION_PATCH} + QT_NAMESPACE="${QT_NAMESPACE}" +) + +qt_get_tool_target_name(target_name syncqt) +if(NOT QT_SYNC_HEADERS_AT_CONFIGURE_TIME) + qt_internal_add_tool(${target_name} + DEFINES ${compile_definitions} + EXCEPTIONS + TOOLS_TARGET Core + CORE_LIBRARY None + INSTALL_DIR "${INSTALL_LIBEXECDIR}" + SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp" + ) +else() + set(config_type "") + if(NOT QT_INTERNAL_AVOID_OVERRIDING_SYNCQT_CONFIG) + set(config_type CONFIG RelWithDebInfo) + endif() + + if(CMAKE_OSX_ARCHITECTURES) + set(osx_architectures "-DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}") + endif() + + if(CMAKE_OSX_SYSROOT) + set(osx_sysroot "-DCMAKE_OSX_SYSROOT:STRING=${CMAKE_OSX_SYSROOT}") + endif() + # Note: configure-time tools reserve the original tool name for the imported executable. + # To re-build syncqt use 'syncqt_build' target. + qt_internal_add_configure_time_tool(${target_name} + DEFINES ${compile_definitions} + TOOLS_TARGET Core + INSTALL_DIRECTORY "${INSTALL_LIBEXECDIR}" + CMAKE_FLAGS + -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=TRUE + -DCMAKE_CXX_STANDARD:STRING=17 + # std::filesystem API is only available in macOS 10.15+ + -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.15 + "${osx_architectures}" + "${osx_sysroot}" + SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp" + ${config_type} + ) +endif() + +# Needs to be called at the end after all relevant target have created +# when using qt_internal_add_tool. +# Doesn't work if QT_SYNC_HEADERS_AT_CONFIGURE_TIME is TRUE. +if(NOT QT_INTERNAL_AVOID_OVERRIDING_SYNCQT_CONFIG) + qt_internal_skip_linking_sanitizer() +endif() diff --git a/src/tools/syncqt/main.cpp b/src/tools/syncqt/main.cpp new file mode 100644 index 0000000000..5df7b03fd5 --- /dev/null +++ b/src/tools/syncqt/main.cpp @@ -0,0 +1,1844 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +/* + * The tool generates deployment artifacts for the Qt builds such as: + * - CaMeL case header files named by public C++ symbols located in public module header files + * - Header file that contains the module version information, and named as <module>Vesion + * - LD version script if applicable + * - Aliases or copies of the header files sorted by the generic Qt-types: public/private/qpa + * and stored in the corresponding directories. + * Also the tool executes conformity checks on each header file if applicable, to make sure they + * follow rules that are relevant for their header type. + * The tool can be run in two modes: with either '-all' or '-headers' options specified. Depending + * on the selected mode, the tool either scans the filesystem to find header files or use the + * pre-defined list of header files. + */ + +#include <iostream> +#include <fstream> +#include <string> +#include <string_view> +#include <cstring> +#include <sstream> +#include <filesystem> +#include <unordered_map> +#include <vector> +#include <regex> +#include <map> +#include <set> +#include <stdexcept> +#include <array> + +enum ErrorCodes { + NoError = 0, + InvalidArguments, + SyncFailed, +}; + +// Enum contains the list of checks that can be executed on header files. +enum HeaderChecks { + NoChecks = 0, + NamespaceChecks = 1, /* Checks if header file is wrapped with QT_<BEGIN|END>_NAMESPACE macros */ + PrivateHeaderChecks = 2, /* Checks if the public header includes a private header */ + IncludeChecks = 4, /* Checks if the real header file but not an alias is included */ + WeMeantItChecks = 8, /* Checks if private header files contains 'We meant it' disclaimer */ + PragmaOnceChecks = 16, + /* Checks that lead to the fatal error of the sync process: */ + CriticalChecks = PrivateHeaderChecks | PragmaOnceChecks, + AllChecks = NamespaceChecks | CriticalChecks | IncludeChecks | WeMeantItChecks, +}; + +constexpr int LinkerScriptCommentAlignment = 55; + +static const std::regex GlobalHeaderRegex("^q(.*)global\\.h$"); + +constexpr std::string_view ErrorMessagePreamble = "ERROR: "; +constexpr std::string_view WarningMessagePreamble = "WARNING: "; + +// This comparator is used to sort include records in master header. +// It's used to put q.*global.h file to the top of the list and sort all other files alphabetically. +bool MasterHeaderIncludeComparator(const std::string &a, const std::string &b) +{ + std::smatch amatch; + std::smatch bmatch; + + if (std::regex_match(a, amatch, GlobalHeaderRegex)) { + if (std::regex_match(b, bmatch, GlobalHeaderRegex)) { + return amatch[1].str().empty() + || (!bmatch[1].str().empty() && amatch[1].str() < bmatch[1].str()); + } + return true; + } else if (std::regex_match(b, bmatch, GlobalHeaderRegex)) { + return false; + } + + return a < b; +}; + +namespace utils { +std::string asciiToLower(std::string s) +{ + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return (c >= 'A' && c <= 'Z') ? c | 0x20 : c; }); + return s; +} + +std::string asciiToUpper(std::string s) +{ + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return (c >= 'a' && c <= 'z') ? c & 0xdf : c; }); + return s; +} + +bool parseVersion(const std::string &version, int &major, int &minor) +{ + const size_t separatorPos = version.find('.'); + if (separatorPos == std::string::npos || separatorPos == (version.size() - 1) + || separatorPos == 0) + return false; + + try { + size_t pos = 0; + major = std::stoi(version.substr(0, separatorPos), &pos); + if (pos != separatorPos) + return false; + + const size_t nextPart = separatorPos + 1; + pos = 0; + minor = std::stoi(version.substr(nextPart), &pos); + if (pos != (version.size() - nextPart)) + return false; + } catch (const std::invalid_argument &) { + return false; + } catch (const std::out_of_range &) { + return false; + } + + return true; +} + +class DummyOutputStream : public std::ostream +{ + struct : public std::streambuf + { + int overflow(int c) override { return c; } + } buff; + +public: + DummyOutputStream() : std::ostream(&buff) { } +} DummyOutput; + +void printInternalError() +{ + std::cerr << "Internal error. Please create bugreport at https://bugreports.qt.io " + "using 'Build tools: Other component.'" + << std::endl; +} + +void printFilesystemError(const std::filesystem::filesystem_error &fserr, std::string_view errorMsg) +{ + std::cerr << errorMsg << ": " << fserr.path1() << ".\n" + << fserr.what() << "(" << fserr.code().value() << ")" << std::endl; +} + +std::filesystem::path normilizedPath(const std::string &path) +{ + try { + auto result = std::filesystem::path(std::filesystem::weakly_canonical(path).generic_string()); + return result; + } catch (const std::filesystem::filesystem_error &fserr) { + printFilesystemError(fserr, "Unable to normalize path"); + throw; + } +} + +bool createDirectories(const std::string &path, std::string_view errorMsg, bool *exists = nullptr) +{ + bool result = true; + try { + if (!std::filesystem::exists(path)) { + if (exists) + *exists = false; + std::filesystem::create_directories(path); + } else { + if (exists) + *exists = true; + } + } catch (const std::filesystem::filesystem_error &fserr) { + result = false; + std::cerr << errorMsg << ": " << path << ".\n" + << fserr.code().message() << "(" << fserr.code().value() << "):" << fserr.what() + << std::endl; + } + return result; +} + +} // namespace utils + +using FileStamp = std::filesystem::file_time_type; + +class CommandLineOptions +{ + template<typename T> + struct CommandLineOption + { + CommandLineOption(T *_value, bool _isOptional = false) + : value(_value), isOptional(_isOptional) + { + } + + T *value; + bool isOptional; + }; + +public: + CommandLineOptions(int argc, char *argv[]) : m_isValid(parseArguments(argc, argv)) { } + + bool isValid() const { return m_isValid; } + + const std::string &moduleName() const { return m_moduleName; } + + const std::string &sourceDir() const { return m_sourceDir; } + + const std::string &binaryDir() const { return m_binaryDir; } + + const std::string &includeDir() const { return m_includeDir; } + + const std::string &privateIncludeDir() const { return m_privateIncludeDir; } + + const std::string &qpaIncludeDir() const { return m_qpaIncludeDir; } + + const std::string &rhiIncludeDir() const { return m_rhiIncludeDir; } + + const std::string &ssgIncludeDir() const { return m_ssgIncludeDir; } + + const std::string &stagingDir() const { return m_stagingDir; } + + const std::string &versionScriptFile() const { return m_versionScriptFile; } + + const std::set<std::string> &knownModules() const { return m_knownModules; } + + const std::regex &qpaHeadersRegex() const { return m_qpaHeadersRegex; } + + const std::regex &rhiHeadersRegex() const { return m_rhiHeadersRegex; } + + const std::regex &ssgHeadersRegex() const { return m_ssgHeadersRegex; } + + const std::regex &privateHeadersRegex() const { return m_privateHeadersRegex; } + + const std::regex &publicNamespaceRegex() const { return m_publicNamespaceRegex; } + + const std::set<std::string> &headers() const { return m_headers; } + + const std::set<std::string> &generatedHeaders() const { return m_generatedHeaders; } + + bool scanAllMode() const { return m_scanAllMode; } + + bool isInternal() const { return m_isInternal; } + + bool isNonQtModule() const { return m_isNonQtModule; } + + bool printHelpOnly() const { return m_printHelpOnly; } + + bool debug() const { return m_debug; } + + bool copy() const { return m_copy; } + + bool minimal() const { return m_minimal; } + + bool showOnly() const { return m_showOnly; } + + bool warningsAreErrors() const { return m_warningsAreErrors; } + + void printHelp() const + { + std::cout << "Usage: syncqt -sourceDir <dir> -binaryDir <dir> -module <module name>" + " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir> -rhiIncludeDir <dir> -ssgIncludeDir <dir>" + " -stagingDir <dir> <-headers <header list>|-all> [-debug]" + " [-versionScript <path>] [-qpaHeadersFilter <regex>] [-rhiHeadersFilter <regex>]" + " [-knownModules <module1> <module2>... <moduleN>]" + " [-nonQt] [-internal] [-copy]\n" + "" + "Mandatory arguments:\n" + " -module Module name.\n" + " -headers List of header files.\n" + " -all In 'all' mode syncqt scans source\n" + " directory for public qt headers and\n" + " artifacts not considering CMake source\n" + " tree. The main use cases are the \n" + " generating of documentation and creating\n" + " API review changes.\n" + " -sourceDir Module source directory.\n" + " -binaryDir Module build directory.\n" + " -includeDir Module include directory where the\n" + " generated header files will be located.\n" + " -privateIncludeDir Module include directory for the\n" + " generated private header files.\n" + " -qpaIncludeDir Module include directory for the \n" + " generated QPA header files.\n" + " -rhiIncludeDir Module include directory for the \n" + " generated RHI header files.\n" + " -ssgIncludeDir Module include directory for the \n" + " generated SSG header files.\n" + " -stagingDir Temporary staging directory to collect\n" + " artifacts that need to be installed.\n" + " -knownModules list of known modules. syncqt uses the\n" + " list to check the #include macros\n" + " consistency.\n" + "Optional arguments:\n" + " -internal Indicates that the module is internal.\n" + " -nonQt Indicates that the module is not a Qt\n" + " module.\n" + " -privateHeadersFilter Regex that filters private header files\n" + " from the list of 'headers'.\n" + " -qpaHeadersFilter Regex that filters qpa header files from.\n" + " the list of 'headers'.\n" + " -rhiHeadersFilter Regex that filters rhi header files from.\n" + " the list of 'headers'.\n" + " -ssgHeadersFilter Regex that filters ssg files from.\n" + " the list of 'headers'.\n" + " -publicNamespaceFilter Symbols that are in the specified\n" + " namespace.\n" + " are treated as public symbols.\n" + " -versionScript Generate linker version script by\n" + " provided path.\n" + " -debug Enable debug output.\n" + " -copy Copy header files instead of creating\n" + " aliases.\n" + " -minimal Do not create CaMeL case headers for the\n" + " public C++ symbols.\n" + " -showonly Show actions, but not perform them.\n" + " -warningsAreErrors Treat all warnings as errors.\n" + " -help Print this help.\n"; + } + +private: + template<typename T> + [[nodiscard]] bool checkRequiredArguments(const std::unordered_map<std::string, T> &arguments) + { + bool ret = true; + for (const auto &argument : arguments) { + if (!argument.second.isOptional + && (!argument.second.value || argument.second.value->size()) == 0) { + std::cerr << "Missing argument: " << argument.first << std::endl; + ret = false; + } + } + return ret; + } + + [[nodiscard]] bool parseArguments(int argc, char *argv[]) + { + std::string qpaHeadersFilter; + std::string rhiHeadersFilter; + std::string ssgHeadersFilter; + std::string privateHeadersFilter; + std::string publicNamespaceFilter; + static std::unordered_map<std::string, CommandLineOption<std::string>> stringArgumentMap = { + { "-module", { &m_moduleName } }, + { "-sourceDir", { &m_sourceDir } }, + { "-binaryDir", { &m_binaryDir } }, + { "-privateHeadersFilter", { &privateHeadersFilter, true } }, + { "-qpaHeadersFilter", { &qpaHeadersFilter, true } }, + { "-rhiHeadersFilter", { &rhiHeadersFilter, true } }, + { "-ssgHeadersFilter", { &ssgHeadersFilter, true } }, + { "-includeDir", { &m_includeDir } }, + { "-privateIncludeDir", { &m_privateIncludeDir } }, + { "-qpaIncludeDir", { &m_qpaIncludeDir } }, + { "-rhiIncludeDir", { &m_rhiIncludeDir } }, + { "-ssgIncludeDir", { &m_ssgIncludeDir } }, + { "-stagingDir", { &m_stagingDir, true } }, + { "-versionScript", { &m_versionScriptFile, true } }, + { "-publicNamespaceFilter", { &publicNamespaceFilter, true } }, + }; + + static const std::unordered_map<std::string, CommandLineOption<std::set<std::string>>> + listArgumentMap = { + { "-headers", { &m_headers, true } }, + { "-generatedHeaders", { &m_generatedHeaders, true } }, + { "-knownModules", { &m_knownModules, true } }, + }; + + static const std::unordered_map<std::string, CommandLineOption<bool>> boolArgumentMap = { + { "-nonQt", { &m_isNonQtModule, true } }, { "-debug", { &m_debug, true } }, + { "-help", { &m_printHelpOnly, true } }, + { "-internal", { &m_isInternal, true } }, { "-all", { &m_scanAllMode, true } }, + { "-copy", { &m_copy, true } }, { "-minimal", { &m_minimal, true } }, + { "-showonly", { &m_showOnly, true } }, { "-showOnly", { &m_showOnly, true } }, + { "-warningsAreErrors", { &m_warningsAreErrors, true } } + }; + + std::string *currentValue = nullptr; + std::set<std::string> *currentListValue = nullptr; + + auto parseArgument = [¤tValue, ¤tListValue](const std::string &arg) -> bool { + if (arg[0] == '-') { + currentValue = nullptr; + currentListValue = nullptr; + { + auto it = stringArgumentMap.find(arg); + if (it != stringArgumentMap.end()) { + if (it->second.value == nullptr) { + utils::printInternalError(); + return false; + } + currentValue = it->second.value; + return true; + } + } + + { + auto it = boolArgumentMap.find(arg); + if (it != boolArgumentMap.end()) { + if (it->second.value == nullptr) { + utils::printInternalError(); + return false; + } + *(it->second.value) = true; + return true; + } + } + + { + auto it = listArgumentMap.find(arg); + if (it != listArgumentMap.end()) { + if (it->second.value == nullptr) { + utils::printInternalError(); + return false; + } + currentListValue = it->second.value; + currentListValue->insert(""); // Indicate that argument is provided + return true; + } + } + + std::cerr << "Unknown argument: " << arg << std::endl; + return false; + } + + if (currentValue != nullptr) { + *currentValue = arg; + currentValue = nullptr; + } else if (currentListValue != nullptr) { + currentListValue->insert(arg); + } else { + std::cerr << "Unknown argument: " << arg << std::endl; + return false; + } + return true; + }; + + for (int i = 1; i < argc; ++i) { + std::string arg(argv[i]); + if (arg.empty()) + continue; + + if (arg[0] == '@') { + std::ifstream ifs(arg.substr(1), std::ifstream::in); + if (!ifs.is_open()) { + std::cerr << "Unable to open rsp file: " << arg[0] << std::endl; + return false; + } + std::string argFromFile; + while (std::getline(ifs, argFromFile)) { + if (argFromFile.empty()) + continue; + if (!parseArgument(argFromFile)) + return false; + } + ifs.close(); + continue; + } + + if (!parseArgument(arg)) + return false; + } + + if (m_printHelpOnly) + return true; + + if (!qpaHeadersFilter.empty()) + m_qpaHeadersRegex = std::regex(qpaHeadersFilter); + + if (!rhiHeadersFilter.empty()) + m_rhiHeadersRegex = std::regex(rhiHeadersFilter); + + if (!ssgHeadersFilter.empty()) + m_ssgHeadersRegex = std::regex(ssgHeadersFilter); + + if (!privateHeadersFilter.empty()) + m_privateHeadersRegex = std::regex(privateHeadersFilter); + + if (!publicNamespaceFilter.empty()) + m_publicNamespaceRegex = std::regex(publicNamespaceFilter); + + if (m_headers.empty() && !m_scanAllMode) { + std::cerr << "You need to specify either -headers or -all option." << std::endl; + return false; + } + + if (!m_headers.empty() && m_scanAllMode) { + std::cerr << "Both -headers and -all are specified. Need to choose only one" + "operational mode." << std::endl; + return false; + } + + for (const auto &argument : listArgumentMap) + argument.second.value->erase(""); + + bool ret = true; + ret &= checkRequiredArguments(stringArgumentMap); + ret &= checkRequiredArguments(listArgumentMap); + + normilizePaths(); + + return ret; + } + + // Convert all paths from command line to a generic one. + void normilizePaths() + { + static std::array<std::string *, 8> paths = { + &m_sourceDir, &m_binaryDir, &m_includeDir, &m_privateIncludeDir, + &m_qpaIncludeDir, &m_rhiIncludeDir, &m_stagingDir, + &m_versionScriptFile, + }; + for (auto path : paths) { + if (!path->empty()) + *path = utils::normilizedPath(*path).generic_string(); + } + } + + std::string m_moduleName; + std::string m_sourceDir; + std::string m_binaryDir; + std::string m_includeDir; + std::string m_privateIncludeDir; + std::string m_qpaIncludeDir; + std::string m_rhiIncludeDir; + std::string m_ssgIncludeDir; + std::string m_stagingDir; + std::string m_versionScriptFile; + std::set<std::string> m_knownModules; + std::set<std::string> m_headers; + std::set<std::string> m_generatedHeaders; + bool m_scanAllMode = false; + bool m_copy = false; + bool m_isNonQtModule = false; + bool m_isInternal = false; + bool m_printHelpOnly = false; + bool m_debug = false; + bool m_minimal = false; + bool m_showOnly = false; + bool m_warningsAreErrors = false; + std::regex m_qpaHeadersRegex; + std::regex m_rhiHeadersRegex; + std::regex m_ssgHeadersRegex; + std::regex m_privateHeadersRegex; + std::regex m_publicNamespaceRegex; + + bool m_isValid; +}; + +class SyncScanner +{ + class SymbolDescriptor + { + public: + // Where the symbol comes from + enum SourceType { + Pragma = 0, // pragma qt_class is mentioned a header file + Declaration, // The symbol declaration inside a header file + MaxSourceType + }; + + void update(const std::string &file, SourceType type) + { + if (type < m_type) { + m_file = file; + m_type = type; + } + } + + // The file that contains a symbol. + const std::string &file() const { return m_file; } + + private: + SourceType m_type = MaxSourceType; + std::string m_file; + }; + using SymbolContainer = std::unordered_map<std::string, SymbolDescriptor>; + + struct ParsingResult + { + std::vector<std::string> versionScriptContent; + std::string requireConfig; + bool masterInclude = true; + }; + + CommandLineOptions *m_commandLineArgs = nullptr; + + std::map<std::string /* header file name */, std::string /* header feature guard name */, + decltype(MasterHeaderIncludeComparator) *> + m_masterHeaderContents; + + std::unordered_map<std::string /* the deprecated header name*/, + std::string /* the replacement */> + m_deprecatedHeaders; + std::vector<std::string> m_versionScriptContents; + std::set<std::string> m_producedHeaders; + std::vector<std::string> m_headerCheckExceptions; + SymbolContainer m_symbols; + std::ostream &scannerDebug() const + { + if (m_commandLineArgs->debug()) + return std::cout; + return utils::DummyOutput; + } + + enum { Active, Stopped, IgnoreNext, Ignore } m_versionScriptGeneratorState = Active; + + std::filesystem::path m_outputRootName; + std::filesystem::path m_currentFile; + std::string m_currentFilename; + std::string m_currentFileString; + size_t m_currentFileLineNumber = 0; + bool m_currentFileInSourceDir = false; + + enum FileType { PublicHeader = 0, PrivateHeader = 1, QpaHeader = 2, ExportHeader = 4, RhiHeader = 8, SsgHeader = 16 }; + unsigned int m_currentFileType = PublicHeader; + + int m_criticalChecks = CriticalChecks; + std::string_view m_warningMessagePreamble; + +public: + SyncScanner(CommandLineOptions *commandLineArgs) + : m_commandLineArgs(commandLineArgs), + m_masterHeaderContents(MasterHeaderIncludeComparator), + m_outputRootName( + std::filesystem::weakly_canonical(m_commandLineArgs->includeDir()).root_name()), + m_warningMessagePreamble(WarningMessagePreamble) + { + } + + // The function converts the relative path to a header files to the absolute. It also makes the + // path canonical(removes '..' and '.' parts of the path). The source directory passed in + // '-sourceDir' command line argument is used as base path for relative paths to create the + // absolute path. + [[nodiscard]] std::filesystem::path makeHeaderAbsolute(const std::string &filename) const; + + ErrorCodes sync() + { + if (m_commandLineArgs->warningsAreErrors()) { + m_criticalChecks = AllChecks; + m_warningMessagePreamble = ErrorMessagePreamble; + } + + m_versionScriptGeneratorState = + m_commandLineArgs->versionScriptFile().empty() ? Stopped : Active; + auto error = NoError; + + // In the scan all mode we ingore the list of header files that is specified in the + // '-headers' argument, and collect header files from the source directory tree. + if (m_commandLineArgs->scanAllMode()) { + for (auto const &entry : + std::filesystem::recursive_directory_iterator(m_commandLineArgs->sourceDir())) { + + const bool isRegularFile = entry.is_regular_file(); + const bool isHeaderFlag = isHeader(entry); + const bool isDocFileHeuristicFlag = + isDocFileHeuristic(entry.path().generic_string()); + const bool shouldProcessHeader = + isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag; + const std::string filePath = entry.path().generic_string(); + + if (shouldProcessHeader) { + scannerDebug() << "Processing header: " << filePath << std::endl; + if (!processHeader(makeHeaderAbsolute(filePath))) + error = SyncFailed; + } else { + scannerDebug() + << "Skipping processing header: " << filePath + << " isRegularFile: " << isRegularFile + << " isHeaderFlag: " << isHeaderFlag + << " isDocFileHeuristicFlag: " << isDocFileHeuristicFlag + << std::endl; + } + } + } else { + // Since the list of header file is quite big syncqt supports response files to avoid + // the issues with long command lines. + std::set<std::string> rspHeaders; + const auto &headers = m_commandLineArgs->headers(); + for (auto it = headers.begin(); it != headers.end(); ++it) { + const auto &header = *it; + scannerDebug() << "Processing header: " << header << std::endl; + if (!processHeader(makeHeaderAbsolute(header))) { + error = SyncFailed; + } + } + for (const auto &header : rspHeaders) { + scannerDebug() << "Processing header: " << header << std::endl; + if (!processHeader(makeHeaderAbsolute(header))) + error = SyncFailed; + } + } + + // No further processing in minimal mode. + if (m_commandLineArgs->minimal()) + return error; + + // Generate aliases for all unique symbols collected during the header files parsing. + for (auto it = m_symbols.begin(); it != m_symbols.end(); ++it) { + const std::string &filename = it->second.file(); + if (!filename.empty()) { + if (generateQtCamelCaseFileIfContentChanged( + m_commandLineArgs->includeDir() + '/' + it->first, filename)) { + m_producedHeaders.insert(it->first); + } else { + error = SyncFailed; + } + } + } + + // Generate the header file containing version information. + if (!m_commandLineArgs->isNonQtModule()) { + std::string moduleNameLower = utils::asciiToLower(m_commandLineArgs->moduleName()); + std::string versionHeaderFilename(moduleNameLower + "version.h"); + std::string versionHeaderCamel(m_commandLineArgs->moduleName() + "Version"); + std::string versionFile = m_commandLineArgs->includeDir() + '/' + versionHeaderFilename; + + std::error_code ec; + FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec); + if (ec) + originalStamp = FileStamp::clock::now(); + + if (generateVersionHeader(versionFile)) { + if (!generateAliasedHeaderFileIfTimestampChanged( + m_commandLineArgs->includeDir() + '/' + versionHeaderCamel, + versionHeaderFilename, originalStamp)) { + error = SyncFailed; + } + m_masterHeaderContents[versionHeaderFilename] = {}; + m_producedHeaders.insert(versionHeaderFilename); + m_producedHeaders.insert(versionHeaderCamel); + } else { + error = SyncFailed; + } + } + + if (!m_commandLineArgs->scanAllMode()) { + if (!m_commandLineArgs->isNonQtModule()) { + if (!generateDeprecatedHeaders()) + error = SyncFailed; + + if (!generateHeaderCheckExceptions()) + error = SyncFailed; + } + + if (!m_commandLineArgs->versionScriptFile().empty()) { + if (!generateLinkerVersionScript()) + error = SyncFailed; + } + } + + if (!m_commandLineArgs->isNonQtModule()) { + if (!generateMasterHeader()) + error = SyncFailed; + } + + if (!m_commandLineArgs->scanAllMode() && !m_commandLineArgs->stagingDir().empty()) { + // Copy the generated files to a spearate staging directory to make the installation + // process eaiser. + if (!copyGeneratedHeadersToStagingDirectory(m_commandLineArgs->stagingDir())) + error = SyncFailed; + } + return error; + } + + // The function copies files, that were generated while the sync procedure to a staging + // directory. This is necessary to simplify the installation of the generated files. + [[nodiscard]] bool copyGeneratedHeadersToStagingDirectory(const std::string &outputDirectory, + bool skipCleanup = false) + { + bool result = true; + bool outDirExists = false; + if (!utils::createDirectories(outputDirectory, "Unable to create staging directory", + &outDirExists)) + return false; + + if (outDirExists && !skipCleanup) { + try { + for (const auto &entry : + std::filesystem::recursive_directory_iterator(outputDirectory)) { + if (m_producedHeaders.find(entry.path().filename().generic_string()) + == m_producedHeaders.end()) { + // Check if header file came from another module as result of the + // cross-module deprecation before removing it. + std::string firstLine; + { + std::ifstream input(entry.path(), std::ifstream::in); + if (input.is_open()) { + std::getline(input, firstLine); + input.close(); + } + } + if (firstLine.find("#ifndef DEPRECATED_HEADER_" + + m_commandLineArgs->moduleName()) + == 0 + || firstLine.find("#ifndef DEPRECATED_HEADER_") != 0) + std::filesystem::remove(entry.path()); + } + } + } catch (const std::filesystem::filesystem_error &fserr) { + utils::printFilesystemError(fserr, "Unable to clean the staging directory"); + return false; + } + } + + for (const auto &header : m_producedHeaders) { + std::filesystem::path src(m_commandLineArgs->includeDir() + '/' + header); + std::filesystem::path dst(outputDirectory + '/' + header); + if (!m_commandLineArgs->showOnly()) + result &= updateOrCopy(src, dst); + } + return result; + } + + void resetCurrentFileInfoData(const std::filesystem::path &headerFile) + { + // This regex filters the generated '*exports.h' and '*exports_p.h' header files. + static const std::regex ExportsHeaderRegex("^q(.*)exports(_p)?\\.h$"); + + m_currentFile = headerFile; + m_currentFileLineNumber = 0; + m_currentFilename = m_currentFile.filename().generic_string(); + m_currentFileType = PublicHeader; + m_currentFileString = m_currentFile.generic_string(); + m_currentFileInSourceDir = m_currentFileString.find(m_commandLineArgs->sourceDir()) == 0; + + if (isHeaderPrivate(m_currentFilename)) + m_currentFileType = PrivateHeader; + + if (isHeaderQpa(m_currentFilename)) + m_currentFileType = QpaHeader | PrivateHeader; + + if (isHeaderRhi(m_currentFilename)) + m_currentFileType = RhiHeader | PrivateHeader; + + if (isHeaderSsg(m_currentFilename)) + m_currentFileType = SsgHeader | PrivateHeader; + + if (std::regex_match(m_currentFilename, ExportsHeaderRegex)) + m_currentFileType |= ExportHeader; + } + + [[nodiscard]] bool processHeader(const std::filesystem::path &headerFile) + { + // This regex filters any paths that contain the '3rdparty' directory. + static const std::regex ThirdPartyFolderRegex("(^|.+/)3rdparty/.+"); + + // This regex filters '-config.h' and '-config_p.h' header files. + static const std::regex ConfigHeaderRegex("^(q|.+-)config(_p)?\\.h"); + + resetCurrentFileInfoData(headerFile); + // We assume that header files ouside of the module source or build directories do not + // belong to the module. Skip any processing. + if (!m_currentFileInSourceDir + && m_currentFileString.find(m_commandLineArgs->binaryDir()) != 0) { + scannerDebug() << "Header file: " << headerFile + << " is outside the sync directories. Skipping." << std::endl; + m_headerCheckExceptions.push_back(m_currentFileString); + return true; + } + + // Check if a directory is passed as argument. That shouldn't happen, print error and exit. + if (m_currentFilename.empty()) { + std::cerr << "Header file name of " << m_currentFileString << "is empty" << std::endl; + return false; + } + + std::error_code ec; + FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec); + if (ec) + originalStamp = FileStamp::clock::now(); + ec.clear(); + + bool isPrivate = m_currentFileType & PrivateHeader; + bool isQpa = m_currentFileType & QpaHeader; + bool isRhi = m_currentFileType & RhiHeader; + bool isSsg = m_currentFileType & SsgHeader; + bool isExport = m_currentFileType & ExportHeader; + scannerDebug() + << "processHeader:start: " << headerFile + << " m_currentFilename: " << m_currentFilename + << " isPrivate: " << isPrivate + << " isQpa: " << isQpa + << " isRhi: " << isRhi + << " isSsg: " << isSsg + << std::endl; + + // Chose the directory where to generate the header aliases or to copy header file if + // the '-copy' argument is passed. + std::string outputDir = m_commandLineArgs->includeDir(); + if (isQpa) + outputDir = m_commandLineArgs->qpaIncludeDir(); + else if (isRhi) + outputDir = m_commandLineArgs->rhiIncludeDir(); + else if (isSsg) + outputDir = m_commandLineArgs->ssgIncludeDir(); + else if (isPrivate) + outputDir = m_commandLineArgs->privateIncludeDir(); + + if (!utils::createDirectories(outputDir, "Unable to create output directory")) + return false; + + bool headerFileExists = std::filesystem::exists(headerFile); + + std::string aliasedFilepath = headerFile.generic_string(); + + std::string aliasPath = outputDir + '/' + m_currentFilename; + + // If the '-copy' argument is passed, we copy the original file to a corresponding output + // directory otherwise we only create a header file alias that contains relative path to + // the original header file in the module source or build tree. + if (m_commandLineArgs->copy() && headerFileExists) { + if (!updateOrCopy(headerFile, aliasPath)) + return false; + } else { + if (!generateAliasedHeaderFileIfTimestampChanged(aliasPath, aliasedFilepath, + originalStamp)) + return false; + } + + // No further processing in minimal mode. + if (m_commandLineArgs->minimal()) + return true; + + // Stop processing if header files doesn't exist. This happens at configure time, since + // either header files are generated later than syncqt is running or header files only + // generated at build time. These files will be processed at build time, if CMake files + // contain the correct dependencies between the missing header files and the module + // 'sync_headers' targets. + if (!headerFileExists) { + scannerDebug() << "Header file: " << headerFile + << " doesn't exist, but is added to syncqt scanning. Skipping."; + return true; + } + + bool isGenerated = isHeaderGenerated(m_currentFileString); + + // Make sure that we detect the '3rdparty' directory inside the source directory only, + // since full path to the Qt sources might contain '/3rdparty/' too. + bool is3rdParty = std::regex_match( + std::filesystem::relative(headerFile, m_commandLineArgs->sourceDir()) + .generic_string(), + ThirdPartyFolderRegex); + + // No processing of generated Qt config header files. + if (!std::regex_match(m_currentFilename, ConfigHeaderRegex)) { + unsigned int skipChecks = m_commandLineArgs->scanAllMode() ? AllChecks : NoChecks; + + // Collect checks that should skipped for the header file. + if (m_commandLineArgs->isNonQtModule() || is3rdParty || isQpa || isRhi || isSsg + || !m_currentFileInSourceDir || isGenerated) { + skipChecks = AllChecks; + } else { + if (std::regex_match(m_currentFilename, GlobalHeaderRegex) || isExport) + skipChecks |= NamespaceChecks; + + if (isHeaderPCH(m_currentFilename)) + skipChecks |= WeMeantItChecks; + + if (isPrivate) { + skipChecks |= NamespaceChecks; + skipChecks |= PrivateHeaderChecks; + skipChecks |= IncludeChecks; + } else { + skipChecks |= WeMeantItChecks; + } + } + + ParsingResult parsingResult; + parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty + && !isQpa && !isRhi && !isSsg && !isPrivate && !isGenerated; + if (!parseHeader(headerFile, parsingResult, skipChecks)) { + scannerDebug() << "parseHeader failed: " << headerFile << std::endl; + return false; + } + + // Record the private header file inside the version script content. + if (isPrivate && !m_commandLineArgs->versionScriptFile().empty() + && !parsingResult.versionScriptContent.empty()) { + m_versionScriptContents.insert(m_versionScriptContents.end(), + parsingResult.versionScriptContent.begin(), + parsingResult.versionScriptContent.end()); + } + + // Add the '#if QT_CONFIG(<feature>)' check for header files that supposed to be + // included into the module master header only if corresponding feature is enabled. + bool willBeInModuleMasterHeader = false; + if (!isQpa && !isRhi && !isSsg && !isPrivate) { + if (m_currentFilename.find('_') == std::string::npos + && parsingResult.masterInclude) { + m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig; + willBeInModuleMasterHeader = true; + } + } + + scannerDebug() + << "processHeader:end: " << headerFile + << " is3rdParty: " << is3rdParty + << " isGenerated: " << isGenerated + << " m_currentFileInSourceDir: " << m_currentFileInSourceDir + << " willBeInModuleMasterHeader: " << willBeInModuleMasterHeader + << std::endl; + } else if (m_currentFilename == "qconfig.h") { + // Hardcode generating of QtConfig alias + updateSymbolDescriptor("QtConfig", "qconfig.h", SyncScanner::SymbolDescriptor::Pragma); + } + + return true; + } + + void parseVersionScriptContent(const std::string buffer, ParsingResult &result) + { + // This regex looks for the symbols that needs to be placed into linker version script. + static const std::regex VersionScriptSymbolRegex( + "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?"); + + // This regex looks for the namespaces that needs to be placed into linker version script. + static const std::regex VersionScriptNamespaceRegex( + "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*"); + + // This regex filters the tailing colon from the symbol name. + static const std::regex TrailingColonRegex("([\\w]+):$"); + + switch (m_versionScriptGeneratorState) { + case Ignore: + scannerDebug() << "line ignored: " << buffer << std::endl; + m_versionScriptGeneratorState = Active; + return; + case Stopped: + return; + case IgnoreNext: + m_versionScriptGeneratorState = Ignore; + break; + case Active: + break; + } + + if (buffer.empty()) + return; + + std::smatch match; + std::string symbol; + if (std::regex_match(buffer, match, VersionScriptSymbolRegex) && match[2].str().empty()) + symbol = match[1].str(); + else if (std::regex_match(buffer, match, VersionScriptNamespaceRegex)) + symbol = match[1].str(); + + if (std::regex_match(symbol, match, TrailingColonRegex)) + symbol = match[1].str(); + + // checkLineForSymbols(buffer, symbol); + if (!symbol.empty() && symbol[symbol.size() - 1] != ';') { + std::string relPath = m_currentFileInSourceDir + ? std::filesystem::relative(m_currentFile, m_commandLineArgs->sourceDir()) + .string() + : std::filesystem::relative(m_currentFile, m_commandLineArgs->binaryDir()) + .string(); + + std::string versionStringRecord = " *"; + size_t startPos = 0; + size_t endPos = 0; + while (endPos != std::string::npos) { + endPos = symbol.find("::", startPos); + size_t length = endPos != std::string::npos ? (endPos - startPos) + : (symbol.size() - startPos); + if (length > 0) { + std::string symbolPart = symbol.substr(startPos, length); + versionStringRecord += std::to_string(symbolPart.size()); + versionStringRecord += symbolPart; + } + startPos = endPos + 2; + } + versionStringRecord += "*;"; + if (versionStringRecord.size() < LinkerScriptCommentAlignment) + versionStringRecord += + std::string(LinkerScriptCommentAlignment - versionStringRecord.size(), ' '); + versionStringRecord += " # "; + versionStringRecord += relPath; + versionStringRecord += ":"; + versionStringRecord += std::to_string(m_currentFileLineNumber); + versionStringRecord += "\n"; + result.versionScriptContent.push_back(versionStringRecord); + } + } + + // The function parses 'headerFile' and collect artifacts that are used at generating step. + // 'timeStamp' is saved in internal structures to compare it when generating files. + // 'result' the function output value that stores the result of parsing. + // 'skipChecks' checks that are not applicable for the header file. + [[nodiscard]] bool parseHeader(const std::filesystem::path &headerFile, + ParsingResult &result, + unsigned int skipChecks) + { + if (m_commandLineArgs->showOnly()) + std::cout << headerFile << " [" << m_commandLineArgs->moduleName() << "]" << std::endl; + // This regex checks if line contains a macro. + static const std::regex MacroRegex("^\\s*#.*"); + + // The regex's bellow check line for known pragmas: + // + // - 'once' is not allowed in installed headers, so error out. + // + // - 'qt_sync_skip_header_check' avoid any header checks. + // + // - 'qt_sync_stop_processing' stops the header proccesing from a moment when pragma is + // found. Important note: All the parsing artifacts were found before this point are + // stored for further processing. + // + // - 'qt_sync_suspend_processing' pauses processing and skip lines inside a header until + // 'qt_sync_resume_processing' is found. 'qt_sync_stop_processing' stops processing if + // it's found before the 'qt_sync_resume_processing'. + // + // - 'qt_sync_resume_processing' resumes processing after 'qt_sync_suspend_processing'. + // + // - 'qt_class(<symbol>)' manually declares the 'symbol' that should be used to generate + // the CaMeL case header alias. + // + // - 'qt_deprecates([module/]<deprecated header file>[,<major.minor>])' indicates that + // this header file replaces the 'deprecated header file'. syncqt will create the + // deprecated header file' with the special deprecation content. Pragma optionally + // accepts the Qt version where file should be removed. If the current Qt version is + // higher than the deprecation version, syncqt displays deprecation warning and skips + // generating the deprecated header. If the module is specified and is different from + // the one this header file belongs to, syncqt attempts to generate header files + // for the specified module. Cross-module deprecation only works within the same repo. + // See the 'generateDeprecatedHeaders' function for details. + // + // - 'qt_no_master_include' indicates that syncqt should avoid including this header + // files into the module master header file. + static const std::regex OnceRegex(R"(^#\s*pragma\s+once$)"); + static const std::regex SkipHeaderCheckRegex("^#\\s*pragma qt_sync_skip_header_check$"); + static const std::regex StopProcessingRegex("^#\\s*pragma qt_sync_stop_processing$"); + static const std::regex SuspendProcessingRegex("^#\\s*pragma qt_sync_suspend_processing$"); + static const std::regex ResumeProcessingRegex("^#\\s*pragma qt_sync_resume_processing$"); + static const std::regex ExplixitClassPragmaRegex("^#\\s*pragma qt_class\\(([^\\)]+)\\)$"); + static const std::regex DeprecatesPragmaRegex("^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$"); + static const std::regex NoMasterIncludePragmaRegex("^#\\s*pragma qt_no_master_include$"); + + // This regex checks if header contains 'We mean it' disclaimer. All private headers should + // contain them. + static const std::string_view WeMeantItString("We mean it."); + + // The regex's check if the content of header files is wrapped with the Qt namespace macros. + static const std::regex BeginNamespaceRegex("^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$"); + static const std::regex EndNamespaceRegex("^QT_END_NAMESPACE(_[A-Z_]+)?$"); + + // This regex checks if line contains the include macro of the following formats: + // - #include <file> + // - #include "file" + // - # include <file> + static const std::regex IncludeRegex("^#\\s*include\\s*[<\"](.+)[>\"]"); + + // This regex checks if line contains namespace definition. + static const std::regex NamespaceRegex("\\s*namespace ([^ ]*)\\s+"); + + // This regex checks if line contains the Qt iterator declaration, that need to have + // CaMel case header alias. + static const std::regex DeclareIteratorRegex("^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$"); + + // This regex checks if header file contains the QT_REQUIRE_CONFIG call. + // The macro argument is used to wrap an include of the header file inside the module master + // header file with the '#if QT_CONFIG(<feature>)' guard. + static const std::regex RequireConfigRegex("^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$"); + + // This regex looks for the ELFVERSION tag this is control key-word for the version script + // content processing. + // ELFVERSION tag accepts the following values: + // - stop - stops the symbols lookup for a version script starting from this line. + // - ignore-next - ignores the line followed by the current one. + // - ignore - ignores the current line. + static const std::regex ElfVersionTagRegex(".*ELFVERSION:(stop|ignore-next|ignore).*"); + + std::ifstream input(headerFile, std::ifstream::in); + if (!input.is_open()) { + std::cerr << "Unable to open " << headerFile << std::endl; + return false; + } + + bool hasQtBeginNamespace = false; + std::string qtBeginNamespace; + std::string qtEndNamespace; + bool hasWeMeantIt = false; + bool isSuspended = false; + bool isMultiLineComment = false; + std::size_t bracesDepth = 0; + std::size_t namespaceCount = 0; + std::string namespaceString; + + std::smatch match; + + std::string buffer; + std::string line; + std::string tmpLine; + std::size_t linesProcessed = 0; + int faults = NoChecks; + + const auto error = [&] () -> decltype(auto) { + return std::cerr << ErrorMessagePreamble << m_currentFileString + << ":" << m_currentFileLineNumber << " "; + }; + + // Read file line by line + while (std::getline(input, tmpLine)) { + ++m_currentFileLineNumber; + line.append(tmpLine); + if (line.empty() || line.at(line.size() - 1) == '\\') { + continue; + } + buffer.clear(); + buffer.reserve(line.size()); + // Optimize processing by looking for a special sequences such as: + // - start-end of comments + // - start-end of class/structures + // And avoid processing of the the data inside these blocks. + for (std::size_t i = 0; i < line.size(); ++i) { + if (line[i] == '\r') + continue; + if (bracesDepth == namespaceCount) { + if (line[i] == '/') { + if ((i + 1) < line.size()) { + if (line[i + 1] == '*') { + isMultiLineComment = true; + continue; + } else if (line[i + 1] == '/') { // Single line comment + if (!(skipChecks & WeMeantItChecks) + && line.find(WeMeantItString) != std::string::npos) { + hasWeMeantIt = true; + continue; + } + if (m_versionScriptGeneratorState != Stopped + && std::regex_match(line, match, ElfVersionTagRegex)) { + if (match[1].str() == "ignore") + m_versionScriptGeneratorState = Ignore; + else if (match[1].str() == "ignore-next") + m_versionScriptGeneratorState = IgnoreNext; + else if (match[1].str() == "stop") + m_versionScriptGeneratorState = Stopped; + } + break; + } + } + } else if (line[i] == '*' && (i + 1) < line.size() && line[i + 1] == '/') { + ++i; + isMultiLineComment = false; + continue; + } + } + + if (isMultiLineComment) { + if (!(skipChecks & WeMeantItChecks) && + line.find(WeMeantItString) != std::string::npos) { + hasWeMeantIt = true; + continue; + } + continue; + } + + if (line[i] == '{') { + if (std::regex_match(buffer, match, NamespaceRegex)) { + ++namespaceCount; + namespaceString += "::"; + namespaceString += match[1].str(); + } + ++bracesDepth; + continue; + } else if (line[i] == '}') { + if (namespaceCount > 0 && bracesDepth == namespaceCount) { + namespaceString.resize(namespaceString.rfind("::")); + --namespaceCount; + } + --bracesDepth; + } else if (bracesDepth == namespaceCount) { + buffer += line[i]; + } + } + line.clear(); + + scannerDebug() << m_currentFilename << ": " << buffer << std::endl; + + if (m_currentFileType & PrivateHeader) { + parseVersionScriptContent(buffer, result); + } + + if (buffer.empty()) + continue; + + ++linesProcessed; + + bool skipSymbols = + (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader) + || (m_currentFileType & SsgHeader); + + // Parse pragmas + if (std::regex_match(buffer, MacroRegex)) { + if (std::regex_match(buffer, SkipHeaderCheckRegex)) { + skipChecks = AllChecks; + faults = NoChecks; + } else if (std::regex_match(buffer, StopProcessingRegex)) { + if (skipChecks == AllChecks) + m_headerCheckExceptions.push_back(m_currentFileString); + return true; + } else if (std::regex_match(buffer, SuspendProcessingRegex)) { + isSuspended = true; + } else if (std::regex_match(buffer, ResumeProcessingRegex)) { + isSuspended = false; + } else if (std::regex_match(buffer, match, ExplixitClassPragmaRegex)) { + if (!skipSymbols) { + updateSymbolDescriptor(match[1].str(), m_currentFilename, + SymbolDescriptor::Pragma); + } else { + // TODO: warn about skipping symbols that are defined explicitly + } + } else if (std::regex_match(buffer, NoMasterIncludePragmaRegex)) { + result.masterInclude = false; + } else if (std::regex_match(buffer, match, DeprecatesPragmaRegex)) { + m_deprecatedHeaders[match[1].str()] = + m_commandLineArgs->moduleName() + '/' + m_currentFilename; + } else if (std::regex_match(buffer, OnceRegex)) { + if (!(skipChecks & PragmaOnceChecks)) { + faults |= PragmaOnceChecks; + error() << "\"#pragma once\" is not allowed in installed header files: " + "https://lists.qt-project.org/pipermail/development/2022-October/043121.html" + << std::endl; + } + } else if (std::regex_match(buffer, match, IncludeRegex) && !isSuspended) { + if (!(skipChecks & IncludeChecks)) { + std::string includedHeader = match[1].str(); + if (!(skipChecks & PrivateHeaderChecks) + && isHeaderPrivate(std::filesystem::path(includedHeader) + .filename() + .generic_string())) { + faults |= PrivateHeaderChecks; + error() << "includes private header " << includedHeader << std::endl; + } + for (const auto &module : m_commandLineArgs->knownModules()) { + std::string suggestedHeader = "Qt" + module + '/' + includedHeader; + if (std::filesystem::exists(m_commandLineArgs->includeDir() + "/../" + + suggestedHeader)) { + faults |= IncludeChecks; + std::cerr << m_warningMessagePreamble << m_currentFileString + << ":" << m_currentFileLineNumber + << " includes " << includedHeader + << " when it should include " + << suggestedHeader << std::endl; + } + } + } + } + continue; + } + + // Logic below this line is affected by the 'qt_sync_suspend_processing' and + // 'qt_sync_resume_processing' pragmas. + if (isSuspended) + continue; + + // Look for the symbols in header file. + if (!skipSymbols) { + std::string symbol; + if (checkLineForSymbols(buffer, symbol)) { + if (namespaceCount == 0 + || std::regex_match(namespaceString, + m_commandLineArgs->publicNamespaceRegex())) { + updateSymbolDescriptor(symbol, m_currentFilename, + SymbolDescriptor::Declaration); + } + continue; + } else if (std::regex_match(buffer, match, DeclareIteratorRegex)) { + std::string iteratorSymbol = match[1].str() + "Iterator"; + updateSymbolDescriptor(std::string("Q") + iteratorSymbol, m_currentFilename, + SymbolDescriptor::Declaration); + updateSymbolDescriptor(std::string("QMutable") + iteratorSymbol, + m_currentFilename, SymbolDescriptor::Declaration); + continue; + } else if (std::regex_match(buffer, match, RequireConfigRegex)) { + result.requireConfig = match[1].str(); + continue; + } + } + + // Check for both QT_BEGIN_NAMESPACE and QT_END_NAMESPACE macros are present in the + // header file. + if (!(skipChecks & NamespaceChecks)) { + if (std::regex_match(buffer, match, BeginNamespaceRegex)) { + qtBeginNamespace = match[1].str(); + hasQtBeginNamespace = true; + } else if (std::regex_match(buffer, match, EndNamespaceRegex)) { + qtEndNamespace = match[1].str(); + } + } + } + input.close(); + + // Error out if namespace checks are failed. + if (!(skipChecks & NamespaceChecks)) { + if (hasQtBeginNamespace) { + if (qtBeginNamespace != qtEndNamespace) { + faults |= NamespaceChecks; + std::cerr << m_warningMessagePreamble << m_currentFileString + << " the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace + << " doesn't match the end namespace macro QT_END_NAMESPACE" + << qtEndNamespace << std::endl; + } + } else { + faults |= NamespaceChecks; + std::cerr << m_warningMessagePreamble << m_currentFileString + << " does not include QT_BEGIN_NAMESPACE" << std::endl; + } + } + + if (!(skipChecks & WeMeantItChecks) && !hasWeMeantIt) { + faults |= WeMeantItChecks; + std::cerr << m_warningMessagePreamble << m_currentFileString + << " does not have the \"We mean it.\" warning" + << std::endl; + } + + scannerDebug() << "linesTotal: " << m_currentFileLineNumber + << " linesProcessed: " << linesProcessed << std::endl; + + if (skipChecks == AllChecks) + m_headerCheckExceptions.push_back(m_currentFileString); + + // Exit with an error if any of critical checks are present. + return !(faults & m_criticalChecks); + } + + // The function checks if line contains the symbol that needs to have a CaMeL-style alias. + [[nodiscard]] bool checkLineForSymbols(const std::string &line, std::string &symbol) + { + scannerDebug() << "checkLineForSymbols: " << line << std::endl; + + // This regex checks if line contains class or structure declaration like: + // - <class|stuct> StructName + // - template <> class ClassName + // - class ClassName : [public|protected|private] BaseClassName + // - class ClassName [final|Q_DECL_FINAL|sealed] + // And possible combinations of the above variants. + static const std::regex ClassRegex( + "^ *(template *<.*> *)?(class|struct) +([^ <>]* " + "+)?((?!Q_DECL_FINAL|final|sealed)[^<\\s\\:]+) ?(<[^>\\:]*> " + "?)?\\s*(?:Q_DECL_FINAL|final|sealed)?\\s*((,|:)\\s*(public|protected|private)? " + "*.*)? *$"); + + // This regex checks if line contains function pointer typedef declaration like: + // - typedef void (* QFunctionPointerType)(int, char); + static const std::regex FunctionPointerRegex( + "^ *typedef *.*\\(\\*(Q[^\\)]+)\\)\\(.*\\); *"); + + // This regex checks if line contains class or structure typedef declaration like: + // - typedef AnySymbol<char> QAnySymbolType; + static const std::regex TypedefRegex("^ *typedef\\s+(.*)\\s+(Q\\w+); *$"); + + // This regex checks if symbols is the Qt public symbol. Assume that Qt public symbols start + // with the capital 'Q'. + static const std::regex QtClassRegex("^Q\\w+$"); + + std::smatch match; + if (std::regex_match(line, match, FunctionPointerRegex)) { + symbol = match[1].str(); + } else if (std::regex_match(line, match, TypedefRegex)) { + symbol = match[2].str(); + } else if (std::regex_match(line, match, ClassRegex)) { + symbol = match[4].str(); + if (!std::regex_match(symbol, QtClassRegex)) + symbol.clear(); + } else { + return false; + } + return !symbol.empty(); + } + + [[nodiscard]] bool isHeaderQpa(const std::string &headerFileName) + { + return std::regex_match(headerFileName, m_commandLineArgs->qpaHeadersRegex()); + } + + [[nodiscard]] bool isHeaderRhi(const std::string &headerFileName) + { + return std::regex_match(headerFileName, m_commandLineArgs->rhiHeadersRegex()); + } + + [[nodiscard]] bool isHeaderSsg(const std::string &headerFileName) + { + return std::regex_match(headerFileName, m_commandLineArgs->ssgHeadersRegex()); + } + + [[nodiscard]] bool isHeaderPrivate(const std::string &headerFile) + { + return std::regex_match(headerFile, m_commandLineArgs->privateHeadersRegex()); + } + + [[nodiscard]] bool isHeaderPCH(const std::string &headerFilename) + { + static const std::string pchSuffix("_pch.h"); + return headerFilename.find(pchSuffix, headerFilename.size() - pchSuffix.size()) + != std::string::npos; + } + + [[nodiscard]] bool isHeader(const std::filesystem::path &path) + { + return path.extension().string() == ".h"; + } + + [[nodiscard]] bool isDocFileHeuristic(const std::string &headerFilePath) + { + return headerFilePath.find("/doc/") != std::string::npos; + } + + [[nodiscard]] bool isHeaderGenerated(const std::string &header) + { + return m_commandLineArgs->generatedHeaders().find(header) + != m_commandLineArgs->generatedHeaders().end(); + } + + [[nodiscard]] bool generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath, + const std::string &aliasedFilePath); + + [[nodiscard]] bool generateAliasedHeaderFileIfTimestampChanged( + const std::string &outputFilePath, const std::string &aliasedFilePath, + const FileStamp &originalStamp = FileStamp::clock::now()); + + bool writeIfDifferent(const std::string &outputFile, const std::string &buffer); + + [[nodiscard]] bool generateMasterHeader() + { + if (m_masterHeaderContents.empty()) + return true; + + std::string outputFile = + m_commandLineArgs->includeDir() + '/' + m_commandLineArgs->moduleName(); + + std::string moduleUpper = utils::asciiToUpper(m_commandLineArgs->moduleName()); + std::stringstream buffer; + buffer << "#ifndef QT_" << moduleUpper << "_MODULE_H\n" + << "#define QT_" << moduleUpper << "_MODULE_H\n" + << "#include <" << m_commandLineArgs->moduleName() << "/" + << m_commandLineArgs->moduleName() << "Depends>\n"; + for (const auto &headerContents : m_masterHeaderContents) { + if (!headerContents.second.empty()) { + buffer << "#if QT_CONFIG(" << headerContents.second << ")\n" + << "#include \"" << headerContents.first << "\"\n" + << "#endif\n"; + } else { + buffer << "#include \"" << headerContents.first << "\"\n"; + } + } + buffer << "#endif\n"; + + m_producedHeaders.insert(m_commandLineArgs->moduleName()); + return writeIfDifferent(outputFile, buffer.str()); + } + + [[nodiscard]] bool generateVersionHeader(const std::string &outputFile) + { + std::string moduleNameUpper = utils::asciiToUpper( m_commandLineArgs->moduleName()); + + std::stringstream buffer; + buffer << "/* This file was generated by syncqt. */\n" + << "#ifndef QT_" << moduleNameUpper << "_VERSION_H\n" + << "#define QT_" << moduleNameUpper << "_VERSION_H\n\n" + << "#define " << moduleNameUpper << "_VERSION_STR \"" << QT_VERSION_STR << "\"\n\n" + << "#define " << moduleNameUpper << "_VERSION " + << "0x0" << QT_VERSION_MAJOR << "0" << QT_VERSION_MINOR << "0" << QT_VERSION_PATCH + << "\n\n" + << "#endif // QT_" << moduleNameUpper << "_VERSION_H\n"; + + return writeIfDifferent(outputFile, buffer.str()); + } + + [[nodiscard]] bool generateDeprecatedHeaders() + { + static std::regex cIdentifierSymbolsRegex("[^a-zA-Z0-9_]"); + static std::string guard_base = "DEPRECATED_HEADER_" + m_commandLineArgs->moduleName(); + bool result = true; + for (auto it = m_deprecatedHeaders.begin(); it != m_deprecatedHeaders.end(); ++it) { + const std::string &descriptor = it->first; + const std::string &replacement = it->second; + + const auto separatorPos = descriptor.find(','); + std::string headerPath = descriptor.substr(0, separatorPos); + std::string versionDisclaimer; + if (separatorPos != std::string::npos) { + std::string version = descriptor.substr(separatorPos + 1); + versionDisclaimer = " and will be removed in Qt " + version; + int minor = 0; + int major = 0; + if (!utils::parseVersion(version, major, minor)) { + std::cerr << ErrorMessagePreamble + << "Invalid version format specified for the deprecated header file " + << headerPath << ": '" << version + << "'. Expected format: 'major.minor'.\n"; + result = false; + continue; + } + + if (QT_VERSION_MAJOR > major + || (QT_VERSION_MAJOR == major && QT_VERSION_MINOR >= minor)) { + std::cerr << WarningMessagePreamble << headerPath + << " is marked as deprecated and will not be generated in Qt " + << QT_VERSION_STR + << ". The respective qt_deprecates pragma needs to be removed.\n"; + continue; + } + } + + const auto moduleSeparatorPos = headerPath.find('/'); + std::string headerName = moduleSeparatorPos != std::string::npos + ? headerPath.substr(moduleSeparatorPos + 1) + : headerPath; + const std::string moduleName = moduleSeparatorPos != std::string::npos + ? headerPath.substr(0, moduleSeparatorPos) + : m_commandLineArgs->moduleName(); + + bool isCrossModuleDeprecation = moduleName != m_commandLineArgs->moduleName(); + + std::string qualifiedHeaderName = + std::regex_replace(headerName, cIdentifierSymbolsRegex, "_"); + std::string guard = guard_base + "_" + qualifiedHeaderName; + std::string warningText = "Header <" + moduleName + "/" + headerName + "> is deprecated" + + versionDisclaimer + ". Please include <" + replacement + "> instead."; + std::stringstream buffer; + buffer << "#ifndef " << guard << "\n" + << "#define " << guard << "\n" + << "#if defined(__GNUC__)\n" + << "# warning " << warningText << "\n" + << "#elif defined(_MSC_VER)\n" + << "# pragma message (\"" << warningText << "\")\n" + << "#endif\n" + << "#include <" << replacement << ">\n" + << "#endif\n"; + + const std::string outputDir = isCrossModuleDeprecation + ? m_commandLineArgs->includeDir() + "/../" + moduleName + : m_commandLineArgs->includeDir(); + writeIfDifferent(outputDir + '/' + headerName, buffer.str()); + + // Add header file to staging installation directory for cross-module deprecation case. + if (isCrossModuleDeprecation) { + const std::string stagingDir = outputDir + "/.syncqt_staging/"; + writeIfDifferent(stagingDir + headerName, buffer.str()); + } + m_producedHeaders.insert(headerName); + } + return result; + } + + [[nodiscard]] bool generateHeaderCheckExceptions() + { + std::stringstream buffer; + for (const auto &header : m_headerCheckExceptions) + buffer << header << ";"; + return writeIfDifferent(m_commandLineArgs->binaryDir() + '/' + + m_commandLineArgs->moduleName() + + "_header_check_exceptions", + buffer.str()); + } + + [[nodiscard]] bool generateLinkerVersionScript() + { + std::stringstream buffer; + for (const auto &content : m_versionScriptContents) + buffer << content; + return writeIfDifferent(m_commandLineArgs->versionScriptFile(), buffer.str()); + } + + bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst) noexcept; + void updateSymbolDescriptor(const std::string &symbol, const std::string &file, + SymbolDescriptor::SourceType type); +}; + +// The function updates information about the symbol: +// - The path and modification time of the file where the symbol was found. +// - The source of finding +// Also displays a short info about a symbol in show only mode. +void SyncScanner::updateSymbolDescriptor(const std::string &symbol, const std::string &file, + SymbolDescriptor::SourceType type) +{ + if (m_commandLineArgs->showOnly()) + std::cout << " SYMBOL: " << symbol << std::endl; + m_symbols[symbol].update(file, type); +} + +[[nodiscard]] std::filesystem::path +SyncScanner::makeHeaderAbsolute(const std::string &filename) const +{ + if (std::filesystem::path(filename).is_relative()) + return utils::normilizedPath(m_commandLineArgs->sourceDir() + '/' + filename); + + return utils::normilizedPath(filename); +} + +bool SyncScanner::updateOrCopy(const std::filesystem::path &src, + const std::filesystem::path &dst) noexcept +{ + if (m_commandLineArgs->showOnly()) + return true; + + if (src == dst) { + std::cout << "Source and destination paths are same when copying " << src.string() + << ". Skipping." << std::endl; + return true; + } + + std::error_code ec; + std::filesystem::copy(src, dst, std::filesystem::copy_options::update_existing, ec); + if (ec) { + ec.clear(); + std::filesystem::remove(dst, ec); + if (ec) { + // On some file systems(e.g. vboxfs) the std::filesystem::copy doesn't support + // std::filesystem::copy_options::overwrite_existing remove file first and then copy. + std::cerr << "Unable to remove file: " << src << " to " << dst << " error: (" + << ec.value() << ")" << ec.message() << std::endl; + return false; + } + + std::filesystem::copy(src, dst, std::filesystem::copy_options::overwrite_existing, ec); + if (ec) { + std::cerr << "Unable to copy file: " << src << " to " << dst << " error: (" + << ec.value() << ")" << ec.message() << std::endl; + return false; + } + } + return true; +} + +// The function generates Qt CaMeL-case files. +// CaMeL-case files can have timestamp that is the same as or newer than timestamp of file that +// supposed to included there. It's not an issue when we generate regular aliases because the +// content of aliases is always the same, but we only want to "touch" them when content of original +// is changed. +bool SyncScanner::generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath, + const std::string &aliasedFilePath) +{ + if (m_commandLineArgs->showOnly()) + return true; + + std::string buffer = "#include \""; + buffer += aliasedFilePath; + buffer += "\" // IWYU pragma: export\n"; + + return writeIfDifferent(outputFilePath, buffer); +} + +// The function generates aliases for files in source tree. Since the content of these aliases is +// always same, it's ok to check only timestamp and touch files in case if stamp of original is +// newer than the timestamp of an alias. +bool SyncScanner::generateAliasedHeaderFileIfTimestampChanged(const std::string &outputFilePath, + const std::string &aliasedFilePath, + const FileStamp &originalStamp) +{ + if (m_commandLineArgs->showOnly()) + return true; + + if (std::filesystem::exists({ outputFilePath }) + && std::filesystem::last_write_time({ outputFilePath }) >= originalStamp) { + return true; + } + scannerDebug() << "Rewrite " << outputFilePath << std::endl; + + std::ofstream ofs; + ofs.open(outputFilePath, std::ofstream::out | std::ofstream::trunc); + if (!ofs.is_open()) { + std::cerr << "Unable to write header file alias: " << outputFilePath << std::endl; + return false; + } + ofs << "#include \"" << aliasedFilePath << "\" // IWYU pragma: export\n"; + ofs.close(); + return true; +} + +bool SyncScanner::writeIfDifferent(const std::string &outputFile, const std::string &buffer) +{ + if (m_commandLineArgs->showOnly()) + return true; + + static const std::streamsize bufferSize = 1025; + bool differs = false; + std::filesystem::path outputFilePath(outputFile); + + std::string outputDirectory = outputFilePath.parent_path().string(); + + if (!utils::createDirectories(outputDirectory, "Unable to create output directory")) + return false; + + auto expectedSize = buffer.size(); +#ifdef _WINDOWS + // File on disk has \r\n instead of just \n + expectedSize += std::count(buffer.begin(), buffer.end(), '\n'); +#endif + + if (std::filesystem::exists(outputFilePath) + && expectedSize == std::filesystem::file_size(outputFilePath)) { + char rdBuffer[bufferSize]; + memset(rdBuffer, 0, bufferSize); + + std::ifstream ifs(outputFile, std::fstream::in); + if (!ifs.is_open()) { + std::cerr << "Unable to open " << outputFile << " for comparison." << std::endl; + return false; + } + std::streamsize currentPos = 0; + + std::size_t bytesRead = 0; + do { + ifs.read(rdBuffer, bufferSize - 1); // Read by 1K + bytesRead = ifs.gcount(); + if (buffer.compare(currentPos, bytesRead, rdBuffer) != 0) { + differs = true; + break; + } + currentPos += bytesRead; + memset(rdBuffer, 0, bufferSize); + } while (bytesRead > 0); + + ifs.close(); + } else { + differs = true; + } + + scannerDebug() << "Update: " << outputFile << " " << differs << std::endl; + if (differs) { + std::ofstream ofs; + ofs.open(outputFilePath, std::fstream::out | std::ofstream::trunc); + if (!ofs.is_open()) { + std::cerr << "Unable to write header content to " << outputFilePath << std::endl; + return false; + } + ofs << buffer; + + ofs.close(); + } + return true; +} + +int main(int argc, char *argv[]) +{ + CommandLineOptions options(argc, argv); + if (!options.isValid()) + return InvalidArguments; + + if (options.printHelpOnly()) { + options.printHelp(); + return NoError; + } + + SyncScanner scanner = SyncScanner(&options); + return scanner.sync(); +} diff --git a/src/tools/tracegen/CMakeLists.txt b/src/tools/tracegen/CMakeLists.txt index 1404013d28..f5f6b2e184 100644 --- a/src/tools/tracegen/CMakeLists.txt +++ b/src/tools/tracegen/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from tracegen.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## tracegen Tool: @@ -6,18 +7,18 @@ qt_get_tool_target_name(target_name tracegen) qt_internal_add_tool(${target_name} - BOOTSTRAP + CORE_LIBRARY Bootstrap INSTALL_DIR "${INSTALL_LIBEXECDIR}" - TOOLS_TARGET Core # special case + TOOLS_TARGET Core SOURCES etw.cpp etw.h helpers.cpp helpers.h + ctf.cpp ctf.h lttng.cpp lttng.h panic.cpp panic.h provider.cpp provider.h qtheaders.cpp qtheaders.h tracegen.cpp + NO_UNITY_BUILD ) - -#### Keys ignored in scope 1:.:.:tracegen.pro:<TRUE>: -# _OPTION = "host_build" +qt_internal_return_unless_building_tools() diff --git a/src/tools/tracegen/ctf.cpp b/src/tools/tracegen/ctf.cpp new file mode 100644 index 0000000000..95ffcf89cc --- /dev/null +++ b/src/tools/tracegen/ctf.cpp @@ -0,0 +1,376 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "ctf.h" +#include "provider.h" +#include "helpers.h" +#include "panic.h" +#include "qtheaders.h" + +#include <qfile.h> +#include <qfileinfo.h> +#include <qtextstream.h> +#include <qdebug.h> + + +static void writePrologue(QTextStream &stream, const QString &fileName, const Provider &provider) +{ + writeCommonPrologue(stream); + const QString guard = includeGuard(fileName); + + // include prefix text or qt headers only once + stream << "#if !defined(" << guard << ")\n"; + stream << qtHeaders(); + stream << "\n"; + if (!provider.prefixText.isEmpty()) + stream << provider.prefixText.join(u'\n') << "\n\n"; + stream << "#endif\n\n"; + + /* the first guard is the usual one, the second is required + * by LTTNG to force the re-evaluation of TRACEPOINT_* macros + */ + stream << "#if !defined(" << guard << ") || defined(TRACEPOINT_HEADER_MULTI_READ)\n"; + + stream << "#define " << guard << "\n\n" + << "#undef TRACEPOINT_INCLUDE\n" + << "#define TRACEPOINT_INCLUDE \"" << fileName << "\"\n\n"; + + stream << "#include <private/qctf_p.h>\n\n"; + + const QString namespaceGuard = guard + QStringLiteral("_USE_NAMESPACE"); + stream << "#if !defined(" << namespaceGuard << ")\n" + << "#define " << namespaceGuard << "\n" + << "QT_USE_NAMESPACE\n" + << "#endif // " << namespaceGuard << "\n\n"; + + stream << "TRACEPOINT_PROVIDER(" << provider.name << ");\n\n"; +} + +static void writeEpilogue(QTextStream &stream, const QString &fileName) +{ + stream << "\n"; + stream << "#endif // " << includeGuard(fileName) << "\n" + << "#include <private/qtrace_p.h>\n"; +} + +static void writeWrapper(QTextStream &stream, + const Tracepoint &tracepoint, const Provider &provider) +{ + const QString argList = formatFunctionSignature(tracepoint.args); + const QString paramList = formatParameterList(provider, tracepoint.args, tracepoint.fields, CTF); + const QString &name = tracepoint.name; + const QString includeGuard = QStringLiteral("TP_%1_%2").arg(provider.name).arg(name).toUpper(); + + /* prevents the redefinion of the inline wrapper functions + */ + stream << "\n" + << "#ifndef " << includeGuard << "\n" + << "#define " << includeGuard << "\n" + << "QT_BEGIN_NAMESPACE\n" + << "namespace QtPrivate {\n"; + + stream << "inline void trace_" << name << "(" << argList << ")\n" + << "{\n" + << " tracepoint(" << provider.name << ", " << name << paramList << ");\n" + << "}\n"; + + stream << "inline void do_trace_" << name << "(" << argList << ")\n" + << "{\n" + << " do_tracepoint(" << provider.name << ", " << name << paramList << ");\n" + << "}\n"; + + stream << "inline bool trace_" << name << "_enabled()\n" + << "{\n" + << " return tracepoint_enabled(" << provider.name << ", " << name << ");\n" + << "}\n"; + + stream << "} // namespace QtPrivate\n" + << "QT_END_NAMESPACE\n" + << "#endif // " << includeGuard << "\n\n"; +} + + +static void writeMetadataGenerators(QTextStream &stream) +{ + stream << R"CPP( +template <typename T> +inline QString integerToMetadata(const QString &name) +{ + QString ret; + if (!std::is_signed<T>().value) + ret += QLatin1Char('u'); + if (sizeof(T) == 8) + ret += QStringLiteral("int64_t "); + else if (sizeof(T) == 4) + ret += QStringLiteral("int32_t "); + else if (sizeof(T) == 2) + ret += QStringLiteral("int16_t "); + else if (sizeof(T) == 1) + ret += QStringLiteral("int8_t "); + ret += name + QLatin1Char(';'); + return ret; +} + +template <typename T> +inline QString integerArrayToMetadata(const QString &size, const QString &name) +{ + QString ret; + if (!std::is_signed<T>().value) + ret += QLatin1Char('u'); + if (sizeof(T) == 8) + ret += QStringLiteral("int64_t "); + else if (sizeof(T) == 4) + ret += QStringLiteral("int32_t "); + else if (sizeof(T) == 2) + ret += QStringLiteral("int16_t "); + else if (sizeof(T) == 1) + ret += QStringLiteral("int8_t "); + ret += name + QLatin1Char('[') + size + QStringLiteral("];"); + return ret; +} + +template <typename T> +inline QString floatToMetadata(const QString &name) +{ + QString ret; + if (sizeof(T) == 8) + ret += QStringLiteral("double "); + else if (sizeof(T) == 4) + ret += QStringLiteral("float "); + ret += name + QLatin1Char(';'); + return ret; +} + +template <typename T> +inline QString floatArrayToMetadata(const QString &size, const QString &name) +{ + QString ret; + if (sizeof(T) == 8) + ret += QStringLiteral("double "); + else if (sizeof(T) == 4) + ret += QStringLiteral("float "); + ret += name + QLatin1Char('[') + size + QLatin1Char(']'); + return ret + QLatin1Char(';'); +} + +inline QString pointerToMetadata(const QString &name) +{ + QString ret; + if (QT_POINTER_SIZE == 8) + ret += QStringLiteral("intptr64_t "); + else if (QT_POINTER_SIZE == 4) + ret += QStringLiteral("intptr32_t "); + ret += name + QLatin1Char(';'); + return ret; +} + +)CPP"; +} + +static void writeTracepoint(QTextStream &stream, + const Tracepoint &tracepoint, const QString &providerName) +{ + stream << "TRACEPOINT_EVENT(\n" + << " " << providerName << ",\n" + << " " << tracepoint.name << ",\n"; + + const auto checkUnknownArgs = [](const Tracepoint &tracepoint) { + for (auto &field : tracepoint.fields) { + if (field.backendType == Tracepoint::Field::Unknown) + return true; + } + return false; + }; + + const auto formatType = [](const QString &type) { + QString ret = type; + if (type.endsWith(QLatin1Char('*')) || type.endsWith(QLatin1Char('&'))) + ret = type.left(type.length() - 1).simplified(); + if (ret.startsWith(QStringLiteral("const"))) + ret = ret.right(ret.length() - 6).simplified(); + return typeToTypeName(ret); + }; + QString eventSize; + bool variableSize = false; + const bool emptyMetadata = checkUnknownArgs(tracepoint) || tracepoint.args.size() == 0; + if (!emptyMetadata) { + for (int i = 0; i < tracepoint.args.size(); i++) { + auto &arg = tracepoint.args[i]; + auto &field = tracepoint.fields[i]; + if (i > 0) { + stream << " + QStringLiteral(\"\\n\\\n \") + "; + eventSize += QStringLiteral(" + "); + } + const bool array = field.arrayLen > 0; + switch (field.backendType) { + case Tracepoint::Field::Boolean: { + stream << "QStringLiteral(\"Boolean " << arg.name << ";\")"; + eventSize += QStringLiteral("sizeof(bool)"); + } break; + case Tracepoint::Field::Integer: { + if (array) { + stream << "integerArrayToMetadata<" << formatType(arg.type) + << ">(QStringLiteral(\"" << field.arrayLen << "\"), QStringLiteral(\"" + << arg.name << "\"))"; + } else { + stream << "integerToMetadata<" << formatType(arg.type) << ">(QStringLiteral(\"" + << arg.name << "\"))"; + } + eventSize += QStringLiteral("sizeof(") + formatType(arg.type) + QStringLiteral(")"); + if (array) + eventSize += QStringLiteral(" * ") + QString::number(field.arrayLen); + } break; + case Tracepoint::Field::Pointer: + case Tracepoint::Field::IntegerHex: { + stream << "pointerToMetadata(QStringLiteral(\"" << formatType(arg.type) << "_" + << arg.name << "\"))"; + eventSize += QStringLiteral("QT_POINTER_SIZE"); + } break; + case Tracepoint::Field::Float: { + if (array) { + stream << "floatArrayToMetadata<" << formatType(arg.type) + << ">(QStringLiteral(\"" << field.arrayLen << "\"), QStringLiteral(\"" + << arg.name << "\"))"; + } else { + stream << "floatToMetadata<" << formatType(arg.type) << ">(QStringLiteral(\"" + << arg.name << "\"))"; + } + eventSize += QStringLiteral("sizeof(") + formatType(arg.type) + QStringLiteral(")"); + if (array) + eventSize += QStringLiteral(" * ") + QString::number(field.arrayLen); + } break; + case Tracepoint::Field::QtUrl: + case Tracepoint::Field::QtString: + case Tracepoint::Field::String: { + stream << "QStringLiteral(\"string " << arg.name << ";\")"; + eventSize += QStringLiteral("1"); + variableSize = true; + } break; + case Tracepoint::Field::QtRect: { + stream << "QStringLiteral(\"int32_t QRect_" << arg.name << "_x;\\n\\\n \")"; + stream << " + QStringLiteral(\"int32_t QRect_" << arg.name << "_y;\\n\\\n \")"; + stream << " + QStringLiteral(\"int32_t QRect_" << arg.name << "_width;\\n\\\n \")"; + stream << " + QStringLiteral(\"int32_t QRect_" << arg.name << "_height;\\n\\\n \")"; + eventSize += QStringLiteral("16"); + } break; + case Tracepoint::Field::QtSize: { + stream << "QStringLiteral(\"int32_t QSize_" << arg.name << "_width;\\n\\\n \")"; + stream << " + QStringLiteral(\"int32_t QSize_" << arg.name << "_height;\\n\\\n \")"; + eventSize += QStringLiteral("8"); + } break; + case Tracepoint::Field::QtRectF: { + stream << "QStringLiteral(\"float QRectF_" << arg.name << "_x;\\n\\\n \")"; + stream << " + QStringLiteral(\"float QRectF_" << arg.name << "_y;\\n\\\n \")"; + stream << " + QStringLiteral(\"float QRectF_" << arg.name << "_width;\\n\\\n \")"; + stream << " + QStringLiteral(\"float QRectF_" << arg.name << "_height;\\n\\\n \")"; + eventSize += QStringLiteral("16"); + } break; + case Tracepoint::Field::QtSizeF: { + stream << "QStringLiteral(\"float QSizeF_" << arg.name << "_width;\\n\\\n \")"; + stream << " + QStringLiteral(\"float QSizeF_" << arg.name << "_height;\\n\\\n \")"; + eventSize += QStringLiteral("8"); + } break; + case Tracepoint::Field::Unknown: + break; + case Tracepoint::Field::EnumeratedType: { + stream << "QStringLiteral(\"" << typeToTypeName(arg.type) << " " << arg.name << ";\")"; + eventSize += QString::number(field.enumValueSize / 8); + variableSize = true; + } break; + case Tracepoint::Field::FlagType: { + stream << "QStringLiteral(\"uint8_t " << arg.name << "_length;\\n\\\n "; + stream << typeToTypeName(arg.type) << " " << arg.name << "[" << arg.name << "_length];\")"; + eventSize += QStringLiteral("2"); + variableSize = true; + } break; + case Tracepoint::Field::QtByteArray: + case Tracepoint::Field::Sequence: + panic("Unhandled type '%s %s", qPrintable(arg.type), qPrintable(arg.name)); + break; + } + } + } + + if (emptyMetadata) + stream << "{},\n"; + else + stream << ",\n"; + if (eventSize.length()) + stream << eventSize << ", \n"; + else + stream << "0, \n"; + stream << (variableSize ? "true" : "false") << "\n"; + stream << ")\n\n"; +} + +static void writeTracepoints(QTextStream &stream, const Provider &provider) +{ + for (const Tracepoint &t : provider.tracepoints) { + writeTracepoint(stream, t, provider.name); + writeWrapper(stream, t, provider); + } +} + +static void writeEnums(QTextStream &stream, const Provider &provider) +{ + for (const auto &e : provider.enumerations) { + QString name = e.name; + name.replace(QStringLiteral("::"), QStringLiteral("_")); + stream << "TRACEPOINT_METADATA(" << provider.name << ", " << name << ", \n"; + stream << "QStringLiteral(\"typealias enum : integer { size = " << e.valueSize << "; } {\\n\\\n"; + + const auto values = e.values; + QList<int> handledValues; + + for (const auto &v : values) { + if (handledValues.contains(v.value)) + continue; + if (v.range) { + stream << v.name << " = " << v.value << " ... " << v.range << ", \\n\\\n"; + } else { + const QString names = aggregateListValues(v.value, values); + stream << names << " = " << v.value << ", \\n\\\n"; + handledValues.append(v.value); + } + } + stream << "} := " << name << ";\\n\\n\"));\n\n"; + } + stream << "\n"; +} + +static void writeFlags(QTextStream &stream, const Provider &provider) +{ + for (const auto &e : provider.flags) { + QString name = e.name; + name.replace(QStringLiteral("::"), QStringLiteral("_")); + stream << "TRACEPOINT_METADATA(" << provider.name << ", " << name << ", \n"; + stream << "QStringLiteral(\"typealias enum : integer { size = 8; } {\\n\\\n"; + + const auto values = e.values; + QList<int> handledValues; + + for (const auto &v : values) { + if (handledValues.contains(v.value)) + continue; + const QString names = aggregateListValues(v.value, values); + stream << names << " = " << v.value << ", \\n\\\n"; + handledValues.append(v.value); + } + stream << "} := " << name << ";\\n\\n\"));\n\n"; + } + stream << "\n"; +} + +void writeCtf(QFile &file, const Provider &provider) +{ + QTextStream stream(&file); + + const QString fileName = QFileInfo(file.fileName()).fileName(); + + writePrologue(stream, fileName, provider); + writeMetadataGenerators(stream); + writeEnums(stream, provider); + writeFlags(stream, provider); + writeTracepoints(stream, provider); + writeEpilogue(stream, fileName); +} diff --git a/src/tools/tracegen/ctf.h b/src/tools/tracegen/ctf.h new file mode 100644 index 0000000000..113e30919f --- /dev/null +++ b/src/tools/tracegen/ctf.h @@ -0,0 +1,12 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CTF_H +#define CTF_H + +struct Provider; +class QFile; + +void writeCtf(QFile &device, const Provider &p); + +#endif // CTF_H diff --git a/src/tools/tracegen/etw.cpp b/src/tools/tracegen/etw.cpp index eac518dbab..f54a7896ea 100644 --- a/src/tools/tracegen/etw.cpp +++ b/src/tools/tracegen/etw.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "etw.h" #include "provider.h" @@ -47,19 +11,31 @@ #include <qtextstream.h> #include <quuid.h> +using namespace Qt::StringLiterals; + static inline QString providerVar(const QString &providerName) { - return providerName + QLatin1String("_provider"); + return providerName + "_provider"_L1; } static void writeEtwMacro(QTextStream &stream, const Tracepoint::Field &field) { const QString &name = field.name; + if (field.arrayLen > 0) { + for (int i = 0; i < field.arrayLen; i++) { + stream << "TraceLoggingValue(" << name << "[" << i << "], \"" << name << "[" << i << "]\")"; + if (i + 1 < field.arrayLen) + stream << ",\n "; + } + return; + } + switch (field.backendType) { case Tracepoint::Field::QtString: stream << "TraceLoggingCountedWideString(reinterpret_cast<LPCWSTR>(" - << name << ".utf16()), " << name << ".size(), \"" << name << "\")"; + << name << ".utf16()), static_cast<ULONG>(" << name << ".size()), \"" + << name << "\")"; return; case Tracepoint::Field::QtByteArray: stream << "TraceLoggingBinary(" << name << ".constData(), " @@ -69,14 +45,31 @@ static void writeEtwMacro(QTextStream &stream, const Tracepoint::Field &field) stream << "TraceLoggingValue(" << name << ".toEncoded().constData(), \"" << name << "\")"; return; case Tracepoint::Field::QtRect: + case Tracepoint::Field::QtRectF: stream << "TraceLoggingValue(" << name << ".x(), \"x\"), " << "TraceLoggingValue(" << name << ".y(), \"y\"), " << "TraceLoggingValue(" << name << ".width(), \"width\"), " << "TraceLoggingValue(" << name << ".height(), \"height\")"; return; + case Tracepoint::Field::QtSize: + case Tracepoint::Field::QtSizeF: + stream << "TraceLoggingValue(" << name << ".width(), \"width\"), " + << "TraceLoggingValue(" << name << ".height(), \"height\")"; + return; case Tracepoint::Field::Pointer: stream << "TraceLoggingPointer(" << name << ", \"" << name << "\")"; return; + case Tracepoint::Field::Unknown: + // Write down the previously stringified data (like we do for QString). + // The string is already created in writeWrapper(). + // Variable name is name##Str. + stream << "TraceLoggingCountedWideString(reinterpret_cast<LPCWSTR>(" << name + << "Str.utf16()), static_cast<ULONG>(" << name << "Str.size()), \"" << name << "\")"; + return; + case Tracepoint::Field::EnumeratedType: + case Tracepoint::Field::FlagType: + stream << "TraceLoggingString(trace_convert_" << typeToTypeName(field.paramType) << "(" << name << ").toUtf8().constData(), \"" << name << "\")"; + return; default: break; } @@ -111,6 +104,7 @@ static QString createGuid(const QUuid &uuid) static void writePrologue(QTextStream &stream, const QString &fileName, const Provider &provider) { + writeCommonPrologue(stream); QUuid uuid = QUuid::createUuidV5(QUuid(), provider.name.toLocal8Bit()); const QString providerV = providerVar(provider.name); @@ -121,7 +115,7 @@ static void writePrologue(QTextStream &stream, const QString &fileName, const Pr stream << "#ifndef " << guard << "\n" << "#define " << guard << "\n" << "\n" - << "#include <windows.h>\n" + << "#include <qt_windows.h>\n" << "#include <TraceLoggingProvider.h>\n" << "\n"; @@ -138,7 +132,7 @@ static void writePrologue(QTextStream &stream, const QString &fileName, const Pr stream << "\n"; if (!provider.prefixText.isEmpty()) - stream << provider.prefixText.join(QLatin1Char('\n')) << "\n\n"; + stream << provider.prefixText.join(u'\n') << "\n\n"; stream << "#ifdef TRACEPOINT_DEFINE\n" << "/* " << guidString << " */\n" @@ -169,20 +163,29 @@ static void writeEpilogue(QTextStream &stream, const QString &fileName) << "#include <private/qtrace_p.h>\n"; } -static void writeWrapper(QTextStream &stream, const Tracepoint &tracepoint, +static void writeWrapper(QTextStream &stream, const Provider &provider, const Tracepoint &tracepoint, const QString &providerName) { const QString argList = formatFunctionSignature(tracepoint.args); - const QString paramList = formatParameterList(tracepoint.args, ETW); + const QString paramList = formatParameterList(provider, tracepoint.args, tracepoint.fields, ETW); const QString &name = tracepoint.name; const QString includeGuard = QStringLiteral("TP_%1_%2").arg(providerName).arg(name).toUpper(); - const QString provider = providerVar(providerName); + const QString provar = providerVar(providerName); stream << "\n"; stream << "inline void trace_" << name << "(" << argList << ")\n" - << "{\n" - << " TraceLoggingWrite(" << provider << ", \"" << name << "\""; + << "{\n"; + + // Convert all unknown types to QString's using QDebug. + // Note the naming convention: it's field.name##Str + for (const Tracepoint::Field &field : tracepoint.fields) { + if (field.backendType == Tracepoint::Field::Unknown) { + stream << " const QString " << field.name << "Str = QDebug::toString(" << field.name + << ");\n"; + } + } + stream << " TraceLoggingWrite(" << provar << ", \"" << name << "\""; for (const Tracepoint::Field &field : tracepoint.fields) { stream << ",\n"; @@ -200,10 +203,56 @@ static void writeWrapper(QTextStream &stream, const Tracepoint &tracepoint, stream << "inline bool trace_" << name << "_enabled()\n" << "{\n" - << " return TraceLoggingProviderEnabled(" << provider << ", 0, 0);\n" + << " return TraceLoggingProviderEnabled(" << provar << ", 0, 0);\n" << "}\n"; } +static void writeEnumConverter(QTextStream &stream, const TraceEnum &enumeration) +{ + stream << "inline QString trace_convert_" << typeToTypeName(enumeration.name) << "(" << enumeration.name << " val)\n"; + stream << "{\n"; + for (const auto &v : enumeration.values) { + if (v.range != 0) { + stream << " if (val >= " << v.value << " && val <= " << v.range << ")\n" + << " return QStringLiteral(\"" << v.name << " + \") + QString::number((int)val - " << v.value << ");\n"; + } + } + stream << "\n QString ret;\n switch (val) {\n"; + + QList<int> handledValues; + for (const auto &v : enumeration.values) { + if (v.range == 0 && !handledValues.contains(v.value)) { + stream << " case " << v.value << ": ret = QStringLiteral(\"" + << aggregateListValues(v.value, enumeration.values) << "\"); break;\n"; + handledValues.append(v.value); + } + } + + stream << " }\n return ret;\n}\n"; +} + +static void writeFlagConverter(QTextStream &stream, const TraceFlags &flag) +{ + stream << "inline QString trace_convert_" << typeToTypeName(flag.name) << "(" << flag.name << " val)\n"; + stream << "{\n QString ret;\n"; + for (const auto &v : flag.values) { + if (v.value == 0) { + stream << " if (val == 0)\n return QStringLiteral(\"" + << aggregateListValues(v.value, flag.values) << "\");\n"; + break; + } + } + QList<int> handledValues; + for (const auto &v : flag.values) { + if (v.value != 0 && !handledValues.contains(v.value)) { + stream << " if (val & " << (1 << (v.value - 1)) + << ") { if (ret.length()) ret += QLatin1Char(\'|\'); ret += QStringLiteral(\"" << v.name << "\"); }\n"; + handledValues.append(v.value); + } + } + stream << " return ret;\n}\n"; +} + static void writeTracepoints(QTextStream &stream, const Provider &provider) { if (provider.tracepoints.isEmpty()) @@ -213,12 +262,20 @@ static void writeTracepoints(QTextStream &stream, const Provider &provider) stream << "#if !defined(" << includeGuard << ") && !defined(TRACEPOINT_DEFINE)\n" << "#define " << includeGuard << "\n" + << "QT_BEGIN_NAMESPACE\n" << "namespace QtPrivate {\n"; + for (const auto &enumeration : provider.enumerations) + writeEnumConverter(stream, enumeration); + + for (const auto &flag : provider.flags) + writeFlagConverter(stream, flag); + for (const Tracepoint &t : provider.tracepoints) - writeWrapper(stream, t, provider.name); + writeWrapper(stream, provider, t, provider.name); stream << "} // namespace QtPrivate\n" + << "QT_END_NAMESPACE\n" << "#endif // " << includeGuard << "\n\n"; } diff --git a/src/tools/tracegen/etw.h b/src/tools/tracegen/etw.h index 5fc9b57eaa..88508e2034 100644 --- a/src/tools/tracegen/etw.h +++ b/src/tools/tracegen/etw.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef ETW_H #define ETW_H diff --git a/src/tools/tracegen/helpers.cpp b/src/tools/tracegen/helpers.cpp index 8ffc088fed..0ea5848493 100644 --- a/src/tools/tracegen/helpers.cpp +++ b/src/tools/tracegen/helpers.cpp @@ -1,52 +1,33 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "helpers.h" #include <qdebug.h> +using namespace Qt::StringLiterals; + +void writeCommonPrologue(QTextStream &stream) +{ + stream << R"CPP( +#ifndef Q_TRACEPOINT +#error "Q_TRACEPOINT not set for the module, Q_TRACE not enabled." +#endif +)CPP"; +} + +QString typeToTypeName(const QString &name) +{ + QString ret = name; + return ret.replace(QStringLiteral("::"), QStringLiteral("_")); +} + QString includeGuard(const QString &filename) { QString guard = filename.toUpper(); for (int i = 0; i < guard.size(); ++i) { if (!guard.at(i).isLetterOrNumber()) - guard[i] = QLatin1Char('_'); + guard[i] = u'_'; } return guard; @@ -60,7 +41,7 @@ static QString joinArguments(const QList<Tracepoint::Argument> &args, T joinFunc for (const Tracepoint::Argument &arg : args) { if (!first) - ret += QLatin1String(", "); + ret += ", "_L1; ret += joinFunction(arg); @@ -77,14 +58,56 @@ QString formatFunctionSignature(const QList<Tracepoint::Argument> &args) }); } -QString formatParameterList(const QList<Tracepoint::Argument> &args, ParamType type) +QString formatParameterList(const Provider &provider, const QList<Tracepoint::Argument> &args, const QList<Tracepoint::Field> &fields, ParamType type) { if (type == LTTNG) { QString ret; - for (const Tracepoint::Argument &arg : args) - ret += QLatin1String(", ") + arg.name; + for (int i = 0; i < args.size(); i++) { + const Tracepoint::Argument &arg = args[i]; + const Tracepoint::Field &field = fields[i]; + if (field.backendType == Tracepoint::Field::FlagType) + ret += ", trace_convert_"_L1 + typeToTypeName(arg.type) + "("_L1 + arg.name + ")"_L1; + else + ret += ", "_L1 + arg.name; + } + return ret; + } + + auto findEnumeration = [](const QList<TraceEnum> &enums, const QString &name) { + for (const auto &e : enums) { + if (e.name == name) + return e; + } + return TraceEnum(); + }; + + if (type == CTF) { + QString ret; + for (int i = 0; i < args.size(); i++) { + const Tracepoint::Argument &arg = args[i]; + const Tracepoint::Field &field = fields[i]; + if (arg.arrayLen > 1) { + ret += ", trace::toByteArrayFromArray("_L1 + arg.name + ", "_L1 + QString::number(arg.arrayLen) + ") "_L1; + } else if (field.backendType == Tracepoint::Field::EnumeratedType) { + const TraceEnum &e = findEnumeration(provider.enumerations, arg.type); + QString integerType; + if (e.valueSize == 8) + integerType = QStringLiteral("quint8"); + else if (e.valueSize == 16) + integerType = QStringLiteral("quint16"); + else + integerType = QStringLiteral("quint32"); + ret += ", trace::toByteArrayFromEnum<"_L1 + integerType + ">("_L1 + arg.name + ")"_L1; + } else if (field.backendType == Tracepoint::Field::FlagType) { + ret += ", trace::toByteArrayFromFlags("_L1 + arg.name + ")"_L1; + } else if (field.backendType == Tracepoint::Field::String) { + ret += ", trace::toByteArrayFromCString("_L1 + arg.name + ")"_L1; + } else { + ret += ", "_L1 + arg.name; + } + } return ret; } diff --git a/src/tools/tracegen/helpers.h b/src/tools/tracegen/helpers.h index 8acf6e2292..ea6db016a6 100644 --- a/src/tools/tracegen/helpers.h +++ b/src/tools/tracegen/helpers.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef HELPERS_H #define HELPERS_H @@ -44,14 +8,30 @@ #include <qlist.h> #include <qstring.h> +#include <qtextstream.h> enum ParamType { LTTNG, - ETW + ETW, + CTF }; +QString typeToTypeName(const QString &type); QString includeGuard(const QString &filename); QString formatFunctionSignature(const QList<Tracepoint::Argument> &args); -QString formatParameterList(const QList<Tracepoint::Argument> &args, ParamType type); +QString formatParameterList(const Provider &provider, const QList<Tracepoint::Argument> &args, const QList<Tracepoint::Field> &fields, ParamType type); + +void writeCommonPrologue(QTextStream &stream); + +template <typename T> +static QString aggregateListValues(int value, const QList<T> &list) +{ + QStringList values; + for (const T &l : list) { + if (l.value == value) + values << l.name; + } + return values.join(QLatin1Char('_')); +} #endif // HELPERS_H diff --git a/src/tools/tracegen/lttng.cpp b/src/tools/tracegen/lttng.cpp index e628fbd680..9711570874 100644 --- a/src/tools/tracegen/lttng.cpp +++ b/src/tools/tracegen/lttng.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "lttng.h" #include "provider.h" @@ -48,23 +12,37 @@ #include <qtextstream.h> #include <qdebug.h> -static void writeCtfMacro(QTextStream &stream, const Tracepoint::Field &field) +static void writeCtfMacro(QTextStream &stream, const Provider &provider, const Tracepoint::Field &field) { const QString ¶mType = field.paramType; const QString &name = field.name; const QString &seqLen = field.seqLen; const int arrayLen = field.arrayLen; - switch (field.backendType) { - case Tracepoint::Field::Array: - stream << "ctf_array(" <<paramType << ", " - << name << ", " << name << ", " << arrayLen << ")"; + if (arrayLen > 0) { + if (paramType == QStringLiteral("double") || paramType == QStringLiteral("float")) { + const char *newline = nullptr; + for (int i = 0; i < arrayLen; i++) { + stream << newline; + stream << "ctf_float(" <<paramType << ", " << name << "_" << QString::number(i) << ", " + << name << "[" << QString::number(i) << "]" << ")"; + newline = "\n "; + } + + } else { + stream << "ctf_array(" <<paramType << ", " + << name << ", " << name << ", " << arrayLen << ")"; + } return; + } + + switch (field.backendType) { case Tracepoint::Field::Sequence: stream << "ctf_sequence(" << paramType << ", " << name << ", " << name << ", unsigned int, " << seqLen << ")"; return; + case Tracepoint::Field::Boolean: case Tracepoint::Field::Integer: stream << "ctf_integer(" << paramType << ", " << name << ", " << name << ")"; return; @@ -79,33 +57,51 @@ static void writeCtfMacro(QTextStream &stream, const Tracepoint::Field &field) stream << "ctf_string(" << name << ", " << name << ")"; return; case Tracepoint::Field::QtString: - stream << "ctf_sequence(const ushort, " << name << ", " - << name << ".utf16(), unsigned int, " << name << ".size())"; + stream << "ctf_string(" << name << ", " << name << ".toUtf8().constData())"; return; case Tracepoint::Field::QtByteArray: stream << "ctf_sequence(const char, " << name << ", " << name << ".constData(), unsigned int, " << name << ".size())"; return; case Tracepoint::Field::QtUrl: - stream << "ctf_sequence(const char, " << name << ", " - << name << ".toEncoded().constData(), unsigned int, " - << name << ".toEncoded().size())"; + stream << "ctf_string(" << name << ", " << name << ".toString().toUtf8().constData())"; return; case Tracepoint::Field::QtRect: - stream << "ctf_integer(int, x, " << name << ".x()) " - << "ctf_integer(int, y, " << name << ".y()) " - << "ctf_integer(int, width, " << name << ".width()) " - << "ctf_integer(int, height, " << name << ".height()) "; + stream << "ctf_integer(int, QRect_" << name << "_x, " << name << ".x()) " + << "ctf_integer(int, QRect_" << name << "_y, " << name << ".y()) " + << "ctf_integer(int, QRect_" << name << "_width, " << name << ".width()) " + << "ctf_integer(int, QRect_" << name << "_height, " << name << ".height()) "; + return; + case Tracepoint::Field::QtSizeF: + stream << "ctf_float(double, QSizeF_" << name << "_width, " << name << ".width()) " + << "ctf_float(double, QSizeF_" << name << "_height, " << name << ".height()) "; + return; + case Tracepoint::Field::QtRectF: + stream << "ctf_float(double, QRectF_" << name << "_x, " << name << ".x()) " + << "ctf_float(double, QRectF_" << name << "_y, " << name << ".y()) " + << "ctf_float(double, QRectF_" << name << "_width, " << name << ".width()) " + << "ctf_float(double, QRectF_" << name << "_height, " << name << ".height()) "; + return; + case Tracepoint::Field::QtSize: + stream << "ctf_integer(int, QSize_" << name << "_width, " << name << ".width()) " + << "ctf_integer(int, QSize_" << name << "_height, " << name << ".height()) "; + return; + case Tracepoint::Field::EnumeratedType: + stream << "ctf_enum(" << provider.name << ", " << typeToTypeName(paramType) << ", int, " << name << ", " << name << ") "; + return; + case Tracepoint::Field::FlagType: + stream << "ctf_sequence(const char , " << name << ", " + << name << ".constData(), unsigned int, " << name << ".size())"; return; case Tracepoint::Field::Unknown: - justified_worry("Cannot deduce CTF type for '%s %s'", qPrintable(paramType), - qPrintable(name)); + panic("Cannot deduce CTF type for '%s %s", qPrintable(paramType), qPrintable(name)); break; } } static void writePrologue(QTextStream &stream, const QString &fileName, const Provider &provider) { + writeCommonPrologue(stream); const QString guard = includeGuard(fileName); stream << "#undef TRACEPOINT_PROVIDER\n"; @@ -117,7 +113,7 @@ static void writePrologue(QTextStream &stream, const QString &fileName, const Pr stream << qtHeaders(); stream << "\n"; if (!provider.prefixText.isEmpty()) - stream << provider.prefixText.join(QLatin1Char('\n')) << "\n\n"; + stream << provider.prefixText.join(u'\n') << "\n\n"; stream << "#endif\n\n"; /* the first guard is the usual one, the second is required @@ -130,6 +126,12 @@ static void writePrologue(QTextStream &stream, const QString &fileName, const Pr << "#define TRACEPOINT_INCLUDE \"" << fileName << "\"\n\n"; stream << "#include <lttng/tracepoint.h>\n\n"; + + const QString namespaceGuard = guard + QStringLiteral("_USE_NAMESPACE"); + stream << "#if !defined(" << namespaceGuard << ")\n" + << "#define " << namespaceGuard << "\n" + << "QT_USE_NAMESPACE\n" + << "#endif // " << namespaceGuard << "\n\n"; } static void writeEpilogue(QTextStream &stream, const QString &fileName) @@ -141,12 +143,12 @@ static void writeEpilogue(QTextStream &stream, const QString &fileName) } static void writeWrapper(QTextStream &stream, - const Tracepoint &tracepoint, const QString &providerName) + const Tracepoint &tracepoint, const Provider &provider) { const QString argList = formatFunctionSignature(tracepoint.args); - const QString paramList = formatParameterList(tracepoint.args, LTTNG); + const QString paramList = formatParameterList(provider, tracepoint.args, tracepoint.fields, LTTNG); const QString &name = tracepoint.name; - const QString includeGuard = QStringLiteral("TP_%1_%2").arg(providerName).arg(name).toUpper(); + const QString includeGuard = QStringLiteral("TP_%1_%2").arg(provider.name).arg(name).toUpper(); /* prevents the redefinion of the inline wrapper functions * once LTTNG recursively includes this header file @@ -154,28 +156,30 @@ static void writeWrapper(QTextStream &stream, stream << "\n" << "#ifndef " << includeGuard << "\n" << "#define " << includeGuard << "\n" + << "QT_BEGIN_NAMESPACE\n" << "namespace QtPrivate {\n"; stream << "inline void trace_" << name << "(" << argList << ")\n" << "{\n" - << " tracepoint(" << providerName << ", " << name << paramList << ");\n" + << " tracepoint(" << provider.name << ", " << name << paramList << ");\n" << "}\n"; stream << "inline void do_trace_" << name << "(" << argList << ")\n" << "{\n" - << " do_tracepoint(" << providerName << ", " << name << paramList << ");\n" + << " do_tracepoint(" << provider.name << ", " << name << paramList << ");\n" << "}\n"; stream << "inline bool trace_" << name << "_enabled()\n" << "{\n" - << " return tracepoint_enabled(" << providerName << ", " << name << ");\n" + << " return tracepoint_enabled(" << provider.name << ", " << name << ");\n" << "}\n"; stream << "} // namespace QtPrivate\n" + << "QT_END_NAMESPACE\n" << "#endif // " << includeGuard << "\n\n"; } -static void writeTracepoint(QTextStream &stream, +static void writeTracepoint(QTextStream &stream, const Provider &provider, const Tracepoint &tracepoint, const QString &providerName) { stream << "TRACEPOINT_EVENT(\n" @@ -185,8 +189,13 @@ static void writeTracepoint(QTextStream &stream, const char *comma = nullptr; - for (const Tracepoint::Argument &arg : tracepoint.args) { - stream << comma << arg.type << ", " << arg.name; + for (int i = 0; i < tracepoint.args.size(); i++) { + const auto &arg = tracepoint.args[i]; + const auto &field = tracepoint.fields[i]; + if (field.backendType == Tracepoint::Field::FlagType) + stream << comma << "QByteArray, " << arg.name; + else + stream << comma << arg.type << ", " << arg.name; comma = ", "; } @@ -197,18 +206,82 @@ static void writeTracepoint(QTextStream &stream, for (const Tracepoint::Field &f : tracepoint.fields) { stream << newline; - writeCtfMacro(stream, f); + writeCtfMacro(stream, provider, f); newline = "\n "; } stream << ")\n)\n\n"; } +static void writeEnums(QTextStream &stream, const Provider &provider) +{ + for (const auto &e : provider.enumerations) { + stream << "TRACEPOINT_ENUM(\n" + << " " << provider.name << ",\n" + << " " << typeToTypeName(e.name) << ",\n" + << " TP_ENUM_VALUES(\n"; + QList<int> handledValues; + for (const auto &v : e.values) { + if (v.range > 0) { + stream << " ctf_enum_range(\"" << v.name << "\", " << v.value << ", " << v.range << ")\n"; + } else if (!handledValues.contains(v.value)) { + stream << " ctf_enum_value(\"" << aggregateListValues(v.value, e.values) << "\", " << v.value << ")\n"; + handledValues.append(v.value); + } + } + stream << " )\n)\n\n"; + } +} + +static void writeFlags(QTextStream &stream, const Provider &provider) +{ + for (const auto &f : provider.flags) { + stream << "TRACEPOINT_ENUM(\n" + << " " << provider.name << ",\n" + << " " << typeToTypeName(f.name) << ",\n" + << " TP_ENUM_VALUES(\n"; + QList<int> handledValues; + for (const auto &v : f.values) { + if (!handledValues.contains(v.value)) { + stream << " ctf_enum_value(\"" << aggregateListValues(v.value, f.values) << "\", " << v.value << ")\n"; + handledValues.append(v.value); + } + } + stream << " )\n)\n\n"; + } + + // converters + const QString includeGuard = QStringLiteral("TP_%1_CONVERTERS").arg(provider.name).toUpper(); + stream << "\n" + << "#ifndef " << includeGuard << "\n" + << "#define " << includeGuard << "\n"; + stream << "QT_BEGIN_NAMESPACE\n"; + stream << "namespace QtPrivate {\n"; + for (const auto &f : provider.flags) { + stream << "inline QByteArray trace_convert_" << typeToTypeName(f.name) << "(" << f.name << " val)\n"; + stream << "{\n"; + stream << " QByteArray ret;\n"; + stream << " if (val == 0) { ret.append((char)0); return ret; }\n"; + + for (const auto &v : f.values) { + if (!v.value) + continue; + stream << " if (val & " << (1 << (v.value - 1)) << ") { ret.append((char)" << v.value << "); };\n"; + } + stream << " return ret;\n"; + stream << "}\n"; + + } + stream << "} // namespace QtPrivate\n" + << "QT_END_NAMESPACE\n\n" + << "#endif // " << includeGuard << "\n\n"; +} + static void writeTracepoints(QTextStream &stream, const Provider &provider) { for (const Tracepoint &t : provider.tracepoints) { - writeTracepoint(stream, t, provider.name); - writeWrapper(stream, t, provider.name); + writeTracepoint(stream, provider, t, provider.name); + writeWrapper(stream, t, provider); } } @@ -219,6 +292,9 @@ void writeLttng(QFile &file, const Provider &provider) const QString fileName = QFileInfo(file.fileName()).fileName(); writePrologue(stream, fileName, provider); + writeEnums(stream, provider); + writeFlags(stream, provider); writeTracepoints(stream, provider); writeEpilogue(stream, fileName); } + diff --git a/src/tools/tracegen/lttng.h b/src/tools/tracegen/lttng.h index 0307b375bc..c36c70a1d4 100644 --- a/src/tools/tracegen/lttng.h +++ b/src/tools/tracegen/lttng.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef LTTNG_H #define LTTNG_H diff --git a/src/tools/tracegen/panic.cpp b/src/tools/tracegen/panic.cpp index ac95b2e9e8..fa4e6b3ee3 100644 --- a/src/tools/tracegen/panic.cpp +++ b/src/tools/tracegen/panic.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "panic.h" @@ -57,16 +21,3 @@ void panic(const char *fmt, ...) exit(EXIT_FAILURE); } - -void justified_worry(const char *fmt, ...) -{ - va_list ap; - - fprintf(stderr, "tracegen: warning: "); - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - fputc('\n', stderr); -} diff --git a/src/tools/tracegen/panic.h b/src/tools/tracegen/panic.h index 58e56c0116..ee635a8aeb 100644 --- a/src/tools/tracegen/panic.h +++ b/src/tools/tracegen/panic.h @@ -1,46 +1,9 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef PANIC_H #define PANIC_H void panic(const char *fmt, ...); -void justified_worry(const char *fmt, ...); #endif // PANIC_H diff --git a/src/tools/tracegen/provider.cpp b/src/tools/tracegen/provider.cpp index 7984fa12a7..bdd669c9cd 100644 --- a/src/tools/tracegen/provider.cpp +++ b/src/tools/tracegen/provider.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "provider.h" #include "panic.h" @@ -45,6 +9,9 @@ #include <qtextstream.h> #include <qregularexpression.h> #include <qstring.h> +#include <qtpreprocessorsupport.h> + +using namespace Qt::StringLiterals; #ifdef TRACEGEN_DEBUG #include <qdebug.h> @@ -129,89 +96,115 @@ static QString removeBraces(QString type) return type.remove(rx); } -static Tracepoint::Field::BackendType backendType(QString rawType) +#define TYPEDATA_ENTRY(type, backendType) \ +{ QT_STRINGIFY(type), backendType } + +static Tracepoint::Field::Type backendType(QString rawType) { - static const struct { + static const struct TypeData { const char *type; - Tracepoint::Field::BackendType backendType; + Tracepoint::Field::Type backendType; } typeTable[] = { - { "bool", Tracepoint::Field::Integer }, - { "short_int", Tracepoint::Field::Integer }, - { "signed_short", Tracepoint::Field::Integer }, - { "signed_short_int", Tracepoint::Field::Integer }, - { "unsigned_short", Tracepoint::Field::Integer }, - { "unsigned_short_int", Tracepoint::Field::Integer }, - { "int", Tracepoint::Field::Integer }, - { "signed", Tracepoint::Field::Integer }, - { "signed_int", Tracepoint::Field::Integer }, - { "unsigned", Tracepoint::Field::Integer }, - { "unsigned_int", Tracepoint::Field::Integer }, - { "long", Tracepoint::Field::Integer }, - { "long_int", Tracepoint::Field::Integer }, - { "signed_long", Tracepoint::Field::Integer }, - { "signed_long_int", Tracepoint::Field::Integer }, - { "unsigned_long", Tracepoint::Field::Integer }, - { "unsigned_long_int", Tracepoint::Field::Integer }, - { "long_long", Tracepoint::Field::Integer }, - { "long_long_int", Tracepoint::Field::Integer }, - { "signed_long_long", Tracepoint::Field::Integer }, - { "signed_long_long_int", Tracepoint::Field::Integer }, - { "unsigned_long_long", Tracepoint::Field::Integer }, - { "char", Tracepoint::Field::Integer }, - { "intptr_t", Tracepoint::Field::IntegerHex }, - { "uintptr_t", Tracepoint::Field::IntegerHex }, - { "std::intptr_t", Tracepoint::Field::IntegerHex }, - { "std::uintptr_t", Tracepoint::Field::IntegerHex }, - { "float", Tracepoint::Field::Float }, - { "double", Tracepoint::Field::Float }, - { "long_double", Tracepoint::Field::Float }, - { "QString", Tracepoint::Field::QtString }, - { "QByteArray", Tracepoint::Field::QtByteArray }, - { "QUrl", Tracepoint::Field::QtUrl }, - { "QRect", Tracepoint::Field::QtRect } + TYPEDATA_ENTRY(short_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(signed_short, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(signed_short_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(unsigned_short, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(unsigned_short_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(unsigned_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(signed_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(long_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(signed_long, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(signed_long_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(unsigned_long, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(unsigned_long_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(long_long, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(long_long_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(signed_long_long, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(signed_long_long_int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(unsigned_long_long, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(bool, Tracepoint::Field::Boolean), + TYPEDATA_ENTRY(int, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(signed, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(unsigned, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(long, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(qint64, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(char, Tracepoint::Field::Integer), + TYPEDATA_ENTRY(intptr_t, Tracepoint::Field::IntegerHex), + TYPEDATA_ENTRY(uintptr_t, Tracepoint::Field::IntegerHex), + TYPEDATA_ENTRY(std::intptr_t, Tracepoint::Field::IntegerHex), + TYPEDATA_ENTRY(std::uintptr_t, Tracepoint::Field::IntegerHex), + TYPEDATA_ENTRY(float, Tracepoint::Field::Float), + TYPEDATA_ENTRY(double, Tracepoint::Field::Float), + TYPEDATA_ENTRY(long double, Tracepoint::Field::Float), + TYPEDATA_ENTRY(QString, Tracepoint::Field::QtString), + TYPEDATA_ENTRY(QByteArray, Tracepoint::Field::QtByteArray), + TYPEDATA_ENTRY(QUrl, Tracepoint::Field::QtUrl), + TYPEDATA_ENTRY(QRect, Tracepoint::Field::QtRect), + TYPEDATA_ENTRY(QSize, Tracepoint::Field::QtSize), + TYPEDATA_ENTRY(QRectF, Tracepoint::Field::QtRectF), + TYPEDATA_ENTRY(QSizeF, Tracepoint::Field::QtSizeF) }; auto backendType = [](const QString &rawType) { static const size_t tableSize = sizeof (typeTable) / sizeof (typeTable[0]); for (size_t i = 0; i < tableSize; ++i) { - if (rawType == QLatin1String(typeTable[i].type)) - return typeTable[i].backendType; + if (rawType == QLatin1StringView(typeTable[i].type)) + return typeTable[i]; } - return Tracepoint::Field::Unknown; + TypeData unknown = { nullptr, Tracepoint::Field::Unknown }; + return unknown; }; - if (arrayLength(rawType) > 0) - return Tracepoint::Field::Array; + int arrayLen = arrayLength(rawType); + if (arrayLen > 0) + rawType = removeBraces(rawType); if (!sequenceLength(rawType).isNull()) return Tracepoint::Field::Sequence; static const QRegularExpression constMatch(QStringLiteral("\\bconst\\b")); rawType.remove(constMatch); - rawType.remove(QLatin1Char('&')); + rawType.remove(u'&'); static const QRegularExpression ptrMatch(QStringLiteral("\\s*\\*\\s*")); rawType.replace(ptrMatch, QStringLiteral("_ptr")); rawType = rawType.trimmed(); rawType.replace(QStringLiteral(" "), QStringLiteral("_")); - if (rawType == QLatin1String("char_ptr")) + if (rawType == "char_ptr"_L1) return Tracepoint::Field::String; - if (rawType.endsWith(QLatin1String("_ptr"))) + if (rawType.endsWith("_ptr"_L1)) return Tracepoint::Field::Pointer; - return backendType(rawType); + TypeData d = backendType(rawType); + return d.backendType; } -static Tracepoint parseTracepoint(const QString &name, const QStringList &args, +static Tracepoint parseTracepoint(const Provider &provider, const QString &name, const QStringList &args, const QString &fileName, const int lineNumber) { Tracepoint t; t.name = name; + auto findEnumeration = [](const QList<TraceEnum> &enums, const QString &name) { + for (const auto &e : enums) { + if (e.name == name) + return e; + } + return TraceEnum(); + }; + auto findFlags = [](const QList<TraceFlags> &flags, const QString &name) { + for (const auto &f : flags) { + if (f.name == name) + return f; + } + return TraceFlags(); + }; + + if (args.isEmpty()) return t; @@ -247,14 +240,23 @@ static Tracepoint parseTracepoint(const QString &name, const QStringList &args, t.args << std::move(a); - Tracepoint::Field f; - f.backendType = backendType(type); - f.paramType = removeBraces(type); - f.name = name; - f.arrayLen = arrayLen; - f.seqLen = sequenceLength(type); + Tracepoint::Field field; + const TraceEnum &e = findEnumeration(provider.enumerations, type); + const TraceFlags &f = findFlags(provider.flags, type); + if (!e.name.isEmpty()) { + field.backendType = Tracepoint::Field::EnumeratedType; + field.enumValueSize = e.valueSize; + } else if (!f.name.isEmpty()) { + field.backendType = Tracepoint::Field::FlagType; + } else { + field.backendType = backendType(type); + } + field.paramType = removeBraces(type); + field.name = name; + field.arrayLen = arrayLen; + field.seqLen = sequenceLength(type); - t.fields << std::move(f); + t.fields << std::move(field); ++i; } @@ -262,6 +264,31 @@ static Tracepoint parseTracepoint(const QString &name, const QStringList &args, return t; } +static int minumumValueSize(int min, int max) +{ + if (min < 0) { + if (min >= std::numeric_limits<char>::min() && max <= std::numeric_limits<char>::max()) + return 8; + if (min >= std::numeric_limits<short>::min() && max <= std::numeric_limits<short>::max()) + return 16; + return 32; + } + if (max <= std::numeric_limits<unsigned char>::max()) + return 8; + if (max <= std::numeric_limits<unsigned short>::max()) + return 16; + return 32; +} + +static bool isPow2OrZero(quint32 value) +{ + return (value & (value - 1)) == 0; +} + +static quint32 pow2Log2(quint32 v) { + return 32 - qCountLeadingZeroBits(v); +} + Provider parseProvider(const QString &filename) { QFile f(filename); @@ -272,35 +299,140 @@ Provider parseProvider(const QString &filename) QTextStream s(&f); static const QRegularExpression tracedef(QStringLiteral("^([A-Za-z][A-Za-z0-9_]*)\\((.*)\\)$")); + static const QRegularExpression enumenddef(QStringLiteral("^} ?([A-Za-z][A-Za-z0-9_:]*);")); + static const QRegularExpression enumdef(QStringLiteral("^([A-Za-z][A-Za-z0-9_]*)( *= *([xabcdef0-9]*))?")); + static const QRegularExpression rangedef(QStringLiteral("^RANGE\\(([A-Za-z][A-Za-z0-9_]*) ?, ?([0-9]*) ?... ?([0-9]*) ?\\)")); Provider provider; provider.name = QFileInfo(filename).baseName(); bool parsingPrefixText = false; + bool parsingEnum = false; + bool parsingFlags = false; + TraceEnum currentEnum; + TraceFlags currentFlags; + int currentEnumValue = 0; + int minEnumValue = std::numeric_limits<int>::max(); + int maxEnumValue = std::numeric_limits<int>::min(); for (int lineNumber = 1; !s.atEnd(); ++lineNumber) { QString line = s.readLine().trimmed(); - if (line == QLatin1String("{")) { + if (line == "{"_L1) { parsingPrefixText = true; continue; - } else if (parsingPrefixText && line == QLatin1String("}")) { + } else if (parsingPrefixText && line == "}"_L1) { parsingPrefixText = false; continue; } else if (parsingPrefixText) { provider.prefixText.append(line); continue; + } else if (line == "ENUM {"_L1) { + parsingEnum = true; + continue; + } else if (line == "FLAGS {"_L1) { + parsingFlags = true; + continue; + } else if (line.startsWith("}"_L1) && (parsingEnum || parsingFlags)) { + auto match = enumenddef.match(line); + if (match.hasMatch()) { + if (parsingEnum) { + currentEnum.name = match.captured(1); + currentEnum.valueSize = minumumValueSize(minEnumValue, maxEnumValue); + provider.enumerations.push_back(currentEnum); + currentEnum = TraceEnum(); + parsingEnum = false; + } else { + currentFlags.name = match.captured(1); + provider.flags.push_back(currentFlags); + currentFlags = TraceFlags(); + parsingFlags = false; + } + + minEnumValue = std::numeric_limits<int>::max(); + maxEnumValue = std::numeric_limits<int>::min(); + } else { + panic("Syntax error while processing '%s' line %d:\n" + " '%s' end of enum/flags does not match", + qPrintable(filename), lineNumber, + qPrintable(line)); + } + + continue; } - if (line.isEmpty() || line.startsWith(QLatin1Char('#'))) + if (line.isEmpty() || line.startsWith(u'#')) continue; + if (parsingEnum || parsingFlags) { + auto m = enumdef.match(line); + if (parsingEnum && line.startsWith(QStringLiteral("RANGE"))) { + auto m = rangedef.match(line); + if (m.hasMatch()) { + TraceEnum::EnumValue value; + value.name = m.captured(1); + value.value = m.captured(2).toInt(); + value.range = m.captured(3).toInt(); + currentEnumValue = value.range + 1; + currentEnum.values.push_back(value); + maxEnumValue = qMax(maxEnumValue, value.range); + minEnumValue = qMin(minEnumValue, value.value); + } + } else if (m.hasMatch()) { + if (m.hasCaptured(3)) { + if (parsingEnum) { + TraceEnum::EnumValue value; + value.name = m.captured(1); + value.value = m.captured(3).toInt(); + value.range = 0; + currentEnumValue = value.value + 1; + currentEnum.values.push_back(value); + maxEnumValue = qMax(maxEnumValue, value.value); + minEnumValue = qMin(minEnumValue, value.value); + } else { + TraceFlags::FlagValue value; + value.name = m.captured(1); + if (m.captured(3).startsWith(QStringLiteral("0x"))) + value.value = m.captured(3).toInt(nullptr, 16); + else + value.value = m.captured(3).toInt(); + if (!isPow2OrZero(value.value)) { + printf("Warning: '%s' line %d:\n" + " '%s' flag value is not power of two.\n", + qPrintable(filename), lineNumber, + qPrintable(line)); + } else { + value.value = pow2Log2(value.value); + currentFlags.values.push_back(value); + } + } + } else { + maxEnumValue = qMax(maxEnumValue, currentEnumValue); + minEnumValue = qMin(minEnumValue, currentEnumValue); + if (parsingEnum) { + currentEnum.values.push_back({m.captured(0), currentEnumValue++, 0}); + } else { + panic("Syntax error while processing '%s' line %d:\n" + " '%s' flags value not set", + qPrintable(filename), lineNumber, + qPrintable(line)); + } + } + } else { + panic("Syntax error while processing '%s' line %d:\n" + " '%s' enum/flags does not match", + qPrintable(filename), lineNumber, + qPrintable(line)); + } + continue; + } + auto match = tracedef.match(line); if (match.hasMatch()) { const QString name = match.captured(1); const QString argsString = match.captured(2); - const QStringList args = argsString.split(QLatin1Char(','), Qt::SkipEmptyParts); + const QStringList args = argsString.split(u',', Qt::SkipEmptyParts); - provider.tracepoints << parseTracepoint(name, args, filename, lineNumber); + provider.tracepoints << parseTracepoint(provider, name, args, filename, lineNumber); } else { panic("Syntax error while processing '%s' line %d:\n" " '%s' does not look like a tracepoint definition", diff --git a/src/tools/tracegen/provider.h b/src/tools/tracegen/provider.h index 99b10b13f1..e5e99b868e 100644 --- a/src/tools/tracegen/provider.h +++ b/src/tools/tracegen/provider.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef PROVIDER_H #define PROVIDER_H @@ -56,9 +20,9 @@ struct Tracepoint struct Field { - enum BackendType { - Array, + enum Type { Sequence, + Boolean, Integer, IntegerHex, Float, @@ -68,13 +32,18 @@ struct Tracepoint QtByteArray, QtUrl, QtRect, + QtSize, + QtRectF, + QtSizeF, + EnumeratedType, + FlagType, Unknown }; - - BackendType backendType; + Type backendType; QString paramType; QString name; int arrayLen; + int enumValueSize; QString seqLen; }; @@ -83,17 +52,41 @@ struct Tracepoint QList<Field> fields; }; +struct TraceEnum { + QString name; + struct EnumValue { + QString name; + int value; + int range; + }; + QList<EnumValue> values; + int valueSize; +}; + +struct TraceFlags { + QString name; + struct FlagValue { + QString name; + int value; + }; + QList<FlagValue> values; +}; + +Q_DECLARE_TYPEINFO(TraceEnum, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(TraceFlags, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(Tracepoint::Argument, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(Tracepoint::Field, Q_RELOCATABLE_TYPE); +Q_DECLARE_TYPEINFO(Tracepoint, Q_RELOCATABLE_TYPE); + struct Provider { QString name; QList<Tracepoint> tracepoints; QStringList prefixText; + QList<TraceEnum> enumerations; + QList<TraceFlags> flags; }; Provider parseProvider(const QString &filename); -Q_DECLARE_TYPEINFO(Tracepoint::Argument, Q_RELOCATABLE_TYPE); -Q_DECLARE_TYPEINFO(Tracepoint::Field, Q_RELOCATABLE_TYPE); -Q_DECLARE_TYPEINFO(Tracepoint, Q_RELOCATABLE_TYPE); - #endif // PROVIDER_H diff --git a/src/tools/tracegen/qtheaders.cpp b/src/tools/tracegen/qtheaders.cpp index eec3488a6d..237c22b237 100644 --- a/src/tools/tracegen/qtheaders.cpp +++ b/src/tools/tracegen/qtheaders.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qtheaders.h" diff --git a/src/tools/tracegen/qtheaders.h b/src/tools/tracegen/qtheaders.h index b80d374ca8..86405c9479 100644 --- a/src/tools/tracegen/qtheaders.h +++ b/src/tools/tracegen/qtheaders.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef QTHEADERS_H #define QTHEADERS_H diff --git a/src/tools/tracegen/tracegen.cpp b/src/tools/tracegen/tracegen.cpp index 978fe406d0..776d81675d 100644 --- a/src/tools/tracegen/tracegen.cpp +++ b/src/tools/tracegen/tracegen.cpp @@ -1,43 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $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$ -** -****************************************************************************/ +// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto <rafael.roquetto@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "provider.h" +#include "ctf.h" #include "lttng.h" #include "etw.h" #include "panic.h" @@ -48,12 +13,13 @@ enum class Target { LTTNG, - ETW + ETW, + CTF, }; static inline void usage(int status) { - printf("Usage: tracegen <lttng|etw> <input file> <output file>\n"); + printf("Usage: tracegen <lttng|etw|ctf> <input file> <output file>\n"); exit(status); } @@ -70,13 +36,15 @@ static void parseArgs(int argc, char *argv[], Target *target, QString *inFile, Q *target = Target::LTTNG; } else if (qstrcmp(targetString, "etw") == 0) { *target = Target::ETW; + } else if (qstrcmp(targetString, "ctf") == 0) { + *target = Target::CTF; } else { fprintf(stderr, "Invalid target: %s\n", targetString); usage(EXIT_FAILURE); } - *inFile = QLatin1String(argv[2]); - *outFile = QLatin1String(argv[3]); + *inFile = QLatin1StringView(argv[2]); + *outFile = QLatin1StringView(argv[3]); } int main(int argc, char *argv[]) @@ -97,6 +65,9 @@ int main(int argc, char *argv[]) } switch (target) { + case Target::CTF: + writeCtf(out, p); + break; case Target::LTTNG: writeLttng(out, p); break; diff --git a/src/tools/tracepointgen/CMakeLists.txt b/src/tools/tracepointgen/CMakeLists.txt new file mode 100644 index 0000000000..2f473d376a --- /dev/null +++ b/src/tools/tracepointgen/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tracepointgen Tool: +##################################################################### + +qt_get_tool_target_name(target_name tracepointgen) +qt_internal_add_tool(${target_name} + CORE_LIBRARY Bootstrap + INSTALL_DIR "${INSTALL_LIBEXECDIR}" + TOOLS_TARGET Core + SOURCES + tracepointgen.cpp tracepointgen.h + parser.cpp parser.h +) +qt_internal_return_unless_building_tools() diff --git a/src/tools/tracepointgen/parser.cpp b/src/tools/tracepointgen/parser.cpp new file mode 100644 index 0000000000..ad94b9a433 --- /dev/null +++ b/src/tools/tracepointgen/parser.cpp @@ -0,0 +1,609 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tracepointgen.h" +#include "parser.h" +#include <qtextstream.h> +#include <qregularexpression.h> +#include <qfileinfo.h> + +static void removeOffsetRange(qsizetype begin, qsizetype end, QList<LineNumber> &offsets) +{ + qsizetype count = end - begin; + qsizetype i = 0; + DEBUGPRINTF2(printf("tracepointgen: removeOffsetRange: %llu %llu\n", begin, end)); + while (i < offsets.size()) { + LineNumber &cur = offsets[i]; + if (begin > cur.end) { + i++; + } else if (begin >= cur.begin && begin <= cur.end) { + cur.end = begin; + i++; + } else if (begin < cur.begin && end > cur.end) { + offsets.remove(i); + DEBUGPRINTF2(printf("tracepointgen: removeOffsetRange: %llu, %llu, %d\n", cur.begin, cur.end, cur.line)); + } else if (end >= cur.begin && end <= cur.end) { + cur.begin = begin; + cur.end -= count; + i++; + } else if (end < cur.begin) { + cur.begin -= count; + cur.end -= count; + i++; + } + } +} + +static bool findSpaceRange(const QString &data, qsizetype &offset, qsizetype &end) { + qsizetype cur = data.indexOf(QLatin1Char(' '), offset); + if (cur >= 0) { + qsizetype i = cur + 1; + while (data.constData()[i] == QLatin1Char(' ')) i++; + if (i - cur > 1) { + offset = cur; + end = i - 1; + return true; + } + cur = data.indexOf(QLatin1Char(' '), cur + 1); + } + return false; +} + +static void simplifyData(QString &data, QList<LineNumber> &offsets) +{ + qsizetype offset = data.indexOf(QStringLiteral("//")); + while (offset >= 0) { + qsizetype endOfLine = data.indexOf(QLatin1Char('\n'), offset); + if (endOfLine == -1) + endOfLine = data.length(); + removeOffsetRange(offset, endOfLine, offsets); + data.remove(offset, endOfLine - offset); + offset = data.indexOf(QStringLiteral("//"), offset); + } + offset = data.indexOf(QStringLiteral("/*")); + while (offset >= 0) { + qsizetype endOfComment = data.indexOf(QStringLiteral("*/"), offset); + if (endOfComment == -1) + break; + removeOffsetRange(offset, endOfComment + 2, offsets); + data.remove(offset, endOfComment - offset + 2); + offset = data.indexOf(QStringLiteral("/*"), offset); + } + offset = 0; + qsizetype end = 0; + data.replace(QLatin1Char('\n'), QLatin1Char(' ')); + while (findSpaceRange(data, offset, end)) { + removeOffsetRange(offset, end, offsets); + data.remove(offset, end - offset); + } +} + +static void simplifyData(QString &data) +{ + qsizetype offset = data.indexOf(QStringLiteral("//")); + while (offset >= 0) { + qsizetype endOfLine = data.indexOf(QLatin1Char('\n'), offset); + if (endOfLine == -1) + endOfLine = data.length(); + data.remove(offset, endOfLine - offset); + offset = data.indexOf(QStringLiteral("//"), offset); + } + offset = data.indexOf(QStringLiteral("/*")); + while (offset >= 0) { + qsizetype endOfComment = data.indexOf(QStringLiteral("*/"), offset); + if (endOfComment == -1) + break; + data.remove(offset, endOfComment - offset + 2); + offset = data.indexOf(QStringLiteral("/*"), offset); + } + offset = 0; + qsizetype end = 0; + while (findSpaceRange(data, offset, end)) + data.remove(offset, end - offset); +} + +static QString preprocessMetadata(const QString &in) +{ + DEBUGPRINTF(printf("in: %s\n", qPrintable(in))); + QList<QString> lines = in.split(QLatin1Char('\\')); + QString out; + for (int i = 0; i < lines.size(); i++) { + QString l = lines.at(i).simplified(); + DEBUGPRINTF(printf("line: %s\n", qPrintable(l))); + if (l.length() < 2) + continue; + if (l.startsWith(QStringLiteral("\""))) + l = l.right(l.length() - 1); + if (l.endsWith(QStringLiteral("\""))) + l = l.left(l.length() - 1); + l = l.simplified(); + + if (l.length() > 1) { + if (out.size() > 0) + out.append(QLatin1Char('\n')); + out.append(l); + } + } + DEBUGPRINTF(printf("out: %s\n", qPrintable(out))); + return out; +} + +int Parser::lineNumber(qsizetype offset) const +{ + DEBUGPRINTF(printf("tracepointgen: lineNumber: offset %llu, line count: %llu\n", offset , m_offsets.size())); + for (const auto line : m_offsets) { + DEBUGPRINTF(printf("tracepointgen: lineNumber: %llu %llu %d\n", line.begin, line.end, line.line)); + if (offset >= line.begin && offset <= line.end) + return line.line; + } + return 0; +} + +void Parser::parseParamReplace(const QString &data, qsizetype offset, const QString &name) +{ + Replace rep; + qsizetype beginBrace = data.indexOf(QLatin1Char('('), offset); + qsizetype endBrace = data.indexOf(QLatin1Char(')'), beginBrace); + QString params = data.mid(beginBrace + 1, endBrace - beginBrace -1); + int punc = params.indexOf(QLatin1Char(',')); + if (punc < 0) + panic("Syntax error in Q_TRACE_PARAM_REPLACE at file %s, line %llu", qPrintable(name), lineNumber(offset)); + rep.in = params.left(punc).simplified(); + rep.out = params.right(params.length() - punc - 1).simplified(); + if (rep.in.endsWith(QLatin1Char('*')) || rep.out.endsWith(QLatin1Char(']'))) + rep.out.append(QLatin1Char(' ')); + DEBUGPRINTF(printf("tracepointgen: replace: %s with %s\n", qPrintable(rep.in), qPrintable(rep.out))); + m_replaces.push_back(rep); +} + +void Parser::parseInstrument(const QString &data, qsizetype offset) +{ + qsizetype beginOfProvider = data.indexOf(QLatin1Char('('), offset); + qsizetype endOfProvider = data.indexOf(QLatin1Char(')'), beginOfProvider); + Function func; + QString provider = data.mid(beginOfProvider + 1, endOfProvider - beginOfProvider - 1).simplified(); + if (provider != m_provider) + return; + + qsizetype classMarker = data.indexOf(QStringLiteral("::"), endOfProvider); + qsizetype beginOfFunctionMarker = data.indexOf(QLatin1Char('{'), classMarker); + QString begin = data.mid(endOfProvider + 1, classMarker - endOfProvider - 1); + QString end = data.mid(classMarker + 2, beginOfFunctionMarker - classMarker - 2); + int spaceIndex = begin.lastIndexOf(QLatin1Char(' ')); + if (spaceIndex == -1) + func.className = begin; + else + func.className = begin.mid(spaceIndex + 1, begin.length() - spaceIndex - 1); + qsizetype braceIndex = end.indexOf(QLatin1Char('(')); + spaceIndex = end.indexOf(QLatin1Char(' ')); + if (spaceIndex < braceIndex) + func.functionName = end.left(spaceIndex).simplified(); + else + func.functionName = end.left(braceIndex).simplified(); + + qsizetype lastBraceIndex = end.lastIndexOf(QLatin1Char(')')); + func.functionParameters = end.mid(braceIndex + 1, lastBraceIndex - braceIndex - 1).simplified(); + + DEBUGPRINTF(printf("tracepointgen: %s(%s)\n", qPrintable(func.functionName), qPrintable(func.functionParameters))); + + m_functions.push_back(func); +} + +void Parser::parsePoint(const QString &data, qsizetype offset) +{ + qsizetype beginOfProvider = data.indexOf(QLatin1Char('('), offset); + qsizetype endOfProvider = data.indexOf(QLatin1Char(','), beginOfProvider); + Point point; + QString provider = data.mid(beginOfProvider + 1, endOfProvider - beginOfProvider - 1).simplified(); + if (provider != m_provider) + return; + + qsizetype endOfPoint = data.indexOf(QLatin1Char(','), endOfProvider + 1); + qsizetype endOfPoint2 = data.indexOf(QLatin1Char(')'), endOfProvider + 1); + bool params = true; + if (endOfPoint == -1 || endOfPoint2 < endOfPoint) { + endOfPoint = endOfPoint2; + params = false; + } + point.name = data.mid(endOfProvider + 1, endOfPoint - endOfProvider - 1).simplified(); + if (params) { + int endOfParams = data.indexOf(QLatin1Char(')'), endOfPoint); + point.parameters = data.mid(endOfPoint + 1, endOfParams - endOfPoint - 1).simplified(); + } + + DEBUGPRINTF(printf("tracepointgen: %s(%s)\n", qPrintable(point.name), qPrintable(point.parameters))); + + m_points.push_back(point); +} + +void Parser::parsePrefix(const QString &data, qsizetype offset) +{ + qsizetype beginOfProvider = data.indexOf(QLatin1Char('('), offset); + qsizetype endOfProvider = data.indexOf(QLatin1Char(','), beginOfProvider); + QString prefix; + QString provider = data.mid(beginOfProvider + 1, endOfProvider - beginOfProvider - 1).simplified(); + if (provider != m_provider) + return; + + qsizetype endOfPoint = data.indexOf(QLatin1Char(')'), endOfProvider + 1); + prefix = data.mid(endOfProvider + 1, endOfPoint - endOfProvider - 1).simplified(); + + DEBUGPRINTF(printf("tracepointgen: prefix: %s\n", qPrintable(prefix))); + + if (!m_prefixes.contains(prefix)) + m_prefixes.push_back(preprocessMetadata(prefix)); +} + +QStringList Parser::findEnumValues(const QString &name, const QStringList &includes) +{ + QStringList split = name.split(QStringLiteral("::")); + QString enumName = split.last(); + DEBUGPRINTF(printf("searching for %s\n", qPrintable(name))); + QStringList ret; + for (const QString &filename : includes) { + QFile input(filename); + if (!input.open(QIODevice::ReadOnly | QIODevice::Text)) { + DEBUGPRINTF(printf("Cannot open '%s' for reading: %s\n", + qPrintable(filename), qPrintable(input.errorString()))); + return ret; + } + QString data; + QTextStream stream(&input); + while (!stream.atEnd()) { + QString line = stream.readLine().trimmed(); + data += line + QLatin1Char('\n'); + } + simplifyData(data); + + int pos = 0; + bool valid = true; + for (int i = 0; i < split.size() - 1; i++) { + QRegularExpression macro(QStringLiteral("(struct|class|namespace) +([A-Za-z0-9_]*)? +([A-Za-z0-9]*;?)")); + QRegularExpressionMatchIterator m = macro.globalMatch(data); + bool found = false; + while (m.hasNext() && !found) { + QRegularExpressionMatch match = m.next(); + QString n = match.captured(2); + if (!n.endsWith(QLatin1Char(';')) && n == split[i] && match.capturedStart(2) > pos) { + pos = match.capturedStart(2); + found = true; + break; + } + if (match.hasCaptured(3)) { + n = match.captured(3); + if (!n.endsWith(QLatin1Char(';')) && n == split[i] && match.capturedStart(3) > pos) { + pos = match.capturedStart(3); + found = true; + break; + } + } + } + if (!found) { + valid = false; + break; + } + } + + if (valid) { + QRegularExpression macro(QStringLiteral("enum +([A-Za-z0-9_]*)")); + QRegularExpressionMatchIterator m = macro.globalMatch(data); + while (m.hasNext()) { + QRegularExpressionMatch match = m.next(); + + if (match.capturedStart() < pos) + continue; + + QString n = match.captured(1); + + if (n == enumName) { + DEBUGPRINTF(printf("Found enum: %s\n", qPrintable(n))); + int begin = data.indexOf(QLatin1Char('{'), match.capturedEnd()); + int end = data.indexOf(QLatin1Char('}'), begin); + QString block = data.mid(begin + 1, end - begin - 1); + const QStringList enums = block.split(QLatin1Char('\n')); + for (const auto &e : enums) { + const auto trimmed = e.trimmed(); + if (!trimmed.isEmpty() && !trimmed.startsWith(QLatin1Char('#'))) + ret << trimmed; + } + + return ret; + } + } + } + } + return ret; +} + +struct EnumNameValue +{ + QString name; + QString valueStr; + int value; +}; + +static QList<EnumNameValue> enumsToValues(const QStringList &values) +{ + int cur = 0; + QList<EnumNameValue> ret; + for (const QString &value : values) { + EnumNameValue r; + if (value.contains(QLatin1Char('='))) { + size_t offset = value.indexOf(QLatin1Char('=')); + r.name = value.left(offset).trimmed(); + QString val = value.right(value.length() - offset - 1).trimmed(); + if (val.endsWith(QLatin1Char(','))) + val = val.left(val.length() - 1); + bool valid = false; + int integer = val.toInt(&valid); + if (!valid) + integer = val.toInt(&valid, 16); + if (valid) { + cur = r.value = integer; + ret << r; + } else { + auto iter = std::find_if(ret.begin(), ret.end(), [&val](const EnumNameValue &elem){ + return elem.name == val; + }); + if (iter != ret.end()) { + cur = r.value = iter->value; + ret << r; + } else { + DEBUGPRINTF(printf("Invalid value: %s %s\n", qPrintable(r.name), qPrintable(value))); + } + } + } else { + if (value.endsWith(QLatin1Char(','))) + r.name = value.left(value.length() - 1); + else + r.name = value; + r.value = ++cur; + ret << r; + } + } + return ret; +} + +void Parser::parseMetadata(const QString &data, qsizetype offset, const QStringList &includes) +{ + qsizetype beginOfProvider = data.indexOf(QLatin1Char('('), offset); + qsizetype endOfProvider = data.indexOf(QLatin1Char(','), beginOfProvider); + QString metadata; + QString provider = data.mid(beginOfProvider + 1, endOfProvider - beginOfProvider - 1).simplified(); + if (provider != m_provider) + return; + + qsizetype endOfPoint = data.indexOf(QLatin1Char(')'), endOfProvider + 1); + metadata = data.mid(endOfProvider + 1, endOfPoint - endOfProvider - 1).simplified(); + + DEBUGPRINTF(printf("tracepointgen: metadata: %s", qPrintable(metadata))); + + QString preprocessed = preprocessMetadata(metadata); + + DEBUGPRINTF2(printf("preprocessed %s\n", qPrintable(preprocessed))); + + QRegularExpression macro(QStringLiteral("([A-Z]*) ?{ ?([A-Za-z0-9=_,. ]*) ?} ?([A-Za-z0-9_:]*) ?;")); + QRegularExpressionMatchIterator i = macro.globalMatch(preprocessed); + qsizetype prev = 0; + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + QString values = match.captured(2).trimmed(); + int cur = match.capturedStart(); + if (cur > prev) + m_metadata.append(preprocessed.mid(prev, cur - prev)); + + prev = match.capturedEnd() + 1; + DEBUGPRINTF2(printf("values: %s\n", qPrintable(values))); + if (values.isEmpty() || values.startsWith(QStringLiteral("AUTO"))) { + values.replace(QLatin1Char('\n'), QLatin1Char(' ')); + QStringList ranges; + if (values.contains(QStringLiteral("RANGE"))) { + QRegularExpression rangeMacro(QStringLiteral("RANGE +([A-Za-z0-9_]*) +... +([A-Za-z0-9_]*)")); + QRegularExpressionMatchIterator r = rangeMacro.globalMatch(values); + while (r.hasNext()) { + QRegularExpressionMatch rm = r.next(); + ranges << rm.captured(1); + ranges << rm.captured(2); + DEBUGPRINTF2(printf("range: %s ... %s\n", qPrintable(rm.captured(1)), qPrintable(rm.captured(2)))); + } + } + + const auto enumOrFlag = match.captured(1); + const auto name = match.captured(3); + const bool flags = enumOrFlag == QStringLiteral("FLAGS"); + + QStringList values = findEnumValues(name, includes); + if (values.isEmpty()) { + if (flags && name.endsWith(QLatin1Char('s'))) + values = findEnumValues(name.left(name.length() - 1), includes); + if (values.isEmpty()) { + DEBUGPRINTF(printf("Unable to find values for %s\n", qPrintable(name))); + } + } + if (!values.isEmpty()) { + auto moreValues = enumsToValues(values); + if (ranges.size()) { + for (int i = 0; i < ranges.size() / 2; i++) { + bool rangeFound = false; + for (auto &v : moreValues) { + if (v.name == ranges[2 * i]) { + rangeFound = true; + QString rangeEnd = ranges[2 * i + 1]; + auto iter = std::find_if(moreValues.begin(), moreValues.end(), [&rangeEnd](const EnumNameValue &elem){ + return elem.name == rangeEnd; + }); + if (iter != moreValues.end()) + v.valueStr = QStringLiteral("RANGE(%1, %2 ... %3)").arg(v.name).arg(v.value).arg(iter->value); + else + panic("Unable to find range end: %s\n", qPrintable(rangeEnd)); + break; + } + } + if (rangeFound == false) + panic("Unable to find range begin: %s\n", qPrintable(ranges[2 * i])); + } + } + std::sort(moreValues.begin(), moreValues.end(), [](const EnumNameValue &a, const EnumNameValue &b) { + return a.value < b.value; + }); + values.clear(); + int prevValue = std::as_const(moreValues).front().value; + for (const auto &v : std::as_const(moreValues)) { + QString a; + if (v.valueStr.isNull()) { + if (v.value == prevValue + 1 && !flags) + a = v.name; + else + a = QStringLiteral("%1 = %2").arg(v.name).arg(v.value); + prevValue = v.value; + } else { + a = v.valueStr; + } + values << a; + } + + metadata = QStringLiteral("%1 {\n %2 \n} %3;").arg(enumOrFlag).arg(values.join(QStringLiteral(",\n"))).arg(name); + if (!m_metadata.contains(metadata)) + m_metadata.append(metadata); + } + } else { + if (!m_metadata.contains(match.captured())) + m_metadata.append(match.captured()); + } + } + if (prev < preprocessed.length()) + m_metadata.append(preprocessed.mid(prev, preprocessed.length() - prev)); +} + +QString Parser::resolveInclude(const QString &filename) +{ + QFileInfo info(filename); + if (info.exists()) + return info.absoluteFilePath(); + for (const QString &sp : std::as_const(m_includeDirs)) { + info = QFileInfo(sp + QLatin1Char('/') + filename); + if (info.exists()) + return info.absoluteFilePath(); + } + return {}; +} + +void Parser::addIncludesRecursive(const QString &filename, QList<QString> &includes) +{ + QFileInfo info(filename); + DEBUGPRINTF(printf("check include: %s\n", qPrintable(filename))); + QFile input(filename); + if (!input.open(QIODevice::ReadOnly | QIODevice::Text)) { + DEBUGPRINTF(printf("Cannot open '%s' for reading: %s\n", + qPrintable(filename), qPrintable(input.errorString()))); + return; + } + QString data; + QTextStream stream(&input); + while (!stream.atEnd()) { + QString line = stream.readLine().trimmed(); + data += line + QLatin1Char(QLatin1Char('\n')); + } + + QRegularExpression includeMacro(QStringLiteral("#include [\"<]([A-Za-z0-9_./]*.h)[\">]")); + QRegularExpressionMatchIterator i = includeMacro.globalMatch(data); + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + QString filename = match.captured(1); + + QString rinc = filename; + if (filename.startsWith(QStringLiteral("../"))) { + QFileInfo info2(info.absolutePath() + QLatin1Char('/') + filename); + if (!info2.exists()) { + DEBUGPRINTF(printf("unable to find %s\n", qPrintable(filename))); + continue; + } + rinc = info2.absoluteFilePath(); + filename = info2.fileName(); + } + + // only search possible qt headers + if (filename.startsWith(QLatin1Char('q'), Qt::CaseInsensitive)) { + QString resolved = resolveInclude(rinc); + if (!resolved.isEmpty() && !includes.contains(resolved)) { + includes.push_back(resolved); + addIncludesRecursive(resolved, includes); + } + } + } +} + +void Parser::parse(QIODevice &input, const QString &name) +{ + QString data; + QTextStream stream(&input); + int lineNumber = 1; + qsizetype prev = 0; + while (!stream.atEnd()) { + QString line = stream.readLine().trimmed(); + m_offsets.push_back({prev, prev + line.length(), lineNumber++}); + prev += line.length() + 1; + data += line + QLatin1Char(QLatin1Char('\n')); + } + + simplifyData(data, m_offsets); + + QStringList includes; + + QRegularExpression includeMacro(QStringLiteral("#include [\"<]([A-Za-z0-9_./]*.h)[\">]")); + QRegularExpressionMatchIterator i = includeMacro.globalMatch(data); + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + const QString filename = match.captured(1); + // only search possible qt headers + if (filename.startsWith(QLatin1Char('q'), Qt::CaseInsensitive)) { + const QString resolved = resolveInclude(filename); + if (!resolved.isEmpty() && !includes.contains(resolved)) { + includes.push_back(resolved); + addIncludesRecursive(resolved, includes); + } + } + } + + QRegularExpression traceMacro(QStringLiteral("Q_TRACE_([A-Z_]*)")); + i = traceMacro.globalMatch(data); + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + + QString macroType = match.captured(1); + if (macroType == QStringLiteral("PARAM_REPLACE")) + parseParamReplace(data, match.capturedEnd(), name); + else if (macroType == QStringLiteral("INSTRUMENT")) + parseInstrument(data, match.capturedEnd()); + else if (macroType == QStringLiteral("POINT")) + parsePoint(data, match.capturedEnd()); + else if (macroType == QStringLiteral("PREFIX")) + parsePrefix(data, match.capturedEnd()); + else if (macroType == QStringLiteral("METADATA")) + parseMetadata(data, match.capturedEnd(), includes); + } + + for (auto &func : m_functions) { + for (auto &rep : m_replaces) + func.functionParameters.replace(rep.in, rep.out); + } +} + +void Parser::write(QIODevice &input) const +{ + QTextStream out(&input); + if (m_prefixes.size() > 0) { + out << QStringLiteral("{\n"); + for (const auto &prefix : m_prefixes) + out << prefix << "\n"; + out << QStringLiteral("}\n"); + } + for (const auto &m : m_metadata) + out << m << "\n"; + for (const auto &func : m_functions) { + out << func.className << "_" << func.functionName << "_entry(" << func.functionParameters << ")\n"; + out << func.className << "_" << func.functionName << "_exit()\n"; + } + for (const auto &point : m_points) + out << point.name << "(" << point.parameters << ")\n"; +} diff --git a/src/tools/tracepointgen/parser.h b/src/tools/tracepointgen/parser.h new file mode 100644 index 0000000000..389734983d --- /dev/null +++ b/src/tools/tracepointgen/parser.h @@ -0,0 +1,77 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PARSER_H +#define PARSER_H + +#include <qiodevice.h> +#include <qlist.h> +#include <qbytearray.h> + +struct Function +{ + QString className; + QString functionName; + QString functionParameters; +}; + +struct Point +{ + QString name; + QString parameters; +}; + +struct Replace +{ + QString in; + QString out; +}; + +struct LineNumber +{ + qsizetype begin; + qsizetype end; + int line; +}; + +struct Parser +{ + Parser(const QString &provider) + : m_provider(provider) + { + + } + + void addIncludeDirs(const QStringList &list) + { + m_includeDirs.append(list); + } + QString resolveInclude(const QString &filename); + void addIncludesRecursive(const QString &filename, QStringList &includes); + QStringList findEnumValues(const QString &name, const QStringList &includes); + + void parseParamReplace(const QString &data, qsizetype offset, const QString &name); + void parseInstrument(const QString &data, qsizetype offset); + void parsePoint(const QString &data, qsizetype offset); + void parsePrefix(const QString &data, qsizetype offset); + void parseMetadata(const QString &data, qsizetype offset, const QStringList &includes); + int lineNumber(qsizetype offset) const; + + void parse(QIODevice &input, const QString &name); + void write(QIODevice &input) const; + bool isEmpty() const + { + return m_functions.isEmpty() && m_points.isEmpty(); + } + + QList<Function> m_functions; + QList<Point> m_points; + QList<Replace> m_replaces; + QList<QString> m_prefixes; + QList<QString> m_metadata; + QList<LineNumber> m_offsets; + QList<QString> m_includeDirs; + QString m_provider; +}; + +#endif // PARSER_H diff --git a/src/tools/tracepointgen/tracepointgen.cpp b/src/tools/tracepointgen/tracepointgen.cpp new file mode 100644 index 0000000000..d814c69873 --- /dev/null +++ b/src/tools/tracepointgen/tracepointgen.cpp @@ -0,0 +1,69 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <qstring.h> +#include <qstringlist.h> +#include <qfile.h> + +#include "tracepointgen.h" +#include "parser.h" + +static void usage(int status) +{ + printf("Generates a tracepoint file for tracegen tool from input files.\n\n"); + printf("Usage: tracepointgen <output file> <input files> \n"); + exit(status); +} + +static void parseArgs(int argc, char *argv[], QString &provider, QString &outFile, QList<QString> &inputFiles) +{ + if (argc == 1) + usage(0); + if (argc < 4) + usage(-1); + + provider = QLatin1StringView(argv[1]); + outFile = QLatin1StringView(argv[2]); + for (int i = 3; i < argc; i++) + inputFiles.append(QLatin1StringView(argv[i])); +} + +int main(int argc, char *argv[]) +{ + QString provider; + QList<QString> inputFiles; + QString outFile; + + parseArgs(argc, argv, provider, outFile, inputFiles); + + Parser parser(provider); + + for (const QString &inputFile : inputFiles) { + if (inputFile.startsWith(QLatin1Char('I'))) { + QStringList includeDirs = inputFile.right(inputFile.length() - 1).split(QLatin1Char(';')); + parser.addIncludeDirs(includeDirs); + continue; + } + QFile in(inputFile); + if (!in.open(QIODevice::ReadOnly | QIODevice::Text)) { + panic("Cannot open '%s' for reading: %s\n", + qPrintable(inputFile), qPrintable(in.errorString())); + } + DEBUGPRINTF(printf("tracepointgen: parse %s\n", qPrintable(inputFile))); + parser.parse(in, inputFile); + } + if (parser.isEmpty()) + panic("empty provider %s\n", qPrintable(provider)); + + QFile out(outFile); + + if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + panic("Cannot open '%s' for writing: %s\n", + qPrintable(outFile), qPrintable(out.errorString())); + } + + parser.write(out); + out.close(); + + return 0; +} diff --git a/src/tools/tracepointgen/tracepointgen.h b/src/tools/tracepointgen/tracepointgen.h new file mode 100644 index 0000000000..6aed3dc574 --- /dev/null +++ b/src/tools/tracepointgen/tracepointgen.h @@ -0,0 +1,42 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TRACEPOINTGEN_H +#define TRACEPOINTGEN_H + +#include <cstdarg> +#include <cstdio> +#include <cstdlib> + +#define DEBUG_TRACEPOINTGEN 0 + +#if DEBUG_TRACEPOINTGEN > 0 + #define DEBUGPRINTF(x) x + #if (DEBUG_TRACEPOINTGEN > 1) + #define DEBUGPRINTF2(x) x + #else + #define DEBUGPRINTF2(x) + #endif +#else + #define DEBUGPRINTF(x) + #define DEBUGPRINTF2(x) +#endif + + + +inline void panic(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "tracepointgen: fatal: "); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputc('\n', stderr); + + exit(EXIT_FAILURE); +} + +#endif diff --git a/src/tools/uic/CMakeLists.txt b/src/tools/uic/CMakeLists.txt index e0747cbc50..9f47ec8b4b 100644 --- a/src/tools/uic/CMakeLists.txt +++ b/src/tools/uic/CMakeLists.txt @@ -1,4 +1,5 @@ -# Generated from uic.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### ## uic Tool: @@ -6,9 +7,10 @@ qt_get_tool_target_name(target_name uic) qt_internal_add_tool(${target_name} + TRY_RUN TARGET_DESCRIPTION "Qt User Interface Compiler" INSTALL_DIR "${INSTALL_LIBEXECDIR}" - TOOLS_TARGET Widgets # special case + TOOLS_TARGET Widgets SOURCES cpp/cppwritedeclaration.cpp cpp/cppwritedeclaration.h cpp/cppwriteincludes.cpp cpp/cppwriteincludes.h @@ -30,6 +32,7 @@ qt_internal_add_tool(${target_name} DEFINES QT_NO_CAST_FROM_ASCII QT_NO_FOREACH + QT_USE_NODISCARD_FILE_OPEN QT_UIC INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR} @@ -37,13 +40,5 @@ qt_internal_add_tool(${target_name} cpp python shared - #PUBLIC_LIBRARIES # special case remove - #Qt::Gui # special case remove ) - -#### Keys ignored in scope 1:.:.:uic.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "Qt User Interface Compiler" -# _OPTION = "host_build" - -## Scopes: -##################################################################### +qt_internal_return_unless_building_tools() diff --git a/src/tools/uic/cpp/cppwritedeclaration.cpp b/src/tools/uic/cpp/cppwritedeclaration.cpp index 995b99b692..8261963cfa 100644 --- a/src/tools/uic/cpp/cppwritedeclaration.cpp +++ b/src/tools/uic/cpp/cppwritedeclaration.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cppwritedeclaration.h" #include "cppwriteinitialization.h" @@ -39,6 +14,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + namespace { void openNameSpaces(const QStringList &namespaceList, QTextStream &output) { @@ -75,10 +52,10 @@ void WriteDeclaration::acceptUI(DomUI *node) QString exportMacro = node->elementExportMacro(); if (!exportMacro.isEmpty()) - exportMacro.append(QLatin1Char(' ')); + exportMacro.append(u' '); - QStringList namespaceList = qualifiedClassName.split(QLatin1String("::")); - if (namespaceList.count()) { + QStringList namespaceList = qualifiedClassName.split("::"_L1); + if (namespaceList.size()) { className = namespaceList.last(); namespaceList.removeLast(); } @@ -88,15 +65,15 @@ void WriteDeclaration::acceptUI(DomUI *node) // is a User using Qt-in-namespace having his own classes not in a namespace. // In this case the generated Ui helper classes will also end up in // the Qt namespace (which is harmless, but not "pretty") - const bool needsMacro = namespaceList.count() == 0 - || namespaceList[0] == QLatin1String("qdesigner_internal"); + const bool needsMacro = m_option.qtNamespace && + (namespaceList.size() == 0 || namespaceList[0] == "qdesigner_internal"_L1); if (needsMacro) m_output << "QT_BEGIN_NAMESPACE\n\n"; openNameSpaces(namespaceList, m_output); - if (namespaceList.count()) + if (namespaceList.size()) m_output << "\n"; m_output << "class " << exportMacro << m_option.prefix << className << "\n" @@ -105,7 +82,7 @@ void WriteDeclaration::acceptUI(DomUI *node) const QStringList connections = m_uic->databaseInfo()->connections(); for (const QString &connection : connections) { - if (connection != QLatin1String("(default)")) + if (connection != "(default)"_L1) m_output << m_option.indent << "QSqlDatabase " << connection << "Connection;\n"; } @@ -121,11 +98,11 @@ void WriteDeclaration::acceptUI(DomUI *node) closeNameSpaces(namespaceList, m_output); - if (namespaceList.count()) + if (namespaceList.size()) m_output << "\n"; if (m_option.generateNamespace && !m_option.prefix.isEmpty()) { - namespaceList.append(QLatin1String("Ui")); + namespaceList.append("Ui"_L1); openNameSpaces(namespaceList, m_output); @@ -133,7 +110,7 @@ void WriteDeclaration::acceptUI(DomUI *node) closeNameSpaces(namespaceList, m_output); - if (namespaceList.count()) + if (namespaceList.size()) m_output << "\n"; } @@ -143,7 +120,7 @@ void WriteDeclaration::acceptUI(DomUI *node) void WriteDeclaration::acceptWidget(DomWidget *node) { - QString className = QLatin1String("QWidget"); + QString className = u"QWidget"_s; if (node->hasAttributeClass()) className = node->attributeClass(); @@ -160,7 +137,7 @@ void WriteDeclaration::acceptSpacer(DomSpacer *node) void WriteDeclaration::acceptLayout(DomLayout *node) { - QString className = QLatin1String("QLayout"); + QString className = u"QLayout"_s; if (node->hasAttributeClass()) className = node->attributeClass(); diff --git a/src/tools/uic/cpp/cppwritedeclaration.h b/src/tools/uic/cpp/cppwritedeclaration.h index f83bfe8f50..2dcb5981e1 100644 --- a/src/tools/uic/cpp/cppwritedeclaration.h +++ b/src/tools/uic/cpp/cppwritedeclaration.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef CPPWRITEDECLARATION_H #define CPPWRITEDECLARATION_H diff --git a/src/tools/uic/cpp/cppwriteincludes.cpp b/src/tools/uic/cpp/cppwriteincludes.cpp index 809c78f700..7cf7c4e59e 100644 --- a/src/tools/uic/cpp/cppwriteincludes.cpp +++ b/src/tools/uic/cpp/cppwriteincludes.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cppwriteincludes.h" #include "driver.h" @@ -40,6 +15,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + enum { debugWriteIncludes = 0 }; enum { warnHeaderGeneration = 0 }; @@ -47,7 +24,7 @@ enum { warnHeaderGeneration = 0 }; static inline QString moduleHeader(const QString &module, const QString &header) { QString rc = module; - rc += QLatin1Char('/'); + rc += u'/'; rc += header; return rc; } @@ -60,11 +37,11 @@ WriteIncludes::WriteIncludes(Uic *uic) : WriteIncludesBase(uic), // When possible (no namespace) use the "QtModule/QClass" convention // and create a re-mapping of the old header "qclass.h" to it. Do not do this // for the "Phonon::Someclass" classes, however. - const QString namespaceDelimiter = QLatin1String("::"); + const QLatin1StringView namespaceDelimiter = "::"_L1; for (const auto &e : classInfoEntries()) { - const QString klass = QLatin1String(e.klass); - const QString module = QLatin1String(e.module); - QLatin1String header = QLatin1String(e.header); + const QString klass = QLatin1StringView(e.klass); + const QString module = QLatin1StringView(e.module); + QLatin1StringView header = QLatin1StringView(e.header); if (klass.contains(namespaceDelimiter)) { m_classToHeader.insert(klass, moduleHeader(module, header)); } else { @@ -113,7 +90,7 @@ void WriteIncludes::insertIncludeForClass(const QString &className, QString head // Quick check by class name to detect includehints provided for custom widgets. // Remove namespaces QString lowerClassName = className.toLower(); - static const QString namespaceSeparator = QLatin1String("::"); + static const auto namespaceSeparator = "::"_L1; const int namespaceIndex = lowerClassName.lastIndexOf(namespaceSeparator); if (namespaceIndex != -1) lowerClassName.remove(0, namespaceIndex + namespaceSeparator.size()); @@ -126,7 +103,7 @@ void WriteIncludes::insertIncludeForClass(const QString &className, QString head if (!uic()->option().implicitIncludes) break; header = lowerClassName; - header += QLatin1String(".h"); + header += ".h"_L1; if (warnHeaderGeneration) { qWarning("%s: Warning: generated header '%s' for class '%s'.", qPrintable(uic()->option().messagePrefix()), @@ -157,7 +134,7 @@ void WriteIncludes::addCppCustomWidget(const QString &className, const DomCustom QString header; bool global = false; if (!m_classToHeader.contains(className)) { - global = domHeader->attributeLocation().toLower() == QLatin1String("global"); + global = domHeader->attributeLocation().toLower() == "global"_L1; header = domHeader->text(); } insertIncludeForClass(className, header, global); @@ -169,7 +146,7 @@ void WriteIncludes::acceptInclude(DomInclude *node) { bool global = true; if (node->hasAttributeLocation()) - global = node->attributeLocation() == QLatin1String("global"); + global = node->attributeLocation() == "global"_L1; insertInclude(node->text(), global); } @@ -190,8 +167,8 @@ void WriteIncludes::insertInclude(const QString &header, bool global) void WriteIncludes::writeHeaders(const OrderedSet &headers, bool global) { - const QChar openingQuote = global ? QLatin1Char('<') : QLatin1Char('"'); - const QChar closingQuote = global ? QLatin1Char('>') : QLatin1Char('"'); + const QChar openingQuote = global ? u'<' : u'"'; + const QChar closingQuote = global ? u'>' : u'"'; // Check for the old headers 'qslider.h' and replace by 'QtGui/QSlider' for (const QString &header : headers) { diff --git a/src/tools/uic/cpp/cppwriteincludes.h b/src/tools/uic/cpp/cppwriteincludes.h index 4ff2a514fb..1ec10f39b2 100644 --- a/src/tools/uic/cpp/cppwriteincludes.h +++ b/src/tools/uic/cpp/cppwriteincludes.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef CPPWRITEINCLUDES_H #define CPPWRITEINCLUDES_H diff --git a/src/tools/uic/cpp/cppwriteinitialization.cpp b/src/tools/uic/cpp/cppwriteinitialization.cpp index 9471fd22d9..205d6a50a9 100644 --- a/src/tools/uic/cpp/cppwriteinitialization.cpp +++ b/src/tools/uic/cpp/cppwriteinitialization.cpp @@ -1,32 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cppwriteinitialization.h" +#include "customwidgetsinfo.h" #include "driver.h" #include "ui4.h" #include "utils.h" @@ -45,17 +21,47 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + namespace { + + // Expand "Horizontal", "Qt::Horizontal" to "Qt::Orientation::Horizontal" + QString expandEnum(QString value, const QString &prefix) + { + if (value.startsWith(prefix)) + return value; + const auto pos = value.lastIndexOf("::"_L1); + if (pos == -1) + return prefix + "::"_L1 + value; + value.replace(0, pos, prefix); + return value; + } + + inline QString expandSizePolicyEnum(const QString &value) + { + return expandEnum(value, "QSizePolicy::Policy"_L1); + } + + inline QString expandToolBarArea(const QString &value) + { + return expandEnum(value, "Qt::ToolBarArea"_L1); + } + + inline QString expandDockWidgetArea(const QString &value) + { + return expandEnum(value, "Qt::DockWidgetArea"_L1); + } + // figure out the toolbar area of a DOM attrib list. // By legacy, it is stored as an integer. As of 4.3.0, it is the enumeration value. QString toolBarAreaStringFromDOMAttributes(const CPP::WriteInitialization::DomPropertyMap &attributes) { - const DomProperty *pstyle = attributes.value(QLatin1String("toolBarArea")); + const DomProperty *pstyle = attributes.value("toolBarArea"_L1); QString result; if (!pstyle) return result; switch (pstyle->kind()) { case DomProperty::Number: - result = QLatin1String(language::toolbarArea(pstyle->elementNumber())); + result = language::toolbarArea(pstyle->elementNumber()); break; case DomProperty::Enum: result = pstyle->elementEnum(); @@ -63,9 +69,7 @@ namespace { default: break; } - if (!result.startsWith(QLatin1String("Qt::"))) - result.prepend(QLatin1String("Qt::")); - return result + QLatin1String(", "); + return expandToolBarArea(result) + ", "_L1; } // Write a statement to create a spacer item. @@ -75,7 +79,7 @@ namespace { int w = 0; int h = 0; - if (const DomProperty *sh = properties.value(QLatin1String("sizeHint"))) { + if (const DomProperty *sh = properties.value("sizeHint"_L1)) { if (const DomSize *sizeHint = sh->elementSize()) { w = sizeHint->elementWidth(); h = sizeHint->elementHeight(); @@ -84,27 +88,17 @@ namespace { output << w << ", " << h << ", "; // size type - QString sizeType; - if (const DomProperty *st = properties.value(QLatin1String("sizeType"))) { - const QString value = st->elementEnum(); - if (value.startsWith(QLatin1String("QSizePolicy::"))) - sizeType = value; - else - sizeType = QLatin1String("QSizePolicy::") + value; - } else { - sizeType = QStringLiteral("QSizePolicy::Expanding"); - } + const DomProperty *st = properties.value("sizeType"_L1); + QString horizType = st != nullptr ? st->elementEnum() : "Expanding"_L1; + QString vertType = "Minimum"_L1; // orientation - bool isVspacer = false; - if (const DomProperty *o = properties.value(QLatin1String("orientation"))) { - const QString orientation = o->elementEnum(); - if (orientation == QLatin1String("Qt::Vertical") || orientation == QLatin1String("Vertical")) - isVspacer = true; - } - const QString horizType = isVspacer ? QLatin1String("QSizePolicy::Minimum") : sizeType; - const QString vertType = isVspacer ? sizeType : QLatin1String("QSizePolicy::Minimum"); - output << language::enumValue(horizType) << ", " << language::enumValue(vertType) << ')'; + const DomProperty *o = properties.value("orientation"_L1); + if (o != nullptr && o->elementEnum().endsWith("Vertical"_L1)) + std::swap(horizType, vertType); + + output << language::enumValue(expandSizePolicyEnum(horizType)) << ", " + << language::enumValue(expandSizePolicyEnum(vertType)) << ')'; } @@ -143,18 +137,18 @@ namespace { // ### fixme Qt 7 remove this: Exclude deprecated properties of Qt 5. case DomProperty::Set: if (p->attributeName() == u"features" - && customWidgetsInfo->extends(className, QLatin1String("QDockWidget")) + && customWidgetsInfo->extends(className, "QDockWidget") && p->elementSet() == u"QDockWidget::AllDockWidgetFeatures") { - const QString msg = fileName + QLatin1String(": Warning: Deprecated enum value QDockWidget::AllDockWidgetFeatures was encountered."); + const QString msg = fileName + ": Warning: Deprecated enum value QDockWidget::AllDockWidgetFeatures was encountered."_L1; qWarning("%s", qPrintable(msg)); return false; } break; case DomProperty::Enum: if (p->attributeName() == u"sizeAdjustPolicy" - && customWidgetsInfo->extends(className, QLatin1String("QComboBox")) + && customWidgetsInfo->extends(className, "QComboBox") && p->elementEnum() == u"QComboBox::AdjustToMinimumContentsLength") { - const QString msg = fileName + QLatin1String(": Warning: Deprecated enum value QComboBox::AdjustToMinimumContentsLength was encountered."); + const QString msg = fileName + ": Warning: Deprecated enum value QComboBox::AdjustToMinimumContentsLength was encountered."_L1; qWarning("%s", qPrintable(msg)); return false; } @@ -202,6 +196,15 @@ FontHandle::FontHandle(const DomFont *domFont) : { } +static QString fontWeight(const DomFont *domFont) +{ + if (domFont->hasElementFontWeight()) + return domFont->elementFontWeight(); + if (domFont->hasElementBold()) + return domFont->elementBold() ? u"Bold"_s : u"Normal"_s; + return {}; +} + int FontHandle::compare(const FontHandle &rhs) const { const QString family = m_domFont->hasElementFamily() ? m_domFont->elementFamily() : QString(); @@ -216,10 +219,10 @@ int FontHandle::compare(const FontHandle &rhs) const if (const int crc = compareInt(pointSize, rhsPointSize)) return crc; - const int bold = m_domFont->hasElementBold() ? (m_domFont->elementBold() ? 1 : 0) : -1; - const int rhsBold = rhs.m_domFont->hasElementBold() ? (rhs.m_domFont->elementBold() ? 1 : 0) : -1; - if (const int crc = compareInt(bold, rhsBold)) - return crc; + const QString fontWeight = CPP::fontWeight(m_domFont); + const QString rhsFontWeight = CPP::fontWeight(rhs.m_domFont); + if (const int wrc = fontWeight.compare(rhsFontWeight)) + return wrc; const int italic = m_domFont->hasElementItalic() ? (m_domFont->elementItalic() ? 1 : 0) : -1; const int rhsItalic = rhs.m_domFont->hasElementItalic() ? (rhs.m_domFont->elementItalic() ? 1 : 0) : -1; @@ -231,11 +234,6 @@ int FontHandle::compare(const FontHandle &rhs) const if (const int crc = compareInt(underline, rhsUnderline)) return crc; - const int weight = m_domFont->hasElementWeight() ? m_domFont->elementWeight() : -1; - const int rhsWeight = rhs.m_domFont->hasElementWeight() ? rhs.m_domFont->elementWeight() : -1; - if (const int crc = compareInt(weight, rhsWeight)) - return crc; - const int strikeOut = m_domFont->hasElementStrikeOut() ? (m_domFont->elementStrikeOut() ? 1 : 0) : -1; const int rhsStrikeOut = rhs.m_domFont->hasElementStrikeOut() ? (rhs.m_domFont->elementStrikeOut() ? 1 : 0) : -1; if (const int crc = compareInt(strikeOut, rhsStrikeOut)) @@ -257,6 +255,13 @@ int FontHandle::compare(const FontHandle &rhs) const if (const int src = styleStrategy.compare(rhsStyleStrategy)) return src; + const QString hintingPreference = m_domFont->hasElementHintingPreference() + ? m_domFont->elementHintingPreference() : QString(); + const QString rhsHintingPreference = rhs.m_domFont->hasElementHintingPreference() + ? rhs.m_domFont->elementHintingPreference() : QString(); + if (const int src = hintingPreference.compare(rhsHintingPreference)) + return src; + return 0; } @@ -387,12 +392,12 @@ void WriteInitialization::LayoutDefaultHandler::acceptLayoutFunction(DomLayoutFu if (node->hasAttributeMargin()) { m_state[Margin] |= HasDefaultFunction; m_functions[Margin] = node->attributeMargin(); - m_functions[Margin] += QLatin1String("()"); + m_functions[Margin] += "()"_L1; } if (node->hasAttributeSpacing()) { m_state[Spacing] |= HasDefaultFunction; m_functions[Spacing] = node->attributeSpacing(); - m_functions[Spacing] += QLatin1String("()"); + m_functions[Spacing] += "()"_L1; } } @@ -400,7 +405,7 @@ static inline void writeContentsMargins(const QString &indent, const QString &ob { QString contentsMargins; QTextStream(&contentsMargins) << value << ", " << value << ", " << value << ", " << value; - writeSetter(indent, objectName, QLatin1String("setContentsMargins"), contentsMargins, str); + writeSetter(indent, objectName, "setContentsMargins"_L1, contentsMargins, str); } void WriteInitialization::LayoutDefaultHandler::writeProperty(int p, const QString &indent, const QString &objectName, @@ -454,11 +459,11 @@ void WriteInitialization::LayoutDefaultHandler::writeProperties(const QString &i // Write out properties and ignore the ones found in // subsequent writing of the property list. int defaultSpacing = marginType == WriteInitialization::Use43UiFile ? -1 : 6; - writeProperty(Spacing, indent, varName, properties, QLatin1String("spacing"), QLatin1String("setSpacing"), + writeProperty(Spacing, indent, varName, properties, "spacing"_L1, "setSpacing"_L1, defaultSpacing, false, str); // We use 9 as TopLevelMargin, since Designer seem to always use 9. static const int layoutmargins[4] = {-1, 9, 9, 0}; - writeProperty(Margin, indent, varName, properties, QLatin1String("margin"), QLatin1String("setMargin"), + writeProperty(Margin, indent, varName, properties, "margin"_L1, "setMargin"_L1, layoutmargins[marginType], suppressMarginDefault, str); } @@ -517,16 +522,16 @@ void WriteInitialization::acceptUI(DomUI *node) const QString widgetClassName = node->elementWidget()->attributeClass(); - const QString parameterType = widgetClassName + QLatin1String(" *"); + const QString parameterType = widgetClassName + " *"_L1; m_output << m_option.indent << language::startFunctionDefinition1("setupUi", parameterType, varName, m_option.indent); const QStringList connections = m_uic->databaseInfo()->connections(); for (const auto &connection : connections) { - if (connection == QLatin1String("(default)")) + if (connection == "(default)"_L1) continue; - const QString varConn = connection + QLatin1String("Connection"); + const QString varConn = connection + "Connection"_L1; m_output << m_indent << varConn << " = QSqlDatabase::database(" << language::charliteral(connection, m_dindent) << ")" << language::eol; } @@ -535,7 +540,7 @@ void WriteInitialization::acceptUI(DomUI *node) if (!m_buddies.empty()) m_output << language::openQtConfig(shortcutConfigKey()); - for (const Buddy &b : qAsConst(m_buddies)) { + for (const Buddy &b : std::as_const(m_buddies)) { const QString buddyVarName = m_driver->widgetVariableName(b.buddyAttributeName); if (buddyVarName.isEmpty()) { fprintf(stderr, "%s: Warning: Buddy assignment: '%s' is not a valid widget.\n", @@ -576,13 +581,13 @@ void WriteInitialization::acceptUI(DomUI *node) if (language::language() == Language::Cpp) { // Mark varName as unused to avoid compiler warnings. m_refreshInitialization += m_indent; - m_refreshInitialization += QLatin1String("(void)"); + m_refreshInitialization += "(void)"_L1; m_refreshInitialization += varName ; m_refreshInitialization += language::eol; } else if (language::language() == Language::Python) { // output a 'pass' to have an empty function m_refreshInitialization += m_indent; - m_refreshInitialization += QLatin1String("pass"); + m_refreshInitialization += "pass"_L1; m_refreshInitialization += language::eol; } } @@ -605,7 +610,7 @@ void WriteInitialization::addWizardPage(const QString &pageVarName, const DomWid const auto &attributes = page->elementAttribute(); if (!attributes.empty()) { for (const DomProperty *p : attributes) { - if (p->attributeName() == QLatin1String("pageId")) { + if (p->attributeName() == "pageId"_L1) { if (const DomString *ds = p->elementString()) id = ds->text(); break; @@ -623,7 +628,7 @@ void WriteInitialization::addWizardPage(const QString &pageVarName, const DomWid void WriteInitialization::acceptWidget(DomWidget *node) { - m_layoutMarginType = m_widgetChain.count() == 1 ? TopLevelMargin : ChildMargin; + m_layoutMarginType = m_widgetChain.size() == 1 ? TopLevelMargin : ChildMargin; const QString className = node->attributeClass(); const QString varName = m_driver->findOrInsertWidget(node); @@ -649,13 +654,13 @@ void WriteInitialization::acceptWidget(DomWidget *node) parentWidget = savedParentWidget; - if (cwi->extends(className, QLatin1String("QComboBox"))) { + if (cwi->extends(className, "QComboBox")) { initializeComboBox(node); - } else if (cwi->extends(className, QLatin1String("QListWidget"))) { + } else if (cwi->extends(className, "QListWidget")) { initializeListWidget(node); - } else if (cwi->extends(className, QLatin1String("QTreeWidget"))) { + } else if (cwi->extends(className, "QTreeWidget")) { initializeTreeWidget(node); - } else if (cwi->extends(className, QLatin1String("QTableWidget"))) { + } else if (cwi->extends(className, "QTableWidget")) { initializeTableWidget(node); } @@ -665,7 +670,7 @@ void WriteInitialization::acceptWidget(DomWidget *node) writeProperties(varName, className, node->elementProperty()); if (!parentWidget.isEmpty() - && cwi->extends(className, QLatin1String("QMenu"))) { + && cwi->extends(className, "QMenu")) { initializeMenu(node, parentWidget); } @@ -673,10 +678,10 @@ void WriteInitialization::acceptWidget(DomWidget *node) m_layoutChain.push(nullptr); m_layoutWidget = false; - if (className == QLatin1String("QWidget") && !node->hasAttributeNative()) { + if (className == "QWidget"_L1 && !node->hasAttributeNative()) { if (const DomWidget* parentWidget = m_widgetChain.top()) { const QString parentClass = parentWidget->attributeClass(); - if (parentClass != QLatin1String("QMainWindow") + if (parentClass != "QMainWindow"_L1 && !m_uic->customWidgetsInfo()->isCustomWidgetContainer(parentClass) && !m_uic->isContainer(parentClass)) m_layoutWidget = true; @@ -691,32 +696,32 @@ void WriteInitialization::acceptWidget(DomWidget *node) const DomPropertyMap attributes = propertyMap(node->elementAttribute()); - const QString pageDefaultString = QLatin1String("Page"); + const QString pageDefaultString = u"Page"_s; - if (cwi->extends(parentClass, QLatin1String("QMainWindow"))) { - if (cwi->extends(className, QLatin1String("QMenuBar"))) { + if (cwi->extends(parentClass, "QMainWindow")) { + if (cwi->extends(className, "QMenuBar")) { m_output << m_indent << parentWidget << language::derefPointer << "setMenuBar(" << varName << ')' << language::eol; - } else if (cwi->extends(className, QLatin1String("QToolBar"))) { + } else if (cwi->extends(className, "QToolBar")) { m_output << m_indent << parentWidget << language::derefPointer << "addToolBar(" << language::enumValue(toolBarAreaStringFromDOMAttributes(attributes)) << varName << ')' << language::eol; - if (const DomProperty *pbreak = attributes.value(QLatin1String("toolBarBreak"))) { - if (pbreak->elementBool() == QLatin1String("true")) { + if (const DomProperty *pbreak = attributes.value("toolBarBreak"_L1)) { + if (pbreak->elementBool() == "true"_L1) { m_output << m_indent << parentWidget << language::derefPointer << "insertToolBarBreak(" << varName << ')' << language::eol; } } - } else if (cwi->extends(className, QLatin1String("QDockWidget"))) { + } else if (cwi->extends(className, "QDockWidget")) { m_output << m_indent << parentWidget << language::derefPointer << "addDockWidget("; - if (DomProperty *pstyle = attributes.value(QLatin1String("dockWidgetArea"))) { - m_output << "Qt" << language::qualifier - << language::dockWidgetArea(pstyle->elementNumber()) << ", "; + if (DomProperty *pstyle = attributes.value("dockWidgetArea"_L1)) { + QString a = expandDockWidgetArea(language::dockWidgetArea(pstyle->elementNumber())); + m_output << language::enumValue(a) << ", "; } m_output << varName << ")" << language::eol; - } else if (m_uic->customWidgetsInfo()->extends(className, QLatin1String("QStatusBar"))) { + } else if (m_uic->customWidgetsInfo()->extends(className, "QStatusBar")) { m_output << m_indent << parentWidget << language::derefPointer << "setStatusBar(" << varName << ')' << language::eol; } else { @@ -732,14 +737,14 @@ void WriteInitialization::acceptWidget(DomWidget *node) if (!addPageMethod.isEmpty()) { m_output << m_indent << parentWidget << language::derefPointer << addPageMethod << '(' << varName << ')' << language::eol; - } else if (m_uic->customWidgetsInfo()->extends(parentClass, QLatin1String("QWizard"))) { + } else if (m_uic->customWidgetsInfo()->extends(parentClass, "QWizard")) { addWizardPage(varName, node, parentWidget); - } else if (m_uic->customWidgetsInfo()->extends(parentClass, QLatin1String("QToolBox"))) { - const DomProperty *plabel = attributes.value(QLatin1String("label")); + } else if (m_uic->customWidgetsInfo()->extends(parentClass, "QToolBox")) { + const DomProperty *plabel = attributes.value("label"_L1); DomString *plabelString = plabel ? plabel->elementString() : nullptr; QString icon; - if (const DomProperty *picon = attributes.value(QLatin1String("icon"))) - icon = QLatin1String(", ") + iconCall(picon); // Side effect: Writes icon definition + if (const DomProperty *picon = attributes.value("icon"_L1)) + icon = ", "_L1 + iconCall(picon); // Side effect: Writes icon definition m_output << m_indent << parentWidget << language::derefPointer << "addItem(" << varName << icon << ", " << noTrCall(plabelString, pageDefaultString) << ')' << language::eol; @@ -749,7 +754,7 @@ void WriteInitialization::acceptWidget(DomWidget *node) << language::derefPointer << "indexOf(" << varName << "), " << autoTrCall(plabelString, pageDefaultString) << ')' << language::eol; - if (DomProperty *ptoolTip = attributes.value(QLatin1String("toolTip"))) { + if (DomProperty *ptoolTip = attributes.value("toolTip"_L1)) { autoTrOutput(ptoolTip->elementString()) << language::openQtConfig(toolTipConfigKey()) << m_indent << parentWidget << language::derefPointer << "setItemToolTip(" << parentWidget @@ -757,12 +762,12 @@ void WriteInitialization::acceptWidget(DomWidget *node) << autoTrCall(ptoolTip->elementString()) << ')' << language::eol << language::closeQtConfig(toolTipConfigKey()); } - } else if (m_uic->customWidgetsInfo()->extends(parentClass, QLatin1String("QTabWidget"))) { - const DomProperty *ptitle = attributes.value(QLatin1String("title")); + } else if (m_uic->customWidgetsInfo()->extends(parentClass, "QTabWidget")) { + const DomProperty *ptitle = attributes.value("title"_L1); DomString *ptitleString = ptitle ? ptitle->elementString() : nullptr; QString icon; - if (const DomProperty *picon = attributes.value(QLatin1String("icon"))) - icon = QLatin1String(", ") + iconCall(picon); // Side effect: Writes icon definition + if (const DomProperty *picon = attributes.value("icon"_L1)) + icon = ", "_L1 + iconCall(picon); // Side effect: Writes icon definition m_output << m_indent << parentWidget << language::derefPointer << "addTab(" << varName << icon << ", " << language::emptyString << ')' << language::eol; @@ -771,7 +776,7 @@ void WriteInitialization::acceptWidget(DomWidget *node) << language::derefPointer << "indexOf(" << varName << "), " << autoTrCall(ptitleString, pageDefaultString) << ')' << language::eol; - if (const DomProperty *ptoolTip = attributes.value(QLatin1String("toolTip"))) { + if (const DomProperty *ptoolTip = attributes.value("toolTip"_L1)) { autoTrOutput(ptoolTip->elementString()) << language::openQtConfig(toolTipConfigKey()) << m_indent << parentWidget << language::derefPointer << "setTabToolTip(" @@ -779,7 +784,7 @@ void WriteInitialization::acceptWidget(DomWidget *node) << "), " << autoTrCall(ptoolTip->elementString()) << ')' << language::eol << language::closeQtConfig(toolTipConfigKey()); } - if (const DomProperty *pwhatsThis = attributes.value(QLatin1String("whatsThis"))) { + if (const DomProperty *pwhatsThis = attributes.value("whatsThis"_L1)) { autoTrOutput(pwhatsThis->elementString()) << language::openQtConfig(whatsThisConfigKey()) << m_indent << parentWidget << language::derefPointer << "setTabWhatsThis(" @@ -792,41 +797,41 @@ void WriteInitialization::acceptWidget(DomWidget *node) // // Special handling for qtableview/qtreeview fake header attributes // - static const QLatin1String realPropertyNames[] = { - QLatin1String("visible"), - QLatin1String("cascadingSectionResizes"), - QLatin1String("minimumSectionSize"), // before defaultSectionSize - QLatin1String("defaultSectionSize"), - QLatin1String("highlightSections"), - QLatin1String("showSortIndicator"), - QLatin1String("stretchLastSection"), + static const QLatin1StringView realPropertyNames[] = { + "visible"_L1, + "cascadingSectionResizes"_L1, + "minimumSectionSize"_L1, // before defaultSectionSize + "defaultSectionSize"_L1, + "highlightSections"_L1, + "showSortIndicator"_L1, + "stretchLastSection"_L1, }; static const QStringList trees = { - QLatin1String("QTreeView"), QLatin1String("QTreeWidget") + u"QTreeView"_s, u"QTreeWidget"_s }; static const QStringList tables = { - QLatin1String("QTableView"), QLatin1String("QTableWidget") + u"QTableView"_s, u"QTableWidget"_s }; if (cwi->extendsOneOf(className, trees)) { DomPropertyList headerProperties; for (auto realPropertyName : realPropertyNames) { - const QString fakePropertyName = QLatin1String("header") + const QString fakePropertyName = "header"_L1 + QChar(realPropertyName.at(0)).toUpper() + realPropertyName.mid(1); if (DomProperty *fakeProperty = attributes.value(fakePropertyName)) { fakeProperty->setAttributeName(realPropertyName); headerProperties << fakeProperty; } } - writeProperties(varName + language::derefPointer + QLatin1String("header()"), - QLatin1String("QHeaderView"), headerProperties, + writeProperties(varName + language::derefPointer + "header()"_L1, + "QHeaderView"_L1, headerProperties, WritePropertyIgnoreObjectName); } else if (cwi->extendsOneOf(className, tables)) { - static const QLatin1String headerPrefixes[] = { - QLatin1String("horizontalHeader"), - QLatin1String("verticalHeader"), + static const QLatin1StringView headerPrefixes[] = { + "horizontalHeader"_L1, + "verticalHeader"_L1, }; for (auto headerPrefix : headerPrefixes) { @@ -840,8 +845,8 @@ void WriteInitialization::acceptWidget(DomWidget *node) } } const QString headerVar = varName + language::derefPointer - + headerPrefix + QLatin1String("()"); - writeProperties(headerVar, QLatin1String("QHeaderView"), + + headerPrefix + "()"_L1; + writeProperties(headerVar, "QHeaderView"_L1, headerProperties, WritePropertyIgnoreObjectName); } } @@ -867,7 +872,7 @@ void WriteInitialization::addButtonGroup(const DomWidget *buttonNode, const QStr { const DomPropertyMap attributes = propertyMap(buttonNode->elementAttribute()); // Look up the button group name as specified in the attribute and find the uniquified name - const DomProperty *prop = attributes.value(QLatin1String("buttonGroup")); + const DomProperty *prop = attributes.value("buttonGroup"_L1); if (!prop) return; const QString attributeName = toString(prop->elementString()); @@ -886,7 +891,7 @@ void WriteInitialization::addButtonGroup(const DomWidget *buttonNode, const QStr const QString groupName = m_driver->findOrInsertButtonGroup(group); // Create on demand if (!m_buttonGroups.contains(groupName)) { - const QString className = QLatin1String("QButtonGroup"); + const QString className = u"QButtonGroup"_s; m_output << m_indent; if (createGroupOnTheFly) m_output << className << " *"; @@ -905,7 +910,7 @@ void WriteInitialization::acceptLayout(DomLayout *node) const QString varName = m_driver->findOrInsertLayout(node); const DomPropertyMap properties = propertyMap(node->elementProperty()); - const bool oldLayoutProperties = properties.value(QLatin1String("margin")) != nullptr; + const bool oldLayoutProperties = properties.value("margin"_L1) != nullptr; bool isGroupBox = false; @@ -932,36 +937,36 @@ void WriteInitialization::acceptLayout(DomLayout *node) left = top = right = bottom = false; for (const DomProperty *p : propList) { const QString propertyName = p->attributeName(); - if (propertyName == QLatin1String("leftMargin") && p->kind() == DomProperty::Number) + if (propertyName == "leftMargin"_L1 && p->kind() == DomProperty::Number) left = true; - else if (propertyName == QLatin1String("topMargin") && p->kind() == DomProperty::Number) + else if (propertyName == "topMargin"_L1 && p->kind() == DomProperty::Number) top = true; - else if (propertyName == QLatin1String("rightMargin") && p->kind() == DomProperty::Number) + else if (propertyName == "rightMargin"_L1 && p->kind() == DomProperty::Number) right = true; - else if (propertyName == QLatin1String("bottomMargin") && p->kind() == DomProperty::Number) + else if (propertyName == "bottomMargin"_L1 && p->kind() == DomProperty::Number) bottom = true; } if (!left) { DomProperty *p = new DomProperty(); - p->setAttributeName(QLatin1String("leftMargin")); + p->setAttributeName("leftMargin"_L1); p->setElementNumber(0); newPropList.append(p); } if (!top) { DomProperty *p = new DomProperty(); - p->setAttributeName(QLatin1String("topMargin")); + p->setAttributeName("topMargin"_L1); p->setElementNumber(0); newPropList.append(p); } if (!right) { DomProperty *p = new DomProperty(); - p->setAttributeName(QLatin1String("rightMargin")); + p->setAttributeName("rightMargin"_L1); p->setElementNumber(0); newPropList.append(p); } if (!bottom) { DomProperty *p = new DomProperty(); - p->setAttributeName(QLatin1String("bottomMargin")); + p->setAttributeName("bottomMargin"_L1); p->setElementNumber(0); newPropList.append(p); } @@ -982,12 +987,12 @@ void WriteInitialization::acceptLayout(DomLayout *node) m_layoutChain.pop(); // Stretch? (Unless we are compiling for UIC3) - const QString numberNull = QString(QLatin1Char('0')); - writePropertyList(varName, QLatin1String("setStretch"), node->attributeStretch(), numberNull); - writePropertyList(varName, QLatin1String("setRowStretch"), node->attributeRowStretch(), numberNull); - writePropertyList(varName, QLatin1String("setColumnStretch"), node->attributeColumnStretch(), numberNull); - writePropertyList(varName, QLatin1String("setColumnMinimumWidth"), node->attributeColumnMinimumWidth(), numberNull); - writePropertyList(varName, QLatin1String("setRowMinimumHeight"), node->attributeRowMinimumHeight(), numberNull); + const QString numberNull(u'0'); + writePropertyList(varName, "setStretch"_L1, node->attributeStretch(), numberNull); + writePropertyList(varName, "setRowStretch"_L1, node->attributeRowStretch(), numberNull); + writePropertyList(varName, "setColumnStretch"_L1, node->attributeColumnStretch(), numberNull); + writePropertyList(varName, "setColumnMinimumWidth"_L1, node->attributeColumnMinimumWidth(), numberNull); + writePropertyList(varName, "setRowMinimumHeight"_L1, node->attributeRowMinimumHeight(), numberNull); } // Apply a comma-separated list of values using a function "setSomething(int idx, value)" @@ -998,8 +1003,8 @@ void WriteInitialization::writePropertyList(const QString &varName, { if (value.isEmpty()) return; - const QStringList list = value.split(QLatin1Char(',')); - const int count = list.count(); + const QStringList list = value.split(u','); + const int count = list.size(); for (int i = 0; i < count; i++) { if (list.at(i) != defaultValue) { m_output << m_indent << varName << language::derefPointer << setFunction @@ -1018,21 +1023,20 @@ void WriteInitialization::acceptSpacer(DomSpacer *node) static inline QString formLayoutRole(int column, int colspan) { if (colspan > 1) - return QLatin1String("QFormLayout::SpanningRole"); - return column == 0 ? QLatin1String("QFormLayout::LabelRole") : QLatin1String("QFormLayout::FieldRole"); + return "QFormLayout::SpanningRole"_L1; + return column == 0 ? "QFormLayout::LabelRole"_L1 : "QFormLayout::FieldRole"_L1; } static QString layoutAddMethod(DomLayoutItem::Kind kind, const QString &layoutClass) { - const QString methodPrefix = layoutClass == QLatin1String("QFormLayout") - ? QLatin1String("set") : QLatin1String("add"); + const auto methodPrefix = layoutClass == "QFormLayout"_L1 ? "set"_L1 : "add"_L1; switch (kind) { case DomLayoutItem::Widget: - return methodPrefix + QLatin1String("Widget"); + return methodPrefix + "Widget"_L1; case DomLayoutItem::Layout: - return methodPrefix + QLatin1String("Layout"); + return methodPrefix + "Layout"_L1; case DomLayoutItem::Spacer: - return methodPrefix + QLatin1String("Item"); + return methodPrefix + "Item"_L1; case DomLayoutItem::Unknown: Q_ASSERT( false ); break; @@ -1055,7 +1059,7 @@ void WriteInitialization::acceptLayoutItem(DomLayoutItem *node) m_output << "\n" << m_indent << layoutName << language::derefPointer << "" << layoutAddMethod(node->kind(), layout->attributeClass()) << '('; - if (layout->attributeClass() == QLatin1String("QGridLayout")) { + if (layout->attributeClass() == "QGridLayout"_L1) { const int row = node->attributeRow(); const int col = node->attributeColumn(); @@ -1064,14 +1068,14 @@ void WriteInitialization::acceptLayoutItem(DomLayoutItem *node) m_output << itemName << ", " << row << ", " << col << ", " << rowSpan << ", " << colSpan; if (!node->attributeAlignment().isEmpty()) m_output << ", " << language::enumValue(node->attributeAlignment()); - } else if (layout->attributeClass() == QLatin1String("QFormLayout")) { + } else if (layout->attributeClass() == "QFormLayout"_L1) { const int row = node->attributeRow(); const int colSpan = node->hasAttributeColSpan() ? node->attributeColSpan() : 1; const QString role = formLayoutRole(node->attributeColumn(), colSpan); m_output << row << ", " << language::enumValue(role) << ", " << itemName; } else { m_output << itemName; - if (layout->attributeClass().contains(QLatin1String("Box")) && !node->attributeAlignment().isEmpty()) + if (layout->attributeClass().contains("Box"_L1) && !node->attributeAlignment().isEmpty()) m_output << ", 0, " << language::enumValue(node->attributeAlignment()); } m_output << ")" << language::eol << "\n"; @@ -1087,7 +1091,7 @@ void WriteInitialization::acceptActionGroup(DomActionGroup *node) m_output << m_indent << actionName << " = " << language::operatorNew << "QActionGroup(" << varName << ")" << language::eol; - writeProperties(actionName, QLatin1String("QActionGroup"), node->elementProperty()); + writeProperties(actionName, "QActionGroup"_L1, node->elementProperty()); m_actionGroupChain.push(node); TreeWalker::acceptActionGroup(node); @@ -1107,7 +1111,7 @@ void WriteInitialization::acceptAction(DomAction *node) m_output << m_indent << actionName << " = " << language::operatorNew << "QAction(" << varName << ')' << language::eol; - writeProperties(actionName, QLatin1String("QAction"), node->elementProperty()); + writeProperties(actionName, "QAction"_L1, node->elementProperty()); } void WriteInitialization::acceptActionRef(DomActionRef *node) @@ -1120,7 +1124,7 @@ void WriteInitialization::acceptActionRef(DomActionRef *node) const QString varName = m_driver->findOrInsertWidget(m_widgetChain.top()); - if (m_widgetChain.top() && actionName == QLatin1String("separator")) { + if (m_widgetChain.top() && actionName == "separator"_L1) { // separator is always reserved! m_actionOut << m_indent << varName << language::derefPointer << "addSeparator()" << language::eol; @@ -1151,35 +1155,50 @@ QString WriteInitialization::writeStringListProperty(const DomStringList *list) { QString propertyValue; QTextStream str(&propertyValue); - str << "QStringList()"; + char trailingDelimiter = '}'; + switch (language::language()) { + case Language::Cpp: + str << "QStringList{"; + break; + case Language::Python: + str << '['; + trailingDelimiter = ']'; + break; + } const QStringList values = list->elementString(); - if (values.isEmpty()) - return propertyValue; - if (needsTranslation(list)) { - const QString comment = list->attributeComment(); - for (int i = 0; i < values.size(); ++i) - str << '\n' << m_indent << " << " << trCall(values.at(i), comment); - } else { - for (int i = 0; i < values.size(); ++i) - str << " << " << language::qstring(values.at(i), m_dindent); + if (!values.isEmpty()) { + if (needsTranslation(list)) { + const QString comment = list->attributeComment(); + const qsizetype last = values.size() - 1; + for (qsizetype i = 0; i <= last; ++i) { + str << '\n' << m_indent << " " << trCall(values.at(i), comment); + if (i != last) + str << ','; + } + } else { + for (qsizetype i = 0; i < values.size(); ++i) { + if (i) + str << ", "; + str << language::qstring(values.at(i), m_dindent); + } + } } + str << trailingDelimiter; return propertyValue; } static QString configKeyForProperty(const QString &propertyName) { - if (propertyName == QLatin1String("toolTip")) + if (propertyName == "toolTip"_L1) return toolTipConfigKey(); - if (propertyName == QLatin1String("whatsThis")) + if (propertyName == "whatsThis"_L1) return whatsThisConfigKey(); - if (propertyName == QLatin1String("statusTip")) + if (propertyName == "statusTip"_L1) return statusTipConfigKey(); - if (propertyName == QLatin1String("shortcut")) + if (propertyName == "shortcut"_L1) return shortcutConfigKey(); - if (propertyName == QLatin1String("accessibleName") - || propertyName == QLatin1String("accessibleDescription")) { + if (propertyName == "accessibleName"_L1 || propertyName == "accessibleDescription"_L1) return accessibilityConfigKey(); - } return QString(); } @@ -1188,11 +1207,11 @@ void WriteInitialization::writeProperties(const QString &varName, const DomPropertyList &lst, unsigned flags) { - const bool isTopLevel = m_widgetChain.count() == 1; + const bool isTopLevel = m_widgetChain.size() == 1; - if (m_uic->customWidgetsInfo()->extends(className, QLatin1String("QAxWidget"))) { + if (m_uic->customWidgetsInfo()->extends(className, "QAxWidget")) { DomPropertyMap properties = propertyMap(lst); - if (DomProperty *p = properties.value(QLatin1String("control"))) { + if (DomProperty *p = properties.value("control"_L1)) { m_output << m_indent << varName << language::derefPointer << "setControl(" << language::qstring(toString(p->elementString()), m_dindent) << ')' << language::eol; @@ -1217,7 +1236,7 @@ void WriteInitialization::writeProperties(const QString &varName, objectName.remove(0, language::self.size()); m_output << m_indent << indent << varName << language::derefPointer << "setObjectName(" - << language::qstring(objectName, m_dindent) << ')' << language::eol; + << language::charliteral(objectName, m_dindent) << ')' << language::eol; } int leftMargin, topMargin, rightMargin, bottomMargin; @@ -1232,58 +1251,58 @@ void WriteInitialization::writeProperties(const QString &varName, bool delayProperty = false; // special case for the property `geometry': Do not use position - if (isTopLevel && propertyName == QLatin1String("geometry") && p->elementRect()) { + if (isTopLevel && propertyName == "geometry"_L1 && p->elementRect()) { const DomRect *r = p->elementRect(); m_output << m_indent << varName << language::derefPointer << "resize(" << r->elementWidth() << ", " << r->elementHeight() << ')' << language::eol; continue; } - if (propertyName == QLatin1String("currentRow") // QListWidget::currentRow - && m_uic->customWidgetsInfo()->extends(className, QLatin1String("QListWidget"))) { + if (propertyName == "currentRow"_L1 // QListWidget::currentRow + && m_uic->customWidgetsInfo()->extends(className, "QListWidget")) { m_delayedOut << m_indent << varName << language::derefPointer << "setCurrentRow(" << p->elementNumber() << ')' << language::eol; continue; } static const QStringList currentIndexWidgets = { - QLatin1String("QComboBox"), QLatin1String("QStackedWidget"), - QLatin1String("QTabWidget"), QLatin1String("QToolBox") + u"QComboBox"_s, u"QStackedWidget"_s, + u"QTabWidget"_s, u"QToolBox"_s }; - if (propertyName == QLatin1String("currentIndex") // set currentIndex later + if (propertyName == "currentIndex"_L1 // set currentIndex later && (m_uic->customWidgetsInfo()->extendsOneOf(className, currentIndexWidgets))) { m_delayedOut << m_indent << varName << language::derefPointer << "setCurrentIndex(" << p->elementNumber() << ')' << language::eol; continue; } - if (propertyName == QLatin1String("tabSpacing") - && m_uic->customWidgetsInfo()->extends(className, QLatin1String("QToolBox"))) { + if (propertyName == "tabSpacing"_L1 + && m_uic->customWidgetsInfo()->extends(className, "QToolBox")) { m_delayedOut << m_indent << varName << language::derefPointer << "layout()" << language::derefPointer << "setSpacing(" << p->elementNumber() << ')' << language::eol; continue; } - if (propertyName == QLatin1String("control") // ActiveQt support - && m_uic->customWidgetsInfo()->extends(className, QLatin1String("QAxWidget"))) { + if (propertyName == "control"_L1 // ActiveQt support + && m_uic->customWidgetsInfo()->extends(className, "QAxWidget")) { // already done ;) continue; } - if (propertyName == QLatin1String("default") - && m_uic->customWidgetsInfo()->extends(className, QLatin1String("QPushButton"))) { + if (propertyName == "default"_L1 + && m_uic->customWidgetsInfo()->extends(className, "QPushButton")) { // QTBUG-44406: Setting of QPushButton::default needs to be delayed until the parent is set delayProperty = true; - } else if (propertyName == QLatin1String("database") + } else if (propertyName == "database"_L1 && p->elementStringList()) { // Sql support continue; - } else if (propertyName == QLatin1String("frameworkCode") + } else if (propertyName == "frameworkCode"_L1 && p->kind() == DomProperty::Bool) { // Sql support continue; - } else if (propertyName == QLatin1String("orientation") - && m_uic->customWidgetsInfo()->extends(className, QLatin1String("Line"))) { + } else if (propertyName == "orientation"_L1 + && m_uic->customWidgetsInfo()->extends(className, "Line")) { // Line support - QString shape = QLatin1String("QFrame::HLine"); - if (p->elementEnum() == QLatin1String("Qt::Vertical")) - shape = QLatin1String("QFrame::VLine"); + QString shape = u"QFrame::Shape::HLine"_s; + if (p->elementEnum().endsWith("::Vertical"_L1)) + shape = u"QFrame::Shape::VLine"_s; m_output << m_indent << varName << language::derefPointer << "setFrameShape(" << language::enumValue(shape) << ')' << language::eol; @@ -1291,32 +1310,32 @@ void WriteInitialization::writeProperties(const QString &varName, if (!frameShadowEncountered) { m_output << m_indent << varName << language::derefPointer << "setFrameShadow(" - << language::enumValue(QLatin1String("QFrame::Sunken")) + << language::enumValue("QFrame::Shadow::Sunken"_L1) << ')' << language::eol; } continue; - } else if ((flags & WritePropertyIgnoreMargin) && propertyName == QLatin1String("margin")) { + } else if ((flags & WritePropertyIgnoreMargin) && propertyName == "margin"_L1) { continue; - } else if ((flags & WritePropertyIgnoreSpacing) && propertyName == QLatin1String("spacing")) { + } else if ((flags & WritePropertyIgnoreSpacing) && propertyName == "spacing"_L1) { continue; - } else if (propertyName == QLatin1String("leftMargin") && p->kind() == DomProperty::Number) { + } else if (propertyName == "leftMargin"_L1 && p->kind() == DomProperty::Number) { leftMargin = p->elementNumber(); continue; - } else if (propertyName == QLatin1String("topMargin") && p->kind() == DomProperty::Number) { + } else if (propertyName == "topMargin"_L1 && p->kind() == DomProperty::Number) { topMargin = p->elementNumber(); continue; - } else if (propertyName == QLatin1String("rightMargin") && p->kind() == DomProperty::Number) { + } else if (propertyName == "rightMargin"_L1 && p->kind() == DomProperty::Number) { rightMargin = p->elementNumber(); continue; - } else if (propertyName == QLatin1String("bottomMargin") && p->kind() == DomProperty::Number) { + } else if (propertyName == "bottomMargin"_L1 && p->kind() == DomProperty::Number) { bottomMargin = p->elementNumber(); continue; - } else if (propertyName == QLatin1String("numDigits") // Deprecated in Qt 4, removed in Qt 5. - && m_uic->customWidgetsInfo()->extends(className, QLatin1String("QLCDNumber"))) { + } else if (propertyName == "numDigits"_L1 // Deprecated in Qt 4, removed in Qt 5. + && m_uic->customWidgetsInfo()->extends(className, "QLCDNumber")) { qWarning("Widget '%s': Deprecated property QLCDNumber::numDigits encountered. It has been replaced by QLCDNumber::digitCount.", qPrintable(varName)); - propertyName = QLatin1String("digitCount"); - } else if (propertyName == QLatin1String("frameShadow")) { + propertyName = "digitCount"_L1; + } else if (propertyName == "frameShadow"_L1) { frameShadowEncountered = true; } @@ -1332,7 +1351,7 @@ void WriteInitialization::writeProperties(const QString &varName, str << language::derefPointer <<"set" << propertyName.at(0).toUpper() << QStringView{propertyName}.mid(1) << '('; } else { - str << language::derefPointer << QLatin1String("setProperty(\"") + str << language::derefPointer << "setProperty(\""_L1 << propertyName << "\", "; if (language::language() == Language::Cpp) { str << "QVariant"; @@ -1354,15 +1373,16 @@ void WriteInitialization::writeProperties(const QString &varName, propertyValue = domColor2QString(p->elementColor()); break; case DomProperty::Cstring: - if (propertyName == QLatin1String("buddy") && m_uic->customWidgetsInfo()->extends(className, QLatin1String("QLabel"))) { + if (propertyName == "buddy"_L1 && m_uic->customWidgetsInfo()->extends(className, "QLabel")) { Buddy buddy = { varName, p->elementCstring() }; m_buddies.append(std::move(buddy)); } else { + const bool useQByteArray = !stdset && language::language() == Language::Cpp; QTextStream str(&propertyValue); - if (!stdset) + if (useQByteArray) str << "QByteArray("; str << language::charliteral(p->elementCstring(), m_dindent); - if (!stdset) + if (useQByteArray) str << ')'; } break; @@ -1372,9 +1392,9 @@ void WriteInitialization::writeProperties(const QString &varName, break; case DomProperty::CursorShape: if (p->hasAttributeStdset() && !p->attributeStdset()) - varNewName += language::derefPointer + QLatin1String("viewport()"); - propertyValue = QLatin1String("QCursor(Qt") + language::qualifier - + p->elementCursorShape() + QLatin1Char(')'); + varNewName += language::derefPointer + "viewport()"_L1; + propertyValue = "QCursor(Qt"_L1 + language::qualifier + "CursorShape"_L1 + + language::qualifier + p->elementCursorShape() + u')'; break; case DomProperty::Enum: propertyValue = p->elementEnum(); @@ -1397,12 +1417,12 @@ void WriteInitialization::writeProperties(const QString &varName, break; case DomProperty::Palette: { const DomPalette *pal = p->elementPalette(); - const QString paletteName = m_driver->unique(QLatin1String("palette")); + const QString paletteName = m_driver->unique("palette"_L1); m_output << m_indent << language::stackVariable("QPalette", paletteName) << language::eol; - writeColorGroup(pal->elementActive(), QLatin1String("QPalette::Active"), paletteName); - writeColorGroup(pal->elementInactive(), QLatin1String("QPalette::Inactive"), paletteName); - writeColorGroup(pal->elementDisabled(), QLatin1String("QPalette::Disabled"), paletteName); + writeColorGroup(pal->elementActive(), "QPalette::Active"_L1, paletteName); + writeColorGroup(pal->elementInactive(), "QPalette::Inactive"_L1, paletteName); + writeColorGroup(pal->elementDisabled(), "QPalette::Disabled"_L1, paletteName); propertyValue = paletteName; break; @@ -1462,7 +1482,7 @@ void WriteInitialization::writeProperties(const QString &varName, break; } case DomProperty::String: { - if (propertyName == QLatin1String("objectName")) { + if (propertyName == "objectName"_L1) { const QString v = p->elementString()->text(); if (v == varName) break; @@ -1478,17 +1498,17 @@ void WriteInitialization::writeProperties(const QString &varName, break; case DomProperty::UInt: propertyValue = QString::number(p->elementUInt()); - propertyValue += QLatin1Char('u'); + propertyValue += u'u'; break; case DomProperty::LongLong: - propertyValue = QLatin1String("Q_INT64_C("); + propertyValue = "Q_INT64_C("_L1; propertyValue += QString::number(p->elementLongLong()); - propertyValue += QLatin1Char(')');; + propertyValue += u')'; break; case DomProperty::ULongLong: - propertyValue = QLatin1String("Q_UINT64_C("); + propertyValue = "Q_UINT64_C("_L1; propertyValue += QString::number(p->elementULongLong()); - propertyValue += QLatin1Char(')'); + propertyValue += u')'; break; case DomProperty::Float: propertyValue = QString::number(p->elementFloat(), 'f', 8); @@ -1586,16 +1606,22 @@ QString WriteInitialization::writeSizePolicy(const DomSizePolicy *sp) // insert with new name - const QString spName = m_driver->unique(QLatin1String("sizePolicy")); + const QString spName = m_driver->unique("sizePolicy"_L1); m_sizePolicyNameMap.insert(sizePolicyHandle, spName); m_output << m_indent << language::stackVariableWithInitParameters("QSizePolicy", spName); + QString horizPolicy; + QString vertPolicy; if (sp->hasElementHSizeType() && sp->hasElementVSizeType()) { - m_output << "QSizePolicy" << language::qualifier << language::sizePolicy(sp->elementHSizeType()) - << ", QSizePolicy" << language::qualifier << language::sizePolicy(sp->elementVSizeType()); + horizPolicy = language::sizePolicy(sp->elementHSizeType()); + vertPolicy = language::sizePolicy(sp->elementVSizeType()); } else if (sp->hasAttributeHSizeType() && sp->hasAttributeVSizeType()) { - m_output << "QSizePolicy" << language::qualifier << sp->attributeHSizeType() - << ", QSizePolicy" << language::qualifier << sp->attributeVSizeType(); + horizPolicy = sp->attributeHSizeType(); + vertPolicy = sp->attributeVSizeType(); + } + if (!horizPolicy.isEmpty() && !vertPolicy.isEmpty()) { + m_output << language::enumValue(expandSizePolicyEnum(horizPolicy)) + << ", " << language::enumValue(expandSizePolicyEnum(vertPolicy)); } m_output << ')' << language::eol; @@ -1618,7 +1644,7 @@ QString WriteInitialization::writeFontProperties(const DomFont *f) } // insert with new name - const QString fontName = m_driver->unique(QLatin1String("font")); + const QString fontName = m_driver->unique("font"_L1); m_fontPropertiesNameMap.insert(FontHandle(f), fontName); m_output << m_indent << language::stackVariable("QFont", fontName) @@ -1634,10 +1660,14 @@ QString WriteInitialization::writeFontProperties(const DomFont *f) << ")" << language::eol; } - if (f->hasElementBold()) { + if (f->hasElementFontWeight()) { + m_output << m_indent << fontName << ".setWeight(QFont" + << language::qualifier << f->elementFontWeight() << ')' << language::eol; + } else if (f->hasElementBold()) { m_output << m_indent << fontName << ".setBold(" << language::boolValue(f->elementBold()) << ')' << language::eol; } + if (f->hasElementItalic()) { m_output << m_indent << fontName << ".setItalic(" << language::boolValue(f->elementItalic()) << ')' << language::eol; @@ -1664,6 +1694,11 @@ QString WriteInitialization::writeFontProperties(const DomFont *f) m_output << m_indent << fontName << ".setStyleStrategy(QFont" << language::qualifier << f->elementStyleStrategy() << ')' << language::eol; } + if (f->hasElementHintingPreference()) { + m_output << m_indent << fontName << ".setHintingPreference(QFont" + << language::qualifier << f->elementHintingPreference() << ')' << language::eol; + } + return fontName; } @@ -1673,8 +1708,9 @@ static void writeIconAddFile(QTextStream &output, const QString &indent, { output << indent << iconName << ".addFile(" << language::qstring(fileName, indent) << ", QSize(), QIcon" - << language::qualifier << mode << ", QIcon" << language::qualifier - << state << ')' << language::eol; + << language::qualifier << "Mode" << language::qualifier << mode + << ", QIcon" << language::qualifier << "State" << language::qualifier << state + << ')' << language::eol; } // Post 4.4 write resource icon @@ -1722,7 +1758,8 @@ static void writeIconAddPixmap(QTextStream &output, const QString &indent, const char *mode, const char *state) { output << indent << iconName << ".addPixmap(" << call << ", QIcon" - << language::qualifier << mode << ", QIcon" << language::qualifier + << language::qualifier << "Mode" << language::qualifier << mode + << ", QIcon" << language::qualifier << "State" << language::qualifier << state << ')' << language::eol; } @@ -1733,46 +1770,99 @@ void WriteInitialization::writePixmapFunctionIcon(QTextStream &output, { if (i->hasElementNormalOff()) { writeIconAddPixmap(output, indent, iconName, - pixCall(QLatin1String("QPixmap"), i->elementNormalOff()->text()), + pixCall("QPixmap"_L1, i->elementNormalOff()->text()), "Normal", "Off"); } if (i->hasElementNormalOn()) { writeIconAddPixmap(output, indent, iconName, - pixCall(QLatin1String("QPixmap"), i->elementNormalOn()->text()), + pixCall("QPixmap"_L1, i->elementNormalOn()->text()), "Normal", "On"); } if (i->hasElementDisabledOff()) { writeIconAddPixmap(output, indent, iconName, - pixCall(QLatin1String("QPixmap"), i->elementDisabledOff()->text()), + pixCall("QPixmap"_L1, i->elementDisabledOff()->text()), "Disabled", "Off"); } if (i->hasElementDisabledOn()) { writeIconAddPixmap(output, indent, iconName, - pixCall(QLatin1String("QPixmap"), i->elementDisabledOn()->text()), + pixCall("QPixmap"_L1, i->elementDisabledOn()->text()), "Disabled", "On"); } if (i->hasElementActiveOff()) { writeIconAddPixmap(output, indent, iconName, - pixCall(QLatin1String("QPixmap"), i->elementActiveOff()->text()), + pixCall("QPixmap"_L1, i->elementActiveOff()->text()), "Active", "Off"); } if (i->hasElementActiveOn()) { writeIconAddPixmap(output, indent, iconName, - pixCall(QLatin1String("QPixmap"), i->elementActiveOn()->text()), + pixCall("QPixmap"_L1, i->elementActiveOn()->text()), "Active", "On"); } if (i->hasElementSelectedOff()) { writeIconAddPixmap(output, indent, iconName, - pixCall(QLatin1String("QPixmap"), i->elementSelectedOff()->text()), + pixCall("QPixmap"_L1, i->elementSelectedOff()->text()), "Selected", "Off"); } if (i->hasElementSelectedOn()) { writeIconAddPixmap(output, indent, iconName, - pixCall(QLatin1String("QPixmap"), i->elementSelectedOn()->text()), + pixCall("QPixmap"_L1, i->elementSelectedOn()->text()), "Selected", "On"); } } +// Write QIcon::fromTheme() (value from enum or variable) +struct iconFromTheme +{ + explicit iconFromTheme(const QString &theme) : m_theme(theme) {} + + QString m_theme; +}; + +QTextStream &operator<<(QTextStream &str, const iconFromTheme &i) +{ + str << "QIcon" << language::qualifier << "fromTheme(" << i.m_theme << ')'; + return str; +} + +// Write QIcon::fromTheme() for an XDG icon from string literal +struct iconFromThemeStringLiteral +{ + explicit iconFromThemeStringLiteral(const QString &theme) : m_theme(theme) {} + + QString m_theme; +}; + +QTextStream &operator<<(QTextStream &str, const iconFromThemeStringLiteral &i) +{ + str << "QIcon" << language::qualifier << "fromTheme(" << language::qstring(i.m_theme) << ')'; + return str; +} + +// Write QIcon::fromTheme() with a path as fallback, add a check using +// QIcon::hasThemeIcon(). +void WriteInitialization::writeThemeIconCheckAssignment(const QString &themeValue, + const QString &iconName, + const DomResourceIcon *i) + +{ + const bool isCpp = language::language() == Language::Cpp; + m_output << m_indent << "if "; + if (isCpp) + m_output << '('; + m_output << "QIcon" << language::qualifier << "hasThemeIcon(" + << themeValue << ')' << (isCpp ? ") {" : ":") << '\n' + << m_dindent << iconName << " = " << iconFromTheme(themeValue) + << language::eol; + m_output << m_indent << (isCpp ? "} else {" : "else:") << '\n'; + if (m_uic->pixmapFunction().isEmpty()) + writeResourceIcon(m_output, iconName, m_dindent, i); + else + writePixmapFunctionIcon(m_output, iconName, m_dindent, i); + if (isCpp) + m_output << m_indent << '}'; + m_output << '\n'; +} + QString WriteInitialization::writeIconProperties(const DomResourceIcon *i) { // check cache @@ -1782,7 +1872,7 @@ QString WriteInitialization::writeIconProperties(const DomResourceIcon *i) return it.value(); // insert with new name - const QString iconName = m_driver->unique(QLatin1String("icon")); + const QString iconName = m_driver->unique("icon"_L1); m_iconPropertiesNameMap.insert(IconHandle(i), iconName); const bool isCpp = language::language() == Language::Cpp; @@ -1791,13 +1881,14 @@ QString WriteInitialization::writeIconProperties(const DomResourceIcon *i) m_output << m_indent; if (isCpp) m_output << "const QIcon "; - m_output << iconName << " = " << pixCall(QLatin1String("QIcon"), i->text()) + m_output << iconName << " = " << pixCall("QIcon"_L1, i->text()) << language::eol; return iconName; } // 4.4 onwards - if (i->attributeTheme().isEmpty()) { + QString theme = i->attributeTheme(); + if (theme.isEmpty()) { // No theme: Write resource icon as is m_output << m_indent << language::stackVariable("QIcon", iconName) << language::eol; @@ -1808,12 +1899,21 @@ QString WriteInitialization::writeIconProperties(const DomResourceIcon *i) return iconName; } + const bool isThemeEnum = theme.startsWith("QIcon::"_L1); + if (isThemeEnum) + theme = language::enumValue(theme); + // Theme: Generate code to check the theme and default to resource if (iconHasStatePixmaps(i)) { // Theme + default state pixmaps: // Generate code to check the theme and default to state pixmaps m_output << m_indent << language::stackVariable("QIcon", iconName) << language::eol; - const char themeNameStringVariableC[] = "iconThemeName"; + if (isThemeEnum) { + writeThemeIconCheckAssignment(theme, iconName, i); + return iconName; + } + + static constexpr auto themeNameStringVariableC = "iconThemeName"_L1; // Store theme name in a variable m_output << m_indent; if (m_firstThemeIcon) { // Declare variable string @@ -1822,32 +1922,19 @@ QString WriteInitialization::writeIconProperties(const DomResourceIcon *i) m_firstThemeIcon = false; } m_output << themeNameStringVariableC << " = " - << language::qstring(i->attributeTheme()) << language::eol; - m_output << m_indent << "if "; - if (isCpp) - m_output << '('; - m_output << "QIcon" << language::qualifier << "hasThemeIcon(" - << themeNameStringVariableC << ')' << (isCpp ? ") {" : ":") << '\n' - << m_dindent << iconName << " = QIcon" << language::qualifier << "fromTheme(" - << themeNameStringVariableC << ')' << language::eol - << m_indent << (isCpp ? "} else {" : "else:") << '\n'; - if (m_uic->pixmapFunction().isEmpty()) - writeResourceIcon(m_output, iconName, m_dindent, i); - else - writePixmapFunctionIcon(m_output, iconName, m_dindent, i); - m_output << m_indent; - if (isCpp) - m_output << '}'; - m_output << '\n'; + << language::qstring(theme) << language::eol; + writeThemeIconCheckAssignment(themeNameStringVariableC, iconName, i); return iconName; } // Theme, but no state pixmaps: Construct from theme directly. m_output << m_indent - << language::stackVariableWithInitParameters("QIcon", iconName) - << "QIcon" << language::qualifier << "fromTheme(" - << language::qstring(i->attributeTheme()) << "))" - << language::eol; + << language::stackVariableWithInitParameters("QIcon", iconName); + if (isThemeEnum) + m_output << iconFromTheme(theme); + else + m_output << iconFromThemeStringLiteral(theme); + m_output << ')' << language::eol; return iconName; } @@ -1867,7 +1954,7 @@ QString WriteInitialization::domColor2QString(const DomColor *c) static inline QVersionNumber colorRoleVersionAdded(const QString &roleName) { - if (roleName == QLatin1String("PlaceholderText")) + if (roleName == "PlaceholderText"_L1) return QVersionNumber(5, 12, 0); return QVersionNumber(); } @@ -1915,7 +2002,7 @@ void WriteInitialization::writeColorGroup(DomColorGroup *colorGroup, const QStri QString WriteInitialization::writeBrushInitialization(const DomBrush *brush) { // Simple solid, colored brushes are cached - const bool solidColoredBrush = !brush->hasAttributeBrushStyle() || brush->attributeBrushStyle() == QLatin1String("SolidPattern"); + const bool solidColoredBrush = !brush->hasAttributeBrushStyle() || brush->attributeBrushStyle() == "SolidPattern"_L1; uint rgb = 0; if (solidColoredBrush) { if (const DomColor *color = brush->elementColor()) { @@ -1929,7 +2016,7 @@ QString WriteInitialization::writeBrushInitialization(const DomBrush *brush) } } // Create and enter into cache if simple - const QString brushName = m_driver->unique(QLatin1String("brush")); + const QString brushName = m_driver->unique("brush"_L1); writeBrush(brush, brushName); if (solidColoredBrush) m_colorBrushHash.insert(rgb, brushName); @@ -1938,24 +2025,24 @@ QString WriteInitialization::writeBrushInitialization(const DomBrush *brush) void WriteInitialization::writeBrush(const DomBrush *brush, const QString &brushName) { - QString style = QLatin1String("SolidPattern"); + QString style = u"SolidPattern"_s; if (brush->hasAttributeBrushStyle()) style = brush->attributeBrushStyle(); - if (style == QLatin1String("LinearGradientPattern") || - style == QLatin1String("RadialGradientPattern") || - style == QLatin1String("ConicalGradientPattern")) { + if (style == "LinearGradientPattern"_L1 || + style == "RadialGradientPattern"_L1 || + style == "ConicalGradientPattern"_L1) { const DomGradient *gradient = brush->elementGradient(); const QString gradientType = gradient->attributeType(); - const QString gradientName = m_driver->unique(QLatin1String("gradient")); - if (gradientType == QLatin1String("LinearGradient")) { + const QString gradientName = m_driver->unique("gradient"_L1); + if (gradientType == "LinearGradient"_L1) { m_output << m_indent << language::stackVariableWithInitParameters("QLinearGradient", gradientName) << gradient->attributeStartX() << ", " << gradient->attributeStartY() << ", " << gradient->attributeEndX() << ", " << gradient->attributeEndY() << ')' << language::eol; - } else if (gradientType == QLatin1String("RadialGradient")) { + } else if (gradientType == "RadialGradient"_L1) { m_output << m_indent << language::stackVariableWithInitParameters("QRadialGradient", gradientName) << gradient->attributeCentralX() @@ -1963,7 +2050,7 @@ void WriteInitialization::writeBrush(const DomBrush *brush, const QString &brush << ", " << gradient->attributeRadius() << ", " << gradient->attributeFocalX() << ", " << gradient->attributeFocalY() << ')' << language::eol; - } else if (gradientType == QLatin1String("ConicalGradient")) { + } else if (gradientType == "ConicalGradient"_L1) { m_output << m_indent << language::stackVariableWithInitParameters("QConicalGradient", gradientName) << gradient->attributeCentralX() @@ -1991,7 +2078,7 @@ void WriteInitialization::writeBrush(const DomBrush *brush, const QString &brush m_output << m_indent << language::stackVariableWithInitParameters("QBrush", brushName) << gradientName << ')' << language::eol; - } else if (style == QLatin1String("TexturePattern")) { + } else if (style == "TexturePattern"_L1) { const DomProperty *property = brush->elementTexture(); const QString iconValue = iconCall(property); @@ -2056,42 +2143,42 @@ QString WriteInitialization::iconCall(const DomProperty *icon) QString WriteInitialization::pixCall(const DomProperty *p) const { - QString type, s; + QLatin1StringView type; + QString s; switch (p->kind()) { case DomProperty::IconSet: - type = QLatin1String("QIcon"); + type = "QIcon"_L1; s = p->elementIconSet()->text(); break; case DomProperty::Pixmap: - type = QLatin1String("QPixmap"); + type = "QPixmap"_L1; s = p->elementPixmap()->text(); break; default: qWarning("%s: Warning: Unknown icon format encountered. The ui-file was generated with a too-recent version of Designer.", qPrintable(m_option.messagePrefix())); - return QLatin1String("QIcon()"); + return "QIcon()"_L1; break; } return pixCall(type, s); } -QString WriteInitialization::pixCall(const QString &t, const QString &text) const +QString WriteInitialization::pixCall(QLatin1StringView t, const QString &text) const { - QString type = t; - if (text.isEmpty()) { - type += QLatin1String("()"); - return type; - } + if (text.isEmpty()) + return t % "()"_L1; - QTextStream str(&type); + QString result; + QTextStream str(&result); + str << t; str << '('; - QString pixFunc = m_uic->pixmapFunction(); + const QString pixFunc = m_uic->pixmapFunction(); if (pixFunc.isEmpty()) str << language::qstring(text, m_dindent); else str << pixFunc << '(' << language::charliteral(text, m_dindent) << ')'; str << ')'; - return type; + return result; } void WriteInitialization::initializeComboBox(DomWidget *w) @@ -2106,8 +2193,8 @@ void WriteInitialization::initializeComboBox(DomWidget *w) for (int i = 0; i < items.size(); ++i) { const DomItem *item = items.at(i); const DomPropertyMap properties = propertyMap(item->elementProperty()); - const DomProperty *text = properties.value(QLatin1String("text")); - const DomProperty *icon = properties.value(QLatin1String("icon")); + const DomProperty *text = properties.value("text"_L1); + const DomProperty *icon = properties.value("icon"_L1); QString iconValue; if (icon) @@ -2134,7 +2221,7 @@ QString WriteInitialization::disableSorting(DomWidget *w, const QString &varName // turn off sortingEnabled to force programmatic item order (setItem()) QString tempName; if (!w->elementItem().isEmpty()) { - tempName = m_driver->unique(QLatin1String("__sortingEnabled")); + tempName = m_driver->unique("__sortingEnabled"_L1); m_refreshOut << "\n"; m_refreshOut << m_indent; if (language::language() == Language::Cpp) @@ -2226,10 +2313,10 @@ void WriteInitialization::addQtFlagsInitializer(Item *item, const DomPropertyMap &properties, const QString &name, int column) const { if (const DomProperty *p = properties.value(name)) { - const QString orOperator = QLatin1Char('|') + language::qtQualifier; + const QString orOperator = u'|' + language::qtQualifier; QString v = p->elementSet(); if (!v.isEmpty()) { - v.replace(QLatin1Char('|'), orOperator); + v.replace(u'|', orOperator); addInitializer(item, name, column, language::qtQualifier + v); } } @@ -2255,20 +2342,20 @@ void WriteInitialization::addQtEnumInitializer(Item *item, void WriteInitialization::addCommonInitializers(Item *item, const DomPropertyMap &properties, int column) { - if (const DomProperty *icon = properties.value(QLatin1String("icon"))) - addInitializer(item, QLatin1String("icon"), column, iconCall(icon)); - addBrushInitializer(item, properties, QLatin1String("foreground"), column); - addBrushInitializer(item, properties, QLatin1String("background"), column); - if (const DomProperty *font = properties.value(QLatin1String("font"))) - addInitializer(item, QLatin1String("font"), column, writeFontProperties(font->elementFont())); - addQtFlagsInitializer(item, properties, QLatin1String("textAlignment"), column); - addQtEnumInitializer(item, properties, QLatin1String("checkState"), column); - addStringInitializer(item, properties, QLatin1String("text"), column); - addStringInitializer(item, properties, QLatin1String("toolTip"), column, + if (const DomProperty *icon = properties.value("icon"_L1)) + addInitializer(item, "icon"_L1, column, iconCall(icon)); + addBrushInitializer(item, properties, "foreground"_L1, column); + addBrushInitializer(item, properties, "background"_L1, column); + if (const DomProperty *font = properties.value("font"_L1)) + addInitializer(item, "font"_L1, column, writeFontProperties(font->elementFont())); + addQtFlagsInitializer(item, properties, "textAlignment"_L1, column); + addQtEnumInitializer(item, properties, "checkState"_L1, column); + addStringInitializer(item, properties, "text"_L1, column); + addStringInitializer(item, properties, "toolTip"_L1, column, toolTipConfigKey()); - addStringInitializer(item, properties, QLatin1String("whatsThis"), column, + addStringInitializer(item, properties, "whatsThis"_L1, column, whatsThisConfigKey()); - addStringInitializer(item, properties, QLatin1String("statusTip"), column, + addStringInitializer(item, properties, "statusTip"_L1, column, statusTipConfigKey()); } @@ -2289,8 +2376,8 @@ void WriteInitialization::initializeListWidget(DomWidget *w) const DomPropertyMap properties = propertyMap(domItem->elementProperty()); - Item item(QLatin1String("QListWidgetItem"), m_indent, m_output, m_refreshOut, m_driver); - addQtFlagsInitializer(&item, properties, QLatin1String("flags")); + Item item("QListWidgetItem"_L1, m_indent, m_output, m_refreshOut, m_driver); + addQtFlagsInitializer(&item, properties, "flags"_L1); addCommonInitializers(&item, properties); item.writeSetupUi(varName); @@ -2306,7 +2393,7 @@ void WriteInitialization::initializeTreeWidget(DomWidget *w) const QString varName = m_driver->findOrInsertWidget(w); // columns - Item item(QLatin1String("QTreeWidgetItem"), m_indent, m_output, m_refreshOut, m_driver); + Item item("QTreeWidgetItem"_L1, m_indent, m_output, m_refreshOut, m_driver); const auto &columns = w->elementColumn(); for (int i = 0; i < columns.size(); ++i) { @@ -2315,7 +2402,7 @@ void WriteInitialization::initializeTreeWidget(DomWidget *w) const DomPropertyMap properties = propertyMap(column->elementProperty()); addCommonInitializers(&item, properties, i); - if (const DomProperty *p = properties.value(QLatin1String("text"))) { + if (const DomProperty *p = properties.value("text"_L1)) { DomString *str = p->elementString(); if (str && str->text().isEmpty()) { m_output << m_indent << varName << language::derefPointer @@ -2325,7 +2412,7 @@ void WriteInitialization::initializeTreeWidget(DomWidget *w) } } const QString itemName = item.writeSetupUi(QString(), Item::DontConstruct); - item.writeRetranslateUi(varName + language::derefPointer + QLatin1String("headerItem()")); + item.writeRetranslateUi(varName + language::derefPointer + "headerItem()"_L1); if (!itemName.isNull()) { m_output << m_indent << varName << language::derefPointer << "setHeaderItem(" << itemName << ')' << language::eol; @@ -2337,7 +2424,7 @@ void WriteInitialization::initializeTreeWidget(DomWidget *w) QString tempName = disableSorting(w, varName); const auto items = initializeTreeWidgetItems(w->elementItem()); - for (int i = 0; i < items.count(); i++) { + for (int i = 0; i < items.size(); i++) { Item *itm = items[i]; itm->writeSetupUi(varName); QString parentPath; @@ -2368,7 +2455,7 @@ WriteInitialization::Items WriteInitialization::initializeTreeWidgetItems(const for (int i = 0; i < numDomItems; ++i) { const DomItem *domItem = domItems.at(i); - Item *item = new Item(QLatin1String("QTreeWidgetItem"), m_indent, m_output, m_refreshOut, m_driver); + Item *item = new Item("QTreeWidgetItem"_L1, m_indent, m_output, m_refreshOut, m_driver); items << item; QHash<QString, DomProperty *> map; @@ -2376,7 +2463,7 @@ WriteInitialization::Items WriteInitialization::initializeTreeWidgetItems(const int col = -1; const DomPropertyList properties = domItem->elementProperty(); for (DomProperty *p : properties) { - if (p->attributeName() == QLatin1String("text")) { + if (p->attributeName() == "text"_L1) { if (!map.isEmpty()) { addCommonInitializers(item, map, col); map.clear(); @@ -2387,7 +2474,7 @@ WriteInitialization::Items WriteInitialization::initializeTreeWidgetItems(const } addCommonInitializers(item, map, col); // AbstractFromBuilder saves flags last, so they always end up in the last column's map. - addQtFlagsInitializer(item, map, QLatin1String("flags")); + addQtFlagsInitializer(item, map, "flags"_L1); const auto subItems = initializeTreeWidgetItems(domItem->elementItem()); for (Item *subItem : subItems) @@ -2417,7 +2504,7 @@ void WriteInitialization::initializeTableWidget(DomWidget *w) if (!column->elementProperty().isEmpty()) { const DomPropertyMap properties = propertyMap(column->elementProperty()); - Item item(QLatin1String("QTableWidgetItem"), m_indent, m_output, m_refreshOut, m_driver); + Item item("QTableWidgetItem"_L1, m_indent, m_output, m_refreshOut, m_driver); addCommonInitializers(&item, properties); QString itemName = item.writeSetupUi(QString(), Item::ConstructItemAndVariable); @@ -2447,7 +2534,7 @@ void WriteInitialization::initializeTableWidget(DomWidget *w) if (!row->elementProperty().isEmpty()) { const DomPropertyMap properties = propertyMap(row->elementProperty()); - Item item(QLatin1String("QTableWidgetItem"), m_indent, m_output, m_refreshOut, m_driver); + Item item("QTableWidgetItem"_L1, m_indent, m_output, m_refreshOut, m_driver); addCommonInitializers(&item, properties); QString itemName = item.writeSetupUi(QString(), Item::ConstructItemAndVariable); @@ -2470,8 +2557,8 @@ void WriteInitialization::initializeTableWidget(DomWidget *w) const int c = cell->attributeColumn(); const DomPropertyMap properties = propertyMap(cell->elementProperty()); - Item item(QLatin1String("QTableWidgetItem"), m_indent, m_output, m_refreshOut, m_driver); - addQtFlagsInitializer(&item, properties, QLatin1String("flags")); + Item item("QTableWidgetItem"_L1, m_indent, m_output, m_refreshOut, m_driver); + addQtFlagsInitializer(&item, properties, "flags"_L1); addCommonInitializers(&item, properties); QString itemName = item.writeSetupUi(QString(), Item::ConstructItemAndVariable); @@ -2523,7 +2610,7 @@ QString WriteInitialization::trCall(const QString &str, const QString &commentHi void WriteInitialization::initializeMenu(DomWidget *w, const QString &/*parentWidget*/) { const QString menuName = m_driver->findOrInsertWidget(w); - const QString menuAction = menuName + QLatin1String("Action"); + const QString menuAction = menuName + "Action"_L1; const DomAction *action = m_driver->actionByName(menuAction); if (action && action->hasAttributeMenu()) { @@ -2618,7 +2705,7 @@ ConnectionSyntax WriteInitialization::connectionSyntax(const language::SignalSlo return ConnectionSyntax::StringBased; } - return sender.signature.endsWith(QLatin1String("()")) + return sender.signature.endsWith("()"_L1) || (!isCustomWidget(sender.className) && !isCustomWidget(receiver.className)) ? ConnectionSyntax::MemberFunctionPtr : ConnectionSyntax::StringBased; } @@ -2640,10 +2727,21 @@ void WriteInitialization::acceptConnection(DomConnection *connection) return; } const QString senderSignature = connection->elementSignal(); + const QString slotSignature = connection->elementSlot(); + const bool senderAmbiguous = m_uic->customWidgetsInfo()->isAmbiguousSignal(senderDecl.className, + senderSignature); + const bool slotAmbiguous = m_uic->customWidgetsInfo()->isAmbiguousSlot(receiverDecl.className, + slotSignature); + + language::SignalSlotOptions signalOptions; + signalOptions.setFlag(language::SignalSlotOption::Ambiguous, senderAmbiguous); + language::SignalSlotOptions slotOptions; + slotOptions.setFlag(language::SignalSlotOption::Ambiguous, slotAmbiguous); + language::SignalSlot theSignal{senderDecl.name, senderSignature, - senderDecl.className}; - language::SignalSlot theSlot{receiverDecl.name, connection->elementSlot(), - receiverDecl.className}; + senderDecl.className, signalOptions}; + language::SignalSlot theSlot{receiverDecl.name, slotSignature, + receiverDecl.className, slotOptions}; m_output << m_indent; language::formatConnection(m_output, theSignal, theSlot, @@ -2714,7 +2812,7 @@ QString WriteInitialization::Item::writeSetupUi(const QString &parent, Item::Emp if (generateMultiDirective) generateMultiDirectiveBegin(m_setupUiStream, m_setupUiData.directives); - const QString uniqueName = m_driver->unique(QLatin1String("__") + m_itemClassName.toLower()); + const QString uniqueName = m_driver->unique("__"_L1 + m_itemClassName.toLower()); m_setupUiStream << m_indent; if (language::language() == Language::Cpp) m_setupUiStream << m_itemClassName << " *"; @@ -2738,7 +2836,7 @@ QString WriteInitialization::Item::writeSetupUi(const QString &parent, Item::Emp m_setupUiStream << language::closeQtConfig(it.key()); ++it; } - for (Item *child : qAsConst(m_children)) + for (Item *child : std::as_const(m_children)) child->writeSetupUi(uniqueName); return uniqueName; } @@ -2751,7 +2849,7 @@ void WriteInitialization::Item::writeRetranslateUi(const QString &parentPath) if (m_retranslateUiData.policy == ItemData::GenerateWithMultiDirective) generateMultiDirectiveBegin(m_retranslateUiStream, m_retranslateUiData.directives); - const QString uniqueName = m_driver->unique(QLatin1String("___") + m_itemClassName.toLower()); + const QString uniqueName = m_driver->unique("___"_L1 + m_itemClassName.toLower()); m_retranslateUiStream << m_indent; if (language::language() == Language::Cpp) m_retranslateUiStream << m_itemClassName << " *"; diff --git a/src/tools/uic/cpp/cppwriteinitialization.h b/src/tools/uic/cpp/cppwriteinitialization.h index 41eca9f0cb..0973def52d 100644 --- a/src/tools/uic/cpp/cppwriteinitialization.h +++ b/src/tools/uic/cpp/cppwriteinitialization.h @@ -1,36 +1,10 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef CPPWRITEINITIALIZATION_H #define CPPWRITEINITIALIZATION_H #include "treewalker.h" -#include <qpair.h> #include <qhash.h> #include <qset.h> #include <qmap.h> @@ -144,7 +118,7 @@ private: QString iconCall(const DomProperty *prop); QString pixCall(const DomProperty *prop) const; - QString pixCall(const QString &type, const QString &text) const; + QString pixCall(QLatin1StringView type, const QString &text) const; QString trCall(const QString &str, const QString &comment = QString(), const QString &id = QString()) const; QString trCall(DomString *str, const QString &defaultString = QString()) const; QString noTrCall(DomString *str, const QString &defaultString = QString()) const; @@ -235,6 +209,8 @@ private: private: QString writeFontProperties(const DomFont *f); QString writeIconProperties(const DomResourceIcon *i); + void writeThemeIconCheckAssignment(const QString &themeValue, const QString &iconName, + const DomResourceIcon *i); void writePixmapFunctionIcon(QTextStream &output, const QString &iconName, const QString &indent, const DomResourceIcon *i) const; QString writeSizePolicy(const DomSizePolicy *sp); diff --git a/src/tools/uic/customwidgetsinfo.cpp b/src/tools/uic/customwidgetsinfo.cpp index c838feaf73..6ec418634c 100644 --- a/src/tools/uic/customwidgetsinfo.cpp +++ b/src/tools/uic/customwidgetsinfo.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "customwidgetsinfo.h" #include "driver.h" @@ -35,6 +10,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + CustomWidgetsInfo::CustomWidgetsInfo() = default; void CustomWidgetsInfo::acceptUI(DomUI *node) @@ -58,7 +35,7 @@ void CustomWidgetsInfo::acceptCustomWidget(DomCustomWidget *node) m_customWidgets.insert(node->elementClass(), node); } -bool CustomWidgetsInfo::extends(const QString &classNameIn, QLatin1String baseClassName) const +bool CustomWidgetsInfo::extends(const QString &classNameIn, QAnyStringView baseClassName) const { if (classNameIn == baseClassName) return true; @@ -101,10 +78,95 @@ bool CustomWidgetsInfo::isCustomWidgetContainer(const QString &className) const return false; } +// FIXME in 7.0 - QTBUG-124241 +// Remove isAmbiguous logic when widget slots have been disambiguated. +bool CustomWidgetsInfo::isAmbiguous(const QString &className, const QString &signature, + QMetaMethod::MethodType type) const +{ + using TypeMap = QHash<QString, QMetaMethod::MethodType>; + struct AmbiguousInClass { + QLatin1StringView className; + TypeMap methodMap; + }; + + static const QList<AmbiguousInClass> ambiguousList = { + + {"QAction"_L1, {{"triggered"_L1, QMetaMethod::Signal}}}, + {"QCommandLinkButton"_L1, {{"triggered"_L1, QMetaMethod::Signal}, + {"clicked"_L1, QMetaMethod::Signal}}}, + {"QPushButton"_L1, {{"triggered"_L1, QMetaMethod::Signal}, + {"clicked"_L1, QMetaMethod::Signal}}}, + {"QCheckBox"_L1, {{"triggered"_L1, QMetaMethod::Signal}, + {"clicked"_L1, QMetaMethod::Signal}}}, + {"QRadioButton"_L1, {{"triggered"_L1, QMetaMethod::Signal}, + {"clicked"_L1, QMetaMethod::Signal}}}, + {"QToolButton"_L1, {{"triggered"_L1, QMetaMethod::Signal}, + {"clicked"_L1, QMetaMethod::Signal}}}, + {"QLabel"_L1, {{"setNum"_L1, QMetaMethod::Slot}}}, + {"QGraphicsView"_L1, {{"invalidateScene"_L1, QMetaMethod::Slot}}}, + {"QListView"_L1, {{"dataChanged"_L1, QMetaMethod::Slot}}}, + {"QColumnView"_L1, {{"dataChanged"_L1, QMetaMethod::Slot}}}, + {"QListWidget"_L1, {{"dataChanged"_L1, QMetaMethod::Slot}, + {"scrollToItem"_L1, QMetaMethod::Slot}}}, + {"QTableView"_L1, {{"dataChanged"_L1, QMetaMethod::Slot}}}, + {"QTableWidget"_L1, {{"dataChanged"_L1, QMetaMethod::Slot}, + {"scrollToItem"_L1, QMetaMethod::Slot}}}, + {"QTreeView"_L1, {{"dataChanged"_L1, QMetaMethod::Slot}, + {"verticalScrollbarValueChanged"_L1, QMetaMethod::Slot}, + {"expandRecursively"_L1, QMetaMethod::Slot}}}, + {"QTreeWidget"_L1, {{"dataChanged"_L1, QMetaMethod::Slot}, + {"verticalScrollbarValueChanged"_L1, QMetaMethod::Slot} + ,{"expandRecursively"_L1, QMetaMethod::Slot} + ,{"scrollToItem"_L1, QMetaMethod::Slot}}}, + {"QUndoView"_L1, {{"dataChanged"_L1, QMetaMethod::Slot}}}, + {"QLCDNumber"_L1, {{"display"_L1, QMetaMethod::Slot}}}, + {"QMenuBar"_L1, {{"setVisible"_L1, QMetaMethod::Slot}}}, + {"QTextBrowser"_L1, {{"setSource"_L1, QMetaMethod::Slot}}}, + + /* + The following widgets with ambiguities are not used in the widget designer: + + {"QSplashScreen"_L1, {{"showMessage"_L1, QMetaMethod::Slot}}}, + {"QCompleter"_L1, {{"activated"_L1, QMetaMethod::Signal}, + {"highlighted"_L1, QMetaMethod::Signal}}}, + {"QSystemTrayIcon"_L1, {{"showMessage"_L1, QMetaMethod::Slot}}}, + {"QStyledItemDelegate"_L1, {{"closeEditor"_L1, QMetaMethod::Signal}}}, + {"QErrorMessage"_L1, {{"showMessage"_L1, QMetaMethod::Slot}}}, + {"QGraphicsDropShadowEffect"_L1, {{"setOffset"_L1, QMetaMethod::Slot}}}, + {"QGraphicsScene"_L1, {{"invalidate"_L1, QMetaMethod::Slot}}}, + {"QItemDelegate"_L1, {{"closeEditor"_L1, QMetaMethod::Signal}}} + */ + }; + + for (auto it = ambiguousList.constBegin(); it != ambiguousList.constEnd(); ++it) { + if (extends(className, it->className)) { + const qsizetype pos = signature.indexOf(u'('); + const QString method = signature.left(pos); + const auto methodIterator = it->methodMap.find(method); + return methodIterator != it->methodMap.constEnd() && type == methodIterator.value(); + } + } + return false; +} + +// Is it ambiguous, resulting in different signals for Python +// "QAbstractButton::clicked(checked=false)" +bool CustomWidgetsInfo::isAmbiguousSignal(const QString &className, + const QString &signalSignature) const +{ + return isAmbiguous(className, signalSignature, QMetaMethod::Signal); +} + +bool CustomWidgetsInfo::isAmbiguousSlot(const QString &className, + const QString &signalSignature) const +{ + return isAmbiguous(className, signalSignature, QMetaMethod::Slot); +} + QString CustomWidgetsInfo::realClassName(const QString &className) const { - if (className == QLatin1String("Line")) - return QLatin1String("QFrame"); + if (className == "Line"_L1) + return u"QFrame"_s; return className; } @@ -119,19 +181,19 @@ QString CustomWidgetsInfo::customWidgetAddPageMethod(const QString &name) const // add page methods for simple containers taking only the widget parameter QString CustomWidgetsInfo::simpleContainerAddPageMethod(const QString &name) const { - using AddPageMethod = std::pair<const char *, const char *>; - - static AddPageMethod addPageMethods[] = { - {"QStackedWidget", "addWidget"}, - {"QToolBar", "addWidget"}, - {"QDockWidget", "setWidget"}, - {"QScrollArea", "setWidget"}, - {"QSplitter", "addWidget"}, - {"QMdiArea", "addSubWindow"} + using AddPageMethod = std::pair<QString, QString>; + + static const AddPageMethod addPageMethods[] = { + {u"QStackedWidget"_s, u"addWidget"_s}, + {u"QToolBar"_s, u"addWidget"_s}, + {u"QDockWidget"_s, u"setWidget"_s}, + {u"QScrollArea"_s, u"setWidget"_s}, + {u"QSplitter"_s, u"addWidget"_s}, + {u"QMdiArea"_s, u"addSubWindow"_s} }; for (const auto &m : addPageMethods) { - if (extends(name, QLatin1String(m.first))) - return QLatin1String(m.second); + if (extends(name, m.first)) + return m.second; } return QString(); } diff --git a/src/tools/uic/customwidgetsinfo.h b/src/tools/uic/customwidgetsinfo.h index a1b24ab042..f336292f2a 100644 --- a/src/tools/uic/customwidgetsinfo.h +++ b/src/tools/uic/customwidgetsinfo.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef CUSTOMWIDGETSINFO_H #define CUSTOMWIDGETSINFO_H @@ -32,6 +7,7 @@ #include "treewalker.h" #include <qstringlist.h> #include <qmap.h> +#include <QtCore/qmetaobject.h> QT_BEGIN_NAMESPACE @@ -56,14 +32,21 @@ public: QString realClassName(const QString &className) const; - bool extends(const QString &className, QLatin1String baseClassName) const; + bool extends(const QString &className, QAnyStringView baseClassName) const; bool extendsOneOf(const QString &className, const QStringList &baseClassNames) const; bool isCustomWidgetContainer(const QString &className) const; + bool isAmbiguousSignal(const QString &className, + const QString &signalSignature) const; + bool isAmbiguousSlot(const QString &className, + const QString &slotSignature) const; + private: using NameCustomWidgetMap = QMap<QString, DomCustomWidget*>; NameCustomWidgetMap m_customWidgets; + bool isAmbiguous(const QString &className, const QString &signature, + QMetaMethod::MethodType type) const; }; QT_END_NAMESPACE diff --git a/src/tools/uic/databaseinfo.cpp b/src/tools/uic/databaseinfo.cpp index 9b0d1614ab..5334f06baf 100644 --- a/src/tools/uic/databaseinfo.cpp +++ b/src/tools/uic/databaseinfo.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "databaseinfo.h" #include "driver.h" @@ -33,6 +8,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + DatabaseInfo::DatabaseInfo() = default; void DatabaseInfo::acceptUI(DomUI *node) @@ -50,11 +27,11 @@ void DatabaseInfo::acceptWidget(DomWidget *node) { QHash<QString, DomProperty*> properties = propertyMap(node->elementProperty()); - DomProperty *frameworkCode = properties.value(QLatin1String("frameworkCode")); + DomProperty *frameworkCode = properties.value("frameworkCode"_L1); if (frameworkCode && toBool(frameworkCode->elementBool()) == false) return; - DomProperty *db = properties.value(QLatin1String("database")); + DomProperty *db = properties.value("database"_L1); if (db && db->elementStringList()) { QStringList info = db->elementStringList()->elementString(); if (info.isEmpty() || info.constFirst().isEmpty()) diff --git a/src/tools/uic/databaseinfo.h b/src/tools/uic/databaseinfo.h index 4015e39dbc..0d2487a56f 100644 --- a/src/tools/uic/databaseinfo.h +++ b/src/tools/uic/databaseinfo.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef DATABASEINFO_H #define DATABASEINFO_H diff --git a/src/tools/uic/driver.cpp b/src/tools/uic/driver.cpp index 8b9b4806e6..110764ee07 100644 --- a/src/tools/uic/driver.cpp +++ b/src/tools/uic/driver.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "driver.h" #include "uic.h" @@ -39,6 +14,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + Driver::Driver() : m_stdout(stdout, QFile::WriteOnly | QFile::Text) { @@ -152,7 +129,7 @@ QString Driver::normalizedName(const QString &name) QString result = name; std::replace_if(result.begin(), result.end(), [] (QChar c) { return !c.isLetterOrNumber(); }, - QLatin1Char('_')); + u'_'); return result; } @@ -172,7 +149,7 @@ QString Driver::unique(const QString &instanceName, const QString &className) } else if (!className.isEmpty()) { name = unique(qtify(className)); } else { - name = unique(QLatin1String("var")); + name = unique("var"_L1); } if (alreadyUsed && !className.isEmpty()) { @@ -190,7 +167,7 @@ QString Driver::qtify(const QString &name) { QString qname = name; - if (qname.at(0) == QLatin1Char('Q') || qname.at(0) == QLatin1Char('K')) + if (qname.at(0) == u'Q' || qname.at(0) == u'K') qname.remove(0, 1); for (int i = 0, size = qname.size(); i < size && qname.at(i).isUpper(); ++i) @@ -201,8 +178,7 @@ QString Driver::qtify(const QString &name) static bool isAnsiCCharacter(QChar c) { - return (c.toUpper() >= QLatin1Char('A') && c.toUpper() <= QLatin1Char('Z')) - || c.isDigit() || c == QLatin1Char('_'); + return (c.toUpper() >= u'A' && c.toUpper() <= u'Z') || c.isDigit() || c == u'_'; } QString Driver::headerFileName() const @@ -210,7 +186,7 @@ QString Driver::headerFileName() const QString name = m_option.outputFile; if (name.isEmpty()) { - name = QLatin1String("ui_"); // ### use ui_ as prefix. + name = "ui_"_L1; // ### use ui_ as prefix. name.append(m_option.inputFile); } @@ -220,23 +196,23 @@ QString Driver::headerFileName() const QString Driver::headerFileName(const QString &fileName) { if (fileName.isEmpty()) - return headerFileName(QLatin1String("noname")); + return headerFileName(u"noname"_s); QFileInfo info(fileName); QString baseName = info.baseName(); // Transform into a valid C++ identifier if (!baseName.isEmpty() && baseName.at(0).isDigit()) - baseName.prepend(QLatin1Char('_')); + baseName.prepend(u'_'); for (int i = 0; i < baseName.size(); ++i) { QChar c = baseName.at(i); if (!isAnsiCCharacter(c)) { // Replace character by its unicode value QString hex = QString::number(c.unicode(), 16); - baseName.replace(i, 1, QLatin1Char('_') + hex + QLatin1Char('_')); + baseName.replace(i, 1, u'_' + hex + u'_'); i += hex.size() + 1; } } - return baseName.toUpper() + QLatin1String("_H"); + return baseName.toUpper() + "_H"_L1; } bool Driver::printDependencies(const QString &fileName) @@ -269,9 +245,10 @@ bool Driver::uic(const QString &fileName, DomUI *ui, QTextStream *out) bool Driver::uic(const QString &fileName, QTextStream *out) { QFile f; - if (fileName.isEmpty()) - f.open(stdin, QIODevice::ReadOnly); - else { + if (fileName.isEmpty()) { + if (!f.open(stdin, QIODevice::ReadOnly)) + return false; + } else { f.setFileName(fileName); if (!f.open(QIODevice::ReadOnly)) return false; diff --git a/src/tools/uic/driver.h b/src/tools/uic/driver.h index 45ec23b4aa..8c54d03d83 100644 --- a/src/tools/uic/driver.h +++ b/src/tools/uic/driver.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef DRIVER_H #define DRIVER_H diff --git a/src/tools/uic/main.cpp b/src/tools/uic/main.cpp index 4a5f0ea309..d46b788419 100644 --- a/src/tools/uic/main.cpp +++ b/src/tools/uic/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "uic.h" #include "option.h" @@ -38,15 +13,49 @@ #include <qcoreapplication.h> #include <qcommandlineoption.h> #include <qcommandlineparser.h> +#include <qfileinfo.h> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + +static const char pythonPathVar[] = "PYTHONPATH"; + +// From the Python paths, find the component the UI file is under +static QString pythonRoot(const QString &pythonPath, const QString &uiFileIn) +{ +#ifdef Q_OS_WIN + static const Qt::CaseSensitivity fsSensitivity = Qt::CaseInsensitive; +#else + static const Qt::CaseSensitivity fsSensitivity = Qt::CaseSensitive; +#endif + + if (pythonPath.isEmpty() || uiFileIn.isEmpty()) + return {}; + const QString uiFile = QFileInfo(uiFileIn).canonicalFilePath(); + if (uiFile.isEmpty()) + return {}; + const auto uiFileSize = uiFile.size(); + const auto paths = pythonPath.split(QDir::listSeparator(), Qt::SkipEmptyParts); + for (const auto &path : paths) { + const QString canonicalPath = QFileInfo(path).canonicalFilePath(); + const auto canonicalPathSize = canonicalPath.size(); + if (uiFileSize > canonicalPathSize + && uiFile.at(canonicalPathSize) == u'/' + && uiFile.startsWith(canonicalPath, fsSensitivity)) { + return canonicalPath; + } + } + return {}; +} + int runUic(int argc, char *argv[]) { - qSetGlobalQHashSeed(0); // set the hash seed to 0 + QHashSeed::setDeterministicGlobalSeed(); QCoreApplication app(argc, argv); - QCoreApplication::setApplicationVersion(QString::fromLatin1(QT_VERSION_STR)); + const QString version = QString::fromLatin1(qVersion()); + QCoreApplication::setApplicationVersion(version); Driver driver; @@ -54,70 +63,89 @@ int runUic(int argc, char *argv[]) // If you use this code as an example for a translated app, make sure to translate the strings. QCommandLineParser parser; parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); - parser.setApplicationDescription(QStringLiteral("Qt User Interface Compiler version %1").arg(QString::fromLatin1(QT_VERSION_STR))); + parser.setApplicationDescription(u"Qt User Interface Compiler version %1"_s.arg(version)); parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption dependenciesOption(QStringList() << QStringLiteral("d") << QStringLiteral("dependencies")); - dependenciesOption.setDescription(QStringLiteral("Display the dependencies.")); + QCommandLineOption dependenciesOption(QStringList{u"d"_s, u"dependencies"_s}); + dependenciesOption.setDescription(u"Display the dependencies."_s); parser.addOption(dependenciesOption); - QCommandLineOption outputOption(QStringList() << QStringLiteral("o") << QStringLiteral("output")); - outputOption.setDescription(QStringLiteral("Place the output into <file>")); - outputOption.setValueName(QStringLiteral("file")); + QCommandLineOption outputOption(QStringList{u"o"_s, u"output"_s}); + outputOption.setDescription(u"Place the output into <file>"_s); + outputOption.setValueName(u"file"_s); parser.addOption(outputOption); - QCommandLineOption noAutoConnectionOption(QStringList() << QStringLiteral("a") << QStringLiteral("no-autoconnection")); - noAutoConnectionOption.setDescription(QStringLiteral("Do not generate a call to QObject::connectSlotsByName().")); + QCommandLineOption noAutoConnectionOption(QStringList{u"a"_s, u"no-autoconnection"_s}); + noAutoConnectionOption.setDescription(u"Do not generate a call to QObject::connectSlotsByName()."_s); parser.addOption(noAutoConnectionOption); - QCommandLineOption noProtOption(QStringList() << QStringLiteral("p") << QStringLiteral("no-protection")); - noProtOption.setDescription(QStringLiteral("Disable header protection.")); + QCommandLineOption noProtOption(QStringList{u"p"_s, u"no-protection"_s}); + noProtOption.setDescription(u"Disable header protection."_s); parser.addOption(noProtOption); - QCommandLineOption noImplicitIncludesOption(QStringList() << QStringLiteral("n") << QStringLiteral("no-implicit-includes")); - noImplicitIncludesOption.setDescription(QStringLiteral("Disable generation of #include-directives.")); + QCommandLineOption noImplicitIncludesOption(QStringList{u"n"_s, u"no-implicit-includes"_s}); + noImplicitIncludesOption.setDescription(u"Disable generation of #include-directives."_s); parser.addOption(noImplicitIncludesOption); - QCommandLineOption postfixOption(QStringLiteral("postfix")); - postfixOption.setDescription(QStringLiteral("Postfix to add to all generated classnames.")); - postfixOption.setValueName(QStringLiteral("postfix")); + QCommandLineOption postfixOption(u"postfix"_s); + postfixOption.setDescription(u"Postfix to add to all generated classnames."_s); + postfixOption.setValueName(u"postfix"_s); parser.addOption(postfixOption); - QCommandLineOption translateOption(QStringList() << QStringLiteral("tr") << QStringLiteral("translate")); - translateOption.setDescription(QStringLiteral("Use <function> for i18n.")); - translateOption.setValueName(QStringLiteral("function")); + QCommandLineOption noQtNamespaceOption(u"no-qt-namespace"_s); + noQtNamespaceOption.setDescription( + u"Disable wrapping the definition of the generated class in QT_{BEGIN,END}_NAMESPACE."_s); + parser.addOption(noQtNamespaceOption); + + QCommandLineOption translateOption(QStringList{u"tr"_s, u"translate"_s}); + translateOption.setDescription(u"Use <function> for i18n."_s); + translateOption.setValueName(u"function"_s); parser.addOption(translateOption); - QCommandLineOption includeOption(QStringList() << QStringLiteral("include")); - includeOption.setDescription(QStringLiteral("Add #include <include-file> to <file>.")); - includeOption.setValueName(QStringLiteral("include-file")); + QCommandLineOption includeOption(QStringList{u"include"_s}); + includeOption.setDescription(u"Add #include <include-file> to <file>."_s); + includeOption.setValueName(u"include-file"_s); parser.addOption(includeOption); - QCommandLineOption generatorOption(QStringList() << QStringLiteral("g") << QStringLiteral("generator")); - generatorOption.setDescription(QStringLiteral("Select generator.")); - generatorOption.setValueName(QStringLiteral("python|cpp")); + QCommandLineOption generatorOption(QStringList{u"g"_s, u"generator"_s}); + generatorOption.setDescription(u"Select generator."_s); + generatorOption.setValueName(u"python|cpp"_s); parser.addOption(generatorOption); - QCommandLineOption connectionsOption(QStringList{QStringLiteral("c"), QStringLiteral("connections")}); - connectionsOption.setDescription(QStringLiteral("Connection syntax.")); - connectionsOption.setValueName(QStringLiteral("pmf|string")); + QCommandLineOption connectionsOption(QStringList{u"c"_s, u"connections"_s}); + connectionsOption.setDescription(u"Connection syntax."_s); + connectionsOption.setValueName(u"pmf|string"_s); parser.addOption(connectionsOption); - QCommandLineOption idBasedOption(QStringLiteral("idbased")); - idBasedOption.setDescription(QStringLiteral("Use id based function for i18n")); + QCommandLineOption idBasedOption(u"idbased"_s); + idBasedOption.setDescription(u"Use id based function for i18n"_s); parser.addOption(idBasedOption); - QCommandLineOption fromImportsOption(QStringLiteral("from-imports")); - fromImportsOption.setDescription(QStringLiteral("Python: generate imports relative to '.'")); + QCommandLineOption fromImportsOption(u"from-imports"_s); + fromImportsOption.setDescription(u"Python: generate imports relative to '.'"_s); parser.addOption(fromImportsOption); + QCommandLineOption absoluteImportsOption(u"absolute-imports"_s); + absoluteImportsOption.setDescription(u"Python: generate absolute imports"_s); + parser.addOption(absoluteImportsOption); + + // FIXME Qt 7: Flip the default? + QCommandLineOption rcPrefixOption(u"rc-prefix"_s); + rcPrefixOption.setDescription(uR"(Python: Generate "rc_file" instead of "file_rc" import)"_s); + parser.addOption(rcPrefixOption); + // FIXME Qt 7: Remove? - QCommandLineOption useStarImportsOption(QStringLiteral("star-imports")); - useStarImportsOption.setDescription(QStringLiteral("Python: Use * imports")); + QCommandLineOption useStarImportsOption(u"star-imports"_s); + useStarImportsOption.setDescription(u"Python: Use * imports"_s); parser.addOption(useStarImportsOption); - parser.addPositionalArgument(QStringLiteral("[uifile]"), QStringLiteral("Input file (*.ui), otherwise stdin.")); + QCommandLineOption pythonPathOption(u"python-paths"_s); + pythonPathOption.setDescription(u"Python paths for --absolute-imports."_s); + pythonPathOption.setValueName(u"pathlist"_s); + parser.addOption(pythonPathOption); + + parser.addPositionalArgument(u"[uifile]"_s, u"Input file (*.ui), otherwise stdin."_s); parser.process(app); @@ -126,31 +154,44 @@ int runUic(int argc, char *argv[]) driver.option().autoConnection = !parser.isSet(noAutoConnectionOption); driver.option().headerProtection = !parser.isSet(noProtOption); driver.option().implicitIncludes = !parser.isSet(noImplicitIncludesOption); + driver.option().qtNamespace = !parser.isSet(noQtNamespaceOption); driver.option().idBased = parser.isSet(idBasedOption); - driver.option().fromImports = parser.isSet(fromImportsOption); - driver.option().useStarImports = parser.isSet(useStarImportsOption); driver.option().postfix = parser.value(postfixOption); driver.option().translateFunction = parser.value(translateOption); driver.option().includeFile = parser.value(includeOption); if (parser.isSet(connectionsOption)) { const auto value = parser.value(connectionsOption); - if (value == QLatin1String("pmf")) + if (value == "pmf"_L1) driver.option().forceMemberFnPtrConnectionSyntax = 1; - else if (value == QLatin1String("string")) + else if (value == "string"_L1) driver.option().forceStringConnectionSyntax = 1; } + const QString inputFile = parser.positionalArguments().value(0); + Language language = Language::Cpp; if (parser.isSet(generatorOption)) { - if (parser.value(generatorOption).compare(QLatin1String("python")) == 0) + if (parser.value(generatorOption).compare("python"_L1) == 0) language = Language::Python; } language::setLanguage(language); + if (language == Language::Python) { + if (parser.isSet(fromImportsOption)) + driver.option().pythonResourceImport = Option::PythonResourceImport::FromDot; + else if (parser.isSet(absoluteImportsOption)) + driver.option().pythonResourceImport = Option::PythonResourceImport::Absolute; + driver.option().useStarImports = parser.isSet(useStarImportsOption); + if (parser.isSet(rcPrefixOption)) + driver.option().rcPrefix = 1; + QString pythonPaths; + if (parser.isSet(pythonPathOption)) + pythonPaths = parser.value(pythonPathOption); + else if (qEnvironmentVariableIsSet(pythonPathVar)) + pythonPaths = QString::fromUtf8(qgetenv(pythonPathVar)); + driver.option().pythonRoot = pythonRoot(pythonPaths, inputFile); + } - QString inputFile; - if (!parser.positionalArguments().isEmpty()) - inputFile = parser.positionalArguments().at(0); - else // reading from stdin + if (inputFile.isEmpty()) // reading from stdin driver.option().headerProtection = false; if (driver.option().dependencies) { diff --git a/src/tools/uic/option.h b/src/tools/uic/option.h index b08aab2d3e..cfdd90fda3 100644 --- a/src/tools/uic/option.h +++ b/src/tools/uic/option.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef OPTION_H #define OPTION_H @@ -36,6 +11,12 @@ QT_BEGIN_NAMESPACE struct Option { + enum class PythonResourceImport { + Default, // "import rc_file" + FromDot, // "from . import rc_file" + Absolute // "import path.rc_file" + }; + unsigned int headerProtection : 1; unsigned int copyrightHeader : 1; unsigned int generateImplemetation : 1; @@ -45,10 +26,11 @@ struct Option unsigned int limitXPM_LineLength : 1; unsigned int implicitIncludes: 1; unsigned int idBased: 1; - unsigned int fromImports: 1; unsigned int forceMemberFnPtrConnectionSyntax: 1; unsigned int forceStringConnectionSyntax: 1; unsigned int useStarImports: 1; + unsigned int rcPrefix: 1; // Python: Generate "rc_file" instead of "file_rc" import + unsigned int qtNamespace: 1; QString inputFile; QString outputFile; @@ -58,6 +40,9 @@ struct Option QString postfix; QString translateFunction; QString includeFile; + QString pythonRoot; + + PythonResourceImport pythonResourceImport = PythonResourceImport::Default; Option() : headerProtection(1), @@ -69,17 +54,18 @@ struct Option limitXPM_LineLength(0), implicitIncludes(1), idBased(0), - fromImports(0), forceMemberFnPtrConnectionSyntax(0), forceStringConnectionSyntax(0), useStarImports(0), - prefix(QLatin1String("Ui_")) - { indent.fill(QLatin1Char(' '), 4); } + rcPrefix(0), + qtNamespace(1), + prefix(QLatin1StringView("Ui_")) + { indent.fill(u' ', 4); } QString messagePrefix() const { return inputFile.isEmpty() ? - QString(QLatin1String("stdin")) : + QString(QLatin1StringView("stdin")) : QDir::toNativeSeparators(inputFile); } }; diff --git a/src/tools/uic/python/pythonwritedeclaration.cpp b/src/tools/uic/python/pythonwritedeclaration.cpp index 5122b18e23..665eb8fe76 100644 --- a/src/tools/uic/python/pythonwritedeclaration.cpp +++ b/src/tools/uic/python/pythonwritedeclaration.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "pythonwritedeclaration.h" #include <cppwriteinitialization.h> @@ -38,6 +13,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + namespace Python { WriteDeclaration::WriteDeclaration(Uic *uic) : @@ -51,7 +28,7 @@ WriteDeclaration::WriteDeclaration(Uic *uic) : void WriteDeclaration::acceptUI(DomUI *node) { // remove any left-over C++ namespaces - const QString qualifiedClassName = QLatin1String("Ui_") + node->elementClass() + const QString qualifiedClassName = "Ui_"_L1 + node->elementClass() + m_option.postfix; m_output << "class " << language::fixClassName(qualifiedClassName) << "(object):\n"; diff --git a/src/tools/uic/python/pythonwritedeclaration.h b/src/tools/uic/python/pythonwritedeclaration.h index a8d50b6fbf..6aed86f382 100644 --- a/src/tools/uic/python/pythonwritedeclaration.h +++ b/src/tools/uic/python/pythonwritedeclaration.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef PYTHONWRITEDECLARATION_H #define PYTHONWRITEDECLARATION_H diff --git a/src/tools/uic/python/pythonwriteimports.cpp b/src/tools/uic/python/pythonwriteimports.cpp index f9dfeb28da..74eeab8387 100644 --- a/src/tools/uic/python/pythonwriteimports.cpp +++ b/src/tools/uic/python/pythonwriteimports.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "pythonwriteimports.h" @@ -35,12 +10,16 @@ #include <ui4.h> +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> #include <QtCore/qtextstream.h> #include <algorithm> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + // Generate imports for Python. Note some things differ from C++: // - qItemView->header()->setFoo() does not require QHeaderView to be imported // - qLabel->setFrameShape(QFrame::Box) however requires QFrame to be imported @@ -70,26 +49,13 @@ static WriteImports::ClassesPerModule defaultClasses() QStringLiteral("QPainter"), QStringLiteral("QPixmap"), QStringLiteral("QTransform"), QStringLiteral("QRadialGradient")} }, + // Add QWidget for QWidget.setTabOrder() {QStringLiteral("QtWidgets"), - {QStringLiteral("QSizePolicy")} + {QStringLiteral("QSizePolicy"), QStringLiteral("QWidget")} } }; } -// Change the name of a qrc file "dir/foo.qrc" file to the Python -// module name "foo_rc" according to project conventions. -static QString pythonResource(QString resource) -{ - const int lastSlash = resource.lastIndexOf(QLatin1Char('/')); - if (lastSlash != -1) - resource.remove(0, lastSlash + 1); - if (resource.endsWith(QLatin1String(".qrc"))) { - resource.chop(4); - resource.append(QLatin1String("_rc")); - } - return resource; -} - // Helpers for WriteImports::ClassesPerModule maps static void insertClass(const QString &module, const QString &className, WriteImports::ClassesPerModule *c) @@ -136,7 +102,7 @@ WriteImports::WriteImports(Uic *uic) : WriteIncludesBase(uic), m_qtClasses(defaultClasses()) { for (const auto &e : classInfoEntries()) - m_classToModule.insert(QLatin1String(e.klass), QLatin1String(e.module)); + m_classToModule.insert(QLatin1StringView(e.klass), QLatin1StringView(e.module)); } void WriteImports::acceptUI(DomUI *node) @@ -162,27 +128,67 @@ void WriteImports::acceptUI(DomUI *node) const auto includes = resources->elementInclude(); for (auto include : includes) { if (include->hasAttributeLocation()) - writeImport(pythonResource(include->attributeLocation())); + writeResourceImport(include->attributeLocation()); } output << '\n'; } } -void WriteImports::writeImport(const QString &module) +QString WriteImports::resourceAbsolutePath(QString resource) const +{ + // If we know the project root, generate an absolute Python import + // to the resource. options. pythonRoot is the Python path component + // under which the UI file is. + const auto &options = uic()->option(); + if (!options.inputFile.isEmpty() && !options.pythonRoot.isEmpty()) { + resource = QDir::cleanPath(QFileInfo(options.inputFile).canonicalPath() + u'/' + resource); + if (resource.size() > options.pythonRoot.size()) + resource.remove(0, options.pythonRoot.size() + 1); + } + // If nothing is known, we assume the directory pointed by "../" is the root + while (resource.startsWith(u"../")) + resource.remove(0, 3); + resource.replace(u'/', u'.'); + return resource; +} + +void WriteImports::writeResourceImport(const QString &module) { - if (uic()->option().fromImports) - uic()->output() << "from . "; - uic()->output() << "import " << module << '\n'; + const auto &options = uic()->option(); + auto &str = uic()->output(); + + QString resource = QDir::cleanPath(module); + if (resource.endsWith(u".qrc")) + resource.chop(4); + const qsizetype basePos = resource.lastIndexOf(u'/') + 1; + // Change the name of a qrc file "dir/foo.qrc" file to the Python + // module name "foo_rc" according to project conventions. + if (options.rcPrefix) + resource.insert(basePos, u"rc_"); + else + resource.append(u"_rc"); + + switch (options.pythonResourceImport) { + case Option::PythonResourceImport::Default: + str << "import " << QStringView{resource}.sliced(basePos) << '\n'; + break; + case Option::PythonResourceImport::FromDot: + str << "from . import " << QStringView{resource}.sliced(basePos) << '\n'; + break; + case Option::PythonResourceImport::Absolute: + str << "import " << resourceAbsolutePath(resource) << '\n'; + break; + } } void WriteImports::doAdd(const QString &className, const DomCustomWidget *dcw) { const CustomWidgetsInfo *cwi = uic()->customWidgetsInfo(); - if (cwi->extends(className, QLatin1String("QListWidget"))) + if (cwi->extends(className, "QListWidget")) add(QStringLiteral("QListWidgetItem")); - else if (cwi->extends(className, QLatin1String("QTreeWidget"))) + else if (cwi->extends(className, "QTreeWidget")) add(QStringLiteral("QTreeWidgetItem")); - else if (cwi->extends(className, QLatin1String("QTableWidget"))) + else if (cwi->extends(className, "QTableWidget")) add(QStringLiteral("QTableWidgetItem")); if (dcw != nullptr) { @@ -209,7 +215,7 @@ bool WriteImports::addQtClass(const QString &className) void WriteImports::addPythonCustomWidget(const QString &className, const DomCustomWidget *node) { - if (className.contains(QLatin1String("::"))) + if (className.contains("::"_L1)) return; // Exclude namespaced names (just to make tests pass). if (addQtClass(className)) // Qt custom widgets like QQuickWidget, QAxWidget, etc @@ -222,26 +228,45 @@ void WriteImports::addPythonCustomWidget(const QString &className, const DomCust } else { // When we do have elementHeader, we know it's a relative import. QString modulePath = node->elementHeader()->text(); // Replace the '/' by '.' - modulePath.replace(QLatin1Char('/'), QLatin1Char('.')); - // '.h' is added by default on headers for <customwidget> - if (modulePath.endsWith(QLatin1String(".h"))) + modulePath.replace(u'/', u'.'); + // '.h' is added by default on headers for <customwidget>. + if (modulePath.endsWith(".h"_L1, Qt::CaseInsensitive)) modulePath.chop(2); + else if (modulePath.endsWith(".hh"_L1)) + modulePath.chop(3); + else if (modulePath.endsWith(".hpp"_L1)) + modulePath.chop(4); insertClass(modulePath, className, &m_customWidgets); } } void WriteImports::acceptProperty(DomProperty *node) { - if (node->kind() == DomProperty::Enum) { - // Add base classes like QFrame for QLabel::frameShape() - const QString &enumV = node->elementEnum(); - const auto colonPos = enumV.indexOf(u"::"); - if (colonPos > 0) - addQtClass(enumV.left(colonPos)); + switch (node->kind()) { + case DomProperty::Enum: + addEnumBaseClass(node->elementEnum()); + break; + case DomProperty::Set: + addEnumBaseClass(node->elementSet()); + break; + default: + break; } + WriteIncludesBase::acceptProperty(node); } +void WriteImports::addEnumBaseClass(const QString &v) +{ + // Add base classes like QFrame for QLabel::frameShape() + const auto colonPos = v.indexOf(u"::"); + if (colonPos > 0) { + const QString base = v.left(colonPos); + if (base.startsWith(u'Q') && base != u"Qt") + addQtClass(base); + } +} + } // namespace Python QT_END_NAMESPACE diff --git a/src/tools/uic/python/pythonwriteimports.h b/src/tools/uic/python/pythonwriteimports.h index c833e216a6..4497b8dc33 100644 --- a/src/tools/uic/python/pythonwriteimports.h +++ b/src/tools/uic/python/pythonwriteimports.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef PYTHONWRITEIMPORTS_H #define PYTHONWRITEIMPORTS_H @@ -55,7 +30,9 @@ protected: private: void addPythonCustomWidget(const QString &className, const DomCustomWidget *dcw); bool addQtClass(const QString &className); - void writeImport(const QString &module); + void addEnumBaseClass(const QString &v); + void writeResourceImport(const QString &module); + QString resourceAbsolutePath(QString resource) const; QHash<QString, QString> m_classToModule; // Module->class (modules sorted) diff --git a/src/tools/uic/qclass_lib_map.h b/src/tools/uic/qclass_lib_map.h index 59b2205e2d..e9e4cfde12 100644 --- a/src/tools/uic/qclass_lib_map.h +++ b/src/tools/uic/qclass_lib_map.h @@ -219,6 +219,7 @@ QT_CLASS_LIB(QStack, QtCore, qstack.h) QT_CLASS_LIB(QStdWString, QtCore, qstring.h) QT_CLASS_LIB(QString, QtCore, qstring.h) QT_CLASS_LIB(QLatin1String, QtCore, qstring.h) +QT_CLASS_LIB(QLatin1StringView, QtCore, qstring.h) QT_CLASS_LIB(QCharRef, QtCore, qstring.h) QT_CLASS_LIB(QLatin1Literal, QtCore, qstring.h) QT_CLASS_LIB(QAbstractConcatenable, QtCore, qstringbuilder.h) @@ -836,6 +837,7 @@ QT_CLASS_LIB(QGraphicsSvgItem, QtSvgWidgets, qgraphicssvgitem.h) QT_CLASS_LIB(QSvgWidget, QtSvgWidgets, qsvgwidget.h) QT_CLASS_LIB(QSvgGenerator, QtSvg, qsvggenerator.h) QT_CLASS_LIB(QSvgRenderer, QtSvg, qsvgrenderer.h) +QT_CLASS_LIB(QPdfView, QtPdfWidgets, qpdfview.h) QT_CLASS_LIB(QQuickWidget, QtQuickWidgets, qquickwidget.h) QT_CLASS_LIB(QVideoWidget, QtMultimediaWidgets, qvideowidget.h) QT_CLASS_LIB(QWebEngineView, QtWebEngineWidgets, qwebengineview.h) diff --git a/src/tools/uic/shared/language.cpp b/src/tools/uic/shared/language.cpp index 2f2ae3ebc6..d59688e346 100644 --- a/src/tools/uic/shared/language.cpp +++ b/src/tools/uic/shared/language.cpp @@ -1,37 +1,15 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "language.h" #include <QtCore/qtextstream.h> +#include <QtCore/QList> namespace language { +using namespace Qt::StringLiterals; + static Encoding encoding = Encoding::Utf8; static Language _language = Language::Cpp; @@ -42,29 +20,29 @@ void setLanguage(Language l) _language = l; switch (_language) { case Language::Cpp: - derefPointer = QLatin1String("->"); + derefPointer = u"->"_s; listStart = '{'; listEnd = '}'; - nullPtr = QLatin1String("nullptr"); - operatorNew = QLatin1String("new "); - qtQualifier = QLatin1String("Qt::"); - qualifier = QLatin1String("::"); - self = QLatin1String(""); // for testing: change to "this->"; - eol = QLatin1String(";\n"); - emptyString = QLatin1String("QString()"); + nullPtr = u"nullptr"_s; + operatorNew = u"new "_s; + qtQualifier = u"Qt::"_s; + qualifier = u"::"_s; + self = u""_s; // for testing: change to "this->"; + eol = u";\n"_s; + emptyString = u"QString()"_s; encoding = Encoding::Utf8; break; case Language::Python: - derefPointer = QLatin1String("."); + derefPointer = u"."_s; listStart = '['; listEnd = ']'; - nullPtr = QLatin1String("None"); - operatorNew = QLatin1String(""); - qtQualifier = QLatin1String("Qt."); - qualifier = QLatin1String("."); - self = QLatin1String("self."); - eol = QLatin1String("\n"); - emptyString = QLatin1String("\"\""); + nullPtr = u"None"_s; + operatorNew = u""_s; + qtQualifier = u"Qt."_s; + qualifier = u"."_s; + self = u"self."_s; + eol = u"\n"_s; + emptyString = u"\"\""_s; encoding = Encoding::Unicode; break; } @@ -81,9 +59,9 @@ QString self; QString eol; QString emptyString; -QString cppQualifier = QLatin1String("::"); -QString cppTrue = QLatin1String("true"); -QString cppFalse = QLatin1String("false"); +QString cppQualifier = u"::"_s; +QString cppTrue = u"true"_s; +QString cppFalse = u"false"_s; QTextStream &operator<<(QTextStream &str, const qtConfig &c) { @@ -106,97 +84,97 @@ QTextStream &operator<<(QTextStream &str, const closeQtConfig &c) struct EnumLookup { int value; - const char *valueString; + QLatin1StringView valueString; }; template <int N> -const char *lookupEnum(const EnumLookup(&array)[N], int value, int defaultIndex = 0) +QLatin1StringView lookupEnum(const EnumLookup(&array)[N], int value, int defaultIndex = 0) { for (int i = 0; i < N; ++i) { if (value == array[i].value) return array[i].valueString; } - const char *defaultValue = array[defaultIndex].valueString; + auto defaultValue = array[defaultIndex].valueString; qWarning("uic: Warning: Invalid enumeration value %d, defaulting to %s", - value, defaultValue); + value, defaultValue.data()); return defaultValue; } QString fixClassName(QString className) { if (language() == Language::Python) - className.replace(cppQualifier, QLatin1String("_")); + className.replace(cppQualifier, "_"_L1); return className; } -const char *toolbarArea(int v) +QLatin1StringView toolbarArea(int v) { static const EnumLookup toolBarAreas[] = { - {0, "NoToolBarArea"}, - {0x1, "LeftToolBarArea"}, - {0x2, "RightToolBarArea"}, - {0x4, "TopToolBarArea"}, - {0x8, "BottomToolBarArea"}, - {0xf, "AllToolBarAreas"} + {0, "NoToolBarArea"_L1}, + {0x1, "LeftToolBarArea"_L1}, + {0x2, "RightToolBarArea"_L1}, + {0x4, "TopToolBarArea"_L1}, + {0x8, "BottomToolBarArea"_L1}, + {0xf, "AllToolBarAreas"_L1} }; return lookupEnum(toolBarAreas, v); } -const char *sizePolicy(int v) +QLatin1StringView sizePolicy(int v) { static const EnumLookup sizePolicies[] = { - {0, "Fixed"}, - {0x1, "Minimum"}, - {0x4, "Maximum"}, - {0x5, "Preferred"}, - {0x3, "MinimumExpanding"}, - {0x7, "Expanding"}, - {0xD, "Ignored"} + {0, "Fixed"_L1}, + {0x1, "Minimum"_L1}, + {0x4, "Maximum"_L1}, + {0x5, "Preferred"_L1}, + {0x3, "MinimumExpanding"_L1}, + {0x7, "Expanding"_L1}, + {0xD, "Ignored"_L1} }; return lookupEnum(sizePolicies, v, 3); } -const char *dockWidgetArea(int v) +QLatin1StringView dockWidgetArea(int v) { static const EnumLookup dockWidgetAreas[] = { - {0, "NoDockWidgetArea"}, - {0x1, "LeftDockWidgetArea"}, - {0x2, "RightDockWidgetArea"}, - {0x4, "TopDockWidgetArea"}, - {0x8, "BottomDockWidgetArea"}, - {0xf, "AllDockWidgetAreas"} + {0, "NoDockWidgetArea"_L1}, + {0x1, "LeftDockWidgetArea"_L1}, + {0x2, "RightDockWidgetArea"_L1}, + {0x4, "TopDockWidgetArea"_L1}, + {0x8, "BottomDockWidgetArea"_L1}, + {0xf, "AllDockWidgetAreas"_L1} }; return lookupEnum(dockWidgetAreas, v); } -const char *paletteColorRole(int v) +QLatin1StringView paletteColorRole(int v) { static const EnumLookup colorRoles[] = { - {0, "WindowText"}, - {1, "Button"}, - {2, "Light"}, - {3, "Midlight"}, - {4, "Dark"}, - {5, "Mid"}, - {6, "Text"}, - {7, "BrightText"}, - {8, "ButtonText"}, - {9, "Base"}, - {10, "Window"}, - {11, "Shadow"}, - {12, "Highlight"}, - {13, "HighlightedText"}, - {14, "Link"}, - {15, "LinkVisited"}, - {16, "AlternateBase"}, - {17, "NoRole"}, - {18, "ToolTipBase"}, - {19, "ToolTipText"}, - {20, "PlaceholderText"}, + {0, "WindowText"_L1}, + {1, "Button"_L1}, + {2, "Light"_L1}, + {3, "Midlight"_L1}, + {4, "Dark"_L1}, + {5, "Mid"_L1}, + {6, "Text"_L1}, + {7, "BrightText"_L1}, + {8, "ButtonText"_L1}, + {9, "Base"_L1}, + {10, "Window"_L1}, + {11, "Shadow"_L1}, + {12, "Highlight"_L1}, + {13, "HighlightedText"_L1}, + {14, "Link"_L1}, + {15, "LinkVisited"_L1}, + {16, "AlternateBase"_L1}, + {17, "NoRole"_L1}, + {18, "ToolTipBase"_L1}, + {19, "ToolTipText"_L1}, + {20, "PlaceholderText"_L1}, }; return lookupEnum(colorRoles, v); } @@ -217,7 +195,7 @@ static int formatEscapedNumber(QTextStream &str, ushort value, int base, int wid const auto oldFieldWidth = str.fieldWidth(); const auto oldFieldAlignment = str.fieldAlignment(); const auto oldIntegerBase = str.integerBase(); - str.setPadChar(QLatin1Char('0')); + str.setPadChar(u'0'); str.setFieldWidth(width); str.setFieldAlignment(QTextStream::AlignRight); str.setIntegerBase(base); @@ -393,29 +371,65 @@ void _formatStackVariable(QTextStream &str, const char *className, QStringView v } } -enum OverloadUse { - UseOverload, - UseOverloadWhenNoArguments, // Use overload only when the argument list is empty, - // in this case there is no chance of connecting - // mismatching T against const T & - DontUseOverload +enum class OverloadUse { + Always, + WhenAmbiguousOrEmpty, // Use overload if + // - signal/slot is ambiguous + // - argument list is empty (chance of connecting mismatching T against const T &) + Never, }; // Format a member function for a signal slot connection -static void formatMemberFnPtr(QTextStream &str, const SignalSlot &s, - OverloadUse useQOverload = DontUseOverload) +static bool isConstRef(const QStringView &arg) { - const int parenPos = s.signature.indexOf(QLatin1Char('(')); + return arg.startsWith(u'Q') && arg != "QPoint"_L1 && arg != "QSize"_L1; +} + +static QString formatOverload(const QStringView ¶meters) +{ + QString result = "qOverload<"_L1; + const auto args = QStringView{parameters}.split(u','); + for (qsizetype i = 0, size = args.size(); i < size; ++i) { + const auto &arg = args.at(i); + if (i > 0) + result += u','; + const bool constRef = isConstRef(arg); + if (constRef) + result += "const "_L1; + result += arg; + if (constRef) + result += u'&'; + } + result += u'>'; + return result; +} + +static void formatMemberFnPtr(QTextStream &str, const SignalSlot &s, OverloadUse useQOverload) +{ + const qsizetype parenPos = s.signature.indexOf(u'('); Q_ASSERT(parenPos >= 0); const auto functionName = QStringView{s.signature}.left(parenPos); const auto parameters = QStringView{s.signature}.mid(parenPos + 1, s.signature.size() - parenPos - 2); - const bool withOverload = useQOverload == UseOverload || - (useQOverload == UseOverloadWhenNoArguments && parameters.isEmpty()); + + const bool isAmbiguous = s.options.testFlag(SignalSlotOption::Ambiguous); + bool withOverload = false; // just to silence the compiler + + switch (useQOverload) { + case OverloadUse::Always: + withOverload = true; + break; + case OverloadUse::Never: + withOverload = false; + break; + case OverloadUse::WhenAmbiguousOrEmpty: + withOverload = parameters.empty() || isAmbiguous; + break; + } if (withOverload) - str << "qOverload<" << parameters << ">("; + str << formatOverload(parameters) << '('; str << '&' << s.className << "::" << functionName; @@ -428,9 +442,9 @@ static void formatMemberFnPtrConnection(QTextStream &str, const SignalSlot &receiver) { str << "QObject::connect(" << sender.name << ", "; - formatMemberFnPtr(str, sender); + formatMemberFnPtr(str, sender, OverloadUse::Never); str << ", " << receiver.name << ", "; - formatMemberFnPtr(str, receiver, UseOverloadWhenNoArguments); + formatMemberFnPtr(str, receiver, OverloadUse::WhenAmbiguousOrEmpty); str << ')'; } @@ -456,12 +470,22 @@ void formatConnection(QTextStream &str, const SignalSlot &sender, const SignalSl break; } break; - case Language::Python: - str << sender.name << '.' - << QStringView{sender.signature}.left(sender.signature.indexOf(QLatin1Char('('))) - << ".connect(" << receiver.name << '.' - << QStringView{receiver.signature}.left(receiver.signature.indexOf(QLatin1Char('('))) + case Language::Python: { + const auto paren = sender.signature.indexOf(u'('); + auto senderSignature = QStringView{sender.signature}; + str << sender.name << '.' << senderSignature.left(paren); + // Signals like "QAbstractButton::clicked(checked=false)" require + // the parameter if it is used. + if (sender.options.testFlag(SignalSlotOption::Ambiguous)) { + const QStringView parameters = + senderSignature.mid(paren + 1, senderSignature.size() - paren - 2); + if (!parameters.isEmpty() && !parameters.contains(u',')) + str << "[\"" << parameters << "\"]"; + } + str << ".connect(" << receiver.name << '.' + << QStringView{receiver.signature}.left(receiver.signature.indexOf(u'(')) << ')'; + } break; } } diff --git a/src/tools/uic/shared/language.h b/src/tools/uic/shared/language.h index 918f25e46a..de39122ee8 100644 --- a/src/tools/uic/shared/language.h +++ b/src/tools/uic/shared/language.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef LANGUAGE_H #define LANGUAGE_H @@ -100,10 +75,10 @@ QTextStream &operator<<(QTextStream &, const closeQtConfig &c); QString fixClassName(QString className); -const char *toolbarArea(int v); -const char *sizePolicy(int v); -const char *dockWidgetArea(int v); -const char *paletteColorRole(int v); +QLatin1StringView toolbarArea(int v); +QLatin1StringView sizePolicy(int v); +QLatin1StringView dockWidgetArea(int v); +QLatin1StringView paletteColorRole(int v); enum class Encoding { Utf8, Unicode }; @@ -198,11 +173,19 @@ inline QTextStream &operator<<(QTextStream &str, const _stackVariable<withInitPa using stackVariable = _stackVariable<false>; using stackVariableWithInitParameters = _stackVariable<true>; +enum class SignalSlotOption +{ + Ambiguous = 0x1 +}; + +Q_DECLARE_FLAGS(SignalSlotOptions, SignalSlotOption) + struct SignalSlot { QString name; QString signature; QString className; + SignalSlotOptions options; }; void formatConnection(QTextStream &str, const SignalSlot &sender, const SignalSlot &receiver, diff --git a/src/tools/uic/shared/writeincludesbase.cpp b/src/tools/uic/shared/writeincludesbase.cpp index 128200ed68..78f69d5391 100644 --- a/src/tools/uic/shared/writeincludesbase.cpp +++ b/src/tools/uic/shared/writeincludesbase.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "writeincludesbase.h" #include "ui4.h" @@ -118,7 +93,7 @@ void WriteIncludesBase::add(const QString &className, const DomCustomWidget *dcw if (cwi->extendsOneOf(className, treeViewsWithHeaders)) add(QStringLiteral("QHeaderView")); - if (!m_laidOut && cwi->extends(className, QLatin1String("QToolBox"))) + if (!m_laidOut && cwi->extends(className, "QToolBox")) add(QStringLiteral("QLayout")); // spacing property of QToolBox) if (className == QStringLiteral("Line")) { // ### hmm, deprecate me! @@ -126,7 +101,7 @@ void WriteIncludesBase::add(const QString &className, const DomCustomWidget *dcw return; } - if (cwi->extends(className, QLatin1String("QDialogButtonBox"))) + if (cwi->extends(className, "QDialogButtonBox")) add(QStringLiteral("QAbstractButton")); // for signal "clicked(QAbstractButton*)" doAdd(className, dcw); diff --git a/src/tools/uic/shared/writeincludesbase.h b/src/tools/uic/shared/writeincludesbase.h index 15e57b8c36..71ddcc80d9 100644 --- a/src/tools/uic/shared/writeincludesbase.h +++ b/src/tools/uic/shared/writeincludesbase.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef WRITEINCLUDES_BASE_H #define WRITEINCLUDES_BASE_H diff --git a/src/tools/uic/treewalker.cpp b/src/tools/uic/treewalker.cpp index 4c9981580b..731017b1a2 100644 --- a/src/tools/uic/treewalker.cpp +++ b/src/tools/uic/treewalker.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "treewalker.h" #include "ui4.h" diff --git a/src/tools/uic/treewalker.h b/src/tools/uic/treewalker.h index abc5658924..ff5d032648 100644 --- a/src/tools/uic/treewalker.h +++ b/src/tools/uic/treewalker.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef TREEWALKER_H #define TREEWALKER_H diff --git a/src/tools/uic/ui4.cpp b/src/tools/uic/ui4.cpp index a155df9b6c..d65fc4a8c3 100644 --- a/src/tools/uic/ui4.cpp +++ b/src/tools/uic/ui4.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT! @@ -32,6 +7,9 @@ QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + #ifdef QFORMINTERNAL_NAMESPACE using namespace QFormInternal; #endif @@ -60,133 +38,133 @@ void DomUI::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("version")) { + if (name == u"version"_s) { setAttributeVersion(attribute.value().toString()); continue; } - if (name == QLatin1String("language")) { + if (name == u"language"_s) { setAttributeLanguage(attribute.value().toString()); continue; } - if (name == QLatin1String("displayname")) { + if (name == u"displayname"_s) { setAttributeDisplayname(attribute.value().toString()); continue; } - if (name == QLatin1String("idbasedtr")) { - setAttributeIdbasedtr(attribute.value() == QLatin1String("true")); + if (name == u"idbasedtr"_s) { + setAttributeIdbasedtr(attribute.value() == u"true"_s); continue; } - if (name == QLatin1String("connectslotsbyname")) { - setAttributeConnectslotsbyname(attribute.value() == QLatin1String("true")); + if (name == u"connectslotsbyname"_s) { + setAttributeConnectslotsbyname(attribute.value() == u"true"_s); continue; } - if (name == QLatin1String("stdsetdef")) { + if (name == u"stdsetdef"_s) { setAttributeStdsetdef(attribute.value().toInt()); continue; } - if (name == QLatin1String("stdSetDef")) { + if (name == u"stdSetDef"_s) { setAttributeStdSetDef(attribute.value().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("author"), Qt::CaseInsensitive)) { + if (!tag.compare(u"author"_s, Qt::CaseInsensitive)) { setElementAuthor(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("comment"), Qt::CaseInsensitive)) { + if (!tag.compare(u"comment"_s, Qt::CaseInsensitive)) { setElementComment(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("exportmacro"), Qt::CaseInsensitive)) { + if (!tag.compare(u"exportmacro"_s, Qt::CaseInsensitive)) { setElementExportMacro(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("class"), Qt::CaseInsensitive)) { + if (!tag.compare(u"class"_s, Qt::CaseInsensitive)) { setElementClass(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("widget"), Qt::CaseInsensitive)) { + if (!tag.compare(u"widget"_s, Qt::CaseInsensitive)) { auto *v = new DomWidget(); v->read(reader); setElementWidget(v); continue; } - if (!tag.compare(QLatin1String("layoutdefault"), Qt::CaseInsensitive)) { + if (!tag.compare(u"layoutdefault"_s, Qt::CaseInsensitive)) { auto *v = new DomLayoutDefault(); v->read(reader); setElementLayoutDefault(v); continue; } - if (!tag.compare(QLatin1String("layoutfunction"), Qt::CaseInsensitive)) { + if (!tag.compare(u"layoutfunction"_s, Qt::CaseInsensitive)) { auto *v = new DomLayoutFunction(); v->read(reader); setElementLayoutFunction(v); continue; } - if (!tag.compare(QLatin1String("pixmapfunction"), Qt::CaseInsensitive)) { + if (!tag.compare(u"pixmapfunction"_s, Qt::CaseInsensitive)) { setElementPixmapFunction(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("customwidgets"), Qt::CaseInsensitive)) { + if (!tag.compare(u"customwidgets"_s, Qt::CaseInsensitive)) { auto *v = new DomCustomWidgets(); v->read(reader); setElementCustomWidgets(v); continue; } - if (!tag.compare(QLatin1String("tabstops"), Qt::CaseInsensitive)) { + if (!tag.compare(u"tabstops"_s, Qt::CaseInsensitive)) { auto *v = new DomTabStops(); v->read(reader); setElementTabStops(v); continue; } - if (!tag.compare(QLatin1String("images"), Qt::CaseInsensitive)) { + if (!tag.compare(u"images"_s, Qt::CaseInsensitive)) { qWarning("Omitting deprecated element <images>."); reader.skipCurrentElement(); continue; } - if (!tag.compare(QLatin1String("includes"), Qt::CaseInsensitive)) { + if (!tag.compare(u"includes"_s, Qt::CaseInsensitive)) { auto *v = new DomIncludes(); v->read(reader); setElementIncludes(v); continue; } - if (!tag.compare(QLatin1String("resources"), Qt::CaseInsensitive)) { + if (!tag.compare(u"resources"_s, Qt::CaseInsensitive)) { auto *v = new DomResources(); v->read(reader); setElementResources(v); continue; } - if (!tag.compare(QLatin1String("connections"), Qt::CaseInsensitive)) { + if (!tag.compare(u"connections"_s, Qt::CaseInsensitive)) { auto *v = new DomConnections(); v->read(reader); setElementConnections(v); continue; } - if (!tag.compare(QLatin1String("designerdata"), Qt::CaseInsensitive)) { + if (!tag.compare(u"designerdata"_s, Qt::CaseInsensitive)) { auto *v = new DomDesignerData(); v->read(reader); setElementDesignerdata(v); continue; } - if (!tag.compare(QLatin1String("slots"), Qt::CaseInsensitive)) { + if (!tag.compare(u"slots"_s, Qt::CaseInsensitive)) { auto *v = new DomSlots(); v->read(reader); setElementSlots(v); continue; } - if (!tag.compare(QLatin1String("buttongroups"), Qt::CaseInsensitive)) { + if (!tag.compare(u"buttongroups"_s, Qt::CaseInsensitive)) { auto *v = new DomButtonGroups(); v->read(reader); setElementButtonGroups(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -202,73 +180,73 @@ void DomUI::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("ui") : tagName.toLower()); if (hasAttributeVersion()) - writer.writeAttribute(QStringLiteral("version"), attributeVersion()); + writer.writeAttribute(u"version"_s, attributeVersion()); if (hasAttributeLanguage()) - writer.writeAttribute(QStringLiteral("language"), attributeLanguage()); + writer.writeAttribute(u"language"_s, attributeLanguage()); if (hasAttributeDisplayname()) - writer.writeAttribute(QStringLiteral("displayname"), attributeDisplayname()); + writer.writeAttribute(u"displayname"_s, attributeDisplayname()); if (hasAttributeIdbasedtr()) - writer.writeAttribute(QStringLiteral("idbasedtr"), (attributeIdbasedtr() ? QLatin1String("true") : QLatin1String("false"))); + writer.writeAttribute(u"idbasedtr"_s, (attributeIdbasedtr() ? u"true"_s : u"false"_s)); if (hasAttributeConnectslotsbyname()) - writer.writeAttribute(QStringLiteral("connectslotsbyname"), (attributeConnectslotsbyname() ? QLatin1String("true") : QLatin1String("false"))); + writer.writeAttribute(u"connectslotsbyname"_s, (attributeConnectslotsbyname() ? u"true"_s : u"false"_s)); if (hasAttributeStdsetdef()) - writer.writeAttribute(QStringLiteral("stdsetdef"), QString::number(attributeStdsetdef())); + writer.writeAttribute(u"stdsetdef"_s, QString::number(attributeStdsetdef())); if (hasAttributeStdSetDef()) - writer.writeAttribute(QStringLiteral("stdsetdef"), QString::number(attributeStdSetDef())); + writer.writeAttribute(u"stdsetdef"_s, QString::number(attributeStdSetDef())); if (m_children & Author) - writer.writeTextElement(QStringLiteral("author"), m_author); + writer.writeTextElement(u"author"_s, m_author); if (m_children & Comment) - writer.writeTextElement(QStringLiteral("comment"), m_comment); + writer.writeTextElement(u"comment"_s, m_comment); if (m_children & ExportMacro) - writer.writeTextElement(QStringLiteral("exportmacro"), m_exportMacro); + writer.writeTextElement(u"exportmacro"_s, m_exportMacro); if (m_children & Class) - writer.writeTextElement(QStringLiteral("class"), m_class); + writer.writeTextElement(u"class"_s, m_class); if (m_children & Widget) - m_widget->write(writer, QStringLiteral("widget")); + m_widget->write(writer, u"widget"_s); if (m_children & LayoutDefault) - m_layoutDefault->write(writer, QStringLiteral("layoutdefault")); + m_layoutDefault->write(writer, u"layoutdefault"_s); if (m_children & LayoutFunction) - m_layoutFunction->write(writer, QStringLiteral("layoutfunction")); + m_layoutFunction->write(writer, u"layoutfunction"_s); if (m_children & PixmapFunction) - writer.writeTextElement(QStringLiteral("pixmapfunction"), m_pixmapFunction); + writer.writeTextElement(u"pixmapfunction"_s, m_pixmapFunction); if (m_children & CustomWidgets) - m_customWidgets->write(writer, QStringLiteral("customwidgets")); + m_customWidgets->write(writer, u"customwidgets"_s); if (m_children & TabStops) - m_tabStops->write(writer, QStringLiteral("tabstops")); + m_tabStops->write(writer, u"tabstops"_s); if (m_children & Includes) - m_includes->write(writer, QStringLiteral("includes")); + m_includes->write(writer, u"includes"_s); if (m_children & Resources) - m_resources->write(writer, QStringLiteral("resources")); + m_resources->write(writer, u"resources"_s); if (m_children & Connections) - m_connections->write(writer, QStringLiteral("connections")); + m_connections->write(writer, u"connections"_s); if (m_children & Designerdata) - m_designerdata->write(writer, QStringLiteral("designerdata")); + m_designerdata->write(writer, u"designerdata"_s); if (m_children & Slots) - m_slots->write(writer, QStringLiteral("slots")); + m_slots->write(writer, u"slots"_s); if (m_children & ButtonGroups) - m_buttonGroups->write(writer, QStringLiteral("buttongroups")); + m_buttonGroups->write(writer, u"buttongroups"_s); writer.writeEndElement(); } @@ -582,13 +560,13 @@ void DomIncludes::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("include"), Qt::CaseInsensitive)) { + if (!tag.compare(u"include"_s, Qt::CaseInsensitive)) { auto *v = new DomInclude(); v->read(reader); m_include.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -604,7 +582,7 @@ void DomIncludes::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("includes") : tagName.toLower()); for (DomInclude *v : m_include) - v->write(writer, QStringLiteral("include")); + v->write(writer, u"include"_s); writer.writeEndElement(); } @@ -622,22 +600,22 @@ void DomInclude::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("location")) { + if (name == u"location"_s) { setAttributeLocation(attribute.value().toString()); continue; } - if (name == QLatin1String("impldecl")) { + if (name == u"impldecl"_s) { setAttributeImpldecl(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -657,10 +635,10 @@ void DomInclude::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("include") : tagName.toLower()); if (hasAttributeLocation()) - writer.writeAttribute(QStringLiteral("location"), attributeLocation()); + writer.writeAttribute(u"location"_s, attributeLocation()); if (hasAttributeImpldecl()) - writer.writeAttribute(QStringLiteral("impldecl"), attributeImpldecl()); + writer.writeAttribute(u"impldecl"_s, attributeImpldecl()); if (!m_text.isEmpty()) writer.writeCharacters(m_text); @@ -679,24 +657,24 @@ void DomResources::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("include"), Qt::CaseInsensitive)) { + if (!tag.compare(u"include"_s, Qt::CaseInsensitive)) { auto *v = new DomResource(); v->read(reader); m_include.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -712,10 +690,10 @@ void DomResources::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("resources") : tagName.toLower()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); for (DomResource *v : m_include) - v->write(writer, QStringLiteral("include")); + v->write(writer, u"include"_s); writer.writeEndElement(); } @@ -733,18 +711,18 @@ void DomResource::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("location")) { + if (name == u"location"_s) { setAttributeLocation(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -760,7 +738,7 @@ void DomResource::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("resource") : tagName.toLower()); if (hasAttributeLocation()) - writer.writeAttribute(QStringLiteral("location"), attributeLocation()); + writer.writeAttribute(u"location"_s, attributeLocation()); writer.writeEndElement(); } @@ -782,42 +760,42 @@ void DomActionGroup::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("action"), Qt::CaseInsensitive)) { + if (!tag.compare(u"action"_s, Qt::CaseInsensitive)) { auto *v = new DomAction(); v->read(reader); m_action.append(v); continue; } - if (!tag.compare(QLatin1String("actiongroup"), Qt::CaseInsensitive)) { + if (!tag.compare(u"actiongroup"_s, Qt::CaseInsensitive)) { auto *v = new DomActionGroup(); v->read(reader); m_actionGroup.append(v); continue; } - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - if (!tag.compare(QLatin1String("attribute"), Qt::CaseInsensitive)) { + if (!tag.compare(u"attribute"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_attribute.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -833,19 +811,19 @@ void DomActionGroup::write(QXmlStreamWriter &writer, const QString &tagName) con writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("actiongroup") : tagName.toLower()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); for (DomAction *v : m_action) - v->write(writer, QStringLiteral("action")); + v->write(writer, u"action"_s); for (DomActionGroup *v : m_actionGroup) - v->write(writer, QStringLiteral("actiongroup")); + v->write(writer, u"actiongroup"_s); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); for (DomProperty *v : m_attribute) - v->write(writer, QStringLiteral("attribute")); + v->write(writer, u"attribute"_s); writer.writeEndElement(); } @@ -887,34 +865,34 @@ void DomAction::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - if (name == QLatin1String("menu")) { + if (name == u"menu"_s) { setAttributeMenu(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - if (!tag.compare(QLatin1String("attribute"), Qt::CaseInsensitive)) { + if (!tag.compare(u"attribute"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_attribute.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -930,16 +908,16 @@ void DomAction::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("action") : tagName.toLower()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); if (hasAttributeMenu()) - writer.writeAttribute(QStringLiteral("menu"), attributeMenu()); + writer.writeAttribute(u"menu"_s, attributeMenu()); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); for (DomProperty *v : m_attribute) - v->write(writer, QStringLiteral("attribute")); + v->write(writer, u"attribute"_s); writer.writeEndElement(); } @@ -963,18 +941,18 @@ void DomActionRef::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -990,7 +968,7 @@ void DomActionRef::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("actionref") : tagName.toLower()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); writer.writeEndElement(); } @@ -1008,30 +986,30 @@ void DomButtonGroup::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - if (!tag.compare(QLatin1String("attribute"), Qt::CaseInsensitive)) { + if (!tag.compare(u"attribute"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_attribute.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1047,13 +1025,13 @@ void DomButtonGroup::write(QXmlStreamWriter &writer, const QString &tagName) con writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("buttongroup") : tagName.toLower()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); for (DomProperty *v : m_attribute) - v->write(writer, QStringLiteral("attribute")); + v->write(writer, u"attribute"_s); writer.writeEndElement(); } @@ -1082,13 +1060,13 @@ void DomButtonGroups::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("buttongroup"), Qt::CaseInsensitive)) { + if (!tag.compare(u"buttongroup"_s, Qt::CaseInsensitive)) { auto *v = new DomButtonGroup(); v->read(reader); m_buttonGroup.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1104,7 +1082,7 @@ void DomButtonGroups::write(QXmlStreamWriter &writer, const QString &tagName) co writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("buttongroups") : tagName.toLower()); for (DomButtonGroup *v : m_buttonGroup) - v->write(writer, QStringLiteral("buttongroup")); + v->write(writer, u"buttongroup"_s); writer.writeEndElement(); } @@ -1127,13 +1105,13 @@ void DomCustomWidgets::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("customwidget"), Qt::CaseInsensitive)) { + if (!tag.compare(u"customwidget"_s, Qt::CaseInsensitive)) { auto *v = new DomCustomWidget(); v->read(reader); m_customWidget.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1149,7 +1127,7 @@ void DomCustomWidgets::write(QXmlStreamWriter &writer, const QString &tagName) c writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("customwidgets") : tagName.toLower()); for (DomCustomWidget *v : m_customWidget) - v->write(writer, QStringLiteral("customwidget")); + v->write(writer, u"customwidget"_s); writer.writeEndElement(); } @@ -1167,18 +1145,18 @@ void DomHeader::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("location")) { + if (name == u"location"_s) { setAttributeLocation(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1198,7 +1176,7 @@ void DomHeader::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("header") : tagName.toLower()); if (hasAttributeLocation()) - writer.writeAttribute(QStringLiteral("location"), attributeLocation()); + writer.writeAttribute(u"location"_s, attributeLocation()); if (!m_text.isEmpty()) writer.writeCharacters(m_text); @@ -1220,66 +1198,66 @@ void DomCustomWidget::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("class"), Qt::CaseInsensitive)) { + if (!tag.compare(u"class"_s, Qt::CaseInsensitive)) { setElementClass(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("extends"), Qt::CaseInsensitive)) { + if (!tag.compare(u"extends"_s, Qt::CaseInsensitive)) { setElementExtends(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("header"), Qt::CaseInsensitive)) { + if (!tag.compare(u"header"_s, Qt::CaseInsensitive)) { auto *v = new DomHeader(); v->read(reader); setElementHeader(v); continue; } - if (!tag.compare(QLatin1String("sizehint"), Qt::CaseInsensitive)) { + if (!tag.compare(u"sizehint"_s, Qt::CaseInsensitive)) { auto *v = new DomSize(); v->read(reader); setElementSizeHint(v); continue; } - if (!tag.compare(QLatin1String("addpagemethod"), Qt::CaseInsensitive)) { + if (!tag.compare(u"addpagemethod"_s, Qt::CaseInsensitive)) { setElementAddPageMethod(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("container"), Qt::CaseInsensitive)) { + if (!tag.compare(u"container"_s, Qt::CaseInsensitive)) { setElementContainer(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("sizepolicy"), Qt::CaseInsensitive)) { + if (!tag.compare(u"sizepolicy"_s, Qt::CaseInsensitive)) { qWarning("Omitting deprecated element <sizepolicy>."); reader.skipCurrentElement(); continue; } - if (!tag.compare(QLatin1String("pixmap"), Qt::CaseInsensitive)) { + if (!tag.compare(u"pixmap"_s, Qt::CaseInsensitive)) { setElementPixmap(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("script"), Qt::CaseInsensitive)) { + if (!tag.compare(u"script"_s, Qt::CaseInsensitive)) { qWarning("Omitting deprecated element <script>."); reader.skipCurrentElement(); continue; } - if (!tag.compare(QLatin1String("properties"), Qt::CaseInsensitive)) { + if (!tag.compare(u"properties"_s, Qt::CaseInsensitive)) { qWarning("Omitting deprecated element <properties>."); reader.skipCurrentElement(); continue; } - if (!tag.compare(QLatin1String("slots"), Qt::CaseInsensitive)) { + if (!tag.compare(u"slots"_s, Qt::CaseInsensitive)) { auto *v = new DomSlots(); v->read(reader); setElementSlots(v); continue; } - if (!tag.compare(QLatin1String("propertyspecifications"), Qt::CaseInsensitive)) { + if (!tag.compare(u"propertyspecifications"_s, Qt::CaseInsensitive)) { auto *v = new DomPropertySpecifications(); v->read(reader); setElementPropertyspecifications(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1295,31 +1273,31 @@ void DomCustomWidget::write(QXmlStreamWriter &writer, const QString &tagName) co writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("customwidget") : tagName.toLower()); if (m_children & Class) - writer.writeTextElement(QStringLiteral("class"), m_class); + writer.writeTextElement(u"class"_s, m_class); if (m_children & Extends) - writer.writeTextElement(QStringLiteral("extends"), m_extends); + writer.writeTextElement(u"extends"_s, m_extends); if (m_children & Header) - m_header->write(writer, QStringLiteral("header")); + m_header->write(writer, u"header"_s); if (m_children & SizeHint) - m_sizeHint->write(writer, QStringLiteral("sizehint")); + m_sizeHint->write(writer, u"sizehint"_s); if (m_children & AddPageMethod) - writer.writeTextElement(QStringLiteral("addpagemethod"), m_addPageMethod); + writer.writeTextElement(u"addpagemethod"_s, m_addPageMethod); if (m_children & Container) - writer.writeTextElement(QStringLiteral("container"), QString::number(m_container)); + writer.writeTextElement(u"container"_s, QString::number(m_container)); if (m_children & Pixmap) - writer.writeTextElement(QStringLiteral("pixmap"), m_pixmap); + writer.writeTextElement(u"pixmap"_s, m_pixmap); if (m_children & Slots) - m_slots->write(writer, QStringLiteral("slots")); + m_slots->write(writer, u"slots"_s); if (m_children & Propertyspecifications) - m_propertyspecifications->write(writer, QStringLiteral("propertyspecifications")); + m_propertyspecifications->write(writer, u"propertyspecifications"_s); writer.writeEndElement(); } @@ -1474,22 +1452,22 @@ void DomLayoutDefault::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("spacing")) { + if (name == u"spacing"_s) { setAttributeSpacing(attribute.value().toInt()); continue; } - if (name == QLatin1String("margin")) { + if (name == u"margin"_s) { setAttributeMargin(attribute.value().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1505,10 +1483,10 @@ void DomLayoutDefault::write(QXmlStreamWriter &writer, const QString &tagName) c writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("layoutdefault") : tagName.toLower()); if (hasAttributeSpacing()) - writer.writeAttribute(QStringLiteral("spacing"), QString::number(attributeSpacing())); + writer.writeAttribute(u"spacing"_s, QString::number(attributeSpacing())); if (hasAttributeMargin()) - writer.writeAttribute(QStringLiteral("margin"), QString::number(attributeMargin())); + writer.writeAttribute(u"margin"_s, QString::number(attributeMargin())); writer.writeEndElement(); } @@ -1520,22 +1498,22 @@ void DomLayoutFunction::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("spacing")) { + if (name == u"spacing"_s) { setAttributeSpacing(attribute.value().toString()); continue; } - if (name == QLatin1String("margin")) { + if (name == u"margin"_s) { setAttributeMargin(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1551,10 +1529,10 @@ void DomLayoutFunction::write(QXmlStreamWriter &writer, const QString &tagName) writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("layoutfunction") : tagName.toLower()); if (hasAttributeSpacing()) - writer.writeAttribute(QStringLiteral("spacing"), attributeSpacing()); + writer.writeAttribute(u"spacing"_s, attributeSpacing()); if (hasAttributeMargin()) - writer.writeAttribute(QStringLiteral("margin"), attributeMargin()); + writer.writeAttribute(u"margin"_s, attributeMargin()); writer.writeEndElement(); } @@ -1570,11 +1548,11 @@ void DomTabStops::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("tabstop"), Qt::CaseInsensitive)) { + if (!tag.compare(u"tabstop"_s, Qt::CaseInsensitive)) { m_tabStop.append(reader.readElementText()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1590,7 +1568,7 @@ void DomTabStops::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("tabstops") : tagName.toLower()); for (const QString &v : m_tabStop) - writer.writeTextElement(QStringLiteral("tabstop"), v); + writer.writeTextElement(u"tabstop"_s, v); writer.writeEndElement(); } @@ -1616,60 +1594,60 @@ void DomLayout::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("class")) { + if (name == u"class"_s) { setAttributeClass(attribute.value().toString()); continue; } - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - if (name == QLatin1String("stretch")) { + if (name == u"stretch"_s) { setAttributeStretch(attribute.value().toString()); continue; } - if (name == QLatin1String("rowstretch")) { + if (name == u"rowstretch"_s) { setAttributeRowStretch(attribute.value().toString()); continue; } - if (name == QLatin1String("columnstretch")) { + if (name == u"columnstretch"_s) { setAttributeColumnStretch(attribute.value().toString()); continue; } - if (name == QLatin1String("rowminimumheight")) { + if (name == u"rowminimumheight"_s) { setAttributeRowMinimumHeight(attribute.value().toString()); continue; } - if (name == QLatin1String("columnminimumwidth")) { + if (name == u"columnminimumwidth"_s) { setAttributeColumnMinimumWidth(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - if (!tag.compare(QLatin1String("attribute"), Qt::CaseInsensitive)) { + if (!tag.compare(u"attribute"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_attribute.append(v); continue; } - if (!tag.compare(QLatin1String("item"), Qt::CaseInsensitive)) { + if (!tag.compare(u"item"_s, Qt::CaseInsensitive)) { auto *v = new DomLayoutItem(); v->read(reader); m_item.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1685,34 +1663,34 @@ void DomLayout::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("layout") : tagName.toLower()); if (hasAttributeClass()) - writer.writeAttribute(QStringLiteral("class"), attributeClass()); + writer.writeAttribute(u"class"_s, attributeClass()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); if (hasAttributeStretch()) - writer.writeAttribute(QStringLiteral("stretch"), attributeStretch()); + writer.writeAttribute(u"stretch"_s, attributeStretch()); if (hasAttributeRowStretch()) - writer.writeAttribute(QStringLiteral("rowstretch"), attributeRowStretch()); + writer.writeAttribute(u"rowstretch"_s, attributeRowStretch()); if (hasAttributeColumnStretch()) - writer.writeAttribute(QStringLiteral("columnstretch"), attributeColumnStretch()); + writer.writeAttribute(u"columnstretch"_s, attributeColumnStretch()); if (hasAttributeRowMinimumHeight()) - writer.writeAttribute(QStringLiteral("rowminimumheight"), attributeRowMinimumHeight()); + writer.writeAttribute(u"rowminimumheight"_s, attributeRowMinimumHeight()); if (hasAttributeColumnMinimumWidth()) - writer.writeAttribute(QStringLiteral("columnminimumwidth"), attributeColumnMinimumWidth()); + writer.writeAttribute(u"columnminimumwidth"_s, attributeColumnMinimumWidth()); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); for (DomProperty *v : m_attribute) - v->write(writer, QStringLiteral("attribute")); + v->write(writer, u"attribute"_s); for (DomLayoutItem *v : m_item) - v->write(writer, QStringLiteral("item")); + v->write(writer, u"item"_s); writer.writeEndElement(); } @@ -1760,52 +1738,52 @@ void DomLayoutItem::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("row")) { + if (name == u"row"_s) { setAttributeRow(attribute.value().toInt()); continue; } - if (name == QLatin1String("column")) { + if (name == u"column"_s) { setAttributeColumn(attribute.value().toInt()); continue; } - if (name == QLatin1String("rowspan")) { + if (name == u"rowspan"_s) { setAttributeRowSpan(attribute.value().toInt()); continue; } - if (name == QLatin1String("colspan")) { + if (name == u"colspan"_s) { setAttributeColSpan(attribute.value().toInt()); continue; } - if (name == QLatin1String("alignment")) { + if (name == u"alignment"_s) { setAttributeAlignment(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("widget"), Qt::CaseInsensitive)) { + if (!tag.compare(u"widget"_s, Qt::CaseInsensitive)) { auto *v = new DomWidget(); v->read(reader); setElementWidget(v); continue; } - if (!tag.compare(QLatin1String("layout"), Qt::CaseInsensitive)) { + if (!tag.compare(u"layout"_s, Qt::CaseInsensitive)) { auto *v = new DomLayout(); v->read(reader); setElementLayout(v); continue; } - if (!tag.compare(QLatin1String("spacer"), Qt::CaseInsensitive)) { + if (!tag.compare(u"spacer"_s, Qt::CaseInsensitive)) { auto *v = new DomSpacer(); v->read(reader); setElementSpacer(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1821,34 +1799,34 @@ void DomLayoutItem::write(QXmlStreamWriter &writer, const QString &tagName) cons writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("layoutitem") : tagName.toLower()); if (hasAttributeRow()) - writer.writeAttribute(QStringLiteral("row"), QString::number(attributeRow())); + writer.writeAttribute(u"row"_s, QString::number(attributeRow())); if (hasAttributeColumn()) - writer.writeAttribute(QStringLiteral("column"), QString::number(attributeColumn())); + writer.writeAttribute(u"column"_s, QString::number(attributeColumn())); if (hasAttributeRowSpan()) - writer.writeAttribute(QStringLiteral("rowspan"), QString::number(attributeRowSpan())); + writer.writeAttribute(u"rowspan"_s, QString::number(attributeRowSpan())); if (hasAttributeColSpan()) - writer.writeAttribute(QStringLiteral("colspan"), QString::number(attributeColSpan())); + writer.writeAttribute(u"colspan"_s, QString::number(attributeColSpan())); if (hasAttributeAlignment()) - writer.writeAttribute(QStringLiteral("alignment"), attributeAlignment()); + writer.writeAttribute(u"alignment"_s, attributeAlignment()); switch (kind()) { case Widget: if (m_widget != nullptr) - m_widget->write(writer, QStringLiteral("widget")); + m_widget->write(writer, u"widget"_s); break; case Layout: if (m_layout != nullptr) - m_layout->write(writer, QStringLiteral("layout")); + m_layout->write(writer, u"layout"_s); break; case Spacer: if (m_spacer != nullptr) - m_spacer->write(writer, QStringLiteral("spacer")); + m_spacer->write(writer, u"spacer"_s); break; default: @@ -1911,13 +1889,13 @@ void DomRow::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1933,7 +1911,7 @@ void DomRow::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("row") : tagName.toLower()); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); writer.writeEndElement(); } @@ -1956,13 +1934,13 @@ void DomColumn::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -1978,7 +1956,7 @@ void DomColumn::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("column") : tagName.toLower()); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); writer.writeEndElement(); } @@ -2002,34 +1980,34 @@ void DomItem::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("row")) { + if (name == u"row"_s) { setAttributeRow(attribute.value().toInt()); continue; } - if (name == QLatin1String("column")) { + if (name == u"column"_s) { setAttributeColumn(attribute.value().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - if (!tag.compare(QLatin1String("item"), Qt::CaseInsensitive)) { + if (!tag.compare(u"item"_s, Qt::CaseInsensitive)) { auto *v = new DomItem(); v->read(reader); m_item.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -2045,16 +2023,16 @@ void DomItem::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("item") : tagName.toLower()); if (hasAttributeRow()) - writer.writeAttribute(QStringLiteral("row"), QString::number(attributeRow())); + writer.writeAttribute(u"row"_s, QString::number(attributeRow())); if (hasAttributeColumn()) - writer.writeAttribute(QStringLiteral("column"), QString::number(attributeColumn())); + writer.writeAttribute(u"column"_s, QString::number(attributeColumn())); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); for (DomItem *v : m_item) - v->write(writer, QStringLiteral("item")); + v->write(writer, u"item"_s); writer.writeEndElement(); } @@ -2102,104 +2080,104 @@ void DomWidget::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("class")) { + if (name == u"class"_s) { setAttributeClass(attribute.value().toString()); continue; } - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - if (name == QLatin1String("native")) { - setAttributeNative(attribute.value() == QLatin1String("true")); + if (name == u"native"_s) { + setAttributeNative(attribute.value() == u"true"_s); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("class"), Qt::CaseInsensitive)) { + if (!tag.compare(u"class"_s, Qt::CaseInsensitive)) { m_class.append(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - if (!tag.compare(QLatin1String("script"), Qt::CaseInsensitive)) { + if (!tag.compare(u"script"_s, Qt::CaseInsensitive)) { qWarning("Omitting deprecated element <script>."); reader.skipCurrentElement(); continue; } - if (!tag.compare(QLatin1String("widgetdata"), Qt::CaseInsensitive)) { + if (!tag.compare(u"widgetdata"_s, Qt::CaseInsensitive)) { qWarning("Omitting deprecated element <widgetdata>."); reader.skipCurrentElement(); continue; } - if (!tag.compare(QLatin1String("attribute"), Qt::CaseInsensitive)) { + if (!tag.compare(u"attribute"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_attribute.append(v); continue; } - if (!tag.compare(QLatin1String("row"), Qt::CaseInsensitive)) { + if (!tag.compare(u"row"_s, Qt::CaseInsensitive)) { auto *v = new DomRow(); v->read(reader); m_row.append(v); continue; } - if (!tag.compare(QLatin1String("column"), Qt::CaseInsensitive)) { + if (!tag.compare(u"column"_s, Qt::CaseInsensitive)) { auto *v = new DomColumn(); v->read(reader); m_column.append(v); continue; } - if (!tag.compare(QLatin1String("item"), Qt::CaseInsensitive)) { + if (!tag.compare(u"item"_s, Qt::CaseInsensitive)) { auto *v = new DomItem(); v->read(reader); m_item.append(v); continue; } - if (!tag.compare(QLatin1String("layout"), Qt::CaseInsensitive)) { + if (!tag.compare(u"layout"_s, Qt::CaseInsensitive)) { auto *v = new DomLayout(); v->read(reader); m_layout.append(v); continue; } - if (!tag.compare(QLatin1String("widget"), Qt::CaseInsensitive)) { + if (!tag.compare(u"widget"_s, Qt::CaseInsensitive)) { auto *v = new DomWidget(); v->read(reader); m_widget.append(v); continue; } - if (!tag.compare(QLatin1String("action"), Qt::CaseInsensitive)) { + if (!tag.compare(u"action"_s, Qt::CaseInsensitive)) { auto *v = new DomAction(); v->read(reader); m_action.append(v); continue; } - if (!tag.compare(QLatin1String("actiongroup"), Qt::CaseInsensitive)) { + if (!tag.compare(u"actiongroup"_s, Qt::CaseInsensitive)) { auto *v = new DomActionGroup(); v->read(reader); m_actionGroup.append(v); continue; } - if (!tag.compare(QLatin1String("addaction"), Qt::CaseInsensitive)) { + if (!tag.compare(u"addaction"_s, Qt::CaseInsensitive)) { auto *v = new DomActionRef(); v->read(reader); m_addAction.append(v); continue; } - if (!tag.compare(QLatin1String("zorder"), Qt::CaseInsensitive)) { + if (!tag.compare(u"zorder"_s, Qt::CaseInsensitive)) { m_zOrder.append(reader.readElementText()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -2215,49 +2193,49 @@ void DomWidget::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("widget") : tagName.toLower()); if (hasAttributeClass()) - writer.writeAttribute(QStringLiteral("class"), attributeClass()); + writer.writeAttribute(u"class"_s, attributeClass()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); if (hasAttributeNative()) - writer.writeAttribute(QStringLiteral("native"), (attributeNative() ? QLatin1String("true") : QLatin1String("false"))); + writer.writeAttribute(u"native"_s, (attributeNative() ? u"true"_s : u"false"_s)); for (const QString &v : m_class) - writer.writeTextElement(QStringLiteral("class"), v); + writer.writeTextElement(u"class"_s, v); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); for (DomProperty *v : m_attribute) - v->write(writer, QStringLiteral("attribute")); + v->write(writer, u"attribute"_s); for (DomRow *v : m_row) - v->write(writer, QStringLiteral("row")); + v->write(writer, u"row"_s); for (DomColumn *v : m_column) - v->write(writer, QStringLiteral("column")); + v->write(writer, u"column"_s); for (DomItem *v : m_item) - v->write(writer, QStringLiteral("item")); + v->write(writer, u"item"_s); for (DomLayout *v : m_layout) - v->write(writer, QStringLiteral("layout")); + v->write(writer, u"layout"_s); for (DomWidget *v : m_widget) - v->write(writer, QStringLiteral("widget")); + v->write(writer, u"widget"_s); for (DomAction *v : m_action) - v->write(writer, QStringLiteral("action")); + v->write(writer, u"action"_s); for (DomActionGroup *v : m_actionGroup) - v->write(writer, QStringLiteral("actiongroup")); + v->write(writer, u"actiongroup"_s); for (DomActionRef *v : m_addAction) - v->write(writer, QStringLiteral("addaction")); + v->write(writer, u"addaction"_s); for (const QString &v : m_zOrder) - writer.writeTextElement(QStringLiteral("zorder"), v); + writer.writeTextElement(u"zorder"_s, v); writer.writeEndElement(); } @@ -2345,24 +2323,24 @@ void DomSpacer::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -2378,10 +2356,10 @@ void DomSpacer::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("spacer") : tagName.toLower()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); writer.writeEndElement(); } @@ -2399,30 +2377,30 @@ void DomColor::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("alpha")) { + if (name == u"alpha"_s) { setAttributeAlpha(attribute.value().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("red"), Qt::CaseInsensitive)) { + if (!tag.compare(u"red"_s, Qt::CaseInsensitive)) { setElementRed(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("green"), Qt::CaseInsensitive)) { + if (!tag.compare(u"green"_s, Qt::CaseInsensitive)) { setElementGreen(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("blue"), Qt::CaseInsensitive)) { + if (!tag.compare(u"blue"_s, Qt::CaseInsensitive)) { setElementBlue(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -2438,16 +2416,16 @@ void DomColor::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("color") : tagName.toLower()); if (hasAttributeAlpha()) - writer.writeAttribute(QStringLiteral("alpha"), QString::number(attributeAlpha())); + writer.writeAttribute(u"alpha"_s, QString::number(attributeAlpha())); if (m_children & Red) - writer.writeTextElement(QStringLiteral("red"), QString::number(m_red)); + writer.writeTextElement(u"red"_s, QString::number(m_red)); if (m_children & Green) - writer.writeTextElement(QStringLiteral("green"), QString::number(m_green)); + writer.writeTextElement(u"green"_s, QString::number(m_green)); if (m_children & Blue) - writer.writeTextElement(QStringLiteral("blue"), QString::number(m_blue)); + writer.writeTextElement(u"blue"_s, QString::number(m_blue)); writer.writeEndElement(); } @@ -2495,24 +2473,24 @@ void DomGradientStop::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("position")) { + if (name == u"position"_s) { setAttributePosition(attribute.value().toDouble()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("color"), Qt::CaseInsensitive)) { + if (!tag.compare(u"color"_s, Qt::CaseInsensitive)) { auto *v = new DomColor(); v->read(reader); setElementColor(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -2528,10 +2506,10 @@ void DomGradientStop::write(QXmlStreamWriter &writer, const QString &tagName) co writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("gradientstop") : tagName.toLower()); if (hasAttributePosition()) - writer.writeAttribute(QStringLiteral("position"), QString::number(attributePosition(), 'f', 15)); + writer.writeAttribute(u"position"_s, QString::number(attributePosition(), 'f', 15)); if (m_children & Color) - m_color->write(writer, QStringLiteral("color")); + m_color->write(writer, u"color"_s); writer.writeEndElement(); } @@ -2569,72 +2547,72 @@ void DomGradient::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("startx")) { + if (name == u"startx"_s) { setAttributeStartX(attribute.value().toDouble()); continue; } - if (name == QLatin1String("starty")) { + if (name == u"starty"_s) { setAttributeStartY(attribute.value().toDouble()); continue; } - if (name == QLatin1String("endx")) { + if (name == u"endx"_s) { setAttributeEndX(attribute.value().toDouble()); continue; } - if (name == QLatin1String("endy")) { + if (name == u"endy"_s) { setAttributeEndY(attribute.value().toDouble()); continue; } - if (name == QLatin1String("centralx")) { + if (name == u"centralx"_s) { setAttributeCentralX(attribute.value().toDouble()); continue; } - if (name == QLatin1String("centraly")) { + if (name == u"centraly"_s) { setAttributeCentralY(attribute.value().toDouble()); continue; } - if (name == QLatin1String("focalx")) { + if (name == u"focalx"_s) { setAttributeFocalX(attribute.value().toDouble()); continue; } - if (name == QLatin1String("focaly")) { + if (name == u"focaly"_s) { setAttributeFocalY(attribute.value().toDouble()); continue; } - if (name == QLatin1String("radius")) { + if (name == u"radius"_s) { setAttributeRadius(attribute.value().toDouble()); continue; } - if (name == QLatin1String("angle")) { + if (name == u"angle"_s) { setAttributeAngle(attribute.value().toDouble()); continue; } - if (name == QLatin1String("type")) { + if (name == u"type"_s) { setAttributeType(attribute.value().toString()); continue; } - if (name == QLatin1String("spread")) { + if (name == u"spread"_s) { setAttributeSpread(attribute.value().toString()); continue; } - if (name == QLatin1String("coordinatemode")) { + if (name == u"coordinatemode"_s) { setAttributeCoordinateMode(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("gradientstop"), Qt::CaseInsensitive)) { + if (!tag.compare(u"gradientstop"_s, Qt::CaseInsensitive)) { auto *v = new DomGradientStop(); v->read(reader); m_gradientStop.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -2650,46 +2628,46 @@ void DomGradient::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("gradient") : tagName.toLower()); if (hasAttributeStartX()) - writer.writeAttribute(QStringLiteral("startx"), QString::number(attributeStartX(), 'f', 15)); + writer.writeAttribute(u"startx"_s, QString::number(attributeStartX(), 'f', 15)); if (hasAttributeStartY()) - writer.writeAttribute(QStringLiteral("starty"), QString::number(attributeStartY(), 'f', 15)); + writer.writeAttribute(u"starty"_s, QString::number(attributeStartY(), 'f', 15)); if (hasAttributeEndX()) - writer.writeAttribute(QStringLiteral("endx"), QString::number(attributeEndX(), 'f', 15)); + writer.writeAttribute(u"endx"_s, QString::number(attributeEndX(), 'f', 15)); if (hasAttributeEndY()) - writer.writeAttribute(QStringLiteral("endy"), QString::number(attributeEndY(), 'f', 15)); + writer.writeAttribute(u"endy"_s, QString::number(attributeEndY(), 'f', 15)); if (hasAttributeCentralX()) - writer.writeAttribute(QStringLiteral("centralx"), QString::number(attributeCentralX(), 'f', 15)); + writer.writeAttribute(u"centralx"_s, QString::number(attributeCentralX(), 'f', 15)); if (hasAttributeCentralY()) - writer.writeAttribute(QStringLiteral("centraly"), QString::number(attributeCentralY(), 'f', 15)); + writer.writeAttribute(u"centraly"_s, QString::number(attributeCentralY(), 'f', 15)); if (hasAttributeFocalX()) - writer.writeAttribute(QStringLiteral("focalx"), QString::number(attributeFocalX(), 'f', 15)); + writer.writeAttribute(u"focalx"_s, QString::number(attributeFocalX(), 'f', 15)); if (hasAttributeFocalY()) - writer.writeAttribute(QStringLiteral("focaly"), QString::number(attributeFocalY(), 'f', 15)); + writer.writeAttribute(u"focaly"_s, QString::number(attributeFocalY(), 'f', 15)); if (hasAttributeRadius()) - writer.writeAttribute(QStringLiteral("radius"), QString::number(attributeRadius(), 'f', 15)); + writer.writeAttribute(u"radius"_s, QString::number(attributeRadius(), 'f', 15)); if (hasAttributeAngle()) - writer.writeAttribute(QStringLiteral("angle"), QString::number(attributeAngle(), 'f', 15)); + writer.writeAttribute(u"angle"_s, QString::number(attributeAngle(), 'f', 15)); if (hasAttributeType()) - writer.writeAttribute(QStringLiteral("type"), attributeType()); + writer.writeAttribute(u"type"_s, attributeType()); if (hasAttributeSpread()) - writer.writeAttribute(QStringLiteral("spread"), attributeSpread()); + writer.writeAttribute(u"spread"_s, attributeSpread()); if (hasAttributeCoordinateMode()) - writer.writeAttribute(QStringLiteral("coordinatemode"), attributeCoordinateMode()); + writer.writeAttribute(u"coordinatemode"_s, attributeCoordinateMode()); for (DomGradientStop *v : m_gradientStop) - v->write(writer, QStringLiteral("gradientstop")); + v->write(writer, u"gradientstop"_s); writer.writeEndElement(); } @@ -2725,36 +2703,36 @@ void DomBrush::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("brushstyle")) { + if (name == u"brushstyle"_s) { setAttributeBrushStyle(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("color"), Qt::CaseInsensitive)) { + if (!tag.compare(u"color"_s, Qt::CaseInsensitive)) { auto *v = new DomColor(); v->read(reader); setElementColor(v); continue; } - if (!tag.compare(QLatin1String("texture"), Qt::CaseInsensitive)) { + if (!tag.compare(u"texture"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); setElementTexture(v); continue; } - if (!tag.compare(QLatin1String("gradient"), Qt::CaseInsensitive)) { + if (!tag.compare(u"gradient"_s, Qt::CaseInsensitive)) { auto *v = new DomGradient(); v->read(reader); setElementGradient(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -2770,22 +2748,22 @@ void DomBrush::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("brush") : tagName.toLower()); if (hasAttributeBrushStyle()) - writer.writeAttribute(QStringLiteral("brushstyle"), attributeBrushStyle()); + writer.writeAttribute(u"brushstyle"_s, attributeBrushStyle()); switch (kind()) { case Color: if (m_color != nullptr) - m_color->write(writer, QStringLiteral("color")); + m_color->write(writer, u"color"_s); break; case Texture: if (m_texture != nullptr) - m_texture->write(writer, QStringLiteral("texture")); + m_texture->write(writer, u"texture"_s); break; case Gradient: if (m_gradient != nullptr) - m_gradient->write(writer, QStringLiteral("gradient")); + m_gradient->write(writer, u"gradient"_s); break; default: @@ -2846,24 +2824,24 @@ void DomColorRole::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("role")) { + if (name == u"role"_s) { setAttributeRole(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("brush"), Qt::CaseInsensitive)) { + if (!tag.compare(u"brush"_s, Qt::CaseInsensitive)) { auto *v = new DomBrush(); v->read(reader); setElementBrush(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -2879,10 +2857,10 @@ void DomColorRole::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("colorrole") : tagName.toLower()); if (hasAttributeRole()) - writer.writeAttribute(QStringLiteral("role"), attributeRole()); + writer.writeAttribute(u"role"_s, attributeRole()); if (m_children & Brush) - m_brush->write(writer, QStringLiteral("brush")); + m_brush->write(writer, u"brush"_s); writer.writeEndElement(); } @@ -2923,19 +2901,19 @@ void DomColorGroup::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("colorrole"), Qt::CaseInsensitive)) { + if (!tag.compare(u"colorrole"_s, Qt::CaseInsensitive)) { auto *v = new DomColorRole(); v->read(reader); m_colorRole.append(v); continue; } - if (!tag.compare(QLatin1String("color"), Qt::CaseInsensitive)) { + if (!tag.compare(u"color"_s, Qt::CaseInsensitive)) { auto *v = new DomColor(); v->read(reader); m_color.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -2951,10 +2929,10 @@ void DomColorGroup::write(QXmlStreamWriter &writer, const QString &tagName) cons writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("colorgroup") : tagName.toLower()); for (DomColorRole *v : m_colorRole) - v->write(writer, QStringLiteral("colorrole")); + v->write(writer, u"colorrole"_s); for (DomColor *v : m_color) - v->write(writer, QStringLiteral("color")); + v->write(writer, u"color"_s); writer.writeEndElement(); } @@ -2984,25 +2962,25 @@ void DomPalette::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("active"), Qt::CaseInsensitive)) { + if (!tag.compare(u"active"_s, Qt::CaseInsensitive)) { auto *v = new DomColorGroup(); v->read(reader); setElementActive(v); continue; } - if (!tag.compare(QLatin1String("inactive"), Qt::CaseInsensitive)) { + if (!tag.compare(u"inactive"_s, Qt::CaseInsensitive)) { auto *v = new DomColorGroup(); v->read(reader); setElementInactive(v); continue; } - if (!tag.compare(QLatin1String("disabled"), Qt::CaseInsensitive)) { + if (!tag.compare(u"disabled"_s, Qt::CaseInsensitive)) { auto *v = new DomColorGroup(); v->read(reader); setElementDisabled(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3018,13 +2996,13 @@ void DomPalette::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("palette") : tagName.toLower()); if (m_children & Active) - m_active->write(writer, QStringLiteral("active")); + m_active->write(writer, u"active"_s); if (m_children & Inactive) - m_inactive->write(writer, QStringLiteral("inactive")); + m_inactive->write(writer, u"inactive"_s); if (m_children & Disabled) - m_disabled->write(writer, QStringLiteral("disabled")); + m_disabled->write(writer, u"disabled"_s); writer.writeEndElement(); } @@ -3103,47 +3081,55 @@ void DomFont::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("family"), Qt::CaseInsensitive)) { + if (!tag.compare(u"family"_s, Qt::CaseInsensitive)) { setElementFamily(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("pointsize"), Qt::CaseInsensitive)) { + if (!tag.compare(u"pointsize"_s, Qt::CaseInsensitive)) { setElementPointSize(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("weight"), Qt::CaseInsensitive)) { + if (!tag.compare(u"weight"_s, Qt::CaseInsensitive)) { setElementWeight(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("italic"), Qt::CaseInsensitive)) { - setElementItalic(reader.readElementText() == QLatin1String("true")); + if (!tag.compare(u"italic"_s, Qt::CaseInsensitive)) { + setElementItalic(reader.readElementText() == u"true"_s); continue; } - if (!tag.compare(QLatin1String("bold"), Qt::CaseInsensitive)) { - setElementBold(reader.readElementText() == QLatin1String("true")); + if (!tag.compare(u"bold"_s, Qt::CaseInsensitive)) { + setElementBold(reader.readElementText() == u"true"_s); continue; } - if (!tag.compare(QLatin1String("underline"), Qt::CaseInsensitive)) { - setElementUnderline(reader.readElementText() == QLatin1String("true")); + if (!tag.compare(u"underline"_s, Qt::CaseInsensitive)) { + setElementUnderline(reader.readElementText() == u"true"_s); continue; } - if (!tag.compare(QLatin1String("strikeout"), Qt::CaseInsensitive)) { - setElementStrikeOut(reader.readElementText() == QLatin1String("true")); + if (!tag.compare(u"strikeout"_s, Qt::CaseInsensitive)) { + setElementStrikeOut(reader.readElementText() == u"true"_s); continue; } - if (!tag.compare(QLatin1String("antialiasing"), Qt::CaseInsensitive)) { - setElementAntialiasing(reader.readElementText() == QLatin1String("true")); + if (!tag.compare(u"antialiasing"_s, Qt::CaseInsensitive)) { + setElementAntialiasing(reader.readElementText() == u"true"_s); continue; } - if (!tag.compare(QLatin1String("stylestrategy"), Qt::CaseInsensitive)) { + if (!tag.compare(u"stylestrategy"_s, Qt::CaseInsensitive)) { setElementStyleStrategy(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("kerning"), Qt::CaseInsensitive)) { - setElementKerning(reader.readElementText() == QLatin1String("true")); + if (!tag.compare(u"kerning"_s, Qt::CaseInsensitive)) { + setElementKerning(reader.readElementText() == u"true"_s); + continue; + } + if (!tag.compare(u"hintingpreference"_s, Qt::CaseInsensitive)) { + setElementHintingPreference(reader.readElementText()); + continue; + } + if (!tag.compare(u"fontweight"_s, Qt::CaseInsensitive)) { + setElementFontWeight(reader.readElementText()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3159,34 +3145,40 @@ void DomFont::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("font") : tagName.toLower()); if (m_children & Family) - writer.writeTextElement(QStringLiteral("family"), m_family); + writer.writeTextElement(u"family"_s, m_family); if (m_children & PointSize) - writer.writeTextElement(QStringLiteral("pointsize"), QString::number(m_pointSize)); + writer.writeTextElement(u"pointsize"_s, QString::number(m_pointSize)); if (m_children & Weight) - writer.writeTextElement(QStringLiteral("weight"), QString::number(m_weight)); + writer.writeTextElement(u"weight"_s, QString::number(m_weight)); if (m_children & Italic) - writer.writeTextElement(QStringLiteral("italic"), (m_italic ? QLatin1String("true") : QLatin1String("false"))); + writer.writeTextElement(u"italic"_s, (m_italic ? u"true"_s : u"false"_s)); if (m_children & Bold) - writer.writeTextElement(QStringLiteral("bold"), (m_bold ? QLatin1String("true") : QLatin1String("false"))); + writer.writeTextElement(u"bold"_s, (m_bold ? u"true"_s : u"false"_s)); if (m_children & Underline) - writer.writeTextElement(QStringLiteral("underline"), (m_underline ? QLatin1String("true") : QLatin1String("false"))); + writer.writeTextElement(u"underline"_s, (m_underline ? u"true"_s : u"false"_s)); if (m_children & StrikeOut) - writer.writeTextElement(QStringLiteral("strikeout"), (m_strikeOut ? QLatin1String("true") : QLatin1String("false"))); + writer.writeTextElement(u"strikeout"_s, (m_strikeOut ? u"true"_s : u"false"_s)); if (m_children & Antialiasing) - writer.writeTextElement(QStringLiteral("antialiasing"), (m_antialiasing ? QLatin1String("true") : QLatin1String("false"))); + writer.writeTextElement(u"antialiasing"_s, (m_antialiasing ? u"true"_s : u"false"_s)); if (m_children & StyleStrategy) - writer.writeTextElement(QStringLiteral("stylestrategy"), m_styleStrategy); + writer.writeTextElement(u"stylestrategy"_s, m_styleStrategy); if (m_children & Kerning) - writer.writeTextElement(QStringLiteral("kerning"), (m_kerning ? QLatin1String("true") : QLatin1String("false"))); + writer.writeTextElement(u"kerning"_s, (m_kerning ? u"true"_s : u"false"_s)); + + if (m_children & HintingPreference) + writer.writeTextElement(u"hintingpreference"_s, m_hintingPreference); + + if (m_children & FontWeight) + writer.writeTextElement(u"fontweight"_s, m_fontWeight); writer.writeEndElement(); } @@ -3251,6 +3243,18 @@ void DomFont::setElementKerning(bool a) m_kerning = a; } +void DomFont::setElementHintingPreference(const QString &a) +{ + m_children |= HintingPreference; + m_hintingPreference = a; +} + +void DomFont::setElementFontWeight(const QString &a) +{ + m_children |= FontWeight; + m_fontWeight = a; +} + void DomFont::clearElementFamily() { m_children &= ~Family; @@ -3301,6 +3305,16 @@ void DomFont::clearElementKerning() m_children &= ~Kerning; } +void DomFont::clearElementHintingPreference() +{ + m_children &= ~HintingPreference; +} + +void DomFont::clearElementFontWeight() +{ + m_children &= ~FontWeight; +} + DomPoint::~DomPoint() = default; void DomPoint::read(QXmlStreamReader &reader) @@ -3309,15 +3323,15 @@ void DomPoint::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("x"), Qt::CaseInsensitive)) { + if (!tag.compare(u"x"_s, Qt::CaseInsensitive)) { setElementX(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("y"), Qt::CaseInsensitive)) { + if (!tag.compare(u"y"_s, Qt::CaseInsensitive)) { setElementY(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3333,10 +3347,10 @@ void DomPoint::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("point") : tagName.toLower()); if (m_children & X) - writer.writeTextElement(QString(QLatin1Char('x')), QString::number(m_x)); + writer.writeTextElement(u"x"_s, QString::number(m_x)); if (m_children & Y) - writer.writeTextElement(QString(QLatin1Char('y')), QString::number(m_y)); + writer.writeTextElement(u"y"_s, QString::number(m_y)); writer.writeEndElement(); } @@ -3371,23 +3385,23 @@ void DomRect::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("x"), Qt::CaseInsensitive)) { + if (!tag.compare(u"x"_s, Qt::CaseInsensitive)) { setElementX(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("y"), Qt::CaseInsensitive)) { + if (!tag.compare(u"y"_s, Qt::CaseInsensitive)) { setElementY(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("width"), Qt::CaseInsensitive)) { + if (!tag.compare(u"width"_s, Qt::CaseInsensitive)) { setElementWidth(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("height"), Qt::CaseInsensitive)) { + if (!tag.compare(u"height"_s, Qt::CaseInsensitive)) { setElementHeight(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3403,16 +3417,16 @@ void DomRect::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("rect") : tagName.toLower()); if (m_children & X) - writer.writeTextElement(QString(QLatin1Char('x')), QString::number(m_x)); + writer.writeTextElement(u"x"_s, QString::number(m_x)); if (m_children & Y) - writer.writeTextElement(QString(QLatin1Char('y')), QString::number(m_y)); + writer.writeTextElement(u"y"_s, QString::number(m_y)); if (m_children & Width) - writer.writeTextElement(QStringLiteral("width"), QString::number(m_width)); + writer.writeTextElement(u"width"_s, QString::number(m_width)); if (m_children & Height) - writer.writeTextElement(QStringLiteral("height"), QString::number(m_height)); + writer.writeTextElement(u"height"_s, QString::number(m_height)); writer.writeEndElement(); } @@ -3468,22 +3482,22 @@ void DomLocale::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("language")) { + if (name == u"language"_s) { setAttributeLanguage(attribute.value().toString()); continue; } - if (name == QLatin1String("country")) { + if (name == u"country"_s) { setAttributeCountry(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3499,10 +3513,10 @@ void DomLocale::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("locale") : tagName.toLower()); if (hasAttributeLanguage()) - writer.writeAttribute(QStringLiteral("language"), attributeLanguage()); + writer.writeAttribute(u"language"_s, attributeLanguage()); if (hasAttributeCountry()) - writer.writeAttribute(QStringLiteral("country"), attributeCountry()); + writer.writeAttribute(u"country"_s, attributeCountry()); writer.writeEndElement(); } @@ -3514,38 +3528,38 @@ void DomSizePolicy::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("hsizetype")) { + if (name == u"hsizetype"_s) { setAttributeHSizeType(attribute.value().toString()); continue; } - if (name == QLatin1String("vsizetype")) { + if (name == u"vsizetype"_s) { setAttributeVSizeType(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("hsizetype"), Qt::CaseInsensitive)) { + if (!tag.compare(u"hsizetype"_s, Qt::CaseInsensitive)) { setElementHSizeType(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("vsizetype"), Qt::CaseInsensitive)) { + if (!tag.compare(u"vsizetype"_s, Qt::CaseInsensitive)) { setElementVSizeType(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("horstretch"), Qt::CaseInsensitive)) { + if (!tag.compare(u"horstretch"_s, Qt::CaseInsensitive)) { setElementHorStretch(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("verstretch"), Qt::CaseInsensitive)) { + if (!tag.compare(u"verstretch"_s, Qt::CaseInsensitive)) { setElementVerStretch(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3561,22 +3575,22 @@ void DomSizePolicy::write(QXmlStreamWriter &writer, const QString &tagName) cons writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("sizepolicy") : tagName.toLower()); if (hasAttributeHSizeType()) - writer.writeAttribute(QStringLiteral("hsizetype"), attributeHSizeType()); + writer.writeAttribute(u"hsizetype"_s, attributeHSizeType()); if (hasAttributeVSizeType()) - writer.writeAttribute(QStringLiteral("vsizetype"), attributeVSizeType()); + writer.writeAttribute(u"vsizetype"_s, attributeVSizeType()); if (m_children & HSizeType) - writer.writeTextElement(QStringLiteral("hsizetype"), QString::number(m_hSizeType)); + writer.writeTextElement(u"hsizetype"_s, QString::number(m_hSizeType)); if (m_children & VSizeType) - writer.writeTextElement(QStringLiteral("vsizetype"), QString::number(m_vSizeType)); + writer.writeTextElement(u"vsizetype"_s, QString::number(m_vSizeType)); if (m_children & HorStretch) - writer.writeTextElement(QStringLiteral("horstretch"), QString::number(m_horStretch)); + writer.writeTextElement(u"horstretch"_s, QString::number(m_horStretch)); if (m_children & VerStretch) - writer.writeTextElement(QStringLiteral("verstretch"), QString::number(m_verStretch)); + writer.writeTextElement(u"verstretch"_s, QString::number(m_verStretch)); writer.writeEndElement(); } @@ -3633,15 +3647,15 @@ void DomSize::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("width"), Qt::CaseInsensitive)) { + if (!tag.compare(u"width"_s, Qt::CaseInsensitive)) { setElementWidth(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("height"), Qt::CaseInsensitive)) { + if (!tag.compare(u"height"_s, Qt::CaseInsensitive)) { setElementHeight(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3657,10 +3671,10 @@ void DomSize::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("size") : tagName.toLower()); if (m_children & Width) - writer.writeTextElement(QStringLiteral("width"), QString::number(m_width)); + writer.writeTextElement(u"width"_s, QString::number(m_width)); if (m_children & Height) - writer.writeTextElement(QStringLiteral("height"), QString::number(m_height)); + writer.writeTextElement(u"height"_s, QString::number(m_height)); writer.writeEndElement(); } @@ -3695,19 +3709,19 @@ void DomDate::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("year"), Qt::CaseInsensitive)) { + if (!tag.compare(u"year"_s, Qt::CaseInsensitive)) { setElementYear(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("month"), Qt::CaseInsensitive)) { + if (!tag.compare(u"month"_s, Qt::CaseInsensitive)) { setElementMonth(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("day"), Qt::CaseInsensitive)) { + if (!tag.compare(u"day"_s, Qt::CaseInsensitive)) { setElementDay(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3723,13 +3737,13 @@ void DomDate::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("date") : tagName.toLower()); if (m_children & Year) - writer.writeTextElement(QStringLiteral("year"), QString::number(m_year)); + writer.writeTextElement(u"year"_s, QString::number(m_year)); if (m_children & Month) - writer.writeTextElement(QStringLiteral("month"), QString::number(m_month)); + writer.writeTextElement(u"month"_s, QString::number(m_month)); if (m_children & Day) - writer.writeTextElement(QStringLiteral("day"), QString::number(m_day)); + writer.writeTextElement(u"day"_s, QString::number(m_day)); writer.writeEndElement(); } @@ -3775,19 +3789,19 @@ void DomTime::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("hour"), Qt::CaseInsensitive)) { + if (!tag.compare(u"hour"_s, Qt::CaseInsensitive)) { setElementHour(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("minute"), Qt::CaseInsensitive)) { + if (!tag.compare(u"minute"_s, Qt::CaseInsensitive)) { setElementMinute(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("second"), Qt::CaseInsensitive)) { + if (!tag.compare(u"second"_s, Qt::CaseInsensitive)) { setElementSecond(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3803,13 +3817,13 @@ void DomTime::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("time") : tagName.toLower()); if (m_children & Hour) - writer.writeTextElement(QStringLiteral("hour"), QString::number(m_hour)); + writer.writeTextElement(u"hour"_s, QString::number(m_hour)); if (m_children & Minute) - writer.writeTextElement(QStringLiteral("minute"), QString::number(m_minute)); + writer.writeTextElement(u"minute"_s, QString::number(m_minute)); if (m_children & Second) - writer.writeTextElement(QStringLiteral("second"), QString::number(m_second)); + writer.writeTextElement(u"second"_s, QString::number(m_second)); writer.writeEndElement(); } @@ -3855,31 +3869,31 @@ void DomDateTime::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("hour"), Qt::CaseInsensitive)) { + if (!tag.compare(u"hour"_s, Qt::CaseInsensitive)) { setElementHour(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("minute"), Qt::CaseInsensitive)) { + if (!tag.compare(u"minute"_s, Qt::CaseInsensitive)) { setElementMinute(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("second"), Qt::CaseInsensitive)) { + if (!tag.compare(u"second"_s, Qt::CaseInsensitive)) { setElementSecond(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("year"), Qt::CaseInsensitive)) { + if (!tag.compare(u"year"_s, Qt::CaseInsensitive)) { setElementYear(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("month"), Qt::CaseInsensitive)) { + if (!tag.compare(u"month"_s, Qt::CaseInsensitive)) { setElementMonth(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("day"), Qt::CaseInsensitive)) { + if (!tag.compare(u"day"_s, Qt::CaseInsensitive)) { setElementDay(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -3895,22 +3909,22 @@ void DomDateTime::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("datetime") : tagName.toLower()); if (m_children & Hour) - writer.writeTextElement(QStringLiteral("hour"), QString::number(m_hour)); + writer.writeTextElement(u"hour"_s, QString::number(m_hour)); if (m_children & Minute) - writer.writeTextElement(QStringLiteral("minute"), QString::number(m_minute)); + writer.writeTextElement(u"minute"_s, QString::number(m_minute)); if (m_children & Second) - writer.writeTextElement(QStringLiteral("second"), QString::number(m_second)); + writer.writeTextElement(u"second"_s, QString::number(m_second)); if (m_children & Year) - writer.writeTextElement(QStringLiteral("year"), QString::number(m_year)); + writer.writeTextElement(u"year"_s, QString::number(m_year)); if (m_children & Month) - writer.writeTextElement(QStringLiteral("month"), QString::number(m_month)); + writer.writeTextElement(u"month"_s, QString::number(m_month)); if (m_children & Day) - writer.writeTextElement(QStringLiteral("day"), QString::number(m_day)); + writer.writeTextElement(u"day"_s, QString::number(m_day)); writer.writeEndElement(); } @@ -3991,34 +4005,34 @@ void DomStringList::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("notr")) { + if (name == u"notr"_s) { setAttributeNotr(attribute.value().toString()); continue; } - if (name == QLatin1String("comment")) { + if (name == u"comment"_s) { setAttributeComment(attribute.value().toString()); continue; } - if (name == QLatin1String("extracomment")) { + if (name == u"extracomment"_s) { setAttributeExtraComment(attribute.value().toString()); continue; } - if (name == QLatin1String("id")) { + if (name == u"id"_s) { setAttributeId(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("string"), Qt::CaseInsensitive)) { + if (!tag.compare(u"string"_s, Qt::CaseInsensitive)) { m_string.append(reader.readElementText()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -4034,19 +4048,19 @@ void DomStringList::write(QXmlStreamWriter &writer, const QString &tagName) cons writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("stringlist") : tagName.toLower()); if (hasAttributeNotr()) - writer.writeAttribute(QStringLiteral("notr"), attributeNotr()); + writer.writeAttribute(u"notr"_s, attributeNotr()); if (hasAttributeComment()) - writer.writeAttribute(QStringLiteral("comment"), attributeComment()); + writer.writeAttribute(u"comment"_s, attributeComment()); if (hasAttributeExtraComment()) - writer.writeAttribute(QStringLiteral("extracomment"), attributeExtraComment()); + writer.writeAttribute(u"extracomment"_s, attributeExtraComment()); if (hasAttributeId()) - writer.writeAttribute(QStringLiteral("id"), attributeId()); + writer.writeAttribute(u"id"_s, attributeId()); for (const QString &v : m_string) - writer.writeTextElement(QStringLiteral("string"), v); + writer.writeTextElement(u"string"_s, v); writer.writeEndElement(); } @@ -4064,22 +4078,22 @@ void DomResourcePixmap::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("resource")) { + if (name == u"resource"_s) { setAttributeResource(attribute.value().toString()); continue; } - if (name == QLatin1String("alias")) { + if (name == u"alias"_s) { setAttributeAlias(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -4099,10 +4113,10 @@ void DomResourcePixmap::write(QXmlStreamWriter &writer, const QString &tagName) writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("resourcepixmap") : tagName.toLower()); if (hasAttributeResource()) - writer.writeAttribute(QStringLiteral("resource"), attributeResource()); + writer.writeAttribute(u"resource"_s, attributeResource()); if (hasAttributeAlias()) - writer.writeAttribute(QStringLiteral("alias"), attributeAlias()); + writer.writeAttribute(u"alias"_s, attributeAlias()); if (!m_text.isEmpty()) writer.writeCharacters(m_text); @@ -4127,70 +4141,70 @@ void DomResourceIcon::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("theme")) { + if (name == u"theme"_s) { setAttributeTheme(attribute.value().toString()); continue; } - if (name == QLatin1String("resource")) { + if (name == u"resource"_s) { setAttributeResource(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("normaloff"), Qt::CaseInsensitive)) { + if (!tag.compare(u"normaloff"_s, Qt::CaseInsensitive)) { auto *v = new DomResourcePixmap(); v->read(reader); setElementNormalOff(v); continue; } - if (!tag.compare(QLatin1String("normalon"), Qt::CaseInsensitive)) { + if (!tag.compare(u"normalon"_s, Qt::CaseInsensitive)) { auto *v = new DomResourcePixmap(); v->read(reader); setElementNormalOn(v); continue; } - if (!tag.compare(QLatin1String("disabledoff"), Qt::CaseInsensitive)) { + if (!tag.compare(u"disabledoff"_s, Qt::CaseInsensitive)) { auto *v = new DomResourcePixmap(); v->read(reader); setElementDisabledOff(v); continue; } - if (!tag.compare(QLatin1String("disabledon"), Qt::CaseInsensitive)) { + if (!tag.compare(u"disabledon"_s, Qt::CaseInsensitive)) { auto *v = new DomResourcePixmap(); v->read(reader); setElementDisabledOn(v); continue; } - if (!tag.compare(QLatin1String("activeoff"), Qt::CaseInsensitive)) { + if (!tag.compare(u"activeoff"_s, Qt::CaseInsensitive)) { auto *v = new DomResourcePixmap(); v->read(reader); setElementActiveOff(v); continue; } - if (!tag.compare(QLatin1String("activeon"), Qt::CaseInsensitive)) { + if (!tag.compare(u"activeon"_s, Qt::CaseInsensitive)) { auto *v = new DomResourcePixmap(); v->read(reader); setElementActiveOn(v); continue; } - if (!tag.compare(QLatin1String("selectedoff"), Qt::CaseInsensitive)) { + if (!tag.compare(u"selectedoff"_s, Qt::CaseInsensitive)) { auto *v = new DomResourcePixmap(); v->read(reader); setElementSelectedOff(v); continue; } - if (!tag.compare(QLatin1String("selectedon"), Qt::CaseInsensitive)) { + if (!tag.compare(u"selectedon"_s, Qt::CaseInsensitive)) { auto *v = new DomResourcePixmap(); v->read(reader); setElementSelectedOn(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -4210,34 +4224,34 @@ void DomResourceIcon::write(QXmlStreamWriter &writer, const QString &tagName) co writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("resourceicon") : tagName.toLower()); if (hasAttributeTheme()) - writer.writeAttribute(QStringLiteral("theme"), attributeTheme()); + writer.writeAttribute(u"theme"_s, attributeTheme()); if (hasAttributeResource()) - writer.writeAttribute(QStringLiteral("resource"), attributeResource()); + writer.writeAttribute(u"resource"_s, attributeResource()); if (m_children & NormalOff) - m_normalOff->write(writer, QStringLiteral("normaloff")); + m_normalOff->write(writer, u"normaloff"_s); if (m_children & NormalOn) - m_normalOn->write(writer, QStringLiteral("normalon")); + m_normalOn->write(writer, u"normalon"_s); if (m_children & DisabledOff) - m_disabledOff->write(writer, QStringLiteral("disabledoff")); + m_disabledOff->write(writer, u"disabledoff"_s); if (m_children & DisabledOn) - m_disabledOn->write(writer, QStringLiteral("disabledon")); + m_disabledOn->write(writer, u"disabledon"_s); if (m_children & ActiveOff) - m_activeOff->write(writer, QStringLiteral("activeoff")); + m_activeOff->write(writer, u"activeoff"_s); if (m_children & ActiveOn) - m_activeOn->write(writer, QStringLiteral("activeon")); + m_activeOn->write(writer, u"activeon"_s); if (m_children & SelectedOff) - m_selectedOff->write(writer, QStringLiteral("selectedoff")); + m_selectedOff->write(writer, u"selectedoff"_s); if (m_children & SelectedOn) - m_selectedOn->write(writer, QStringLiteral("selectedon")); + m_selectedOn->write(writer, u"selectedon"_s); if (!m_text.isEmpty()) writer.writeCharacters(m_text); @@ -4428,30 +4442,30 @@ void DomString::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("notr")) { + if (name == u"notr"_s) { setAttributeNotr(attribute.value().toString()); continue; } - if (name == QLatin1String("comment")) { + if (name == u"comment"_s) { setAttributeComment(attribute.value().toString()); continue; } - if (name == QLatin1String("extracomment")) { + if (name == u"extracomment"_s) { setAttributeExtraComment(attribute.value().toString()); continue; } - if (name == QLatin1String("id")) { + if (name == u"id"_s) { setAttributeId(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -4471,16 +4485,16 @@ void DomString::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("string") : tagName.toLower()); if (hasAttributeNotr()) - writer.writeAttribute(QStringLiteral("notr"), attributeNotr()); + writer.writeAttribute(u"notr"_s, attributeNotr()); if (hasAttributeComment()) - writer.writeAttribute(QStringLiteral("comment"), attributeComment()); + writer.writeAttribute(u"comment"_s, attributeComment()); if (hasAttributeExtraComment()) - writer.writeAttribute(QStringLiteral("extracomment"), attributeExtraComment()); + writer.writeAttribute(u"extracomment"_s, attributeExtraComment()); if (hasAttributeId()) - writer.writeAttribute(QStringLiteral("id"), attributeId()); + writer.writeAttribute(u"id"_s, attributeId()); if (!m_text.isEmpty()) writer.writeCharacters(m_text); @@ -4496,15 +4510,15 @@ void DomPointF::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("x"), Qt::CaseInsensitive)) { + if (!tag.compare(u"x"_s, Qt::CaseInsensitive)) { setElementX(reader.readElementText().toDouble()); continue; } - if (!tag.compare(QLatin1String("y"), Qt::CaseInsensitive)) { + if (!tag.compare(u"y"_s, Qt::CaseInsensitive)) { setElementY(reader.readElementText().toDouble()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -4520,10 +4534,10 @@ void DomPointF::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("pointf") : tagName.toLower()); if (m_children & X) - writer.writeTextElement(QString(QLatin1Char('x')), QString::number(m_x, 'f', 15)); + writer.writeTextElement(u"x"_s, QString::number(m_x, 'f', 15)); if (m_children & Y) - writer.writeTextElement(QString(QLatin1Char('y')), QString::number(m_y, 'f', 15)); + writer.writeTextElement(u"y"_s, QString::number(m_y, 'f', 15)); writer.writeEndElement(); } @@ -4558,23 +4572,23 @@ void DomRectF::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("x"), Qt::CaseInsensitive)) { + if (!tag.compare(u"x"_s, Qt::CaseInsensitive)) { setElementX(reader.readElementText().toDouble()); continue; } - if (!tag.compare(QLatin1String("y"), Qt::CaseInsensitive)) { + if (!tag.compare(u"y"_s, Qt::CaseInsensitive)) { setElementY(reader.readElementText().toDouble()); continue; } - if (!tag.compare(QLatin1String("width"), Qt::CaseInsensitive)) { + if (!tag.compare(u"width"_s, Qt::CaseInsensitive)) { setElementWidth(reader.readElementText().toDouble()); continue; } - if (!tag.compare(QLatin1String("height"), Qt::CaseInsensitive)) { + if (!tag.compare(u"height"_s, Qt::CaseInsensitive)) { setElementHeight(reader.readElementText().toDouble()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -4590,16 +4604,16 @@ void DomRectF::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("rectf") : tagName.toLower()); if (m_children & X) - writer.writeTextElement(QString(QLatin1Char('x')), QString::number(m_x, 'f', 15)); + writer.writeTextElement(u"x"_s, QString::number(m_x, 'f', 15)); if (m_children & Y) - writer.writeTextElement(QString(QLatin1Char('y')), QString::number(m_y, 'f', 15)); + writer.writeTextElement(u"y"_s, QString::number(m_y, 'f', 15)); if (m_children & Width) - writer.writeTextElement(QStringLiteral("width"), QString::number(m_width, 'f', 15)); + writer.writeTextElement(u"width"_s, QString::number(m_width, 'f', 15)); if (m_children & Height) - writer.writeTextElement(QStringLiteral("height"), QString::number(m_height, 'f', 15)); + writer.writeTextElement(u"height"_s, QString::number(m_height, 'f', 15)); writer.writeEndElement(); } @@ -4656,15 +4670,15 @@ void DomSizeF::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("width"), Qt::CaseInsensitive)) { + if (!tag.compare(u"width"_s, Qt::CaseInsensitive)) { setElementWidth(reader.readElementText().toDouble()); continue; } - if (!tag.compare(QLatin1String("height"), Qt::CaseInsensitive)) { + if (!tag.compare(u"height"_s, Qt::CaseInsensitive)) { setElementHeight(reader.readElementText().toDouble()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -4680,10 +4694,10 @@ void DomSizeF::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("sizef") : tagName.toLower()); if (m_children & Width) - writer.writeTextElement(QStringLiteral("width"), QString::number(m_width, 'f', 15)); + writer.writeTextElement(u"width"_s, QString::number(m_width, 'f', 15)); if (m_children & Height) - writer.writeTextElement(QStringLiteral("height"), QString::number(m_height, 'f', 15)); + writer.writeTextElement(u"height"_s, QString::number(m_height, 'f', 15)); writer.writeEndElement(); } @@ -4718,11 +4732,11 @@ void DomChar::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("unicode"), Qt::CaseInsensitive)) { + if (!tag.compare(u"unicode"_s, Qt::CaseInsensitive)) { setElementUnicode(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -4738,7 +4752,7 @@ void DomChar::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("char") : tagName.toLower()); if (m_children & Unicode) - writer.writeTextElement(QStringLiteral("unicode"), QString::number(m_unicode)); + writer.writeTextElement(u"unicode"_s, QString::number(m_unicode)); writer.writeEndElement(); } @@ -4765,13 +4779,13 @@ void DomUrl::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("string"), Qt::CaseInsensitive)) { + if (!tag.compare(u"string"_s, Qt::CaseInsensitive)) { auto *v = new DomString(); v->read(reader); setElementString(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -4787,7 +4801,7 @@ void DomUrl::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("url") : tagName.toLower()); if (m_children & String) - m_string->write(writer, QStringLiteral("string")); + m_string->write(writer, u"string"_s); writer.writeEndElement(); } @@ -4900,196 +4914,196 @@ void DomProperty::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - if (name == QLatin1String("stdset")) { + if (name == u"stdset"_s) { setAttributeStdset(attribute.value().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("bool"), Qt::CaseInsensitive)) { + if (!tag.compare(u"bool"_s, Qt::CaseInsensitive)) { setElementBool(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("color"), Qt::CaseInsensitive)) { + if (!tag.compare(u"color"_s, Qt::CaseInsensitive)) { auto *v = new DomColor(); v->read(reader); setElementColor(v); continue; } - if (!tag.compare(QLatin1String("cstring"), Qt::CaseInsensitive)) { + if (!tag.compare(u"cstring"_s, Qt::CaseInsensitive)) { setElementCstring(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("cursor"), Qt::CaseInsensitive)) { + if (!tag.compare(u"cursor"_s, Qt::CaseInsensitive)) { setElementCursor(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("cursorshape"), Qt::CaseInsensitive)) { + if (!tag.compare(u"cursorshape"_s, Qt::CaseInsensitive)) { setElementCursorShape(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("enum"), Qt::CaseInsensitive)) { + if (!tag.compare(u"enum"_s, Qt::CaseInsensitive)) { setElementEnum(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("font"), Qt::CaseInsensitive)) { + if (!tag.compare(u"font"_s, Qt::CaseInsensitive)) { auto *v = new DomFont(); v->read(reader); setElementFont(v); continue; } - if (!tag.compare(QLatin1String("iconset"), Qt::CaseInsensitive)) { + if (!tag.compare(u"iconset"_s, Qt::CaseInsensitive)) { auto *v = new DomResourceIcon(); v->read(reader); setElementIconSet(v); continue; } - if (!tag.compare(QLatin1String("pixmap"), Qt::CaseInsensitive)) { + if (!tag.compare(u"pixmap"_s, Qt::CaseInsensitive)) { auto *v = new DomResourcePixmap(); v->read(reader); setElementPixmap(v); continue; } - if (!tag.compare(QLatin1String("palette"), Qt::CaseInsensitive)) { + if (!tag.compare(u"palette"_s, Qt::CaseInsensitive)) { auto *v = new DomPalette(); v->read(reader); setElementPalette(v); continue; } - if (!tag.compare(QLatin1String("point"), Qt::CaseInsensitive)) { + if (!tag.compare(u"point"_s, Qt::CaseInsensitive)) { auto *v = new DomPoint(); v->read(reader); setElementPoint(v); continue; } - if (!tag.compare(QLatin1String("rect"), Qt::CaseInsensitive)) { + if (!tag.compare(u"rect"_s, Qt::CaseInsensitive)) { auto *v = new DomRect(); v->read(reader); setElementRect(v); continue; } - if (!tag.compare(QLatin1String("set"), Qt::CaseInsensitive)) { + if (!tag.compare(u"set"_s, Qt::CaseInsensitive)) { setElementSet(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("locale"), Qt::CaseInsensitive)) { + if (!tag.compare(u"locale"_s, Qt::CaseInsensitive)) { auto *v = new DomLocale(); v->read(reader); setElementLocale(v); continue; } - if (!tag.compare(QLatin1String("sizepolicy"), Qt::CaseInsensitive)) { + if (!tag.compare(u"sizepolicy"_s, Qt::CaseInsensitive)) { auto *v = new DomSizePolicy(); v->read(reader); setElementSizePolicy(v); continue; } - if (!tag.compare(QLatin1String("size"), Qt::CaseInsensitive)) { + if (!tag.compare(u"size"_s, Qt::CaseInsensitive)) { auto *v = new DomSize(); v->read(reader); setElementSize(v); continue; } - if (!tag.compare(QLatin1String("string"), Qt::CaseInsensitive)) { + if (!tag.compare(u"string"_s, Qt::CaseInsensitive)) { auto *v = new DomString(); v->read(reader); setElementString(v); continue; } - if (!tag.compare(QLatin1String("stringlist"), Qt::CaseInsensitive)) { + if (!tag.compare(u"stringlist"_s, Qt::CaseInsensitive)) { auto *v = new DomStringList(); v->read(reader); setElementStringList(v); continue; } - if (!tag.compare(QLatin1String("number"), Qt::CaseInsensitive)) { + if (!tag.compare(u"number"_s, Qt::CaseInsensitive)) { setElementNumber(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("float"), Qt::CaseInsensitive)) { + if (!tag.compare(u"float"_s, Qt::CaseInsensitive)) { setElementFloat(reader.readElementText().toFloat()); continue; } - if (!tag.compare(QLatin1String("double"), Qt::CaseInsensitive)) { + if (!tag.compare(u"double"_s, Qt::CaseInsensitive)) { setElementDouble(reader.readElementText().toDouble()); continue; } - if (!tag.compare(QLatin1String("date"), Qt::CaseInsensitive)) { + if (!tag.compare(u"date"_s, Qt::CaseInsensitive)) { auto *v = new DomDate(); v->read(reader); setElementDate(v); continue; } - if (!tag.compare(QLatin1String("time"), Qt::CaseInsensitive)) { + if (!tag.compare(u"time"_s, Qt::CaseInsensitive)) { auto *v = new DomTime(); v->read(reader); setElementTime(v); continue; } - if (!tag.compare(QLatin1String("datetime"), Qt::CaseInsensitive)) { + if (!tag.compare(u"datetime"_s, Qt::CaseInsensitive)) { auto *v = new DomDateTime(); v->read(reader); setElementDateTime(v); continue; } - if (!tag.compare(QLatin1String("pointf"), Qt::CaseInsensitive)) { + if (!tag.compare(u"pointf"_s, Qt::CaseInsensitive)) { auto *v = new DomPointF(); v->read(reader); setElementPointF(v); continue; } - if (!tag.compare(QLatin1String("rectf"), Qt::CaseInsensitive)) { + if (!tag.compare(u"rectf"_s, Qt::CaseInsensitive)) { auto *v = new DomRectF(); v->read(reader); setElementRectF(v); continue; } - if (!tag.compare(QLatin1String("sizef"), Qt::CaseInsensitive)) { + if (!tag.compare(u"sizef"_s, Qt::CaseInsensitive)) { auto *v = new DomSizeF(); v->read(reader); setElementSizeF(v); continue; } - if (!tag.compare(QLatin1String("longlong"), Qt::CaseInsensitive)) { + if (!tag.compare(u"longlong"_s, Qt::CaseInsensitive)) { setElementLongLong(reader.readElementText().toLongLong()); continue; } - if (!tag.compare(QLatin1String("char"), Qt::CaseInsensitive)) { + if (!tag.compare(u"char"_s, Qt::CaseInsensitive)) { auto *v = new DomChar(); v->read(reader); setElementChar(v); continue; } - if (!tag.compare(QLatin1String("url"), Qt::CaseInsensitive)) { + if (!tag.compare(u"url"_s, Qt::CaseInsensitive)) { auto *v = new DomUrl(); v->read(reader); setElementUrl(v); continue; } - if (!tag.compare(QLatin1String("uint"), Qt::CaseInsensitive)) { + if (!tag.compare(u"uint"_s, Qt::CaseInsensitive)) { setElementUInt(reader.readElementText().toUInt()); continue; } - if (!tag.compare(QLatin1String("ulonglong"), Qt::CaseInsensitive)) { + if (!tag.compare(u"ulonglong"_s, Qt::CaseInsensitive)) { setElementULongLong(reader.readElementText().toULongLong()); continue; } - if (!tag.compare(QLatin1String("brush"), Qt::CaseInsensitive)) { + if (!tag.compare(u"brush"_s, Qt::CaseInsensitive)) { auto *v = new DomBrush(); v->read(reader); setElementBrush(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -5105,163 +5119,163 @@ void DomProperty::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("property") : tagName.toLower()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); if (hasAttributeStdset()) - writer.writeAttribute(QStringLiteral("stdset"), QString::number(attributeStdset())); + writer.writeAttribute(u"stdset"_s, QString::number(attributeStdset())); switch (kind()) { case Bool: - writer.writeTextElement(QStringLiteral("bool"), elementBool()); + writer.writeTextElement(u"bool"_s, elementBool()); break; case Color: if (m_color != nullptr) - m_color->write(writer, QStringLiteral("color")); + m_color->write(writer, u"color"_s); break; case Cstring: - writer.writeTextElement(QStringLiteral("cstring"), elementCstring()); + writer.writeTextElement(u"cstring"_s, elementCstring()); break; case Cursor: - writer.writeTextElement(QStringLiteral("cursor"), QString::number(elementCursor())); + writer.writeTextElement(u"cursor"_s, QString::number(elementCursor())); break; case CursorShape: - writer.writeTextElement(QStringLiteral("cursorShape"), elementCursorShape()); + writer.writeTextElement(u"cursorShape"_s, elementCursorShape()); break; case Enum: - writer.writeTextElement(QStringLiteral("enum"), elementEnum()); + writer.writeTextElement(u"enum"_s, elementEnum()); break; case Font: if (m_font != nullptr) - m_font->write(writer, QStringLiteral("font")); + m_font->write(writer, u"font"_s); break; case IconSet: if (m_iconSet != nullptr) - m_iconSet->write(writer, QStringLiteral("iconset")); + m_iconSet->write(writer, u"iconset"_s); break; case Pixmap: if (m_pixmap != nullptr) - m_pixmap->write(writer, QStringLiteral("pixmap")); + m_pixmap->write(writer, u"pixmap"_s); break; case Palette: if (m_palette != nullptr) - m_palette->write(writer, QStringLiteral("palette")); + m_palette->write(writer, u"palette"_s); break; case Point: if (m_point != nullptr) - m_point->write(writer, QStringLiteral("point")); + m_point->write(writer, u"point"_s); break; case Rect: if (m_rect != nullptr) - m_rect->write(writer, QStringLiteral("rect")); + m_rect->write(writer, u"rect"_s); break; case Set: - writer.writeTextElement(QStringLiteral("set"), elementSet()); + writer.writeTextElement(u"set"_s, elementSet()); break; case Locale: if (m_locale != nullptr) - m_locale->write(writer, QStringLiteral("locale")); + m_locale->write(writer, u"locale"_s); break; case SizePolicy: if (m_sizePolicy != nullptr) - m_sizePolicy->write(writer, QStringLiteral("sizepolicy")); + m_sizePolicy->write(writer, u"sizepolicy"_s); break; case Size: if (m_size != nullptr) - m_size->write(writer, QStringLiteral("size")); + m_size->write(writer, u"size"_s); break; case String: if (m_string != nullptr) - m_string->write(writer, QStringLiteral("string")); + m_string->write(writer, u"string"_s); break; case StringList: if (m_stringList != nullptr) - m_stringList->write(writer, QStringLiteral("stringlist")); + m_stringList->write(writer, u"stringlist"_s); break; case Number: - writer.writeTextElement(QStringLiteral("number"), QString::number(elementNumber())); + writer.writeTextElement(u"number"_s, QString::number(elementNumber())); break; case Float: - writer.writeTextElement(QStringLiteral("float"), QString::number(elementFloat(), 'f', 8)); + writer.writeTextElement(u"float"_s, QString::number(elementFloat(), 'f', 8)); break; case Double: - writer.writeTextElement(QStringLiteral("double"), QString::number(elementDouble(), 'f', 15)); + writer.writeTextElement(u"double"_s, QString::number(elementDouble(), 'f', 15)); break; case Date: if (m_date != nullptr) - m_date->write(writer, QStringLiteral("date")); + m_date->write(writer, u"date"_s); break; case Time: if (m_time != nullptr) - m_time->write(writer, QStringLiteral("time")); + m_time->write(writer, u"time"_s); break; case DateTime: if (m_dateTime != nullptr) - m_dateTime->write(writer, QStringLiteral("datetime")); + m_dateTime->write(writer, u"datetime"_s); break; case PointF: if (m_pointF != nullptr) - m_pointF->write(writer, QStringLiteral("pointf")); + m_pointF->write(writer, u"pointf"_s); break; case RectF: if (m_rectF != nullptr) - m_rectF->write(writer, QStringLiteral("rectf")); + m_rectF->write(writer, u"rectf"_s); break; case SizeF: if (m_sizeF != nullptr) - m_sizeF->write(writer, QStringLiteral("sizef")); + m_sizeF->write(writer, u"sizef"_s); break; case LongLong: - writer.writeTextElement(QStringLiteral("longLong"), QString::number(elementLongLong())); + writer.writeTextElement(u"longLong"_s, QString::number(elementLongLong())); break; case Char: if (m_char != nullptr) - m_char->write(writer, QStringLiteral("char")); + m_char->write(writer, u"char"_s); break; case Url: if (m_url != nullptr) - m_url->write(writer, QStringLiteral("url")); + m_url->write(writer, u"url"_s); break; case UInt: - writer.writeTextElement(QStringLiteral("UInt"), QString::number(elementUInt())); + writer.writeTextElement(u"UInt"_s, QString::number(elementUInt())); break; case ULongLong: - writer.writeTextElement(QStringLiteral("uLongLong"), QString::number(elementULongLong())); + writer.writeTextElement(u"uLongLong"_s, QString::number(elementULongLong())); break; case Brush: if (m_brush != nullptr) - m_brush->write(writer, QStringLiteral("brush")); + m_brush->write(writer, u"brush"_s); break; default: @@ -5660,13 +5674,13 @@ void DomConnections::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("connection"), Qt::CaseInsensitive)) { + if (!tag.compare(u"connection"_s, Qt::CaseInsensitive)) { auto *v = new DomConnection(); v->read(reader); m_connection.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -5682,7 +5696,7 @@ void DomConnections::write(QXmlStreamWriter &writer, const QString &tagName) con writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("connections") : tagName.toLower()); for (DomConnection *v : m_connection) - v->write(writer, QStringLiteral("connection")); + v->write(writer, u"connection"_s); writer.writeEndElement(); } @@ -5704,29 +5718,29 @@ void DomConnection::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("sender"), Qt::CaseInsensitive)) { + if (!tag.compare(u"sender"_s, Qt::CaseInsensitive)) { setElementSender(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("signal"), Qt::CaseInsensitive)) { + if (!tag.compare(u"signal"_s, Qt::CaseInsensitive)) { setElementSignal(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("receiver"), Qt::CaseInsensitive)) { + if (!tag.compare(u"receiver"_s, Qt::CaseInsensitive)) { setElementReceiver(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("slot"), Qt::CaseInsensitive)) { + if (!tag.compare(u"slot"_s, Qt::CaseInsensitive)) { setElementSlot(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("hints"), Qt::CaseInsensitive)) { + if (!tag.compare(u"hints"_s, Qt::CaseInsensitive)) { auto *v = new DomConnectionHints(); v->read(reader); setElementHints(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -5742,19 +5756,19 @@ void DomConnection::write(QXmlStreamWriter &writer, const QString &tagName) cons writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("connection") : tagName.toLower()); if (m_children & Sender) - writer.writeTextElement(QStringLiteral("sender"), m_sender); + writer.writeTextElement(u"sender"_s, m_sender); if (m_children & Signal) - writer.writeTextElement(QStringLiteral("signal"), m_signal); + writer.writeTextElement(u"signal"_s, m_signal); if (m_children & Receiver) - writer.writeTextElement(QStringLiteral("receiver"), m_receiver); + writer.writeTextElement(u"receiver"_s, m_receiver); if (m_children & Slot) - writer.writeTextElement(QStringLiteral("slot"), m_slot); + writer.writeTextElement(u"slot"_s, m_slot); if (m_children & Hints) - m_hints->write(writer, QStringLiteral("hints")); + m_hints->write(writer, u"hints"_s); writer.writeEndElement(); } @@ -5837,13 +5851,13 @@ void DomConnectionHints::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("hint"), Qt::CaseInsensitive)) { + if (!tag.compare(u"hint"_s, Qt::CaseInsensitive)) { auto *v = new DomConnectionHint(); v->read(reader); m_hint.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -5859,7 +5873,7 @@ void DomConnectionHints::write(QXmlStreamWriter &writer, const QString &tagName) writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("connectionhints") : tagName.toLower()); for (DomConnectionHint *v : m_hint) - v->write(writer, QStringLiteral("hint")); + v->write(writer, u"hint"_s); writer.writeEndElement(); } @@ -5877,26 +5891,26 @@ void DomConnectionHint::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("type")) { + if (name == u"type"_s) { setAttributeType(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("x"), Qt::CaseInsensitive)) { + if (!tag.compare(u"x"_s, Qt::CaseInsensitive)) { setElementX(reader.readElementText().toInt()); continue; } - if (!tag.compare(QLatin1String("y"), Qt::CaseInsensitive)) { + if (!tag.compare(u"y"_s, Qt::CaseInsensitive)) { setElementY(reader.readElementText().toInt()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -5912,13 +5926,13 @@ void DomConnectionHint::write(QXmlStreamWriter &writer, const QString &tagName) writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("connectionhint") : tagName.toLower()); if (hasAttributeType()) - writer.writeAttribute(QStringLiteral("type"), attributeType()); + writer.writeAttribute(u"type"_s, attributeType()); if (m_children & X) - writer.writeTextElement(QString(QLatin1Char('x')), QString::number(m_x)); + writer.writeTextElement(u"x"_s, QString::number(m_x)); if (m_children & Y) - writer.writeTextElement(QString(QLatin1Char('y')), QString::number(m_y)); + writer.writeTextElement(u"y"_s, QString::number(m_y)); writer.writeEndElement(); } @@ -5957,13 +5971,13 @@ void DomDesignerData::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("property"), Qt::CaseInsensitive)) { + if (!tag.compare(u"property"_s, Qt::CaseInsensitive)) { auto *v = new DomProperty(); v->read(reader); m_property.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -5979,7 +5993,7 @@ void DomDesignerData::write(QXmlStreamWriter &writer, const QString &tagName) co writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("designerdata") : tagName.toLower()); for (DomProperty *v : m_property) - v->write(writer, QStringLiteral("property")); + v->write(writer, u"property"_s); writer.writeEndElement(); } @@ -6002,15 +6016,15 @@ void DomSlots::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("signal"), Qt::CaseInsensitive)) { + if (!tag.compare(u"signal"_s, Qt::CaseInsensitive)) { m_signal.append(reader.readElementText()); continue; } - if (!tag.compare(QLatin1String("slot"), Qt::CaseInsensitive)) { + if (!tag.compare(u"slot"_s, Qt::CaseInsensitive)) { m_slot.append(reader.readElementText()); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -6026,10 +6040,10 @@ void DomSlots::write(QXmlStreamWriter &writer, const QString &tagName) const writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("slots") : tagName.toLower()); for (const QString &v : m_signal) - writer.writeTextElement(QStringLiteral("signal"), v); + writer.writeTextElement(u"signal"_s, v); for (const QString &v : m_slot) - writer.writeTextElement(QStringLiteral("slot"), v); + writer.writeTextElement(u"slot"_s, v); writer.writeEndElement(); } @@ -6060,19 +6074,19 @@ void DomPropertySpecifications::read(QXmlStreamReader &reader) switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - if (!tag.compare(QLatin1String("tooltip"), Qt::CaseInsensitive)) { + if (!tag.compare(u"tooltip"_s, Qt::CaseInsensitive)) { auto *v = new DomPropertyToolTip(); v->read(reader); m_tooltip.append(v); continue; } - if (!tag.compare(QLatin1String("stringpropertyspecification"), Qt::CaseInsensitive)) { + if (!tag.compare(u"stringpropertyspecification"_s, Qt::CaseInsensitive)) { auto *v = new DomStringPropertySpecification(); v->read(reader); m_stringpropertyspecification.append(v); continue; } - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -6088,10 +6102,10 @@ void DomPropertySpecifications::write(QXmlStreamWriter &writer, const QString &t writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("propertyspecifications") : tagName.toLower()); for (DomPropertyToolTip *v : m_tooltip) - v->write(writer, QStringLiteral("tooltip")); + v->write(writer, u"tooltip"_s); for (DomStringPropertySpecification *v : m_stringpropertyspecification) - v->write(writer, QStringLiteral("stringpropertyspecification")); + v->write(writer, u"stringpropertyspecification"_s); writer.writeEndElement(); } @@ -6115,18 +6129,18 @@ void DomPropertyToolTip::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -6142,7 +6156,7 @@ void DomPropertyToolTip::write(QXmlStreamWriter &writer, const QString &tagName) writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("propertytooltip") : tagName.toLower()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); writer.writeEndElement(); } @@ -6154,26 +6168,26 @@ void DomStringPropertySpecification::read(QXmlStreamReader &reader) const QXmlStreamAttributes &attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const auto name = attribute.name(); - if (name == QLatin1String("name")) { + if (name == u"name"_s) { setAttributeName(attribute.value().toString()); continue; } - if (name == QLatin1String("type")) { + if (name == u"type"_s) { setAttributeType(attribute.value().toString()); continue; } - if (name == QLatin1String("notr")) { + if (name == u"notr"_s) { setAttributeNotr(attribute.value().toString()); continue; } - reader.raiseError(QLatin1String("Unexpected attribute ") + name); + reader.raiseError("Unexpected attribute "_L1 + name); } while (!reader.hasError()) { switch (reader.readNext()) { case QXmlStreamReader::StartElement : { const auto tag = reader.name(); - reader.raiseError(QLatin1String("Unexpected element ") + tag); + reader.raiseError("Unexpected element "_L1 + tag); } break; case QXmlStreamReader::EndElement : @@ -6189,13 +6203,13 @@ void DomStringPropertySpecification::write(QXmlStreamWriter &writer, const QStri writer.writeStartElement(tagName.isEmpty() ? QStringLiteral("stringpropertyspecification") : tagName.toLower()); if (hasAttributeName()) - writer.writeAttribute(QStringLiteral("name"), attributeName()); + writer.writeAttribute(u"name"_s, attributeName()); if (hasAttributeType()) - writer.writeAttribute(QStringLiteral("type"), attributeType()); + writer.writeAttribute(u"type"_s, attributeType()); if (hasAttributeNotr()) - writer.writeAttribute(QStringLiteral("notr"), attributeNotr()); + writer.writeAttribute(u"notr"_s, attributeNotr()); writer.writeEndElement(); } diff --git a/src/tools/uic/ui4.h b/src/tools/uic/ui4.h index a5ac51f521..333f7f4e6a 100644 --- a/src/tools/uic/ui4.h +++ b/src/tools/uic/ui4.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // // W A R N I N G @@ -1670,6 +1645,16 @@ public: inline bool hasElementKerning() const { return m_children & Kerning; } void clearElementKerning(); + inline QString elementHintingPreference() const { return m_hintingPreference; } + void setElementHintingPreference(const QString &a); + inline bool hasElementHintingPreference() const { return m_children & HintingPreference; } + void clearElementHintingPreference(); + + inline QString elementFontWeight() const { return m_fontWeight; } + void setElementFontWeight(const QString &a); + inline bool hasElementFontWeight() const { return m_children & FontWeight; } + void clearElementFontWeight(); + private: // child element data @@ -1684,6 +1669,8 @@ private: bool m_antialiasing = false; QString m_styleStrategy; bool m_kerning = false; + QString m_hintingPreference; + QString m_fontWeight; enum Child { Family = 1, @@ -1695,7 +1682,9 @@ private: StrikeOut = 64, Antialiasing = 128, StyleStrategy = 256, - Kerning = 512 + Kerning = 512, + HintingPreference = 1024, + FontWeight = 2048 }; }; diff --git a/src/tools/uic/uic.cpp b/src/tools/uic/uic.cpp index 3601280144..fb0a37d21d 100644 --- a/src/tools/uic/uic.cpp +++ b/src/tools/uic/uic.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "uic.h" #include "ui4.h" @@ -47,6 +22,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + Uic::Uic(Driver *d) : drv(d), out(d->output()), @@ -61,9 +38,10 @@ bool Uic::printDependencies() QString fileName = opt.inputFile; QFile f; - if (fileName.isEmpty()) - f.open(stdin, QIODevice::ReadOnly); - else { + if (fileName.isEmpty()) { + if (!f.open(stdin, QIODevice::ReadOnly)) + return false; + } else { f.setFileName(fileName); if (!f.open(QIODevice::ReadOnly)) return false; @@ -127,7 +105,7 @@ void Uic::writeCopyrightHeaderCpp(const DomUI *ui) const static inline bool isCppCommentChar(QChar c) { - return c == QLatin1Char('/') || c == QLatin1Char('*'); + return c == u'/' || c == u'*'; } static int leadingCppCommentCharCount(QStringView s) @@ -142,13 +120,13 @@ void Uic::writeCopyrightHeaderPython(const DomUI *ui) const { QString comment = ui->elementComment(); if (!comment.isEmpty()) { - const auto lines = QStringView{comment}.split(QLatin1Char('\n')); + const auto lines = QStringView{comment}.split(u'\n'); for (const auto &line : lines) { if (const int leadingCommentChars = leadingCppCommentCharCount(line)) { out << language::repeat(leadingCommentChars, '#') << line.right(line.size() - leadingCommentChars); } else { - if (!line.startsWith(QLatin1Char('#'))) + if (!line.startsWith(u'#')) out << "# "; out << line; } @@ -169,7 +147,7 @@ void Uic::writeCopyrightHeaderPython(const DomUI *ui) const static double versionFromUiAttribute(QXmlStreamReader &reader) { const QXmlStreamAttributes attributes = reader.attributes(); - const QString versionAttribute = QLatin1String("version"); + const auto versionAttribute = "version"_L1; if (!attributes.hasAttribute(versionAttribute)) return 4.0; const QStringView version = attributes.value(versionAttribute); @@ -180,7 +158,7 @@ DomUI *Uic::parseUiFile(QXmlStreamReader &reader) { DomUI *ui = nullptr; - const QString uiElement = QLatin1String("ui"); + const auto uiElement = "ui"_L1; while (!reader.atEnd()) { if (reader.readNext() == QXmlStreamReader::StartElement) { if (reader.name().compare(uiElement, Qt::CaseInsensitive) == 0 @@ -195,7 +173,7 @@ DomUI *Uic::parseUiFile(QXmlStreamReader &reader) ui = new DomUI(); ui->read(reader); } else { - reader.raiseError(QLatin1String("Unexpected element ") + reader.name().toString()); + reader.raiseError("Unexpected element "_L1 + reader.name().toString()); } } } @@ -231,7 +209,7 @@ bool Uic::write(QIODevice *in) const QString &language = ui->attributeLanguage(); driver()->setUseIdBasedTranslations(ui->attributeIdbasedtr()); - if (!language.isEmpty() && language.compare(QLatin1String("c++"), Qt::CaseInsensitive) != 0) { + if (!language.isEmpty() && language.compare("c++"_L1, Qt::CaseInsensitive) != 0) { fprintf(stderr, "uic: File is not a \"c++\" ui file, language=%s\n", qPrintable(language)); return false; } @@ -266,8 +244,7 @@ bool Uic::write(DomUI *ui) } pixFunction = ui->elementPixmapFunction(); - if (pixFunction == QLatin1String("QPixmap::fromMimeSource") - || pixFunction == QLatin1String("qPixmapFromMimeSource")) { + if (pixFunction == "QPixmap::fromMimeSource"_L1 || pixFunction == "qPixmapFromMimeSource"_L1) { fprintf(stderr, "%s: Warning: Obsolete pixmap function '%s' specified in the UI file.\n", qPrintable(opt.messagePrefix()), qPrintable(pixFunction)); pixFunction.clear(); @@ -315,9 +292,9 @@ void Uic::writeHeaderProtectionEnd() bool Uic::isButton(const QString &className) const { static const QStringList buttons = { - QLatin1String("QRadioButton"), QLatin1String("QToolButton"), - QLatin1String("QCheckBox"), QLatin1String("QPushButton"), - QLatin1String("QCommandLinkButton") + u"QRadioButton"_s, u"QToolButton"_s, + u"QCheckBox"_s, u"QPushButton"_s, + u"QCommandLinkButton"_s }; return customWidgetsInfo()->extendsOneOf(className, buttons); } @@ -325,10 +302,10 @@ bool Uic::isButton(const QString &className) const bool Uic::isContainer(const QString &className) const { static const QStringList containers = { - QLatin1String("QStackedWidget"), QLatin1String("QToolBox"), - QLatin1String("QTabWidget"), QLatin1String("QScrollArea"), - QLatin1String("QMdiArea"), QLatin1String("QWizard"), - QLatin1String("QDockWidget") + u"QStackedWidget"_s, u"QToolBox"_s, + u"QTabWidget"_s, u"QScrollArea"_s, + u"QMdiArea"_s, u"QWizard"_s, + u"QDockWidget"_s }; return customWidgetsInfo()->extendsOneOf(className, containers); @@ -337,7 +314,7 @@ bool Uic::isContainer(const QString &className) const bool Uic::isMenu(const QString &className) const { static const QStringList menus = { - QLatin1String("QMenu"), QLatin1String("QPopupMenu") + u"QMenu"_s, u"QPopupMenu"_s }; return customWidgetsInfo()->extendsOneOf(className, menus); } diff --git a/src/tools/uic/uic.h b/src/tools/uic/uic.h index f3dfd49149..ef5f0a90bd 100644 --- a/src/tools/uic/uic.h +++ b/src/tools/uic/uic.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef UIC_H #define UIC_H diff --git a/src/tools/uic/utils.h b/src/tools/uic/utils.h index 34c4ab23d4..37247cdea6 100644 --- a/src/tools/uic/utils.h +++ b/src/tools/uic/utils.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef UTILS_H #define UTILS_H @@ -37,7 +12,7 @@ QT_BEGIN_NAMESPACE inline bool toBool(const QString &str) -{ return str.toLower() == QLatin1String("true"); } +{ return QString::compare(str, QLatin1StringView("true"), Qt::CaseInsensitive) == 0; } inline QString toString(const DomString *str) { return str ? str->text() : QString(); } diff --git a/src/tools/uic/validator.cpp b/src/tools/uic/validator.cpp index d3b1300ee7..c9f8c0a496 100644 --- a/src/tools/uic/validator.cpp +++ b/src/tools/uic/validator.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "validator.h" #include "driver.h" diff --git a/src/tools/uic/validator.h b/src/tools/uic/validator.h index f7f7401991..6f965b84d5 100644 --- a/src/tools/uic/validator.h +++ b/src/tools/uic/validator.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the tools applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef VALIDATOR_H #define VALIDATOR_H diff --git a/src/tools/windeployqt/CMakeLists.txt b/src/tools/windeployqt/CMakeLists.txt new file mode 100644 index 0000000000..2e50116484 --- /dev/null +++ b/src/tools/windeployqt/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## windeployqt Tool: +##################################################################### + +qt_get_tool_target_name(target_name windeployqt) +qt_internal_add_tool(${target_name} + TOOLS_TARGET Core + USER_FACING + INSTALL_VERSIONED_LINK + TARGET_DESCRIPTION "Qt Windows Deployment Tool" + SOURCES + qmlutils.cpp qmlutils.h + qtmoduleinfo.cpp qtmoduleinfo.h + qtplugininfo.cpp qtplugininfo.h + utils.cpp utils.h + main.cpp + DEFINES + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + QT_NO_FOREACH + QT_NO_QPAIR + LIBRARIES + Qt::CorePrivate +) +qt_internal_return_unless_building_tools() + +qt_internal_extend_target(${target_name} CONDITION WIN32 + PUBLIC_LIBRARIES + shlwapi +) diff --git a/src/tools/windeployqt/main.cpp b/src/tools/windeployqt/main.cpp new file mode 100644 index 0000000000..084345a4d8 --- /dev/null +++ b/src/tools/windeployqt/main.cpp @@ -0,0 +1,2007 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "utils.h" +#include "qmlutils.h" +#include "qtmoduleinfo.h" +#include "qtplugininfo.h" + +#include <QtCore/QCommandLineOption> +#include <QtCore/QCommandLineParser> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QCoreApplication> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> +#include <QtCore/QList> +#include <QtCore/QOperatingSystemVersion> +#include <QtCore/QSharedPointer> + +#ifdef Q_OS_WIN +#include <QtCore/qt_windows.h> +#else +#define IMAGE_FILE_MACHINE_ARM64 0xaa64 +#endif + +#include <QtCore/private/qconfig_p.h> + +#include <algorithm> +#include <cstdio> +#include <iostream> +#include <iterator> +#include <unordered_map> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QtModuleInfoStore qtModuleEntries; + +#define DECLARE_KNOWN_MODULE(name) \ + static size_t Qt##name ## ModuleId = QtModule::InvalidId + +DECLARE_KNOWN_MODULE(3DQuick); +DECLARE_KNOWN_MODULE(Core); +DECLARE_KNOWN_MODULE(Designer); +DECLARE_KNOWN_MODULE(DesignerComponents); +DECLARE_KNOWN_MODULE(Gui); +DECLARE_KNOWN_MODULE(Qml); +DECLARE_KNOWN_MODULE(QmlTooling); +DECLARE_KNOWN_MODULE(Quick); +DECLARE_KNOWN_MODULE(WebEngineCore); +DECLARE_KNOWN_MODULE(Widgets); + +#define DEFINE_KNOWN_MODULE(name) \ + m[QLatin1String("Qt6" #name)] = &Qt##name ## ModuleId + +static void assignKnownModuleIds() +{ + std::unordered_map<QString, size_t *> m; + DEFINE_KNOWN_MODULE(3DQuick); + DEFINE_KNOWN_MODULE(Core); + DEFINE_KNOWN_MODULE(Designer); + DEFINE_KNOWN_MODULE(DesignerComponents); + DEFINE_KNOWN_MODULE(Gui); + DEFINE_KNOWN_MODULE(Qml); + DEFINE_KNOWN_MODULE(QmlTooling); + DEFINE_KNOWN_MODULE(Quick); + DEFINE_KNOWN_MODULE(WebEngineCore); + DEFINE_KNOWN_MODULE(Widgets); + for (size_t i = 0; i < qtModuleEntries.size(); ++i) { + const QtModule &module = qtModuleEntries.moduleById(i); + auto it = m.find(module.name); + if (it == m.end()) + continue; + *(it->second) = i; + } +} + +#undef DECLARE_KNOWN_MODULE +#undef DEFINE_KNOWN_MODULE + +static const char webEngineProcessC[] = "QtWebEngineProcess"; + +static inline QString webProcessBinary(const char *binaryName, Platform p) +{ + const QString webProcess = QLatin1StringView(binaryName); + return (p & WindowsBased) ? webProcess + QStringLiteral(".exe") : webProcess; +} + +static QString moduleNameToOptionName(const QString &moduleName) +{ + QString result = moduleName + .mid(3) // strip the "Qt6" prefix + .toLower(); + if (result == u"help"_s) + result.prepend("qt"_L1); + return result; +} + +static QByteArray formatQtModules(const ModuleBitset &mask, bool option = false) +{ + QByteArray result; + for (const auto &qtModule : qtModuleEntries) { + if (mask.test(qtModule.id)) { + if (!result.isEmpty()) + result.append(' '); + result.append(option + ? moduleNameToOptionName(qtModule.name).toUtf8() + : qtModule.name.toUtf8()); + if (qtModule.internal) + result.append("Internal"); + } + } + return result; +} + +static QString formatQtPlugins(const PluginInformation &pluginInfo) +{ + QString result(u'\n'); + for (const auto &pair : pluginInfo.typeMap()) { + result += pair.first; + result += u": \n"; + for (const QString &plugin : pair.second) { + result += u" "; + result += plugin; + result += u'\n'; + } + } + return result; +} + +static Platform platformFromMkSpec(const QString &xSpec) +{ + if (xSpec.startsWith("win32-"_L1)) { + if (xSpec.contains("clang-g++"_L1)) + return WindowsDesktopClangMinGW; + if (xSpec.contains("clang-msvc++"_L1)) + return WindowsDesktopClangMsvc; + if (xSpec.contains("arm"_L1)) + return WindowsDesktopMsvcArm; + + return xSpec.contains("g++"_L1) ? WindowsDesktopMinGW : WindowsDesktopMsvcIntel; + } + return UnknownPlatform; +} + +// Helpers for exclusive options, "-foo", "--no-foo" +enum ExlusiveOptionValue { + OptionAuto, + OptionEnabled, + OptionDisabled +}; + +static ExlusiveOptionValue parseExclusiveOptions(const QCommandLineParser *parser, + const QCommandLineOption &enableOption, + const QCommandLineOption &disableOption) +{ + const bool enabled = parser->isSet(enableOption); + const bool disabled = parser->isSet(disableOption); + if (enabled) { + if (disabled) { + std::wcerr << "Warning: both -" << enableOption.names().first() + << " and -" << disableOption.names().first() << " were specified, defaulting to -" + << enableOption.names().first() << ".\n"; + } + return OptionEnabled; + } + return disabled ? OptionDisabled : OptionAuto; +} + +struct Options { + enum DebugDetection { + DebugDetectionAuto, + DebugDetectionForceDebug, + DebugDetectionForceRelease + }; + + bool plugins = true; + bool libraries = true; + bool quickImports = true; + bool translations = true; + bool systemD3dCompiler = true; + bool systemDxc = true; + bool compilerRunTime = false; + bool softwareRasterizer = true; + bool ffmpeg = true; + PluginSelections pluginSelections; + Platform platform = WindowsDesktopMsvcIntel; + ModuleBitset additionalLibraries; + ModuleBitset disabledLibraries; + unsigned updateFileFlags = 0; + QStringList qmlDirectories; // Project's QML files. + QStringList qmlImportPaths; // Custom QML module locations. + QString directory; + QString qtpathsBinary; + QString translationsDirectory; // Translations target directory + QStringList languages; + QString libraryDirectory; + QString pluginDirectory; + QString openSslRootDirectory; + QString qmlDirectory; + QStringList binaries; + JsonOutput *json = nullptr; + ListOption list = ListNone; + DebugDetection debugDetection = DebugDetectionAuto; + bool deployPdb = false; + bool dryRun = false; + bool patchQt = true; + bool ignoreLibraryErrors = false; + bool deployInsightTrackerPlugin = false; + bool forceOpenSslPlugin = false; +}; + +// Return binary to be deployed from folder, ignore pre-existing web engine process. +static inline QString findBinary(const QString &directory, Platform platform) +{ + const QStringList nameFilters = (platform & WindowsBased) ? + QStringList(QStringLiteral("*.exe")) : QStringList(); + const QFileInfoList &binaries = + QDir(QDir::cleanPath(directory)).entryInfoList(nameFilters, QDir::Files | QDir::Executable); + for (const QFileInfo &binaryFi : binaries) { + const QString binary = binaryFi.fileName(); + if (!binary.contains(QLatin1StringView(webEngineProcessC), Qt::CaseInsensitive)) { + return binaryFi.absoluteFilePath(); + } + } + return QString(); +} + +static QString msgFileDoesNotExist(const QString & file) +{ + return u'"' + QDir::toNativeSeparators(file) + "\" does not exist."_L1; +} + +enum CommandLineParseFlag { + CommandLineParseError = 0x1, + CommandLineParseHelpRequested = 0x2, + CommandLineVersionRequested = 0x4 +}; + +static QCommandLineOption createQMakeOption() +{ + return { + u"qmake"_s, + u"Use specified qmake instead of qmake from PATH. Deprecated, use qtpaths instead."_s, + u"path"_s + }; +} + +static QCommandLineOption createQtPathsOption() +{ + return { + u"qtpaths"_s, + u"Use specified qtpaths.exe instead of qtpaths.exe from PATH."_s, + u"path"_s + }; +} + +static QCommandLineOption createVerboseOption() +{ + return { + u"verbose"_s, + u"Verbose level (0-2)."_s, + u"level"_s + }; +} + +static int parseEarlyArguments(const QStringList &arguments, Options *options, + QString *errorMessage) +{ + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + + QCommandLineOption qmakeOption = createQMakeOption(); + parser.addOption(qmakeOption); + + QCommandLineOption qtpathsOption = createQtPathsOption(); + parser.addOption(qtpathsOption); + + QCommandLineOption verboseOption = createVerboseOption(); + parser.addOption(verboseOption); + + // Deliberately don't check for errors. We want to ignore options we don't know about. + parser.parse(arguments); + + if (parser.isSet(qmakeOption) && parser.isSet(qtpathsOption)) { + *errorMessage = QStringLiteral("-qmake and -qtpaths are mutually exclusive."); + return CommandLineParseError; + } + + if (parser.isSet(qmakeOption) && optVerboseLevel >= 1) + std::wcerr << "Warning: -qmake option is deprecated. Use -qtpaths instead.\n"; + + if (parser.isSet(qtpathsOption) || parser.isSet(qmakeOption)) { + const QString qtpathsArg = parser.isSet(qtpathsOption) ? parser.value(qtpathsOption) + : parser.value(qmakeOption); + + const QString qtpathsBinary = QDir::cleanPath(qtpathsArg); + const QFileInfo fi(qtpathsBinary); + if (!fi.exists()) { + *errorMessage = msgFileDoesNotExist(qtpathsBinary); + return CommandLineParseError; + } + + if (!fi.isExecutable()) { + *errorMessage = u'"' + QDir::toNativeSeparators(qtpathsBinary) + + QStringLiteral("\" is not an executable."); + return CommandLineParseError; + } + options->qtpathsBinary = qtpathsBinary; + } + + if (parser.isSet(verboseOption)) { + bool ok; + const QString value = parser.value(verboseOption); + optVerboseLevel = value.toInt(&ok); + if (!ok || optVerboseLevel < 0) { + *errorMessage = QStringLiteral("Invalid value \"%1\" passed for verbose level.") + .arg(value); + return CommandLineParseError; + } + } + + return 0; +} + +static inline int parseArguments(const QStringList &arguments, QCommandLineParser *parser, + Options *options, QString *errorMessage) +{ + using CommandLineOptionPtr = QSharedPointer<QCommandLineOption>; + using OptionPtrVector = QList<CommandLineOptionPtr>; + + parser->setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + parser->setApplicationDescription(u"Qt Deploy Tool " QT_VERSION_STR + "\n\nThe simplest way to use windeployqt is to add the bin directory of your Qt\n" + "installation (e.g. <QT_DIR\\bin>) to the PATH variable and then run:\n windeployqt <path-to-app-binary>\n\n" + "If your application uses Qt Quick, run:\n windeployqt --qmldir <path-to-app-qml-files> <path-to-app-binary>"_s); + const QCommandLineOption helpOption = parser->addHelpOption(); + const QCommandLineOption versionOption = parser->addVersionOption(); + + QCommandLineOption dirOption(QStringLiteral("dir"), + QStringLiteral("Use directory instead of binary directory."), + QStringLiteral("directory")); + parser->addOption(dirOption); + + // Add early options to have them available in the help text. + parser->addOption(createQMakeOption()); + parser->addOption(createQtPathsOption()); + + QCommandLineOption libDirOption(QStringLiteral("libdir"), + QStringLiteral("Copy libraries to path."), + QStringLiteral("path")); + parser->addOption(libDirOption); + + QCommandLineOption pluginDirOption(QStringLiteral("plugindir"), + QStringLiteral("Copy plugins to path."), + QStringLiteral("path")); + parser->addOption(pluginDirOption); + + const QCommandLineOption translationDirOption( + u"translationdir"_s, + u"Copy translations to path."_s, + u"path"_s); + parser->addOption(translationDirOption); + + QCommandLineOption qmlDeployDirOption(QStringLiteral("qml-deploy-dir"), + QStringLiteral("Copy qml files to path."), + QStringLiteral("path")); + parser->addOption(qmlDeployDirOption); + + QCommandLineOption debugOption(QStringLiteral("debug"), + QStringLiteral("Assume debug binaries.")); + parser->addOption(debugOption); + QCommandLineOption releaseOption(QStringLiteral("release"), + QStringLiteral("Assume release binaries.")); + parser->addOption(releaseOption); + QCommandLineOption releaseWithDebugInfoOption(QStringLiteral("release-with-debug-info"), + QStringLiteral("Assume release binaries with debug information.")); + releaseWithDebugInfoOption.setFlags(QCommandLineOption::HiddenFromHelp); // Deprecated by improved debug detection. + parser->addOption(releaseWithDebugInfoOption); + + QCommandLineOption deployPdbOption(QStringLiteral("pdb"), + QStringLiteral("Deploy .pdb files (MSVC).")); + parser->addOption(deployPdbOption); + + QCommandLineOption forceOption(QStringLiteral("force"), + QStringLiteral("Force updating files.")); + parser->addOption(forceOption); + + QCommandLineOption dryRunOption(QStringLiteral("dry-run"), + QStringLiteral("Simulation mode. Behave normally, but do not copy/update any files.")); + parser->addOption(dryRunOption); + + QCommandLineOption noPatchQtOption(QStringLiteral("no-patchqt"), + QStringLiteral("Do not patch the Qt6Core library.")); + parser->addOption(noPatchQtOption); + + QCommandLineOption ignoreErrorOption(QStringLiteral("ignore-library-errors"), + QStringLiteral("Ignore errors when libraries cannot be found.")); + parser->addOption(ignoreErrorOption); + + QCommandLineOption noPluginsOption(QStringLiteral("no-plugins"), + QStringLiteral("Skip plugin deployment.")); + parser->addOption(noPluginsOption); + + QCommandLineOption includeSoftPluginsOption(QStringLiteral("include-soft-plugins"), + QStringLiteral("Include in the deployment all relevant plugins by taking into account all soft dependencies.")); + parser->addOption(includeSoftPluginsOption); + + QCommandLineOption skipPluginTypesOption(QStringLiteral("skip-plugin-types"), + QStringLiteral("A comma-separated list of plugin types that are not deployed (qmltooling,generic)."), + QStringLiteral("plugin types")); + parser->addOption(skipPluginTypesOption); + + QCommandLineOption addPluginTypesOption(QStringLiteral("add-plugin-types"), + QStringLiteral("A comma-separated list of plugin types that will be added to deployment (imageformats,iconengines)"), + QStringLiteral("plugin types")); + parser->addOption(addPluginTypesOption); + + QCommandLineOption includePluginsOption(QStringLiteral("include-plugins"), + QStringLiteral("A comma-separated list of individual plugins that will be added to deployment (scene2d,qjpeg)"), + QStringLiteral("plugins")); + parser->addOption(includePluginsOption); + + QCommandLineOption excludePluginsOption(QStringLiteral("exclude-plugins"), + QStringLiteral("A comma-separated list of individual plugins that will not be deployed (qsvg,qpdf)"), + QStringLiteral("plugins")); + parser->addOption(excludePluginsOption); + + QCommandLineOption noLibraryOption(QStringLiteral("no-libraries"), + QStringLiteral("Skip library deployment.")); + parser->addOption(noLibraryOption); + + QCommandLineOption qmlDirOption(QStringLiteral("qmldir"), + QStringLiteral("Scan for QML-imports starting from directory."), + QStringLiteral("directory")); + parser->addOption(qmlDirOption); + + QCommandLineOption qmlImportOption(QStringLiteral("qmlimport"), + QStringLiteral("Add the given path to the QML module search locations."), + QStringLiteral("directory")); + parser->addOption(qmlImportOption); + + QCommandLineOption noQuickImportOption(QStringLiteral("no-quick-import"), + QStringLiteral("Skip deployment of Qt Quick imports.")); + parser->addOption(noQuickImportOption); + + + QCommandLineOption translationOption(QStringLiteral("translations"), + QStringLiteral("A comma-separated list of languages to deploy (de,fi)."), + QStringLiteral("languages")); + parser->addOption(translationOption); + + QCommandLineOption noTranslationOption(QStringLiteral("no-translations"), + QStringLiteral("Skip deployment of translations.")); + parser->addOption(noTranslationOption); + + QCommandLineOption noSystemD3DCompilerOption(QStringLiteral("no-system-d3d-compiler"), + QStringLiteral("Skip deployment of the system D3D compiler.")); + parser->addOption(noSystemD3DCompilerOption); + + QCommandLineOption noSystemDxcOption(QStringLiteral("no-system-dxc-compiler"), + QStringLiteral("Skip deployment of the system DXC (dxcompiler.dll, dxil.dll).")); + parser->addOption(noSystemDxcOption); + + + QCommandLineOption compilerRunTimeOption(QStringLiteral("compiler-runtime"), + QStringLiteral("Deploy compiler runtime (Desktop only).")); + parser->addOption(compilerRunTimeOption); + + QCommandLineOption noCompilerRunTimeOption(QStringLiteral("no-compiler-runtime"), + QStringLiteral("Do not deploy compiler runtime (Desktop only).")); + parser->addOption(noCompilerRunTimeOption); + + QCommandLineOption jsonOption(QStringLiteral("json"), + QStringLiteral("Print to stdout in JSON format.")); + parser->addOption(jsonOption); + + QCommandLineOption suppressSoftwareRasterizerOption(QStringLiteral("no-opengl-sw"), + QStringLiteral("Do not deploy the software rasterizer library.")); + parser->addOption(suppressSoftwareRasterizerOption); + + QCommandLineOption noFFmpegOption(QStringLiteral("no-ffmpeg"), + QStringLiteral("Do not deploy the FFmpeg libraries.")); + parser->addOption(noFFmpegOption); + + QCommandLineOption forceOpenSslOption(QStringLiteral("force-openssl"), + QStringLiteral("Deploy openssl plugin but ignore openssl library dependency")); + parser->addOption(forceOpenSslOption); + + QCommandLineOption openSslRootOption(QStringLiteral("openssl-root"), + QStringLiteral("Directory containing openSSL libraries."), + QStringLiteral("directory")); + parser->addOption(openSslRootOption); + + + QCommandLineOption listOption(QStringLiteral("list"), + "Print only the names of the files copied.\n" + "Available options:\n" + " source: absolute path of the source files\n" + " target: absolute path of the target files\n" + " relative: paths of the target files, relative\n" + " to the target directory\n" + " mapping: outputs the source and the relative\n" + " target, suitable for use within an\n" + " Appx mapping file"_L1, + QStringLiteral("option")); + parser->addOption(listOption); + + // Add early option to have it available in the help text. + parser->addOption(createVerboseOption()); + + parser->addPositionalArgument(QStringLiteral("[files]"), + QStringLiteral("Binaries or directory containing the binary.")); + + QCommandLineOption deployInsightTrackerOption(QStringLiteral("deploy-insighttracker"), + QStringLiteral("Deploy insight tracker plugin.")); + // The option will be added to the parser if the module is available (see block below) + bool insightTrackerModuleAvailable = false; + + OptionPtrVector enabledModuleOptions; + OptionPtrVector disabledModuleOptions; + const size_t qtModulesCount = qtModuleEntries.size(); + enabledModuleOptions.reserve(qtModulesCount); + disabledModuleOptions.reserve(qtModulesCount); + for (const QtModule &module : qtModuleEntries) { + const QString option = moduleNameToOptionName(module.name); + const QString name = module.name; + if (name == u"InsightTracker") { + parser->addOption(deployInsightTrackerOption); + insightTrackerModuleAvailable = true; + } + const QString enabledDescription = QStringLiteral("Add ") + name + QStringLiteral(" module."); + CommandLineOptionPtr enabledOption(new QCommandLineOption(option, enabledDescription)); + parser->addOption(*enabledOption.data()); + enabledModuleOptions.append(enabledOption); + const QString disabledDescription = QStringLiteral("Remove ") + name + QStringLiteral(" module."); + CommandLineOptionPtr disabledOption(new QCommandLineOption(QStringLiteral("no-") + option, + disabledDescription)); + disabledModuleOptions.append(disabledOption); + parser->addOption(*disabledOption.data()); + } + + const bool success = parser->parse(arguments); + if (parser->isSet(helpOption)) + return CommandLineParseHelpRequested; + if (parser->isSet(versionOption)) + return CommandLineVersionRequested; + if (!success) { + *errorMessage = parser->errorText(); + return CommandLineParseError; + } + + options->libraryDirectory = parser->value(libDirOption); + options->pluginDirectory = parser->value(pluginDirOption); + options->translationsDirectory = parser->value(translationDirOption); + options->qmlDirectory = parser->value(qmlDeployDirOption); + options->plugins = !parser->isSet(noPluginsOption); + options->libraries = !parser->isSet(noLibraryOption); + options->translations = !parser->isSet(noTranslationOption); + if (parser->isSet(translationOption)) + options->languages = parser->value(translationOption).split(u','); + options->systemD3dCompiler = !parser->isSet(noSystemD3DCompilerOption); + options->systemDxc = !parser->isSet(noSystemDxcOption); + options->quickImports = !parser->isSet(noQuickImportOption); + + // default to deployment of compiler runtime for windows desktop configurations + if (options->platform == WindowsDesktopMinGW || options->platform.testFlags(WindowsDesktopMsvc) + || parser->isSet(compilerRunTimeOption)) + options->compilerRunTime = true; + if (parser->isSet(noCompilerRunTimeOption)) + options->compilerRunTime = false; + + if (options->compilerRunTime && options->platform != WindowsDesktopMinGW + && !options->platform.testFlags(WindowsDesktopMsvc)) { + *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for Desktop MSVC/g++ only."); + return CommandLineParseError; + } + + options->pluginSelections.includeSoftPlugins = parser->isSet(includeSoftPluginsOption); + + if (parser->isSet(skipPluginTypesOption)) + options->pluginSelections.disabledPluginTypes = parser->value(skipPluginTypesOption).split(u','); + + if (parser->isSet(addPluginTypesOption)) + options->pluginSelections.enabledPluginTypes = parser->value(addPluginTypesOption).split(u','); + + if (parser->isSet(includePluginsOption)) + options->pluginSelections.includedPlugins = parser->value(includePluginsOption).split(u','); + + if (parser->isSet(excludePluginsOption)) + options->pluginSelections.excludedPlugins = parser->value(excludePluginsOption).split(u','); + + if (parser->isSet(releaseWithDebugInfoOption)) + std::wcerr << "Warning: " << releaseWithDebugInfoOption.names().first() << " is obsolete."; + + switch (parseExclusiveOptions(parser, debugOption, releaseOption)) { + case OptionAuto: + break; + case OptionEnabled: + options->debugDetection = Options::DebugDetectionForceDebug; + break; + case OptionDisabled: + options->debugDetection = Options::DebugDetectionForceRelease; + break; + } + + if (parser->isSet(deployPdbOption)) { + if (options->platform.testFlag(WindowsBased) && !options->platform.testFlag(MinGW)) + options->deployPdb = true; + else + std::wcerr << "Warning: --" << deployPdbOption.names().first() << " is not supported on this platform.\n"; + } + + if (parser->isSet(suppressSoftwareRasterizerOption)) + options->softwareRasterizer = false; + + if (parser->isSet(noFFmpegOption)) + options->ffmpeg = false; + + if (parser->isSet(forceOpenSslOption)) + options->forceOpenSslPlugin = true; + + if (parser->isSet(openSslRootOption)) + options->openSslRootDirectory = parser->value(openSslRootOption); + + if (options->forceOpenSslPlugin && !options->openSslRootDirectory.isEmpty()) { + *errorMessage = QStringLiteral("force-openssl and openssl-root are mutually exclusive"); + return CommandLineParseError; + } + + if (parser->isSet(forceOption)) + options->updateFileFlags |= ForceUpdateFile; + if (parser->isSet(dryRunOption)) { + options->dryRun = true; + options->updateFileFlags |= SkipUpdateFile; + } + + options->patchQt = !parser->isSet(noPatchQtOption); + options->ignoreLibraryErrors = parser->isSet(ignoreErrorOption); + if (insightTrackerModuleAvailable) + options->deployInsightTrackerPlugin = parser->isSet(deployInsightTrackerOption); + + for (const QtModule &module : qtModuleEntries) { + if (parser->isSet(*enabledModuleOptions.at(module.id))) + options->additionalLibraries[module.id] = 1; + if (parser->isSet(*disabledModuleOptions.at(module.id))) + options->disabledLibraries[module.id] = 1; + } + + // Add some dependencies + if (options->additionalLibraries.test(QtQuickModuleId)) + options->additionalLibraries[QtQmlModuleId] = 1; + if (options->additionalLibraries.test(QtDesignerComponentsModuleId)) + options->additionalLibraries[QtDesignerModuleId] = 1; + + if (parser->isSet(listOption)) { + const QString value = parser->value(listOption); + if (value == QStringLiteral("source")) { + options->list = ListSource; + } else if (value == QStringLiteral("target")) { + options->list = ListTarget; + } else if (value == QStringLiteral("relative")) { + options->list = ListRelative; + } else if (value == QStringLiteral("mapping")) { + options->list = ListMapping; + } else { + *errorMessage = QStringLiteral("Please specify a valid option for -list (source, target, relative, mapping)."); + return CommandLineParseError; + } + } + + if (parser->isSet(jsonOption) || options->list) { + optVerboseLevel = 0; + options->json = new JsonOutput; + } + + const QStringList posArgs = parser->positionalArguments(); + if (posArgs.isEmpty()) { + *errorMessage = QStringLiteral("Please specify the binary or folder."); + return CommandLineParseError | CommandLineParseHelpRequested; + } + + if (parser->isSet(dirOption)) + options->directory = parser->value(dirOption); + + if (parser->isSet(qmlDirOption)) + options->qmlDirectories = parser->values(qmlDirOption); + + if (parser->isSet(qmlImportOption)) + options->qmlImportPaths = parser->values(qmlImportOption); + + const QString &file = posArgs.front(); + const QFileInfo fi(QDir::cleanPath(file)); + if (!fi.exists()) { + *errorMessage = msgFileDoesNotExist(file); + return CommandLineParseError; + } + + if (!options->directory.isEmpty() && !fi.isFile()) { // -dir was specified - expecting file. + *errorMessage = u'"' + file + QStringLiteral("\" is not an executable file."); + return CommandLineParseError; + } + + if (fi.isFile()) { + options->binaries.append(fi.absoluteFilePath()); + if (options->directory.isEmpty()) + options->directory = fi.absolutePath(); + } else { + const QString binary = findBinary(fi.absoluteFilePath(), options->platform); + if (binary.isEmpty()) { + *errorMessage = QStringLiteral("Unable to find binary in \"") + file + u'"'; + return CommandLineParseError; + } + options->directory = fi.absoluteFilePath(); + options->binaries.append(binary); + } // directory. + + // Remaining files or plugin directories + bool multipleDirs = false; + for (int i = 1; i < posArgs.size(); ++i) { + const QFileInfo fi(QDir::cleanPath(posArgs.at(i))); + const QString path = fi.absoluteFilePath(); + if (!fi.exists()) { + *errorMessage = msgFileDoesNotExist(path); + return CommandLineParseError; + } + if (fi.isDir()) { + const QStringList libraries = + findSharedLibraries(QDir(path), options->platform, MatchDebugOrRelease, QString()); + for (const QString &library : libraries) + options->binaries.append(path + u'/' + library); + } else { + if (!parser->isSet(dirOption) && fi.absolutePath() != options->directory) + multipleDirs = true; + options->binaries.append(path); + } + } + if (multipleDirs) { + std::wcerr << "Warning: using binaries from different directories, deploying to following path: " + << options->directory << '\n' << "To disable this warning, use the --dir option\n"; + } + if (options->translationsDirectory.isEmpty()) + options->translationsDirectory = options->directory + "/translations"_L1; + return 0; +} + +// Simple line wrapping at 80 character boundaries. +static inline QString lineBreak(QString s) +{ + for (qsizetype i = 80; i < s.size(); i += 80) { + const qsizetype lastBlank = s.lastIndexOf(u' ', i); + if (lastBlank >= 0) { + s[lastBlank] = u'\n'; + i = lastBlank + 1; + } + } + return s; +} + +static inline QString helpText(const QCommandLineParser &p, const PluginInformation &pluginInfo) +{ + QString result = p.helpText(); + // Replace the default-generated text which is too long by a short summary + // explaining how to enable single libraries. + if (qtModuleEntries.size() == 0) + return result; + const QtModule &firstModule = qtModuleEntries.moduleById(0); + const QString firstModuleOption = moduleNameToOptionName(firstModule.name); + const qsizetype moduleStart = result.indexOf("\n --"_L1 + firstModuleOption); + const qsizetype argumentsStart = result.lastIndexOf("\nArguments:"_L1); + if (moduleStart >= argumentsStart) + return result; + QString moduleHelp; + moduleHelp += + "\n\nQt libraries can be added by passing their name (-xml) or removed by passing\n" + "the name prepended by --no- (--no-xml). Available libraries:\n"_L1; + ModuleBitset mask; + moduleHelp += lineBreak(QString::fromLatin1(formatQtModules(mask.set(), true))); + moduleHelp += u"\n\n"; + moduleHelp += + u"Qt plugins can be included or excluded individually or by type.\n" + u"To deploy or block plugins individually, use the --include-plugins\n" + u"and --exclude-plugins options (--include-plugins qjpeg,qsvgicon)\n" + u"You can also use the --skip-plugin-types or --add-plugin-types to\n" + u"achieve similar results with entire plugin groups, like imageformats, e.g.\n" + u"(--add-plugin-types imageformats,iconengines). Exclusion always takes\n" + u"precedence over inclusion, and types take precedence over specific plugins.\n" + u"For example, including qjpeg, but skipping imageformats, will NOT deploy qjpeg.\n" + u"\nDetected available plugins:\n"; + moduleHelp += formatQtPlugins(pluginInfo); + result.replace(moduleStart, argumentsStart - moduleStart, moduleHelp); + return result; +} + +static inline bool isQtModule(const QString &libName) +{ + // Match Standard modules named Qt6XX.dll + if (libName.size() < 3 || !libName.startsWith("Qt"_L1, Qt::CaseInsensitive)) + return false; + const QChar version = libName.at(2); + return version.isDigit() && (version.toLatin1() - '0') == QT_VERSION_MAJOR; +} + +// Helper for recursively finding all dependent Qt libraries. +static bool findDependentQtLibraries(const QString &qtBinDir, const QString &binary, Platform platform, + QString *errorMessage, QStringList *result, + unsigned *wordSize = nullptr, bool *isDebug = nullptr, + unsigned short *machineArch = nullptr, + int *directDependencyCount = nullptr, int recursionDepth = 0) +{ + QStringList dependentLibs; + if (directDependencyCount) + *directDependencyCount = 0; + if (!readPeExecutable(binary, errorMessage, &dependentLibs, wordSize, isDebug, + platform == WindowsDesktopMinGW, machineArch)) { + errorMessage->prepend("Unable to find dependent libraries of "_L1 + + QDir::toNativeSeparators(binary) + " :"_L1); + return false; + } + // Filter out the Qt libraries. Note that depends.exe finds libs from optDirectory if we + // are run the 2nd time (updating). We want to check against the Qt bin dir libraries + const int start = result->size(); + for (const QString &lib : std::as_const(dependentLibs)) { + if (isQtModule(lib)) { + const QString path = normalizeFileName(qtBinDir + u'/' + QFileInfo(lib).fileName()); + if (!result->contains(path)) + result->append(path); + } + } + const int end = result->size(); + if (directDependencyCount) + *directDependencyCount = end - start; + // Recurse + for (int i = start; i < end; ++i) + if (!findDependentQtLibraries(qtBinDir, result->at(i), platform, errorMessage, result, + nullptr, nullptr, nullptr, nullptr, recursionDepth + 1)) + return false; + return true; +} + +// Base class to filter debug/release Windows DLLs for functions to be passed to updateFile(). +// Tries to pre-filter by namefilter and does check via PE. +class DllDirectoryFileEntryFunction { +public: + explicit DllDirectoryFileEntryFunction(Platform platform, + DebugMatchMode debugMatchMode, const QString &prefix = QString()) : + m_platform(platform), m_debugMatchMode(debugMatchMode), m_prefix(prefix) {} + + QStringList operator()(const QDir &dir) const + { return findSharedLibraries(dir, m_platform, m_debugMatchMode, m_prefix); } + +private: + const Platform m_platform; + const DebugMatchMode m_debugMatchMode; + const QString m_prefix; +}; + +static QString pdbFileName(QString libraryFileName) +{ + const qsizetype lastDot = libraryFileName.lastIndexOf(u'.') + 1; + if (lastDot <= 0) + return QString(); + libraryFileName.replace(lastDot, libraryFileName.size() - lastDot, "pdb"_L1); + return libraryFileName; +} +static inline QStringList qmlCacheFileFilters() +{ + return QStringList() << QStringLiteral("*.jsc") << QStringLiteral("*.qmlc"); +} + +// File entry filter function for updateFile() that returns a list of files for +// QML import trees: DLLs (matching debug) and .qml/,js, etc. +class QmlDirectoryFileEntryFunction { +public: + enum Flags { + DeployPdb = 0x1, + SkipSources = 0x2 + }; + + explicit QmlDirectoryFileEntryFunction( + const QString &moduleSourcePath, Platform platform, DebugMatchMode debugMatchMode, unsigned flags) + : m_flags(flags), m_qmlNameFilter(QmlDirectoryFileEntryFunction::qmlNameFilters(flags)) + , m_dllFilter(platform, debugMatchMode), m_moduleSourcePath(moduleSourcePath) + {} + + QStringList operator()(const QDir &dir) const + { + if (moduleSourceDir(dir).canonicalPath() != m_moduleSourcePath) { + // If we're in a different module, return nothing. + return {}; + } + + QStringList result; + const QStringList &libraries = m_dllFilter(dir); + for (const QString &library : libraries) { + result.append(library); + if (m_flags & DeployPdb) { + const QString pdb = pdbFileName(library); + if (QFileInfo(dir.absoluteFilePath(pdb)).isFile()) + result.append(pdb); + } + } + result.append(m_qmlNameFilter(dir)); + return result; + } + +private: + static QDir moduleSourceDir(const QDir &dir) + { + QDir moduleSourceDir = dir; + while (!moduleSourceDir.exists(QStringLiteral("qmldir"))) { + if (!moduleSourceDir.cdUp()) { + return {}; + } + } + return moduleSourceDir; + } + + static inline QStringList qmlNameFilters(unsigned flags) + { + QStringList result; + result << QStringLiteral("qmldir") << QStringLiteral("*.qmltypes") + << QStringLiteral("*.frag") << QStringLiteral("*.vert") // Shaders + << QStringLiteral("*.ttf"); + if (!(flags & SkipSources)) { + result << QStringLiteral("*.js") << QStringLiteral("*.qml") << QStringLiteral("*.png"); + result.append(qmlCacheFileFilters()); + } + return result; + } + + const unsigned m_flags; + NameFilterFileEntryFunction m_qmlNameFilter; + DllDirectoryFileEntryFunction m_dllFilter; + QString m_moduleSourcePath; +}; + +static qint64 qtModule(QString module, const QString &infix) +{ + // Match needle 'path/Qt6Core<infix><d>.dll' or 'path/libQt6Core<infix>.so.5.0' + const qsizetype lastSlashPos = module.lastIndexOf(u'/'); + if (lastSlashPos > 0) + module.remove(0, lastSlashPos + 1); + if (module.startsWith("lib"_L1)) + module.remove(0, 3); + int endPos = infix.isEmpty() ? -1 : module.lastIndexOf(infix); + if (endPos == -1) + endPos = module.indexOf(u'.'); // strip suffixes '.so.5.0'. + if (endPos > 0) + module.truncate(endPos); + // That should leave us with 'Qt6Core<d>'. + for (const auto &qtModule : qtModuleEntries) { + const QString &libraryName = qtModule.name; + if (module == libraryName + || (module.size() == libraryName.size() + 1 && module.startsWith(libraryName))) { + return qtModule.id; + } + } + std::wcerr << "Warning: module " << qPrintable(module) << " could not be found\n"; + return -1; +} + +// Return the path if a plugin is to be deployed +static QString deployPlugin(const QString &plugin, const QDir &subDir, const bool dueToModule, + const DebugMatchMode &debugMatchMode, ModuleBitset *pluginNeededQtModules, + const ModuleBitset &disabledQtModules, + const PluginSelections &pluginSelections, const QString &libraryLocation, + const QString &infix, Platform platform, + bool deployInsightTrackerPlugin, bool deployOpenSslPlugin) +{ + const QString subDirName = subDir.dirName(); + // Filter out disabled plugins + if (optVerboseLevel && pluginSelections.disabledPluginTypes.contains(subDirName)) { + std::wcout << "Skipping plugin " << plugin << " due to skipped plugin type " << subDirName << '\n'; + return {}; + } + if (optVerboseLevel && subDirName == u"generic" && plugin.contains(u"qinsighttracker") + && !deployInsightTrackerPlugin) { + std::wcout << "Skipping plugin " << plugin + << ". Use -deploy-insighttracker if you want to use it.\n"; + return {}; + } + if (optVerboseLevel && subDirName == u"tls" && plugin.contains(u"qopensslbackend") + && !deployOpenSslPlugin) { + std::wcout << "Skipping plugin " << plugin + << ". Use -force_openssl or specify -openssl-root if you want to use it.\n"; + return {}; + } + + const int dotIndex = plugin.lastIndexOf(u'.'); + // Strip the .dll from the name, and an additional 'd' if it's a debug library with the 'd' + // suffix + const int stripIndex = debugMatchMode == MatchDebug && platformHasDebugSuffix(platform) + ? dotIndex - 1 + : dotIndex; + const QString pluginName = plugin.first(stripIndex); + + if (optVerboseLevel && pluginSelections.excludedPlugins.contains(pluginName)) { + std::wcout << "Skipping plugin " << plugin << " due to exclusion option" << '\n'; + return {}; + } + + // By default, only deploy qwindows.dll + if (subDirName == u"platforms" + && !(pluginSelections.includedPlugins.contains(pluginName) + || (pluginSelections.enabledPluginTypes.contains(subDirName))) + && !pluginName.startsWith(u"qwindows")) { + return {}; + } + + const QString pluginPath = subDir.absoluteFilePath(plugin); + + // If dueToModule is false, check if the user included the plugin or the entire type. In the + // former's case, only deploy said plugin and not all plugins of that type. + const bool requiresPlugin = pluginSelections.includedPlugins.contains(pluginName) + || pluginSelections.enabledPluginTypes.contains(subDirName); + if (!dueToModule && !requiresPlugin) + return {}; + + // Deploy QUiTools plugins as is without further dependency checking. + // The user needs to ensure all required libraries are present (would + // otherwise pull QtWebEngine for its plugin). + if (subDirName == u"designer") + return pluginPath; + + QStringList dependentQtLibs; + QString errorMessage; + if (findDependentQtLibraries(libraryLocation, pluginPath, platform, + &errorMessage, &dependentQtLibs)) { + for (int d = 0; d < dependentQtLibs.size(); ++d) { + const qint64 module = qtModule(dependentQtLibs.at(d), infix); + if (module >= 0) + (*pluginNeededQtModules)[module] = 1; + } + } else { + std::wcerr << "Warning: Cannot determine dependencies of " + << QDir::toNativeSeparators(pluginPath) << ": " << errorMessage << '\n'; + } + + ModuleBitset disabledNeededQtModules; + disabledNeededQtModules = *pluginNeededQtModules & disabledQtModules; + if (disabledNeededQtModules.any()) { + if (optVerboseLevel) { + std::wcout << "Skipping plugin " << plugin + << " due to disabled dependencies (" + << formatQtModules(disabledNeededQtModules).constData() << ").\n"; + } + return {}; + } + + return pluginPath; +} + +static bool needsPluginType(const QString &subDirName, const PluginInformation &pluginInfo, + const PluginSelections &pluginSelections) +{ + bool needsTypeForPlugin = false; + for (const QString &plugin: pluginSelections.includedPlugins) { + if (pluginInfo.isTypeForPlugin(subDirName, plugin)) + needsTypeForPlugin = true; + } + return (pluginSelections.enabledPluginTypes.contains(subDirName) || needsTypeForPlugin); +} + +QStringList findQtPlugins(ModuleBitset *usedQtModules, const ModuleBitset &disabledQtModules, + const PluginInformation &pluginInfo, const PluginSelections &pluginSelections, + const QString &qtPluginsDirName, const QString &libraryLocation, + const QString &infix, DebugMatchMode debugMatchModeIn, Platform platform, + QString *platformPlugin, bool deployInsightTrackerPlugin, + bool deployOpenSslPlugin) +{ + if (qtPluginsDirName.isEmpty()) + return QStringList(); + QDir pluginsDir(qtPluginsDirName); + QStringList result; + bool missingQtModulesAdded = false; + const QFileInfoList &pluginDirs = pluginsDir.entryInfoList(QStringList(u"*"_s), QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &subDirFi : pluginDirs) { + const QString subDirName = subDirFi.fileName(); + const size_t module = qtModuleEntries.moduleIdForPluginType(subDirName); + if (module == QtModule::InvalidId) { + if (optVerboseLevel > 1) { + std::wcerr << "No Qt module found for plugin type \"" << subDirName << "\".\n"; + } + continue; + } + const bool dueToModule = usedQtModules->test(module); + if (dueToModule || needsPluginType(subDirName, pluginInfo, pluginSelections)) { + const DebugMatchMode debugMatchMode = (module == QtWebEngineCoreModuleId) + ? MatchDebugOrRelease // QTBUG-44331: Debug detection does not work for webengine, deploy all. + : debugMatchModeIn; + QDir subDir(subDirFi.absoluteFilePath()); + if (optVerboseLevel) + std::wcout << "Adding in plugin type " << subDirFi.baseName() << " for module: " << qtModuleEntries.moduleById(module).name << '\n'; + + const bool isPlatformPlugin = subDirName == "platforms"_L1; + const QStringList plugins = + findSharedLibraries(subDir, platform, debugMatchMode, QString()); + for (const QString &plugin : plugins) { + ModuleBitset pluginNeededQtModules; + const QString pluginPath = + deployPlugin(plugin, subDir, dueToModule, debugMatchMode, &pluginNeededQtModules, + disabledQtModules, pluginSelections, libraryLocation, infix, + platform, deployInsightTrackerPlugin, deployOpenSslPlugin); + if (!pluginPath.isEmpty()) { + if (isPlatformPlugin && plugin.startsWith(u"qwindows")) + *platformPlugin = subDir.absoluteFilePath(plugin); + result.append(pluginPath); + + const ModuleBitset missingModules = (pluginNeededQtModules & ~*usedQtModules); + if (missingModules.any()) { + *usedQtModules |= missingModules; + missingQtModulesAdded = true; + if (optVerboseLevel) { + std::wcout << "Adding " << formatQtModules(missingModules).constData() + << " for " << plugin << " from plugin type: " << subDirName << '\n'; + } + } + } + } // for filter + } // type matches + } // for plugin folder + + // If missing Qt modules were added during plugin deployment make additional pass, because we may need + // additional plugins. + if (pluginSelections.includeSoftPlugins && missingQtModulesAdded) { + if (optVerboseLevel) { + std::wcout << "Performing additional pass of finding Qt plugins due to updated Qt module list: " + << formatQtModules(*usedQtModules).constData() << "\n"; + } + return findQtPlugins(usedQtModules, disabledQtModules, pluginInfo, pluginSelections, qtPluginsDirName, + libraryLocation, infix, debugMatchModeIn, platform, platformPlugin, + deployInsightTrackerPlugin, deployOpenSslPlugin); + } + + return result; +} + +static QStringList translationNameFilters(const ModuleBitset &modules, const QString &prefix) +{ + QStringList result; + for (const auto &qtModule : qtModuleEntries) { + if (modules.test(qtModule.id) && !qtModule.translationCatalog.isEmpty()) { + const QString name = qtModule.translationCatalog + u'_' + prefix + ".qm"_L1; + if (!result.contains(name)) + result.push_back(name); + } + } + return result; +} + +static bool deployTranslations(const QString &sourcePath, const ModuleBitset &usedQtModules, + const QString &target, const Options &options, + QString *errorMessage) +{ + // Find available languages prefixes by checking on qtbase. + QStringList prefixes; + QDir sourceDir(sourcePath); + const QStringList qmFilter = QStringList(QStringLiteral("qtbase_*.qm")); + const QFileInfoList &qmFiles = sourceDir.entryInfoList(qmFilter); + for (const QFileInfo &qmFi : qmFiles) { + const QString prefix = qmFi.baseName().mid(7); + if (options.languages.isEmpty() || options.languages.contains(prefix)) + prefixes.append(prefix); + } + if (prefixes.isEmpty()) { + std::wcerr << "Warning: Could not find any translations in " + << QDir::toNativeSeparators(sourcePath) << " (developer build?)\n."; + return true; + } + // Run lconvert to concatenate all files into a single named "qt_<prefix>.qm" in the application folder + // Use QT_INSTALL_TRANSLATIONS as working directory to keep the command line short. + const QString absoluteTarget = QFileInfo(target).absoluteFilePath(); + const QString binary = QStringLiteral("lconvert"); + QStringList arguments; + for (const QString &prefix : std::as_const(prefixes)) { + arguments.clear(); + const QString targetFile = QStringLiteral("qt_") + prefix + QStringLiteral(".qm"); + arguments.append(QStringLiteral("-o")); + const QString targetFilePath = absoluteTarget + u'/' + targetFile; + if (options.json) + options.json->addFile(sourcePath + u'/' + targetFile, absoluteTarget); + arguments.append(QDir::toNativeSeparators(targetFilePath)); + const QStringList translationFilters = translationNameFilters(usedQtModules, prefix); + if (translationFilters.isEmpty()){ + std::wcerr << "Warning: translation catalogs are all empty, skipping translation deployment\n"; + return true; + } + const QFileInfoList &langQmFiles = sourceDir.entryInfoList(translationFilters); + for (const QFileInfo &langQmFileFi : langQmFiles) { + if (options.json) { + options.json->addFile(langQmFileFi.absoluteFilePath(), + absoluteTarget); + } + arguments.append(langQmFileFi.fileName()); + } + if (optVerboseLevel) + std::wcout << "Creating " << targetFile << "...\n"; + unsigned long exitCode; + if ((options.updateFileFlags & SkipUpdateFile) == 0 + && (!runProcess(binary, arguments, sourcePath, &exitCode, nullptr, nullptr, errorMessage) + || exitCode)) { + return false; + } + } // for prefixes. + return true; +} + +static QStringList findFFmpegLibs(const QString &qtBinDir, Platform platform) +{ + const std::vector<QLatin1StringView> ffmpegHints = { "avcodec"_L1, "avformat"_L1, "avutil"_L1, + "swresample"_L1, "swscale"_L1 }; + const QStringList bundledLibs = + findSharedLibraries(qtBinDir, platform, MatchDebugOrRelease, {}); + + QStringList ffmpegLibs; + for (const QLatin1StringView &libHint : ffmpegHints) { + const QStringList ffmpegLib = bundledLibs.filter(libHint, Qt::CaseInsensitive); + + if (ffmpegLib.empty()) { + std::wcerr << "Warning: Cannot find FFmpeg libraries. Multimedia features will not work as expected.\n"; + return {}; + } else if (ffmpegLib.size() != 1u) { + std::wcerr << "Warning: Multiple versions of FFmpeg libraries found. Multimedia features will not work as expected.\n"; + return {}; + } + + const QChar slash(u'/'); + QFileInfo ffmpegLibPath{ qtBinDir + slash + ffmpegLib.front() }; + ffmpegLibs.append(ffmpegLibPath.absoluteFilePath()); + } + + return ffmpegLibs; +} + +// Find the openssl libraries Qt executables depend on. +static QStringList findOpenSslLibraries(const QString &openSslRootDir, Platform platform) +{ + const std::vector<QLatin1StringView> libHints = { "libcrypto"_L1, "libssl"_L1 }; + const QChar slash(u'/'); + const QString openSslBinDir = openSslRootDir + slash + "bin"_L1; + const QStringList openSslRootLibs = + findSharedLibraries(openSslBinDir, platform, MatchDebugOrRelease, {}); + + QStringList result; + for (const QLatin1StringView &libHint : libHints) { + const QStringList lib = openSslRootLibs.filter(libHint, Qt::CaseInsensitive); + + if (lib.empty()) { + std::wcerr << "Warning: Cannot find openssl libraries.\n"; + return {}; + } else if (lib.size() != 1u) { + std::wcerr << "Warning: Multiple versions of openssl libraries found.\n"; + return {}; + } + + QFileInfo libPath{ openSslBinDir + slash + lib.front() }; + result.append(libPath.absoluteFilePath()); + } + + return result; +} + + +struct DeployResult +{ + operator bool() const { return success; } + + bool success = false; + bool isDebug = false; + ModuleBitset directlyUsedQtLibraries; + ModuleBitset usedQtLibraries; + ModuleBitset deployedQtLibraries; +}; + +static QString libraryPath(const QString &libraryLocation, const char *name, + const QString &infix, Platform platform, bool debug) +{ + QString result = libraryLocation + u'/'; + result += QLatin1StringView(name); + result += infix; + if (debug && platformHasDebugSuffix(platform)) + result += u'd'; + result += sharedLibrarySuffix(); + return result; +} + +static QString vcDebugRedistDir() { return QStringLiteral("Debug_NonRedist"); } + +static QString vcRedistDir() +{ + const char vcDirVar[] = "VCINSTALLDIR"; + const QChar slash(u'/'); + QString vcRedistDirName = QDir::cleanPath(QFile::decodeName(qgetenv(vcDirVar))); + if (vcRedistDirName.isEmpty()) { + std::wcerr << "Warning: Cannot find Visual Studio installation directory, " << vcDirVar + << " is not set.\n"; + return QString(); + } + if (!vcRedistDirName.endsWith(slash)) + vcRedistDirName.append(slash); + vcRedistDirName.append(QStringLiteral("redist/MSVC")); + if (!QFileInfo(vcRedistDirName).isDir()) { + std::wcerr << "Warning: Cannot find Visual Studio redist directory, " + << QDir::toNativeSeparators(vcRedistDirName).toStdWString() << ".\n"; + return QString(); + } + // Look in reverse order for folder containing the debug redist folder + const QFileInfoList subDirs = + QDir(vcRedistDirName) + .entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name | QDir::Reversed); + for (const QFileInfo &f : subDirs) { + QString path = f.absoluteFilePath(); + if (QFileInfo(path + slash + vcDebugRedistDir()).isDir()) + return path; + path += QStringLiteral("/onecore"); + if (QFileInfo(path + slash + vcDebugRedistDir()).isDir()) + return path; + } + std::wcerr << "Warning: Cannot find Visual Studio redist directory under " + << QDir::toNativeSeparators(vcRedistDirName).toStdWString() << ".\n"; + return QString(); +} + +static QStringList findMinGWRuntimePaths(const QString &qtBinDir, Platform platform, const QStringList &runtimeFilters) +{ + //MinGW: Add runtime libraries. Check first for the Qt binary directory, and default to path if nothing is found. + QStringList result; + const bool isClang = platform == WindowsDesktopClangMinGW; + QStringList filters; + const QString suffix = u'*' + sharedLibrarySuffix(); + for (const auto &minGWRuntime : runtimeFilters) + filters.append(minGWRuntime + suffix); + + QFileInfoList dlls = QDir(qtBinDir).entryInfoList(filters, QDir::Files); + if (dlls.isEmpty()) { + std::wcerr << "Warning: Runtime libraries not found in Qt binary folder, defaulting to looking in path\n"; + const QString binaryPath = isClang ? findInPath("clang++.exe"_L1) : findInPath("g++.exe"_L1); + if (binaryPath.isEmpty()) { + std::wcerr << "Warning: Cannot find " << (isClang ? "Clang" : "GCC") << " installation directory, " << (isClang ? "clang++" : "g++") << ".exe must be in the path\n"; + return {}; + } + const QString binaryFolder = QFileInfo(binaryPath).absolutePath(); + dlls = QDir(binaryFolder).entryInfoList(filters, QDir::Files); + } + + for (const QFileInfo &dllFi : dlls) + result.append(dllFi.absoluteFilePath()); + + return result; +} + +static QStringList compilerRunTimeLibs(const QString &qtBinDir, Platform platform, bool isDebug, unsigned short machineArch) +{ + QStringList result; + switch (platform) { + case WindowsDesktopMinGW: { + const QStringList minGWRuntimes = { "*gcc_"_L1, "*stdc++"_L1, "*winpthread"_L1 }; + result.append(findMinGWRuntimePaths(qtBinDir, platform, minGWRuntimes)); + break; + } + case WindowsDesktopClangMinGW: { + const QStringList clangMinGWRuntimes = { "*unwind"_L1, "*c++"_L1 }; + result.append(findMinGWRuntimePaths(qtBinDir, platform, clangMinGWRuntimes)); + break; + } +#ifdef Q_OS_WIN + case WindowsDesktopMsvcIntel: + case WindowsDesktopMsvcArm: { // MSVC/Desktop: Add redistributable packages. + QString vcRedistDirName = vcRedistDir(); + if (vcRedistDirName.isEmpty()) + break; + QStringList redistFiles; + QDir vcRedistDir(vcRedistDirName); + const QString machineArchString = getArchString(machineArch); + if (isDebug) { + // Append DLLs from Debug_NonRedist\x??\Microsoft.VC<version>.DebugCRT. + if (vcRedistDir.cd(vcDebugRedistDir()) && vcRedistDir.cd(machineArchString)) { + const QStringList names = vcRedistDir.entryList(QStringList(QStringLiteral("Microsoft.VC*.DebugCRT")), QDir::Dirs); + if (!names.isEmpty() && vcRedistDir.cd(names.first())) { + const QFileInfoList &dlls = vcRedistDir.entryInfoList(QStringList("*.dll"_L1)); + for (const QFileInfo &dll : dlls) + redistFiles.append(dll.absoluteFilePath()); + } + } + } else { // release: Bundle vcredist<>.exe + QString releaseRedistDir = vcRedistDirName; + const QStringList countryCodes = vcRedistDir.entryList(QStringList(QStringLiteral("[0-9]*")), QDir::Dirs); + if (!countryCodes.isEmpty()) // Pre MSVC2017 + releaseRedistDir += u'/' + countryCodes.constFirst(); + QFileInfo fi(releaseRedistDir + "/vc_redist."_L1 + + machineArchString + ".exe"_L1); + if (!fi.isFile()) { // Pre MSVC2017/15.5 + fi.setFile(releaseRedistDir + "/vcredist_"_L1 + + machineArchString + ".exe"_L1); + } + if (fi.isFile()) + redistFiles.append(fi.absoluteFilePath()); + } + if (redistFiles.isEmpty()) { + std::wcerr << "Warning: Cannot find Visual Studio " << (isDebug ? "debug" : "release") + << " redistributable files in " << QDir::toNativeSeparators(vcRedistDirName).toStdWString() << ".\n"; + break; + } + result.append(redistFiles); + } + break; +#endif // Q_OS_WIN + default: + break; + } + return result; +} + +static inline int qtVersion(const QMap<QString, QString> &qtpathsVariables) +{ + const QString versionString = qtpathsVariables.value(QStringLiteral("QT_VERSION")); + const QChar dot = u'.'; + const int majorVersion = versionString.section(dot, 0, 0).toInt(); + const int minorVersion = versionString.section(dot, 1, 1).toInt(); + const int patchVersion = versionString.section(dot, 2, 2).toInt(); + return (majorVersion << 16) | (minorVersion << 8) | patchVersion; +} + +// Deploy a library along with its .pdb debug info file (MSVC) should it exist. +static bool updateLibrary(const QString &sourceFileName, const QString &targetDirectory, + const Options &options, QString *errorMessage) +{ + if (!updateFile(sourceFileName, targetDirectory, options.updateFileFlags, options.json, errorMessage)) { + if (options.ignoreLibraryErrors) { + std::wcerr << "Warning: Could not update " << sourceFileName << " :" << *errorMessage << '\n'; + errorMessage->clear(); + return true; + } + return false; + } + + if (options.deployPdb) { + const QFileInfo pdb(pdbFileName(sourceFileName)); + if (pdb.isFile()) + return updateFile(pdb.absoluteFilePath(), targetDirectory, options.updateFileFlags, nullptr, errorMessage); + } + return true; +} + +// Find out the ICU version to add the data library icudtXX.dll, which does not +// show as a dependency. +static QString getIcuVersion(const QString &libName) +{ + QString version; + std::copy_if(libName.cbegin(), libName.cend(), std::back_inserter(version), + [](QChar c) { return c.isDigit(); }); + return version; +} + +static DeployResult deploy(const Options &options, const QMap<QString, QString> &qtpathsVariables, + const PluginInformation &pluginInfo, QString *errorMessage) +{ + DeployResult result; + + const QChar slash = u'/'; + + const QString qtBinDir = qtpathsVariables.value(QStringLiteral("QT_INSTALL_BINS")); + const QString libraryLocation = qtBinDir; + const QString infix = qtpathsVariables.value(QLatin1StringView(qmakeInfixKey)); + const int version = qtVersion(qtpathsVariables); + Q_UNUSED(version); + + if (optVerboseLevel > 1) + std::wcout << "Qt binaries in " << QDir::toNativeSeparators(qtBinDir) << '\n'; + + QStringList dependentQtLibs; + bool detectedDebug; + unsigned wordSize; + unsigned short machineArch; + int directDependencyCount = 0; + if (!findDependentQtLibraries(libraryLocation, options.binaries.first(), options.platform, errorMessage, &dependentQtLibs, &wordSize, + &detectedDebug, &machineArch, &directDependencyCount)) { + return result; + } + for (int b = 1; b < options.binaries.size(); ++b) { + if (!findDependentQtLibraries(libraryLocation, options.binaries.at(b), options.platform, errorMessage, &dependentQtLibs, + nullptr, nullptr, nullptr)) { + return result; + } + } + + DebugMatchMode debugMatchMode = MatchDebugOrRelease; + result.isDebug = false; + switch (options.debugDetection) { + case Options::DebugDetectionAuto: + // Debug detection is only relevant for Msvc/ClangMsvc which have distinct + // runtimes and binaries. For anything else, use MatchDebugOrRelease + // since also debug cannot be reliably detect for MinGW. + if (options.platform.testFlag(Msvc) || options.platform.testFlag(ClangMsvc)) { + result.isDebug = detectedDebug; + debugMatchMode = result.isDebug ? MatchDebug : MatchRelease; + } + break; + case Options::DebugDetectionForceDebug: + result.isDebug = true; + debugMatchMode = MatchDebug; + break; + case Options::DebugDetectionForceRelease: + debugMatchMode = MatchRelease; + break; + } + + // Determine application type, check Quick2 is used by looking at the + // direct dependencies (do not be fooled by QtWebKit depending on it). + for (int m = 0; m < dependentQtLibs.size(); ++m) { + const qint64 module = qtModule(dependentQtLibs.at(m), infix); + if (module >= 0) + result.directlyUsedQtLibraries[module] = 1; + } + + const bool usesQml = result.directlyUsedQtLibraries.test(QtQmlModuleId); + const bool usesQuick = result.directlyUsedQtLibraries.test(QtQuickModuleId); + const bool uses3DQuick = result.directlyUsedQtLibraries.test(Qt3DQuickModuleId); + const bool usesQml2 = !(options.disabledLibraries.test(QtQmlModuleId)) + && (usesQml || usesQuick || uses3DQuick || (options.additionalLibraries.test(QtQmlModuleId))); + + if (optVerboseLevel) { + std::wcout << QDir::toNativeSeparators(options.binaries.first()) << ' ' + << wordSize << " bit, " << (result.isDebug ? "debug" : "release") + << " executable"; + if (usesQml2) + std::wcout << " [QML]"; + std::wcout << '\n'; + } + + if (dependentQtLibs.isEmpty()) { + *errorMessage = QDir::toNativeSeparators(options.binaries.first()) + QStringLiteral(" does not seem to be a Qt executable."); + return result; + } + + // Some Windows-specific checks: Qt5Core depends on ICU when configured with "-icu". Other than + // that, Qt5WebKit has a hard dependency on ICU. + if (options.platform.testFlag(WindowsBased)) { + const QStringList qtLibs = dependentQtLibs.filter(QStringLiteral("Qt6Core"), Qt::CaseInsensitive) + + dependentQtLibs.filter(QStringLiteral("Qt5WebKit"), Qt::CaseInsensitive); + for (const QString &qtLib : qtLibs) { + QStringList icuLibs = findDependentLibraries(qtLib, errorMessage).filter(QStringLiteral("ICU"), Qt::CaseInsensitive); + if (!icuLibs.isEmpty()) { + // Find out the ICU version to add the data library icudtXX.dll, which does not show + // as a dependency. + const QString icuVersion = getIcuVersion(icuLibs.constFirst()); + if (!icuVersion.isEmpty()) { + if (optVerboseLevel > 1) + std::wcout << "Adding ICU version " << icuVersion << '\n'; + QString icuLib = QStringLiteral("icudt") + icuVersion + + QLatin1StringView(windowsSharedLibrarySuffix); + // Some packages contain debug dlls of ICU libraries even though it's a C + // library and the official packages do not differentiate (QTBUG-87677) + if (result.isDebug) { + const QString icuLibCandidate = QStringLiteral("icudtd") + icuVersion + + QLatin1StringView(windowsSharedLibrarySuffix); + if (!findInPath(icuLibCandidate).isEmpty()) { + icuLib = icuLibCandidate; + } + } + icuLibs.push_back(icuLib); + } + for (const QString &icuLib : std::as_const(icuLibs)) { + const QString icuPath = findInPath(icuLib); + if (icuPath.isEmpty()) { + *errorMessage = QStringLiteral("Unable to locate ICU library ") + icuLib; + return result; + } + dependentQtLibs.push_back(icuPath); + } // for each icuLib + break; + } // !icuLibs.isEmpty() + } // Qt6Core/Qt6WebKit + } // Windows + + // Scan Quick2 imports + QmlImportScanResult qmlScanResult; + if (options.quickImports && usesQml2) { + // Custom list of import paths provided by user + QStringList qmlImportPaths = options.qmlImportPaths; + // Qt's own QML modules + qmlImportPaths << qtpathsVariables.value(QStringLiteral("QT_INSTALL_QML")); + QStringList qmlDirectories = options.qmlDirectories; + if (qmlDirectories.isEmpty()) { + const QString qmlDirectory = findQmlDirectory(options.platform, options.directory); + if (!qmlDirectory.isEmpty()) + qmlDirectories.append(qmlDirectory); + } + for (const QString &qmlDirectory : std::as_const(qmlDirectories)) { + if (optVerboseLevel >= 1) + std::wcout << "Scanning " << QDir::toNativeSeparators(qmlDirectory) << ":\n"; + const QmlImportScanResult scanResult = + runQmlImportScanner(qmlDirectory, qmlImportPaths, + result.directlyUsedQtLibraries.test(QtWidgetsModuleId), + options.platform, debugMatchMode, errorMessage); + if (!scanResult.ok) + return result; + qmlScanResult.append(scanResult); + // Additional dependencies of QML plugins. + for (const QString &plugin : std::as_const(qmlScanResult.plugins)) { + if (!findDependentQtLibraries(libraryLocation, plugin, options.platform, errorMessage, &dependentQtLibs, &wordSize, &detectedDebug, &machineArch)) + return result; + } + if (optVerboseLevel >= 1) { + std::wcout << "QML imports:\n"; + for (const QmlImportScanResult::Module &mod : std::as_const(qmlScanResult.modules)) { + std::wcout << " '" << mod.name << "' " + << QDir::toNativeSeparators(mod.sourcePath) << '\n'; + } + if (optVerboseLevel >= 2) { + std::wcout << "QML plugins:\n"; + for (const QString &p : std::as_const(qmlScanResult.plugins)) + std::wcout << " " << QDir::toNativeSeparators(p) << '\n'; + } + } + } + } + + QString platformPlugin; + // Sort apart Qt 5 libraries in the ones that are represented by the + // QtModule enumeration (and thus controlled by flags) and others. + QStringList deployedQtLibraries; + for (int i = 0 ; i < dependentQtLibs.size(); ++i) { + const qint64 module = qtModule(dependentQtLibs.at(i), infix); + if (module >= 0) + result.usedQtLibraries[module] = 1; + else + deployedQtLibraries.push_back(dependentQtLibs.at(i)); // Not represented by flag. + } + result.deployedQtLibraries = (result.usedQtLibraries | options.additionalLibraries) & ~options.disabledLibraries; + + ModuleBitset disabled = options.disabledLibraries; + if (!usesQml2) { + disabled[QtQmlModuleId] = 1; + disabled[QtQuickModuleId] = 1; + } + + QStringList openSslLibs; + if (!options.openSslRootDirectory.isEmpty()) { + openSslLibs = findOpenSslLibraries(options.openSslRootDirectory, options.platform); + if (openSslLibs.isEmpty()) { + *errorMessage = QStringLiteral("Unable to find openSSL libraries in ") + + options.openSslRootDirectory; + return result; + } + + deployedQtLibraries.append(openSslLibs); + } + const bool deployOpenSslPlugin = options.forceOpenSslPlugin || !openSslLibs.isEmpty(); + + const QStringList plugins = findQtPlugins( + &result.deployedQtLibraries, + // For non-QML applications, disable QML to prevent it from being pulled in by the + // qtaccessiblequick plugin. + disabled, pluginInfo, + options.pluginSelections, qtpathsVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")), + libraryLocation, infix, debugMatchMode, options.platform, &platformPlugin, + options.deployInsightTrackerPlugin, deployOpenSslPlugin); + + // Apply options flags and re-add library names. + QString qtGuiLibrary; + for (const auto &qtModule : qtModuleEntries) { + if (result.deployedQtLibraries.test(qtModule.id)) { + const QString library = libraryPath(libraryLocation, qtModule.name.toUtf8(), infix, + options.platform, result.isDebug); + deployedQtLibraries.append(library); + if (qtModule.id == QtGuiModuleId) + qtGuiLibrary = library; + } + } + + if (optVerboseLevel >= 1) { + std::wcout << "Direct dependencies: " << formatQtModules(result.directlyUsedQtLibraries).constData() + << "\nAll dependencies : " << formatQtModules(result.usedQtLibraries).constData() + << "\nTo be deployed : " << formatQtModules(result.deployedQtLibraries).constData() << '\n'; + } + + if (optVerboseLevel > 1) + std::wcout << "Plugins: " << plugins.join(u',') << '\n'; + + if (result.deployedQtLibraries.test(QtGuiModuleId) && platformPlugin.isEmpty()) { + *errorMessage =QStringLiteral("Unable to find the platform plugin."); + return result; + } + + if (options.platform.testFlag(WindowsBased) && !qtGuiLibrary.isEmpty()) { + const QStringList guiLibraries = findDependentLibraries(qtGuiLibrary, errorMessage); + const bool dependsOnOpenGl = !guiLibraries.filter(QStringLiteral("opengl32"), Qt::CaseInsensitive).isEmpty(); + if (options.softwareRasterizer && !dependsOnOpenGl) { + const QFileInfo softwareRasterizer(qtBinDir + slash + QStringLiteral("opengl32sw") + QLatin1StringView(windowsSharedLibrarySuffix)); + if (softwareRasterizer.isFile()) + deployedQtLibraries.append(softwareRasterizer.absoluteFilePath()); + } + if (options.systemD3dCompiler && machineArch != IMAGE_FILE_MACHINE_ARM64) { + const QString d3dCompiler = findD3dCompiler(options.platform, qtBinDir, wordSize); + if (d3dCompiler.isEmpty()) { + std::wcerr << "Warning: Cannot find any version of the d3dcompiler DLL.\n"; + } else { + deployedQtLibraries.push_back(d3dCompiler); + } + } + if (options.systemDxc) { + const QStringList dxcLibs = findDxc(options.platform, qtBinDir, wordSize); + if (!dxcLibs.isEmpty()) + deployedQtLibraries.append(dxcLibs); + else + std::wcerr << "Warning: Cannot find any version of the dxcompiler.dll and dxil.dll.\n"; + } + } // Windows + + // Add FFmpeg if we deploy the FFmpeg backend + if (options.ffmpeg + && !plugins.filter(QStringLiteral("ffmpegmediaplugin"), Qt::CaseInsensitive).empty()) { + deployedQtLibraries.append(findFFmpegLibs(qtBinDir, options.platform)); + } + + // Update libraries + if (options.libraries) { + const QString targetPath = options.libraryDirectory.isEmpty() ? + options.directory : options.libraryDirectory; + QStringList libraries = deployedQtLibraries; + if (options.compilerRunTime) + libraries.append(compilerRunTimeLibs(qtBinDir, options.platform, result.isDebug, machineArch)); + for (const QString &qtLib : std::as_const(libraries)) { + if (!updateLibrary(qtLib, targetPath, options, errorMessage)) + return result; + } + +#if !QT_CONFIG(relocatable) + if (options.patchQt && !options.dryRun) { + const QString qt6CoreName = QFileInfo(libraryPath(libraryLocation, "Qt6Core", infix, + options.platform, result.isDebug)).fileName(); + if (!patchQtCore(targetPath + u'/' + qt6CoreName, errorMessage)) { + std::wcerr << "Warning: " << *errorMessage << '\n'; + errorMessage->clear(); + } + } +#endif // QT_CONFIG(relocatable) + } // optLibraries + + // Update plugins + if (options.plugins) { + const QString targetPath = options.pluginDirectory.isEmpty() ? + options.directory : options.pluginDirectory; + QDir dir(targetPath); + if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) { + *errorMessage = "Cannot create "_L1 + + QDir::toNativeSeparators(dir.absolutePath()) + u'.'; + return result; + } + for (const QString &plugin : plugins) { + const QString targetDirName = plugin.section(slash, -2, -2); + const QString targetPath = dir.absoluteFilePath(targetDirName); + if (!dir.exists(targetDirName)) { + if (optVerboseLevel) + std::wcout << "Creating directory " << targetPath << ".\n"; + if (!(options.updateFileFlags & SkipUpdateFile) && !dir.mkdir(targetDirName)) { + *errorMessage = QStringLiteral("Cannot create ") + targetDirName + u'.'; + return result; + } + } + if (!updateLibrary(plugin, targetPath, options, errorMessage)) + return result; + } + } // optPlugins + + // Update Quick imports + // Do not be fooled by QtWebKit.dll depending on Quick into always installing Quick imports + // for WebKit1-applications. Check direct dependency only. + if (options.quickImports && usesQml2) { + const QString targetPath = options.qmlDirectory.isEmpty() + ? options.directory + QStringLiteral("/qml") + : options.qmlDirectory; + if (!createDirectory(targetPath, errorMessage, options.dryRun)) + return result; + for (const QmlImportScanResult::Module &module : std::as_const(qmlScanResult.modules)) { + const QString installPath = module.installPath(targetPath); + if (optVerboseLevel > 1) + std::wcout << "Installing: '" << module.name + << "' from " << module.sourcePath << " to " + << QDir::toNativeSeparators(installPath) << '\n'; + if (installPath != targetPath && !createDirectory(installPath, errorMessage, options.dryRun)) + return result; + unsigned updateFileFlags = options.updateFileFlags + | SkipQmlDesignerSpecificsDirectories; + unsigned qmlDirectoryFileFlags = 0; + if (options.deployPdb) + qmlDirectoryFileFlags |= QmlDirectoryFileEntryFunction::DeployPdb; + if (!updateFile(module.sourcePath, QmlDirectoryFileEntryFunction(module.sourcePath, + options.platform, + debugMatchMode, + qmlDirectoryFileFlags), + installPath, updateFileFlags, options.json, errorMessage)) { + return result; + } + } + } // optQuickImports + + if (options.translations) { + if (!createDirectory(options.translationsDirectory, errorMessage, options.dryRun)) + return result; + if (!deployTranslations(qtpathsVariables.value(QStringLiteral("QT_INSTALL_TRANSLATIONS")), + result.deployedQtLibraries, options.translationsDirectory, options, + errorMessage)) { + return result; + } + } + + result.success = true; + return result; +} + +static bool deployWebProcess(const QMap<QString, QString> &qtpathsVariables, const char *binaryName, + const PluginInformation &pluginInfo, const Options &sourceOptions, + QString *errorMessage) +{ + // Copy the web process and its dependencies + const QString webProcess = webProcessBinary(binaryName, sourceOptions.platform); + const QString webProcessSource = qtpathsVariables.value(QStringLiteral("QT_INSTALL_LIBEXECS")) + + u'/' + webProcess; + if (!updateFile(webProcessSource, sourceOptions.directory, sourceOptions.updateFileFlags, sourceOptions.json, errorMessage)) + return false; + Options options(sourceOptions); + options.binaries.append(options.directory + u'/' + webProcess); + options.quickImports = false; + options.translations = false; + return deploy(options, qtpathsVariables, pluginInfo, errorMessage); +} + +static bool deployWebEngineCore(const QMap<QString, QString> &qtpathsVariables, + const PluginInformation &pluginInfo, const Options &options, + bool isDebug, QString *errorMessage) +{ + static const char *installDataFiles[] = { "icudtl.dat", + "qtwebengine_devtools_resources.pak", + "qtwebengine_resources.pak", + "qtwebengine_resources_100p.pak", + "qtwebengine_resources_200p.pak", + isDebug ? "v8_context_snapshot.debug.bin" + : "v8_context_snapshot.bin" }; + QByteArray webEngineProcessName(webEngineProcessC); + if (isDebug && platformHasDebugSuffix(options.platform)) + webEngineProcessName.append('d'); + if (optVerboseLevel) + std::wcout << "Deploying: " << webEngineProcessName.constData() << "...\n"; + if (!deployWebProcess(qtpathsVariables, webEngineProcessName, pluginInfo, options, errorMessage)) + return false; + const QString resourcesSubDir = QStringLiteral("/resources"); + const QString resourcesSourceDir = qtpathsVariables.value(QStringLiteral("QT_INSTALL_DATA")) + + resourcesSubDir + u'/'; + const QString resourcesTargetDir(options.directory + resourcesSubDir); + if (!createDirectory(resourcesTargetDir, errorMessage, options.dryRun)) + return false; + for (auto installDataFile : installDataFiles) { + if (!updateFile(resourcesSourceDir + QLatin1StringView(installDataFile), + resourcesTargetDir, options.updateFileFlags, options.json, errorMessage)) { + return false; + } + } + const QFileInfo translations(qtpathsVariables.value(QStringLiteral("QT_INSTALL_TRANSLATIONS")) + + QStringLiteral("/qtwebengine_locales")); + if (!translations.isDir()) { + std::wcerr << "Warning: Cannot find the translation files of the QtWebEngine module at " + << QDir::toNativeSeparators(translations.absoluteFilePath()) << ".\n"; + return true; + } + if (options.translations) { + // Copy the whole translations directory. + return createDirectory(options.translationsDirectory, errorMessage, options.dryRun) + && updateFile(translations.absoluteFilePath(), options.translationsDirectory, + options.updateFileFlags, options.json, errorMessage); + } + // Translations have been turned off, but QtWebEngine needs at least one. + const QFileInfo enUSpak(translations.filePath() + QStringLiteral("/en-US.pak")); + if (!enUSpak.exists()) { + std::wcerr << "Warning: Cannot find " + << QDir::toNativeSeparators(enUSpak.absoluteFilePath()) << ".\n"; + return true; + } + const QString webEngineTranslationsDir = options.translationsDirectory + u'/' + + translations.fileName(); + if (!createDirectory(webEngineTranslationsDir, errorMessage, options.dryRun)) + return false; + return updateFile(enUSpak.absoluteFilePath(), webEngineTranslationsDir, + options.updateFileFlags, options.json, errorMessage); +} + +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +int main(int argc, char **argv) +{ + QCoreApplication a(argc, argv); + QCoreApplication::setApplicationVersion(QT_VERSION_STR ""_L1); + + const QByteArray qtBinPath = QFile::encodeName(QDir::toNativeSeparators(QCoreApplication::applicationDirPath())); + QByteArray path = qgetenv("PATH"); + if (!path.contains(qtBinPath)) { // QTBUG-39177, ensure Qt is in the path so that qt.conf is taken into account. + path.prepend(QDir::listSeparator().toLatin1()); + path.prepend(qtBinPath); + qputenv("PATH", path); + } + + Options options; + QString errorMessage; + + // Early parse the --qmake and --qtpaths options, because they are needed to determine the + // options that select/deselect Qt modules. + { + int result = parseEarlyArguments(QCoreApplication::arguments(), &options, &errorMessage); + if (result & CommandLineParseError) { + std::wcerr << "Error: " << errorMessage << "\n"; + return 1; + } + } + + const QMap<QString, QString> qtpathsVariables = + queryQtPaths(options.qtpathsBinary, &errorMessage); + const QString xSpec = qtpathsVariables.value(QStringLiteral("QMAKE_XSPEC")); + if (qtpathsVariables.isEmpty() || xSpec.isEmpty() + || !qtpathsVariables.contains(QStringLiteral("QT_INSTALL_BINS"))) { + std::wcerr << "Unable to query qtpaths: " << errorMessage << '\n'; + return 1; + } + + options.platform = platformFromMkSpec(xSpec); + if (options.platform == UnknownPlatform) { + std::wcerr << "Unsupported platform " << xSpec << '\n'; + return 1; + } + + // Read the Qt module information from the Qt installation directory. + const QString modulesDir + = qtpathsVariables.value(QLatin1String("QT_INSTALL_ARCHDATA")) + + QLatin1String("/modules"); + const QString translationsDir + = qtpathsVariables.value(QLatin1String("QT_INSTALL_TRANSLATIONS")); + if (!qtModuleEntries.populate(modulesDir, translationsDir, optVerboseLevel > 1, + &errorMessage)) { + std::wcerr << "Error: " << errorMessage << "\n"; + return 1; + } + assignKnownModuleIds(); + + // Read the Qt plugin types information from the Qt installation directory. + PluginInformation pluginInfo{}; + pluginInfo.generateAvailablePlugins(qtpathsVariables, options.platform); + + // Parse the full command line. + { + QCommandLineParser parser; + QString errorMessage; + const int result = parseArguments(QCoreApplication::arguments(), &parser, &options, &errorMessage); + if (result & CommandLineParseError) + std::wcerr << errorMessage << "\n\n"; + if (result & CommandLineVersionRequested) { + std::fputs(QT_VERSION_STR "\n", stdout); + return 0; + } + if (result & CommandLineParseHelpRequested) + std::fputs(qPrintable(helpText(parser, pluginInfo)), stdout); + if (result & CommandLineParseError) + return 1; + if (result & CommandLineParseHelpRequested) + return 0; + } + + // Create directories + if (!createDirectory(options.directory, &errorMessage, options.dryRun)) { + std::wcerr << errorMessage << '\n'; + return 1; + } + if (!options.libraryDirectory.isEmpty() && options.libraryDirectory != options.directory + && !createDirectory(options.libraryDirectory, &errorMessage, options.dryRun)) { + std::wcerr << errorMessage << '\n'; + return 1; + } + + const DeployResult result = deploy(options, qtpathsVariables, pluginInfo, &errorMessage); + if (!result) { + std::wcerr << errorMessage << '\n'; + return 1; + } + + if (result.deployedQtLibraries.test(QtWebEngineCoreModuleId)) { + if (!deployWebEngineCore(qtpathsVariables, pluginInfo, options, result.isDebug, + &errorMessage)) { + std::wcerr << errorMessage << '\n'; + return 1; + } + } + + if (options.json) { + if (options.list) + std::fputs(options.json->toList(options.list, options.directory).constData(), stdout); + else + std::fputs(options.json->toJson().constData(), stdout); + delete options.json; + options.json = nullptr; + } + + return 0; +} diff --git a/src/tools/windeployqt/qmlutils.cpp b/src/tools/windeployqt/qmlutils.cpp new file mode 100644 index 0000000000..a7e63e7470 --- /dev/null +++ b/src/tools/windeployqt/qmlutils.cpp @@ -0,0 +1,138 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmlutils.h" +#include "utils.h" + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QCoreApplication> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> +#include <QtCore/QJsonParseError> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +bool operator==(const QmlImportScanResult::Module &m1, const QmlImportScanResult::Module &m2) +{ + return m1.className.isEmpty() ? m1.name == m2.name : m1.className == m2.className; +} + +// Return install path (cp -r semantics) +QString QmlImportScanResult::Module::installPath(const QString &root) const +{ + QString result = root; + const qsizetype lastSlashPos = relativePath.lastIndexOf(u'/'); + if (lastSlashPos != -1) { + result += u'/'; + result += QStringView{relativePath}.left(lastSlashPos); + } + return result; +} + +static QString qmlDirectoryRecursion(Platform platform, const QString &path) +{ + QDir dir(path); + if (!dir.entryList(QStringList(QStringLiteral("*.qml")), QDir::Files, QDir::NoSort).isEmpty()) + return dir.path(); + const QFileInfoList &subDirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort); + for (const QFileInfo &subDirFi : subDirs) { + if (!isBuildDirectory(platform, subDirFi.fileName())) { + const QString subPath = qmlDirectoryRecursion(platform, subDirFi.absoluteFilePath()); + if (!subPath.isEmpty()) + return subPath; + } + } + return QString(); +} + +// Find a directory containing QML files in the project +QString findQmlDirectory(Platform platform, const QString &startDirectoryName) +{ + QDir startDirectory(startDirectoryName); + if (isBuildDirectory(platform, startDirectory.dirName())) + startDirectory.cdUp(); + return qmlDirectoryRecursion(platform, startDirectory.path()); +} + +static void findFileRecursion(const QDir &directory, Platform platform, + DebugMatchMode debugMatchMode, QStringList *matches) +{ + const QStringList &dlls = findSharedLibraries(directory, platform, debugMatchMode); + for (const QString &dll : dlls) + matches->append(directory.filePath(dll)); + const QFileInfoList &subDirs = directory.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); + for (const QFileInfo &subDirFi : subDirs) { + QDir subDirectory(subDirFi.absoluteFilePath()); + // Don't enter other QML modules when recursing! + if (subDirectory.isReadable() && !subDirectory.exists(QStringLiteral("qmldir"))) + findFileRecursion(subDirectory, platform, debugMatchMode, matches); + } +} + +QmlImportScanResult runQmlImportScanner(const QString &directory, const QStringList &qmlImportPaths, + bool usesWidgets, int platform, DebugMatchMode debugMatchMode, + QString *errorMessage) +{ + Q_UNUSED(usesWidgets); + QmlImportScanResult result; + QStringList arguments; + for (const QString &importPath : qmlImportPaths) + arguments << QStringLiteral("-importPath") << importPath; + arguments << QStringLiteral("-rootPath") << directory; + unsigned long exitCode; + QByteArray stdOut; + QByteArray stdErr; + const QString binary = QStringLiteral("qmlimportscanner"); + if (!runProcess(binary, arguments, QDir::currentPath(), &exitCode, &stdOut, &stdErr, errorMessage)) + return result; + if (exitCode) { + *errorMessage = binary + QStringLiteral(" returned ") + QString::number(exitCode) + + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr); + return result; + } + QJsonParseError jsonParseError{}; + const QJsonDocument data = QJsonDocument::fromJson(stdOut, &jsonParseError); + if (data.isNull() ) { + *errorMessage = binary + QStringLiteral(" returned invalid JSON output: ") + + jsonParseError.errorString() + QStringLiteral(" :\"") + + QString::fromLocal8Bit(stdOut) + u'"'; + return result; + } + const QJsonArray array = data.array(); + const int childCount = array.count(); + for (int c = 0; c < childCount; ++c) { + const QJsonObject object = array.at(c).toObject(); + if (object.value(QStringLiteral("type")).toString() == "module"_L1) { + const QString path = object.value(QStringLiteral("path")).toString(); + if (!path.isEmpty()) { + QmlImportScanResult::Module module; + module.name = object.value(QStringLiteral("name")).toString(); + module.className = object.value(QStringLiteral("classname")).toString(); + module.sourcePath = path; + module.relativePath = object.value(QStringLiteral("relativePath")).toString(); + result.modules.append(module); + findFileRecursion(QDir(path), Platform(platform), debugMatchMode, &result.plugins); + } + } + } + result.ok = true; + return result; +} + +void QmlImportScanResult::append(const QmlImportScanResult &other) +{ + for (const QmlImportScanResult::Module &module : other.modules) { + if (std::find(modules.cbegin(), modules.cend(), module) == modules.cend()) + modules.append(module); + } + for (const QString &plugin : other.plugins) { + if (!plugins.contains(plugin)) + plugins.append(plugin); + } +} + +QT_END_NAMESPACE diff --git a/src/tools/windeployqt/qmlutils.h b/src/tools/windeployqt/qmlutils.h new file mode 100644 index 0000000000..bff1fb3a9b --- /dev/null +++ b/src/tools/windeployqt/qmlutils.h @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLUTILS_H +#define QMLUTILS_H + +#include "utils.h" + +#include <QStringList> + +QT_BEGIN_NAMESPACE + +QString findQmlDirectory(Platform platform, const QString &startDirectoryName); + +struct QmlImportScanResult { + struct Module { + QString installPath(const QString &root) const; + + QString name; + QString className; + QString sourcePath; + QString relativePath; + }; + + void append(const QmlImportScanResult &other); + + bool ok = false; + QList<Module> modules; + QStringList plugins; +}; + +bool operator==(const QmlImportScanResult::Module &m1, const QmlImportScanResult::Module &m2); + +QmlImportScanResult runQmlImportScanner(const QString &directory, const QStringList &qmlImportPaths, + bool usesWidgets, int platform, DebugMatchMode debugMatchMode, + QString *errorMessage); + +QT_END_NAMESPACE + +#endif // QMLUTILS_H diff --git a/src/tools/windeployqt/qtmoduleinfo.cpp b/src/tools/windeployqt/qtmoduleinfo.cpp new file mode 100644 index 0000000000..b928a64478 --- /dev/null +++ b/src/tools/windeployqt/qtmoduleinfo.cpp @@ -0,0 +1,183 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtmoduleinfo.h" +#include "utils.h" + +#include <QDirListing> +#include <QJsonDocument> +#include <QJsonArray> +#include <QDebug> + +#include <iostream> +#include <algorithm> +#include <unordered_map> + +using namespace Qt::StringLiterals; + +static QStringList toStringList(const QJsonArray &jsonArray) +{ + QStringList result; + for (const auto &item : jsonArray) { + if (item.isString()) + result.append(item.toString()); + } + return result; +} + +struct TranslationCatalog +{ + QString name; + QStringList repositories; + QStringList modules; +}; + +using TranslationCatalogs = std::vector<TranslationCatalog>; + +static TranslationCatalogs readTranslationsCatalogs(const QString &translationsDir, + bool verbose, + QString *errorString) +{ + QFile file(translationsDir + QLatin1String("/catalogs.json")); + if (verbose) { + std::wcerr << "Trying to read translation catalogs from \"" + << qUtf8Printable(file.fileName()) << "\".\n"; + } + if (!file.open(QIODevice::ReadOnly)) { + *errorString = QLatin1String("Cannot open ") + file.fileName(); + return {}; + } + + QJsonParseError jsonParseError; + QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &jsonParseError); + if (jsonParseError.error != QJsonParseError::NoError) { + *errorString = jsonParseError.errorString(); + return {}; + } + + if (!document.isArray()) { + *errorString = QLatin1String("Expected an array as root element of ") + file.fileName(); + return {}; + } + + TranslationCatalogs catalogs; + for (const QJsonValueRef &item : document.array()) { + TranslationCatalog catalog; + catalog.name = item[QLatin1String("name")].toString(); + catalog.repositories = toStringList(item[QLatin1String("repositories")].toArray()); + catalog.modules = toStringList(item[QLatin1String("modules")].toArray()); + if (verbose) + std::wcerr << "Found catalog \"" << qUtf8Printable(catalog.name) << "\".\n"; + catalogs.emplace_back(std::move(catalog)); + } + + return catalogs; +} + +static QtModule moduleFromJsonFile(const QString &filePath, QString *errorString) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + *errorString = QLatin1String("Cannot open ") + file.fileName(); + return {}; + } + + QJsonParseError jsonParseError; + QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &jsonParseError); + if (jsonParseError.error != QJsonParseError::NoError) { + *errorString = jsonParseError.errorString(); + return {}; + } + + if (!document.isObject()) { + *errorString = QLatin1String("Expected an object as root element of ") + file.fileName(); + return {}; + } + + const QJsonObject obj = document.object(); + QtModule module; + module.name = "Qt6"_L1 + obj[QLatin1String("name")].toString(); + module.repository = obj[QLatin1String("repository")].toString(); + module.internal = obj[QLatin1String("internal")].toBool(); + module.pluginTypes = toStringList(obj[QLatin1String("plugin_types")].toArray()); + return module; +} + +static void dump(const QtModule &module) +{ + std::wcerr << "Found module \"" << qUtf8Printable(module.name) << "\".\n"; + if (!module.pluginTypes.isEmpty()) + qDebug().nospace() << " plugin types: " << module.pluginTypes; + if (!module.translationCatalog.isEmpty()) + qDebug().nospace() << " translation catalog: "<< module.translationCatalog; +} + +bool QtModuleInfoStore::populate(const QString &modulesDir, const QString &translationsDir, + bool verbose, QString *errorString) +{ + const TranslationCatalogs catalogs = readTranslationsCatalogs(translationsDir, verbose, + errorString); + if (!errorString->isEmpty()) { + std::wcerr << "Warning: Translations will not be available due to the following error." + << std::endl << *errorString << std::endl; + errorString->clear(); + } + std::unordered_map<QString, QString> moduleToCatalogMap; + std::unordered_map<QString, QString> repositoryToCatalogMap; + for (const TranslationCatalog &catalog : catalogs) { + for (const QString &module : catalog.modules) { + moduleToCatalogMap.insert(std::make_pair(module, catalog.name)); + } + for (const QString &repository : catalog.repositories) { + repositoryToCatalogMap.insert(std::make_pair(repository, catalog.name)); + } + } + + // Read modules, and assign a bit as ID. + for (const auto &dirEntry : QDirListing(modulesDir, {u"*.json"_s}, QDir::Files)) { + QtModule module = moduleFromJsonFile(dirEntry.filePath(), errorString); + if (!errorString->isEmpty()) + return false; + if (module.internal && module.name.endsWith(QStringLiteral("Private"))) + module.name.chop(7); + module.id = modules.size(); + if (module.id == QtModule::InvalidId) { + *errorString = "Internal Error: too many modules for ModuleBitset to hold."_L1; + return false; + } + + { + auto it = moduleToCatalogMap.find(module.name); + if (it != moduleToCatalogMap.end()) + module.translationCatalog = it->second; + } + if (module.translationCatalog.isEmpty()) { + auto it = repositoryToCatalogMap.find(module.repository); + if (it != repositoryToCatalogMap.end()) + module.translationCatalog = it->second; + } + if (verbose) + dump(module); + modules.emplace_back(std::move(module)); + } + + return true; +} + +const QtModule &QtModuleInfoStore::moduleById(size_t id) const +{ + return modules.at(id); +} + +size_t QtModuleInfoStore::moduleIdForPluginType(const QString &pluginType) const +{ + auto moduleHasPluginType = [&pluginType] (const QtModule &module) { + return module.pluginTypes.contains(pluginType); + }; + + auto it = std::find_if(modules.begin(), modules.end(), moduleHasPluginType); + if (it != modules.end()) + return it->id ; + + return QtModule::InvalidId; +} diff --git a/src/tools/windeployqt/qtmoduleinfo.h b/src/tools/windeployqt/qtmoduleinfo.h new file mode 100644 index 0000000000..b35403a090 --- /dev/null +++ b/src/tools/windeployqt/qtmoduleinfo.h @@ -0,0 +1,51 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QTMODULEINFO_H +#define QTMODULEINFO_H + +#include <QString> +#include <QStringList> + +#include <bitset> +#include <vector> + +constexpr size_t ModuleBitsetSize = 1024; +using ModuleBitset = std::bitset<ModuleBitsetSize>; + +struct QtModule +{ + static constexpr size_t InvalidId = ModuleBitsetSize - 1; + size_t id = InvalidId; + bool internal = false; + QString name; + QString repository; + QStringList pluginTypes; + QString translationCatalog; +}; + +inline bool contains(const ModuleBitset &modules, const QtModule &module) +{ + return modules.test(module.id); +} + +class QtModuleInfoStore +{ +public: + QtModuleInfoStore() = default; + + bool populate(const QString &modulesDir, const QString &translationsDir, bool verbose, + QString *errorString); + + size_t size() const { return modules.size(); } + std::vector<QtModule>::const_iterator begin() const { return modules.begin(); } + std::vector<QtModule>::const_iterator end() const { return modules.end(); } + + const QtModule &moduleById(size_t id) const; + size_t moduleIdForPluginType(const QString &pluginType) const; + +private: + std::vector<QtModule> modules; +}; + +#endif diff --git a/src/tools/windeployqt/qtplugininfo.cpp b/src/tools/windeployqt/qtplugininfo.cpp new file mode 100644 index 0000000000..1deaa35f35 --- /dev/null +++ b/src/tools/windeployqt/qtplugininfo.cpp @@ -0,0 +1,100 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtplugininfo.h" + +#include <QDir> + +static PluginDetection determinePluginLibrary(const QDir &platformPluginDir, const QString &infix) +{ + // Use the platform plugin to determine which dlls are there (release/debug/both) + QString platformReleaseFilter(QStringLiteral("qwindows")); + if (!infix.isEmpty()) + platformReleaseFilter += infix; + QString platformFilter = platformReleaseFilter + u'*'; + platformFilter += sharedLibrarySuffix(); + + const QFileInfoList &dlls = + platformPluginDir.entryInfoList(QStringList(platformFilter), QDir::Files); + if (dlls.size() == 1) { + const QFileInfo dllFi = dlls.first(); + const bool hasDebugDlls = + dllFi.fileName() == QString(platformReleaseFilter + sharedLibrarySuffix()) ? false + : true; + return (hasDebugDlls ? PluginDetection::DebugOnly : PluginDetection::ReleaseOnly); + } else { + return PluginDetection::DebugAndRelease; + } +} + +static QStringList findPluginNames(const QDir &pluginDir, const PluginDetection libraryType, + const Platform &platform) +{ + QString errorMessage{}; + QStringList result{}; + QString filter{}; + filter += u"*"; + filter += sharedLibrarySuffix(); + + const QFileInfoList &dlls = + pluginDir.entryInfoList(QStringList(filter), QDir::Files, QDir::Name); + + for (const QFileInfo &dllFi : dlls) { + QString plugin = dllFi.fileName(); + const int dotIndex = plugin.lastIndexOf(u'.'); + // We don't need the .dll for the name + plugin = plugin.first(dotIndex); + + if (libraryType == PluginDetection::DebugAndRelease) { + bool isDebugDll{}; + if (!readPeExecutable(dllFi.absoluteFilePath(), &errorMessage, 0, 0, &isDebugDll, + (platform == WindowsDesktopMinGW))) { + std::wcerr << "Warning: Unable to read " + << QDir::toNativeSeparators(dllFi.absoluteFilePath()) << ": " + << errorMessage; + } + if (isDebugDll && platformHasDebugSuffix(platform)) + plugin.removeLast(); + } + else if (libraryType == PluginDetection::DebugOnly) + plugin.removeLast(); + + if (!result.contains(plugin)) + result.append(plugin); + } + return result; +} + +bool PluginInformation::isTypeForPlugin(const QString &type, const QString &plugin) const +{ + return m_pluginMap.at(plugin) == type; +} + +void PluginInformation::populatePluginToType(const QDir &pluginDir, const QStringList &plugins) +{ + for (const QString &plugin : plugins) + m_pluginMap.insert({ plugin, pluginDir.dirName() }); +} + +void PluginInformation::generateAvailablePlugins(const QMap<QString, QString> &qtPathsVariables, + const Platform &platform) +{ + const QDir pluginTypesDir(qtPathsVariables.value(QLatin1String("QT_INSTALL_PLUGINS"))); + const QDir platformPluginDir(pluginTypesDir.absolutePath() + QStringLiteral("/platforms")); + const QString infix(qtPathsVariables.value(QLatin1String(qmakeInfixKey))); + const PluginDetection debugDetection = determinePluginLibrary(platformPluginDir, infix); + + const QFileInfoList &pluginTypes = + pluginTypesDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &pluginType : pluginTypes) { + const QString pluginTypeName = pluginType.baseName(); + m_typeMap.insert({ pluginTypeName, QStringList{} }); + const QStringList plugins = + findPluginNames(pluginType.absoluteFilePath(), debugDetection, platform); + m_typeMap.at(pluginTypeName) = plugins; + populatePluginToType(pluginTypeName, plugins); + } + if (!m_typeMap.size() || !m_pluginMap.size()) + std::wcerr << "Warning: could not parse available plugins properly, plugin " + "inclusion/exclusion options will not work\n"; +} diff --git a/src/tools/windeployqt/qtplugininfo.h b/src/tools/windeployqt/qtplugininfo.h new file mode 100644 index 0000000000..420b2b5e1a --- /dev/null +++ b/src/tools/windeployqt/qtplugininfo.h @@ -0,0 +1,48 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QTPLUGININFO_H +#define QTPLUGININFO_H + +#include "utils.h" + +#include <QString> +#include <QStringList> + +#include <unordered_map> + +enum class PluginDetection +{ + DebugOnly, + ReleaseOnly, + DebugAndRelease +}; + +struct PluginSelections +{ + QStringList disabledPluginTypes; + QStringList enabledPluginTypes; + QStringList excludedPlugins; + QStringList includedPlugins; + bool includeSoftPlugins = false; +}; + +class PluginInformation +{ +public: + PluginInformation() = default; + + bool isTypeForPlugin(const QString &type, const QString &plugin) const; + + void generateAvailablePlugins(const QMap<QString, QString> &qtPathsVariables, + const Platform &platform); + void populatePluginToType(const QDir &pluginDir, const QStringList &plugins); + + const std::unordered_map<QString, QStringList> &typeMap() const { return m_typeMap; } + +private: + std::unordered_map<QString, QStringList> m_typeMap; + std::unordered_map<QString, QString> m_pluginMap; +}; + +#endif diff --git a/src/tools/windeployqt/utils.cpp b/src/tools/windeployqt/utils.cpp new file mode 100644 index 0000000000..5141119254 --- /dev/null +++ b/src/tools/windeployqt/utils.cpp @@ -0,0 +1,1022 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "utils.h" + +#include <QtCore/QString> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QTemporaryFile> +#include <QtCore/QScopedPointer> +#include <QtCore/QScopedArrayPointer> +#include <QtCore/QStandardPaths> +#if defined(Q_OS_WIN) +# include <QtCore/qt_windows.h> +# include <QtCore/private/qsystemerror_p.h> +# include <shlwapi.h> +# include <delayimp.h> +#else // Q_OS_WIN +# include <sys/wait.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <unistd.h> +# include <stdlib.h> +# include <string.h> +# include <errno.h> +# include <fcntl.h> +#endif // !Q_OS_WIN + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +int optVerboseLevel = 1; + +bool isBuildDirectory(Platform platform, const QString &dirName) +{ + return (platform.testFlag(Msvc) || platform.testFlag(ClangMsvc)) + && (dirName == "debug"_L1 || dirName == "release"_L1); +} + +// Create a symbolic link by changing to the source directory to make sure the +// link uses relative paths only (QFile::link() otherwise uses the absolute path). +bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage) +{ + const QString oldDirectory = QDir::currentPath(); + if (!QDir::setCurrent(source.absolutePath())) { + *errorMessage = QStringLiteral("Unable to change to directory %1.").arg(QDir::toNativeSeparators(source.absolutePath())); + return false; + } + QFile file(source.fileName()); + const bool success = file.link(target); + QDir::setCurrent(oldDirectory); + if (!success) { + *errorMessage = QString::fromLatin1("Failed to create symbolic link %1 -> %2: %3") + .arg(QDir::toNativeSeparators(source.absoluteFilePath()), + QDir::toNativeSeparators(target), file.errorString()); + return false; + } + return true; +} + +bool createDirectory(const QString &directory, QString *errorMessage, bool dryRun) +{ + const QFileInfo fi(directory); + if (fi.isDir()) + return true; + if (fi.exists()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a directory."). + arg(QDir::toNativeSeparators(directory)); + return false; + } + if (optVerboseLevel) + std::wcout << "Creating " << QDir::toNativeSeparators(directory) << "...\n"; + if (!dryRun) { + QDir dir; + if (!dir.mkpath(directory)) { + *errorMessage = QString::fromLatin1("Could not create directory %1.") + .arg(QDir::toNativeSeparators(directory)); + return false; + } + } + return true; +} + +// Find shared libraries matching debug/Platform in a directory, return relative names. +QStringList findSharedLibraries(const QDir &directory, Platform platform, + DebugMatchMode debugMatchMode, + const QString &prefix) +{ + QString nameFilter = prefix; + if (nameFilter.isEmpty()) + nameFilter += u'*'; + if (debugMatchMode == MatchDebug && platformHasDebugSuffix(platform)) + nameFilter += u'd'; + nameFilter += sharedLibrarySuffix(); + QStringList result; + QString errorMessage; + const QFileInfoList &dlls = directory.entryInfoList(QStringList(nameFilter), QDir::Files); + for (const QFileInfo &dllFi : dlls) { + const QString dllPath = dllFi.absoluteFilePath(); + bool matches = true; + if (debugMatchMode != MatchDebugOrRelease && (platform & WindowsBased)) { + bool debugDll; + if (readPeExecutable(dllPath, &errorMessage, 0, 0, &debugDll, + (platform == WindowsDesktopMinGW))) { + matches = debugDll == (debugMatchMode == MatchDebug); + } else { + std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(dllPath) + << ": " << errorMessage; + } + } // Windows + if (matches) + result += dllFi.fileName(); + } // for + return result; +} + +#ifdef Q_OS_WIN + +// Case-Normalize file name via GetShortPathNameW()/GetLongPathNameW() +QString normalizeFileName(const QString &name) +{ + wchar_t shortBuffer[MAX_PATH]; + const QString nativeFileName = QDir::toNativeSeparators(name); + if (!GetShortPathNameW(reinterpret_cast<LPCWSTR>(nativeFileName.utf16()), shortBuffer, MAX_PATH)) + return name; + wchar_t result[MAX_PATH]; + if (!GetLongPathNameW(shortBuffer, result, MAX_PATH)) + return name; + return QDir::fromNativeSeparators(QString::fromWCharArray(result)); +} + +// Find a tool binary in the Windows SDK 8 +QString findSdkTool(const QString &tool) +{ + QStringList paths = QString::fromLocal8Bit(qgetenv("PATH")).split(u';'); + const QByteArray sdkDir = qgetenv("WindowsSdkDir"); + if (!sdkDir.isEmpty()) + paths.prepend(QDir::cleanPath(QString::fromLocal8Bit(sdkDir)) + "/Tools/x64"_L1); + return QStandardPaths::findExecutable(tool, paths); +} + +// runProcess helper: Create a temporary file for stdout/stderr redirection. +static HANDLE createInheritableTemporaryFile() +{ + wchar_t path[MAX_PATH]; + if (!GetTempPath(MAX_PATH, path)) + return INVALID_HANDLE_VALUE; + wchar_t name[MAX_PATH]; + if (!GetTempFileName(path, L"temp", 0, name)) // Creates file. + return INVALID_HANDLE_VALUE; + SECURITY_ATTRIBUTES securityAttributes; + ZeroMemory(&securityAttributes, sizeof(securityAttributes)); + securityAttributes.nLength = sizeof(securityAttributes); + securityAttributes.bInheritHandle = TRUE; + return CreateFile(name, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes, + TRUNCATE_EXISTING, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); +} + +// runProcess helper: Rewind and read out a temporary file for stdout/stderr. +static inline void readTemporaryProcessFile(HANDLE handle, QByteArray *result) +{ + if (SetFilePointer(handle, 0, 0, FILE_BEGIN) == 0xFFFFFFFF) + return; + char buf[1024]; + DWORD bytesRead; + while (ReadFile(handle, buf, sizeof(buf), &bytesRead, NULL) && bytesRead) + result->append(buf, int(bytesRead)); + CloseHandle(handle); +} + +static inline void appendToCommandLine(const QString &argument, QString *commandLine) +{ + const bool needsQuote = argument.contains(u' '); + if (!commandLine->isEmpty()) + commandLine->append(u' '); + if (needsQuote) + commandLine->append(u'"'); + commandLine->append(argument); + if (needsQuote) + commandLine->append(u'"'); +} + +// runProcess: Run a command line process (replacement for QProcess which +// does not exist in the bootstrap library). +bool runProcess(const QString &binary, const QStringList &args, + const QString &workingDirectory, + unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr, + QString *errorMessage) +{ + if (exitCode) + *exitCode = 0; + + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + STARTUPINFO myInfo; + GetStartupInfo(&myInfo); + si.hStdInput = myInfo.hStdInput; + si.hStdOutput = myInfo.hStdOutput; + si.hStdError = myInfo.hStdError; + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + const QChar backSlash = u'\\'; + QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory.isEmpty() ? QDir::currentPath() : workingDirectory); + if (!nativeWorkingDir.endsWith(backSlash)) + nativeWorkingDir += backSlash; + + if (stdOut) { + si.hStdOutput = createInheritableTemporaryFile(); + if (si.hStdOutput == INVALID_HANDLE_VALUE) { + if (errorMessage) + *errorMessage = QStringLiteral("Error creating stdout temporary file"); + return false; + } + si.dwFlags |= STARTF_USESTDHANDLES; + } + + if (stdErr) { + si.hStdError = createInheritableTemporaryFile(); + if (si.hStdError == INVALID_HANDLE_VALUE) { + if (errorMessage) + *errorMessage = QStringLiteral("Error creating stderr temporary file"); + return false; + } + si.dwFlags |= STARTF_USESTDHANDLES; + } + + // Create a copy of the command line which CreateProcessW can modify. + QString commandLine; + appendToCommandLine(binary, &commandLine); + for (const QString &a : args) + appendToCommandLine(a, &commandLine); + if (optVerboseLevel > 1) + std::wcout << "Running: " << commandLine << '\n'; + + QScopedArrayPointer<wchar_t> commandLineW(new wchar_t[commandLine.size() + 1]); + commandLine.toWCharArray(commandLineW.data()); + commandLineW[commandLine.size()] = 0; + if (!CreateProcessW(0, commandLineW.data(), 0, 0, /* InheritHandles */ TRUE, 0, 0, + reinterpret_cast<LPCWSTR>(nativeWorkingDir.utf16()), &si, &pi)) { + if (stdOut) + CloseHandle(si.hStdOutput); + if (stdErr) + CloseHandle(si.hStdError); + if (errorMessage) { + *errorMessage = QStringLiteral("CreateProcessW failed: ") + + QSystemError::windowsString(); + } + return false; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hThread); + if (exitCode) + GetExitCodeProcess(pi.hProcess, exitCode); + CloseHandle(pi.hProcess); + + if (stdOut) + readTemporaryProcessFile(si.hStdOutput, stdOut); + if (stdErr) + readTemporaryProcessFile(si.hStdError, stdErr); + return true; +} + +#else // Q_OS_WIN + +static inline char *encodeFileName(const QString &f) +{ + const QByteArray encoded = QFile::encodeName(f); + char *result = new char[encoded.size() + 1]; + strcpy(result, encoded.constData()); + return result; +} + +static inline char *tempFilePattern() +{ + QString path = QDir::tempPath(); + if (!path.endsWith(u'/')) + path += u'/'; + path += QStringLiteral("tmpXXXXXX"); + return encodeFileName(path); +} + +static inline QByteArray readOutRedirectFile(int fd) +{ + enum { bufSize = 256 }; + + QByteArray result; + if (!lseek(fd, 0, 0)) { + char buf[bufSize]; + while (true) { + const ssize_t rs = read(fd, buf, bufSize); + if (rs <= 0) + break; + result.append(buf, int(rs)); + } + } + close(fd); + return result; +} + +// runProcess: Run a command line process (replacement for QProcess which +// does not exist in the bootstrap library). +bool runProcess(const QString &binary, const QStringList &args, + const QString &workingDirectory, + unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr, + QString *errorMessage) +{ + QScopedArrayPointer<char> stdOutFileName; + QScopedArrayPointer<char> stdErrFileName; + + int stdOutFile = 0; + if (stdOut) { + stdOutFileName.reset(tempFilePattern()); + stdOutFile = mkstemp(stdOutFileName.data()); + if (stdOutFile < 0) { + *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno)); + return false; + } + } + + int stdErrFile = 0; + if (stdErr) { + stdErrFileName.reset(tempFilePattern()); + stdErrFile = mkstemp(stdErrFileName.data()); + if (stdErrFile < 0) { + *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno)); + return false; + } + } + + const pid_t pID = fork(); + + if (pID < 0) { + *errorMessage = QStringLiteral("Fork failed: ") + QString::fromLocal8Bit(strerror(errno)); + return false; + } + + if (!pID) { // Child + if (stdOut) { + dup2(stdOutFile, STDOUT_FILENO); + close(stdOutFile); + } + if (stdErr) { + dup2(stdErrFile, STDERR_FILENO); + close(stdErrFile); + } + + if (!workingDirectory.isEmpty() && !QDir::setCurrent(workingDirectory)) { + std::wcerr << "Failed to change working directory to " << workingDirectory << ".\n"; + ::_exit(-1); + } + + char **argv = new char *[args.size() + 2]; // Create argv. + char **ap = argv; + *ap++ = encodeFileName(binary); + for (const QString &a : std::as_const(args)) + *ap++ = encodeFileName(a); + *ap = 0; + + execvp(argv[0], argv); + ::_exit(-1); + } + + int status; + pid_t waitResult; + + do { + waitResult = waitpid(pID, &status, 0); + } while (waitResult == -1 && errno == EINTR); + + if (stdOut) { + *stdOut = readOutRedirectFile(stdOutFile); + unlink(stdOutFileName.data()); + } + if (stdErr) { + *stdErr = readOutRedirectFile(stdErrFile); + unlink(stdErrFileName.data()); + } + + if (waitResult < 0) { + *errorMessage = QStringLiteral("Wait failed: ") + QString::fromLocal8Bit(strerror(errno)); + return false; + } + if (!WIFEXITED(status)) { + *errorMessage = binary + QStringLiteral(" did not exit cleanly."); + return false; + } + if (exitCode) + *exitCode = WEXITSTATUS(status); + return true; +} + +#endif // !Q_OS_WIN + +// Find a file in the path using ShellAPI. This can be used to locate DLLs which +// QStandardPaths cannot do. +QString findInPath(const QString &file) +{ +#if defined(Q_OS_WIN) + if (file.size() < MAX_PATH - 1) { + wchar_t buffer[MAX_PATH]; + file.toWCharArray(buffer); + buffer[file.size()] = 0; + if (PathFindOnPath(buffer, NULL)) + return QDir::cleanPath(QString::fromWCharArray(buffer)); + } + return QString(); +#else // Q_OS_WIN + return QStandardPaths::findExecutable(file); +#endif // !Q_OS_WIN +} + +const char *qmakeInfixKey = "QT_INFIX"; + +QMap<QString, QString> queryQtPaths(const QString &qtpathsBinary, QString *errorMessage) +{ + const QString binary = !qtpathsBinary.isEmpty() ? qtpathsBinary : QStringLiteral("qtpaths"); + const QString colonSpace = QStringLiteral(": "); + QByteArray stdOut; + QByteArray stdErr; + unsigned long exitCode = 0; + if (!runProcess(binary, QStringList(QStringLiteral("-query")), QString(), &exitCode, &stdOut, + &stdErr, errorMessage)) { + *errorMessage = QStringLiteral("Error running binary ") + binary + colonSpace + *errorMessage; + return QMap<QString, QString>(); + } + if (exitCode) { + *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode) + + colonSpace + QString::fromLocal8Bit(stdErr); + return QMap<QString, QString>(); + } + const QString output = QString::fromLocal8Bit(stdOut).trimmed().remove(u'\r'); + QMap<QString, QString> result; + const qsizetype size = output.size(); + for (qsizetype pos = 0; pos < size; ) { + const qsizetype colonPos = output.indexOf(u':', pos); + if (colonPos < 0) + break; + qsizetype endPos = output.indexOf(u'\n', colonPos + 1); + if (endPos < 0) + endPos = size; + const QString key = output.mid(pos, colonPos - pos); + const QString value = output.mid(colonPos + 1, endPos - colonPos - 1); + result.insert(key, value); + pos = endPos + 1; + } + QFile qconfigPriFile(result.value(QStringLiteral("QT_HOST_DATA")) + QStringLiteral("/mkspecs/qconfig.pri")); + if (qconfigPriFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + while (true) { + const QByteArray line = qconfigPriFile.readLine(); + if (line.isEmpty()) + break; + if (line.startsWith("QT_LIBINFIX")) { + const int pos = line.indexOf('='); + if (pos >= 0) { + const QString infix = QString::fromUtf8(line.right(line.size() - pos - 1).trimmed()); + if (!infix.isEmpty()) + result.insert(QLatin1StringView(qmakeInfixKey), infix); + } + break; + } + } + } else { + std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(qconfigPriFile.fileName()) + << colonSpace << qconfigPriFile.errorString()<< '\n'; + } + return result; +} + +// Update a file or directory. +bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, + const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage) +{ + const QFileInfo sourceFileInfo(sourceFileName); + const QString targetFileName = targetDirectory + u'/' + sourceFileInfo.fileName(); + if (optVerboseLevel > 1) + std::wcout << "Checking " << sourceFileName << ", " << targetFileName<< '\n'; + + if (!sourceFileInfo.exists()) { + *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + if (sourceFileInfo.isSymLink()) { + *errorMessage = QString::fromLatin1("Symbolic links are not supported (%1).") + .arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + const QFileInfo targetFileInfo(targetFileName); + + if (sourceFileInfo.isDir()) { + if (targetFileInfo.exists()) { + if (!targetFileInfo.isDir()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } // Not a directory. + } else { // exists. + QDir d(targetDirectory); + if (optVerboseLevel) + std::wcout << "Creating " << QDir::toNativeSeparators(targetFileName) << ".\n"; + if (!(flags & SkipUpdateFile) && !d.mkdir(sourceFileInfo.fileName())) { + *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.") + .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory)); + return false; + } + } + // Recurse into directory + QDir dir(sourceFileName); + const QFileInfoList allEntries = dir.entryInfoList(nameFilters, QDir::Files) + + dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &entryFi : allEntries) { + if (!updateFile(entryFi.absoluteFilePath(), nameFilters, targetFileName, flags, json, errorMessage)) + return false; + } + return true; + } // Source is directory. + + if (targetFileInfo.exists()) { + if (!(flags & ForceUpdateFile) + && targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) { + if (optVerboseLevel) + std::wcout << sourceFileInfo.fileName() << " is up to date.\n"; + if (json) + json->addFile(sourceFileName, targetDirectory); + return true; + } + QFile targetFile(targetFileName); + if (!(flags & SkipUpdateFile) && !targetFile.remove()) { + *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString()); + return false; + } + } // target exists + QFile file(sourceFileName); + if (optVerboseLevel) + std::wcout << "Updating " << sourceFileInfo.fileName() << ".\n"; + if (!(flags & SkipUpdateFile) && !file.copy(targetFileName)) { + *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3") + .arg(QDir::toNativeSeparators(sourceFileName), + QDir::toNativeSeparators(targetFileName), + file.errorString()); + return false; + } + if (json) + json->addFile(sourceFileName, targetDirectory); + return true; +} + +#ifdef Q_OS_WIN + +static inline QString stringFromRvaPtr(const void *rvaPtr) +{ + return QString::fromLocal8Bit(static_cast<const char *>(rvaPtr)); +} + +// Helper for reading out PE executable files: Find a section header for an RVA +// (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32). +template <class ImageNtHeader> +const IMAGE_SECTION_HEADER *findSectionHeader(DWORD rva, const ImageNtHeader *nTHeader) +{ + const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader); + const IMAGE_SECTION_HEADER *sectionEnd = section + nTHeader->FileHeader.NumberOfSections; + for ( ; section < sectionEnd; ++section) + if (rva >= section->VirtualAddress && rva < (section->VirtualAddress + section->Misc.VirtualSize)) + return section; + return 0; +} + +// Helper for reading out PE executable files: convert RVA to pointer (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32). +template <class ImageNtHeader> +inline const void *rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader, const void *imageBase) +{ + const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader); + if (!sectionHdr) + return 0; + const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData; + return static_cast<const char *>(imageBase) + rva - delta; +} + +// Helper for reading out PE executable files: return word size of a IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32 +template <class ImageNtHeader> +inline unsigned ntHeaderWordSize(const ImageNtHeader *header) +{ + // defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC + enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b }; + if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic) + return 32; + if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic) + return 64; + return 0; +} + +// Helper for reading out PE executable files: Retrieve the NT image header of an +// executable via the legacy DOS header. +static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, QString *errorMessage) +{ + IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory); + // Check DOS header consistency + if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER)) + || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { + *errorMessage = QString::fromLatin1("DOS header check failed."); + return 0; + } + // Retrieve NT header + char *ntHeaderC = static_cast<char *>(fileMemory) + dosHeader->e_lfanew; + IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC); + // check NT header consistency + if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature)) + || ntHeaders->Signature != IMAGE_NT_SIGNATURE + || IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) { + *errorMessage = QString::fromLatin1("NT header check failed."); + return 0; + } + // Check magic + if (!ntHeaderWordSize(ntHeaders)) { + *errorMessage = QString::fromLatin1("NT header check failed; magic %1 is invalid."). + arg(ntHeaders->OptionalHeader.Magic); + return 0; + } + // Check section headers + IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders); + if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) { + *errorMessage = QString::fromLatin1("NT header section header check failed."); + return 0; + } + return ntHeaders; +} + +// Helper for reading out PE executable files: Read out import sections from +// IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32. +template <class ImageNtHeader> +inline QStringList readImportSections(const ImageNtHeader *ntHeaders, const void *base, QString *errorMessage) +{ + // Get import directory entry RVA and read out + const DWORD importsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + if (!importsStartRVA) { + *errorMessage = QString::fromLatin1("Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry."); + return QStringList(); + } + const IMAGE_IMPORT_DESCRIPTOR *importDesc = static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(rvaToPtr(importsStartRVA, ntHeaders, base)); + if (!importDesc) { + *errorMessage = QString::fromLatin1("Failed to find IMAGE_IMPORT_DESCRIPTOR entry."); + return QStringList(); + } + QStringList result; + for ( ; importDesc->Name; ++importDesc) + result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, base))); + + // Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx . + // Check on grAttr bit 1 whether this is the format using RVA's > VS 6 + if (const DWORD delayedImportsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress) { + const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(rvaToPtr(delayedImportsStartRVA, ntHeaders, base)); + for ( ; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1); ++delayedImportDesc) + result.push_back(stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, base))); + } + + return result; +} + +// Check for MSCV runtime (MSVCP90D.dll/MSVCP90.dll, MSVCP120D.dll/MSVCP120.dll, +// VCRUNTIME140D.DLL/VCRUNTIME140.DLL (VS2015) or msvcp120d_app.dll/msvcp120_app.dll). +enum MsvcDebugRuntimeResult { MsvcDebugRuntime, MsvcReleaseRuntime, NoMsvcRuntime }; + +static inline MsvcDebugRuntimeResult checkMsvcDebugRuntime(const QStringList &dependentLibraries) +{ + for (const QString &lib : dependentLibraries) { + qsizetype pos = 0; + if (lib.startsWith("MSVCR"_L1, Qt::CaseInsensitive) + || lib.startsWith("MSVCP"_L1, Qt::CaseInsensitive) + || lib.startsWith("VCRUNTIME"_L1, Qt::CaseInsensitive) + || lib.startsWith("VCCORLIB"_L1, Qt::CaseInsensitive) + || lib.startsWith("CONCRT"_L1, Qt::CaseInsensitive) + || lib.startsWith("UCRTBASE"_L1, Qt::CaseInsensitive)) { + qsizetype lastDotPos = lib.lastIndexOf(u'.'); + pos = -1 == lastDotPos ? 0 : lastDotPos - 1; + } + + if (pos > 0) { + const auto removeExtraSuffix = [&lib, &pos](const QString &suffix) -> void { + if (lib.contains(suffix, Qt::CaseInsensitive)) + pos -= suffix.size(); + }; + removeExtraSuffix("_app"_L1); + removeExtraSuffix("_atomic_wait"_L1); + removeExtraSuffix("_codecvt_ids"_L1); + } + + if (pos) + return lib.at(pos).toLower() == u'd' ? MsvcDebugRuntime : MsvcReleaseRuntime; + } + return NoMsvcRuntime; +} + +template <class ImageNtHeader> +inline QStringList determineDependentLibs(const ImageNtHeader *nth, const void *fileMemory, + QString *errorMessage) +{ + return readImportSections(nth, fileMemory, errorMessage); +} + +template <class ImageNtHeader> +inline bool determineDebug(const ImageNtHeader *nth, const void *fileMemory, + QStringList *dependentLibrariesIn, QString *errorMessage) +{ + if (nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED) + return false; + + const QStringList dependentLibraries = dependentLibrariesIn != nullptr ? + *dependentLibrariesIn : + determineDependentLibs(nth, fileMemory, errorMessage); + + const bool hasDebugEntry = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; + // When an MSVC debug entry is present, check whether the debug runtime + // is actually used to detect -release / -force-debug-info builds. + const MsvcDebugRuntimeResult msvcrt = checkMsvcDebugRuntime(dependentLibraries); + if (msvcrt == NoMsvcRuntime) + return hasDebugEntry; + else + return hasDebugEntry && msvcrt == MsvcDebugRuntime; +} + +template <class ImageNtHeader> +inline void determineDebugAndDependentLibs(const ImageNtHeader *nth, const void *fileMemory, + QStringList *dependentLibrariesIn, + bool *isDebugIn, QString *errorMessage) +{ + if (dependentLibrariesIn) + *dependentLibrariesIn = determineDependentLibs(nth, fileMemory, errorMessage); + + if (isDebugIn) + *isDebugIn = determineDebug(nth, fileMemory, dependentLibrariesIn, errorMessage); +} + +// Read a PE executable and determine dependent libraries, word size +// and debug flags. +bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage, + QStringList *dependentLibrariesIn, unsigned *wordSizeIn, + bool *isDebugIn, bool isMinGW, unsigned short *machineArchIn) +{ + bool result = false; + HANDLE hFile = NULL; + HANDLE hFileMap = NULL; + void *fileMemory = 0; + + if (dependentLibrariesIn) + dependentLibrariesIn->clear(); + if (wordSizeIn) + *wordSizeIn = 0; + if (isDebugIn) + *isDebugIn = false; + + do { + // Create a memory mapping of the file + hFile = CreateFile(reinterpret_cast<const WCHAR*>(peExecutableFileName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) { + *errorMessage = QString::fromLatin1("Cannot open '%1': %2") + .arg(peExecutableFileName, QSystemError::windowsString()); + break; + } + + hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (hFileMap == NULL) { + *errorMessage = QString::fromLatin1("Cannot create file mapping of '%1': %2") + .arg(peExecutableFileName, QSystemError::windowsString()); + break; + } + + fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0); + if (!fileMemory) { + *errorMessage = QString::fromLatin1("Cannot map '%1': %2") + .arg(peExecutableFileName, QSystemError::windowsString()); + break; + } + + const IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage); + if (!ntHeaders) + break; + + const unsigned wordSize = ntHeaderWordSize(ntHeaders); + if (wordSizeIn) + *wordSizeIn = wordSize; + if (wordSize == 32) { + determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders), + fileMemory, dependentLibrariesIn, isDebugIn, errorMessage); + } else { + determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders), + fileMemory, dependentLibrariesIn, isDebugIn, errorMessage); + } + + if (machineArchIn) + *machineArchIn = ntHeaders->FileHeader.Machine; + + result = true; + if (optVerboseLevel > 1) { + std::wcout << __FUNCTION__ << ": " << QDir::toNativeSeparators(peExecutableFileName) + << ' ' << wordSize << " bit"; + if (isMinGW) + std::wcout << ", MinGW"; + if (dependentLibrariesIn) { + std::wcout << ", dependent libraries: "; + if (optVerboseLevel > 2) + std::wcout << dependentLibrariesIn->join(u' '); + else + std::wcout << dependentLibrariesIn->size(); + } + if (isDebugIn) + std::wcout << (*isDebugIn ? ", debug" : ", release"); + std::wcout << '\n'; + } + } while (false); + + if (fileMemory) + UnmapViewOfFile(fileMemory); + + if (hFileMap != NULL) + CloseHandle(hFileMap); + + if (hFile != NULL && hFile != INVALID_HANDLE_VALUE) + CloseHandle(hFile); + + return result; +} + +QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize) +{ + const QString prefix = QStringLiteral("D3Dcompiler_"); + const QString suffix = QLatin1StringView(windowsSharedLibrarySuffix); + // Get the DLL from Kit 8.0 onwards + const QString kitDir = QString::fromLocal8Bit(qgetenv("WindowsSdkDir")); + if (!kitDir.isEmpty()) { + QString redistDirPath = QDir::cleanPath(kitDir) + QStringLiteral("/Redist/D3D/"); + if (platform.testFlag(ArmBased)) { + redistDirPath += QStringLiteral("arm"); + } else { + redistDirPath += wordSize == 32 ? QStringLiteral("x86") : QStringLiteral("x64"); + } + QDir redistDir(redistDirPath); + if (redistDir.exists()) { + const QFileInfoList files = redistDir.entryInfoList(QStringList(prefix + u'*' + suffix), QDir::Files); + if (!files.isEmpty()) + return files.front().absoluteFilePath(); + } + } + QStringList candidateVersions; + for (int i = 47 ; i >= 40 ; --i) + candidateVersions.append(prefix + QString::number(i) + suffix); + // Check the bin directory of the Qt SDK (in case it is shadowed by the + // Windows system directory in PATH). + for (const QString &candidate : std::as_const(candidateVersions)) { + const QFileInfo fi(qtBinDir + u'/' + candidate); + if (fi.isFile()) + return fi.absoluteFilePath(); + } + // Find the latest D3D compiler DLL in path (Windows 8.1 has d3dcompiler_47). + if (platform.testFlag(IntelBased)) { + QString errorMessage; + unsigned detectedWordSize; + for (const QString &candidate : std::as_const(candidateVersions)) { + const QString dll = findInPath(candidate); + if (!dll.isEmpty() + && readPeExecutable(dll, &errorMessage, 0, &detectedWordSize, 0) + && detectedWordSize == wordSize) { + return dll; + } + } + } + return QString(); +} + +QStringList findDxc(Platform platform, const QString &qtBinDir, unsigned wordSize) +{ + QStringList results; + const QString kitDir = QString::fromLocal8Bit(qgetenv("WindowsSdkDir")); + const QString suffix = QLatin1StringView(windowsSharedLibrarySuffix); + for (QString prefix : { QStringLiteral("dxcompiler"), QStringLiteral("dxil") }) { + QString name = prefix + suffix; + if (!kitDir.isEmpty()) { + QString redistDirPath = QDir::cleanPath(kitDir) + QStringLiteral("/Redist/D3D/"); + if (platform.testFlag(ArmBased)) { + redistDirPath += wordSize == 32 ? QStringLiteral("arm") : QStringLiteral("arm64"); + } else { + redistDirPath += wordSize == 32 ? QStringLiteral("x86") : QStringLiteral("x64"); + } + QDir redistDir(redistDirPath); + if (redistDir.exists()) { + const QFileInfoList files = redistDir.entryInfoList(QStringList(prefix + u'*' + suffix), QDir::Files); + if (!files.isEmpty()) { + results.append(files.front().absoluteFilePath()); + continue; + } + } + } + // Check the bin directory of the Qt SDK (in case it is shadowed by the + // Windows system directory in PATH). + const QFileInfo fi(qtBinDir + u'/' + name); + if (fi.isFile()) { + results.append(fi.absoluteFilePath()); + continue; + } + // Try to find it in the PATH (e.g. the Vulkan SDK ships these, even if Windows itself doesn't). + if (platform.testFlag(IntelBased)) { + QString errorMessage; + unsigned detectedWordSize; + const QString dll = findInPath(name); + if (!dll.isEmpty() + && readPeExecutable(dll, &errorMessage, 0, &detectedWordSize, 0) + && detectedWordSize == wordSize) + { + results.append(dll); + continue; + } + } + } + return results; +} + +#else // Q_OS_WIN + +bool readPeExecutable(const QString &, QString *errorMessage, + QStringList *, unsigned *, bool *, bool, unsigned short *) +{ + *errorMessage = QStringLiteral("Not implemented."); + return false; +} + +QString findD3dCompiler(Platform, const QString &, unsigned) +{ + return QString(); +} + +QStringList findDxc(Platform, const QString &, unsigned) +{ + return QStringList(); +} + +#endif // !Q_OS_WIN + +// Search for "qt_prfxpath=xxxx" in \a path, and replace it with "qt_prfxpath=." +bool patchQtCore(const QString &path, QString *errorMessage) +{ + if (optVerboseLevel) + std::wcout << "Patching " << QFileInfo(path).fileName() << "...\n"; + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + *errorMessage = QString::fromLatin1("Unable to patch %1: %2").arg( + QDir::toNativeSeparators(path), file.errorString()); + return false; + } + const QByteArray oldContent = file.readAll(); + + if (oldContent.isEmpty()) { + *errorMessage = QString::fromLatin1("Unable to patch %1: Could not read file content").arg( + QDir::toNativeSeparators(path)); + return false; + } + file.close(); + + QByteArray content = oldContent; + + QByteArray prfxpath("qt_prfxpath="); + int startPos = content.indexOf(prfxpath); + if (startPos == -1) { + *errorMessage = QString::fromLatin1( + "Unable to patch %1: Could not locate pattern \"qt_prfxpath=\"").arg( + QDir::toNativeSeparators(path)); + return false; + } + startPos += prfxpath.length(); + int endPos = content.indexOf(char(0), startPos); + if (endPos == -1) { + *errorMessage = QString::fromLatin1("Unable to patch %1: Internal error").arg( + QDir::toNativeSeparators(path)); + return false; + } + + QByteArray replacement = QByteArray(endPos - startPos, char(0)); + replacement[0] = '.'; + content.replace(startPos, endPos - startPos, replacement); + if (content == oldContent) + return true; + + if (!file.open(QIODevice::WriteOnly) + || (file.write(content) != content.size())) { + *errorMessage = QString::fromLatin1("Unable to patch %1: Could not write to file: %2").arg( + QDir::toNativeSeparators(path), file.errorString()); + return false; + } + return true; +} + +#ifdef Q_OS_WIN +QString getArchString(unsigned short machineArch) +{ + switch (machineArch) { + case IMAGE_FILE_MACHINE_I386: + return QStringLiteral("x86"); + case IMAGE_FILE_MACHINE_ARM: + return QStringLiteral("arm"); + case IMAGE_FILE_MACHINE_AMD64: + return QStringLiteral("x64"); + case IMAGE_FILE_MACHINE_ARM64: + return QStringLiteral("arm64"); + default: + break; + } + return QString(); +} +#endif // Q_OS_WIN + +QT_END_NAMESPACE diff --git a/src/tools/windeployqt/utils.h b/src/tools/windeployqt/utils.h new file mode 100644 index 0000000000..fb3ba0b40b --- /dev/null +++ b/src/tools/windeployqt/utils.h @@ -0,0 +1,366 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef UTILS_H +#define UTILS_H + +#include <QStringList> +#include <QMap> +#include <QtCore/QFile> +#include <QtCore/QDir> +#include <QtCore/QDateTime> +#include <QtCore/QJsonArray> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonDocument> + +#include <iostream> + +QT_BEGIN_NAMESPACE + +enum PlatformFlag { + // OS + WindowsBased = 0x00001, + // CPU + IntelBased = 0x00010, + ArmBased = 0x00020, + // Compiler + Msvc = 0x00100, + MinGW = 0x00200, + ClangMsvc = 0x00400, + ClangMinGW = 0x00800, + // Platforms + WindowsDesktopMsvc = WindowsBased + Msvc, + WindowsDesktopMsvcIntel = WindowsDesktopMsvc + IntelBased, + WindowsDesktopMsvcArm = WindowsDesktopMsvc + ArmBased, + WindowsDesktopMinGW = WindowsBased + IntelBased + MinGW, + WindowsDesktopClangMsvc = WindowsBased + IntelBased + ClangMsvc, + WindowsDesktopClangMinGW = WindowsBased + IntelBased + ClangMinGW, + UnknownPlatform +}; + +Q_DECLARE_FLAGS(Platform, PlatformFlag) + +Q_DECLARE_OPERATORS_FOR_FLAGS(Platform) + +inline bool platformHasDebugSuffix(Platform p) // Uses 'd' debug suffix +{ + return p.testFlag(Msvc) || p.testFlag(ClangMsvc); +} + +enum ListOption { + ListNone = 0, + ListSource, + ListTarget, + ListRelative, + ListMapping +}; + +inline std::wostream &operator<<(std::wostream &str, const QString &s) +{ +#ifdef Q_OS_WIN + str << reinterpret_cast<const wchar_t *>(s.utf16()); +#else + str << s.toStdWString(); +#endif + return str; +} + +// Container class for JSON output +class JsonOutput +{ + using SourceTargetMapping = std::pair<QString, QString>; + using SourceTargetMappings = QList<SourceTargetMapping>; + +public: + void addFile(const QString &source, const QString &target) + { + m_files.append(SourceTargetMapping(source, target)); + } + + void removeTargetDirectory(const QString &targetDirectory) + { + for (int i = m_files.size() - 1; i >= 0; --i) { + if (m_files.at(i).second == targetDirectory) + m_files.removeAt(i); + } + } + + QByteArray toJson() const + { + QJsonObject document; + QJsonArray files; + for (const SourceTargetMapping &mapping : m_files) { + QJsonObject object; + object.insert(QStringLiteral("source"), QDir::toNativeSeparators(mapping.first)); + object.insert(QStringLiteral("target"), QDir::toNativeSeparators(mapping.second)); + files.append(object); + } + document.insert(QStringLiteral("files"), files); + return QJsonDocument(document).toJson(); + } + QByteArray toList(ListOption option, const QDir &base) const + { + QByteArray list; + for (const SourceTargetMapping &mapping : m_files) { + const QString source = QDir::toNativeSeparators(mapping.first); + const QString fileName = QFileInfo(mapping.first).fileName(); + const QString target = QDir::toNativeSeparators(mapping.second) + QDir::separator() + fileName; + switch (option) { + case ListNone: + break; + case ListSource: + list += source.toUtf8() + '\n'; + break; + case ListTarget: + list += target.toUtf8() + '\n'; + break; + case ListRelative: + list += QDir::toNativeSeparators(base.relativeFilePath(target)).toUtf8() + '\n'; + break; + case ListMapping: + list += '"' + source.toUtf8() + "\" \"" + QDir::toNativeSeparators(base.relativeFilePath(target)).toUtf8() + "\"\n"; + break; + } + } + return list; + } +private: + SourceTargetMappings m_files; +}; + +#ifdef Q_OS_WIN +QString normalizeFileName(const QString &name); +QString winErrorMessage(unsigned long error); +QString findSdkTool(const QString &tool); +#else // !Q_OS_WIN +inline QString normalizeFileName(const QString &name) { return name; } +#endif // !Q_OS_WIN + +static const char windowsSharedLibrarySuffix[] = ".dll"; + +inline QString sharedLibrarySuffix() { return QLatin1StringView(windowsSharedLibrarySuffix); } +bool isBuildDirectory(Platform platform, const QString &dirName); + +bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage); +bool createDirectory(const QString &directory, QString *errorMessage, bool dryRun); +QString findInPath(const QString &file); + +extern const char *qmakeInfixKey; // Fake key containing the libinfix + +QMap<QString, QString> queryQtPaths(const QString &qmakeBinary, QString *errorMessage); + +enum DebugMatchMode { + MatchDebug, + MatchRelease, + MatchDebugOrRelease +}; + +QStringList findSharedLibraries(const QDir &directory, Platform platform, + DebugMatchMode debugMatchMode, + const QString &prefix = QString()); + +bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, + const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage); +bool runProcess(const QString &binary, const QStringList &args, + const QString &workingDirectory = QString(), + unsigned long *exitCode = 0, QByteArray *stdOut = 0, QByteArray *stdErr = 0, + QString *errorMessage = 0); + +bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage, + QStringList *dependentLibraries = 0, unsigned *wordSize = 0, + bool *isDebug = 0, bool isMinGW = false, unsigned short *machineArch = nullptr); + +#ifdef Q_OS_WIN +# if !defined(IMAGE_FILE_MACHINE_ARM64) +# define IMAGE_FILE_MACHINE_ARM64 0xAA64 +# endif +QString getArchString (unsigned short machineArch); +#endif // Q_OS_WIN + +// Return dependent modules of executable files. + +inline QStringList findDependentLibraries(const QString &executableFileName, QString *errorMessage) +{ + QStringList result; + readPeExecutable(executableFileName, errorMessage, &result); + return result; +} + +QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize); +QStringList findDxc(Platform platform, const QString &qtBinDir, unsigned wordSize); + +bool patchQtCore(const QString &path, QString *errorMessage); + +extern int optVerboseLevel; + +// Recursively update a file or directory, matching DirectoryFileEntryFunction against the QDir +// to obtain the files. +enum UpdateFileFlag { + ForceUpdateFile = 0x1, + SkipUpdateFile = 0x2, + RemoveEmptyQmlDirectories = 0x4, + SkipQmlDesignerSpecificsDirectories = 0x8 +}; + +template <class DirectoryFileEntryFunction> +bool updateFile(const QString &sourceFileName, + DirectoryFileEntryFunction directoryFileEntryFunction, + const QString &targetDirectory, + unsigned flags, + JsonOutput *json, + QString *errorMessage) +{ + const QFileInfo sourceFileInfo(sourceFileName); + const QString targetFileName = targetDirectory + u'/' + sourceFileInfo.fileName(); + if (optVerboseLevel > 1) + std::wcout << "Checking " << sourceFileName << ", " << targetFileName << '\n'; + + if (!sourceFileInfo.exists()) { + *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + const QFileInfo targetFileInfo(targetFileName); + + if (sourceFileInfo.isSymLink()) { + const QString sourcePath = sourceFileInfo.symLinkTarget(); + const QString relativeSource = QDir(sourceFileInfo.absolutePath()).relativeFilePath(sourcePath); + if (relativeSource.contains(u'/')) { + *errorMessage = QString::fromLatin1("Symbolic links across directories are not supported (%1).") + .arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + // Update the linked-to file + if (!updateFile(sourcePath, directoryFileEntryFunction, targetDirectory, flags, json, errorMessage)) + return false; + + if (targetFileInfo.exists()) { + if (!targetFileInfo.isSymLink()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a symbolic link.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } // Not a symlink + const QString relativeTarget = QDir(targetFileInfo.absolutePath()).relativeFilePath(targetFileInfo.symLinkTarget()); + if (relativeSource == relativeTarget) // Exists and points to same entry: happy. + return true; + QFile existingTargetFile(targetFileName); + if (!(flags & SkipUpdateFile) && !existingTargetFile.remove()) { + *errorMessage = QString::fromLatin1("Cannot remove existing symbolic link %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), existingTargetFile.errorString()); + return false; + } + } // target symbolic link exists + return createSymbolicLink(QFileInfo(targetDirectory + u'/' + relativeSource), sourceFileInfo.fileName(), errorMessage); + } // Source is symbolic link + + if (sourceFileInfo.isDir()) { + if ((flags & SkipQmlDesignerSpecificsDirectories) && sourceFileInfo.fileName() == QLatin1StringView("designer")) { + if (optVerboseLevel) + std::wcout << "Skipping " << QDir::toNativeSeparators(sourceFileName) << ".\n"; + return true; + } + bool created = false; + if (targetFileInfo.exists()) { + if (!targetFileInfo.isDir()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } // Not a directory. + } else { // exists. + QDir d(targetDirectory); + if (optVerboseLevel) + std::wcout << "Creating " << targetFileName << ".\n"; + if (!(flags & SkipUpdateFile)) { + created = d.mkdir(sourceFileInfo.fileName()); + if (!created) { + *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.") + .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory)); + return false; + } + } + } + // Recurse into directory + QDir dir(sourceFileName); + + const QStringList allEntries = directoryFileEntryFunction(dir) + dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &entry : allEntries) + if (!updateFile(sourceFileName + u'/' + entry, directoryFileEntryFunction, targetFileName, flags, json, errorMessage)) + return false; + // Remove empty directories, for example QML import folders for which the filter did not match. + if (created && (flags & RemoveEmptyQmlDirectories)) { + QDir d(targetFileName); + const QStringList entries = d.entryList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + if (entries.isEmpty() || (entries.size() == 1 && entries.first() == QLatin1StringView("qmldir"))) { + if (!d.removeRecursively()) { + *errorMessage = QString::fromLatin1("Cannot remove empty directory %1.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } + if (json) + json->removeTargetDirectory(targetFileName); + } + } + return true; + } // Source is directory. + + if (targetFileInfo.exists()) { + if (!(flags & ForceUpdateFile) + && targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) { + if (optVerboseLevel) + std::wcout << sourceFileInfo.fileName() << " is up to date.\n"; + if (json) + json->addFile(sourceFileName, targetDirectory); + return true; + } + QFile targetFile(targetFileName); + if (!(flags & SkipUpdateFile) && !targetFile.remove()) { + *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString()); + return false; + } + } // target exists + QFile file(sourceFileName); + if (optVerboseLevel) + std::wcout << "Updating " << sourceFileInfo.fileName() << ".\n"; + if (!(flags & SkipUpdateFile)) { + if (!file.copy(targetFileName)) { + *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3") + .arg(QDir::toNativeSeparators(sourceFileName), + QDir::toNativeSeparators(targetFileName), + file.errorString()); + return false; + } + if (!(file.permissions() & QFile::WriteUser)) { // QTBUG-40152, clear inherited read-only attribute + QFile targetFile(targetFileName); + if (!targetFile.setPermissions(targetFile.permissions() | QFile::WriteUser)) { + *errorMessage = QString::fromLatin1("Cannot set write permission on %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), file.errorString()); + return false; + } + } // Check permissions + } // !SkipUpdateFile + if (json) + json->addFile(sourceFileName, targetDirectory); + return true; +} + +// Base class to filter files by name filters functions to be passed to updateFile(). +class NameFilterFileEntryFunction { +public: + explicit NameFilterFileEntryFunction(const QStringList &nameFilters) : m_nameFilters(nameFilters) {} + QStringList operator()(const QDir &dir) const { return dir.entryList(m_nameFilters, QDir::Files); } + +private: + const QStringList m_nameFilters; +}; + +// Convenience for all files. +inline bool updateFile(const QString &sourceFileName, const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage) +{ + return updateFile(sourceFileName, NameFilterFileEntryFunction(QStringList()), targetDirectory, flags, json, errorMessage); +} + +QT_END_NAMESPACE + +#endif // UTILS_H |