10int main(
int argc,
char **argv)
12 QCoreApplication app(argc, argv);
14 QString appBundlePath;
16 appBundlePath = QString::fromLocal8Bit(argv[1]);
18 if (argc < 2 || appBundlePath.startsWith(
'-')) {
19 qDebug() <<
"Usage: macdeployqt app-bundle [options]";
21 qDebug() <<
"Options:";
22 qDebug() <<
" -verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug";
23 qDebug() <<
" -no-plugins : Skip plugin deployment";
24 qDebug() <<
" -dmg : Create a .dmg disk image";
25 qDebug() <<
" -no-strip : Don't run 'strip' on the binaries";
26 qDebug() <<
" -use-debug-libs : Deploy with debug versions of frameworks and plugins (implies -no-strip)";
27 qDebug() <<
" -executable=<path> : Let the given executable use the deployed frameworks too";
28 qDebug() <<
" -qmldir=<path> : Scan for QML imports in the given path";
29 qDebug() <<
" -qmlimport=<path> : Add the given path to the QML module search locations";
30 qDebug() <<
" -always-overwrite : Copy files even if the target file exists";
31 qDebug() <<
" -codesign=<ident> : Run codesign with the given identity on "
32 "all executables (default: ad-hoc sign)";
33 qDebug() <<
" -no-codesign : Disable code signing";
34 qDebug() <<
" -hardened-runtime : Enable Hardened Runtime when code signing";
35 qDebug() <<
" -timestamp : Include a secure timestamp when code signing (requires internet connection)";
36 qDebug() <<
" -sign-for-notarization=<ident>: Activate the necessary options for notarization (requires internet connection)";
37 qDebug() <<
" -appstore-compliant : Skip deployment of components that use private API";
38 qDebug() <<
" -libpath=<path> : Add the given path to the library search path";
39 qDebug() <<
" -exclude-plugins=<list> : Do not deploy the given plugins, a comma-separated";
40 qDebug() <<
" list of plugin names such as qjpeg,qsvgicon";
41 qDebug() <<
" -include-plugins=<list> : Also deploy the given plugins, a comma-separated";
42 qDebug() <<
" list of plugin names";
43 qDebug() <<
" -exclude-plugin-types=<list> : Do not deploy plugins of the given types, a comma-separated";
44 qDebug() <<
" list of plugin types such as imageformats,iconengines";
45 qDebug() <<
" -include-plugin-types=<list> : Also deploy all plugins of the given types";
46 qDebug() <<
" -fs=<filesystem> : Set the filesystem used for the .dmg disk image (defaults to HFS+)";
48 qDebug() <<
"macdeployqt takes an application bundle as input and makes it";
49 qDebug() <<
"self-contained by copying in the Qt frameworks and plugins that";
50 qDebug() <<
"the application uses.";
52 qDebug() <<
"Plugins related to a framework are copied in with the";
53 qDebug() <<
"framework. The accessibility, image formats, and text codec";
54 qDebug() <<
"plugins are always copied, unless \"-no-plugins\" is specified.";
56 qDebug() <<
"Qt plugins may use private API and will cause the app to be";
57 qDebug() <<
"rejected from the Mac App store. macdeployqt will print a warning";
58 qDebug() <<
"when known incompatible plugins are deployed. Use -appstore-compliant ";
59 qDebug() <<
"to skip these plugins. Currently two SQL plugins are known to";
60 qDebug() <<
"be incompatible: qsqlodbc and qsqlpsql.";
62 qDebug() <<
"See the \"Qt for macOS - Deployment\" topic in the";
63 qDebug() <<
"documentation for more information about deployment on macOS.";
68 appBundlePath = QDir::cleanPath(appBundlePath);
70 if (!QDir(appBundlePath).exists()) {
71 qDebug() <<
"Error: Could not find app bundle" << appBundlePath;
77 QByteArray filesystem(
"HFS+");
78 bool useDebugLibs =
false;
82 QStringList additionalExecutables;
83 bool qmldirArgumentUsed =
false;
85 QStringList qmlImportPaths;
87 QString codesignIdentity = QStringLiteral(
"-");
89 bool noCodesignExplicit =
false;
94 for (
int i = 2; i < argc; ++i) {
95 QByteArray argument = QByteArray(argv[i]);
96 if (argument == QByteArrayView(
"-no-plugins")) {
97 LogDebug() <<
"Argument found:" << argument;
99 }
else if (argument == QByteArrayView(
"-dmg")) {
100 LogDebug() <<
"Argument found:" << argument;
102 }
else if (argument == QByteArrayView(
"-no-strip")) {
103 LogDebug() <<
"Argument found:" << argument;
105 }
else if (argument == QByteArrayView(
"-use-debug-libs")) {
106 LogDebug() <<
"Argument found:" << argument;
109 }
else if (argument.startsWith(QByteArrayView(
"-verbose"))) {
110 LogDebug() <<
"Argument found:" << argument;
111 int index = argument.indexOf(
'=');
113 int number = argument.mid(index+1).toInt(&ok);
115 LogError() <<
"Could not parse verbose level";
120 }
else if (argument.startsWith(QByteArrayView(
"-executable"))) {
121 LogDebug() <<
"Argument found:" << argument;
122 int index = argument.indexOf(
'=');
124 LogError() <<
"Missing executable path";
127 additionalExecutables << argument.mid(index+1);
129 }
else if (argument.startsWith(QByteArrayView(
"-qmldir"))) {
130 LogDebug() <<
"Argument found:" << argument;
131 qmldirArgumentUsed =
true;
132 int index = argument.indexOf(
'=');
134 LogError() <<
"Missing qml directory path";
137 qmlDirs << argument.mid(index+1);
139 }
else if (argument.startsWith(QByteArrayView(
"-qmlimport"))) {
140 LogDebug() <<
"Argument found:" << argument;
141 int index = argument.indexOf(
'=');
143 LogError() <<
"Missing qml import path";
146 qmlImportPaths << argument.mid(index+1);
148 }
else if (argument.startsWith(QByteArrayView(
"-include-plugin-types"))) {
149 LogDebug() <<
"Argument found:" << argument;
150 int index = argument.indexOf(
'=');
152 LogError() <<
"Missing plugin type list";
155 includedPluginTypes += QString::fromLocal8Bit(argument.mid(index + 1))
156 .split(u',', Qt::SkipEmptyParts);
157 }
else if (argument.startsWith(QByteArrayView(
"-exclude-plugin-types"))) {
158 LogDebug() <<
"Argument found:" << argument;
159 int index = argument.indexOf(
'=');
161 LogError() <<
"Missing plugin type list";
164 excludedPluginTypes += QString::fromLocal8Bit(argument.mid(index + 1))
165 .split(u',', Qt::SkipEmptyParts);
166 }
else if (argument.startsWith(QByteArrayView(
"-include-plugins"))) {
167 LogDebug() <<
"Argument found:" << argument;
168 int index = argument.indexOf(
'=');
170 LogError() <<
"Missing plugin list";
173 includedPlugins += QString::fromLocal8Bit(argument.mid(index + 1))
174 .split(u',', Qt::SkipEmptyParts);
175 }
else if (argument.startsWith(QByteArrayView(
"-exclude-plugins"))) {
176 LogDebug() <<
"Argument found:" << argument;
177 int index = argument.indexOf(
'=');
179 LogError() <<
"Missing plugin list";
182 excludedPlugins += QString::fromLocal8Bit(argument.mid(index + 1))
183 .split(u',', Qt::SkipEmptyParts);
184 }
else if (argument.startsWith(QByteArrayView(
"-libpath"))) {
185 LogDebug() <<
"Argument found:" << argument;
186 int index = argument.indexOf(
'=');
188 LogError() <<
"Missing library search path";
193 }
else if (argument == QByteArrayView(
"-always-overwrite")) {
194 LogDebug() <<
"Argument found:" << argument;
196 }
else if (argument == QByteArrayView(
"-no-codesign")) {
197 LogDebug() <<
"Argument found:" << argument;
199 noCodesignExplicit =
true;
200 }
else if (argument.startsWith(QByteArrayView(
"-codesign"))) {
201 LogDebug() <<
"Argument found:" << argument;
202 if (noCodesignExplicit) {
203 LogError() <<
"Error: -no-codesign cannot be combined with -codesign\n";
206 int index = argument.indexOf(
'=');
207 if (index < 0 || index >= argument.size()) {
208 LogError() <<
"Missing code signing identity";
212 codesignIdentity = argument.mid(index + 1);
214 }
else if (argument.startsWith(QByteArrayView(
"-sign-for-notarization"))) {
215 LogDebug() <<
"Argument found:" << argument;
216 if (noCodesignExplicit) {
217 LogError() <<
"Error: -no-codesign cannot be combined with -sign-for-notarization\n";
220 int index = argument.indexOf(
'=');
221 if (index < 0 || index >= argument.size()) {
222 LogError() <<
"Missing code signing identity";
228 codesignIdentity = argument.mid(index + 1);
230 }
else if (argument.startsWith(QByteArrayView(
"-hardened-runtime"))) {
231 LogDebug() <<
"Argument found:" << argument;
233 }
else if (argument.startsWith(QByteArrayView(
"-timestamp"))) {
234 LogDebug() <<
"Argument found:" << argument;
236 }
else if (argument == QByteArrayView(
"-appstore-compliant")) {
237 LogDebug() <<
"Argument found:" << argument;
241 }
else if (argument == QByteArrayView(
"-deploy-framework")) {
242 LogDebug() <<
"Argument found:" << argument;
245 }
else if (argument.startsWith(QByteArrayView(
"-fs"))) {
246 LogDebug() <<
"Argument found:" << argument;
247 int index = argument.indexOf(
'=');
249 LogError() <<
"Missing filesystem type";
252 filesystem = argument.mid(index+1);
254 }
else if (argument.startsWith(
'-')) {
255 LogError() <<
"Unknown argument" << argument <<
"\n";
260 DeploymentInfo deploymentInfo = deployQtFrameworks(appBundlePath, additionalExecutables, useDebugLibs);
266 fixupFramework(appBundlePath);
269 if (qmlDirs.isEmpty()) {
270 using namespace Qt::StringLiterals;
271 using F = QDirListing::IteratorFlag;
272 QDirListing lister(QDir::currentPath(), QStringList(u"*.qml"_s), F::FilesOnly);
273 if (lister.cbegin() != lister.cend())
274 qmlDirs += QStringLiteral(
".");
277 if (!qmlDirs.isEmpty()) {
278 bool ok = deployQmlImports(appBundlePath, deploymentInfo, qmlDirs, qmlImportPaths);
279 if (!ok && qmldirArgumentUsed)
284 deploymentInfo.deployedFrameworks += findAppFrameworkNames(appBundlePath);
285 deploymentInfo.deployedFrameworks =
286 QSet<QString>(deploymentInfo.deployedFrameworks.begin(),
287 deploymentInfo.deployedFrameworks.end()).values();
293 QStringList pluginPaths = QLibraryInfo::paths(QLibraryInfo::PluginsPath);
296 if (pluginPaths.isEmpty()) {
297 LogError() <<
"Missing Qt plugins path\n";
301 deploymentInfo.pluginPath = pluginPaths.takeFirst();
302 if (!QDir(deploymentInfo.pluginPath).exists()) {
303 LogError() <<
"Plugins path does not exist" << deploymentInfo.pluginPath <<
"\n";
308 Q_ASSERT(!deploymentInfo.pluginPath.isEmpty());
309 if (!deploymentInfo.pluginPath.isEmpty()) {
311 deployPlugins(appBundlePath, deploymentInfo, useDebugLibs);
316 stripAppBinary(appBundlePath);
319 codesign(codesignIdentity, appBundlePath);
323 createDiskImage(appBundlePath, filesystem);