summaryrefslogtreecommitdiffstats
path: root/src/tools/windeployqt
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/windeployqt')
-rw-r--r--src/tools/windeployqt/CMakeLists.txt33
-rw-r--r--src/tools/windeployqt/main.cpp2007
-rw-r--r--src/tools/windeployqt/qmlutils.cpp138
-rw-r--r--src/tools/windeployqt/qmlutils.h40
-rw-r--r--src/tools/windeployqt/qtmoduleinfo.cpp183
-rw-r--r--src/tools/windeployqt/qtmoduleinfo.h51
-rw-r--r--src/tools/windeployqt/qtplugininfo.cpp100
-rw-r--r--src/tools/windeployqt/qtplugininfo.h48
-rw-r--r--src/tools/windeployqt/utils.cpp1022
-rw-r--r--src/tools/windeployqt/utils.h366
10 files changed, 3988 insertions, 0 deletions
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