summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoerg Bornemann <joerg.bornemann@qt.io>2021-11-18 14:52:19 +0100
committerJoerg Bornemann <joerg.bornemann@qt.io>2021-11-23 21:11:45 +0100
commit3f56950862181f4d50f30d66f577c933795522c3 (patch)
treed6d47e2693605b3b207e22614860563ee606aff8
parent1e9f9a4b7d1efa7efd5d501754f2de000a507cc5 (diff)
Move macdeployqt and windeployqt from qttools to qtbase
Having all *deployqt tools in qtbase will allow us to couple deployment support more tightly with the build system. Change-Id: I299efdacfa6b66a303bb3996ff3ff84e723210a5 Reviewed-by: Kai Koehne <kai.koehne@qt.io>
-rw-r--r--src/tools/CMakeLists.txt8
-rw-r--r--src/tools/configure.cmake14
-rw-r--r--src/tools/macdeployqt/CMakeLists.txt6
-rw-r--r--src/tools/macdeployqt/macdeployqt/CMakeLists.txt16
-rw-r--r--src/tools/macdeployqt/macdeployqt/main.cpp278
-rw-r--r--src/tools/macdeployqt/shared/shared.cpp1604
-rw-r--r--src/tools/macdeployqt/shared/shared.h141
-rw-r--r--src/tools/windeployqt/CMakeLists.txt32
-rw-r--r--src/tools/windeployqt/elfreader.cpp440
-rw-r--r--src/tools/windeployqt/elfreader.h176
-rw-r--r--src/tools/windeployqt/main.cpp1723
-rw-r--r--src/tools/windeployqt/qmlutils.cpp160
-rw-r--r--src/tools/windeployqt/qmlutils.h65
-rw-r--r--src/tools/windeployqt/utils.cpp1006
-rw-r--r--src/tools/windeployqt/utils.h404
-rw-r--r--tests/auto/tools/CMakeLists.txt9
-rw-r--r--tests/auto/tools/macdeployqt/CMakeLists.txt10
-rw-r--r--tests/auto/tools/macdeployqt/source_basicapp/basicapp.pro1
-rw-r--r--tests/auto/tools/macdeployqt/source_basicapp/main.cpp44
-rw-r--r--tests/auto/tools/macdeployqt/source_plugin_sqlite/main.cpp36
-rw-r--r--tests/auto/tools/macdeployqt/source_plugin_sqlite/plugin_sqlite.pro2
-rw-r--r--tests/auto/tools/macdeployqt/source_plugin_tls/main.cpp35
-rw-r--r--tests/auto/tools/macdeployqt/source_plugin_tls/plugin_tls.pro2
-rw-r--r--tests/auto/tools/macdeployqt/tst_macdeployqt.cpp316
-rw-r--r--tests/auto/tools/windeployqt/CMakeLists.txt4
-rw-r--r--tests/auto/tools/windeployqt/test/CMakeLists.txt11
-rw-r--r--tests/auto/tools/windeployqt/testapp/CMakeLists.txt21
-rw-r--r--tests/auto/tools/windeployqt/testapp/main.cpp46
-rw-r--r--tests/auto/tools/windeployqt/tst_windeployqt.cpp181
29 files changed, 6791 insertions, 0 deletions
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index 247b0193e6..77348abcc7 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -20,3 +20,11 @@ if(QT_FEATURE_androiddeployqt)
add_subdirectory(androidtestrunner)
endif()
endif()
+
+if(QT_FEATURE_macdeployqt)
+ add_subdirectory(macdeployqt)
+endif()
+
+if(QT_FEATURE_windeployqt)
+ add_subdirectory(windeployqt)
+endif()
diff --git a/src/tools/configure.cmake b/src/tools/configure.cmake
index bee8a90ef7..8c474046ef 100644
--- a/src/tools/configure.cmake
+++ b/src/tools/configure.cmake
@@ -4,6 +4,18 @@ qt_feature("androiddeployqt" PRIVATE
PURPOSE "The Android deployment tool automates the process of creating Android packages."
CONDITION NOT CMAKE_CROSSCOMPILING AND QT_FEATURE_regularexpression)
+qt_feature("macdeployqt" PRIVATE
+ SECTION "Deployment"
+ LABEL "macOS deployment tool"
+ PURPOSE "The Mac deployment tool automates the process of creating a deployable application bundle that contains the Qt libraries as private frameworks."
+ CONDITION MACOS)
+
+qt_feature("windeployqt" PRIVATE
+ SECTION "Deployment"
+ LABEL "Windows deployment tool"
+ PURPOSE "The Windows deployment tool is designed to automate the process of creating a deployable folder containing the Qt-related dependencies (libraries, QML imports, plugins, and translations) required to run the application from that folder. It creates a sandbox for Universal Windows Platform (UWP) or an installation tree for Windows desktop applications, which can be easily bundled into an installation package."
+ CONDITION WIN32)
+
qt_feature("qmake" PRIVATE
PURPOSE "The qmake tool helps simplify the build process for development projects across different platforms."
CONDITION QT_FEATURE_settings AND QT_FEATURE_alloca AND
@@ -12,5 +24,7 @@ qt_feature("qmake" PRIVATE
qt_configure_add_summary_section(NAME "Core tools")
qt_configure_add_summary_entry(ARGS "androiddeployqt")
+qt_configure_add_summary_entry(ARGS "macdeployqt")
+qt_configure_add_summary_entry(ARGS "windeployqt")
qt_configure_add_summary_entry(ARGS "qmake")
qt_configure_end_summary_section()
diff --git a/src/tools/macdeployqt/CMakeLists.txt b/src/tools/macdeployqt/CMakeLists.txt
new file mode 100644
index 0000000000..cbce4e4ca3
--- /dev/null
+++ b/src/tools/macdeployqt/CMakeLists.txt
@@ -0,0 +1,6 @@
+# Generated from macdeployqt.pro.
+
+if(NOT QT_FEATURE_macdeployqt)
+ return()
+endif()
+add_subdirectory(macdeployqt)
diff --git a/src/tools/macdeployqt/macdeployqt/CMakeLists.txt b/src/tools/macdeployqt/macdeployqt/CMakeLists.txt
new file mode 100644
index 0000000000..85f117667d
--- /dev/null
+++ b/src/tools/macdeployqt/macdeployqt/CMakeLists.txt
@@ -0,0 +1,16 @@
+#####################################################################
+## macdeployqt Tool:
+#####################################################################
+
+qt_get_tool_target_name(target_name macdeployqt)
+qt_internal_add_tool(${target_name}
+ TOOLS_TARGET Core
+ USER_FACING
+ TARGET_DESCRIPTION "Qt macOS Deployment Tool"
+ SOURCES
+ ../shared/shared.cpp
+ main.cpp
+ LIBRARIES
+ ${FWCoreFoundation}
+)
+qt_internal_return_unless_building_tools()
diff --git a/src/tools/macdeployqt/macdeployqt/main.cpp b/src/tools/macdeployqt/macdeployqt/main.cpp
new file mode 100644
index 0000000000..145ee854af
--- /dev/null
+++ b/src/tools/macdeployqt/macdeployqt/main.cpp
@@ -0,0 +1,278 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QCoreApplication>
+#include <QDir>
+#include <QLibraryInfo>
+
+#include "../shared/shared.h"
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ QString appBundlePath;
+ if (argc > 1)
+ appBundlePath = QString::fromLocal8Bit(argv[1]);
+
+ if (argc < 2 || appBundlePath.startsWith("-")) {
+ qDebug() << "Usage: macdeployqt app-bundle [options]";
+ qDebug() << "";
+ qDebug() << "Options:";
+ qDebug() << " -verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug";
+ qDebug() << " -no-plugins : Skip plugin deployment";
+ qDebug() << " -dmg : Create a .dmg disk image";
+ qDebug() << " -no-strip : Don't run 'strip' on the binaries";
+ qDebug() << " -use-debug-libs : Deploy with debug versions of frameworks and plugins (implies -no-strip)";
+ qDebug() << " -executable=<path> : Let the given executable use the deployed frameworks too";
+ qDebug() << " -qmldir=<path> : Scan for QML imports in the given path";
+ qDebug() << " -qmlimport=<path> : Add the given path to the QML module search locations";
+ qDebug() << " -always-overwrite : Copy files even if the target file exists";
+ qDebug() << " -codesign=<ident> : Run codesign with the given identity on all executables";
+ qDebug() << " -hardened-runtime : Enable Hardened Runtime when code signing";
+ qDebug() << " -timestamp : Include a secure timestamp when code signing (requires internet connection)";
+ qDebug() << " -sign-for-notarization=<ident>: Activate the necessary options for notarization (requires internet connection)";
+ qDebug() << " -appstore-compliant : Skip deployment of components that use private API";
+ qDebug() << " -libpath=<path> : Add the given path to the library search path";
+ qDebug() << " -fs=<filesystem> : Set the filesystem used for the .dmg disk image (defaults to HFS+)";
+ qDebug() << "";
+ qDebug() << "macdeployqt takes an application bundle as input and makes it";
+ qDebug() << "self-contained by copying in the Qt frameworks and plugins that";
+ qDebug() << "the application uses.";
+ qDebug() << "";
+ qDebug() << "Plugins related to a framework are copied in with the";
+ qDebug() << "framework. The accessibility, image formats, and text codec";
+ qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified.";
+ qDebug() << "";
+ qDebug() << "Qt plugins may use private API and will cause the app to be";
+ qDebug() << "rejected from the Mac App store. MacDeployQt will print a warning";
+ qDebug() << "when known incompatible plugins are deployed. Use -appstore-compliant ";
+ qDebug() << "to skip these plugins. Currently two SQL plugins are known to";
+ qDebug() << "be incompatible: qsqlodbc and qsqlpsql.";
+ qDebug() << "";
+ qDebug() << "See the \"Deploying Applications on OS X\" topic in the";
+ qDebug() << "documentation for more information about deployment on OS X.";
+
+ return 1;
+ }
+
+ appBundlePath = QDir::cleanPath(appBundlePath);
+
+ if (!QDir(appBundlePath).exists()) {
+ qDebug() << "Error: Could not find app bundle" << appBundlePath;
+ return 1;
+ }
+
+ bool plugins = true;
+ bool dmg = false;
+ QByteArray filesystem("HFS+");
+ bool useDebugLibs = false;
+ extern bool runStripEnabled;
+ extern bool alwaysOwerwriteEnabled;
+ extern QStringList librarySearchPath;
+ QStringList additionalExecutables;
+ bool qmldirArgumentUsed = false;
+ QStringList qmlDirs;
+ QStringList qmlImportPaths;
+ extern bool runCodesign;
+ extern QString codesignIdentiy;
+ extern bool hardenedRuntime;
+ extern bool appstoreCompliant;
+ extern bool deployFramework;
+ extern bool secureTimestamp;
+
+ for (int i = 2; i < argc; ++i) {
+ QByteArray argument = QByteArray(argv[i]);
+ if (argument == QByteArray("-no-plugins")) {
+ LogDebug() << "Argument found:" << argument;
+ plugins = false;
+ } else if (argument == QByteArray("-dmg")) {
+ LogDebug() << "Argument found:" << argument;
+ dmg = true;
+ } else if (argument == QByteArray("-no-strip")) {
+ LogDebug() << "Argument found:" << argument;
+ runStripEnabled = false;
+ } else if (argument == QByteArray("-use-debug-libs")) {
+ LogDebug() << "Argument found:" << argument;
+ useDebugLibs = true;
+ runStripEnabled = false;
+ } else if (argument.startsWith(QByteArray("-verbose"))) {
+ LogDebug() << "Argument found:" << argument;
+ int index = argument.indexOf("=");
+ bool ok = false;
+ int number = argument.mid(index+1).toInt(&ok);
+ if (!ok)
+ LogError() << "Could not parse verbose level";
+ else
+ logLevel = number;
+ } else if (argument.startsWith(QByteArray("-executable"))) {
+ LogDebug() << "Argument found:" << argument;
+ int index = argument.indexOf('=');
+ if (index == -1)
+ LogError() << "Missing executable path";
+ else
+ additionalExecutables << argument.mid(index+1);
+ } else if (argument.startsWith(QByteArray("-qmldir"))) {
+ LogDebug() << "Argument found:" << argument;
+ qmldirArgumentUsed = true;
+ int index = argument.indexOf('=');
+ if (index == -1)
+ LogError() << "Missing qml directory path";
+ else
+ qmlDirs << argument.mid(index+1);
+ } else if (argument.startsWith(QByteArray("-qmlimport"))) {
+ LogDebug() << "Argument found:" << argument;
+ int index = argument.indexOf('=');
+ if (index == -1)
+ LogError() << "Missing qml import path";
+ else
+ qmlImportPaths << argument.mid(index+1);
+ } else if (argument.startsWith(QByteArray("-libpath"))) {
+ LogDebug() << "Argument found:" << argument;
+ int index = argument.indexOf('=');
+ if (index == -1)
+ LogError() << "Missing library search path";
+ else
+ librarySearchPath << argument.mid(index+1);
+ } else if (argument == QByteArray("-always-overwrite")) {
+ LogDebug() << "Argument found:" << argument;
+ alwaysOwerwriteEnabled = true;
+ } else if (argument.startsWith(QByteArray("-codesign"))) {
+ LogDebug() << "Argument found:" << argument;
+ int index = argument.indexOf("=");
+ if (index < 0 || index >= argument.size()) {
+ LogError() << "Missing code signing identity";
+ } else {
+ runCodesign = true;
+ codesignIdentiy = argument.mid(index+1);
+ }
+ } else if (argument.startsWith(QByteArray("-sign-for-notarization"))) {
+ LogDebug() << "Argument found:" << argument;
+ int index = argument.indexOf("=");
+ if (index < 0 || index >= argument.size()) {
+ LogError() << "Missing code signing identity";
+ } else {
+ runCodesign = true;
+ hardenedRuntime = true;
+ secureTimestamp = true;
+ codesignIdentiy = argument.mid(index+1);
+ }
+ } else if (argument.startsWith(QByteArray("-hardened-runtime"))) {
+ LogDebug() << "Argument found:" << argument;
+ hardenedRuntime = true;
+ } else if (argument.startsWith(QByteArray("-timestamp"))) {
+ LogDebug() << "Argument found:" << argument;
+ secureTimestamp = true;
+ } else if (argument == QByteArray("-appstore-compliant")) {
+ LogDebug() << "Argument found:" << argument;
+ appstoreCompliant = true;
+
+ // Undocumented option, may not work as intended
+ } else if (argument == QByteArray("-deploy-framework")) {
+ LogDebug() << "Argument found:" << argument;
+ deployFramework = true;
+
+ } else if (argument.startsWith(QByteArray("-fs"))) {
+ LogDebug() << "Argument found:" << argument;
+ int index = argument.indexOf('=');
+ if (index == -1)
+ LogError() << "Missing filesystem type";
+ else
+ filesystem = argument.mid(index+1);
+ } else if (argument.startsWith("-")) {
+ LogError() << "Unknown argument" << argument << "\n";
+ return 1;
+ }
+ }
+
+ DeploymentInfo deploymentInfo = deployQtFrameworks(appBundlePath, additionalExecutables, useDebugLibs);
+
+ if (deploymentInfo.isDebug)
+ useDebugLibs = true;
+
+ if (deployFramework && deploymentInfo.isFramework)
+ fixupFramework(appBundlePath);
+
+ // Convenience: Look for .qml files in the current directory if no -qmldir specified.
+ if (qmlDirs.isEmpty()) {
+ QDir dir;
+ if (!dir.entryList(QStringList() << QStringLiteral("*.qml")).isEmpty()) {
+ qmlDirs += QStringLiteral(".");
+ }
+ }
+
+ if (!qmlDirs.isEmpty()) {
+ bool ok = deployQmlImports(appBundlePath, deploymentInfo, qmlDirs, qmlImportPaths);
+ if (!ok && qmldirArgumentUsed)
+ return 1; // exit if the user explicitly asked for qml import deployment
+
+ // Update deploymentInfo.deployedFrameworks - the QML imports
+ // may have brought in extra frameworks as dependencies.
+ deploymentInfo.deployedFrameworks += findAppFrameworkNames(appBundlePath);
+ deploymentInfo.deployedFrameworks =
+ QSet<QString>(deploymentInfo.deployedFrameworks.begin(),
+ deploymentInfo.deployedFrameworks.end()).values();
+ }
+
+ // Handle plugins
+ if (plugins) {
+ // Set the plugins search directory
+ deploymentInfo.pluginPath = QLibraryInfo::path(QLibraryInfo::PluginsPath);
+
+ // Sanity checks
+ if (deploymentInfo.pluginPath.isEmpty()) {
+ LogError() << "Missing Qt plugins path\n";
+ return 1;
+ }
+
+ if (!QDir(deploymentInfo.pluginPath).exists()) {
+ LogError() << "Plugins path does not exist" << deploymentInfo.pluginPath << "\n";
+ return 1;
+ }
+
+ // Deploy plugins
+ Q_ASSERT(!deploymentInfo.pluginPath.isEmpty());
+ if (!deploymentInfo.pluginPath.isEmpty()) {
+ LogNormal();
+ deployPlugins(appBundlePath, deploymentInfo, useDebugLibs);
+ createQtConf(appBundlePath);
+ }
+ }
+
+ if (runStripEnabled)
+ stripAppBinary(appBundlePath);
+
+ if (runCodesign)
+ codesign(codesignIdentiy, appBundlePath);
+
+ if (dmg) {
+ LogNormal();
+ createDiskImage(appBundlePath, filesystem);
+ }
+
+ return 0;
+}
diff --git a/src/tools/macdeployqt/shared/shared.cpp b/src/tools/macdeployqt/shared/shared.cpp
new file mode 100644
index 0000000000..b6eeaa8e80
--- /dev/null
+++ b/src/tools/macdeployqt/shared/shared.cpp
@@ -0,0 +1,1604 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QCoreApplication>
+#include <QString>
+#include <QStringList>
+#include <QDebug>
+#include <iostream>
+#include <utility>
+#include <QProcess>
+#include <QDir>
+#include <QSet>
+#include <QStack>
+#include <QDirIterator>
+#include <QLibraryInfo>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonValue>
+#include <QRegularExpression>
+#include "shared.h"
+
+#ifdef Q_OS_DARWIN
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+bool runStripEnabled = true;
+bool alwaysOwerwriteEnabled = false;
+bool runCodesign = false;
+QStringList librarySearchPath;
+QString codesignIdentiy;
+QString extraEntitlements;
+bool hardenedRuntime = false;
+bool secureTimestamp = false;
+bool appstoreCompliant = false;
+int logLevel = 1;
+bool deployFramework = false;
+
+using std::cout;
+using std::endl;
+
+bool operator==(const FrameworkInfo &a, const FrameworkInfo &b)
+{
+ return ((a.frameworkPath == b.frameworkPath) && (a.binaryPath == b.binaryPath));
+}
+
+QDebug operator<<(QDebug debug, const FrameworkInfo &info)
+{
+ debug << "Framework name" << info.frameworkName << "\n";
+ debug << "Framework directory" << info.frameworkDirectory << "\n";
+ debug << "Framework path" << info.frameworkPath << "\n";
+ debug << "Binary directory" << info.binaryDirectory << "\n";
+ debug << "Binary name" << info.binaryName << "\n";
+ debug << "Binary path" << info.binaryPath << "\n";
+ debug << "Version" << info.version << "\n";
+ debug << "Install name" << info.installName << "\n";
+ debug << "Deployed install name" << info.deployedInstallName << "\n";
+ debug << "Source file Path" << info.sourceFilePath << "\n";
+ debug << "Framework Destination Directory (relative to bundle)" << info.frameworkDestinationDirectory << "\n";
+ debug << "Binary Destination Directory (relative to bundle)" << info.binaryDestinationDirectory << "\n";
+
+ return debug;
+}
+
+const QString bundleFrameworkDirectory = "Contents/Frameworks";
+
+inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
+{
+ debug << "Application bundle path" << info.path << "\n";
+ debug << "Binary path" << info.binaryPath << "\n";
+ debug << "Additional libraries" << info.libraryPaths << "\n";
+ return debug;
+}
+
+bool copyFilePrintStatus(const QString &from, const QString &to)
+{
+ if (QFile::exists(to)) {
+ if (alwaysOwerwriteEnabled) {
+ QFile(to).remove();
+ } else {
+ qDebug() << "File exists, skip copy:" << to;
+ return false;
+ }
+ }
+
+ if (QFile::copy(from, to)) {
+ QFile dest(to);
+ dest.setPermissions(dest.permissions() | QFile::WriteOwner | QFile::WriteUser);
+ LogNormal() << " copied:" << from;
+ LogNormal() << " to" << to;
+
+ // The source file might not have write permissions set. Set the
+ // write permission on the target file to make sure we can use
+ // install_name_tool on it later.
+ QFile toFile(to);
+ if (toFile.permissions() & QFile::WriteOwner)
+ return true;
+
+ if (!toFile.setPermissions(toFile.permissions() | QFile::WriteOwner)) {
+ LogError() << "Failed to set u+w permissions on target file: " << to;
+ return false;
+ }
+
+ return true;
+ } else {
+ LogError() << "file copy failed from" << from;
+ LogError() << " to" << to;
+ return false;
+ }
+}
+
+bool linkFilePrintStatus(const QString &file, const QString &link)
+{
+ if (QFile::exists(link)) {
+ if (QFile(link).symLinkTarget().isEmpty())
+ LogError() << link << "exists but it's a file.";
+ else
+ LogNormal() << "Symlink exists, skipping:" << link;
+ return false;
+ } else if (QFile::link(file, link)) {
+ LogNormal() << " symlink" << link;
+ LogNormal() << " points to" << file;
+ return true;
+ } else {
+ LogError() << "failed to symlink" << link;
+ LogError() << " to" << file;
+ return false;
+ }
+}
+
+void patch_debugInInfoPlist(const QString &infoPlistPath)
+{
+ // Older versions of qmake may have the "_debug" binary as
+ // the value for CFBundleExecutable. Remove it.
+ QFile infoPlist(infoPlistPath);
+ infoPlist.open(QIODevice::ReadOnly);
+ QByteArray contents = infoPlist.readAll();
+ infoPlist.close();
+ infoPlist.open(QIODevice::WriteOnly | QIODevice::Truncate);
+ contents.replace("_debug", ""); // surely there are no legit uses of "_debug" in an Info.plist
+ infoPlist.write(contents);
+}
+
+OtoolInfo findDependencyInfo(const QString &binaryPath)
+{
+ OtoolInfo info;
+ info.binaryPath = binaryPath;
+
+ LogDebug() << "Using otool:";
+ LogDebug() << " inspecting" << binaryPath;
+ QProcess otool;
+ otool.start("otool", QStringList() << "-L" << binaryPath);
+ otool.waitForFinished();
+
+ if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) {
+ LogError() << otool.readAllStandardError();
+ return info;
+ }
+
+ static const QRegularExpression regexp(QStringLiteral(
+ "^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "
+ "current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$"));
+
+ QString output = otool.readAllStandardOutput();
+ QStringList outputLines = output.split("\n", Qt::SkipEmptyParts);
+ if (outputLines.size() < 2) {
+ LogError() << "Could not parse otool output:" << output;
+ return info;
+ }
+
+ outputLines.removeFirst(); // remove line containing the binary path
+ if (binaryPath.contains(".framework/") || binaryPath.endsWith(".dylib")) {
+ const auto match = regexp.match(outputLines.first());
+ if (match.hasMatch()) {
+ QString installname = match.captured(1);
+ if (QFileInfo(binaryPath).fileName() == QFileInfo(installname).fileName()) {
+ info.installName = installname;
+ info.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
+ info.currentVersion = QVersionNumber::fromString(match.captured(3));
+ outputLines.removeFirst();
+ } else {
+ info.installName = binaryPath;
+ }
+ } else {
+ LogError() << "Could not parse otool output line:" << outputLines.first();
+ outputLines.removeFirst();
+ }
+ }
+
+ for (const QString &outputLine : outputLines) {
+ const auto match = regexp.match(outputLine);
+ if (match.hasMatch()) {
+ DylibInfo dylib;
+ dylib.binaryPath = match.captured(1);
+ dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
+ dylib.currentVersion = QVersionNumber::fromString(match.captured(3));
+ info.dependencies << dylib;
+ } else {
+ LogError() << "Could not parse otool output line:" << outputLine;
+ }
+ }
+
+ return info;
+}
+
+FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
+{
+ FrameworkInfo info;
+ QString trimmed = line.trimmed();
+
+ if (trimmed.isEmpty())
+ return info;
+
+ // Don't deploy system libraries.
+ if (trimmed.startsWith("/System/Library/") ||
+ (trimmed.startsWith("/usr/lib/") && trimmed.contains("libQt") == false) // exception for libQtuitools and libQtlucene
+ || trimmed.startsWith("@executable_path") || trimmed.startsWith("@loader_path"))
+ return info;
+
+ // Resolve rpath relative libraries.
+ if (trimmed.startsWith("@rpath/")) {
+ QString rpathRelativePath = trimmed.mid(QStringLiteral("@rpath/").length());
+ bool foundInsideBundle = false;
+ for (const QString &rpath : std::as_const(rpaths)) {
+ QString path = QDir::cleanPath(rpath + "/" + rpathRelativePath);
+ // Skip paths already inside the bundle.
+ if (!appBundlePath.isEmpty()) {
+ if (QDir::isAbsolutePath(appBundlePath)) {
+ if (path.startsWith(QDir::cleanPath(appBundlePath) + "/")) {
+ foundInsideBundle = true;
+ continue;
+ }
+ } else {
+ if (path.startsWith(QDir::cleanPath(QDir::currentPath() + "/" + appBundlePath) + "/")) {
+ foundInsideBundle = true;
+ continue;
+ }
+ }
+ }
+ // Try again with substituted rpath.
+ FrameworkInfo resolvedInfo = parseOtoolLibraryLine(path, appBundlePath, rpaths, useDebugLibs);
+ if (!resolvedInfo.frameworkName.isEmpty() && QFile::exists(resolvedInfo.frameworkPath)) {
+ resolvedInfo.rpathUsed = rpath;
+ resolvedInfo.installName = trimmed;
+ return resolvedInfo;
+ }
+ }
+ if (!rpaths.isEmpty() && !foundInsideBundle) {
+ LogError() << "Cannot resolve rpath" << trimmed;
+ LogError() << " using" << rpaths;
+ }
+ return info;
+ }
+
+ enum State {QtPath, FrameworkName, DylibName, Version, FrameworkBinary, End};
+ State state = QtPath;
+ int part = 0;
+ QString name;
+ QString qtPath;
+ QString suffix = useDebugLibs ? "_debug" : "";
+
+ // Split the line into [Qt-path]/lib/qt[Module].framework/Versions/[Version]/
+ QStringList parts = trimmed.split("/");
+ while (part < parts.count()) {
+ const QString currentPart = parts.at(part).simplified();
+ ++part;
+ if (currentPart == "")
+ continue;
+
+ if (state == QtPath) {
+ // Check for library name part
+ if (part < parts.count() && parts.at(part).contains(".dylib")) {
+ info.frameworkDirectory += "/" + QString(qtPath + currentPart + "/").simplified();
+ state = DylibName;
+ continue;
+ } else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
+ info.frameworkDirectory += "/" + QString(qtPath + "lib/").simplified();
+ state = FrameworkName;
+ continue;
+ } else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package.
+ QStringList partsCopy = parts;
+ partsCopy.removeLast();
+ for (QString &path : librarySearchPath) {
+ if (!path.endsWith("/"))
+ path += '/';
+ QString nameInPath = path + parts.join(QLatin1Char('/'));
+ if (QFile::exists(nameInPath)) {
+ info.frameworkDirectory = path + partsCopy.join(QLatin1Char('/'));
+ break;
+ }
+ }
+ if (currentPart.contains(".framework")) {
+ if (info.frameworkDirectory.isEmpty())
+ info.frameworkDirectory = "/Library/Frameworks/" + partsCopy.join(QLatin1Char('/'));
+ if (!info.frameworkDirectory.endsWith("/"))
+ info.frameworkDirectory += "/";
+ state = FrameworkName;
+ --part;
+ continue;
+ } else if (currentPart.contains(".dylib")) {
+ if (info.frameworkDirectory.isEmpty())
+ info.frameworkDirectory = "/usr/lib/" + partsCopy.join(QLatin1Char('/'));
+ if (!info.frameworkDirectory.endsWith("/"))
+ info.frameworkDirectory += "/";
+ state = DylibName;
+ --part;
+ continue;
+ }
+ }
+ qtPath += (currentPart + "/");
+
+ } if (state == FrameworkName) {
+ // remove ".framework"
+ name = currentPart;
+ name.chop(QString(".framework").length());
+ info.isDylib = false;
+ info.frameworkName = currentPart;
+ state = Version;
+ ++part;
+ continue;
+ } if (state == DylibName) {
+ name = currentPart;
+ info.isDylib = true;
+ info.frameworkName = name;
+ info.binaryName = name.contains(suffix) ? name : name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.'));
+ info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName;
+ info.frameworkPath = info.frameworkDirectory + info.binaryName;
+ info.sourceFilePath = info.frameworkPath;
+ info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/";
+ info.binaryDestinationDirectory = info.frameworkDestinationDirectory;
+ info.binaryDirectory = info.frameworkDirectory;
+ info.binaryPath = info.frameworkPath;
+ state = End;
+ ++part;
+ continue;
+ } else if (state == Version) {
+ info.version = currentPart;
+ info.binaryDirectory = "Versions/" + info.version;
+ info.frameworkPath = info.frameworkDirectory + info.frameworkName;
+ info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName;
+ info.binaryDestinationDirectory = info.frameworkDestinationDirectory + "/" + info.binaryDirectory;
+ state = FrameworkBinary;
+ } else if (state == FrameworkBinary) {
+ info.binaryName = currentPart.contains(suffix) ? currentPart : currentPart + suffix;
+ info.binaryPath = "/" + info.binaryDirectory + "/" + info.binaryName;
+ info.deployedInstallName = "@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath;
+ info.sourceFilePath = info.frameworkPath + info.binaryPath;
+ state = End;
+ } else if (state == End) {
+ break;
+ }
+ }
+
+ if (!info.sourceFilePath.isEmpty() && QFile::exists(info.sourceFilePath)) {
+ info.installName = findDependencyInfo(info.sourceFilePath).installName;
+ if (info.installName.startsWith("@rpath/"))
+ info.deployedInstallName = info.installName;
+ }
+
+ return info;
+}
+
+QString findAppBinary(const QString &appBundlePath)
+{
+ QString binaryPath;
+
+#ifdef Q_OS_DARWIN
+ CFStringRef bundlePath = appBundlePath.toCFString();
+ CFURLRef bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath,
+ kCFURLPOSIXPathStyle, true);
+ CFRelease(bundlePath);
+ CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, bundleURL);
+ if (bundle) {
+ CFURLRef executableURL = CFBundleCopyExecutableURL(bundle);
+ if (executableURL) {
+ CFURLRef absoluteExecutableURL = CFURLCopyAbsoluteURL(executableURL);
+ if (absoluteExecutableURL) {
+ CFStringRef executablePath = CFURLCopyFileSystemPath(absoluteExecutableURL,
+ kCFURLPOSIXPathStyle);
+ if (executablePath) {
+ binaryPath = QString::fromCFString(executablePath);
+ CFRelease(executablePath);
+ }
+ CFRelease(absoluteExecutableURL);
+ }
+ CFRelease(executableURL);
+ }
+ CFRelease(bundle);
+ }
+ CFRelease(bundleURL);
+#endif
+
+ if (QFile::exists(binaryPath))
+ return binaryPath;
+ LogError() << "Could not find bundle binary for" << appBundlePath;
+ return QString();
+}
+
+QStringList findAppFrameworkNames(const QString &appBundlePath)
+{
+ QStringList frameworks;
+
+ // populate the frameworks list with QtFoo.framework etc,
+ // as found in /Contents/Frameworks/
+ QString searchPath = appBundlePath + "/Contents/Frameworks/";
+ QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"),
+ QDir::Dirs | QDir::NoSymLinks);
+ while (iter.hasNext()) {
+ iter.next();
+ frameworks << iter.fileInfo().fileName();
+ }
+
+ return frameworks;
+}
+
+QStringList findAppFrameworkPaths(const QString &appBundlePath)
+{
+ QStringList frameworks;
+ QString searchPath = appBundlePath + "/Contents/Frameworks/";
+ QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"),
+ QDir::Dirs | QDir::NoSymLinks);
+ while (iter.hasNext()) {
+ iter.next();
+ frameworks << iter.fileInfo().filePath();
+ }
+
+ return frameworks;
+}
+
+QStringList findAppLibraries(const QString &appBundlePath)
+{
+ QStringList result;
+ // dylibs
+ QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*.dylib"),
+ QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
+ while (iter.hasNext()) {
+ iter.next();
+ result << iter.fileInfo().filePath();
+ }
+ return result;
+}
+
+QStringList findAppBundleFiles(const QString &appBundlePath, bool absolutePath = false)
+{
+ QStringList result;
+
+ QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*"),
+ QDir::Files, QDirIterator::Subdirectories);
+
+ while (iter.hasNext()) {
+ iter.next();
+ if (iter.fileInfo().isSymLink())
+ continue;
+ result << (absolutePath ? iter.fileInfo().absoluteFilePath() : iter.fileInfo().filePath());
+ }
+
+ return result;
+}
+
+QString findEntitlementsFile(const QString& path)
+{
+ QDirIterator iter(path, QStringList() << QString::fromLatin1("*.entitlements"),
+ QDir::Files, QDirIterator::Subdirectories);
+
+ while (iter.hasNext()) {
+ iter.next();
+ if (iter.fileInfo().isSymLink())
+ continue;
+
+ //return the first entitlements file - only one is used for signing anyway
+ return iter.fileInfo().absoluteFilePath();
+ }
+
+ return QString();
+}
+
+QList<FrameworkInfo> getQtFrameworks(const QList<DylibInfo> &dependencies, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
+{
+ QList<FrameworkInfo> libraries;
+ for (const DylibInfo &dylibInfo : dependencies) {
+ FrameworkInfo info = parseOtoolLibraryLine(dylibInfo.binaryPath, appBundlePath, rpaths, useDebugLibs);
+ if (info.frameworkName.isEmpty() == false) {
+ LogDebug() << "Adding framework:";
+ LogDebug() << info;
+ libraries.append(info);
+ }
+ }
+ return libraries;
+}
+
+QString resolveDyldPrefix(const QString &path, const QString &loaderPath, const QString &executablePath)
+{
+ if (path.startsWith("@")) {
+ if (path.startsWith(QStringLiteral("@executable_path/"))) {
+ // path relative to bundle executable dir
+ if (QDir::isAbsolutePath(executablePath)) {
+ return QDir::cleanPath(QFileInfo(executablePath).path() + path.mid(QStringLiteral("@executable_path").length()));
+ } else {
+ return QDir::cleanPath(QDir::currentPath() + "/" +
+ QFileInfo(executablePath).path() + path.mid(QStringLiteral("@executable_path").length()));
+ }
+ } else if (path.startsWith(QStringLiteral("@loader_path"))) {
+ // path relative to loader dir
+ if (QDir::isAbsolutePath(loaderPath)) {
+ return QDir::cleanPath(QFileInfo(loaderPath).path() + path.mid(QStringLiteral("@loader_path").length()));
+ } else {
+ return QDir::cleanPath(QDir::currentPath() + "/" +
+ QFileInfo(loaderPath).path() + path.mid(QStringLiteral("@loader_path").length()));
+ }
+ } else {
+ LogError() << "Unexpected prefix" << path;
+ }
+ }
+ return path;
+}
+
+QList<QString> getBinaryRPaths(const QString &path, bool resolve = true, QString executablePath = QString())
+{
+ QList<QString> rpaths;
+
+ QProcess otool;
+ otool.start("otool", QStringList() << "-l" << path);
+ otool.waitForFinished();
+
+ if (otool.exitCode() != 0) {
+ LogError() << otool.readAllStandardError();
+ }
+
+ if (resolve && executablePath.isEmpty()) {
+ executablePath = path;
+ }
+
+ QString output = otool.readAllStandardOutput();
+ QStringList outputLines = output.split("\n");
+
+ for (auto i = outputLines.cbegin(), end = outputLines.cend(); i != end; ++i) {
+ if (i->contains("cmd LC_RPATH") && ++i != end &&
+ i->contains("cmdsize") && ++i != end) {
+ const QString &rpathCmd = *i;
+ int pathStart = rpathCmd.indexOf("path ");
+ int pathEnd = rpathCmd.indexOf(" (");
+ if (pathStart >= 0 && pathEnd >= 0 && pathStart < pathEnd) {
+ QString rpath = rpathCmd.mid(pathStart + 5, pathEnd - pathStart - 5);
+ if (resolve) {
+ rpaths << resolveDyldPrefix(rpath, path, executablePath);
+ } else {
+ rpaths << rpath;
+ }
+ }
+ }
+ }
+
+ return rpaths;
+}
+
+QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
+{
+ const OtoolInfo info = findDependencyInfo(path);
+ QList<QString> allRPaths = rpaths + getBinaryRPaths(path);
+ allRPaths.removeDuplicates();
+ return getQtFrameworks(info.dependencies, appBundlePath, allRPaths, useDebugLibs);
+}
+
+QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
+{
+ QList<FrameworkInfo> result;
+ QSet<QString> existing;
+ for (const QString &path : paths) {
+ for (const FrameworkInfo &info : getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) {
+ if (!existing.contains(info.frameworkPath)) { // avoid duplicates
+ existing.insert(info.frameworkPath);
+ result << info;
+ }
+ }
+ }
+ return result;
+}
+
+QStringList getBinaryDependencies(const QString executablePath,
+ const QString &path,
+ const QList<QString> &additionalBinariesContainingRpaths)
+{
+ QStringList binaries;
+
+ const auto dependencies = findDependencyInfo(path).dependencies;
+
+ bool rpathsLoaded = false;
+ QList<QString> rpaths;
+
+ // return bundle-local dependencies. (those starting with @executable_path)
+ for (const DylibInfo &info : dependencies) {
+ QString trimmedLine = info.binaryPath;
+ if (trimmedLine.startsWith("@executable_path/")) {
+ QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length()));
+ if (binary != path)
+ binaries.append(binary);
+ } else if (trimmedLine.startsWith("@rpath/")) {
+ if (!rpathsLoaded) {
+ rpaths = getBinaryRPaths(path, true, executablePath);
+ foreach (const QString &binaryPath, additionalBinariesContainingRpaths)
+ rpaths += getBinaryRPaths(binaryPath, true);
+ rpaths.removeDuplicates();
+ rpathsLoaded = true;
+ }
+ bool resolved = false;
+ for (const QString &rpath : std::as_const(rpaths)) {
+ QString binary = QDir::cleanPath(rpath + "/" + trimmedLine.mid(QStringLiteral("@rpath/").length()));
+ LogDebug() << "Checking for" << binary;
+ if (QFile::exists(binary)) {
+ binaries.append(binary);
+ resolved = true;
+ break;
+ }
+ }
+ if (!resolved && !rpaths.isEmpty()) {
+ LogError() << "Cannot resolve rpath" << trimmedLine;
+ LogError() << " using" << rpaths;
+ }
+ }
+ }
+
+ return binaries;
+}
+
+// copies everything _inside_ sourcePath to destinationPath
+bool recursiveCopy(const QString &sourcePath, const QString &destinationPath)
+{
+ if (!QDir(sourcePath).exists())
+ return false;
+ QDir().mkpath(destinationPath);
+
+ LogNormal() << "copy:" << sourcePath << destinationPath;
+
+ QStringList files = QDir(sourcePath).entryList(QStringList() << "*", QDir::Files | QDir::NoDotAndDotDot);
+ for (const QString &file : files) {
+ const QString fileSourcePath = sourcePath + "/" + file;
+ const QString fileDestinationPath = destinationPath + "/" + file;
+ copyFilePrintStatus(fileSourcePath, fileDestinationPath);
+ }
+
+ QStringList subdirs = QDir(sourcePath).entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString &dir : subdirs) {
+ recursiveCopy(sourcePath + "/" + dir, destinationPath + "/" + dir);
+ }
+ return true;
+}
+
+void recursiveCopyAndDeploy(const QString &appBundlePath, const QList<QString> &rpaths, const QString &sourcePath, const QString &destinationPath)
+{
+ QDir().mkpath(destinationPath);
+
+ LogNormal() << "copy:" << sourcePath << destinationPath;
+ const bool isDwarfPath = sourcePath.endsWith("DWARF");
+
+ QStringList files = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Files | QDir::NoDotAndDotDot);
+ for (const QString &file : files) {
+ const QString fileSourcePath = sourcePath + QLatin1Char('/') + file;
+
+ if (file.endsWith("_debug.dylib")) {
+ continue; // Skip debug versions
+ } else if (!isDwarfPath && file.endsWith(QStringLiteral(".dylib"))) {
+ // App store code signing rules forbids code binaries in Contents/Resources/,
+ // which poses a problem for deploying mixed .qml/.dylib Qt Quick imports.
+ // Solve this by placing the dylibs in Contents/PlugIns/quick, and then
+ // creting a symlink to there from the Qt Quick import in Contents/Resources/.
+ //
+ // Example:
+ // MyApp.app/Contents/Resources/qml/QtQuick/Controls/libqtquickcontrolsplugin.dylib ->
+ // ../../../../PlugIns/quick/libqtquickcontrolsplugin.dylib
+ //
+
+ // The .dylib destination path:
+ QString fileDestinationDir = appBundlePath + QStringLiteral("/Contents/PlugIns/quick/");
+ QDir().mkpath(fileDestinationDir);
+ QString fileDestinationPath = fileDestinationDir + file;
+
+ // The .dylib symlink destination path:
+ QString linkDestinationPath = destinationPath + QLatin1Char('/') + file;
+
+ // The (relative) link; with a correct number of "../"'s.
+ QString linkPath = QStringLiteral("PlugIns/quick/") + file;
+ int cdupCount = linkDestinationPath.count(QStringLiteral("/")) - appBundlePath.count(QStringLiteral("/"));
+ for (int i = 0; i < cdupCount - 2; ++i)
+ linkPath.prepend("../");
+
+ if (copyFilePrintStatus(fileSourcePath, fileDestinationPath)) {
+ linkFilePrintStatus(linkPath, linkDestinationPath);
+
+ runStrip(fileDestinationPath);
+ bool useDebugLibs = false;
+ bool useLoaderPath = false;
+ QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, appBundlePath, rpaths, useDebugLibs);
+ deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath);
+ }
+ } else {
+ QString fileDestinationPath = destinationPath + QLatin1Char('/') + file;
+ copyFilePrintStatus(fileSourcePath, fileDestinationPath);
+ }
+ }
+
+ QStringList subdirs = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString &dir : subdirs) {
+ recursiveCopyAndDeploy(appBundlePath, rpaths, sourcePath + QLatin1Char('/') + dir, destinationPath + QLatin1Char('/') + dir);
+ }
+}
+
+QString copyDylib(const FrameworkInfo &framework, const QString path)
+{
+ if (!QFile::exists(framework.sourceFilePath)) {
+ LogError() << "no file at" << framework.sourceFilePath;
+ return QString();
+ }
+
+ // Construct destination paths. The full path typically looks like
+ // MyApp.app/Contents/Frameworks/libfoo.dylib
+ QString dylibDestinationDirectory = path + QLatin1Char('/') + framework.frameworkDestinationDirectory;
+ QString dylibDestinationBinaryPath = dylibDestinationDirectory + QLatin1Char('/') + framework.binaryName;
+
+ // Create destination directory
+ if (!QDir().mkpath(dylibDestinationDirectory)) {
+ LogError() << "could not create destination directory" << dylibDestinationDirectory;
+ return QString();
+ }
+
+ // Return if the dylib has already been deployed
+ if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled)
+ return dylibDestinationBinaryPath;
+
+ // Copy dylib binary
+ copyFilePrintStatus(framework.sourceFilePath, dylibDestinationBinaryPath);
+ return dylibDestinationBinaryPath;
+}
+
+QString copyFramework(const FrameworkInfo &framework, const QString path)
+{
+ if (!QFile::exists(framework.sourceFilePath)) {
+ LogError() << "no file at" << framework.sourceFilePath;
+ return QString();
+ }
+
+ // Construct destination paths. The full path typically looks like
+ // MyApp.app/Contents/Frameworks/Foo.framework/Versions/5/QtFoo
+ QString frameworkDestinationDirectory = path + QLatin1Char('/') + framework.frameworkDestinationDirectory;
+ QString frameworkBinaryDestinationDirectory = frameworkDestinationDirectory + QLatin1Char('/') + framework.binaryDirectory;
+ QString frameworkDestinationBinaryPath = frameworkBinaryDestinationDirectory + QLatin1Char('/') + framework.binaryName;
+
+ // Return if the framework has aleardy been deployed
+ if (QDir(frameworkDestinationDirectory).exists() && !alwaysOwerwriteEnabled)
+ return QString();
+
+ // Create destination directory
+ if (!QDir().mkpath(frameworkBinaryDestinationDirectory)) {
+ LogError() << "could not create destination directory" << frameworkBinaryDestinationDirectory;
+ return QString();
+ }
+
+ // Now copy the framework. Some parts should be left out (headers/, .prl files).
+ // Some parts should be included (Resources/, symlink structure). We want this
+ // function to make as few assumptions about the framework as possible while at
+ // the same time producing a codesign-compatible framework.
+
+ // Copy framework binary
+ copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath);
+
+ // Copy Resources/, Libraries/ and Helpers/
+ const QString resourcesSourcePath = framework.frameworkPath + "/Resources";
+ const QString resourcesDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Resources";
+ recursiveCopy(resourcesSourcePath, resourcesDestianationPath);
+ const QString librariesSourcePath = framework.frameworkPath + "/Libraries";
+ const QString librariesDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Libraries";
+ bool createdLibraries = recursiveCopy(librariesSourcePath, librariesDestianationPath);
+ const QString helpersSourcePath = framework.frameworkPath + "/Helpers";
+ const QString helpersDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Helpers";
+ bool createdHelpers = recursiveCopy(helpersSourcePath, helpersDestianationPath);
+
+ // Create symlink structure. Links at the framework root point to Versions/Current/
+ // which again points to the actual version:
+ // QtFoo.framework/QtFoo -> Versions/Current/QtFoo
+ // QtFoo.framework/Resources -> Versions/Current/Resources
+ // QtFoo.framework/Versions/Current -> 5
+ linkFilePrintStatus("Versions/Current/" + framework.binaryName, frameworkDestinationDirectory + "/" + framework.binaryName);
+ linkFilePrintStatus("Versions/Current/Resources", frameworkDestinationDirectory + "/Resources");
+ if (createdLibraries)
+ linkFilePrintStatus("Versions/Current/Libraries", frameworkDestinationDirectory + "/Libraries");
+ if (createdHelpers)
+ linkFilePrintStatus("Versions/Current/Helpers", frameworkDestinationDirectory + "/Helpers");
+ linkFilePrintStatus(framework.version, frameworkDestinationDirectory + "/Versions/Current");
+
+ // Correct Info.plist location for frameworks produced by older versions of qmake
+ // Contents/Info.plist should be Versions/5/Resources/Info.plist
+ const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist";
+ const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist";
+ if (QFile::exists(legacyInfoPlistPath)) {
+ copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
+ patch_debugInInfoPlist(correctInfoPlistPath);
+ }
+ return frameworkDestinationBinaryPath;
+}
+
+void runInstallNameTool(QStringList options)
+{
+ QProcess installNametool;
+ installNametool.start("install_name_tool", options);
+ installNametool.waitForFinished();
+ if (installNametool.exitCode() != 0) {
+ LogError() << installNametool.readAllStandardError();
+ LogError() << installNametool.readAllStandardOutput();
+ }
+}
+
+void changeIdentification(const QString &id, const QString &binaryPath)
+{
+ LogDebug() << "Using install_name_tool:";
+ LogDebug() << " change identification in" << binaryPath;
+ LogDebug() << " to" << id;
+ runInstallNameTool(QStringList() << "-id" << id << binaryPath);
+}
+
+void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework, const QStringList &binaryPaths, bool useLoaderPath)
+{
+ const QString absBundlePath = QFileInfo(bundlePath).absoluteFilePath();
+ for (const QString &binary : binaryPaths) {
+ QString deployedInstallName;
+ if (useLoaderPath) {
+ deployedInstallName = QLatin1String("@loader_path/")
+ + QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + QLatin1Char('/') + framework.binaryDestinationDirectory + QLatin1Char('/') + framework.binaryName);
+ } else {
+ deployedInstallName = framework.deployedInstallName;
+ }
+ changeInstallName(framework.installName, deployedInstallName, binary);
+ // Workaround for the case when the library ID name is a symlink, while the dependencies
+ // specified using the canonical path to the library (QTBUG-56814)
+ QString canonicalInstallName = QFileInfo(framework.installName).canonicalFilePath();
+ if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) {
+ changeInstallName(canonicalInstallName, deployedInstallName, binary);
+ }
+ }
+}
+
+void addRPath(const QString &rpath, const QString &binaryPath)
+{
+ runInstallNameTool(QStringList() << "-add_rpath" << rpath << binaryPath);
+}
+
+void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QString &binaryPath, bool useLoaderPath)
+{
+ const QString absFrameworksPath = QFileInfo(bundlePath).absoluteFilePath()
+ + QLatin1String("/Contents/Frameworks");
+ const QString relativeFrameworkPath = QFileInfo(binaryPath).absoluteDir().relativeFilePath(absFrameworksPath);
+ const QString loaderPathToFrameworks = QLatin1String("@loader_path/") + relativeFrameworkPath;
+ bool rpathToFrameworksFound = false;
+ QStringList args;
+ QList<QString> binaryRPaths = getBinaryRPaths(binaryPath, false);
+ for (const QString &rpath : std::as_const(binaryRPaths)) {
+ if (rpath == "@executable_path/../Frameworks" ||
+ rpath == loaderPathToFrameworks) {
+ rpathToFrameworksFound = true;
+ continue;
+ }
+ if (rpaths.contains(resolveDyldPrefix(rpath, binaryPath, binaryPath))) {
+ args << "-delete_rpath" << rpath;
+ }
+ }
+ if (!args.length()) {
+ return;
+ }
+ if (!rpathToFrameworksFound) {
+ if (!useLoaderPath) {
+ args << "-add_rpath" << "@executable_path/../Frameworks";
+ } else {
+ args << "-add_rpath" << loaderPathToFrameworks;
+ }
+ }
+ LogDebug() << "Using install_name_tool:";
+ LogDebug() << " change rpaths in" << binaryPath;
+ LogDebug() << " using" << args;
+ runInstallNameTool(QStringList() << args << binaryPath);
+}
+
+void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QStringList &binaryPaths, bool useLoaderPath)
+{
+ for (const QString &binary : binaryPaths) {
+ deployRPaths(bundlePath, rpaths, binary, useLoaderPath);
+ }
+}
+
+void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath)
+{
+ LogDebug() << "Using install_name_tool:";
+ LogDebug() << " in" << binaryPath;
+ LogDebug() << " change reference" << oldName;
+ LogDebug() << " to" << newName;
+ runInstallNameTool(QStringList() << "-change" << oldName << newName << binaryPath);
+}
+
+void runStrip(const QString &binaryPath)
+{
+ if (runStripEnabled == false)
+ return;
+
+ LogDebug() << "Using strip:";
+ LogDebug() << " stripped" << binaryPath;
+ QProcess strip;
+ strip.start("strip", QStringList() << "-x" << binaryPath);
+ strip.waitForFinished();
+ if (strip.exitCode() != 0) {
+ LogError() << strip.readAllStandardError();
+ LogError() << strip.readAllStandardOutput();
+ }
+}
+
+void stripAppBinary(const QString &bundlePath)
+{
+ runStrip(findAppBinary(bundlePath));
+}
+
+bool DeploymentInfo::containsModule(const QString &module, const QString &libInFix) const
+{
+ // Check for framework first
+ if (deployedFrameworks.contains(QLatin1String("Qt") + module + libInFix +
+ QLatin1String(".framework"))) {
+ return true;
+ }
+ // Check for dylib
+ const QRegularExpression dylibRegExp(QLatin1String("libQt[0-9]+") + module +
+ libInFix + QLatin1String(".[0-9]+.dylib"));
+ return deployedFrameworks.filter(dylibRegExp).size() > 0;
+}
+
+/*
+ Deploys the the listed frameworks listed into an app bundle.
+ The frameworks are searched for dependencies, which are also deployed.
+ (deploying Qt3Support will also deploy QtNetwork and QtSql for example.)
+ Returns a DeploymentInfo structure containing the Qt path used and a
+ a list of actually deployed frameworks.
+*/
+DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
+ const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs,
+ bool useLoaderPath)
+{
+ LogNormal();
+ LogNormal() << "Deploying Qt frameworks found inside:" << binaryPaths;
+ QStringList copiedFrameworks;
+ DeploymentInfo deploymentInfo;
+ deploymentInfo.useLoaderPath = useLoaderPath;
+ deploymentInfo.isFramework = bundlePath.contains(".framework");
+ deploymentInfo.isDebug = false;
+ QList<QString> rpathsUsed;
+
+ while (frameworks.isEmpty() == false) {
+ const FrameworkInfo framework = frameworks.takeFirst();
+ copiedFrameworks.append(framework.frameworkName);
+
+ // If a single dependency has the _debug suffix, we treat that as
+ // the whole deployment being a debug deployment, including deploying
+ // the debug version of plugins.
+ if (framework.isDebugLibrary())
+ deploymentInfo.isDebug = true;
+
+ if (deploymentInfo.qtPath.isNull())
+ deploymentInfo.qtPath = QLibraryInfo::path(QLibraryInfo::PrefixPath);
+
+ if (framework.frameworkDirectory.startsWith(bundlePath)) {
+ LogError() << framework.frameworkName << "already deployed, skipping.";
+ continue;
+ }
+
+ if (!framework.rpathUsed.isEmpty() && !rpathsUsed.contains(framework.rpathUsed)) {
+ rpathsUsed.append(framework.rpathUsed);
+ }
+
+ // Copy the framework/dylib to the app bundle.
+ const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
+ : copyFramework(framework, bundlePath);
+
+ // Install_name_tool the new id into the binaries
+ changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath);
+
+ // Skip the rest if already was deployed.
+ if (deployedBinaryPath.isNull())
+ continue;
+
+ runStrip(deployedBinaryPath);
+
+ // Install_name_tool it a new id.
+ if (!framework.rpathUsed.length()) {
+ changeIdentification(framework.deployedInstallName, deployedBinaryPath);
+ }
+
+ // Check for framework dependencies
+ QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
+
+ for (const FrameworkInfo &dependency : dependencies) {
+ if (dependency.rpathUsed.isEmpty()) {
+ changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath);
+ } else {
+ rpathsUsed.append(dependency.rpathUsed);
+ }
+
+ // Deploy framework if necessary.
+ if (copiedFrameworks.contains(dependency.frameworkName) == false && frameworks.contains(dependency) == false) {
+ frameworks.append(dependency);
+ }
+ }
+ }
+ deploymentInfo.deployedFrameworks = copiedFrameworks;
+ deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath);
+ deploymentInfo.rpathsUsed += rpathsUsed;
+ deploymentInfo.rpathsUsed.removeDuplicates();
+ return deploymentInfo;
+}
+
+DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs)
+{
+ ApplicationBundleInfo applicationBundle;
+ applicationBundle.path = appBundlePath;
+ applicationBundle.binaryPath = findAppBinary(appBundlePath);
+ applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
+ QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
+ << additionalExecutables;
+
+ QList<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true);
+ allLibraryPaths.append(QLibraryInfo::path(QLibraryInfo::LibrariesPath));
+ allLibraryPaths.removeDuplicates();
+
+ QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
+ if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) {
+ LogWarning();
+ LogWarning() << "Could not find any external Qt frameworks to deploy in" << appBundlePath;
+ LogWarning() << "Perhaps macdeployqt was already used on" << appBundlePath << "?";
+ LogWarning() << "If so, you will need to rebuild" << appBundlePath << "before trying again.";
+ return DeploymentInfo();
+ } else {
+ return deployQtFrameworks(frameworks, applicationBundle.path, allBinaryPaths, useDebugLibs, !additionalExecutables.isEmpty());
+ }
+}
+
+QString getLibInfix(const QStringList &deployedFrameworks)
+{
+ QString libInfix;
+ for (const QString &framework : deployedFrameworks) {
+ if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework")) &&
+ !framework.contains(QStringLiteral("5Compat"))) {
+ Q_ASSERT(framework.length() >= 16);
+ // 16 == "QtCore" + ".framework"
+ const int lengthOfLibInfix = framework.length() - 16;
+ if (lengthOfLibInfix)
+ libInfix = framework.mid(6, lengthOfLibInfix);
+ break;
+ }
+ }
+ return libInfix;
+}
+
+void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pluginSourcePath,
+ const QString pluginDestinationPath, DeploymentInfo deploymentInfo, bool useDebugLibs)
+{
+ LogNormal() << "Deploying plugins from" << pluginSourcePath;
+
+ if (!pluginSourcePath.contains(deploymentInfo.pluginPath))
+ return;
+
+ // Plugin white list:
+ QStringList pluginList;
+
+ const auto addPlugins = [&pluginSourcePath,&pluginList,useDebugLibs](const QString &subDirectory,
+ const std::function<bool(QString)> &predicate = std::function<bool(QString)>()) {
+ const QStringList libs = QDir(pluginSourcePath + QLatin1Char('/') + subDirectory)
+ .entryList({QStringLiteral("*.dylib")});
+ for (const QString &lib : libs) {
+ if (lib.endsWith(QStringLiteral("_debug.dylib")) != useDebugLibs)
+ continue;
+ if (!predicate || predicate(lib))
+ pluginList.append(subDirectory + QLatin1Char('/') + lib);
+ }
+ };
+
+ // Platform plugin:
+ addPlugins(QStringLiteral("platforms"), [](const QString &lib) {
+ // Ignore minimal and offscreen platform plugins
+ if (!lib.contains(QStringLiteral("cocoa")))
+ return false;
+ return true;
+ });
+
+ // Cocoa print support
+ addPlugins(QStringLiteral("printsupport"));
+
+ // Styles
+ addPlugins(QStringLiteral("styles"));
+
+ // Check if Qt was configured with -libinfix
+ const QString libInfix = getLibInfix(deploymentInfo.deployedFrameworks);
+
+ // Network
+ if (deploymentInfo.containsModule("Network", libInfix))
+ addPlugins(QStringLiteral("tls"));
+
+ // All image formats (svg if QtSvg is used)
+ const bool usesSvg = deploymentInfo.containsModule("Svg", libInfix);
+ addPlugins(QStringLiteral("imageformats"), [usesSvg](const QString &lib) {
+ if (lib.contains(QStringLiteral("qsvg")) && !usesSvg)
+ return false;
+ return true;
+ });
+
+ addPlugins(QStringLiteral("iconengines"));
+
+ // Platforminputcontext plugins if QtGui is in use
+ if (deploymentInfo.containsModule("Gui", libInfix)) {
+ addPlugins(QStringLiteral("platforminputcontexts"), [&addPlugins](const QString &lib) {
+ // Deploy the virtual keyboard plugins if we have deployed virtualkeyboard
+ if (lib.startsWith(QStringLiteral("libqtvirtualkeyboard")))
+ addPlugins(QStringLiteral("virtualkeyboard"));
+ return true;
+ });
+ }
+
+ // Sql plugins if QtSql is in use
+ if (deploymentInfo.containsModule("Sql", libInfix)) {
+ addPlugins(QStringLiteral("sqldrivers"), [](const QString &lib) {
+ if (lib.startsWith(QStringLiteral("libqsqlodbc")) || lib.startsWith(QStringLiteral("libqsqlpsql"))) {
+ LogWarning() << "Plugin" << lib << "uses private API and is not Mac App store compliant.";
+ if (appstoreCompliant) {
+ LogWarning() << "Skip plugin" << lib;
+ return false;
+ }
+ }
+ return true;
+ });
+ }
+
+ // WebView plugins if QtWebView is in use
+ if (deploymentInfo.containsModule("WebView", libInfix)) {
+ addPlugins(QStringLiteral("webview"), [](const QString &lib) {
+ if (lib.startsWith(QStringLiteral("libqtwebview_webengine"))) {
+ LogWarning() << "Plugin" << lib << "uses QtWebEngine and is not Mac App store compliant.";
+ if (appstoreCompliant) {
+ LogWarning() << "Skip plugin" << lib;
+ return false;
+ }
+ }
+ return true;
+ });
+ }
+
+ static const std::map<QString, std::vector<QString>> map {
+ {QStringLiteral("Multimedia"), {QStringLiteral("mediaservice"), QStringLiteral("audio")}},
+ {QStringLiteral("3DRender"), {QStringLiteral("sceneparsers"), QStringLiteral("geometryloaders"), QStringLiteral("renderers")}},
+ {QStringLiteral("3DQuickRender"), {QStringLiteral("renderplugins")}},
+ {QStringLiteral("Positioning"), {QStringLiteral("position")}},
+ {QStringLiteral("Location"), {QStringLiteral("geoservices")}},
+ {QStringLiteral("TextToSpeech"), {QStringLiteral("texttospeech")}}
+ };
+
+ for (const auto &it : map) {
+ if (deploymentInfo.containsModule(it.first, libInfix)) {
+ for (const auto &pluginType : it.second) {
+ addPlugins(pluginType);
+ }
+ }
+ }
+
+ for (const QString &plugin : pluginList) {
+ QString sourcePath = pluginSourcePath + "/" + plugin;
+ const QString destinationPath = pluginDestinationPath + "/" + plugin;
+ QDir dir;
+ dir.mkpath(QFileInfo(destinationPath).path());
+
+ if (copyFilePrintStatus(sourcePath, destinationPath)) {
+ runStrip(destinationPath);
+ QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
+ deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
+ }
+ }
+}
+
+void createQtConf(const QString &appBundlePath)
+{
+ // Set Plugins and imports paths. These are relative to App.app/Contents.
+ QByteArray contents = "[Paths]\n"
+ "Plugins = PlugIns\n"
+ "Imports = Resources/qml\n"
+ "QmlImports = Resources/qml\n";
+
+ QString filePath = appBundlePath + "/Contents/Resources/";
+ QString fileName = filePath + "qt.conf";
+
+ QDir().mkpath(filePath);
+
+ QFile qtconf(fileName);
+ if (qtconf.exists() && !alwaysOwerwriteEnabled) {
+ LogWarning();
+ LogWarning() << fileName << "already exists, will not overwrite.";
+ LogWarning() << "To make sure the plugins are loaded from the correct location,";
+ LogWarning() << "please make sure qt.conf contains the following lines:";
+ LogWarning() << "[Paths]";
+ LogWarning() << " Plugins = PlugIns";
+ return;
+ }
+
+ qtconf.open(QIODevice::WriteOnly);
+ if (qtconf.write(contents) != -1) {
+ LogNormal() << "Created configuration file:" << fileName;
+ LogNormal() << "This file sets the plugin search path to" << appBundlePath + "/Contents/PlugIns";
+ }
+}
+
+void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs)
+{
+ ApplicationBundleInfo applicationBundle;
+ applicationBundle.path = appBundlePath;
+ applicationBundle.binaryPath = findAppBinary(appBundlePath);
+
+ const QString pluginDestinationPath = appBundlePath + "/" + "Contents/PlugIns";
+ deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
+}
+
+void deployQmlImport(const QString &appBundlePath, const QList<QString> &rpaths, const QString &importSourcePath, const QString &importName)
+{
+ QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName;
+
+ // Skip already deployed imports. This can happen in cases like "QtQuick.Controls.Styles",
+ // where deploying QtQuick.Controls will also deploy the "Styles" sub-import.
+ if (QDir().exists(importDestinationPath))
+ return;
+
+ recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath);
+}
+
+static bool importLessThan(const QVariant &v1, const QVariant &v2)
+{
+ QVariantMap import1 = v1.toMap();
+ QVariantMap import2 = v2.toMap();
+ QString path1 = import1["path"].toString();
+ QString path2 = import2["path"].toString();
+ return path1 < path2;
+}
+
+// Scan qml files in qmldirs for import statements, deploy used imports from QmlImportsPath to Contents/Resources/qml.
+bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths)
+{
+ LogNormal() << "";
+ LogNormal() << "Deploying QML imports ";
+ LogNormal() << "Application QML file path(s) is" << qmlDirs;
+ LogNormal() << "QML module search path(s) is" << qmlImportPaths;
+
+ // Use qmlimportscanner from QLibraryInfo::LibraryExecutablesPath
+ QString qmlImportScannerPath =
+ QDir::cleanPath(QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath)
+ + "/qmlimportscanner");
+
+ // Fallback: Look relative to the macdeployqt binary
+ if (!QFile::exists(qmlImportScannerPath))
+ qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
+
+ // Verify that we found a qmlimportscanner binary
+ if (!QFile::exists(qmlImportScannerPath)) {
+ LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
+ LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
+ return false;
+ }
+
+ // build argument list for qmlimportsanner: "-rootPath foo/ -rootPath bar/ -importPath path/to/qt/qml"
+ // ("rootPath" points to a directory containing app qml, "importPath" is where the Qt imports are installed)
+ QStringList argumentList;
+ for (const QString &qmlDir : qmlDirs) {
+ argumentList.append("-rootPath");
+ argumentList.append(qmlDir);
+ }
+ for (const QString &importPath : qmlImportPaths)
+ argumentList << "-importPath" << importPath;
+ QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
+ argumentList.append( "-importPath");
+ argumentList.append(qmlImportsPath);
+
+ // run qmlimportscanner
+ QProcess qmlImportScanner;
+ qmlImportScanner.start(qmlImportScannerPath, argumentList);
+ if (!qmlImportScanner.waitForStarted()) {
+ LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
+ return false;
+ }
+ qmlImportScanner.waitForFinished(-1);
+
+ // log qmlimportscanner errors
+ qmlImportScanner.setReadChannel(QProcess::StandardError);
+ QByteArray errors = qmlImportScanner.readAll();
+ if (!errors.isEmpty()) {
+ LogWarning() << "QML file parse error (deployment will continue):";
+ LogWarning() << errors;
+ }
+
+ // parse qmlimportscanner json
+ qmlImportScanner.setReadChannel(QProcess::StandardOutput);
+ QByteArray json = qmlImportScanner.readAll();
+ QJsonDocument doc = QJsonDocument::fromJson(json);
+ if (!doc.isArray()) {
+ LogError() << "qmlimportscanner output error. Expected json array, got:";
+ LogError() << json;
+ return false;
+ }
+
+ // sort imports to deploy a module before its sub-modules (otherwise
+ // deployQmlImports can consider the module deployed if it has already
+ // deployed one of its sub-module)
+ QVariantList array = doc.array().toVariantList();
+ std::sort(array.begin(), array.end(), importLessThan);
+
+ // deploy each import
+ for (const QVariant &importValue : array) {
+ QVariantMap import = importValue.toMap();
+ QString name = import["name"].toString();
+ QString path = import["path"].toString();
+ QString type = import["type"].toString();
+
+ LogNormal() << "Deploying QML import" << name;
+
+ // Skip imports with missing info - path will be empty if the import is not found.
+ if (name.isEmpty() || path.isEmpty()) {
+ LogNormal() << " Skip import: name or path is empty";
+ LogNormal() << "";
+ continue;
+ }
+
+ // Deploy module imports only, skip directory (local/remote) and js imports. These
+ // should be deployed as a part of the application build.
+ if (type != QStringLiteral("module")) {
+ LogNormal() << " Skip non-module import";
+ LogNormal() << "";
+ continue;
+ }
+
+ // Create the destination path from the name
+ // and version (grabbed from the source path)
+ // ### let qmlimportscanner provide this.
+ name.replace(QLatin1Char('.'), QLatin1Char('/'));
+ int secondTolast = path.length() - 2;
+ QString version = path.mid(secondTolast);
+ if (version.startsWith(QLatin1Char('.')))
+ name.append(version);
+
+ deployQmlImport(appBundlePath, deploymentInfo.rpathsUsed, path, name);
+ LogNormal() << "";
+ }
+ return true;
+}
+
+void codesignFile(const QString &identity, const QString &filePath)
+{
+ if (!runCodesign)
+ return;
+
+ QString codeSignLogMessage = "codesign";
+ if (hardenedRuntime)
+ codeSignLogMessage += ", enable hardened runtime";
+ if (secureTimestamp)
+ codeSignLogMessage += ", include secure timestamp";
+ LogNormal() << codeSignLogMessage << filePath;
+
+ QStringList codeSignOptions = { "--preserve-metadata=identifier,entitlements", "--force", "-s",
+ identity, filePath };
+ if (hardenedRuntime)
+ codeSignOptions << "-o" << "runtime";
+
+ if (secureTimestamp)
+ codeSignOptions << "--timestamp";
+
+ if (!extraEntitlements.isEmpty())
+ codeSignOptions << "--entitlements" << extraEntitlements;
+
+ QProcess codesign;
+ codesign.start("codesign", codeSignOptions);
+ codesign.waitForFinished(-1);
+
+ QByteArray err = codesign.readAllStandardError();
+ if (codesign.exitCode() > 0) {
+ LogError() << "Codesign signing error:";
+ LogError() << err;
+ } else if (!err.isEmpty()) {
+ LogDebug() << err;
+ }
+}
+
+QSet<QString> codesignBundle(const QString &identity,
+ const QString &appBundlePath,
+ QList<QString> additionalBinariesContainingRpaths)
+{
+ // Code sign all binaries in the app bundle. This needs to
+ // be done inside-out, e.g sign framework dependencies
+ // before the main app binary. The codesign tool itself has
+ // a "--deep" option to do this, but usage when signing is
+ // not recommended: "Signing with --deep is for emergency
+ // repairs and temporary adjustments only."
+
+ LogNormal() << "";
+ LogNormal() << "Signing" << appBundlePath << "with identity" << identity;
+
+ QStack<QString> pendingBinaries;
+ QSet<QString> pendingBinariesSet;
+ QSet<QString> signedBinaries;
+
+ // Create the root code-binary set. This set consists of the application
+ // executable(s) and the plugins.
+ QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath();
+ QString rootBinariesPath = appBundleAbsolutePath + "/Contents/MacOS/";
+ QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() << "*", QDir::Files);
+ for (const QString &binary : foundRootBinaries) {
+ QString binaryPath = rootBinariesPath + binary;
+ pendingBinaries.push(binaryPath);
+ pendingBinariesSet.insert(binaryPath);
+ additionalBinariesContainingRpaths.append(binaryPath);
+ }
+
+ bool getAbsoltuePath = true;
+ QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath + "/Contents/PlugIns/", getAbsoltuePath);
+ for (const QString &binary : foundPluginBinaries) {
+ pendingBinaries.push(binary);
+ pendingBinariesSet.insert(binary);
+ }
+
+ // Add frameworks for processing.
+ QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath);
+ for (const QString &frameworkPath : frameworkPaths) {
+
+ // Prioritise first to sign any additional inner bundles found in the Helpers folder (e.g
+ // used by QtWebEngine).
+ QDirIterator helpersIterator(frameworkPath, QStringList() << QString::fromLatin1("Helpers"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
+ while (helpersIterator.hasNext()) {
+ helpersIterator.next();
+ QString helpersPath = helpersIterator.filePath();
+ QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() << "*.app", QDir::Dirs);
+ for (const QString &innerBundleName : innerBundleNames)
+ signedBinaries += codesignBundle(identity,
+ helpersPath + "/" + innerBundleName,
+ additionalBinariesContainingRpaths);
+ }
+
+ // Also make sure to sign any libraries that will not be found by otool because they
+ // are not linked and won't be seen as a dependency.
+ QDirIterator librariesIterator(frameworkPath, QStringList() << QString::fromLatin1("Libraries"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
+ while (librariesIterator.hasNext()) {
+ librariesIterator.next();
+ QString librariesPath = librariesIterator.filePath();
+ QStringList bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath);
+ for (const QString &binary : bundleFiles) {
+ pendingBinaries.push(binary);
+ pendingBinariesSet.insert(binary);
+ }
+ }
+ }
+
+ // Sign all binaries; use otool to find and sign dependencies first.
+ while (!pendingBinaries.isEmpty()) {
+ QString binary = pendingBinaries.pop();
+ if (signedBinaries.contains(binary))
+ continue;
+
+ // Check if there are unsigned dependencies, sign these first.
+ QStringList dependencies = getBinaryDependencies(rootBinariesPath, binary,
+ additionalBinariesContainingRpaths);
+ dependencies = QSet<QString>(dependencies.begin(), dependencies.end())
+ .subtract(signedBinaries)
+ .subtract(pendingBinariesSet)
+ .values();
+
+ if (!dependencies.isEmpty()) {
+ pendingBinaries.push(binary);
+ pendingBinariesSet.insert(binary);
+ int dependenciesSkipped = 0;
+ for (const QString &dependency : std::as_const(dependencies)) {
+ // Skip dependencies that are outside the current app bundle, because this might
+ // cause a codesign error if the current bundle is part of the dependency (e.g.
+ // a bundle is part of a framework helper, and depends on that framework).
+ // The dependencies will be taken care of after the current bundle is signed.
+ if (!dependency.startsWith(appBundleAbsolutePath)) {
+ ++dependenciesSkipped;
+ LogNormal() << "Skipping outside dependency: " << dependency;
+ continue;
+ }
+ pendingBinaries.push(dependency);
+ pendingBinariesSet.insert(dependency);
+ }
+
+ // If all dependencies were skipped, make sure the binary is actually signed, instead
+ // of going into an infinite loop.
+ if (dependenciesSkipped == dependencies.size()) {
+ pendingBinaries.pop();
+ } else {
+ continue;
+ }
+ }
+
+ // Look for an entitlements file in the bundle to include when signing
+ extraEntitlements = findEntitlementsFile(appBundleAbsolutePath + "/Contents/Resources/");
+
+ // All dependencies are signed, now sign this binary.
+ codesignFile(identity, binary);
+ signedBinaries.insert(binary);
+ pendingBinariesSet.remove(binary);
+ }
+
+ LogNormal() << "Finished codesigning " << appBundlePath << "with identity" << identity;
+
+ // Verify code signature
+ QProcess codesign;
+ codesign.start("codesign", QStringList() << "--deep" << "-v" << appBundlePath);
+ codesign.waitForFinished(-1);
+ QByteArray err = codesign.readAllStandardError();
+ if (codesign.exitCode() > 0) {
+ LogError() << "codesign verification error:";
+ LogError() << err;
+ } else if (!err.isEmpty()) {
+ LogDebug() << err;
+ }
+
+ return signedBinaries;
+}
+
+void codesign(const QString &identity, const QString &appBundlePath) {
+ codesignBundle(identity, appBundlePath, QList<QString>());
+}
+
+void createDiskImage(const QString &appBundlePath, const QString &filesystemType)
+{
+ QString appBaseName = appBundlePath;
+ appBaseName.chop(4); // remove ".app" from end
+
+ QString dmgName = appBaseName + ".dmg";
+
+ QFile dmg(dmgName);
+
+ if (dmg.exists() && alwaysOwerwriteEnabled)
+ dmg.remove();
+
+ if (dmg.exists()) {
+ LogNormal() << "Disk image already exists, skipping .dmg creation for" << dmg.fileName();
+ } else {
+ LogNormal() << "Creating disk image (.dmg) for" << appBundlePath;
+ }
+
+ LogNormal() << "Image will use" << filesystemType;
+
+ // More dmg options can be found in the hdiutil man page.
+ QStringList options = QStringList()
+ << "create" << dmgName
+ << "-srcfolder" << appBundlePath
+ << "-format" << "UDZO"
+ << "-fs" << filesystemType
+ << "-volname" << appBaseName;
+
+ QProcess hdutil;
+ hdutil.start("hdiutil", options);
+ hdutil.waitForFinished(-1);
+ if (hdutil.exitCode() != 0) {
+ LogError() << "Bundle creation error:" << hdutil.readAllStandardError();
+ }
+}
+
+void fixupFramework(const QString &frameworkName)
+{
+ // Expected framework name looks like "Foo.framework"
+ QStringList parts = frameworkName.split(".");
+ if (parts.count() < 2) {
+ LogError() << "fixupFramework: Unexpected framework name" << frameworkName;
+ return;
+ }
+
+ // Assume framework binary path is Foo.framework/Foo
+ QString frameworkBinary = frameworkName + QStringLiteral("/") + parts[0];
+
+ // Xcode expects to find Foo.framework/Versions/A when code
+ // signing, while qmake typically generates numeric versions.
+ // Create symlink to the actual version in the framework.
+ linkFilePrintStatus("Current", frameworkName + "/Versions/A");
+
+ // Set up @rpath structure.
+ changeIdentification("@rpath/" + frameworkBinary, frameworkBinary);
+ addRPath("@loader_path/../../Contents/Frameworks/", frameworkBinary);
+}
diff --git a/src/tools/macdeployqt/shared/shared.h b/src/tools/macdeployqt/shared/shared.h
new file mode 100644
index 0000000000..48ea24b119
--- /dev/null
+++ b/src/tools/macdeployqt/shared/shared.h
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef MAC_DEPLOMYMENT_SHARED_H
+#define MAC_DEPLOMYMENT_SHARED_H
+
+#include <QString>
+#include <QStringList>
+#include <QDebug>
+#include <QSet>
+#include <QVersionNumber>
+
+extern int logLevel;
+#define LogError() if (logLevel < 0) {} else qDebug() << "ERROR:"
+#define LogWarning() if (logLevel < 1) {} else qDebug() << "WARNING:"
+#define LogNormal() if (logLevel < 2) {} else qDebug() << "Log:"
+#define LogDebug() if (logLevel < 3) {} else qDebug() << "Log:"
+
+extern bool runStripEnabled;
+
+class FrameworkInfo
+{
+public:
+ bool isDylib;
+ QString frameworkDirectory;
+ QString frameworkName;
+ QString frameworkPath;
+ QString binaryDirectory;
+ QString binaryName;
+ QString binaryPath;
+ QString rpathUsed;
+ QString version;
+ QString installName;
+ QString deployedInstallName;
+ QString sourceFilePath;
+ QString frameworkDestinationDirectory;
+ QString binaryDestinationDirectory;
+
+ bool isDebugLibrary() const
+ {
+ return binaryName.endsWith(QStringLiteral("_debug"));
+ }
+};
+
+class DylibInfo
+{
+public:
+ QString binaryPath;
+ QVersionNumber currentVersion;
+ QVersionNumber compatibilityVersion;
+};
+
+class OtoolInfo
+{
+public:
+ QString installName;
+ QString binaryPath;
+ QVersionNumber currentVersion;
+ QVersionNumber compatibilityVersion;
+ QList<DylibInfo> dependencies;
+};
+
+bool operator==(const FrameworkInfo &a, const FrameworkInfo &b);
+QDebug operator<<(QDebug debug, const FrameworkInfo &info);
+
+class ApplicationBundleInfo
+{
+ public:
+ QString path;
+ QString binaryPath;
+ QStringList libraryPaths;
+};
+
+class DeploymentInfo
+{
+public:
+ QString qtPath;
+ QString pluginPath;
+ QStringList deployedFrameworks;
+ QList<QString> rpathsUsed;
+ bool useLoaderPath;
+ bool isFramework;
+ bool isDebug;
+
+ bool containsModule(const QString &module, const QString &libInFix) const;
+};
+
+inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info);
+
+OtoolInfo findDependencyInfo(const QString &binaryPath);
+FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs);
+QString findAppBinary(const QString &appBundlePath);
+QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs);
+QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs);
+QString copyFramework(const FrameworkInfo &framework, const QString path);
+DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs);
+DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath);
+void createQtConf(const QString &appBundlePath);
+void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs);
+bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths);
+void changeIdentification(const QString &id, const QString &binaryPath);
+void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath);
+void runStrip(const QString &binaryPath);
+void stripAppBinary(const QString &bundlePath);
+QString findAppBinary(const QString &appBundlePath);
+QStringList findAppFrameworkNames(const QString &appBundlePath);
+QStringList findAppFrameworkPaths(const QString &appBundlePath);
+void codesignFile(const QString &identity, const QString &filePath);
+QSet<QString> codesignBundle(const QString &identity,
+ const QString &appBundlePath,
+ QList<QString> additionalBinariesContainingRpaths);
+void codesign(const QString &identity, const QString &appBundlePath);
+void createDiskImage(const QString &appBundlePath, const QString &filesystemType);
+void fixupFramework(const QString &appBundlePath);
+
+
+#endif
diff --git a/src/tools/windeployqt/CMakeLists.txt b/src/tools/windeployqt/CMakeLists.txt
new file mode 100644
index 0000000000..a63af56efb
--- /dev/null
+++ b/src/tools/windeployqt/CMakeLists.txt
@@ -0,0 +1,32 @@
+#####################################################################
+## windeployqt Tool:
+#####################################################################
+
+qt_get_tool_target_name(target_name windeployqt)
+qt_internal_add_tool(${target_name}
+ TOOLS_TARGET Core
+ USER_FACING
+ TARGET_DESCRIPTION "Qt Windows Deployment Tool"
+ SOURCES
+ elfreader.cpp elfreader.h
+ qmlutils.cpp qmlutils.h
+ utils.cpp utils.h
+ main.cpp
+ DEFINES
+ QT_NO_CAST_FROM_ASCII
+ QT_NO_CAST_TO_ASCII
+ QT_NO_FOREACH
+ LIBRARIES
+ Qt::CorePrivate
+)
+qt_internal_return_unless_building_tools()
+
+qt_internal_extend_target(${target_name} CONDITION WIN32
+ PUBLIC_LIBRARIES
+ shlwapi
+)
+
+qt_internal_extend_target(${target_name} CONDITION QT_FEATURE_relocatable
+ DEFINES
+ QT_RELOCATABLE
+)
diff --git a/src/tools/windeployqt/elfreader.cpp b/src/tools/windeployqt/elfreader.cpp
new file mode 100644
index 0000000000..f375f5841d
--- /dev/null
+++ b/src/tools/windeployqt/elfreader.cpp
@@ -0,0 +1,440 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "elfreader.h"
+
+#include <QDir>
+
+QT_BEGIN_NAMESPACE
+
+/* This is a copy of the ELF reader contained in Qt Creator (src/libs/utils),
+ * extended by the dependencies() function to read out the dependencies of a dynamic executable. */
+
+quint16 getHalfWord(const unsigned char *&s, const ElfData &context)
+{
+ quint16 res;
+ if (context.endian == Elf_ELFDATA2MSB)
+ res = qFromBigEndian<quint16>(s);
+ else
+ res = qFromLittleEndian<quint16>(s);
+ s += 2;
+ return res;
+}
+
+quint32 getWord(const unsigned char *&s, const ElfData &context)
+{
+ quint32 res;
+ if (context.endian == Elf_ELFDATA2MSB)
+ res = qFromBigEndian<quint32>(s);
+ else
+ res = qFromLittleEndian<quint32>(s);
+ s += 4;
+ return res;
+}
+
+quint64 getAddress(const unsigned char *&s, const ElfData &context)
+{
+ quint64 res;
+ if (context.elfclass == Elf_ELFCLASS32) {
+ if (context.endian == Elf_ELFDATA2MSB)
+ res = qFromBigEndian<quint32>(s);
+ else
+ res = qFromLittleEndian<quint32>(s);
+ s += 4;
+ } else {
+ if (context.endian == Elf_ELFDATA2MSB)
+ res = qFromBigEndian<quint64>(s);
+ else
+ res = qFromLittleEndian<quint64>(s);
+ s += 8;
+ }
+ return res;
+}
+
+quint64 getOffset(const unsigned char *&s, const ElfData &context)
+{
+ return getAddress(s, context);
+}
+
+static void parseSectionHeader(const uchar *s, ElfSectionHeader *sh, const ElfData &context)
+{
+ sh->index = getWord(s, context);
+ sh->type = getWord(s, context);
+ sh->flags = quint32(getOffset(s, context));
+ sh->addr = getAddress(s, context);
+ sh->offset = getOffset(s, context);
+ sh->size = getOffset(s, context);
+}
+
+static void parseProgramHeader(const uchar *s, ElfProgramHeader *sh, const ElfData &context)
+{
+ sh->type = getWord(s, context);
+ sh->offset = getOffset(s, context);
+ /* p_vaddr = */ getAddress(s, context);
+ /* p_paddr = */ getAddress(s, context);
+ sh->filesz = getWord(s, context);
+ sh->memsz = getWord(s, context);
+}
+
+class ElfMapper
+{
+public:
+ ElfMapper(const ElfReader *reader) : file(reader->m_binary) {}
+
+ bool map()
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ return false;
+
+ fdlen = quint64(file.size());
+ ustart = file.map(0, qint64(fdlen));
+ if (ustart == 0) {
+ // Try reading the data into memory instead.
+ raw = file.readAll();
+ start = raw.constData();
+ fdlen = quint64(raw.size());
+ }
+ return true;
+ }
+
+public:
+ QFile file;
+ QByteArray raw;
+ union { const char *start; const uchar *ustart; };
+ quint64 fdlen;
+};
+
+ElfReader::ElfReader(const QString &binary)
+ : m_binary(binary)
+{
+}
+
+ElfData ElfReader::readHeaders()
+{
+ readIt();
+ return m_elfData;
+}
+
+static inline QString msgInvalidElfObject(const QString &binary, const QString &why)
+{
+ return QStringLiteral("'%1' is an invalid ELF object (%2)")
+ .arg(QDir::toNativeSeparators(binary), why);
+}
+
+ElfReader::Result ElfReader::readIt()
+{
+ if (!m_elfData.sectionHeaders.isEmpty())
+ return Ok;
+ if (!m_elfData.programHeaders.isEmpty())
+ return Ok;
+
+ ElfMapper mapper(this);
+ if (!mapper.map())
+ return Corrupt;
+
+ const quint64 fdlen = mapper.fdlen;
+
+ if (fdlen < 64) {
+ m_errorString = QStringLiteral("'%1' is not an ELF object (file too small)").arg(QDir::toNativeSeparators(m_binary));
+ return NotElf;
+ }
+
+ if (strncmp(mapper.start, "\177ELF", 4) != 0) {
+ m_errorString = QStringLiteral("'%1' is not an ELF object").arg(QDir::toNativeSeparators(m_binary));
+ return NotElf;
+ }
+
+ // 32 or 64 bit
+ m_elfData.elfclass = ElfClass(mapper.start[4]);
+ const bool is64Bit = m_elfData.elfclass == Elf_ELFCLASS64;
+ if (m_elfData.elfclass != Elf_ELFCLASS32 && m_elfData.elfclass != Elf_ELFCLASS64) {
+ m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("odd cpu architecture"));
+ return Corrupt;
+ }
+
+ // int bits = (data[4] << 5);
+ // If you remove this check to read ELF objects of a different arch,
+ // please make sure you modify the typedefs
+ // to match the _plugin_ architecture.
+ // if ((sizeof(void*) == 4 && bits != 32)
+ // || (sizeof(void*) == 8 && bits != 64)) {
+ // if (errorString)
+ // *errorString = QLibrary::QStringLiteral("'%1' is an invalid ELF object (%2)")
+ // .arg(m_binary).arg(QLatin1String("wrong cpu architecture"));
+ // return Corrupt;
+ // }
+
+ // Read Endianhness.
+ m_elfData.endian = ElfEndian(mapper.ustart[5]);
+ if (m_elfData.endian != Elf_ELFDATA2LSB && m_elfData.endian != Elf_ELFDATA2MSB) {
+ m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("odd endianness"));
+ return Corrupt;
+ }
+
+ const uchar *data = mapper.ustart + 16; // e_ident
+ m_elfData.elftype = ElfType(getHalfWord(data, m_elfData));
+ m_elfData.elfmachine = ElfMachine(getHalfWord(data, m_elfData));
+ /* e_version = */ getWord(data, m_elfData);
+ m_elfData.entryPoint = getAddress(data, m_elfData);
+
+ quint64 e_phoff = getOffset(data, m_elfData);
+ quint64 e_shoff = getOffset(data, m_elfData);
+ /* e_flags = */ getWord(data, m_elfData);
+
+ quint32 e_shsize = getHalfWord(data, m_elfData);
+
+ if (e_shsize > fdlen) {
+ m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_shsize"));
+ return Corrupt;
+ }
+
+ quint32 e_phentsize = getHalfWord(data, m_elfData);
+ if (e_phentsize != (is64Bit ? 56 : 32)) {
+ m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("invalid structure"));
+ return ElfReader::Corrupt;
+ }
+ quint32 e_phnum = getHalfWord(data, m_elfData);
+
+ quint32 e_shentsize = getHalfWord(data, m_elfData);
+
+ if (e_shentsize % 4) {
+ m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_shentsize"));
+ return Corrupt;
+ }
+
+ quint32 e_shnum = getHalfWord(data, m_elfData);
+ quint32 e_shtrndx = getHalfWord(data, m_elfData);
+ if (data != mapper.ustart + (is64Bit ? 64 : 52)) {
+ m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_phentsize"));
+ return ElfReader::Corrupt;
+ }
+
+ if (quint64(e_shnum) * e_shentsize > fdlen) {
+ const QString reason = QStringLiteral("announced %1 sections, each %2 bytes, exceed file size").arg(e_shnum).arg(e_shentsize);
+ m_errorString = msgInvalidElfObject(m_binary, reason);
+ return Corrupt;
+ }
+
+ quint64 soff = e_shoff + e_shentsize * e_shtrndx;
+
+// if ((soff + e_shentsize) > fdlen || soff % 4 || soff == 0) {
+// m_errorString = QLibrary::QStringLiteral("'%1' is an invalid ELF object (%2)")
+// .arg(m_binary)
+// .arg(QLatin1String("shstrtab section header seems to be at %1"))
+// .arg(QString::number(soff, 16));
+// return Corrupt;
+// }
+
+ if (e_shoff) {
+ ElfSectionHeader strtab;
+ parseSectionHeader(mapper.ustart + soff, &strtab, m_elfData);
+ const quint64 stringTableFileOffset = strtab.offset;
+ if (quint32(stringTableFileOffset + e_shentsize) >= fdlen
+ || stringTableFileOffset == 0) {
+ const QString reason = QStringLiteral("string table seems to be at 0x%1").arg(soff, 0, 16);
+ m_errorString = msgInvalidElfObject(m_binary, reason);
+ return Corrupt;
+ }
+
+ for (quint32 i = 0; i < e_shnum; ++i) {
+ const uchar *s = mapper.ustart + e_shoff + i * e_shentsize;
+ ElfSectionHeader sh;
+ parseSectionHeader(s, &sh, m_elfData);
+
+ if (stringTableFileOffset + sh.index > fdlen) {
+ const QString reason = QStringLiteral("section name %1 of %2 behind end of file")
+ .arg(i).arg(e_shnum);
+ m_errorString = msgInvalidElfObject(m_binary, reason);
+ return Corrupt;
+ }
+
+ sh.name = mapper.start + stringTableFileOffset + sh.index;
+ if (sh.name == ".gdb_index") {
+ m_elfData.symbolsType = FastSymbols;
+ } else if (sh.name == ".debug_info") {
+ m_elfData.symbolsType = PlainSymbols;
+ } else if (sh.name == ".gnu_debuglink") {
+ m_elfData.debugLink = QByteArray(mapper.start + sh.offset);
+ m_elfData.symbolsType = LinkedSymbols;
+ } else if (sh.name == ".note.gnu.build-id") {
+ m_elfData.symbolsType = BuildIdSymbols;
+ if (sh.size > 16)
+ m_elfData.buildId = QByteArray(mapper.start + sh.offset + 16,
+ int(sh.size) - 16).toHex();
+ }
+ m_elfData.sectionHeaders.append(sh);
+ }
+ }
+
+ if (e_phoff) {
+ for (quint32 i = 0; i < e_phnum; ++i) {
+ const uchar *s = mapper.ustart + e_phoff + i * e_phentsize;
+ ElfProgramHeader ph;
+ parseProgramHeader(s, &ph, m_elfData);
+ m_elfData.programHeaders.append(ph);
+ }
+ }
+ return Ok;
+}
+
+QByteArray ElfReader::readSection(const QByteArray &name)
+{
+ readIt();
+ int i = m_elfData.indexOf(name);
+ if (i == -1)
+ return QByteArray();
+
+ ElfMapper mapper(this);
+ if (!mapper.map())
+ return QByteArray();
+
+ const ElfSectionHeader &section = m_elfData.sectionHeaders.at(i);
+ return QByteArray(mapper.start + section.offset, int(section.size));
+}
+
+static QByteArray cutout(const char *s)
+{
+ QByteArray res(s, 80);
+ const int pos = res.indexOf('\0');
+ if (pos != -1)
+ res.resize(pos - 1);
+ return res;
+}
+
+QByteArray ElfReader::readCoreName(bool *isCore)
+{
+ *isCore = false;
+
+ readIt();
+
+ ElfMapper mapper(this);
+ if (!mapper.map())
+ return QByteArray();
+
+ if (m_elfData.elftype != Elf_ET_CORE)
+ return QByteArray();
+
+ *isCore = true;
+
+ for (int i = 0, n = m_elfData.sectionHeaders.size(); i != n; ++i)
+ if (m_elfData.sectionHeaders.at(i).type == Elf_SHT_NOTE) {
+ const ElfSectionHeader &header = m_elfData.sectionHeaders.at(i);
+ return cutout(mapper.start + header.offset + 0x40);
+ }
+
+ for (int i = 0, n = m_elfData.programHeaders.size(); i != n; ++i)
+ if (m_elfData.programHeaders.at(i).type == Elf_PT_NOTE) {
+ const ElfProgramHeader &header = m_elfData.programHeaders.at(i);
+ return cutout(mapper.start + header.offset + 0xec);
+ }
+
+ return QByteArray();
+}
+
+int ElfData::indexOf(const QByteArray &name) const
+{
+ for (int i = 0, n = sectionHeaders.size(); i != n; ++i)
+ if (sectionHeaders.at(i).name == name)
+ return i;
+ return -1;
+}
+
+/* Helpers for reading out the .dynamic section containing the dependencies.
+ * The ".dynamic" section is an array of
+ * typedef struct {
+ * Elf32_Sword d_tag;
+ * union {
+ * Elf32_Word d_val;
+ * dElf32_Addr d_ptr;
+ * } d_un;
+ * } Elf32_Dyn
+ * with entries where a tag DT_NEEDED indicates that m_val is an offset into
+ * the string table ".dynstr". The documentation states that entries with the
+ * tag DT_STRTAB contain an offset for the string table to be used, but that
+ * has been found not to contain valid entries. */
+
+enum DynamicSectionTags {
+ DT_NULL = 0,
+ DT_NEEDED = 1,
+ DT_STRTAB = 5,
+ DT_SONAME = 14,
+ DT_RPATH = 15
+};
+
+QList<QByteArray> ElfReader::dependencies()
+{
+ QList<QByteArray> result;
+
+ ElfMapper mapper(this);
+ if (!mapper.map()) {
+ m_errorString = QStringLiteral("Mapper failure");
+ return result;
+ }
+ quint64 dynStrOffset = 0;
+ quint64 dynamicOffset = 0;
+ quint64 dynamicSize = 0;
+
+ const QList<ElfSectionHeader> &headers = readHeaders().sectionHeaders;
+ for (const ElfSectionHeader &eh : headers) {
+ if (eh.name == QByteArrayLiteral(".dynstr")) {
+ dynStrOffset = eh.offset;
+ } else if (eh.name == QByteArrayLiteral(".dynamic")) {
+ dynamicOffset = eh.offset;
+ dynamicSize = eh.size;
+ }
+ if (dynStrOffset && dynamicOffset)
+ break;
+ }
+
+ if (!dynStrOffset || !dynamicOffset) {
+ m_errorString = QStringLiteral("Not a dynamically linked executable.");
+ return result;
+ }
+
+ const unsigned char *dynamicData = mapper.ustart + dynamicOffset;
+ const unsigned char *dynamicDataEnd = dynamicData + dynamicSize;
+ while (dynamicData < dynamicDataEnd) {
+ const quint32 tag = getWord(dynamicData, m_elfData);
+ if (tag == DT_NULL)
+ break;
+ if (m_elfData.elfclass == Elf_ELFCLASS64)
+ dynamicData += sizeof(quint32); // padding to d_val/d_ptr.
+ if (tag == DT_NEEDED) {
+ const quint32 offset = getWord(dynamicData, m_elfData);
+ if (m_elfData.elfclass == Elf_ELFCLASS64)
+ dynamicData += sizeof(quint32); // past d_ptr.
+ const char *name = mapper.start + dynStrOffset + offset;
+ result.push_back(name);
+ } else {
+ dynamicData += m_elfData.elfclass == Elf_ELFCLASS64 ? 8 : 4;
+ }
+ }
+ return result;
+}
+
+QT_END_NAMESPACE
diff --git a/src/tools/windeployqt/elfreader.h b/src/tools/windeployqt/elfreader.h
new file mode 100644
index 0000000000..d84f5a0602
--- /dev/null
+++ b/src/tools/windeployqt/elfreader.h
@@ -0,0 +1,176 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ELFREADER_H
+#define ELFREADER_H
+
+#include <QtCore/QList>
+#include <QtCore/QString>
+#include <QtCore/QtEndian>
+
+QT_BEGIN_NAMESPACE
+
+enum ElfProgramHeaderType
+{
+ Elf_PT_NULL = 0,
+ Elf_PT_LOAD = 1,
+ Elf_PT_DYNAMIC = 2,
+ Elf_PT_INTERP = 3,
+ Elf_PT_NOTE = 4,
+ Elf_PT_SHLIB = 5,
+ Elf_PT_PHDR = 6,
+ Elf_PT_TLS = 7,
+ Elf_PT_NUM = 8
+};
+
+enum ElfSectionHeaderType
+{
+ Elf_SHT_NULL = 0,
+ Elf_SHT_PROGBITS = 1,
+ Elf_SHT_SYMTAB = 2,
+ Elf_SHT_STRTAB = 3,
+ Elf_SHT_RELA = 4,
+ Elf_SHT_HASH = 5,
+ Elf_SHT_DYNAMIC = 6,
+ Elf_SHT_NOTE = 7,
+ Elf_SHT_NOBITS = 8,
+ Elf_SHT_REL = 9,
+ Elf_SHT_SHLIB = 10,
+ Elf_SHT_DYNSYM = 11,
+ Elf_SHT_INIT_ARRAY = 14,
+ Elf_SHT_FINI_ARRAY = 15,
+ Elf_SHT_PREINIT_ARRAY = 16,
+ Elf_SHT_GROUP = 17,
+ Elf_SHT_SYMTAB_SHNDX = 18
+};
+
+enum ElfEndian
+{
+ Elf_ELFDATANONE = 0,
+ Elf_ELFDATA2LSB = 1,
+ Elf_ELFDATA2MSB = 2,
+ Elf_ELFDATANUM = 3
+};
+
+enum ElfClass
+{
+ Elf_ELFCLASS32 = 1,
+ Elf_ELFCLASS64 = 2
+};
+
+enum ElfType
+{
+ Elf_ET_NONE = 0,
+ Elf_ET_REL = 1,
+ Elf_ET_EXEC = 2,
+ Elf_ET_DYN = 3,
+ Elf_ET_CORE = 4
+};
+
+enum ElfMachine
+{
+ Elf_EM_386 = 3,
+ Elf_EM_ARM = 40,
+ Elf_EM_X86_64 = 62
+};
+
+enum DebugSymbolsType
+{
+ UnknownSymbols = 0, // Unknown.
+ NoSymbols = 1, // No usable symbols.
+ LinkedSymbols = 2, // Link to symols available.
+ BuildIdSymbols = 4, // BuildId available.
+ PlainSymbols = 8, // Ordinary symbols available.
+ FastSymbols = 16 // Dwarf index available.
+};
+
+class ElfSectionHeader
+{
+public:
+ QByteArray name;
+ quint32 index;
+ quint32 type;
+ quint32 flags;
+ quint64 offset;
+ quint64 size;
+ quint64 addr;
+};
+
+class ElfProgramHeader
+{
+public:
+ quint32 name;
+ quint32 type;
+ quint64 offset;
+ quint64 filesz;
+ quint64 memsz;
+};
+
+class ElfData
+{
+public:
+ ElfData() : symbolsType(UnknownSymbols) {}
+ int indexOf(const QByteArray &name) const;
+
+public:
+ ElfEndian endian;
+ ElfType elftype;
+ ElfMachine elfmachine;
+ ElfClass elfclass;
+ quint64 entryPoint;
+ QByteArray debugLink;
+ QByteArray buildId;
+ DebugSymbolsType symbolsType;
+ QList<ElfSectionHeader> sectionHeaders;
+ QList<ElfProgramHeader> programHeaders;
+};
+
+class ElfReader
+{
+public:
+ explicit ElfReader(const QString &binary);
+ enum Result { Ok, NotElf, Corrupt };
+
+ ElfData readHeaders();
+ QByteArray readSection(const QByteArray &sectionName);
+ QString errorString() const { return m_errorString; }
+ QByteArray readCoreName(bool *isCore);
+ QList<QByteArray> dependencies();
+
+private:
+ friend class ElfMapper;
+ Result readIt();
+
+ QString m_binary;
+ QString m_errorString;
+ ElfData m_elfData;
+};
+
+QT_END_NAMESPACE
+
+#endif // ELFREADER_H
diff --git a/src/tools/windeployqt/main.cpp b/src/tools/windeployqt/main.cpp
new file mode 100644
index 0000000000..27b57ea9ad
--- /dev/null
+++ b/src/tools/windeployqt/main.cpp
@@ -0,0 +1,1723 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "utils.h"
+#include "qmlutils.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 <algorithm>
+#include <iostream>
+#include <iterator>
+#include <cstdio>
+
+QT_BEGIN_NAMESPACE
+
+enum QtModule
+#if defined(Q_COMPILER_CLASS_ENUM) || defined(Q_CC_MSVC)
+ : quint64
+#endif
+{
+ QtBluetoothModule = 0x0000000000000001,
+ QtConcurrentModule = 0x0000000000000002,
+ QtCoreModule = 0x0000000000000004,
+ QtDeclarativeModule = 0x0000000000000008,
+ QtDesignerComponents = 0x0000000000000010,
+ QtDesignerModule = 0x0000000000000020,
+ QtGuiModule = 0x0000000000000040,
+ QtHelpModule = 0x0000000000000080,
+ QtMultimediaModule = 0x0000000000000100,
+ QtMultimediaWidgetsModule = 0x0000000000000200,
+ QtMultimediaQuickModule = 0x0000000000000400,
+ QtNetworkModule = 0x0000000000000800,
+ QtNfcModule = 0x0000000000001000,
+ QtOpenGLModule = 0x0000000000002000,
+ QtOpenGLWidgetsModule = 0x0000000000004000,
+ QtPositioningModule = 0x0000000000008000,
+ QtPrintSupportModule = 0x0000000000010000,
+ QtQmlModule = 0x0000000000020000,
+ QtQuickModule = 0x0000000000040000,
+ QtQuickParticlesModule = 0x0000000000080000,
+ QtScriptModule = 0x0000000000100000,
+ QtScriptToolsModule = 0x0000000000200000,
+ QtSensorsModule = 0x0000000000400000,
+ QtSerialPortModule = 0x0000000000800000,
+ QtSqlModule = 0x0000000001000000,
+ QtSvgModule = 0x0000000002000000,
+ QtSvgWidgetsModule = 0x0000000004000000,
+ QtTestModule = 0x0000000008000000,
+ QtWidgetsModule = 0x0000000010000000,
+ QtWinExtrasModule = 0x0000000020000000,
+ QtXmlModule = 0x0000000040000000,
+ QtQuickWidgetsModule = 0x0000000100000000,
+ QtWebSocketsModule = 0x0000000200000000,
+ QtWebEngineCoreModule = 0x0000000800000000,
+ QtWebEngineModule = 0x0000001000000000,
+ QtWebEngineWidgetsModule = 0x0000002000000000,
+ QtQmlToolingModule = 0x0000004000000000,
+ Qt3DCoreModule = 0x0000008000000000,
+ Qt3DRendererModule = 0x0000010000000000,
+ Qt3DQuickModule = 0x0000020000000000,
+ Qt3DQuickRendererModule = 0x0000040000000000,
+ Qt3DInputModule = 0x0000080000000000,
+ QtLocationModule = 0x0000100000000000,
+ QtWebChannelModule = 0x0000200000000000,
+ QtTextToSpeechModule = 0x0000400000000000,
+ QtSerialBusModule = 0x0000800000000000,
+ QtGamePadModule = 0x0001000000000000,
+ Qt3DAnimationModule = 0x0002000000000000,
+ QtWebViewModule = 0x0004000000000000,
+ Qt3DExtrasModule = 0x0008000000000000,
+ QtShaderToolsModule = 0x0010000000000000
+};
+
+struct QtModuleEntry {
+ quint64 module;
+ const char *option;
+ const char *libraryName;
+ const char *translation;
+};
+
+static QtModuleEntry qtModuleEntries[] = {
+ { QtBluetoothModule, "bluetooth", "Qt6Bluetooth", nullptr },
+ { QtConcurrentModule, "concurrent", "Qt6Concurrent", "qtbase" },
+ { QtCoreModule, "core", "Qt6Core", "qtbase" },
+ { QtDeclarativeModule, "declarative", "Qt6Declarative", "qtquick1" },
+ { QtDesignerModule, "designer", "Qt6Designer", nullptr },
+ { QtDesignerComponents, "designercomponents", "Qt6DesignerComponents", nullptr },
+ { QtGamePadModule, "gamepad", "Qt6Gamepad", nullptr },
+ { QtGuiModule, "gui", "Qt6Gui", "qtbase" },
+ { QtHelpModule, "qthelp", "Qt6Help", "qt_help" },
+ { QtMultimediaModule, "multimedia", "Qt6Multimedia", "qtmultimedia" },
+ { QtMultimediaWidgetsModule, "multimediawidgets", "Qt6MultimediaWidgets", "qtmultimedia" },
+ { QtMultimediaQuickModule, "multimediaquick", "Qt6MultimediaQuick_p", "qtmultimedia" },
+ { QtNetworkModule, "network", "Qt6Network", "qtbase" },
+ { QtNfcModule, "nfc", "Qt6Nfc", nullptr },
+ { QtOpenGLModule, "opengl", "Qt6OpenGL", nullptr },
+ { QtOpenGLWidgetsModule, "openglwidgets", "Qt6OpenGLWidgets", nullptr },
+ { QtPositioningModule, "positioning", "Qt6Positioning", nullptr },
+ { QtPrintSupportModule, "printsupport", "Qt6PrintSupport", nullptr },
+ { QtQmlModule, "qml", "Qt6Qml", "qtdeclarative" },
+ { QtQmlToolingModule, "qmltooling", "qmltooling", nullptr },
+ { QtQuickModule, "quick", "Qt6Quick", "qtdeclarative" },
+ { QtQuickParticlesModule, "quickparticles", "Qt6QuickParticles", nullptr },
+ { QtQuickWidgetsModule, "quickwidgets", "Qt6QuickWidgets", nullptr },
+ { QtScriptModule, "script", "Qt6Script", "qtscript" },
+ { QtScriptToolsModule, "scripttools", "Qt6ScriptTools", "qtscript" },
+ { QtSensorsModule, "sensors", "Qt6Sensors", nullptr },
+ { QtSerialPortModule, "serialport", "Qt6SerialPort", "qtserialport" },
+ { QtSqlModule, "sql", "Qt6Sql", "qtbase" },
+ { QtSvgModule, "svg", "Qt6Svg", nullptr },
+ { QtSvgWidgetsModule, "svgwidgets", "Qt6SvgWidgets", nullptr },
+ { QtTestModule, "test", "Qt6Test", "qtbase" },
+ { QtWebSocketsModule, "websockets", "Qt6WebSockets", nullptr },
+ { QtWidgetsModule, "widgets", "Qt6Widgets", "qtbase" },
+ { QtWinExtrasModule, "winextras", "Qt6WinExtras", nullptr },
+ { QtXmlModule, "xml", "Qt6Xml", "qtbase" },
+ { QtWebEngineCoreModule, "webenginecore", "Qt6WebEngineCore", nullptr },
+ { QtWebEngineModule, "webengine", "Qt6WebEngine", "qtwebengine" },
+ { QtWebEngineWidgetsModule, "webenginewidgets", "Qt6WebEngineWidgets", nullptr },
+ { Qt3DCoreModule, "3dcore", "Qt63DCore", nullptr },
+ { Qt3DRendererModule, "3drenderer", "Qt63DRender", nullptr },
+ { Qt3DQuickModule, "3dquick", "Qt63DQuick", nullptr },
+ { Qt3DQuickRendererModule, "3dquickrenderer", "Qt63DQuickRender", nullptr },
+ { Qt3DInputModule, "3dinput", "Qt63DInput", nullptr },
+ { Qt3DAnimationModule, "3danimation", "Qt63DAnimation", nullptr },
+ { Qt3DExtrasModule, "3dextras", "Qt63DExtras", nullptr },
+ { QtLocationModule, "geoservices", "Qt6Location", nullptr },
+ { QtWebChannelModule, "webchannel", "Qt6WebChannel", nullptr },
+ { QtTextToSpeechModule, "texttospeech", "Qt6TextToSpeech", nullptr },
+ { QtSerialBusModule, "serialbus", "Qt6SerialBus", nullptr },
+ { QtWebViewModule, "webview", "Qt6WebView", nullptr },
+ { QtShaderToolsModule, "shadertools", "Qt6ShaderTools", nullptr }
+};
+
+enum QtPlugin {
+ QtVirtualKeyboardPlugin = 0x1
+};
+
+static const char webEngineProcessC[] = "QtWebEngineProcess";
+
+static inline QString webProcessBinary(const char *binaryName, Platform p)
+{
+ const QString webProcess = QLatin1String(binaryName);
+ return (p & WindowsBased) ? webProcess + QStringLiteral(".exe") : webProcess;
+}
+
+static QByteArray formatQtModules(quint64 mask, bool option = false)
+{
+ QByteArray result;
+ for (const auto &qtModule : qtModuleEntries) {
+ if (mask & qtModule.module) {
+ if (!result.isEmpty())
+ result.append(' ');
+ result.append(option ? qtModule.option : qtModule.libraryName);
+ }
+ }
+ return result;
+}
+
+static Platform platformFromMkSpec(const QString &xSpec)
+{
+ if (xSpec == QLatin1String("linux-g++"))
+ return Unix;
+ if (xSpec.startsWith(QLatin1String("win32-"))) {
+ if (xSpec.contains(QLatin1String("clang-g++")))
+ return WindowsDesktopClangMinGW;
+ if (xSpec.contains(QLatin1String("clang-msvc++")))
+ return WindowsDesktopClangMsvc;
+ return xSpec.contains(QLatin1String("g++")) ? WindowsDesktopMinGW : WindowsDesktopMsvc;
+ }
+ return UnknownPlatform;
+}
+
+// Helpers for exclusive options, "-foo", "--no-foo"
+enum ExlusiveOptionValue {
+ OptionAuto,
+ OptionEnabled,
+ OptionDisabled
+};
+
+static ExlusiveOptionValue parseExclusiveOptions(const QCommandLineParser *parser,
+ const QCommandLineOption &enableOption,
+ const QCommandLineOption &disableOption)
+{
+ const bool enabled = parser->isSet(enableOption);
+ const bool disabled = parser->isSet(disableOption);
+ if (enabled) {
+ if (disabled) {
+ std::wcerr << "Warning: both -" << enableOption.names().first()
+ << " and -" << disableOption.names().first() << " were specified, defaulting to -"
+ << enableOption.names().first() << ".\n";
+ }
+ return OptionEnabled;
+ }
+ return disabled ? OptionDisabled : OptionAuto;
+}
+
+struct Options {
+ enum DebugDetection {
+ DebugDetectionAuto,
+ DebugDetectionForceDebug,
+ DebugDetectionForceRelease
+ };
+
+ bool plugins = true;
+ bool libraries = true;
+ bool quickImports = true;
+ bool translations = true;
+ bool systemD3dCompiler = true;
+ bool compilerRunTime = false;
+ unsigned disabledPlugins = 0;
+ bool softwareRasterizer = true;
+ Platform platform = WindowsDesktopMsvc;
+ quint64 additionalLibraries = 0;
+ quint64 disabledLibraries = 0;
+ 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;
+ QStringList binaries;
+ JsonOutput *json = nullptr;
+ ListOption list = ListNone;
+ DebugDetection debugDetection = DebugDetectionAuto;
+ bool deployPdb = false;
+ bool dryRun = false;
+ bool patchQt = true;
+ bool ignoreLibraryErrors = 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(QLatin1String(webEngineProcessC), Qt::CaseInsensitive)) {
+ return binaryFi.absoluteFilePath();
+ }
+ }
+ return QString();
+}
+
+static QString msgFileDoesNotExist(const QString & file)
+{
+ return QLatin1Char('"') + QDir::toNativeSeparators(file)
+ + QStringLiteral("\" does not exist.");
+}
+
+enum CommandLineParseFlag {
+ CommandLineParseError = 0x1,
+ CommandLineParseHelpRequested = 0x2
+};
+
+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(QStringLiteral("Qt Deploy Tool ") + QLatin1String(QT_VERSION_STR)
+ + QLatin1String("\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>"));
+ const QCommandLineOption helpOption = parser->addHelpOption();
+ parser->addVersionOption();
+
+ QCommandLineOption dirOption(QStringLiteral("dir"),
+ QStringLiteral("Use directory instead of binary directory."),
+ QStringLiteral("directory"));
+ parser->addOption(dirOption);
+
+ QCommandLineOption qmakeOption(QStringLiteral("qmake"),
+ QStringLiteral("Use specified qmake instead of qmake from PATH. "
+ "Deprecated, use qtpaths instead."),
+ QStringLiteral("path"));
+ parser->addOption(qmakeOption);
+
+ QCommandLineOption qtpathsOption(
+ QStringLiteral("qtpaths"),
+ QStringLiteral("Use specified qtpaths.exe instead of qtpaths.exe from PATH."),
+ QStringLiteral("path"));
+ parser->addOption(qtpathsOption);
+
+ 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);
+
+ 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 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 compilerRunTimeOption(QStringLiteral("compiler-runtime"),
+ QStringLiteral("Deploy compiler runtime (Desktop only)."));
+ parser->addOption(compilerRunTimeOption);
+
+ QCommandLineOption noVirtualKeyboardOption(QStringLiteral("no-virtualkeyboard"),
+ QStringLiteral("Disable deployment of the Virtual Keyboard."));
+ parser->addOption(noVirtualKeyboardOption);
+
+ 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 listOption(QStringLiteral("list"),
+ QLatin1String("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"),
+ QStringLiteral("option"));
+ parser->addOption(listOption);
+
+ QCommandLineOption verboseOption(QStringLiteral("verbose"),
+ QStringLiteral("Verbose level (0-2)."),
+ QStringLiteral("level"));
+ parser->addOption(verboseOption);
+
+ parser->addPositionalArgument(QStringLiteral("[files]"),
+ QStringLiteral("Binaries or directory containing the binary."));
+
+ OptionPtrVector enabledModuleOptions;
+ OptionPtrVector disabledModuleOptions;
+ const int qtModulesCount = int(sizeof(qtModuleEntries) / sizeof(QtModuleEntry));
+ enabledModuleOptions.reserve(qtModulesCount);
+ disabledModuleOptions.reserve(qtModulesCount);
+ for (int i = 0; i < qtModulesCount; ++i) {
+ const QString option = QLatin1String(qtModuleEntries[i].option);
+ const QString name = QLatin1String(qtModuleEntries[i].libraryName);
+ 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 (!success) {
+ *errorMessage = parser->errorText();
+ return CommandLineParseError;
+ }
+
+ options->libraryDirectory = parser->value(libDirOption);
+ options->pluginDirectory = parser->value(pluginDirOption);
+ 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(QLatin1Char(','));
+ options->systemD3dCompiler = !parser->isSet(noSystemD3DCompilerOption);
+ options->quickImports = !parser->isSet(noQuickImportOption);
+
+ // default to deployment of compiler runtime for windows desktop configurations
+ if (options->platform == WindowsDesktopMinGW || options->platform == WindowsDesktopMsvc
+ || parser->isSet(compilerRunTimeOption))
+ options->compilerRunTime = true;
+ if (parser->isSet(noCompilerRunTimeOption))
+ options->compilerRunTime = false;
+
+ if (options->compilerRunTime && options->platform != WindowsDesktopMinGW && options->platform != WindowsDesktopMsvc) {
+ *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for Desktop MSVC/g++ only.");
+ return CommandLineParseError;
+ }
+
+ if (parser->isSet(noVirtualKeyboardOption))
+ options->disabledPlugins |= QtVirtualKeyboardPlugin;
+
+ 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(forceOption))
+ options->updateFileFlags |= ForceUpdateFile;
+ if (parser->isSet(dryRunOption)) {
+ options->dryRun = true;
+ options->updateFileFlags |= SkipUpdateFile;
+ }
+
+ options->patchQt = !parser->isSet(noPatchQtOption);
+ options->ignoreLibraryErrors = parser->isSet(ignoreErrorOption);
+
+ for (int i = 0; i < qtModulesCount; ++i) {
+ if (parser->isSet(*enabledModuleOptions.at(i)))
+ options->additionalLibraries |= qtModuleEntries[i].module;
+ if (parser->isSet(*disabledModuleOptions.at(i)))
+ options->disabledLibraries |= qtModuleEntries[i].module;
+ }
+
+ // Add some dependencies
+ if (options->additionalLibraries & QtQuickModule)
+ options->additionalLibraries |= QtQmlModule;
+ if (options->additionalLibraries & QtDesignerComponents)
+ options->additionalLibraries |= QtDesignerModule;
+
+ 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;
+ } else {
+ 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;
+ }
+ }
+ }
+
+ 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(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 -qpaths 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 = QLatin1Char('"') + QDir::toNativeSeparators(qtpathsBinary)
+ + QStringLiteral("\" is not an executable.");
+ return CommandLineParseError;
+ }
+ options->qtpathsBinary = qtpathsBinary;
+ }
+
+ 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 = QLatin1Char('"') + 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 + QLatin1Char('"');
+ return CommandLineParseError;
+ }
+ options->directory = fi.absoluteFilePath();
+ options->binaries.append(binary);
+ } // directory.
+
+ // Remaining files or plugin directories
+ 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 + QLatin1Char('/') + library);
+ } else {
+ options->binaries.append(path);
+ }
+ }
+ options->translationsDirectory = options->directory + QLatin1String("/translations");
+ return 0;
+}
+
+// Simple line wrapping at 80 character boundaries.
+static inline QString lineBreak(QString s)
+{
+ for (int i = 80; i < s.size(); i += 80) {
+ const int lastBlank = s.lastIndexOf(QLatin1Char(' '), i);
+ if (lastBlank >= 0) {
+ s[lastBlank] = QLatin1Char('\n');
+ i = lastBlank + 1;
+ }
+ }
+ return s;
+}
+
+static inline QString helpText(const QCommandLineParser &p)
+{
+ QString result = p.helpText();
+ // Replace the default-generated text which is too long by a short summary
+ // explaining how to enable single libraries.
+ const int moduleStart = result.indexOf(QLatin1String("\n --bluetooth"));
+ const int argumentsStart = result.lastIndexOf(QLatin1String("\nArguments:"));
+ if (moduleStart >= argumentsStart)
+ return result;
+ QString moduleHelp = QLatin1String(
+ "\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");
+ moduleHelp += lineBreak(QString::fromLatin1(formatQtModules(0xFFFFFFFFFFFFFFFFull, true)));
+ moduleHelp += QLatin1Char('\n');
+ 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(QLatin1String("Qt"), 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 (!readExecutable(binary, platform, errorMessage, &dependentLibs, wordSize, isDebug, machineArch)) {
+ errorMessage->prepend(QLatin1String("Unable to find dependent libraries of ") +
+ QDir::toNativeSeparators(binary) + QLatin1String(" :"));
+ 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 : qAsConst(dependentLibs)) {
+ if (isQtModule(lib)) {
+ const QString path = normalizeFileName(qtBinDir + QLatin1Char('/') + 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 int lastDot = libraryFileName.lastIndexOf(QLatin1Char('.')) + 1;
+ if (lastDot <= 0)
+ return QString();
+ libraryFileName.replace(lastDot, libraryFileName.size() - lastDot, QLatin1String("pdb"));
+ 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 debgug) and .qml/,js, etc.
+class QmlDirectoryFileEntryFunction {
+public:
+ enum Flags {
+ DeployPdb = 0x1,
+ SkipSources = 0x2
+ };
+
+ explicit QmlDirectoryFileEntryFunction(Platform platform, DebugMatchMode debugMatchMode, unsigned flags)
+ : m_flags(flags), m_qmlNameFilter(QmlDirectoryFileEntryFunction::qmlNameFilters(flags))
+ , m_dllFilter(platform, debugMatchMode)
+ {}
+
+ QStringList operator()(const QDir &dir) const
+ {
+ 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 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;
+};
+
+struct PluginModuleMapping
+{
+ const char *directoryName;
+ quint64 module;
+};
+
+static const PluginModuleMapping pluginModuleMappings[] =
+{
+ {"qml1tooling", QtDeclarativeModule},
+#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
+ {"gamepads", QtGamePadModule},
+#endif
+ {"accessible", QtGuiModule},
+ {"iconengines", QtGuiModule},
+ {"imageformats", QtGuiModule},
+ {"platforms", QtGuiModule},
+ {"platforminputcontexts", QtGuiModule},
+ {"virtualkeyboard", QtGuiModule},
+ {"geoservices", QtLocationModule},
+ {"audio", QtMultimediaModule},
+ {"mediaservice", QtMultimediaModule},
+ {"playlistformats", QtMultimediaModule},
+ {"networkaccess", QtNetworkModule},
+ {"networkinformation", QtNetworkModule},
+ {"tls", QtNetworkModule},
+ {"position", QtPositioningModule},
+ {"printsupport", QtPrintSupportModule},
+ {"scenegraph", QtQuickModule},
+ {"qmltooling", QtQuickModule | QtQmlToolingModule},
+ {"sensors", QtSensorsModule},
+ {"sensorgestures", QtSensorsModule},
+ {"canbus", QtSerialBusModule},
+ {"sqldrivers", QtSqlModule},
+#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
+ {"texttospeech", QtTextToSpeechModule},
+#endif
+ {"qtwebengine", QtWebEngineModule | QtWebEngineCoreModule | QtWebEngineWidgetsModule},
+ {"styles", QtWidgetsModule},
+ {"sceneparsers", Qt3DRendererModule},
+ {"renderers", Qt3DRendererModule | QtShaderToolsModule},
+ {"renderplugins", Qt3DRendererModule},
+ {"geometryloaders", Qt3DRendererModule},
+ {"webview", QtWebViewModule}
+};
+
+static inline quint64 qtModuleForPlugin(const QString &subDirName)
+{
+ const auto end = std::end(pluginModuleMappings);
+ const auto result =
+ std::find_if(std::begin(pluginModuleMappings), end,
+ [&subDirName] (const PluginModuleMapping &m) { return subDirName == QLatin1String(m.directoryName); });
+ return result != end ? result->module : 0; // "designer"
+}
+
+static quint64 qtModule(QString module, const QString &infix)
+{
+ // Match needle 'path/Qt6Core<infix><d>.dll' or 'path/libQt6Core<infix>.so.5.0'
+ const int lastSlashPos = module.lastIndexOf(QLatin1Char('/'));
+ if (lastSlashPos > 0)
+ module.remove(0, lastSlashPos + 1);
+ if (module.startsWith(QLatin1String("lib")))
+ module.remove(0, 3);
+ int endPos = infix.isEmpty() ? -1 : module.lastIndexOf(infix);
+ if (endPos == -1)
+ endPos = module.indexOf(QLatin1Char('.')); // strip suffixes '.so.5.0'.
+ if (endPos > 0)
+ module.truncate(endPos);
+ // That should leave us with 'Qt6Core<d>'.
+ for (const auto &qtModule : qtModuleEntries) {
+ const QLatin1String libraryName(qtModule.libraryName);
+ if (module == libraryName
+ || (module.size() == libraryName.size() + 1 && module.startsWith(libraryName))) {
+ return qtModule.module;
+ }
+ }
+ return 0;
+}
+
+QStringList findQtPlugins(quint64 *usedQtModules, quint64 disabledQtModules,
+ unsigned disabledPlugins,
+ const QString &qtPluginsDirName, const QString &libraryLocation,
+ const QString &infix,
+ DebugMatchMode debugMatchModeIn, Platform platform, QString *platformPlugin)
+{
+ QString errorMessage;
+ if (qtPluginsDirName.isEmpty())
+ return QStringList();
+ QDir pluginsDir(qtPluginsDirName);
+ QStringList result;
+ const QFileInfoList &pluginDirs = pluginsDir.entryInfoList(QStringList(QLatin1String("*")), QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QFileInfo &subDirFi : pluginDirs) {
+ const QString subDirName = subDirFi.fileName();
+ const quint64 module = qtModuleForPlugin(subDirName);
+ if (module & *usedQtModules) {
+ const DebugMatchMode debugMatchMode = (module & QtWebEngineCoreModule)
+ ? MatchDebugOrRelease // QTBUG-44331: Debug detection does not work for webengine, deploy all.
+ : debugMatchModeIn;
+ QDir subDir(subDirFi.absoluteFilePath());
+ // Filter out disabled plugins
+ if ((disabledPlugins & QtVirtualKeyboardPlugin) && subDirName == QLatin1String("virtualkeyboard"))
+ continue;
+ if (disabledQtModules & QtQmlToolingModule && subDirName == QLatin1String("qmltooling"))
+ continue;
+ // Filter for platform or any.
+ QString filter;
+ const bool isPlatformPlugin = subDirName == QLatin1String("platforms");
+ if (isPlatformPlugin) {
+ switch (platform) {
+ case WindowsDesktopMsvc:
+ case WindowsDesktopMinGW:
+ filter = QStringLiteral("qwindows");
+ break;
+ case Unix:
+ filter = QStringLiteral("libqxcb");
+ break;
+ case UnknownPlatform:
+ break;
+ }
+ } else {
+ filter = QLatin1String("*");
+ }
+ const QStringList plugins = findSharedLibraries(subDir, platform, debugMatchMode, filter);
+ for (const QString &plugin : plugins) {
+ // Filter out disabled plugins
+ if ((disabledPlugins & QtVirtualKeyboardPlugin)
+ && plugin.startsWith(QLatin1String("qtvirtualkeyboardplugin"))) {
+ continue;
+ }
+ const QString pluginPath = subDir.absoluteFilePath(plugin);
+ if (isPlatformPlugin)
+ *platformPlugin = pluginPath;
+ QStringList dependentQtLibs;
+ quint64 neededModules = 0;
+ if (findDependentQtLibraries(libraryLocation, pluginPath, platform, &errorMessage, &dependentQtLibs)) {
+ for (int d = 0; d < dependentQtLibs.size(); ++ d)
+ neededModules |= qtModule(dependentQtLibs.at(d), infix);
+ } else {
+ std::wcerr << "Warning: Cannot determine dependencies of "
+ << QDir::toNativeSeparators(pluginPath) << ": " << errorMessage << '\n';
+ }
+ if (const quint64 missingModules = neededModules & disabledQtModules) {
+ if (optVerboseLevel) {
+ std::wcout << "Skipping plugin " << plugin
+ << " due to disabled dependencies ("
+ << formatQtModules(missingModules).constData() << ").\n";
+ }
+ } else {
+ if (const quint64 missingModules = (neededModules & ~*usedQtModules)) {
+ *usedQtModules |= missingModules;
+ if (optVerboseLevel)
+ std::wcout << "Adding " << formatQtModules(missingModules).constData() << " for " << plugin << '\n';
+ }
+ result.append(pluginPath);
+ }
+ } // for filter
+ } // type matches
+ } // for plugin folder
+ return result;
+}
+
+static QStringList translationNameFilters(quint64 modules, const QString &prefix)
+{
+ QStringList result;
+ for (const auto &qtModule : qtModuleEntries) {
+ if ((qtModule.module & modules) && qtModule.translation) {
+ const QString name = QLatin1String(qtModule.translation) +
+ QLatin1Char('_') + prefix + QStringLiteral(".qm");
+ if (!result.contains(name))
+ result.push_back(name);
+ }
+ }
+ return result;
+}
+
+static bool deployTranslations(const QString &sourcePath, quint64 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 : qAsConst(prefixes)) {
+ arguments.clear();
+ const QString targetFile = QStringLiteral("qt_") + prefix + QStringLiteral(".qm");
+ arguments.append(QStringLiteral("-o"));
+ const QString targetFilePath = absoluteTarget + QLatin1Char('/') + targetFile;
+ if (options.json)
+ options.json->addFile(sourcePath + QLatin1Char('/') + targetFile, absoluteTarget);
+ arguments.append(QDir::toNativeSeparators(targetFilePath));
+ const QFileInfoList &langQmFiles = sourceDir.entryInfoList(translationNameFilters(usedQtModules, prefix));
+ 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;
+}
+
+struct DeployResult
+{
+ operator bool() const { return success; }
+
+ bool success = false;
+ bool isDebug = false;
+ quint64 directlyUsedQtLibraries = 0;
+ quint64 usedQtLibraries = 0;
+ quint64 deployedQtLibraries = 0;
+};
+
+static QString libraryPath(const QString &libraryLocation, const char *name,
+ const QString &qtLibInfix, Platform platform, bool debug)
+{
+ QString result = libraryLocation + QLatin1Char('/');
+ if (platform & WindowsBased) {
+ result += QLatin1String(name);
+ result += qtLibInfix;
+ if (debug && platformHasDebugSuffix(platform))
+ result += QLatin1Char('d');
+ } else if (platform.testFlag(UnixBased)) {
+ result += QStringLiteral("lib");
+ result += QLatin1String(name);
+ result += qtLibInfix;
+ }
+ result += sharedLibrarySuffix(platform);
+ return result;
+}
+
+static QString vcDebugRedistDir() { return QStringLiteral("Debug_NonRedist"); }
+
+static QString vcRedistDir()
+{
+ const char vcDirVar[] = "VCINSTALLDIR";
+ const QChar slash(QLatin1Char('/'));
+ 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 compilerRunTimeLibs(Platform platform, bool isDebug, unsigned short machineArch)
+{
+ QStringList result;
+ switch (platform) {
+ case WindowsDesktopMinGW: { // MinGW: Add runtime libraries
+ static const char *minGwRuntimes[] = {"*gcc_", "*stdc++", "*winpthread"};
+ const QString gcc = findInPath(QStringLiteral("g++.exe"));
+ if (gcc.isEmpty()) {
+ std::wcerr << "Warning: Cannot find GCC installation directory. g++.exe must be in the path.\n";
+ break;
+ }
+ const QString binPath = QFileInfo(gcc).absolutePath();
+ QStringList filters;
+ const QString suffix = QLatin1Char('*') + sharedLibrarySuffix(platform);
+ for (auto minGwRuntime : minGwRuntimes)
+ filters.append(QLatin1String(minGwRuntime) + suffix);
+ const QFileInfoList &dlls = QDir(binPath).entryInfoList(filters, QDir::Files);
+ for (const QFileInfo &dllFi : dlls)
+ result.append(dllFi.absoluteFilePath());
+ }
+ break;
+#ifdef Q_OS_WIN
+ case WindowsDesktopMsvc: { // MSVC/Desktop: Add redistributable packages.
+ QString vcRedistDirName = vcRedistDir();
+ if (vcRedistDirName.isEmpty())
+ break;
+ QStringList redistFiles;
+ QDir vcRedistDir(vcRedistDirName);
+ const QString machineArchString = getArchString(machineArch);
+ if (isDebug) {
+ // Append DLLs from Debug_NonRedist\x??\Microsoft.VC<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(QLatin1String("*.dll")));
+ 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 += QLatin1Char('/') + countryCodes.constFirst();
+ QFileInfo fi(releaseRedistDir + QLatin1Char('/') + QStringLiteral("vc_redist.")
+ + machineArchString + QStringLiteral(".exe"));
+ if (!fi.isFile()) { // Pre MSVC2017/15.5
+ fi.setFile(releaseRedistDir + QLatin1Char('/') + QStringLiteral("vcredist_")
+ + machineArchString + QStringLiteral(".exe"));
+ }
+ 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 = QLatin1Char('.');
+ 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;
+}
+
+// Determine the Qt lib infix from the library path of "Qt6Core<qtblibinfix>[d].dll".
+static inline QString qtlibInfixFromCoreLibName(const QString &path, bool isDebug, Platform platform)
+{
+ const int startPos = path.lastIndexOf(QLatin1Char('/')) + 8;
+ int endPos = path.lastIndexOf(QLatin1Char('.'));
+ if (isDebug && (platform & WindowsBased))
+ endPos--;
+ return endPos > startPos ? path.mid(startPos, endPos - startPos) : QString();
+}
+
+// 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,
+ QString *errorMessage)
+{
+ DeployResult result;
+
+ const QChar slash = QLatin1Char('/');
+
+ const QString qtBinDir = qtpathsVariables.value(QStringLiteral("QT_INSTALL_BINS"));
+ const QString libraryLocation = options.platform == Unix
+ ? qtpathsVariables.value(QStringLiteral("QT_INSTALL_LIBS"))
+ : qtBinDir;
+ const QString infix = qtpathsVariables.value(QLatin1String(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).
+ QString qtLibInfix;
+ for (int m = 0; m < directDependencyCount; ++m) {
+ const quint64 module = qtModule(dependentQtLibs.at(m), infix);
+ result.directlyUsedQtLibraries |= module;
+ if (module == QtCoreModule)
+ qtLibInfix = qtlibInfixFromCoreLibName(dependentQtLibs.at(m), detectedDebug, options.platform);
+ }
+
+ const bool usesQml2 = !(options.disabledLibraries & QtQmlModule)
+ && ((result.directlyUsedQtLibraries & (QtQmlModule | QtQuickModule | Qt3DQuickModule))
+ || (options.additionalLibraries & QtQmlModule));
+
+ 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, options.platform, 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
+ + QLatin1String(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
+ + QLatin1String(windowsSharedLibrarySuffix);
+ if (!findInPath(icuLibCandidate).isEmpty()) {
+ icuLib = icuLibCandidate;
+ }
+ }
+ icuLibs.push_back(icuLib);
+ }
+ for (const QString &icuLib : qAsConst(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 : qAsConst(qmlDirectories)) {
+ if (optVerboseLevel >= 1)
+ std::wcout << "Scanning " << QDir::toNativeSeparators(qmlDirectory) << ":\n";
+ const QmlImportScanResult scanResult =
+ runQmlImportScanner(qmlDirectory, qmlImportPaths,
+ result.directlyUsedQtLibraries & QtWidgetsModule,
+ options.platform, debugMatchMode, errorMessage);
+ if (!scanResult.ok)
+ return result;
+ qmlScanResult.append(scanResult);
+ // Additional dependencies of QML plugins.
+ for (const QString &plugin : qAsConst(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 : qAsConst(qmlScanResult.modules)) {
+ std::wcout << " '" << mod.name << "' "
+ << QDir::toNativeSeparators(mod.sourcePath) << '\n';
+ }
+ if (optVerboseLevel >= 2) {
+ std::wcout << "QML plugins:\n";
+ for (const QString &p : qAsConst(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) {
+ if (const quint64 qtm = qtModule(dependentQtLibs.at(i), infix))
+ result.usedQtLibraries |= qtm;
+ else
+ deployedQtLibraries.push_back(dependentQtLibs.at(i)); // Not represented by flag.
+ }
+ result.deployedQtLibraries = (result.usedQtLibraries | options.additionalLibraries) & ~options.disabledLibraries;
+
+ const QStringList plugins = findQtPlugins(
+ &result.deployedQtLibraries,
+ // For non-QML applications, disable QML to prevent it from being pulled in by the
+ // qtaccessiblequick plugin.
+ options.disabledLibraries | (usesQml2 ? 0 : (QtQmlModule | QtQuickModule)),
+ options.disabledPlugins, qtpathsVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")),
+ libraryLocation, infix, debugMatchMode, options.platform, &platformPlugin);
+
+ // Apply options flags and re-add library names.
+ QString qtGuiLibrary;
+ for (const auto &qtModule : qtModuleEntries) {
+ if (result.deployedQtLibraries & qtModule.module) {
+ const QString library = libraryPath(libraryLocation, qtModule.libraryName, qtLibInfix, options.platform, result.isDebug);
+ deployedQtLibraries.append(library);
+ if (qtModule.module == QtGuiModule)
+ 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(QLatin1Char(',')) << '\n';
+
+ if ((result.deployedQtLibraries & QtGuiModule) && platformPlugin.isEmpty()) {
+ *errorMessage =QStringLiteral("Unable to find the platform plugin.");
+ return result;
+ }
+
+ if (options.platform.testFlag(WindowsBased) && !qtGuiLibrary.isEmpty()) {
+ const QStringList guiLibraries = findDependentLibraries(qtGuiLibrary, options.platform, errorMessage);
+ const bool dependsOnOpenGl = !guiLibraries.filter(QStringLiteral("opengl32"), Qt::CaseInsensitive).isEmpty();
+ if (options.softwareRasterizer && !dependsOnOpenGl) {
+ const QFileInfo softwareRasterizer(qtBinDir + slash + QStringLiteral("opengl32sw") + QLatin1String(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);
+ }
+ }
+ } // Windows
+
+ // Update libraries
+ if (options.libraries) {
+ const QString targetPath = options.libraryDirectory.isEmpty() ?
+ options.directory : options.libraryDirectory;
+ QStringList libraries = deployedQtLibraries;
+ if (options.compilerRunTime)
+ libraries.append(compilerRunTimeLibs(options.platform, result.isDebug, machineArch));
+ for (const QString &qtLib : qAsConst(libraries)) {
+ if (!updateLibrary(qtLib, targetPath, options, errorMessage))
+ return result;
+ }
+
+ if (options.patchQt && !options.dryRun) {
+ const QString qt6CoreName = QFileInfo(libraryPath(libraryLocation, "Qt6Core", qtLibInfix,
+ options.platform, result.isDebug)).fileName();
+#ifndef QT_RELOCATABLE
+ if (!patchQtCore(targetPath + QLatin1Char('/') + qt6CoreName, errorMessage)) {
+ std::wcerr << "Warning: " << *errorMessage << '\n';
+ errorMessage->clear();
+ }
+#endif
+ }
+ } // 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 = QLatin1String("Cannot create ") +
+ QDir::toNativeSeparators(dir.absolutePath()) + QLatin1Char('.');
+ 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 + QLatin1Char('.');
+ return result;
+ }
+ }
+ if (!updateLibrary(plugin, targetPath, options, errorMessage))
+ return result;
+ }
+ } // optPlugins
+
+ // Update Quick imports
+ const bool usesQuick1 = result.deployedQtLibraries & QtDeclarativeModule;
+ // 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 && (usesQuick1 || usesQml2)) {
+ if (usesQml2) {
+ for (const QmlImportScanResult::Module &module : qAsConst(qmlScanResult.modules)) {
+ const QString installPath = module.installPath(options.directory);
+ if (optVerboseLevel > 1)
+ std::wcout << "Installing: '" << module.name
+ << "' from " << module.sourcePath << " to "
+ << QDir::toNativeSeparators(installPath) << '\n';
+ if (installPath != options.directory && !createDirectory(installPath, errorMessage))
+ return result;
+ unsigned updateFileFlags = options.updateFileFlags | SkipQmlDesignerSpecificsDirectories;
+ unsigned qmlDirectoryFileFlags = 0;
+ if (options.deployPdb)
+ qmlDirectoryFileFlags |= QmlDirectoryFileEntryFunction::DeployPdb;
+ if (!updateFile(module.sourcePath, QmlDirectoryFileEntryFunction(options.platform, debugMatchMode, qmlDirectoryFileFlags),
+ installPath, updateFileFlags, options.json, errorMessage)) {
+ return result;
+ }
+ }
+ } // Quick 2
+ if (usesQuick1) {
+ const QString quick1ImportPath =
+ qtpathsVariables.value(QStringLiteral("QT_INSTALL_IMPORTS"));
+ const QmlDirectoryFileEntryFunction qmlFileEntryFunction(options.platform, debugMatchMode, options.deployPdb ? QmlDirectoryFileEntryFunction::DeployPdb : 0);
+ QStringList quick1Imports(QStringLiteral("Qt"));
+ for (const QString &quick1Import : qAsConst(quick1Imports)) {
+ const QString sourceFile = quick1ImportPath + slash + quick1Import;
+ if (!updateFile(sourceFile, qmlFileEntryFunction, options.directory, options.updateFileFlags, options.json, errorMessage))
+ return result;
+ }
+ } // Quick 1
+ } // optQuickImports
+
+ if (options.translations) {
+ if (!options.dryRun && !createDirectory(options.translationsDirectory, errorMessage))
+ 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 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"))
+ + QLatin1Char('/') + webProcess;
+ if (!updateFile(webProcessSource, sourceOptions.directory, sourceOptions.updateFileFlags, sourceOptions.json, errorMessage))
+ return false;
+ Options options(sourceOptions);
+ options.binaries.append(options.directory + QLatin1Char('/') + webProcess);
+ options.quickImports = false;
+ options.translations = false;
+ return deploy(options, qtpathsVariables, errorMessage);
+}
+
+static bool deployWebEngineCore(const QMap<QString, QString> &qtpathsVariables,
+ 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"};
+ QByteArray webEngineProcessName(webEngineProcessC);
+ if (isDebug && platformHasDebugSuffix(options.platform))
+ webEngineProcessName.append('d');
+ if (optVerboseLevel)
+ std::wcout << "Deploying: " << webEngineProcessName.constData() << "...\n";
+ if (!deployWebProcess(qtpathsVariables, webEngineProcessName, options, errorMessage))
+ return false;
+ const QString resourcesSubDir = QStringLiteral("/resources");
+ const QString resourcesSourceDir = qtpathsVariables.value(QStringLiteral("QT_INSTALL_DATA"))
+ + resourcesSubDir + QLatin1Char('/');
+ const QString resourcesTargetDir(options.directory + resourcesSubDir);
+ if (!createDirectory(resourcesTargetDir, errorMessage))
+ return false;
+ for (auto installDataFile : installDataFiles) {
+ if (!updateFile(resourcesSourceDir + QLatin1String(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)
+ && 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 + QLatin1Char('/')
+ + translations.fileName();
+ if (!createDirectory(webEngineTranslationsDir, errorMessage))
+ 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(QLatin1String(QT_VERSION_STR));
+
+ 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 += ';';
+ path += qtBinPath;
+ qputenv("PATH", path);
+ }
+
+ Options options;
+ QString errorMessage;
+
+ { // 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 & CommandLineParseHelpRequested)
+ std::fputs(qPrintable(helpText(parser)), stdout);
+ if (result & CommandLineParseError)
+ return 1;
+ if (result & CommandLineParseHelpRequested)
+ return 0;
+ }
+
+ const QMap<QString, QString> qtpathsVariables =
+ queryQtPaths(options.qtpathsBinary, &errorMessage);
+ const QString xSpec = qtpathsVariables.value(QStringLiteral("QMAKE_XSPEC"));
+ options.platform = platformFromMkSpec(xSpec);
+
+ if (qtpathsVariables.isEmpty() || xSpec.isEmpty()
+ || !qtpathsVariables.contains(QStringLiteral("QT_INSTALL_BINS"))) {
+ std::wcerr << "Unable to query qtpaths: " << errorMessage << '\n';
+ return 1;
+ }
+
+ if (options.platform == UnknownPlatform) {
+ std::wcerr << "Unsupported platform " << xSpec << '\n';
+ return 1;
+ }
+
+ // Create directories
+ if (!createDirectory(options.directory, &errorMessage)) {
+ std::wcerr << errorMessage << '\n';
+ return 1;
+ }
+ if (!options.libraryDirectory.isEmpty() && options.libraryDirectory != options.directory
+ && !createDirectory(options.libraryDirectory, &errorMessage)) {
+ std::wcerr << errorMessage << '\n';
+ return 1;
+ }
+
+ const DeployResult result = deploy(options, qtpathsVariables, &errorMessage);
+ if (!result) {
+ std::wcerr << errorMessage << '\n';
+ return 1;
+ }
+
+ if (result.deployedQtLibraries & QtWebEngineCoreModule) {
+ if (!deployWebEngineCore(qtpathsVariables, options, result.isDebug, &errorMessage)) {
+ std::wcerr << errorMessage << '\n';
+ return 1;
+ }
+ }
+
+ if (options.json) {
+ if (options.list)
+ std::fputs(options.json->toList(options.list, options.directory).constData(), stdout);
+ else
+ std::fputs(options.json->toJson().constData(), stdout);
+ delete options.json;
+ options.json = nullptr;
+ }
+
+ return 0;
+}
diff --git a/src/tools/windeployqt/qmlutils.cpp b/src/tools/windeployqt/qmlutils.cpp
new file mode 100644
index 0000000000..6eebf6d86e
--- /dev/null
+++ b/src/tools/windeployqt/qmlutils.cpp
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmlutils.h"
+#include "utils.h"
+
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+#include <QtCore/QJsonParseError>
+
+QT_BEGIN_NAMESPACE
+
+bool operator==(const QmlImportScanResult::Module &m1, const QmlImportScanResult::Module &m2)
+{
+ return m1.className.isEmpty() ? m1.name == m2.name : m1.className == m2.className;
+}
+
+// Return install path (cp -r semantics)
+QString QmlImportScanResult::Module::installPath(const QString &root) const
+{
+ QString result = root;
+ const int lastSlashPos = relativePath.lastIndexOf(QLatin1Char('/'));
+ if (lastSlashPos != -1) {
+ result += QLatin1Char('/');
+ result += QStringView{relativePath}.left(lastSlashPos);
+ }
+ return result;
+}
+
+static QString qmlDirectoryRecursion(Platform platform, const QString &path)
+{
+ QDir dir(path);
+ if (!dir.entryList(QStringList(QStringLiteral("*.qml")), QDir::Files, QDir::NoSort).isEmpty())
+ return dir.path();
+ const QFileInfoList &subDirs = dir.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort);
+ for (const QFileInfo &subDirFi : subDirs) {
+ if (!isBuildDirectory(platform, subDirFi.fileName())) {
+ const QString subPath = qmlDirectoryRecursion(platform, subDirFi.absoluteFilePath());
+ if (!subPath.isEmpty())
+ return subPath;
+ }
+ }
+ return QString();
+}
+
+// Find a directory containing QML files in the project
+QString findQmlDirectory(Platform platform, const QString &startDirectoryName)
+{
+ QDir startDirectory(startDirectoryName);
+ if (isBuildDirectory(platform, startDirectory.dirName()))
+ startDirectory.cdUp();
+ return qmlDirectoryRecursion(platform, startDirectory.path());
+}
+
+static void findFileRecursion(const QDir &directory, Platform platform,
+ DebugMatchMode debugMatchMode, QStringList *matches)
+{
+ const QStringList &dlls = findSharedLibraries(directory, platform, debugMatchMode);
+ for (const QString &dll : dlls)
+ matches->append(directory.filePath(dll));
+ const QFileInfoList &subDirs = directory.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
+ for (const QFileInfo &subDirFi : subDirs) {
+ QDir subDirectory(subDirFi.absoluteFilePath());
+ if (subDirectory.isReadable())
+ findFileRecursion(subDirectory, platform, debugMatchMode, matches);
+ }
+}
+
+QmlImportScanResult runQmlImportScanner(const QString &directory, const QStringList &qmlImportPaths,
+ bool usesWidgets, int platform, DebugMatchMode debugMatchMode,
+ QString *errorMessage)
+{
+ Q_UNUSED(usesWidgets);
+ QmlImportScanResult result;
+ QStringList arguments;
+ for (const QString &importPath : qmlImportPaths)
+ arguments << QStringLiteral("-importPath") << importPath;
+ arguments << QStringLiteral("-rootPath") << directory;
+ unsigned long exitCode;
+ QByteArray stdOut;
+ QByteArray stdErr;
+ const QString binary = QStringLiteral("qmlimportscanner");
+ if (!runProcess(binary, arguments, QDir::currentPath(), &exitCode, &stdOut, &stdErr, errorMessage))
+ return result;
+ if (exitCode) {
+ *errorMessage = binary + QStringLiteral(" returned ") + QString::number(exitCode)
+ + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr);
+ return result;
+ }
+ QJsonParseError jsonParseError{};
+ const QJsonDocument data = QJsonDocument::fromJson(stdOut, &jsonParseError);
+ if (data.isNull() ) {
+ *errorMessage = binary + QStringLiteral(" returned invalid JSON output: ")
+ + jsonParseError.errorString() + QStringLiteral(" :\"")
+ + QString::fromLocal8Bit(stdOut) + QLatin1Char('"');
+ return result;
+ }
+ const QJsonArray array = data.array();
+ const int childCount = array.count();
+ for (int c = 0; c < childCount; ++c) {
+ const QJsonObject object = array.at(c).toObject();
+ if (object.value(QStringLiteral("type")).toString() == QLatin1String("module")) {
+ const QString path = object.value(QStringLiteral("path")).toString();
+ if (!path.isEmpty()) {
+ QmlImportScanResult::Module module;
+ module.name = object.value(QStringLiteral("name")).toString();
+ module.className = object.value(QStringLiteral("classname")).toString();
+ module.sourcePath = path;
+ module.relativePath = object.value(QStringLiteral("relativePath")).toString();
+ result.modules.append(module);
+ findFileRecursion(QDir(path), Platform(platform), debugMatchMode, &result.plugins);
+ }
+ }
+ }
+ result.ok = true;
+ return result;
+}
+
+void QmlImportScanResult::append(const QmlImportScanResult &other)
+{
+ for (const QmlImportScanResult::Module &module : other.modules) {
+ if (std::find(modules.cbegin(), modules.cend(), module) == modules.cend())
+ modules.append(module);
+ }
+ for (const QString &plugin : other.plugins) {
+ if (!plugins.contains(plugin))
+ plugins.append(plugin);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/tools/windeployqt/qmlutils.h b/src/tools/windeployqt/qmlutils.h
new file mode 100644
index 0000000000..572be7cab0
--- /dev/null
+++ b/src/tools/windeployqt/qmlutils.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMLUTILS_H
+#define QMLUTILS_H
+
+#include "utils.h"
+
+#include <QStringList>
+
+QT_BEGIN_NAMESPACE
+
+QString findQmlDirectory(Platform platform, const QString &startDirectoryName);
+
+struct QmlImportScanResult {
+ struct Module {
+ QString installPath(const QString &root) const;
+
+ QString name;
+ QString className;
+ QString sourcePath;
+ QString relativePath;
+ };
+
+ void append(const QmlImportScanResult &other);
+
+ bool ok = false;
+ QList<Module> modules;
+ QStringList plugins;
+};
+
+bool operator==(const QmlImportScanResult::Module &m1, const QmlImportScanResult::Module &m2);
+
+QmlImportScanResult runQmlImportScanner(const QString &directory, const QStringList &qmlImportPaths,
+ bool usesWidgets, int platform, DebugMatchMode debugMatchMode,
+ QString *errorMessage);
+
+QT_END_NAMESPACE
+
+#endif // QMLUTILS_H
diff --git a/src/tools/windeployqt/utils.cpp b/src/tools/windeployqt/utils.cpp
new file mode 100644
index 0000000000..bc52de924e
--- /dev/null
+++ b/src/tools/windeployqt/utils.cpp
@@ -0,0 +1,1006 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "utils.h"
+#include "elfreader.h"
+
+#include <QtCore/QString>
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+#include <QtCore/QTemporaryFile>
+#include <QtCore/QScopedPointer>
+#include <QtCore/QScopedArrayPointer>
+#include <QtCore/QStandardPaths>
+#if defined(Q_OS_WIN)
+# include <QtCore/qt_windows.h>
+# include <shlwapi.h>
+# include <delayimp.h>
+#else // Q_OS_WIN
+# include <sys/wait.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <unistd.h>
+# include <stdlib.h>
+# include <string.h>
+# include <errno.h>
+# include <fcntl.h>
+#endif // !Q_OS_WIN
+
+QT_BEGIN_NAMESPACE
+
+int optVerboseLevel = 1;
+
+bool isBuildDirectory(Platform platform, const QString &dirName)
+{
+ return (platform.testFlag(Msvc) || platform.testFlag(ClangMsvc))
+ && (dirName == QLatin1String("debug") || dirName == QLatin1String("release"));
+}
+
+// Create a symbolic link by changing to the source directory to make sure the
+// link uses relative paths only (QFile::link() otherwise uses the absolute path).
+bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage)
+{
+ const QString oldDirectory = QDir::currentPath();
+ if (!QDir::setCurrent(source.absolutePath())) {
+ *errorMessage = QStringLiteral("Unable to change to directory %1.").arg(QDir::toNativeSeparators(source.absolutePath()));
+ return false;
+ }
+ QFile file(source.fileName());
+ const bool success = file.link(target);
+ QDir::setCurrent(oldDirectory);
+ if (!success) {
+ *errorMessage = QString::fromLatin1("Failed to create symbolic link %1 -> %2: %3")
+ .arg(QDir::toNativeSeparators(source.absoluteFilePath()),
+ QDir::toNativeSeparators(target), file.errorString());
+ return false;
+ }
+ return true;
+}
+
+bool createDirectory(const QString &directory, QString *errorMessage)
+{
+ const QFileInfo fi(directory);
+ if (fi.isDir())
+ return true;
+ if (fi.exists()) {
+ *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.").
+ arg(QDir::toNativeSeparators(directory));
+ return false;
+ }
+ if (optVerboseLevel)
+ std::wcout << "Creating " << QDir::toNativeSeparators(directory) << "...\n";
+ QDir dir;
+ if (!dir.mkpath(directory)) {
+ *errorMessage = QString::fromLatin1("Could not create directory %1.").
+ arg(QDir::toNativeSeparators(directory));
+ return false;
+ }
+ return true;
+}
+
+// Find shared libraries matching debug/Platform in a directory, return relative names.
+QStringList findSharedLibraries(const QDir &directory, Platform platform,
+ DebugMatchMode debugMatchMode,
+ const QString &prefix)
+{
+ QString nameFilter = prefix;
+ if (nameFilter.isEmpty())
+ nameFilter += QLatin1Char('*');
+ if (debugMatchMode == MatchDebug && platformHasDebugSuffix(platform))
+ nameFilter += QLatin1Char('d');
+ nameFilter += sharedLibrarySuffix(platform);
+ QStringList result;
+ QString errorMessage;
+ const QFileInfoList &dlls = directory.entryInfoList(QStringList(nameFilter), QDir::Files);
+ for (const QFileInfo &dllFi : dlls) {
+ const QString dllPath = dllFi.absoluteFilePath();
+ bool matches = true;
+ if (debugMatchMode != MatchDebugOrRelease && (platform & WindowsBased)) {
+ bool debugDll;
+ if (readPeExecutable(dllPath, &errorMessage, 0, 0, &debugDll,
+ (platform == WindowsDesktopMinGW))) {
+ matches = debugDll == (debugMatchMode == MatchDebug);
+ } else {
+ std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(dllPath)
+ << ": " << errorMessage;
+ }
+ } // Windows
+ if (matches)
+ result += dllFi.fileName();
+ } // for
+ return result;
+}
+
+#ifdef Q_OS_WIN
+QString winErrorMessage(unsigned long error)
+{
+ QString rc = QString::fromLatin1("#%1: ").arg(error);
+ char16_t *lpMsgBuf;
+
+ const DWORD len = FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, error, 0, reinterpret_cast<LPTSTR>(&lpMsgBuf), 0, NULL);
+ if (len) {
+ rc = QString::fromUtf16(lpMsgBuf, int(len));
+ LocalFree(lpMsgBuf);
+ } else {
+ rc += QString::fromLatin1("<unknown error>");
+ }
+ return rc;
+}
+
+// Case-Normalize file name via GetShortPathNameW()/GetLongPathNameW()
+QString normalizeFileName(const QString &name)
+{
+ wchar_t shortBuffer[MAX_PATH];
+ const QString nativeFileName = QDir::toNativeSeparators(name);
+ if (!GetShortPathNameW(reinterpret_cast<LPCWSTR>(nativeFileName.utf16()), shortBuffer, MAX_PATH))
+ return name;
+ wchar_t result[MAX_PATH];
+ if (!GetLongPathNameW(shortBuffer, result, MAX_PATH))
+ return name;
+ return QDir::fromNativeSeparators(QString::fromWCharArray(result));
+}
+
+// Find a tool binary in the Windows SDK 8
+QString findSdkTool(const QString &tool)
+{
+ QStringList paths = QString::fromLocal8Bit(qgetenv("PATH")).split(QLatin1Char(';'));
+ const QByteArray sdkDir = qgetenv("WindowsSdkDir");
+ if (!sdkDir.isEmpty())
+ paths.prepend(QDir::cleanPath(QString::fromLocal8Bit(sdkDir)) + QLatin1String("/Tools/x64"));
+ return QStandardPaths::findExecutable(tool, paths);
+}
+
+// runProcess helper: Create a temporary file for stdout/stderr redirection.
+static HANDLE createInheritableTemporaryFile()
+{
+ wchar_t path[MAX_PATH];
+ if (!GetTempPath(MAX_PATH, path))
+ return INVALID_HANDLE_VALUE;
+ wchar_t name[MAX_PATH];
+ if (!GetTempFileName(path, L"temp", 0, name)) // Creates file.
+ return INVALID_HANDLE_VALUE;
+ SECURITY_ATTRIBUTES securityAttributes;
+ ZeroMemory(&securityAttributes, sizeof(securityAttributes));
+ securityAttributes.nLength = sizeof(securityAttributes);
+ securityAttributes.bInheritHandle = TRUE;
+ return CreateFile(name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes,
+ TRUNCATE_EXISTING,
+ FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
+}
+
+// runProcess helper: Rewind and read out a temporary file for stdout/stderr.
+static inline void readTemporaryProcessFile(HANDLE handle, QByteArray *result)
+{
+ if (SetFilePointer(handle, 0, 0, FILE_BEGIN) == 0xFFFFFFFF)
+ return;
+ char buf[1024];
+ DWORD bytesRead;
+ while (ReadFile(handle, buf, sizeof(buf), &bytesRead, NULL) && bytesRead)
+ result->append(buf, int(bytesRead));
+ CloseHandle(handle);
+}
+
+static inline void appendToCommandLine(const QString &argument, QString *commandLine)
+{
+ const bool needsQuote = argument.contains(QLatin1Char(' '));
+ if (!commandLine->isEmpty())
+ commandLine->append(QLatin1Char(' '));
+ if (needsQuote)
+ commandLine->append(QLatin1Char('"'));
+ commandLine->append(argument);
+ if (needsQuote)
+ commandLine->append(QLatin1Char('"'));
+}
+
+// runProcess: Run a command line process (replacement for QProcess which
+// does not exist in the bootstrap library).
+bool runProcess(const QString &binary, const QStringList &args,
+ const QString &workingDirectory,
+ unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr,
+ QString *errorMessage)
+{
+ if (exitCode)
+ *exitCode = 0;
+
+ STARTUPINFO si;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+
+ STARTUPINFO myInfo;
+ GetStartupInfo(&myInfo);
+ si.hStdInput = myInfo.hStdInput;
+ si.hStdOutput = myInfo.hStdOutput;
+ si.hStdError = myInfo.hStdError;
+
+ PROCESS_INFORMATION pi;
+ ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
+ const QChar backSlash = QLatin1Char('\\');
+ QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory.isEmpty() ? QDir::currentPath() : workingDirectory);
+ if (!nativeWorkingDir.endsWith(backSlash))
+ nativeWorkingDir += backSlash;
+
+ if (stdOut) {
+ si.hStdOutput = createInheritableTemporaryFile();
+ if (si.hStdOutput == INVALID_HANDLE_VALUE) {
+ if (errorMessage)
+ *errorMessage = QStringLiteral("Error creating stdout temporary file");
+ return false;
+ }
+ si.dwFlags |= STARTF_USESTDHANDLES;
+ }
+
+ if (stdErr) {
+ si.hStdError = createInheritableTemporaryFile();
+ if (si.hStdError == INVALID_HANDLE_VALUE) {
+ if (errorMessage)
+ *errorMessage = QStringLiteral("Error creating stderr temporary file");
+ return false;
+ }
+ si.dwFlags |= STARTF_USESTDHANDLES;
+ }
+
+ // Create a copy of the command line which CreateProcessW can modify.
+ QString commandLine;
+ appendToCommandLine(binary, &commandLine);
+ for (const QString &a : args)
+ appendToCommandLine(a, &commandLine);
+ if (optVerboseLevel > 1)
+ std::wcout << "Running: " << commandLine << '\n';
+
+ QScopedArrayPointer<wchar_t> commandLineW(new wchar_t[commandLine.size() + 1]);
+ commandLine.toWCharArray(commandLineW.data());
+ commandLineW[commandLine.size()] = 0;
+ if (!CreateProcessW(0, commandLineW.data(), 0, 0, /* InheritHandles */ TRUE, 0, 0,
+ reinterpret_cast<LPCWSTR>(nativeWorkingDir.utf16()), &si, &pi)) {
+ if (stdOut)
+ CloseHandle(si.hStdOutput);
+ if (stdErr)
+ CloseHandle(si.hStdError);
+ if (errorMessage)
+ *errorMessage = QStringLiteral("CreateProcessW failed: ") + winErrorMessage(GetLastError());
+ return false;
+ }
+
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hThread);
+ if (exitCode)
+ GetExitCodeProcess(pi.hProcess, exitCode);
+ CloseHandle(pi.hProcess);
+
+ if (stdOut)
+ readTemporaryProcessFile(si.hStdOutput, stdOut);
+ if (stdErr)
+ readTemporaryProcessFile(si.hStdError, stdErr);
+ return true;
+}
+
+#else // Q_OS_WIN
+
+static inline char *encodeFileName(const QString &f)
+{
+ const QByteArray encoded = QFile::encodeName(f);
+ char *result = new char[encoded.size() + 1];
+ strcpy(result, encoded.constData());
+ return result;
+}
+
+static inline char *tempFilePattern()
+{
+ QString path = QDir::tempPath();
+ if (!path.endsWith(QLatin1Char('/')))
+ path += QLatin1Char('/');
+ path += QStringLiteral("tmpXXXXXX");
+ return encodeFileName(path);
+}
+
+static inline QByteArray readOutRedirectFile(int fd)
+{
+ enum { bufSize = 256 };
+
+ QByteArray result;
+ if (!lseek(fd, 0, 0)) {
+ char buf[bufSize];
+ while (true) {
+ const ssize_t rs = read(fd, buf, bufSize);
+ if (rs <= 0)
+ break;
+ result.append(buf, int(rs));
+ }
+ }
+ close(fd);
+ return result;
+}
+
+// runProcess: Run a command line process (replacement for QProcess which
+// does not exist in the bootstrap library).
+bool runProcess(const QString &binary, const QStringList &args,
+ const QString &workingDirectory,
+ unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr,
+ QString *errorMessage)
+{
+ QScopedArrayPointer<char> stdOutFileName;
+ QScopedArrayPointer<char> stdErrFileName;
+
+ int stdOutFile = 0;
+ if (stdOut) {
+ stdOutFileName.reset(tempFilePattern());
+ stdOutFile = mkstemp(stdOutFileName.data());
+ if (stdOutFile < 0) {
+ *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno));
+ return false;
+ }
+ }
+
+ int stdErrFile = 0;
+ if (stdErr) {
+ stdErrFileName.reset(tempFilePattern());
+ stdErrFile = mkstemp(stdErrFileName.data());
+ if (stdErrFile < 0) {
+ *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno));
+ return false;
+ }
+ }
+
+ const pid_t pID = fork();
+
+ if (pID < 0) {
+ *errorMessage = QStringLiteral("Fork failed: ") + QString::fromLocal8Bit(strerror(errno));
+ return false;
+ }
+
+ if (!pID) { // Child
+ if (stdOut) {
+ dup2(stdOutFile, STDOUT_FILENO);
+ close(stdOutFile);
+ }
+ if (stdErr) {
+ dup2(stdErrFile, STDERR_FILENO);
+ close(stdErrFile);
+ }
+
+ if (!workingDirectory.isEmpty() && !QDir::setCurrent(workingDirectory)) {
+ std::wcerr << "Failed to change working directory to " << workingDirectory << ".\n";
+ ::_exit(-1);
+ }
+
+ char **argv = new char *[args.size() + 2]; // Create argv.
+ char **ap = argv;
+ *ap++ = encodeFileName(binary);
+ for (const QString &a : qAsConst(args))
+ *ap++ = encodeFileName(a);
+ *ap = 0;
+
+ execvp(argv[0], argv);
+ ::_exit(-1);
+ }
+
+ int status;
+ pid_t waitResult;
+
+ do {
+ waitResult = waitpid(pID, &status, 0);
+ } while (waitResult == -1 && errno == EINTR);
+
+ if (stdOut) {
+ *stdOut = readOutRedirectFile(stdOutFile);
+ unlink(stdOutFileName.data());
+ }
+ if (stdErr) {
+ *stdErr = readOutRedirectFile(stdErrFile);
+ unlink(stdErrFileName.data());
+ }
+
+ if (waitResult < 0) {
+ *errorMessage = QStringLiteral("Wait failed: ") + QString::fromLocal8Bit(strerror(errno));
+ return false;
+ }
+ if (!WIFEXITED(status)) {
+ *errorMessage = binary + QStringLiteral(" did not exit cleanly.");
+ return false;
+ }
+ if (exitCode)
+ *exitCode = WEXITSTATUS(status);
+ return true;
+}
+
+#endif // !Q_OS_WIN
+
+// Find a file in the path using ShellAPI. This can be used to locate DLLs which
+// QStandardPaths cannot do.
+QString findInPath(const QString &file)
+{
+#if defined(Q_OS_WIN)
+ if (file.size() < MAX_PATH - 1) {
+ wchar_t buffer[MAX_PATH];
+ file.toWCharArray(buffer);
+ buffer[file.size()] = 0;
+ if (PathFindOnPath(buffer, NULL))
+ return QDir::cleanPath(QString::fromWCharArray(buffer));
+ }
+ return QString();
+#else // Q_OS_WIN
+ return QStandardPaths::findExecutable(file);
+#endif // !Q_OS_WIN
+}
+
+const char *qmakeInfixKey = "QT_INFIX";
+
+QMap<QString, QString> queryQtPaths(const QString &qtpathsBinary, QString *errorMessage)
+{
+ const QString binary = !qtpathsBinary.isEmpty() ? qtpathsBinary : QStringLiteral("qtpaths");
+ QByteArray stdOut;
+ QByteArray stdErr;
+ unsigned long exitCode = 0;
+ if (!runProcess(binary, QStringList(QStringLiteral("-query")), QString(), &exitCode, &stdOut, &stdErr, errorMessage))
+ return QMap<QString, QString>();
+ if (exitCode) {
+ *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode)
+ + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr);
+ return QMap<QString, QString>();
+ }
+ const QString output = QString::fromLocal8Bit(stdOut).trimmed().remove(QLatin1Char('\r'));
+ QMap<QString, QString> result;
+ const int size = output.size();
+ for (int pos = 0; pos < size; ) {
+ const int colonPos = output.indexOf(QLatin1Char(':'), pos);
+ if (colonPos < 0)
+ break;
+ int endPos = output.indexOf(QLatin1Char('\n'), colonPos + 1);
+ if (endPos < 0)
+ endPos = size;
+ const QString key = output.mid(pos, colonPos - pos);
+ const QString value = output.mid(colonPos + 1, endPos - colonPos - 1);
+ result.insert(key, value);
+ pos = endPos + 1;
+ }
+ QFile qconfigPriFile(result.value(QStringLiteral("QT_HOST_DATA")) + QStringLiteral("/mkspecs/qconfig.pri"));
+ if (qconfigPriFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ while (true) {
+ const QByteArray line = qconfigPriFile.readLine();
+ if (line.isEmpty())
+ break;
+ if (line.startsWith("QT_LIBINFIX")) {
+ const int pos = line.indexOf('=');
+ if (pos >= 0) {
+ const QString infix = QString::fromUtf8(line.right(line.size() - pos - 1).trimmed());
+ if (!infix.isEmpty())
+ result.insert(QLatin1String(qmakeInfixKey), infix);
+ }
+ break;
+ }
+ }
+ } else {
+ std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(qconfigPriFile.fileName())
+ << ": " << qconfigPriFile.errorString()<< '\n';
+ }
+ return result;
+}
+
+// Update a file or directory.
+bool updateFile(const QString &sourceFileName, const QStringList &nameFilters,
+ const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage)
+{
+ const QFileInfo sourceFileInfo(sourceFileName);
+ const QString targetFileName = targetDirectory + QLatin1Char('/') + sourceFileInfo.fileName();
+ if (optVerboseLevel > 1)
+ std::wcout << "Checking " << sourceFileName << ", " << targetFileName<< '\n';
+
+ if (!sourceFileInfo.exists()) {
+ *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName));
+ return false;
+ }
+
+ if (sourceFileInfo.isSymLink()) {
+ *errorMessage = QString::fromLatin1("Symbolic links are not supported (%1).")
+ .arg(QDir::toNativeSeparators(sourceFileName));
+ return false;
+ }
+
+ const QFileInfo targetFileInfo(targetFileName);
+
+ if (sourceFileInfo.isDir()) {
+ if (targetFileInfo.exists()) {
+ if (!targetFileInfo.isDir()) {
+ *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.")
+ .arg(QDir::toNativeSeparators(targetFileName));
+ return false;
+ } // Not a directory.
+ } else { // exists.
+ QDir d(targetDirectory);
+ if (optVerboseLevel)
+ std::wcout << "Creating " << QDir::toNativeSeparators(targetFileName) << ".\n";
+ if (!(flags & SkipUpdateFile) && !d.mkdir(sourceFileInfo.fileName())) {
+ *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.")
+ .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory));
+ return false;
+ }
+ }
+ // Recurse into directory
+ QDir dir(sourceFileName);
+ const QFileInfoList allEntries = dir.entryInfoList(nameFilters, QDir::Files)
+ + dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QFileInfo &entryFi : allEntries) {
+ if (!updateFile(entryFi.absoluteFilePath(), nameFilters, targetFileName, flags, json, errorMessage))
+ return false;
+ }
+ return true;
+ } // Source is directory.
+
+ if (targetFileInfo.exists()) {
+ if (!(flags & ForceUpdateFile)
+ && targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) {
+ if (optVerboseLevel)
+ std::wcout << sourceFileInfo.fileName() << " is up to date.\n";
+ if (json)
+ json->addFile(sourceFileName, targetDirectory);
+ return true;
+ }
+ QFile targetFile(targetFileName);
+ if (!(flags & SkipUpdateFile) && !targetFile.remove()) {
+ *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2")
+ .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString());
+ return false;
+ }
+ } // target exists
+ QFile file(sourceFileName);
+ if (optVerboseLevel)
+ std::wcout << "Updating " << sourceFileInfo.fileName() << ".\n";
+ if (!(flags & SkipUpdateFile) && !file.copy(targetFileName)) {
+ *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3")
+ .arg(QDir::toNativeSeparators(sourceFileName),
+ QDir::toNativeSeparators(targetFileName),
+ file.errorString());
+ return false;
+ }
+ if (json)
+ json->addFile(sourceFileName, targetDirectory);
+ return true;
+}
+
+bool readElfExecutable(const QString &elfExecutableFileName, QString *errorMessage,
+ QStringList *dependentLibraries, unsigned *wordSize,
+ bool *isDebug)
+{
+ ElfReader elfReader(elfExecutableFileName);
+ const ElfData data = elfReader.readHeaders();
+ if (data.sectionHeaders.isEmpty()) {
+ *errorMessage = QStringLiteral("Unable to read ELF binary \"")
+ + QDir::toNativeSeparators(elfExecutableFileName) + QStringLiteral("\": ")
+ + elfReader.errorString();
+ return false;
+ }
+ if (wordSize)
+ *wordSize = data.elfclass == Elf_ELFCLASS64 ? 64 : 32;
+ if (dependentLibraries) {
+ dependentLibraries->clear();
+ const QList<QByteArray> libs = elfReader.dependencies();
+ if (libs.isEmpty()) {
+ *errorMessage = QStringLiteral("Unable to read dependenices of ELF binary \"")
+ + QDir::toNativeSeparators(elfExecutableFileName) + QStringLiteral("\": ")
+ + elfReader.errorString();
+ return false;
+ }
+ for (const QByteArray &l : libs)
+ dependentLibraries->push_back(QString::fromLocal8Bit(l));
+ }
+ if (isDebug)
+ *isDebug = data.symbolsType != UnknownSymbols && data.symbolsType != NoSymbols;
+ return true;
+}
+
+#ifdef Q_OS_WIN
+
+static inline QString stringFromRvaPtr(const void *rvaPtr)
+{
+ return QString::fromLocal8Bit(static_cast<const char *>(rvaPtr));
+}
+
+// Helper for reading out PE executable files: Find a section header for an RVA
+// (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32).
+template <class ImageNtHeader>
+const IMAGE_SECTION_HEADER *findSectionHeader(DWORD rva, const ImageNtHeader *nTHeader)
+{
+ const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader);
+ const IMAGE_SECTION_HEADER *sectionEnd = section + nTHeader->FileHeader.NumberOfSections;
+ for ( ; section < sectionEnd; ++section)
+ if (rva >= section->VirtualAddress && rva < (section->VirtualAddress + section->Misc.VirtualSize))
+ return section;
+ return 0;
+}
+
+// Helper for reading out PE executable files: convert RVA to pointer (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32).
+template <class ImageNtHeader>
+inline const void *rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader, const void *imageBase)
+{
+ const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader);
+ if (!sectionHdr)
+ return 0;
+ const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData;
+ return static_cast<const char *>(imageBase) + rva - delta;
+}
+
+// Helper for reading out PE executable files: return word size of a IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32
+template <class ImageNtHeader>
+inline unsigned ntHeaderWordSize(const ImageNtHeader *header)
+{
+ // defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC
+ enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b };
+ if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic)
+ return 32;
+ if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic)
+ return 64;
+ return 0;
+}
+
+// Helper for reading out PE executable files: Retrieve the NT image header of an
+// executable via the legacy DOS header.
+static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, QString *errorMessage)
+{
+ IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory);
+ // Check DOS header consistency
+ if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER))
+ || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
+ *errorMessage = QString::fromLatin1("DOS header check failed.");
+ return 0;
+ }
+ // Retrieve NT header
+ char *ntHeaderC = static_cast<char *>(fileMemory) + dosHeader->e_lfanew;
+ IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC);
+ // check NT header consistency
+ if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature))
+ || ntHeaders->Signature != IMAGE_NT_SIGNATURE
+ || IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) {
+ *errorMessage = QString::fromLatin1("NT header check failed.");
+ return 0;
+ }
+ // Check magic
+ if (!ntHeaderWordSize(ntHeaders)) {
+ *errorMessage = QString::fromLatin1("NT header check failed; magic %1 is invalid.").
+ arg(ntHeaders->OptionalHeader.Magic);
+ return 0;
+ }
+ // Check section headers
+ IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders);
+ if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) {
+ *errorMessage = QString::fromLatin1("NT header section header check failed.");
+ return 0;
+ }
+ return ntHeaders;
+}
+
+// Helper for reading out PE executable files: Read out import sections from
+// IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32.
+template <class ImageNtHeader>
+inline QStringList readImportSections(const ImageNtHeader *ntHeaders, const void *base, QString *errorMessage)
+{
+ // Get import directory entry RVA and read out
+ const DWORD importsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
+ if (!importsStartRVA) {
+ *errorMessage = QString::fromLatin1("Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry.");
+ return QStringList();
+ }
+ const IMAGE_IMPORT_DESCRIPTOR *importDesc = static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(rvaToPtr(importsStartRVA, ntHeaders, base));
+ if (!importDesc) {
+ *errorMessage = QString::fromLatin1("Failed to find IMAGE_IMPORT_DESCRIPTOR entry.");
+ return QStringList();
+ }
+ QStringList result;
+ for ( ; importDesc->Name; ++importDesc)
+ result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, base)));
+
+ // Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx .
+ // Check on grAttr bit 1 whether this is the format using RVA's > VS 6
+ if (const DWORD delayedImportsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress) {
+ const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(rvaToPtr(delayedImportsStartRVA, ntHeaders, base));
+ for ( ; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1); ++delayedImportDesc)
+ result.push_back(stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, base)));
+ }
+
+ return result;
+}
+
+// Check for MSCV runtime (MSVCP90D.dll/MSVCP90.dll, MSVCP120D.dll/MSVCP120.dll,
+// VCRUNTIME140D.DLL/VCRUNTIME140.DLL (VS2015) or msvcp120d_app.dll/msvcp120_app.dll).
+enum MsvcDebugRuntimeResult { MsvcDebugRuntime, MsvcReleaseRuntime, NoMsvcRuntime };
+
+static inline MsvcDebugRuntimeResult checkMsvcDebugRuntime(const QStringList &dependentLibraries)
+{
+ for (const QString &lib : dependentLibraries) {
+ int pos = 0;
+ if (lib.startsWith(QLatin1String("MSVCR"), Qt::CaseInsensitive)
+ || lib.startsWith(QLatin1String("MSVCP"), Qt::CaseInsensitive)
+ || lib.startsWith(QLatin1String("VCRUNTIME"), Qt::CaseInsensitive)) {
+ int lastDotPos = lib.lastIndexOf(QLatin1Char('.'));
+ pos = -1 == lastDotPos ? 0 : lastDotPos - 1;
+ }
+
+ if (pos > 0 && lib.contains(QLatin1String("_app"), Qt::CaseInsensitive))
+ pos -= 4;
+
+ if (pos) {
+ return lib.at(pos).toLower() == QLatin1Char('d')
+ ? MsvcDebugRuntime : MsvcReleaseRuntime;
+ }
+ }
+ return NoMsvcRuntime;
+}
+
+template <class ImageNtHeader>
+inline void determineDebugAndDependentLibs(const ImageNtHeader *nth, const void *fileMemory,
+ bool isMinGW,
+ QStringList *dependentLibrariesIn,
+ bool *isDebugIn, QString *errorMessage)
+{
+ const bool hasDebugEntry = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
+ QStringList dependentLibraries;
+ if (dependentLibrariesIn || (isDebugIn != nullptr && hasDebugEntry && !isMinGW))
+ dependentLibraries = readImportSections(nth, fileMemory, errorMessage);
+
+ if (dependentLibrariesIn)
+ *dependentLibrariesIn = dependentLibraries;
+ if (isDebugIn != nullptr) {
+ if (isMinGW) {
+ // Use logic that's used e.g. in objdump / pfd library
+ *isDebugIn = !(nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED);
+ } else {
+ // When an MSVC debug entry is present, check whether the debug runtime
+ // is actually used to detect -release / -force-debug-info builds.
+ *isDebugIn = hasDebugEntry && checkMsvcDebugRuntime(dependentLibraries) != MsvcReleaseRuntime;
+ }
+ }
+}
+
+// Read a PE executable and determine dependent libraries, word size
+// and debug flags.
+bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage,
+ QStringList *dependentLibrariesIn, unsigned *wordSizeIn,
+ bool *isDebugIn, bool isMinGW, unsigned short *machineArchIn)
+{
+ bool result = false;
+ HANDLE hFile = NULL;
+ HANDLE hFileMap = NULL;
+ void *fileMemory = 0;
+
+ if (dependentLibrariesIn)
+ dependentLibrariesIn->clear();
+ if (wordSizeIn)
+ *wordSizeIn = 0;
+ if (isDebugIn)
+ *isDebugIn = false;
+
+ do {
+ // Create a memory mapping of the file
+ hFile = CreateFile(reinterpret_cast<const WCHAR*>(peExecutableFileName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
+ *errorMessage = QString::fromLatin1("Cannot open '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError()));
+ break;
+ }
+
+ hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+ if (hFileMap == NULL) {
+ *errorMessage = QString::fromLatin1("Cannot create file mapping of '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError()));
+ break;
+ }
+
+ fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
+ if (!fileMemory) {
+ *errorMessage = QString::fromLatin1("Cannot map '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError()));
+ break;
+ }
+
+ const IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage);
+ if (!ntHeaders)
+ break;
+
+ const unsigned wordSize = ntHeaderWordSize(ntHeaders);
+ if (wordSizeIn)
+ *wordSizeIn = wordSize;
+ if (wordSize == 32) {
+ determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders),
+ fileMemory, isMinGW, dependentLibrariesIn, isDebugIn, errorMessage);
+ } else {
+ determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders),
+ fileMemory, isMinGW, dependentLibrariesIn, isDebugIn, errorMessage);
+ }
+
+ if (machineArchIn)
+ *machineArchIn = ntHeaders->FileHeader.Machine;
+
+ result = true;
+ if (optVerboseLevel > 1) {
+ std::wcout << __FUNCTION__ << ": " << QDir::toNativeSeparators(peExecutableFileName)
+ << ' ' << wordSize << " bit";
+ if (isMinGW)
+ std::wcout << ", MinGW";
+ if (dependentLibrariesIn) {
+ std::wcout << ", dependent libraries: ";
+ if (optVerboseLevel > 2)
+ std::wcout << dependentLibrariesIn->join(QLatin1Char(' '));
+ else
+ std::wcout << dependentLibrariesIn->size();
+ }
+ if (isDebugIn)
+ std::wcout << (*isDebugIn ? ", debug" : ", release");
+ std::wcout << '\n';
+ }
+ } while (false);
+
+ if (fileMemory)
+ UnmapViewOfFile(fileMemory);
+
+ if (hFileMap != NULL)
+ CloseHandle(hFileMap);
+
+ if (hFile != NULL && hFile != INVALID_HANDLE_VALUE)
+ CloseHandle(hFile);
+
+ return result;
+}
+
+QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize)
+{
+ const QString prefix = QStringLiteral("D3Dcompiler_");
+ const QString suffix = QLatin1String(windowsSharedLibrarySuffix);
+ // Get the DLL from Kit 8.0 onwards
+ const QString kitDir = QString::fromLocal8Bit(qgetenv("WindowsSdkDir"));
+ if (!kitDir.isEmpty()) {
+ QString redistDirPath = QDir::cleanPath(kitDir) + QStringLiteral("/Redist/D3D/");
+ if (platform.testFlag(ArmBased)) {
+ redistDirPath += QStringLiteral("arm");
+ } else {
+ redistDirPath += wordSize == 32 ? QStringLiteral("x86") : QStringLiteral("x64");
+ }
+ QDir redistDir(redistDirPath);
+ if (redistDir.exists()) {
+ const QFileInfoList files = redistDir.entryInfoList(QStringList(prefix + QLatin1Char('*') + suffix), QDir::Files);
+ if (!files.isEmpty())
+ return files.front().absoluteFilePath();
+ }
+ }
+ QStringList candidateVersions;
+ for (int i = 47 ; i >= 40 ; --i)
+ candidateVersions.append(prefix + QString::number(i) + suffix);
+ // Check the bin directory of the Qt SDK (in case it is shadowed by the
+ // Windows system directory in PATH).
+ for (const QString &candidate : qAsConst(candidateVersions)) {
+ const QFileInfo fi(qtBinDir + QLatin1Char('/') + candidate);
+ if (fi.isFile())
+ return fi.absoluteFilePath();
+ }
+ // Find the latest D3D compiler DLL in path (Windows 8.1 has d3dcompiler_47).
+ if (platform.testFlag(IntelBased)) {
+ QString errorMessage;
+ unsigned detectedWordSize;
+ for (const QString &candidate : qAsConst(candidateVersions)) {
+ const QString dll = findInPath(candidate);
+ if (!dll.isEmpty()
+ && readPeExecutable(dll, &errorMessage, 0, &detectedWordSize, 0)
+ && detectedWordSize == wordSize) {
+ return dll;
+ }
+ }
+ }
+ return QString();
+}
+
+#else // Q_OS_WIN
+
+bool readPeExecutable(const QString &, QString *errorMessage,
+ QStringList *, unsigned *, bool *, bool, unsigned short *)
+{
+ *errorMessage = QStringLiteral("Not implemented.");
+ return false;
+}
+
+QString findD3dCompiler(Platform, const QString &, unsigned)
+{
+ return QString();
+}
+
+#endif // !Q_OS_WIN
+
+// Search for "qt_prfxpath=xxxx" in \a path, and replace it with "qt_prfxpath=."
+bool patchQtCore(const QString &path, QString *errorMessage)
+{
+ if (optVerboseLevel)
+ std::wcout << "Patching " << QFileInfo(path).fileName() << "...\n";
+
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly)) {
+ *errorMessage = QString::fromLatin1("Unable to patch %1: %2").arg(
+ QDir::toNativeSeparators(path), file.errorString());
+ return false;
+ }
+ const QByteArray oldContent = file.readAll();
+
+ if (oldContent.isEmpty()) {
+ *errorMessage = QString::fromLatin1("Unable to patch %1: Could not read file content").arg(
+ QDir::toNativeSeparators(path));
+ return false;
+ }
+ file.close();
+
+ QByteArray content = oldContent;
+
+ QByteArray prfxpath("qt_prfxpath=");
+ int startPos = content.indexOf(prfxpath);
+ if (startPos == -1) {
+ *errorMessage = QString::fromLatin1(
+ "Unable to patch %1: Could not locate pattern \"qt_prfxpath=\"").arg(
+ QDir::toNativeSeparators(path));
+ return false;
+ }
+ startPos += prfxpath.length();
+ int endPos = content.indexOf(char(0), startPos);
+ if (endPos == -1) {
+ *errorMessage = QString::fromLatin1("Unable to patch %1: Internal error").arg(
+ QDir::toNativeSeparators(path));
+ return false;
+ }
+
+ QByteArray replacement = QByteArray(endPos - startPos, char(0));
+ replacement[0] = '.';
+ content.replace(startPos, endPos - startPos, replacement);
+ if (content == oldContent)
+ return true;
+
+ if (!file.open(QIODevice::WriteOnly)
+ || (file.write(content) != content.size())) {
+ *errorMessage = QString::fromLatin1("Unable to patch %1: Could not write to file: %2").arg(
+ QDir::toNativeSeparators(path), file.errorString());
+ return false;
+ }
+ return true;
+}
+
+#ifdef Q_OS_WIN
+QString getArchString(unsigned short machineArch)
+{
+ switch (machineArch) {
+ case IMAGE_FILE_MACHINE_I386:
+ return QStringLiteral("x86");
+ case IMAGE_FILE_MACHINE_ARM:
+ return QStringLiteral("arm");
+ case IMAGE_FILE_MACHINE_AMD64:
+ return QStringLiteral("x64");
+ case IMAGE_FILE_MACHINE_ARM64:
+ return QStringLiteral("arm64");
+ default:
+ break;
+ }
+ return QString();
+}
+#endif // Q_OS_WIN
+
+QT_END_NAMESPACE
diff --git a/src/tools/windeployqt/utils.h b/src/tools/windeployqt/utils.h
new file mode 100644
index 0000000000..0fe3a6948f
--- /dev/null
+++ b/src/tools/windeployqt/utils.h
@@ -0,0 +1,404 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <QStringList>
+#include <QMap>
+#include <QtCore/QFile>
+#include <QtCore/QDir>
+#include <QtCore/QDateTime>
+#include <QtCore/QJsonArray>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonDocument>
+
+#include <iostream>
+
+QT_BEGIN_NAMESPACE
+
+enum PlatformFlag {
+ // OS
+ WindowsBased = 0x00001,
+ UnixBased = 0x00002,
+ // CPU
+ IntelBased = 0x00010,
+ ArmBased = 0x00020,
+ // Compiler
+ Msvc = 0x00100,
+ MinGW = 0x00200,
+ ClangMsvc = 0x00400,
+ ClangMinGW = 0x00800,
+ // Platforms
+ WindowsDesktopMsvc = WindowsBased + IntelBased + Msvc,
+ WindowsDesktopMinGW = WindowsBased + IntelBased + MinGW,
+ WindowsDesktopClangMsvc = WindowsBased + IntelBased + ClangMsvc,
+ WindowsDesktopClangMinGW = WindowsBased + IntelBased + ClangMinGW,
+ Unix = UnixBased,
+ UnknownPlatform
+};
+
+Q_DECLARE_FLAGS(Platform, PlatformFlag)
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Platform)
+
+inline bool platformHasDebugSuffix(Platform p) // Uses 'd' debug suffix
+{
+ return p.testFlag(Msvc) || p.testFlag(ClangMsvc);
+}
+
+enum ListOption {
+ ListNone = 0,
+ ListSource,
+ ListTarget,
+ ListRelative,
+ ListMapping
+};
+
+inline std::wostream &operator<<(std::wostream &str, const QString &s)
+{
+#ifdef Q_OS_WIN
+ str << reinterpret_cast<const wchar_t *>(s.utf16());
+#else
+ str << s.toStdWString();
+#endif
+ return str;
+}
+
+// Container class for JSON output
+class JsonOutput
+{
+ using SourceTargetMapping = QPair<QString, QString>;
+ using SourceTargetMappings = QList<SourceTargetMapping>;
+
+public:
+ void addFile(const QString &source, const QString &target)
+ {
+ m_files.append(SourceTargetMapping(source, target));
+ }
+
+ void removeTargetDirectory(const QString &targetDirectory)
+ {
+ for (int i = m_files.size() - 1; i >= 0; --i) {
+ if (m_files.at(i).second == targetDirectory)
+ m_files.removeAt(i);
+ }
+ }
+
+ QByteArray toJson() const
+ {
+ QJsonObject document;
+ QJsonArray files;
+ for (const SourceTargetMapping &mapping : m_files) {
+ QJsonObject object;
+ object.insert(QStringLiteral("source"), QDir::toNativeSeparators(mapping.first));
+ object.insert(QStringLiteral("target"), QDir::toNativeSeparators(mapping.second));
+ files.append(object);
+ }
+ document.insert(QStringLiteral("files"), files);
+ return QJsonDocument(document).toJson();
+ }
+ QByteArray toList(ListOption option, const QDir &base) const
+ {
+ QByteArray list;
+ for (const SourceTargetMapping &mapping : m_files) {
+ const QString source = QDir::toNativeSeparators(mapping.first);
+ const QString fileName = QFileInfo(mapping.first).fileName();
+ const QString target = QDir::toNativeSeparators(mapping.second) + QDir::separator() + fileName;
+ switch (option) {
+ case ListNone:
+ break;
+ case ListSource:
+ list += source.toUtf8() + '\n';
+ break;
+ case ListTarget:
+ list += target.toUtf8() + '\n';
+ break;
+ case ListRelative:
+ list += QDir::toNativeSeparators(base.relativeFilePath(target)).toUtf8() + '\n';
+ break;
+ case ListMapping:
+ list += '"' + source.toUtf8() + "\" \"" + QDir::toNativeSeparators(base.relativeFilePath(target)).toUtf8() + "\"\n";
+ break;
+ }
+ }
+ return list;
+ }
+private:
+ SourceTargetMappings m_files;
+};
+
+#ifdef Q_OS_WIN
+QString normalizeFileName(const QString &name);
+QString winErrorMessage(unsigned long error);
+QString findSdkTool(const QString &tool);
+#else // !Q_OS_WIN
+inline QString normalizeFileName(const QString &name) { return name; }
+#endif // !Q_OS_WIN
+
+static const char windowsSharedLibrarySuffix[] = ".dll";
+static const char unixSharedLibrarySuffix[] = ".so";
+
+inline QString sharedLibrarySuffix(Platform platform) { return QLatin1String((platform & WindowsBased) ? windowsSharedLibrarySuffix : unixSharedLibrarySuffix); }
+bool isBuildDirectory(Platform platform, const QString &dirName);
+
+bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage);
+bool createDirectory(const QString &directory, QString *errorMessage);
+QString findInPath(const QString &file);
+
+extern const char *qmakeInfixKey; // Fake key containing the libinfix
+
+QMap<QString, QString> queryQtPaths(const QString &qmakeBinary, QString *errorMessage);
+
+enum DebugMatchMode {
+ MatchDebug,
+ MatchRelease,
+ MatchDebugOrRelease
+};
+
+QStringList findSharedLibraries(const QDir &directory, Platform platform,
+ DebugMatchMode debugMatchMode,
+ const QString &prefix = QString());
+
+bool updateFile(const QString &sourceFileName, const QStringList &nameFilters,
+ const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage);
+bool runProcess(const QString &binary, const QStringList &args,
+ const QString &workingDirectory = QString(),
+ unsigned long *exitCode = 0, QByteArray *stdOut = 0, QByteArray *stdErr = 0,
+ QString *errorMessage = 0);
+
+bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage,
+ QStringList *dependentLibraries = 0, unsigned *wordSize = 0,
+ bool *isDebug = 0, bool isMinGW = false, unsigned short *machineArch = nullptr);
+bool readElfExecutable(const QString &elfExecutableFileName, QString *errorMessage,
+ QStringList *dependentLibraries = 0, unsigned *wordSize = 0,
+ bool *isDebug = 0);
+
+inline bool readExecutable(const QString &executableFileName, Platform platform,
+ QString *errorMessage, QStringList *dependentLibraries = 0,
+ unsigned *wordSize = 0, bool *isDebug = 0, unsigned short *machineArch = nullptr)
+{
+ return platform == Unix ?
+ readElfExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug) :
+ readPeExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug,
+ (platform == WindowsDesktopMinGW), machineArch);
+}
+
+#ifdef Q_OS_WIN
+# if !defined(IMAGE_FILE_MACHINE_ARM64)
+# define IMAGE_FILE_MACHINE_ARM64 0xAA64
+# endif
+QString getArchString (unsigned short machineArch);
+#endif // Q_OS_WIN
+
+// Return dependent modules of executable files.
+
+inline QStringList findDependentLibraries(const QString &executableFileName, Platform platform, QString *errorMessage)
+{
+ QStringList result;
+ readExecutable(executableFileName, platform, errorMessage, &result);
+ return result;
+}
+
+QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize);
+
+bool patchQtCore(const QString &path, QString *errorMessage);
+
+extern int optVerboseLevel;
+
+// Recursively update a file or directory, matching DirectoryFileEntryFunction against the QDir
+// to obtain the files.
+enum UpdateFileFlag {
+ ForceUpdateFile = 0x1,
+ SkipUpdateFile = 0x2,
+ RemoveEmptyQmlDirectories = 0x4,
+ SkipQmlDesignerSpecificsDirectories = 0x8
+};
+
+template <class DirectoryFileEntryFunction>
+bool updateFile(const QString &sourceFileName,
+ DirectoryFileEntryFunction directoryFileEntryFunction,
+ const QString &targetDirectory,
+ unsigned flags,
+ JsonOutput *json,
+ QString *errorMessage)
+{
+ const QFileInfo sourceFileInfo(sourceFileName);
+ const QString targetFileName = targetDirectory + QLatin1Char('/') + sourceFileInfo.fileName();
+ if (optVerboseLevel > 1)
+ std::wcout << "Checking " << sourceFileName << ", " << targetFileName << '\n';
+
+ if (!sourceFileInfo.exists()) {
+ *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName));
+ return false;
+ }
+
+ const QFileInfo targetFileInfo(targetFileName);
+
+ if (sourceFileInfo.isSymLink()) {
+ const QString sourcePath = sourceFileInfo.symLinkTarget();
+ const QString relativeSource = QDir(sourceFileInfo.absolutePath()).relativeFilePath(sourcePath);
+ if (relativeSource.contains(QLatin1Char('/'))) {
+ *errorMessage = QString::fromLatin1("Symbolic links across directories are not supported (%1).")
+ .arg(QDir::toNativeSeparators(sourceFileName));
+ return false;
+ }
+
+ // Update the linked-to file
+ if (!updateFile(sourcePath, directoryFileEntryFunction, targetDirectory, flags, json, errorMessage))
+ return false;
+
+ if (targetFileInfo.exists()) {
+ if (!targetFileInfo.isSymLink()) {
+ *errorMessage = QString::fromLatin1("%1 already exists and is not a symbolic link.")
+ .arg(QDir::toNativeSeparators(targetFileName));
+ return false;
+ } // Not a symlink
+ const QString relativeTarget = QDir(targetFileInfo.absolutePath()).relativeFilePath(targetFileInfo.symLinkTarget());
+ if (relativeSource == relativeTarget) // Exists and points to same entry: happy.
+ return true;
+ QFile existingTargetFile(targetFileName);
+ if (!(flags & SkipUpdateFile) && !existingTargetFile.remove()) {
+ *errorMessage = QString::fromLatin1("Cannot remove existing symbolic link %1: %2")
+ .arg(QDir::toNativeSeparators(targetFileName), existingTargetFile.errorString());
+ return false;
+ }
+ } // target symbolic link exists
+ return createSymbolicLink(QFileInfo(targetDirectory + QLatin1Char('/') + relativeSource), sourceFileInfo.fileName(), errorMessage);
+ } // Source is symbolic link
+
+ if (sourceFileInfo.isDir()) {
+ if ((flags & SkipQmlDesignerSpecificsDirectories) && sourceFileInfo.fileName() == QLatin1String("designer")) {
+ if (optVerboseLevel)
+ std::wcout << "Skipping " << QDir::toNativeSeparators(sourceFileName) << ".\n";
+ return true;
+ }
+ bool created = false;
+ if (targetFileInfo.exists()) {
+ if (!targetFileInfo.isDir()) {
+ *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.")
+ .arg(QDir::toNativeSeparators(targetFileName));
+ return false;
+ } // Not a directory.
+ } else { // exists.
+ QDir d(targetDirectory);
+ if (optVerboseLevel)
+ std::wcout << "Creating " << targetFileName << ".\n";
+ if (!(flags & SkipUpdateFile)) {
+ created = d.mkdir(sourceFileInfo.fileName());
+ if (!created) {
+ *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.")
+ .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory));
+ return false;
+ }
+ }
+ }
+ // Recurse into directory
+ QDir dir(sourceFileName);
+
+ const QStringList allEntries = directoryFileEntryFunction(dir) + dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString &entry : allEntries)
+ if (!updateFile(sourceFileName + QLatin1Char('/') + entry, directoryFileEntryFunction, targetFileName, flags, json, errorMessage))
+ return false;
+ // Remove empty directories, for example QML import folders for which the filter did not match.
+ if (created && (flags & RemoveEmptyQmlDirectories)) {
+ QDir d(targetFileName);
+ const QStringList entries = d.entryList(QStringList(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
+ if (entries.isEmpty() || (entries.size() == 1 && entries.first() == QLatin1String("qmldir"))) {
+ if (!d.removeRecursively()) {
+ *errorMessage = QString::fromLatin1("Cannot remove empty directory %1.")
+ .arg(QDir::toNativeSeparators(targetFileName));
+ return false;
+ }
+ if (json)
+ json->removeTargetDirectory(targetFileName);
+ }
+ }
+ return true;
+ } // Source is directory.
+
+ if (targetFileInfo.exists()) {
+ if (!(flags & ForceUpdateFile)
+ && targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) {
+ if (optVerboseLevel)
+ std::wcout << sourceFileInfo.fileName() << " is up to date.\n";
+ if (json)
+ json->addFile(sourceFileName, targetDirectory);
+ return true;
+ }
+ QFile targetFile(targetFileName);
+ if (!(flags & SkipUpdateFile) && !targetFile.remove()) {
+ *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2")
+ .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString());
+ return false;
+ }
+ } // target exists
+ QFile file(sourceFileName);
+ if (optVerboseLevel)
+ std::wcout << "Updating " << sourceFileInfo.fileName() << ".\n";
+ if (!(flags & SkipUpdateFile)) {
+ if (!file.copy(targetFileName)) {
+ *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3")
+ .arg(QDir::toNativeSeparators(sourceFileName),
+ QDir::toNativeSeparators(targetFileName),
+ file.errorString());
+ return false;
+ }
+ if (!(file.permissions() & QFile::WriteUser)) { // QTBUG-40152, clear inherited read-only attribute
+ QFile targetFile(targetFileName);
+ if (!targetFile.setPermissions(targetFile.permissions() | QFile::WriteUser)) {
+ *errorMessage = QString::fromLatin1("Cannot set write permission on %1: %2")
+ .arg(QDir::toNativeSeparators(targetFileName), file.errorString());
+ return false;
+ }
+ } // Check permissions
+ } // !SkipUpdateFile
+ if (json)
+ json->addFile(sourceFileName, targetDirectory);
+ return true;
+}
+
+// Base class to filter files by name filters functions to be passed to updateFile().
+class NameFilterFileEntryFunction {
+public:
+ explicit NameFilterFileEntryFunction(const QStringList &nameFilters) : m_nameFilters(nameFilters) {}
+ QStringList operator()(const QDir &dir) const { return dir.entryList(m_nameFilters, QDir::Files); }
+
+private:
+ const QStringList m_nameFilters;
+};
+
+// Convenience for all files.
+inline bool updateFile(const QString &sourceFileName, const QString &targetDirectory, unsigned flags, JsonOutput *json, QString *errorMessage)
+{
+ return updateFile(sourceFileName, NameFilterFileEntryFunction(QStringList()), targetDirectory, flags, json, errorMessage);
+}
+
+QT_END_NAMESPACE
+
+#endif // UTILS_H
diff --git a/tests/auto/tools/CMakeLists.txt b/tests/auto/tools/CMakeLists.txt
index f1a9ced60e..8a827d11e4 100644
--- a/tests/auto/tools/CMakeLists.txt
+++ b/tests/auto/tools/CMakeLists.txt
@@ -15,3 +15,12 @@ if(TARGET Qt::DBus)
add_subdirectory(qdbuscpp2xml)
add_subdirectory(qdbusxml2cpp)
endif()
+if(QT_FEATURE_process AND NOT CMAKE_CROSSCOMPILING)
+ if(QT_FEATURE_macdeployqt)
+ add_subdirectory(macdeployqt)
+ endif()
+ if(QT_FEATURE_windeployqt AND BUILD_SHARED_LIBS)
+ # windeployqt does not work with static Qt builds. See QTBUG-69427.
+ add_subdirectory(windeployqt)
+ endif()
+endif()
diff --git a/tests/auto/tools/macdeployqt/CMakeLists.txt b/tests/auto/tools/macdeployqt/CMakeLists.txt
new file mode 100644
index 0000000000..073c2a9e70
--- /dev/null
+++ b/tests/auto/tools/macdeployqt/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Generated from macdeployqt.pro.
+
+#####################################################################
+## tst_macdeployqt Test:
+#####################################################################
+
+qt_internal_add_test(tst_macdeployqt
+ SOURCES
+ tst_macdeployqt.cpp
+)
diff --git a/tests/auto/tools/macdeployqt/source_basicapp/basicapp.pro b/tests/auto/tools/macdeployqt/source_basicapp/basicapp.pro
new file mode 100644
index 0000000000..bba41b9c12
--- /dev/null
+++ b/tests/auto/tools/macdeployqt/source_basicapp/basicapp.pro
@@ -0,0 +1 @@
+SOURCES = main.cpp
diff --git a/tests/auto/tools/macdeployqt/source_basicapp/main.cpp b/tests/auto/tools/macdeployqt/source_basicapp/main.cpp
new file mode 100644
index 0000000000..093a882f32
--- /dev/null
+++ b/tests/auto/tools/macdeployqt/source_basicapp/main.cpp
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QGuiApplication>
+#include <QRasterWindow>
+#include <QScreen>
+#include <QTimer>
+
+// Simple test application just to verify that it comes up properly
+
+int main(int argc, char ** argv)
+{
+ QGuiApplication app(argc, argv);
+ QRasterWindow w;
+ w.setTitle("macdeployqt test application");
+ w.show();
+ QTimer::singleShot(200, &w, &QCoreApplication::quit);
+ return app.exec();
+}
diff --git a/tests/auto/tools/macdeployqt/source_plugin_sqlite/main.cpp b/tests/auto/tools/macdeployqt/source_plugin_sqlite/main.cpp
new file mode 100644
index 0000000000..31e2e8117c
--- /dev/null
+++ b/tests/auto/tools/macdeployqt/source_plugin_sqlite/main.cpp
@@ -0,0 +1,36 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtSql>
+
+int main(int argc, char ** argv)
+{
+ QCoreApplication app(argc, argv);
+ QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
+ return db.isValid() ? 0 : 1;
+}
diff --git a/tests/auto/tools/macdeployqt/source_plugin_sqlite/plugin_sqlite.pro b/tests/auto/tools/macdeployqt/source_plugin_sqlite/plugin_sqlite.pro
new file mode 100644
index 0000000000..e8183d3cee
--- /dev/null
+++ b/tests/auto/tools/macdeployqt/source_plugin_sqlite/plugin_sqlite.pro
@@ -0,0 +1,2 @@
+SOURCES = main.cpp
+QT += sql
diff --git a/tests/auto/tools/macdeployqt/source_plugin_tls/main.cpp b/tests/auto/tools/macdeployqt/source_plugin_tls/main.cpp
new file mode 100644
index 0000000000..7d1070b2c5
--- /dev/null
+++ b/tests/auto/tools/macdeployqt/source_plugin_tls/main.cpp
@@ -0,0 +1,35 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtNetwork>
+
+int main(int argc, char ** argv)
+{
+ QCoreApplication app(argc, argv);
+ return QSslSocket::supportsSsl() ? 0 : 1;
+}
diff --git a/tests/auto/tools/macdeployqt/source_plugin_tls/plugin_tls.pro b/tests/auto/tools/macdeployqt/source_plugin_tls/plugin_tls.pro
new file mode 100644
index 0000000000..23954f5941
--- /dev/null
+++ b/tests/auto/tools/macdeployqt/source_plugin_tls/plugin_tls.pro
@@ -0,0 +1,2 @@
+SOURCES = main.cpp
+QT += network
diff --git a/tests/auto/tools/macdeployqt/tst_macdeployqt.cpp b/tests/auto/tools/macdeployqt/tst_macdeployqt.cpp
new file mode 100644
index 0000000000..8922025570
--- /dev/null
+++ b/tests/auto/tools/macdeployqt/tst_macdeployqt.cpp
@@ -0,0 +1,316 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore>
+#include <QtTest>
+
+bool g_testDirectoryBuild = false; // toggle to keep build output for debugging.
+QTemporaryDir *g_temporaryDirectory;
+QString g_macdeployqtBinary;
+QString g_qmakeBinary;
+QString g_makeBinary;
+QString g_installNameToolBinary;
+
+#if QT_CONFIG(process)
+
+static const QString msgProcessError(const QProcess &process, const QString &what)
+{
+ QString result;
+ QTextStream(&result) << what << ": \"" << process.program() << ' '
+ << process.arguments().join(QLatin1Char(' ')) << "\": " << process.errorString();
+ return result;
+}
+
+static bool runProcess(const QString &binary,
+ const QStringList &arguments,
+ QString *errorMessage,
+ const QString &workingDir = QString(),
+ const QProcessEnvironment &env = QProcessEnvironment(),
+ int timeOut = 10000,
+ QByteArray *stdOut = nullptr, QByteArray *stdErr = nullptr)
+{
+ QProcess process;
+ if (!env.isEmpty())
+ process.setProcessEnvironment(env);
+ if (!workingDir.isEmpty())
+ process.setWorkingDirectory(workingDir);
+ process.start(binary, arguments, QIODevice::ReadOnly);
+ if (!process.waitForStarted()) {
+ *errorMessage = msgProcessError(process, "Failed to start");
+ return false;
+ }
+ if (!process.waitForFinished(timeOut)) {
+ *errorMessage = msgProcessError(process, "Timed out");
+ process.terminate();
+ if (!process.waitForFinished(300))
+ process.kill();
+ return false;
+ }
+ if (stdOut)
+ *stdOut = process.readAllStandardOutput();
+ if (stdErr)
+ *stdErr= process.readAllStandardError();
+ if (process.exitStatus() != QProcess::NormalExit) {
+ *errorMessage = msgProcessError(process, "Crashed");
+ return false;
+ }
+ if (process.exitCode() != QProcess::NormalExit) {
+ *errorMessage = msgProcessError(process, "Exit code " + QString::number(process.exitCode()));
+ return false;
+ }
+ return true;
+}
+
+#else
+
+static bool runProcess(const QString &binary,
+ const QStringList &arguments,
+ QString *arguments,
+ const QString &workingDir = QString(),
+ const QProcessEnvironment &env = QProcessEnvironment(),
+ int timeOut = 5000,
+ QByteArray *stdOut = Q_NULLPTR, QByteArray *stdErr = Q_NULLPTR)
+{
+ Q_UNUSED(binary);
+ Q_UNUSED(arguments);
+ Q_UNUSED(arguments);
+ Q_UNUSED(workingDir);
+ Q_UNUSED(env);
+ Q_UNUSED(timeOut);
+ Q_UNUSED(stdOut);
+ Q_UNUSED(stdErr);
+ return false;
+}
+
+#endif
+
+QString sourcePath(const QString &name)
+{
+ return "source_" + name;
+}
+
+QString buildPath(const QString &name)
+{
+ if (g_testDirectoryBuild)
+ return "build_" + name;
+ return g_temporaryDirectory->path() + "/build_" + name;
+}
+
+bool qmake(const QString &source, const QString &destination, QString *errorMessage)
+{
+ QStringList args = QStringList() << source;
+ return runProcess(g_qmakeBinary, args, errorMessage, destination);
+}
+
+bool make(const QString &destination, QString *errorMessage)
+{
+ QStringList args;
+ return runProcess(g_makeBinary, args, errorMessage, destination,
+ {}, 60000);
+}
+
+void build(const QString &name)
+{
+ // Build the app or framework according to the convention used
+ // by this test:
+ // source_name (source code)
+ // build_name (build artifacts)
+
+ QString source = sourcePath(name);
+ QString build = buildPath(name);
+ QString profile = name + ".pro";
+
+ QString sourcePath = QFINDTESTDATA(source);
+ QVERIFY(!sourcePath.isEmpty());
+
+ // Clear/set up build dir
+ QString buildPath = build;
+ QVERIFY(QDir(buildPath).removeRecursively());
+ QVERIFY(QDir().mkdir(buildPath));
+ QVERIFY(QDir(buildPath).exists());
+
+ // Build application
+ QString sourceProFile = QDir(sourcePath).canonicalPath() + '/' + profile;
+ QString errorMessage;
+ QVERIFY2(qmake(sourceProFile, buildPath, &errorMessage), qPrintable(errorMessage));
+ QVERIFY2(make(buildPath, &errorMessage), qPrintable(errorMessage));
+}
+
+bool changeInstallName(const QString &path, const QString &binary, const QString &from, const QString &to)
+{
+ QStringList args = QStringList() << binary << "-change" << from << to;
+ QString errorMessage;
+ return runProcess(g_installNameToolBinary, args, &errorMessage, path);
+}
+
+bool deploy(const QString &name, const QStringList &options, QString *errorMessage)
+{
+ QString bundle = name + ".app";
+ QString path = buildPath(name);
+ QStringList args = QStringList() << bundle << options;
+ return runProcess(g_macdeployqtBinary, args, errorMessage, path);
+}
+
+bool debugDeploy(const QString &name, const QStringList &options, QString *errorMessage)
+{
+ QString bundle = name + ".app";
+ QString path = buildPath(name);
+ QStringList args = QStringList() << bundle << options << "-verbose=3";
+ QByteArray stdOut;
+ QByteArray stdErr;
+ bool exitOK = runProcess(g_macdeployqtBinary, args, errorMessage, path, QProcessEnvironment(),
+ 10000, &stdOut, &stdErr);
+
+ qDebug() << "macdeployqt exit OK" << exitOK;
+ qDebug() << qPrintable(stdOut);
+ qDebug() << qPrintable(stdErr);
+
+ return exitOK;
+}
+
+bool run(const QString &name, QString *errorMessage)
+{
+ QString path = buildPath(name);
+ QStringList args;
+ QString binary = name + ".app/Contents/MacOS/" + name;
+ return runProcess(binary, args, errorMessage, path);
+}
+
+bool runPrintLibraries(const QString &name, QString *errorMessage, QByteArray *stdErr)
+{
+ QString binary = name + ".app/Contents/MacOS/" + name;
+ QString path = buildPath(name);
+ QStringList args;
+ QProcessEnvironment env = QProcessEnvironment();
+ env.insert("DYLD_PRINT_LIBRARIES", "true");
+ QByteArray stdOut;
+ return runProcess(binary, args, errorMessage, path, env, 5000, &stdOut, stdErr);
+}
+
+void runVerifyDeployment(const QString &name)
+{
+ QString errorMessage;
+ // Verify that the application runs after deployment and that it loads binaries from
+ // the application bundle only.
+ QByteArray libraries;
+ QVERIFY2(runPrintLibraries(name, &errorMessage, &libraries), qPrintable(errorMessage));
+ const QList<QString> parts = QString::fromLocal8Bit(libraries).split("dyld: loaded:");
+ const QString qtPath = QLibraryInfo::path(QLibraryInfo::PrefixPath);
+ // Let assume Qt is not installed in system
+ foreach (QString part, parts) {
+ part = part.trimmed();
+ if (part.isEmpty())
+ continue;
+ QVERIFY(!parts.startsWith(qtPath));
+ }
+}
+
+class tst_macdeployqt : public QObject
+{
+ Q_OBJECT
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void basicapp();
+ void plugins_data();
+ void plugins();
+};
+
+void tst_macdeployqt::initTestCase()
+{
+#ifdef QT_NO_PROCESS
+ QSKIP("This test requires QProcess support");
+#endif
+
+ // Set up test-global unique temporary directory
+ g_temporaryDirectory = new QTemporaryDir();
+ QVERIFY(g_temporaryDirectory->isValid());
+
+ // Locate build and deployment tools
+ g_macdeployqtBinary = QLibraryInfo::path(QLibraryInfo::BinariesPath) + "/macdeployqt";
+ QVERIFY(!g_macdeployqtBinary.isEmpty());
+ g_qmakeBinary = QLibraryInfo::path(QLibraryInfo::BinariesPath) + "/qmake";
+ QVERIFY(!g_qmakeBinary.isEmpty());
+ g_makeBinary = QStandardPaths::findExecutable("make");
+ QVERIFY(!g_makeBinary.isEmpty());
+ g_installNameToolBinary = QStandardPaths::findExecutable("install_name_tool");
+ QVERIFY(!g_installNameToolBinary.isEmpty());
+}
+
+void tst_macdeployqt::cleanupTestCase()
+{
+ delete g_temporaryDirectory;
+}
+
+// Verify that deployment of a basic Qt Gui application works
+void tst_macdeployqt::basicapp()
+{
+#ifdef QT_NO_PROCESS
+ QSKIP("This test requires QProcess support");
+#endif
+
+ QString errorMessage;
+ QString name = "basicapp";
+
+ // Build and verify that the application runs before deployment
+ build(name);
+ QVERIFY2(run(name, &errorMessage), qPrintable(errorMessage));
+
+ // Deploy application
+ QVERIFY2(deploy(name, QStringList(), &errorMessage), qPrintable(errorMessage));
+
+ // Verify deployment
+ runVerifyDeployment(name);
+}
+
+void tst_macdeployqt::plugins_data()
+{
+ QTest::addColumn<QString>("name");
+ QTest::newRow("sqlite") << "plugin_sqlite";
+ QTest::newRow("tls") << "plugin_tls";
+}
+
+void tst_macdeployqt::plugins()
+{
+ QFETCH(QString, name);
+
+ build(name);
+
+ // Verify that the test app runs before deployment.
+ QString errorMessage;
+ if (!run(name, &errorMessage)) {
+ qDebug() << qPrintable(errorMessage);
+ QSKIP("Could not run test application before deployment");
+ }
+
+ QVERIFY2(deploy(name, QStringList(), &errorMessage), qPrintable(errorMessage));
+ runVerifyDeployment(name);
+}
+
+QTEST_MAIN(tst_macdeployqt)
+#include "tst_macdeployqt.moc"
diff --git a/tests/auto/tools/windeployqt/CMakeLists.txt b/tests/auto/tools/windeployqt/CMakeLists.txt
new file mode 100644
index 0000000000..5eae1ca164
--- /dev/null
+++ b/tests/auto/tools/windeployqt/CMakeLists.txt
@@ -0,0 +1,4 @@
+# Generated from windeployqt.pro.
+
+add_subdirectory(testapp)
+add_subdirectory(test)
diff --git a/tests/auto/tools/windeployqt/test/CMakeLists.txt b/tests/auto/tools/windeployqt/test/CMakeLists.txt
new file mode 100644
index 0000000000..d412249a7f
--- /dev/null
+++ b/tests/auto/tools/windeployqt/test/CMakeLists.txt
@@ -0,0 +1,11 @@
+# Generated from test.pro.
+
+#####################################################################
+## tst_windeployqt Test:
+#####################################################################
+
+qt_internal_add_test(tst_windeployqt
+ OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../" # special case
+ SOURCES
+ ../tst_windeployqt.cpp
+)
diff --git a/tests/auto/tools/windeployqt/testapp/CMakeLists.txt b/tests/auto/tools/windeployqt/testapp/CMakeLists.txt
new file mode 100644
index 0000000000..83851dae65
--- /dev/null
+++ b/tests/auto/tools/windeployqt/testapp/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Generated from testapp.pro.
+
+#####################################################################
+## testapp Binary:
+#####################################################################
+
+qt_internal_add_executable(windeploy_testapp # special case
+ GUI
+ OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
+ SOURCES
+ main.cpp
+ PUBLIC_LIBRARIES
+ Qt::Gui
+)
+
+# special case begin
+set_target_properties(windeploy_testapp
+ PROPERTIES
+ OUTPUT_NAME testapp
+)
+# special case end
diff --git a/tests/auto/tools/windeployqt/testapp/main.cpp b/tests/auto/tools/windeployqt/testapp/main.cpp
new file mode 100644
index 0000000000..4dc03cdad4
--- /dev/null
+++ b/tests/auto/tools/windeployqt/testapp/main.cpp
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QGuiApplication>
+#include <QRasterWindow>
+#include <QScreen>
+#include <QTimer>
+
+// Simple test application just to verify that it comes up properly
+
+int main(int argc, char ** argv)
+{
+ QGuiApplication app(argc, argv);
+ QRasterWindow w;
+ w.setTitle("windeployqt test application");
+ const QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry();
+ w.resize(availableGeometry.size() / 4);
+ w.show();
+ QTimer::singleShot(200, &w, &QCoreApplication::quit);
+ return app.exec();
+}
diff --git a/tests/auto/tools/windeployqt/tst_windeployqt.cpp b/tests/auto/tools/windeployqt/tst_windeployqt.cpp
new file mode 100644
index 0000000000..38403c90fc
--- /dev/null
+++ b/tests/auto/tools/windeployqt/tst_windeployqt.cpp
@@ -0,0 +1,181 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+#include <QtCore/QLibraryInfo>
+#include <QtCore/QProcess>
+#include <QtCore/QProcessEnvironment>
+#include <QtCore/QStandardPaths>
+#include <QtCore/QTextStream>
+#include <QtTest/QtTest>
+
+static const QString msgProcessError(const QProcess &process, const QString &what,
+ const QByteArray &stdOut = QByteArray(),
+ const QByteArray &stdErr = QByteArray())
+{
+ QString result;
+ QTextStream str(&result);
+ str << what << ": \"" << process.program() << ' '
+ << process.arguments().join(QLatin1Char(' ')) << "\": " << process.errorString();
+ if (!stdOut.isEmpty())
+ str << "\nStandard output:\n" << stdOut;
+ if (!stdErr.isEmpty())
+ str << "\nStandard error:\n" << stdErr;
+ return result;
+}
+
+static bool runProcess(const QString &binary,
+ const QStringList &arguments,
+ QString *errorMessage,
+ const QString &workingDir = QString(),
+ const QProcessEnvironment &env = QProcessEnvironment(),
+ int timeOut = 5000,
+ QByteArray *stdOutIn = nullptr, QByteArray *stdErrIn = nullptr)
+{
+ QProcess process;
+ if (!env.isEmpty())
+ process.setProcessEnvironment(env);
+ if (!workingDir.isEmpty())
+ process.setWorkingDirectory(workingDir);
+ qDebug().noquote().nospace() << "Running: " << QDir::toNativeSeparators(binary)
+ << ' ' << arguments.join(QLatin1Char(' '));
+ process.start(binary, arguments, QIODevice::ReadOnly);
+ if (!process.waitForStarted()) {
+ *errorMessage = msgProcessError(process, "Failed to start");
+ return false;
+ }
+ if (!process.waitForFinished(timeOut)) {
+ *errorMessage = msgProcessError(process, "Timed out");
+ process.terminate();
+ if (!process.waitForFinished(300))
+ process.kill();
+ return false;
+ }
+ const QByteArray stdOut = process.readAllStandardOutput();
+ const QByteArray stdErr = process.readAllStandardError();
+ if (stdOutIn)
+ *stdOutIn = stdOut;
+ if (stdErrIn)
+ *stdErrIn = stdErr;
+ if (process.exitStatus() != QProcess::NormalExit) {
+ *errorMessage = msgProcessError(process, "Crashed", stdOut, stdErr);
+ return false;
+ }
+ if (process.exitCode() != QProcess::NormalExit) {
+ *errorMessage = msgProcessError(process, "Exit code " + QString::number(process.exitCode()),
+ stdOut, stdErr);
+ return false;
+ }
+ return true;
+}
+
+class tst_windeployqt : public QObject
+{
+ Q_OBJECT
+private slots:
+ void initTestCase();
+ void help();
+ void deploy();
+
+private:
+ QString m_windeployqtBinary;
+ QString m_testApp;
+ QString m_testAppBinary;
+};
+
+void tst_windeployqt::initTestCase()
+{
+ m_windeployqtBinary = QStandardPaths::findExecutable("windeployqt");
+ QVERIFY(!m_windeployqtBinary.isEmpty());
+ m_testApp = QFINDTESTDATA("testapp");
+ QVERIFY(!m_testApp.isEmpty());
+ const QFileInfo testAppBinary(m_testApp + QLatin1String("/testapp.exe"));
+ QVERIFY2(testAppBinary.isFile(), qPrintable(testAppBinary.absoluteFilePath()));
+ m_testAppBinary = testAppBinary.absoluteFilePath();
+}
+
+void tst_windeployqt::help()
+{
+ QString errorMessage;
+ QByteArray stdOut;
+ QByteArray stdErr;
+ QVERIFY2(runProcess(m_windeployqtBinary, QStringList("--help"), &errorMessage,
+ QString(), QProcessEnvironment(), 5000, &stdOut, &stdErr),
+ qPrintable(errorMessage));
+ QVERIFY2(!stdOut.isEmpty(), stdErr);
+}
+
+// deploy(): Deploys the test application and launches it with Qt removed from the environment
+// to verify it runs stand-alone.
+
+void tst_windeployqt::deploy()
+{
+ QString errorMessage;
+ // Deploy application
+ QStringList deployArguments;
+ deployArguments << QLatin1String("--no-translations") << QDir::toNativeSeparators(m_testAppBinary);
+ QVERIFY2(runProcess(m_windeployqtBinary, deployArguments, &errorMessage, QString(), QProcessEnvironment(), 20000),
+ qPrintable(errorMessage));
+
+ // Create environment with Qt and all "lib" paths removed.
+ const QString qtBinDir = QDir::toNativeSeparators(QLibraryInfo::path(QLibraryInfo::BinariesPath));
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ const QString pathKey = QLatin1String("PATH");
+ const QChar pathSeparator(QLatin1Char(';')); // ### fixme: Qt 5.6: QDir::listSeparator()
+ const QString origPath = env.value(pathKey);
+ QString newPath;
+ const QStringList pathElements = origPath.split(pathSeparator, Qt::SkipEmptyParts);
+ for (const QString &pathElement : pathElements) {
+ if (pathElement.compare(qtBinDir, Qt::CaseInsensitive)
+ && !pathElement.contains(QLatin1String("\\lib"), Qt::CaseInsensitive)) {
+ if (!newPath.isEmpty())
+ newPath.append(pathSeparator);
+ newPath.append(pathElement);
+ }
+ }
+ if (newPath == origPath)
+ qWarning() << "Unable to remove Qt from PATH";
+ env.insert(pathKey, newPath);
+
+ // Create qt.conf to enforce usage of local plugins
+ QFile qtConf(QFileInfo(m_testAppBinary).absolutePath() + QLatin1String("/qt.conf"));
+ QVERIFY2(qtConf.open(QIODevice::WriteOnly | QIODevice::Text),
+ qPrintable(qtConf.fileName() + QLatin1String(": ") + qtConf.errorString()));
+ QVERIFY(qtConf.write("[Paths]\nPrefix = .\n"));
+ qtConf.close();
+
+ // Verify that application still runs
+ QVERIFY2(runProcess(m_testAppBinary, QStringList(), &errorMessage, QString(), env, 10000),
+ qPrintable(errorMessage));
+}
+
+QTEST_MAIN(tst_windeployqt)
+#include "tst_windeployqt.moc"