Oliver Wolff f017087214 windeployqt: Make "recursive plugin deployment" the default
5010eda5345bdbfc12e134d6fb3ae5b7370e2185 added a command line options
which made sure that plugins of dependent Qt modules were deployed. As
these plugins are mandatory for proper functionality of these modules,
this should be windeployqt's default. This change reflects this reality.
Deploy plugins of dependent Qt libraries by default.

The command line option was removed as opting out of this functionality
does not make sense

[ChangeLog][Tools][windeployqt] windeployqt now deploys plugins of all
dependent Qt modules by default. "--include-soft-plugins" became default
and the command line option was removed.

Change-Id: Ifc5e4d24803b0398c249461f14f2e58562477c80
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
2025-04-10 12:35:45 +00:00

2026 lines
87 KiB
C++

// 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, bool internal)
{
QString result = moduleName
.mid(3) // strip the "Qt6" prefix
.toLower();
if (result == u"help"_s)
result.prepend("qt"_L1);
if (internal)
result.append("Internal"_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, qtModule.internal).toUtf8()
: qtModule.name.toUtf8());
}
}
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;
if (xSpec.contains("g++"_L1))
return WindowsDesktopMinGW;
return WindowsDesktopMsvc;
}
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 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, module.internal);
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;
}
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, firstModule.internal);
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 (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;
const QString webProcessTargetDir = sourceOptions.platform & WindowsBased
&& !sourceOptions.libraryDirectory.isEmpty() ?
sourceOptions.libraryDirectory :
sourceOptions.directory;
if (!updateFile(webProcessSource, webProcessTargetDir, sourceOptions.updateFileFlags, sourceOptions.json, errorMessage))
return false;
Options options(sourceOptions);
options.binaries.append(webProcessTargetDir + 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);
// We are on MSVC and not crosscompiling. We need the host arch
if (options.platform == WindowsDesktopMsvc) {
SYSTEM_INFO si;
GetSystemInfo(&si);
switch (si.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_INTEL:
case PROCESSOR_ARCHITECTURE_IA64:
case PROCESSOR_ARCHITECTURE_AMD64:
options.platform |= IntelBased;
break;
case PROCESSOR_ARCHITECTURE_ARM:
case PROCESSOR_ARCHITECTURE_ARM64:
options.platform |= ArmBased;
break;
default:
options.platform = UnknownPlatform;
}
}
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;
}