Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QCoreApplication>
#include <QDir>
#include <QDirListing>
#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 (default: ad-hoc sign)";
qDebug() << " -no-codesign : Disable code signing";
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() << " -exclude-plugins=<list> : Do not deploy the given plugins, a comma-separated";
qDebug() << " list of plugin names such as qjpeg,qsvgicon";
qDebug() << " -include-plugins=<list> : Also deploy the given plugins, a comma-separated";
qDebug() << " list of plugin names";
qDebug() << " -exclude-plugin-types=<list> : Do not deploy plugins of the given types, a comma-separated";
qDebug() << " list of plugin types such as imageformats,iconengines";
qDebug() << " -include-plugin-types=<list> : Also deploy all plugins of the given types";
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 \"Qt for macOS - Deployment\" topic in the";
qDebug() << "documentation for more information about deployment on macOS.";
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;
QString codesignIdentity = QStringLiteral("-");
extern bool hardenedRuntime;
bool noCodesignExplicit = false;
extern bool appstoreCompliant;
extern bool deployFramework;
extern bool secureTimestamp;
for (int i = 2; i < argc; ++i) {
QByteArray argument = QByteArray(argv[i]);
if (argument == QByteArrayView("-no-plugins")) {
LogDebug() << "Argument found:" << argument;
plugins = false;
} else if (argument == QByteArrayView("-dmg")) {
LogDebug() << "Argument found:" << argument;
dmg = true;
} else if (argument == QByteArrayView("-no-strip")) {
LogDebug() << "Argument found:" << argument;
runStripEnabled = false;
} else if (argument == QByteArrayView("-use-debug-libs")) {
LogDebug() << "Argument found:" << argument;
useDebugLibs = true;
runStripEnabled = false;
} else if (argument.startsWith(QByteArrayView("-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";
return 1;
} else {
logLevel = number;
}
} else if (argument.startsWith(QByteArrayView("-executable"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1) {
LogError() << "Missing executable path";
return 1;
} else {
additionalExecutables << argument.mid(index+1);
}
} else if (argument.startsWith(QByteArrayView("-qmldir"))) {
LogDebug() << "Argument found:" << argument;
qmldirArgumentUsed = true;
int index = argument.indexOf('=');
if (index == -1) {
LogError() << "Missing qml directory path";
return 1;
} else {
qmlDirs << argument.mid(index+1);
}
} else if (argument.startsWith(QByteArrayView("-qmlimport"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1) {
LogError() << "Missing qml import path";
return 1;
} else {
qmlImportPaths << argument.mid(index+1);
}
} else if (argument.startsWith(QByteArrayView("-include-plugin-types"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1) {
LogError() << "Missing plugin type list";
return 1;
}
includedPluginTypes += QString::fromLocal8Bit(argument.mid(index + 1))
.split(u',', Qt::SkipEmptyParts);
} else if (argument.startsWith(QByteArrayView("-exclude-plugin-types"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1) {
LogError() << "Missing plugin type list";
return 1;
}
excludedPluginTypes += QString::fromLocal8Bit(argument.mid(index + 1))
.split(u',', Qt::SkipEmptyParts);
} else if (argument.startsWith(QByteArrayView("-include-plugins"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1) {
LogError() << "Missing plugin list";
return 1;
}
includedPlugins += QString::fromLocal8Bit(argument.mid(index + 1))
.split(u',', Qt::SkipEmptyParts);
} else if (argument.startsWith(QByteArrayView("-exclude-plugins"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1) {
LogError() << "Missing plugin list";
return 1;
}
excludedPlugins += QString::fromLocal8Bit(argument.mid(index + 1))
.split(u',', Qt::SkipEmptyParts);
} else if (argument.startsWith(QByteArrayView("-libpath"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1) {
LogError() << "Missing library search path";
return 1;
} else {
librarySearchPath << argument.mid(index+1);
}
} else if (argument == QByteArrayView("-always-overwrite")) {
LogDebug() << "Argument found:" << argument;
alwaysOwerwriteEnabled = true;
} else if (argument == QByteArrayView("-no-codesign")) {
LogDebug() << "Argument found:" << argument;
runCodesign = false;
noCodesignExplicit = true;
} else if (argument.startsWith(QByteArrayView("-codesign"))) {
LogDebug() << "Argument found:" << argument;
if (noCodesignExplicit) {
LogError() << "Error: -no-codesign cannot be combined with -codesign\n";
return 1;
}
int index = argument.indexOf('=');
if (index < 0 || index >= argument.size()) {
LogError() << "Missing code signing identity";
return 1;
} else {
runCodesign = true;
codesignIdentity = argument.mid(index + 1);
}
} else if (argument.startsWith(QByteArrayView("-sign-for-notarization"))) {
LogDebug() << "Argument found:" << argument;
if (noCodesignExplicit) {
LogError() << "Error: -no-codesign cannot be combined with -sign-for-notarization\n";
return 1;
}
int index = argument.indexOf('=');
if (index < 0 || index >= argument.size()) {
LogError() << "Missing code signing identity";
return 1;
} else {
runCodesign = true;
hardenedRuntime = true;
secureTimestamp = true;
codesignIdentity = argument.mid(index + 1);
}
} else if (argument.startsWith(QByteArrayView("-hardened-runtime"))) {
LogDebug() << "Argument found:" << argument;
hardenedRuntime = true;
} else if (argument.startsWith(QByteArrayView("-timestamp"))) {
LogDebug() << "Argument found:" << argument;
secureTimestamp = true;
} else if (argument == QByteArrayView("-appstore-compliant")) {
LogDebug() << "Argument found:" << argument;
appstoreCompliant = true;
// Undocumented option, may not work as intended
} else if (argument == QByteArrayView("-deploy-framework")) {
LogDebug() << "Argument found:" << argument;
deployFramework = true;
} else if (argument.startsWith(QByteArrayView("-fs"))) {
LogDebug() << "Argument found:" << argument;
int index = argument.indexOf('=');
if (index == -1) {
LogError() << "Missing filesystem type";
return 1;
} 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()) {
using namespace Qt::StringLiterals;
using F = QDirListing::IteratorFlag;
QDirListing lister(QDir::currentPath(), QStringList(u"*.qml"_s), F::FilesOnly);
if (lister.cbegin() != lister.cend())
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
QStringList pluginPaths = QLibraryInfo::paths(QLibraryInfo::PluginsPath);
// Sanity checks
if (pluginPaths.isEmpty()) {
LogError() << "Missing Qt plugins path\n";
return 1;
}
deploymentInfo.pluginPath = pluginPaths.takeFirst();
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);
}
}
if (runStripEnabled)
stripAppBinary(appBundlePath);
if (runCodesign)
codesign(codesignIdentity, appBundlePath);
if (dmg) {
LogNormal();
createDiskImage(appBundlePath, filesystem);
}
return 0;
}