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() <<
" -fs=<filesystem> : Set the filesystem used for the .dmg disk image (defaults to HFS+)";
41 qDebug() <<
"macdeployqt takes an application bundle as input and makes it";
42 qDebug() <<
"self-contained by copying in the Qt frameworks and plugins that";
43 qDebug() <<
"the application uses.";
45 qDebug() <<
"Plugins related to a framework are copied in with the";
46 qDebug() <<
"framework. The accessibility, image formats, and text codec";
47 qDebug() <<
"plugins are always copied, unless \"-no-plugins\" is specified.";
49 qDebug() <<
"Qt plugins may use private API and will cause the app to be";
50 qDebug() <<
"rejected from the Mac App store. macdeployqt will print a warning";
51 qDebug() <<
"when known incompatible plugins are deployed. Use -appstore-compliant ";
52 qDebug() <<
"to skip these plugins. Currently two SQL plugins are known to";
53 qDebug() <<
"be incompatible: qsqlodbc and qsqlpsql.";
55 qDebug() <<
"See the \"Qt for macOS - Deployment\" topic in the";
56 qDebug() <<
"documentation for more information about deployment on macOS.";
61 appBundlePath = QDir::cleanPath(appBundlePath);
63 if (!QDir(appBundlePath).exists()) {
64 qDebug() <<
"Error: Could not find app bundle" << appBundlePath;
70 QByteArray filesystem(
"HFS+");
71 bool useDebugLibs =
false;
75 QStringList additionalExecutables;
76 bool qmldirArgumentUsed =
false;
78 QStringList qmlImportPaths;
80 QString codesignIdentity = QStringLiteral(
"-");
82 bool noCodesignExplicit =
false;
87 for (
int i = 2; i < argc; ++i) {
88 QByteArray argument = QByteArray(argv[i]);
89 if (argument == QByteArray(
"-no-plugins")) {
90 LogDebug() <<
"Argument found:" << argument;
92 }
else if (argument == QByteArray(
"-dmg")) {
93 LogDebug() <<
"Argument found:" << argument;
95 }
else if (argument == QByteArray(
"-no-strip")) {
96 LogDebug() <<
"Argument found:" << argument;
98 }
else if (argument == QByteArray(
"-use-debug-libs")) {
99 LogDebug() <<
"Argument found:" << argument;
102 }
else if (argument.startsWith(QByteArray(
"-verbose"))) {
103 LogDebug() <<
"Argument found:" << argument;
104 int index = argument.indexOf(
"=");
106 int number = argument.mid(index+1).toInt(&ok);
108 LogError() <<
"Could not parse verbose level";
113 }
else if (argument.startsWith(QByteArray(
"-executable"))) {
114 LogDebug() <<
"Argument found:" << argument;
115 int index = argument.indexOf(
'=');
117 LogError() <<
"Missing executable path";
120 additionalExecutables << argument.mid(index+1);
122 }
else if (argument.startsWith(QByteArray(
"-qmldir"))) {
123 LogDebug() <<
"Argument found:" << argument;
124 qmldirArgumentUsed =
true;
125 int index = argument.indexOf(
'=');
127 LogError() <<
"Missing qml directory path";
130 qmlDirs << argument.mid(index+1);
132 }
else if (argument.startsWith(QByteArray(
"-qmlimport"))) {
133 LogDebug() <<
"Argument found:" << argument;
134 int index = argument.indexOf(
'=');
136 LogError() <<
"Missing qml import path";
139 qmlImportPaths << argument.mid(index+1);
141 }
else if (argument.startsWith(QByteArray(
"-libpath"))) {
142 LogDebug() <<
"Argument found:" << argument;
143 int index = argument.indexOf(
'=');
145 LogError() <<
"Missing library search path";
150 }
else if (argument == QByteArray(
"-always-overwrite")) {
151 LogDebug() <<
"Argument found:" << argument;
153 }
else if (argument == QByteArray(
"-no-codesign")) {
154 LogDebug() <<
"Argument found:" << argument;
156 noCodesignExplicit =
true;
157 }
else if (argument.startsWith(QByteArray(
"-codesign"))) {
158 LogDebug() <<
"Argument found:" << argument;
159 if (noCodesignExplicit) {
160 LogError() <<
"Error: -no-codesign cannot be combined with -codesign\n";
163 int index = argument.indexOf(
"=");
164 if (index < 0 || index >= argument.size()) {
165 LogError() <<
"Missing code signing identity";
169 codesignIdentity = argument.mid(index + 1);
171 }
else if (argument.startsWith(QByteArray(
"-sign-for-notarization"))) {
172 LogDebug() <<
"Argument found:" << argument;
173 if (noCodesignExplicit) {
174 LogError() <<
"Error: -no-codesign cannot be combined with -sign-for-notarization\n";
177 int index = argument.indexOf(
"=");
178 if (index < 0 || index >= argument.size()) {
179 LogError() <<
"Missing code signing identity";
185 codesignIdentity = argument.mid(index + 1);
187 }
else if (argument.startsWith(QByteArray(
"-hardened-runtime"))) {
188 LogDebug() <<
"Argument found:" << argument;
190 }
else if (argument.startsWith(QByteArray(
"-timestamp"))) {
191 LogDebug() <<
"Argument found:" << argument;
193 }
else if (argument == QByteArray(
"-appstore-compliant")) {
194 LogDebug() <<
"Argument found:" << argument;
198 }
else if (argument == QByteArray(
"-deploy-framework")) {
199 LogDebug() <<
"Argument found:" << argument;
202 }
else if (argument.startsWith(QByteArray(
"-fs"))) {
203 LogDebug() <<
"Argument found:" << argument;
204 int index = argument.indexOf(
'=');
206 LogError() <<
"Missing filesystem type";
209 filesystem = argument.mid(index+1);
211 }
else if (argument.startsWith(
"-")) {
212 LogError() <<
"Unknown argument" << argument <<
"\n";
217 DeploymentInfo deploymentInfo = deployQtFrameworks(appBundlePath, additionalExecutables, useDebugLibs);
223 fixupFramework(appBundlePath);
226 if (qmlDirs.isEmpty()) {
227 using namespace Qt::StringLiterals;
228 using F = QDirListing::IteratorFlag;
229 QDirListing lister(QDir::currentPath(), QStringList(u"*.qml"_s), F::FilesOnly);
230 if (lister.cbegin() != lister.cend())
231 qmlDirs += QStringLiteral(
".");
234 if (!qmlDirs.isEmpty()) {
235 bool ok = deployQmlImports(appBundlePath, deploymentInfo, qmlDirs, qmlImportPaths);
236 if (!ok && qmldirArgumentUsed)
241 deploymentInfo.deployedFrameworks += findAppFrameworkNames(appBundlePath);
242 deploymentInfo.deployedFrameworks =
243 QSet<QString>(deploymentInfo.deployedFrameworks.begin(),
244 deploymentInfo.deployedFrameworks.end()).values();
250 deploymentInfo.pluginPath = QLibraryInfo::path(QLibraryInfo::PluginsPath);
253 if (deploymentInfo.pluginPath.isEmpty()) {
254 LogError() <<
"Missing Qt plugins path\n";
258 if (!QDir(deploymentInfo.pluginPath).exists()) {
259 LogError() <<
"Plugins path does not exist" << deploymentInfo.pluginPath <<
"\n";
264 Q_ASSERT(!deploymentInfo.pluginPath.isEmpty());
265 if (!deploymentInfo.pluginPath.isEmpty()) {
267 deployPlugins(appBundlePath, deploymentInfo, useDebugLibs);
268 createQtConf(appBundlePath);
273 stripAppBinary(appBundlePath);
276 codesign(codesignIdentity, appBundlePath);
280 createDiskImage(appBundlePath, filesystem);