// 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 #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #else #define IMAGE_FILE_MACHINE_ARM64 0xaa64 #endif #include #include #include #include #include #include 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 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; return xSpec.contains("g++"_L1) ? WindowsDesktopMinGW : 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 = WindowsDesktopMsvc; 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; using OptionPtrVector = QList; 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. ) to the PATH variable and then run:\n windeployqt \n\n" "If your application uses Qt Quick, run:\n windeployqt --qmldir "_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 == WindowsDesktopMsvc || parser->isSet(compilerRunTimeOption)) options->compilerRunTime = true; if (parser->isSet(noCompilerRunTimeOption)) options->compilerRunTime = false; if (options->compilerRunTime && options->platform != WindowsDesktopMinGW && options->platform != 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.dll' or 'path/libQt6Core.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'. 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_.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 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 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 WindowsDesktopMsvc: { // 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.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 &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 &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 &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 &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 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; }