Windeployqt: add options to deploy/block plugins

Some plugin types are pulled in by default by certain modules.
Give users the option to add/skip plugins and/or their types.

[ChangeLog][Tools][Windeployqt] Windeployqt now has options that allow for custom plugin deployment. Users can include or exclude them, either individually, or by type.

Fixes: QTBUG-117910
Change-Id: I85235783dcd814396f184912269cd5976717b2dd
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
(cherry picked from commit d53c0d721f111d53ea95eb8914ad88560a00feaa)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Timothée Keller 2023-10-12 16:58:13 +02:00 committed by Qt Cherry-pick Bot
parent c2869caa7e
commit ce6a81a6f0
4 changed files with 269 additions and 29 deletions

View File

@ -14,6 +14,7 @@ qt_internal_add_tool(${target_name}
SOURCES
qmlutils.cpp qmlutils.h
qtmoduleinfo.cpp qtmoduleinfo.h
qtplugininfo.cpp qtplugininfo.h
utils.cpp utils.h
main.cpp
DEFINES

View File

@ -4,6 +4,7 @@
#include "utils.h"
#include "qmlutils.h"
#include "qtmoduleinfo.h"
#include "qtplugininfo.h"
#include <QtCore/QCommandLineOption>
#include <QtCore/QCommandLineParser>
@ -114,6 +115,21 @@ static QByteArray formatQtModules(const ModuleBitset &mask, bool option = false)
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)) {
@ -163,8 +179,8 @@ struct Options {
bool translations = true;
bool systemD3dCompiler = true;
bool compilerRunTime = false;
QStringList disabledPluginTypes;
bool softwareRasterizer = true;
PluginLists pluginSelections;
Platform platform = WindowsDesktopMsvc;
ModuleBitset additionalLibraries;
ModuleBitset disabledLibraries;
@ -385,6 +401,21 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse
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);
@ -514,7 +545,16 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse
}
if (parser->isSet(skipPluginTypesOption))
options->disabledPluginTypes = parser->value(skipPluginTypesOption).split(u',');
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.";
@ -667,7 +707,7 @@ static inline QString lineBreak(QString s)
return s;
}
static inline QString helpText(const QCommandLineParser &p)
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
@ -686,7 +726,18 @@ static inline QString helpText(const QCommandLineParser &p)
"the name prepended by --no- (--no-xml). Available libraries:\n"_L1;
ModuleBitset mask;
moduleHelp += lineBreak(QString::fromLatin1(formatQtModules(mask.set(), true)));
moduleHelp += u'\n';
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;
}
@ -860,15 +911,16 @@ static qint64 qtModule(QString module, const QString &infix)
}
// Return the path if a plugin is to be deployed
static QString deployPlugin(const QString &plugin, const QDir &subDir,
ModuleBitset *usedQtModules, const ModuleBitset &disabledQtModules,
const QStringList &disabledPluginTypes,
const QString &libraryLocation, const QString &infix,
Platform platform, bool deployInsightTrackerPlugin)
static QString deployPlugin(const QString &plugin, const QDir &subDir, const bool dueToModule,
const DebugMatchMode &debugMatchMode, ModuleBitset *usedQtModules,
const ModuleBitset &disabledQtModules,
const PluginLists &pluginSelections, const QString &libraryLocation,
const QString &infix, Platform platform,
bool deployInsightTrackerPlugin)
{
const QString subDirName = subDir.dirName();
// Filter out disabled plugins
if (disabledPluginTypes.contains(subDirName)) {
if (pluginSelections.disabledPluginTypes.contains(subDirName)) {
std::wcout << "Skipping plugin " << plugin << " due to skipped plugin type " << subDirName << '\n';
return {};
}
@ -879,7 +931,28 @@ static QString deployPlugin(const QString &plugin, const QDir &subDir,
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 (pluginSelections.excludedPlugins.contains(pluginName)) {
std::wcout << "Skipping plugin " << plugin << " due to exclusion option" << '\n';
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).
@ -923,12 +996,22 @@ static QString deployPlugin(const QString &plugin, const QDir &subDir,
return pluginPath;
}
static bool needsPluginType(const QString &subDirName, const PluginInformation &pluginInfo,
const PluginLists &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 QStringList &disabledPluginTypes,
const PluginInformation &pluginInfo, const PluginLists &pluginSelections,
const QString &qtPluginsDirName, const QString &libraryLocation,
const QString &infix,
DebugMatchMode debugMatchModeIn, Platform platform, QString *platformPlugin,
bool deployInsightTrackerPlugin)
const QString &infix, DebugMatchMode debugMatchModeIn, Platform platform,
QString *platformPlugin, bool deployInsightTrackerPlugin)
{
if (qtPluginsDirName.isEmpty())
return QStringList();
@ -944,7 +1027,8 @@ QStringList findQtPlugins(ModuleBitset *usedQtModules, const ModuleBitset &disab
}
continue;
}
if (usedQtModules->test(module)) {
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;
@ -960,12 +1044,13 @@ QStringList findQtPlugins(ModuleBitset *usedQtModules, const ModuleBitset &disab
} else {
filter = u"*"_s;
}
const QStringList plugins = findSharedLibraries(subDir, platform, debugMatchMode, filter);
const QStringList plugins =
findSharedLibraries(subDir, platform, debugMatchMode, filter);
for (const QString &plugin : plugins) {
const QString pluginPath =
deployPlugin(plugin, subDir, usedQtModules, disabledQtModules,
disabledPluginTypes, libraryLocation, infix, platform,
deployInsightTrackerPlugin);
deployPlugin(plugin, subDir, dueToModule, debugMatchMode, usedQtModules,
disabledQtModules, pluginSelections, libraryLocation, infix,
platform, deployInsightTrackerPlugin);
if (!pluginPath.isEmpty()) {
if (isPlatformPlugin)
*platformPlugin = subDir.absoluteFilePath(plugin);
@ -1238,7 +1323,7 @@ static QString getIcuVersion(const QString &libName)
}
static DeployResult deploy(const Options &options, const QMap<QString, QString> &qtpathsVariables,
QString *errorMessage)
const PluginInformation &pluginInfo, QString *errorMessage)
{
DeployResult result;
@ -1423,8 +1508,8 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString>
&result.deployedQtLibraries,
// For non-QML applications, disable QML to prevent it from being pulled in by the
// qtaccessiblequick plugin.
disabled,
options.disabledPluginTypes, qtpathsVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")),
disabled, pluginInfo,
options.pluginSelections, qtpathsVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")),
libraryLocation, infix, debugMatchMode, options.platform, &platformPlugin,
options.deployInsightTrackerPlugin);
@ -1569,7 +1654,8 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString>
}
static bool deployWebProcess(const QMap<QString, QString> &qtpathsVariables, const char *binaryName,
const Options &sourceOptions, QString *errorMessage)
const PluginInformation &pluginInfo, const Options &sourceOptions,
QString *errorMessage)
{
// Copy the web process and its dependencies
const QString webProcess = webProcessBinary(binaryName, sourceOptions.platform);
@ -1581,11 +1667,12 @@ static bool deployWebProcess(const QMap<QString, QString> &qtpathsVariables, con
options.binaries.append(options.directory + u'/' + webProcess);
options.quickImports = false;
options.translations = false;
return deploy(options, qtpathsVariables, errorMessage);
return deploy(options, qtpathsVariables, pluginInfo, errorMessage);
}
static bool deployWebEngineCore(const QMap<QString, QString> &qtpathsVariables,
const Options &options, bool isDebug, QString *errorMessage)
const PluginInformation &pluginInfo, const Options &options,
bool isDebug, QString *errorMessage)
{
static const char *installDataFiles[] = { "icudtl.dat",
"qtwebengine_devtools_resources.pak",
@ -1599,7 +1686,7 @@ static bool deployWebEngineCore(const QMap<QString, QString> &qtpathsVariables,
webEngineProcessName.append('d');
if (optVerboseLevel)
std::wcout << "Deploying: " << webEngineProcessName.constData() << "...\n";
if (!deployWebProcess(qtpathsVariables, webEngineProcessName, options, errorMessage))
if (!deployWebProcess(qtpathsVariables, webEngineProcessName, pluginInfo, options, errorMessage))
return false;
const QString resourcesSubDir = QStringLiteral("/resources");
const QString resourcesSourceDir = qtpathsVariables.value(QStringLiteral("QT_INSTALL_DATA"))
@ -1699,6 +1786,10 @@ int main(int argc, char **argv)
}
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;
@ -1707,7 +1798,7 @@ int main(int argc, char **argv)
if (result & CommandLineParseError)
std::wcerr << errorMessage << "\n\n";
if (result & CommandLineParseHelpRequested)
std::fputs(qPrintable(helpText(parser)), stdout);
std::fputs(qPrintable(helpText(parser, pluginInfo)), stdout);
if (result & CommandLineParseError)
return 1;
if (result & CommandLineParseHelpRequested)
@ -1725,14 +1816,15 @@ int main(int argc, char **argv)
return 1;
}
const DeployResult result = deploy(options, qtpathsVariables, &errorMessage);
const DeployResult result = deploy(options, qtpathsVariables, pluginInfo, &errorMessage);
if (!result) {
std::wcerr << errorMessage << '\n';
return 1;
}
if (result.deployedQtLibraries.test(QtWebEngineCoreModuleId)) {
if (!deployWebEngineCore(qtpathsVariables, options, result.isDebug, &errorMessage)) {
if (!deployWebEngineCore(qtpathsVariables, pluginInfo, options, result.isDebug,
&errorMessage)) {
std::wcerr << errorMessage << '\n';
return 1;
}

View File

@ -0,0 +1,100 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qtplugininfo.h"
#include <QDir>
static PluginDetection determinePluginLibrary(const QDir &platformPluginDir, const QString &infix)
{
// Use the platform plugin to determine which dlls are there (release/debug/both)
QString platformReleaseFilter(QStringLiteral("qwindows"));
if (!infix.isEmpty())
platformReleaseFilter += infix;
QString platformFilter = platformReleaseFilter + u'*';
platformFilter += sharedLibrarySuffix();
const QFileInfoList &dlls =
platformPluginDir.entryInfoList(QStringList(platformFilter), QDir::Files);
if (dlls.size() == 1) {
const QFileInfo dllFi = dlls.first();
const bool hasDebugDlls =
dllFi.fileName() == QString(platformReleaseFilter + sharedLibrarySuffix()) ? false
: true;
return (hasDebugDlls ? PluginDetection::DebugOnly : PluginDetection::ReleaseOnly);
} else {
return PluginDetection::DebugAndRelease;
}
}
static QStringList findPluginNames(const QDir &pluginDir, const PluginDetection libraryType,
const Platform &platform)
{
QString errorMessage{};
QStringList result{};
QString filter{};
filter += u"*";
filter += sharedLibrarySuffix();
const QFileInfoList &dlls =
pluginDir.entryInfoList(QStringList(filter), QDir::Files, QDir::Name);
for (const QFileInfo &dllFi : dlls) {
QString plugin = dllFi.fileName();
const int dotIndex = plugin.lastIndexOf(u'.');
// We don't need the .dll for the name
plugin = plugin.first(dotIndex);
if (libraryType == PluginDetection::DebugAndRelease) {
bool isDebugDll{};
if (!readPeExecutable(dllFi.absoluteFilePath(), &errorMessage, 0, 0, &isDebugDll,
(platform == WindowsDesktopMinGW))) {
std::wcerr << "Warning: Unable to read "
<< QDir::toNativeSeparators(dllFi.absoluteFilePath()) << ": "
<< errorMessage;
}
if (isDebugDll && platformHasDebugSuffix(platform))
plugin.removeLast();
}
else if (libraryType == PluginDetection::DebugOnly)
plugin.removeLast();
if (!result.contains(plugin))
result.append(plugin);
}
return result;
}
bool PluginInformation::isTypeForPlugin(const QString &type, const QString &plugin) const
{
return m_pluginMap.at(plugin) == type;
}
void PluginInformation::populatePluginToType(const QDir &pluginDir, const QStringList &plugins)
{
for (const QString &plugin : plugins)
m_pluginMap.insert({ plugin, pluginDir.dirName() });
}
void PluginInformation::generateAvailablePlugins(const QMap<QString, QString> &qtPathsVariables,
const Platform &platform)
{
const QDir pluginTypesDir(qtPathsVariables.value(QLatin1String("QT_INSTALL_PLUGINS")));
const QDir platformPluginDir(pluginTypesDir.absolutePath() + QStringLiteral("/platforms"));
const QString infix(qtPathsVariables.value(QLatin1String(qmakeInfixKey)));
const PluginDetection debugDetection = determinePluginLibrary(platformPluginDir, infix);
const QFileInfoList &pluginTypes =
pluginTypesDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QFileInfo &pluginType : pluginTypes) {
const QString pluginTypeName = pluginType.baseName();
m_typeMap.insert({ pluginTypeName, QStringList{} });
const QStringList plugins =
findPluginNames(pluginType.absoluteFilePath(), debugDetection, platform);
m_typeMap.at(pluginTypeName) = plugins;
populatePluginToType(pluginTypeName, plugins);
}
if (!m_typeMap.size() || !m_pluginMap.size())
std::wcerr << "Warning: could not parse available plugins properly, plugin "
"inclusion/exclusion options will not work\n";
}

View File

@ -0,0 +1,47 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef QTPLUGININFO_H
#define QTPLUGININFO_H
#include "utils.h"
#include <QString>
#include <QStringList>
#include <unordered_map>
enum class PluginDetection
{
DebugOnly,
ReleaseOnly,
DebugAndRelease
};
struct PluginLists
{
QStringList disabledPluginTypes;
QStringList enabledPluginTypes;
QStringList excludedPlugins;
QStringList includedPlugins;
};
class PluginInformation
{
public:
PluginInformation() = default;
bool isTypeForPlugin(const QString &type, const QString &plugin) const;
void generateAvailablePlugins(const QMap<QString, QString> &qtPathsVariables,
const Platform &platform);
void populatePluginToType(const QDir &pluginDir, const QStringList &plugins);
const std::unordered_map<QString, QStringList> &typeMap() const { return m_typeMap; }
private:
std::unordered_map<QString, QStringList> m_typeMap;
std::unordered_map<QString, QString> m_pluginMap;
};
#endif