// 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 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, 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;
    }

    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, 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 (pluginSelections.includeSoftPlugins && missingQtModulesAdded) {
        if (optVerboseLevel) {
            std::wcout << "Performing additional pass of finding Qt plugins due to updated Qt module list: "
                       << formatQtModules(*usedQtModules).constData() << "\n";
        }
        return findQtPlugins(usedQtModules, disabledQtModules, pluginInfo, pluginSelections, qtPluginsDirName,
                             libraryLocation, infix, debugMatchModeIn, platform, platformPlugin,
                             deployInsightTrackerPlugin, deployOpenSslPlugin);
    }

    return result;
}

static QStringList translationNameFilters(const ModuleBitset &modules, const QString &prefix)
{
    QStringList result;
    for (const auto &qtModule : qtModuleEntries) {
        if (modules.test(qtModule.id) && !qtModule.translationCatalog.isEmpty()) {
            const QString name = qtModule.translationCatalog + u'_' + prefix + ".qm"_L1;
            if (!result.contains(name))
                result.push_back(name);
        }
    }
    return result;
}

static bool deployTranslations(const QString &sourcePath, const ModuleBitset &usedQtModules,
                               const QString &target, const Options &options,
                               QString *errorMessage)
{
    // Find available languages prefixes by checking on qtbase.
    QStringList prefixes;
    QDir sourceDir(sourcePath);
    const QStringList qmFilter = QStringList(QStringLiteral("qtbase_*.qm"));
    const QFileInfoList &qmFiles = sourceDir.entryInfoList(qmFilter);
    for (const QFileInfo &qmFi : qmFiles) {
        const QString prefix = qmFi.baseName().mid(7);
        if (options.languages.isEmpty() || options.languages.contains(prefix))
            prefixes.append(prefix);
    }
    if (prefixes.isEmpty()) {
        std::wcerr << "Warning: Could not find any translations in "
                   << QDir::toNativeSeparators(sourcePath) << " (developer build?)\n.";
        return true;
    }
    // Run lconvert to concatenate all files into a single named "qt_<prefix>.qm" in the application folder
    // Use QT_INSTALL_TRANSLATIONS as working directory to keep the command line short.
    const QString absoluteTarget = QFileInfo(target).absoluteFilePath();
    const QString binary = QStringLiteral("lconvert");
    QStringList arguments;
    for (const QString &prefix : std::as_const(prefixes)) {
        arguments.clear();
        const QString targetFile = QStringLiteral("qt_") + prefix + QStringLiteral(".qm");
        arguments.append(QStringLiteral("-o"));
        const QString targetFilePath = absoluteTarget + u'/' + targetFile;
        if (options.json)
            options.json->addFile(sourcePath +  u'/' + targetFile, absoluteTarget);
        arguments.append(QDir::toNativeSeparators(targetFilePath));
        const QStringList translationFilters = translationNameFilters(usedQtModules, prefix);
        if (translationFilters.isEmpty()){
            std::wcerr << "Warning: translation catalogs are all empty, skipping translation deployment\n";
            return true;
        }
        const QFileInfoList &langQmFiles = sourceDir.entryInfoList(translationFilters);
        for (const QFileInfo &langQmFileFi : langQmFiles) {
            if (options.json) {
                options.json->addFile(langQmFileFi.absoluteFilePath(),
                                      absoluteTarget);
            }
            arguments.append(langQmFileFi.fileName());
        }
        if (optVerboseLevel)
            std::wcout << "Creating " << targetFile << "...\n";
        unsigned long exitCode;
        if ((options.updateFileFlags & SkipUpdateFile) == 0
            && (!runProcess(binary, arguments, sourcePath, &exitCode, nullptr, nullptr, errorMessage)
                || exitCode)) {
            return false;
        }
    } // for prefixes.
    return true;
}

static QStringList findFFmpegLibs(const QString &qtBinDir, Platform platform)
{
    const std::vector<QLatin1StringView> ffmpegHints = { "avcodec"_L1, "avformat"_L1, "avutil"_L1,
                                                         "swresample"_L1, "swscale"_L1 };
    const QStringList bundledLibs =
            findSharedLibraries(qtBinDir, platform, MatchDebugOrRelease, {});

    QStringList ffmpegLibs;
    for (const QLatin1StringView &libHint : ffmpegHints) {
        const QStringList ffmpegLib = bundledLibs.filter(libHint, Qt::CaseInsensitive);

        if (ffmpegLib.empty()) {
            std::wcerr << "Warning: Cannot find FFmpeg libraries. Multimedia features will not work as expected.\n";
            return {};
        } else if (ffmpegLib.size() != 1u) {
            std::wcerr << "Warning: Multiple versions of FFmpeg libraries found. Multimedia features will not work as expected.\n";
            return {};
        }

        const QChar slash(u'/');
        QFileInfo ffmpegLibPath{ qtBinDir + slash + ffmpegLib.front() };
        ffmpegLibs.append(ffmpegLibPath.absoluteFilePath());
    }

    return ffmpegLibs;
}

// Find the openssl libraries Qt executables depend on.
static QStringList findOpenSslLibraries(const QString &openSslRootDir, Platform platform)
{
    const std::vector<QLatin1StringView> libHints = { "libcrypto"_L1, "libssl"_L1 };
    const QChar slash(u'/');
    const QString openSslBinDir = openSslRootDir + slash + "bin"_L1;
    const QStringList openSslRootLibs =
            findSharedLibraries(openSslBinDir, platform, MatchDebugOrRelease, {});

    QStringList result;
    for (const QLatin1StringView &libHint : libHints) {
        const QStringList lib = openSslRootLibs.filter(libHint, Qt::CaseInsensitive);

        if (lib.empty()) {
            std::wcerr << "Warning: Cannot find openssl libraries.\n";
            return {};
        } else if (lib.size() != 1u) {
            std::wcerr << "Warning: Multiple versions of openssl libraries found.\n";
            return {};
        }

        QFileInfo libPath{ openSslBinDir + slash + lib.front() };
        result.append(libPath.absoluteFilePath());
    }

    return result;
}


struct DeployResult
{
    operator bool() const { return success; }

    bool success = false;
    bool isDebug = false;
    ModuleBitset directlyUsedQtLibraries;
    ModuleBitset usedQtLibraries;
    ModuleBitset deployedQtLibraries;
};

static QString libraryPath(const QString &libraryLocation, const char *name,
                           const QString &infix, Platform platform, bool debug)
{
    QString result = libraryLocation + u'/';
    result += QLatin1StringView(name);
    result += infix;
    if (debug && platformHasDebugSuffix(platform))
        result += u'd';
    result += sharedLibrarySuffix();
    return result;
}

static QString vcDebugRedistDir() { return QStringLiteral("Debug_NonRedist"); }

static QString vcRedistDir()
{
    const char vcDirVar[] = "VCINSTALLDIR";
    const QChar slash(u'/');
    QString vcRedistDirName = QDir::cleanPath(QFile::decodeName(qgetenv(vcDirVar)));
    if (vcRedistDirName.isEmpty()) {
        std::wcerr << "Warning: Cannot find Visual Studio installation directory, " << vcDirVar
            << " is not set.\n";
        return QString();
    }
    if (!vcRedistDirName.endsWith(slash))
        vcRedistDirName.append(slash);
    vcRedistDirName.append(QStringLiteral("redist/MSVC"));
    if (!QFileInfo(vcRedistDirName).isDir()) {
        std::wcerr << "Warning: Cannot find Visual Studio redist directory, "
            << QDir::toNativeSeparators(vcRedistDirName).toStdWString() << ".\n";
        return QString();
    }
    // Look in reverse order for folder containing the debug redist folder
    const QFileInfoList subDirs =
            QDir(vcRedistDirName)
                    .entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name | QDir::Reversed);
    for (const QFileInfo &f : subDirs) {
        QString path = f.absoluteFilePath();
        if (QFileInfo(path + slash + vcDebugRedistDir()).isDir())
            return path;
        path += QStringLiteral("/onecore");
        if (QFileInfo(path + slash + vcDebugRedistDir()).isDir())
            return path;
    }
    std::wcerr << "Warning: Cannot find Visual Studio redist directory under "
               << QDir::toNativeSeparators(vcRedistDirName).toStdWString() << ".\n";
    return QString();
}

static QStringList findMinGWRuntimePaths(const QString &qtBinDir, Platform platform, const QStringList &runtimeFilters)
{
    //MinGW: Add runtime libraries. Check first for the Qt binary directory, and default to path if nothing is found.
    QStringList result;
    const bool isClang = platform == WindowsDesktopClangMinGW;
    QStringList filters;
    const QString suffix = u'*' + sharedLibrarySuffix();
    for (const auto &minGWRuntime : runtimeFilters)
        filters.append(minGWRuntime + suffix);

    QFileInfoList dlls = QDir(qtBinDir).entryInfoList(filters, QDir::Files);
    if (dlls.isEmpty()) {
        std::wcerr << "Warning: Runtime libraries not found in Qt binary folder, defaulting to looking in path\n";
        const QString binaryPath = isClang ? findInPath("clang++.exe"_L1) : findInPath("g++.exe"_L1);
        if (binaryPath.isEmpty()) {
            std::wcerr << "Warning: Cannot find " << (isClang ? "Clang" : "GCC") << " installation directory, " << (isClang ? "clang++" : "g++") << ".exe must be in the path\n";
            return {};
        }
        const QString binaryFolder = QFileInfo(binaryPath).absolutePath();
        dlls = QDir(binaryFolder).entryInfoList(filters, QDir::Files);
    }

    for (const QFileInfo &dllFi : dlls)
        result.append(dllFi.absoluteFilePath());

    return result;
}

static QStringList compilerRunTimeLibs(const QString &qtBinDir, Platform platform, bool isDebug, unsigned short machineArch)
{
    QStringList result;
    switch (platform) {
    case WindowsDesktopMinGW: {
        const QStringList minGWRuntimes = { "*gcc_"_L1, "*stdc++"_L1, "*winpthread"_L1 };
        result.append(findMinGWRuntimePaths(qtBinDir, platform, minGWRuntimes));
        break;
    }
    case WindowsDesktopClangMinGW: {
        const QStringList clangMinGWRuntimes = { "*unwind"_L1, "*c++"_L1 };
        result.append(findMinGWRuntimePaths(qtBinDir, platform, clangMinGWRuntimes));
        break;
    }
#ifdef Q_OS_WIN
    case WindowsDesktopMsvcIntel:
    case WindowsDesktopMsvcArm: { // MSVC/Desktop: Add redistributable packages.
        QString vcRedistDirName = vcRedistDir();
        if (vcRedistDirName.isEmpty())
             break;
        QStringList redistFiles;
        QDir vcRedistDir(vcRedistDirName);
        const QString machineArchString = getArchString(machineArch);
        if (isDebug) {
            // Append DLLs from Debug_NonRedist\x??\Microsoft.VC<version>.DebugCRT.
            if (vcRedistDir.cd(vcDebugRedistDir()) && vcRedistDir.cd(machineArchString)) {
                const QStringList names = vcRedistDir.entryList(QStringList(QStringLiteral("Microsoft.VC*.DebugCRT")), QDir::Dirs);
                if (!names.isEmpty() && vcRedistDir.cd(names.first())) {
                    const QFileInfoList &dlls = vcRedistDir.entryInfoList(QStringList("*.dll"_L1));
                    for (const QFileInfo &dll : dlls)
                        redistFiles.append(dll.absoluteFilePath());
                }
            }
        } else { // release: Bundle vcredist<>.exe
            QString releaseRedistDir = vcRedistDirName;
            const QStringList countryCodes = vcRedistDir.entryList(QStringList(QStringLiteral("[0-9]*")), QDir::Dirs);
            if (!countryCodes.isEmpty()) // Pre MSVC2017
                releaseRedistDir += u'/' + countryCodes.constFirst();
            QFileInfo fi(releaseRedistDir + "/vc_redist."_L1
                         + machineArchString + ".exe"_L1);
            if (!fi.isFile()) { // Pre MSVC2017/15.5
                fi.setFile(releaseRedistDir + "/vcredist_"_L1
                           + machineArchString + ".exe"_L1);
            }
            if (fi.isFile())
                redistFiles.append(fi.absoluteFilePath());
        }
        if (redistFiles.isEmpty()) {
            std::wcerr << "Warning: Cannot find Visual Studio " << (isDebug ? "debug" : "release")
                       << " redistributable files in " << QDir::toNativeSeparators(vcRedistDirName).toStdWString() << ".\n";
            break;
        }
        result.append(redistFiles);
    }
        break;
#endif // Q_OS_WIN
    default:
        break;
    }
    return result;
}

static inline int qtVersion(const QMap<QString, QString> &qtpathsVariables)
{
    const QString versionString = qtpathsVariables.value(QStringLiteral("QT_VERSION"));
    const QChar dot = u'.';
    const int majorVersion = versionString.section(dot, 0, 0).toInt();
    const int minorVersion = versionString.section(dot, 1, 1).toInt();
    const int patchVersion = versionString.section(dot, 2, 2).toInt();
    return (majorVersion << 16) | (minorVersion << 8) | patchVersion;
}

// Deploy a library along with its .pdb debug info file (MSVC) should it exist.
static bool updateLibrary(const QString &sourceFileName, const QString &targetDirectory,
                          const Options &options, QString *errorMessage)
{
    if (!updateFile(sourceFileName, targetDirectory, options.updateFileFlags, options.json, errorMessage)) {
        if (options.ignoreLibraryErrors) {
            std::wcerr << "Warning: Could not update " << sourceFileName << " :" << *errorMessage << '\n';
            errorMessage->clear();
            return true;
        }
        return false;
    }

    if (options.deployPdb) {
        const QFileInfo pdb(pdbFileName(sourceFileName));
        if (pdb.isFile())
            return updateFile(pdb.absoluteFilePath(), targetDirectory, options.updateFileFlags, nullptr, errorMessage);
    }
    return true;
}

// Find out the ICU version to add the data library icudtXX.dll, which does not
// show as a dependency.
static QString getIcuVersion(const QString &libName)
{
    QString version;
    std::copy_if(libName.cbegin(), libName.cend(), std::back_inserter(version),
                 [](QChar c) { return c.isDigit(); });
    return version;
}

static DeployResult deploy(const Options &options, const QMap<QString, QString> &qtpathsVariables,
                           const PluginInformation &pluginInfo, QString *errorMessage)
{
    DeployResult result;

    const QChar slash = u'/';

    const QString qtBinDir = qtpathsVariables.value(QStringLiteral("QT_INSTALL_BINS"));
    const QString libraryLocation = qtBinDir;
    const QString infix = qtpathsVariables.value(QLatin1StringView(qmakeInfixKey));
    const int version = qtVersion(qtpathsVariables);
    Q_UNUSED(version);

    if (optVerboseLevel > 1)
        std::wcout << "Qt binaries in " << QDir::toNativeSeparators(qtBinDir) << '\n';

    QStringList dependentQtLibs;
    bool detectedDebug;
    unsigned wordSize;
    unsigned short machineArch;
    int directDependencyCount = 0;
    if (!findDependentQtLibraries(libraryLocation, options.binaries.first(), options.platform, errorMessage, &dependentQtLibs, &wordSize,
                                  &detectedDebug, &machineArch, &directDependencyCount)) {
        return result;
    }
    for (int b = 1; b < options.binaries.size(); ++b) {
        if (!findDependentQtLibraries(libraryLocation, options.binaries.at(b), options.platform, errorMessage, &dependentQtLibs,
                                      nullptr, nullptr, nullptr)) {
            return result;
        }
    }

    DebugMatchMode debugMatchMode = MatchDebugOrRelease;
    result.isDebug = false;
    switch (options.debugDetection) {
    case Options::DebugDetectionAuto:
        // Debug detection is only relevant for Msvc/ClangMsvc which have distinct
        // runtimes and binaries. For anything else, use MatchDebugOrRelease
        // since also debug cannot be reliably detect for MinGW.
        if (options.platform.testFlag(Msvc) || options.platform.testFlag(ClangMsvc)) {
            result.isDebug = detectedDebug;
            debugMatchMode = result.isDebug ? MatchDebug : MatchRelease;
        }
        break;
    case Options::DebugDetectionForceDebug:
        result.isDebug = true;
        debugMatchMode = MatchDebug;
        break;
    case Options::DebugDetectionForceRelease:
        debugMatchMode = MatchRelease;
        break;
    }

    // Determine application type, check Quick2 is used by looking at the
    // direct dependencies (do not be fooled by QtWebKit depending on it).
    for (int m = 0; m < dependentQtLibs.size(); ++m) {
        const qint64 module = qtModule(dependentQtLibs.at(m), infix);
        if (module >= 0)
            result.directlyUsedQtLibraries[module] = 1;
    }

    const bool usesQml = result.directlyUsedQtLibraries.test(QtQmlModuleId);
    const bool usesQuick = result.directlyUsedQtLibraries.test(QtQuickModuleId);
    const bool uses3DQuick = result.directlyUsedQtLibraries.test(Qt3DQuickModuleId);
    const bool usesQml2 = !(options.disabledLibraries.test(QtQmlModuleId))
        && (usesQml || usesQuick || uses3DQuick || (options.additionalLibraries.test(QtQmlModuleId)));

    if (optVerboseLevel) {
        std::wcout << QDir::toNativeSeparators(options.binaries.first()) << ' '
                   << wordSize << " bit, " << (result.isDebug ? "debug" : "release")
                   << " executable";
        if (usesQml2)
            std::wcout << " [QML]";
        std::wcout << '\n';
    }

    if (dependentQtLibs.isEmpty()) {
        *errorMessage = QDir::toNativeSeparators(options.binaries.first()) +  QStringLiteral(" does not seem to be a Qt executable.");
        return result;
    }

    // Some Windows-specific checks: Qt5Core depends on ICU when configured with "-icu". Other than
    // that, Qt5WebKit has a hard dependency on ICU.
    if (options.platform.testFlag(WindowsBased))  {
        const QStringList qtLibs = dependentQtLibs.filter(QStringLiteral("Qt6Core"), Qt::CaseInsensitive)
            + dependentQtLibs.filter(QStringLiteral("Qt5WebKit"), Qt::CaseInsensitive);
        for (const QString &qtLib : qtLibs) {
            QStringList icuLibs = findDependentLibraries(qtLib, errorMessage).filter(QStringLiteral("ICU"), Qt::CaseInsensitive);
            if (!icuLibs.isEmpty()) {
                // Find out the ICU version to add the data library icudtXX.dll, which does not show
                // as a dependency.
                const QString icuVersion = getIcuVersion(icuLibs.constFirst());
                if (!icuVersion.isEmpty())  {
                    if (optVerboseLevel > 1)
                        std::wcout << "Adding ICU version " << icuVersion << '\n';
                    QString icuLib = QStringLiteral("icudt") + icuVersion
                            + QLatin1StringView(windowsSharedLibrarySuffix);
                    // Some packages contain debug dlls of ICU libraries even though it's a C
                    // library and the official packages do not differentiate (QTBUG-87677)
                    if (result.isDebug) {
                        const QString icuLibCandidate = QStringLiteral("icudtd") + icuVersion
                                + QLatin1StringView(windowsSharedLibrarySuffix);
                        if (!findInPath(icuLibCandidate).isEmpty()) {
                            icuLib = icuLibCandidate;
                        }
                    }
                    icuLibs.push_back(icuLib);
                }
                for (const QString &icuLib : std::as_const(icuLibs)) {
                    const QString icuPath = findInPath(icuLib);
                    if (icuPath.isEmpty()) {
                        *errorMessage = QStringLiteral("Unable to locate ICU library ") + icuLib;
                        return result;
                    }
                    dependentQtLibs.push_back(icuPath);
                } // for each icuLib
                break;
            } // !icuLibs.isEmpty()
        } // Qt6Core/Qt6WebKit
    } // Windows

    // Scan Quick2 imports
    QmlImportScanResult qmlScanResult;
    if (options.quickImports && usesQml2) {
        // Custom list of import paths provided by user
        QStringList qmlImportPaths = options.qmlImportPaths;
        // Qt's own QML modules
        qmlImportPaths << qtpathsVariables.value(QStringLiteral("QT_INSTALL_QML"));
        QStringList qmlDirectories = options.qmlDirectories;
        if (qmlDirectories.isEmpty()) {
            const QString qmlDirectory = findQmlDirectory(options.platform, options.directory);
            if (!qmlDirectory.isEmpty())
                qmlDirectories.append(qmlDirectory);
        }
        for (const QString &qmlDirectory : std::as_const(qmlDirectories)) {
            if (optVerboseLevel >= 1)
                std::wcout << "Scanning " << QDir::toNativeSeparators(qmlDirectory) << ":\n";
            const QmlImportScanResult scanResult =
                runQmlImportScanner(qmlDirectory, qmlImportPaths,
                                    result.directlyUsedQtLibraries.test(QtWidgetsModuleId),
                                    options.platform, debugMatchMode, errorMessage);
            if (!scanResult.ok)
                return result;
            qmlScanResult.append(scanResult);
            // Additional dependencies of QML plugins.
            for (const QString &plugin : std::as_const(qmlScanResult.plugins)) {
                if (!findDependentQtLibraries(libraryLocation, plugin, options.platform, errorMessage, &dependentQtLibs, &wordSize, &detectedDebug, &machineArch))
                    return result;
            }
            if (optVerboseLevel >= 1) {
                std::wcout << "QML imports:\n";
                for (const QmlImportScanResult::Module &mod : std::as_const(qmlScanResult.modules)) {
                    std::wcout << "  '" << mod.name << "' "
                               << QDir::toNativeSeparators(mod.sourcePath) << '\n';
                }
                if (optVerboseLevel >= 2) {
                    std::wcout << "QML plugins:\n";
                    for (const QString &p : std::as_const(qmlScanResult.plugins))
                        std::wcout << "  " << QDir::toNativeSeparators(p) << '\n';
                }
            }
        }
    }

    QString platformPlugin;
    // Sort apart Qt 5 libraries in the ones that are represented by the
    // QtModule enumeration (and thus controlled by flags) and others.
    QStringList deployedQtLibraries;
    for (int i = 0 ; i < dependentQtLibs.size(); ++i)  {
        const qint64 module = qtModule(dependentQtLibs.at(i), infix);
        if (module >= 0)
            result.usedQtLibraries[module] = 1;
        else
            deployedQtLibraries.push_back(dependentQtLibs.at(i)); // Not represented by flag.
    }
    result.deployedQtLibraries = (result.usedQtLibraries | options.additionalLibraries) & ~options.disabledLibraries;

    ModuleBitset disabled = options.disabledLibraries;
    if (!usesQml2) {
        disabled[QtQmlModuleId] = 1;
        disabled[QtQuickModuleId] = 1;
    }

    QStringList openSslLibs;
    if (!options.openSslRootDirectory.isEmpty()) {
        openSslLibs = findOpenSslLibraries(options.openSslRootDirectory, options.platform);
        if (openSslLibs.isEmpty()) {
            *errorMessage = QStringLiteral("Unable to find openSSL libraries in ")
                    + options.openSslRootDirectory;
            return result;
        }

        deployedQtLibraries.append(openSslLibs);
    }
    const bool deployOpenSslPlugin = options.forceOpenSslPlugin || !openSslLibs.isEmpty();

    const QStringList plugins = findQtPlugins(
            &result.deployedQtLibraries,
            // For non-QML applications, disable QML to prevent it from being pulled in by the
            // qtaccessiblequick plugin.
            disabled, pluginInfo,
            options.pluginSelections, qtpathsVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")),
            libraryLocation, infix, debugMatchMode, options.platform, &platformPlugin,
            options.deployInsightTrackerPlugin, deployOpenSslPlugin);

    // Apply options flags and re-add library names.
    QString qtGuiLibrary;
    for (const auto &qtModule : qtModuleEntries) {
        if (result.deployedQtLibraries.test(qtModule.id)) {
            const QString library = libraryPath(libraryLocation, qtModule.name.toUtf8(), infix,
                                                options.platform, result.isDebug);
            deployedQtLibraries.append(library);
            if (qtModule.id == QtGuiModuleId)
                qtGuiLibrary = library;
        }
    }

    if (optVerboseLevel >= 1) {
        std::wcout << "Direct dependencies: " << formatQtModules(result.directlyUsedQtLibraries).constData()
                   << "\nAll dependencies   : " << formatQtModules(result.usedQtLibraries).constData()
                   << "\nTo be deployed     : " << formatQtModules(result.deployedQtLibraries).constData() << '\n';
    }

    if (optVerboseLevel > 1)
        std::wcout << "Plugins: " << plugins.join(u',') << '\n';

    if (result.deployedQtLibraries.test(QtGuiModuleId) && platformPlugin.isEmpty()) {
        *errorMessage =QStringLiteral("Unable to find the platform plugin.");
        return result;
    }

    if (options.platform.testFlag(WindowsBased) && !qtGuiLibrary.isEmpty())  {
        const QStringList guiLibraries = findDependentLibraries(qtGuiLibrary, errorMessage);
        const bool dependsOnOpenGl = !guiLibraries.filter(QStringLiteral("opengl32"), Qt::CaseInsensitive).isEmpty();
        if (options.softwareRasterizer && !dependsOnOpenGl) {
            const QFileInfo softwareRasterizer(qtBinDir + slash + QStringLiteral("opengl32sw") + QLatin1StringView(windowsSharedLibrarySuffix));
            if (softwareRasterizer.isFile())
                deployedQtLibraries.append(softwareRasterizer.absoluteFilePath());
        }
        if (options.systemD3dCompiler && machineArch != IMAGE_FILE_MACHINE_ARM64) {
            const QString d3dCompiler = findD3dCompiler(options.platform, qtBinDir, wordSize);
            if (d3dCompiler.isEmpty()) {
                std::wcerr << "Warning: Cannot find any version of the d3dcompiler DLL.\n";
            } else {
                deployedQtLibraries.push_back(d3dCompiler);
            }
        }
        if (options.systemDxc) {
            const QStringList dxcLibs = findDxc(options.platform, qtBinDir, wordSize);
            if (!dxcLibs.isEmpty())
                deployedQtLibraries.append(dxcLibs);
            else
                std::wcerr << "Warning: Cannot find any version of the dxcompiler.dll and dxil.dll.\n";
        }
    } // Windows

    // Add FFmpeg if we deploy the FFmpeg backend
    if (options.ffmpeg
        && !plugins.filter(QStringLiteral("ffmpegmediaplugin"), Qt::CaseInsensitive).empty()) {
        deployedQtLibraries.append(findFFmpegLibs(qtBinDir, options.platform));
    }

    // Update libraries
    if (options.libraries) {
        const QString targetPath = options.libraryDirectory.isEmpty() ?
            options.directory : options.libraryDirectory;
        QStringList libraries = deployedQtLibraries;
        if (options.compilerRunTime)
            libraries.append(compilerRunTimeLibs(qtBinDir, options.platform, result.isDebug, machineArch));
        for (const QString &qtLib : std::as_const(libraries)) {
            if (!updateLibrary(qtLib, targetPath, options, errorMessage))
                return result;
        }

#if !QT_CONFIG(relocatable)
        if (options.patchQt  && !options.dryRun) {
            const QString qt6CoreName = QFileInfo(libraryPath(libraryLocation, "Qt6Core", infix,
                                                              options.platform, result.isDebug)).fileName();
            if (!patchQtCore(targetPath + u'/' + qt6CoreName, errorMessage)) {
                std::wcerr << "Warning: " << *errorMessage << '\n';
                errorMessage->clear();
            }
        }
#endif // QT_CONFIG(relocatable)
    } // optLibraries

    // Update plugins
    if (options.plugins) {
        const QString targetPath = options.pluginDirectory.isEmpty() ?
            options.directory : options.pluginDirectory;
        QDir dir(targetPath);
        if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) {
            *errorMessage = "Cannot create "_L1 +
                            QDir::toNativeSeparators(dir.absolutePath()) +  u'.';
            return result;
        }
        for (const QString &plugin : plugins) {
            const QString targetDirName = plugin.section(slash, -2, -2);
            const QString targetPath = dir.absoluteFilePath(targetDirName);
            if (!dir.exists(targetDirName)) {
                if (optVerboseLevel)
                    std::wcout << "Creating directory " << targetPath << ".\n";
                if (!(options.updateFileFlags & SkipUpdateFile) && !dir.mkdir(targetDirName)) {
                    *errorMessage = QStringLiteral("Cannot create ") + targetDirName +  u'.';
                    return result;
                }
            }
            if (!updateLibrary(plugin, targetPath, options, errorMessage))
                return result;
        }
    } // optPlugins

    // Update Quick imports
    // Do not be fooled by QtWebKit.dll depending on Quick into always installing Quick imports
    // for WebKit1-applications. Check direct dependency only.
    if (options.quickImports && usesQml2) {
        const QString targetPath = options.qmlDirectory.isEmpty()
                ? options.directory + QStringLiteral("/qml")
                : options.qmlDirectory;
        if (!createDirectory(targetPath, errorMessage, options.dryRun))
            return result;
        for (const QmlImportScanResult::Module &module : std::as_const(qmlScanResult.modules)) {
            const QString installPath = module.installPath(targetPath);
            if (optVerboseLevel > 1)
                std::wcout << "Installing: '" << module.name
                           << "' from " << module.sourcePath << " to "
                           << QDir::toNativeSeparators(installPath) << '\n';
            if (installPath != targetPath && !createDirectory(installPath, errorMessage, options.dryRun))
                return result;
            unsigned updateFileFlags = options.updateFileFlags
                    | SkipQmlDesignerSpecificsDirectories;
            unsigned qmlDirectoryFileFlags = 0;
            if (options.deployPdb)
                qmlDirectoryFileFlags |= QmlDirectoryFileEntryFunction::DeployPdb;
            if (!updateFile(module.sourcePath, QmlDirectoryFileEntryFunction(module.sourcePath,
                                                                             options.platform,
                                                                             debugMatchMode,
                                                                             qmlDirectoryFileFlags),
                            installPath, updateFileFlags, options.json, errorMessage)) {
                return result;
            }
        }
    } // optQuickImports

    if (options.translations) {
        if (!createDirectory(options.translationsDirectory, errorMessage, options.dryRun))
            return result;
        if (!deployTranslations(qtpathsVariables.value(QStringLiteral("QT_INSTALL_TRANSLATIONS")),
                                result.deployedQtLibraries, options.translationsDirectory, options,
                                errorMessage)) {
            return result;
        }
    }

    result.success = true;
    return result;
}

static bool deployWebProcess(const QMap<QString, QString> &qtpathsVariables, const char *binaryName,
                             const PluginInformation &pluginInfo, const Options &sourceOptions,
                             QString *errorMessage)
{
    // Copy the web process and its dependencies
    const QString webProcess = webProcessBinary(binaryName, sourceOptions.platform);
    const QString webProcessSource = qtpathsVariables.value(QStringLiteral("QT_INSTALL_LIBEXECS"))
            + u'/' + webProcess;
    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;
}
