Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
shared.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3#include <QCoreApplication>
4#include <QString>
5#include <QStringList>
6#include <QDebug>
7#include <iostream>
8#include <utility>
9#include <QProcess>
10#include <QDir>
11#include <QSet>
12#include <QVariant>
13#include <QVariantMap>
14#include <QStack>
15#include <QDirIterator>
16#include <QLibraryInfo>
17#include <QJsonDocument>
18#include <QJsonObject>
19#include <QJsonArray>
20#include <QJsonValue>
21#include <QRegularExpression>
22#include <QSaveFile>
23#include "shared.h"
24
25#ifdef Q_OS_DARWIN
26#include <CoreFoundation/CoreFoundation.h>
27#endif
28
29bool runStripEnabled = true;
31bool runCodesign = true;
34bool hardenedRuntime = false;
35bool secureTimestamp = false;
36bool appstoreCompliant = false;
37int logLevel = 1;
38bool deployFramework = false;
39
40using std::cout;
41using std::endl;
42using namespace Qt::StringLiterals;
43
44bool operator==(const FrameworkInfo &a, const FrameworkInfo &b)
45{
46 return ((a.frameworkPath == b.frameworkPath) && (a.binaryPath == b.binaryPath));
47}
48
49QDebug operator<<(QDebug debug, const FrameworkInfo &info)
50{
51 debug << "Framework name" << info.frameworkName << "\n";
52 debug << "Framework directory" << info.frameworkDirectory << "\n";
53 debug << "Framework path" << info.frameworkPath << "\n";
54 debug << "Binary directory" << info.binaryDirectory << "\n";
55 debug << "Binary name" << info.binaryName << "\n";
56 debug << "Binary path" << info.binaryPath << "\n";
57 debug << "Version" << info.version << "\n";
58 debug << "Install name" << info.installName << "\n";
59 debug << "Deployed install name" << info.deployedInstallName << "\n";
60 debug << "Source file Path" << info.sourceFilePath << "\n";
61 debug << "Framework Destination Directory (relative to bundle)" << info.frameworkDestinationDirectory << "\n";
62 debug << "Binary Destination Directory (relative to bundle)" << info.binaryDestinationDirectory << "\n";
63
64 return debug;
65}
66
67const QString bundleFrameworkDirectory = "Contents/Frameworks";
68
69inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
70{
71 debug << "Application bundle path" << info.path << "\n";
72 debug << "Binary path" << info.binaryPath << "\n";
73 debug << "Additional libraries" << info.libraryPaths << "\n";
74 return debug;
75}
76
77bool copyFilePrintStatus(const QString &from, const QString &to)
78{
79 if (QFile::exists(to)) {
81 QFile(to).remove();
82 } else {
83 qDebug() << "File exists, skip copy:" << to;
84 return false;
85 }
86 }
87
88 if (QFile::copy(from, to)) {
89 QFile dest(to);
90 dest.setPermissions(dest.permissions() | QFile::WriteOwner | QFile::WriteUser);
91 LogNormal() << " copied:" << from;
92 LogNormal() << " to" << to;
93
94 // The source file might not have write permissions set. Set the
95 // write permission on the target file to make sure we can use
96 // install_name_tool on it later.
97 QFile toFile(to);
98 if (toFile.permissions() & QFile::WriteOwner)
99 return true;
100
101 if (!toFile.setPermissions(toFile.permissions() | QFile::WriteOwner)) {
102 LogError() << "Failed to set u+w permissions on target file: " << to;
103 return false;
104 }
105
106 return true;
107 } else {
108 LogError() << "file copy failed from" << from;
109 LogError() << " to" << to;
110 return false;
111 }
112}
113
114bool linkFilePrintStatus(const QString &file, const QString &link)
115{
116 if (QFile::exists(link)) {
117 if (QFile(link).symLinkTarget().isEmpty())
118 LogError() << link << "exists but it's a file.";
119 else
120 LogNormal() << "Symlink exists, skipping:" << link;
121 return false;
122 } else if (QFile::link(file, link)) {
123 LogNormal() << " symlink" << link;
124 LogNormal() << " points to" << file;
125 return true;
126 } else {
127 LogError() << "failed to symlink" << link;
128 LogError() << " to" << file;
129 return false;
130 }
131}
132
133void patch_debugInInfoPlist(const QString &infoPlistPath)
134{
135 // Older versions of qmake may have the "_debug" binary as
136 // the value for CFBundleExecutable. Remove it.
137 if (QFile infoPlist(infoPlistPath); infoPlist.open(QIODevice::ReadOnly)) {
138 QByteArray contents = infoPlist.readAll();
139 infoPlist.close();
140 QSaveFile writableInfoPlist(infoPlistPath);
141 bool success = writableInfoPlist.open(QIODevice::WriteOnly | QIODevice::Truncate);
142 if (success) {
143 contents.replace("_debug", ""); // surely there are no legit uses of "_debug" in an Info.plist
144 writableInfoPlist.write(contents);
145 success = writableInfoPlist.commit();
146 }
147 if (!success) {
148 LogError() << "Failed to write Info.plist file" << infoPlistPath;
149 }
150 } else {
151 LogError() << "Failed to read Info.plist file" << infoPlistPath;
152 }
153}
154
155OtoolInfo findDependencyInfo(const QString &binaryPath)
156{
157 OtoolInfo info;
158 info.binaryPath = binaryPath;
159
160 LogDebug() << "Using otool:";
161 LogDebug() << " inspecting" << binaryPath;
162 QProcess otool;
163 otool.start("otool", QStringList() << "-L" << binaryPath);
164 otool.waitForFinished(-1);
165
166 if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) {
167 LogError() << otool.readAllStandardError();
168 return info;
169 }
170
171 static const QRegularExpression regexp(QStringLiteral(
172 "^\\t(.+) \\‍(compatibility version (\\d+\\.\\d+\\.\\d+), "
173 "current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\‍)$"));
174
175 QString output = otool.readAllStandardOutput();
176 QStringList outputLines = output.split("\n", Qt::SkipEmptyParts);
177 if (outputLines.size() < 2) {
178 LogError() << "Could not parse otool output:" << output;
179 return info;
180 }
181
182 outputLines.removeFirst(); // remove line containing the binary path
183 if (binaryPath.contains(".framework/") || binaryPath.endsWith(".dylib")) {
184 const auto match = regexp.match(outputLines.constFirst());
185 if (match.hasMatch()) {
186 QString installname = match.captured(1);
187 if (QFileInfo(binaryPath).fileName() == QFileInfo(installname).fileName()) {
188 info.installName = installname;
189 info.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
190 info.currentVersion = QVersionNumber::fromString(match.captured(3));
191 outputLines.removeFirst();
192 } else {
193 info.installName = binaryPath;
194 }
195 } else {
196 LogDebug() << "Could not parse otool output line:" << outputLines.constFirst();
197 outputLines.removeFirst();
198 }
199 }
200
201 for (const QString &outputLine : outputLines) {
202 const auto match = regexp.match(outputLine);
203 if (match.hasMatch()) {
204 if (match.captured(1) == info.installName)
205 continue; // Another arch reference to the same binary
206 DylibInfo dylib;
207 dylib.binaryPath = match.captured(1);
208 dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
209 dylib.currentVersion = QVersionNumber::fromString(match.captured(3));
210 info.dependencies << dylib;
211 } else {
212 LogDebug() << "Could not parse otool output line:" << outputLine;
213 }
214 }
215
216 return info;
217}
218
219FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
220{
221 FrameworkInfo info;
222 QString trimmed = line.trimmed();
223
224 if (trimmed.isEmpty())
225 return info;
226
227 // Don't deploy system libraries.
228 if (trimmed.startsWith("/System/Library/") ||
229 (trimmed.startsWith("/usr/lib/") && trimmed.contains("libQt") == false) // exception for libQtuitools and libQtlucene
230 || trimmed.startsWith("@executable_path") || trimmed.startsWith("@loader_path"))
231 return info;
232
233 // Resolve rpath relative libraries.
234 if (trimmed.startsWith("@rpath/")) {
235 QString rpathRelativePath = trimmed.mid(QStringLiteral("@rpath/").length());
236 bool foundInsideBundle = false;
237 for (const QString &rpath : std::as_const(rpaths)) {
238 QString path = QDir::cleanPath(rpath + "/" + rpathRelativePath);
239 // Skip paths already inside the bundle.
240 if (!appBundlePath.isEmpty()) {
241 if (QDir::isAbsolutePath(appBundlePath)) {
242 if (path.startsWith(QDir::cleanPath(appBundlePath) + "/")) {
243 foundInsideBundle = true;
244 continue;
245 }
246 } else {
247 if (path.startsWith(QDir::cleanPath(QDir::currentPath() + "/" + appBundlePath) + "/")) {
248 foundInsideBundle = true;
249 continue;
250 }
251 }
252 }
253 // Try again with substituted rpath.
254 FrameworkInfo resolvedInfo = parseOtoolLibraryLine(path, appBundlePath, rpaths, useDebugLibs);
255 if (!resolvedInfo.frameworkName.isEmpty() && QFile::exists(resolvedInfo.frameworkPath)) {
256 resolvedInfo.rpathUsed = rpath;
257 resolvedInfo.installName = trimmed;
258 return resolvedInfo;
259 }
260 }
261 if (!rpaths.isEmpty() && !foundInsideBundle) {
262 LogError() << "Cannot resolve rpath" << trimmed;
263 LogError() << " using" << rpaths;
264 }
265 return info;
266 }
267
268 enum State {QtPath, FrameworkName, DylibName, Version, FrameworkBinary, End};
269 State state = QtPath;
270 int part = 0;
271 QString name;
272 QString qtPath;
273 QString suffix = useDebugLibs ? "_debug" : "";
274
275 // Split the line into [Qt-path]/lib/qt[Module].framework/Versions/[Version]/
276 QStringList parts = trimmed.split("/");
277 while (part < parts.count()) {
278 const QString currentPart = parts.at(part).simplified();
279 ++part;
280 if (currentPart == "")
281 continue;
282
283 if (state == QtPath) {
284 // Check for library name part
285 if (part < parts.count() && parts.at(part).contains(".dylib")) {
286 info.frameworkDirectory += "/" + QString(qtPath + currentPart + "/").simplified();
287 state = DylibName;
288 continue;
289 } else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
290 info.frameworkDirectory += "/" + QString(qtPath + "lib/").simplified();
291 state = FrameworkName;
292 continue;
293 } else if (trimmed.startsWith('/') == false) { // If the line does not contain a full path, the app is using a binary Qt package.
294 QStringList partsCopy = parts;
295 partsCopy.removeLast();
296 for (QString &path : librarySearchPath) {
297 if (!path.endsWith("/"))
298 path += '/';
299 QString nameInPath = path + parts.join(u'/');
300 if (QFile::exists(nameInPath)) {
301 info.frameworkDirectory = path + partsCopy.join(u'/');
302 break;
303 }
304 }
305 if (currentPart.contains(".framework")) {
306 if (info.frameworkDirectory.isEmpty())
307 info.frameworkDirectory = "/Library/Frameworks/" + partsCopy.join(u'/');
308 if (!info.frameworkDirectory.endsWith("/"))
309 info.frameworkDirectory += "/";
310 state = FrameworkName;
311 --part;
312 continue;
313 } else if (currentPart.contains(".dylib")) {
314 if (info.frameworkDirectory.isEmpty())
315 info.frameworkDirectory = "/usr/lib/" + partsCopy.join(u'/');
316 if (!info.frameworkDirectory.endsWith("/"))
317 info.frameworkDirectory += "/";
318 state = DylibName;
319 --part;
320 continue;
321 }
322 }
323 qtPath += (currentPart + "/");
324
325 } if (state == FrameworkName) {
326 // remove ".framework"
327 name = currentPart;
328 name.chop(QString(".framework").length());
329 info.isDylib = false;
330 info.frameworkName = currentPart;
331 state = Version;
332 ++part;
333 continue;
334 } if (state == DylibName) {
335 name = currentPart;
336 info.isDylib = true;
337 info.frameworkName = name;
338 info.binaryName = name.contains(suffix) ? name : name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.'));
339 info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName;
340 info.frameworkPath = info.frameworkDirectory + info.binaryName;
341 info.sourceFilePath = info.frameworkPath;
342 info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/";
343 info.binaryDestinationDirectory = info.frameworkDestinationDirectory;
344 info.binaryDirectory = info.frameworkDirectory;
345 info.binaryPath = info.frameworkPath;
346 state = End;
347 ++part;
348 continue;
349 } else if (state == Version) {
350 info.version = currentPart;
351 info.binaryDirectory = "Versions/" + info.version;
352 info.frameworkPath = info.frameworkDirectory + info.frameworkName;
353 info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName;
354 info.binaryDestinationDirectory = info.frameworkDestinationDirectory + "/" + info.binaryDirectory;
355 state = FrameworkBinary;
356 } else if (state == FrameworkBinary) {
357 info.binaryName = currentPart.contains(suffix) ? currentPart : currentPart + suffix;
358 info.binaryPath = "/" + info.binaryDirectory + "/" + info.binaryName;
359 info.deployedInstallName = "@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath;
360 info.sourceFilePath = info.frameworkPath + info.binaryPath;
361 state = End;
362 } else if (state == End) {
363 break;
364 }
365 }
366
367 if (!info.sourceFilePath.isEmpty() && QFile::exists(info.sourceFilePath)) {
368 info.installName = findDependencyInfo(info.sourceFilePath).installName;
369 if (info.installName.startsWith("@rpath/"))
370 info.deployedInstallName = info.installName;
371 }
372
373 return info;
374}
375
376QString findAppBinary(const QString &appBundlePath)
377{
378 QString binaryPath;
379
380#ifdef Q_OS_DARWIN
381 CFStringRef bundlePath = appBundlePath.toCFString();
382 CFURLRef bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath,
383 kCFURLPOSIXPathStyle, true);
384 CFRelease(bundlePath);
385 CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, bundleURL);
386 if (bundle) {
387 CFURLRef executableURL = CFBundleCopyExecutableURL(bundle);
388 if (executableURL) {
389 CFURLRef absoluteExecutableURL = CFURLCopyAbsoluteURL(executableURL);
390 if (absoluteExecutableURL) {
391 CFStringRef executablePath = CFURLCopyFileSystemPath(absoluteExecutableURL,
392 kCFURLPOSIXPathStyle);
393 if (executablePath) {
394 binaryPath = QString::fromCFString(executablePath);
395 CFRelease(executablePath);
396 }
397 CFRelease(absoluteExecutableURL);
398 }
399 CFRelease(executableURL);
400 }
401 CFRelease(bundle);
402 }
403 CFRelease(bundleURL);
404#endif
405
406 if (QFile::exists(binaryPath))
407 return binaryPath;
408 LogError() << "Could not find bundle binary for" << appBundlePath;
409 return QString();
410}
411
412QStringList findAppFrameworkNames(const QString &appBundlePath)
413{
414 QStringList frameworks;
415
416 // populate the frameworks list with QtFoo.framework etc,
417 // as found in /Contents/Frameworks/
418 QString searchPath = appBundlePath + "/Contents/Frameworks/";
419 QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"),
420 QDir::Dirs | QDir::NoSymLinks);
421 while (iter.hasNext()) {
422 iter.next();
423 frameworks << iter.fileInfo().fileName();
424 }
425
426 return frameworks;
427}
428
429QStringList findAppFrameworkPaths(const QString &appBundlePath)
430{
431 QStringList frameworks;
432 QString searchPath = appBundlePath + "/Contents/Frameworks/";
433 QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"),
434 QDir::Dirs | QDir::NoSymLinks);
435 while (iter.hasNext()) {
436 iter.next();
437 frameworks << iter.fileInfo().filePath();
438 }
439
440 return frameworks;
441}
442
443QStringList findAppLibraries(const QString &appBundlePath)
444{
445 QStringList result;
446 // dylibs
447 QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*.dylib") << QString::fromLatin1("*.so"),
448 QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
449 while (iter.hasNext()) {
450 iter.next();
451 result << iter.fileInfo().filePath();
452 }
453 return result;
454}
455
456QStringList findAppBundleFiles(const QString &appBundlePath, bool absolutePath = false)
457{
458 QStringList result;
459
460 QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*"),
461 QDir::Files, QDirIterator::Subdirectories);
462
463 while (iter.hasNext()) {
464 iter.next();
465 if (iter.fileInfo().isSymLink())
466 continue;
467 result << (absolutePath ? iter.fileInfo().absoluteFilePath() : iter.fileInfo().filePath());
468 }
469
470 return result;
471}
472
473QString findEntitlementsFile(const QString& path)
474{
475 QDirIterator iter(path, QStringList() << QString::fromLatin1("*.entitlements"),
476 QDir::Files, QDirIterator::Subdirectories);
477
478 while (iter.hasNext()) {
479 iter.next();
480 if (iter.fileInfo().isSymLink())
481 continue;
482
483 //return the first entitlements file - only one is used for signing anyway
484 return iter.fileInfo().absoluteFilePath();
485 }
486
487 return QString();
488}
489
490QList<FrameworkInfo> getQtFrameworks(const QList<DylibInfo> &dependencies, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
491{
492 QList<FrameworkInfo> libraries;
493 for (const DylibInfo &dylibInfo : dependencies) {
494 FrameworkInfo info = parseOtoolLibraryLine(dylibInfo.binaryPath, appBundlePath, rpaths, useDebugLibs);
495 if (info.frameworkName.isEmpty() == false) {
496 LogDebug() << "Adding framework:";
497 LogDebug() << info;
498 libraries.append(info);
499 }
500 }
501 return libraries;
502}
503
504QString resolveDyldPrefix(const QString &path, const QString &loaderPath, const QString &executablePath)
505{
506 if (path.startsWith('@')) {
507 if (path.startsWith(QStringLiteral("@executable_path/"))) {
508 // path relative to bundle executable dir
509 if (QDir::isAbsolutePath(executablePath)) {
510 return QDir::cleanPath(QFileInfo(executablePath).path() + path.mid(QStringLiteral("@executable_path").length()));
511 } else {
512 return QDir::cleanPath(QDir::currentPath() + "/" +
513 QFileInfo(executablePath).path() + path.mid(QStringLiteral("@executable_path").length()));
514 }
515 } else if (path.startsWith(QStringLiteral("@loader_path"))) {
516 // path relative to loader dir
517 if (QDir::isAbsolutePath(loaderPath)) {
518 return QDir::cleanPath(QFileInfo(loaderPath).path() + path.mid(QStringLiteral("@loader_path").length()));
519 } else {
520 return QDir::cleanPath(QDir::currentPath() + "/" +
521 QFileInfo(loaderPath).path() + path.mid(QStringLiteral("@loader_path").length()));
522 }
523 } else {
524 LogError() << "Unexpected prefix" << path;
525 }
526 }
527 return path;
528}
529
530QList<QString> getBinaryRPaths(const QString &path, bool resolve = true, QString executablePath = QString())
531{
532 QList<QString> rpaths;
533
534 QProcess otool;
535 otool.start("otool", QStringList() << "-l" << path);
536 otool.waitForFinished();
537
538 if (otool.exitCode() != 0) {
539 LogError() << otool.readAllStandardError();
540 }
541
542 if (resolve && executablePath.isEmpty()) {
543 executablePath = path;
544 }
545
546 QString output = otool.readAllStandardOutput();
547 QStringList outputLines = output.split("\n");
548
549 for (auto i = outputLines.cbegin(), end = outputLines.cend(); i != end; ++i) {
550 if (i->contains("cmd LC_RPATH") && ++i != end &&
551 i->contains("cmdsize") && ++i != end) {
552 const QString &rpathCmd = *i;
553 int pathStart = rpathCmd.indexOf("path ");
554 int pathEnd = rpathCmd.indexOf(" (");
555 if (pathStart >= 0 && pathEnd >= 0 && pathStart < pathEnd) {
556 QString rpath = rpathCmd.mid(pathStart + 5, pathEnd - pathStart - 5);
557 if (resolve) {
558 rpaths << resolveDyldPrefix(rpath, path, executablePath);
559 } else {
560 rpaths << rpath;
561 }
562 }
563 }
564 }
565
566 return rpaths;
567}
568
569QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
570{
571 const OtoolInfo info = findDependencyInfo(path);
572 QList<QString> allRPaths = rpaths + getBinaryRPaths(path);
573 allRPaths.removeDuplicates();
574 return getQtFrameworks(info.dependencies, appBundlePath, allRPaths, useDebugLibs);
575}
576
577QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
578{
579 QList<FrameworkInfo> result;
580 QSet<QString> existing;
581 for (const QString &path : paths) {
582 for (const FrameworkInfo &info : getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) {
583 if (!existing.contains(info.frameworkPath)) { // avoid duplicates
584 existing.insert(info.frameworkPath);
585 result << info;
586 }
587 }
588 }
589 return result;
590}
591
592QStringList getBinaryDependencies(const QString executablePath,
593 const QString &path,
594 const QList<QString> &additionalBinariesContainingRpaths)
595{
596 QStringList binaries;
597
598 const auto dependencies = findDependencyInfo(path).dependencies;
599
600 bool rpathsLoaded = false;
601 QList<QString> rpaths;
602
603 // return bundle-local dependencies. (those starting with @executable_path)
604 for (const DylibInfo &info : dependencies) {
605 QString trimmedLine = info.binaryPath;
606 if (trimmedLine.startsWith("@executable_path/")) {
607 QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length()));
608 if (binary != path)
609 binaries.append(binary);
610 } else if (trimmedLine.startsWith("@loader_path/")) {
611 QString binary = QDir::cleanPath(QFileInfo(path).path() + "/" + trimmedLine.mid(QStringLiteral("@loader_path/").length()));
612 if (binary != path)
613 binaries.append(binary);
614 } else if (trimmedLine.startsWith("@rpath/")) {
615 if (!rpathsLoaded) {
616 rpaths = getBinaryRPaths(path, true, executablePath);
617 for (const QString &binaryPath : additionalBinariesContainingRpaths)
618 rpaths += getBinaryRPaths(binaryPath, true);
619 rpaths.removeDuplicates();
620 rpathsLoaded = true;
621 }
622 bool resolved = false;
623 for (const QString &rpath : std::as_const(rpaths)) {
624 QString binary = QDir::cleanPath(rpath + "/" + trimmedLine.mid(QStringLiteral("@rpath/").length()));
625 LogDebug() << "Checking for" << binary;
626 if (QFile::exists(binary)) {
627 binaries.append(binary);
628 resolved = true;
629 break;
630 }
631 }
632 if (!resolved && !rpaths.isEmpty()) {
633 LogError() << "Cannot resolve rpath" << trimmedLine;
634 LogError() << " using" << rpaths;
635 }
636 }
637 }
638
639 return binaries;
640}
641
642// copies everything _inside_ sourcePath to destinationPath
643bool recursiveCopy(const QString &sourcePath, const QString &destinationPath,
644 const QRegularExpression &ignoreRegExp = QRegularExpression())
645{
646 const QDir sourceDir(sourcePath);
647 if (!sourceDir.exists())
648 return false;
649 QDir().mkpath(destinationPath);
650
651 LogNormal() << "copy:" << sourcePath << destinationPath;
652
653 const QStringList files = sourceDir.entryList(QStringList() << "*", QDir::Files | QDir::NoDotAndDotDot);
654 const bool hasValidRegExp = ignoreRegExp.isValid() && ignoreRegExp.pattern().length() > 0;
655 for (const QString &file : files) {
656 if (hasValidRegExp && ignoreRegExp.match(file).hasMatch())
657 continue;
658 const QString fileSourcePath = sourcePath + "/" + file;
659 const QString fileDestinationPath = destinationPath + "/" + file;
660 copyFilePrintStatus(fileSourcePath, fileDestinationPath);
661 }
662
663 const QStringList subdirs = sourceDir.entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot);
664 for (const QString &dir : subdirs) {
665 recursiveCopy(sourcePath + "/" + dir, destinationPath + "/" + dir);
666 }
667 return true;
668}
669
670void recursiveCopyAndDeploy(const QString &appBundlePath, const QList<QString> &rpaths, const QString &sourcePath, const QString &destinationPath)
671{
672 QDir().mkpath(destinationPath);
673
674 LogNormal() << "copy:" << sourcePath << destinationPath;
675
676 const QDir sourceDir(sourcePath);
677
678 const QStringList files = sourceDir.entryList(QStringList() << QStringLiteral("*"), QDir::Files | QDir::NoDotAndDotDot);
679 for (const QString &file : files) {
680 if (file.endsWith("_debug.dylib"))
681 continue; // Skip debug versions
682
683 if (file.endsWith(".qrc"))
684 continue;
685
686 const QString fileSourcePath = sourcePath + u'/' + file;
687
688 if (file.endsWith(QStringLiteral(".dylib"))) {
689 // App store code signing rules forbids code binaries in Contents/Resources/,
690 // which poses a problem for deploying mixed .qml/.dylib Qt Quick imports.
691 // Solve this by placing the dylibs in Contents/PlugIns/quick, and then
692 // creting a symlink to there from the Qt Quick import in Contents/Resources/.
693 //
694 // Example:
695 // MyApp.app/Contents/Resources/qml/QtQuick/Controls/libqtquickcontrolsplugin.dylib ->
696 // ../../../../PlugIns/quick/libqtquickcontrolsplugin.dylib
697 //
698
699 // The .dylib destination path:
700 QString fileDestinationDir = appBundlePath + QStringLiteral("/Contents/PlugIns/quick/");
701 QDir().mkpath(fileDestinationDir);
702 QString fileDestinationPath = fileDestinationDir + file;
703
704 // The .dylib symlink destination path:
705 QString linkDestinationPath = destinationPath + u'/' + file;
706
707 // The (relative) link; with a correct number of "../"'s.
708 QString linkPath = QStringLiteral("PlugIns/quick/") + file;
709 int cdupCount = linkDestinationPath.count(QStringLiteral("/")) - appBundlePath.count(QStringLiteral("/"));
710 for (int i = 0; i < cdupCount - 2; ++i)
711 linkPath.prepend("../");
712
713 if (copyFilePrintStatus(fileSourcePath, fileDestinationPath)) {
714 linkFilePrintStatus(linkPath, linkDestinationPath);
715
716 runStrip(fileDestinationPath);
717 bool useDebugLibs = false;
718 bool useLoaderPath = false;
719 QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, appBundlePath, rpaths, useDebugLibs);
720 deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath);
721 }
722 } else {
723 QString fileDestinationPath = destinationPath + u'/' + file;
724 copyFilePrintStatus(fileSourcePath, fileDestinationPath);
725 }
726 }
727
728 const QStringList subdirs = sourceDir.entryList(QStringList() << QStringLiteral("*"), QDir::Dirs | QDir::NoDotAndDotDot);
729 for (const QString &dir : subdirs) {
730 if (dir.endsWith(".dSYM"))
731 continue;
732
733 recursiveCopyAndDeploy(appBundlePath, rpaths, sourcePath + u'/' + dir, destinationPath + u'/' + dir);
734 }
735}
736
737QString copyDylib(const FrameworkInfo &framework, const QString path)
738{
739 if (!QFile::exists(framework.sourceFilePath)) {
740 LogError() << "no file at" << framework.sourceFilePath;
741 return QString();
742 }
743
744 // Construct destination paths. The full path typically looks like
745 // MyApp.app/Contents/Frameworks/libfoo.dylib
746 QString dylibDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory;
747 QString dylibDestinationBinaryPath = dylibDestinationDirectory + u'/' + framework.binaryName;
748
749 // Create destination directory
750 if (!QDir().mkpath(dylibDestinationDirectory)) {
751 LogError() << "could not create destination directory" << dylibDestinationDirectory;
752 return QString();
753 }
754
755 // Return if the dylib has already been deployed
756 if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled)
757 return dylibDestinationBinaryPath;
758
759 // Copy dylib binary
760 copyFilePrintStatus(framework.sourceFilePath, dylibDestinationBinaryPath);
761 return dylibDestinationBinaryPath;
762}
763
764QString copyFramework(const FrameworkInfo &framework, const QString path)
765{
766 if (!QFile::exists(framework.sourceFilePath)) {
767 LogError() << "no file at" << framework.sourceFilePath;
768 return QString();
769 }
770
771 // Construct destination paths. The full path typically looks like
772 // MyApp.app/Contents/Frameworks/Foo.framework/Versions/5/QtFoo
773 QString frameworkDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory;
774 QString frameworkBinaryDestinationDirectory = frameworkDestinationDirectory + u'/' + framework.binaryDirectory;
775 QString frameworkDestinationBinaryPath = frameworkBinaryDestinationDirectory + u'/' + framework.binaryName;
776
777 // Return if the framework has aleardy been deployed
778 if (QDir(frameworkDestinationDirectory).exists() && !alwaysOwerwriteEnabled)
779 return QString();
780
781 // Create destination directory
782 if (!QDir().mkpath(frameworkBinaryDestinationDirectory)) {
783 LogError() << "could not create destination directory" << frameworkBinaryDestinationDirectory;
784 return QString();
785 }
786
787 // Now copy the framework. Some parts should be left out (headers/, .prl files).
788 // Some parts should be included (Resources/, symlink structure). We want this
789 // function to make as few assumptions about the framework as possible while at
790 // the same time producing a codesign-compatible framework.
791
792 // Copy framework binary
793 copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath);
794
795 // Copy Resources/, Libraries/ and Helpers/
796 const QString resourcesSourcePath = framework.frameworkPath + "/Resources";
797 const QString resourcesDestinationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Resources";
798 // Ignore *.prl files that are in the Resources directory
799 recursiveCopy(resourcesSourcePath, resourcesDestinationPath,
800 QRegularExpression("\\A(?:[^/]*\\.prl)\\z"));
801 const QString librariesSourcePath = framework.frameworkPath + "/Libraries";
802 const QString librariesDestinationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Libraries";
803 bool createdLibraries = recursiveCopy(librariesSourcePath, librariesDestinationPath);
804 const QString helpersSourcePath = framework.frameworkPath + "/Helpers";
805 const QString helpersDestinationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Helpers";
806 bool createdHelpers = recursiveCopy(helpersSourcePath, helpersDestinationPath);
807
808 // Create symlink structure. Links at the framework root point to Versions/Current/
809 // which again points to the actual version:
810 // QtFoo.framework/QtFoo -> Versions/Current/QtFoo
811 // QtFoo.framework/Resources -> Versions/Current/Resources
812 // QtFoo.framework/Versions/Current -> 5
813 linkFilePrintStatus("Versions/Current/" + framework.binaryName, frameworkDestinationDirectory + "/" + framework.binaryName);
814 linkFilePrintStatus("Versions/Current/Resources", frameworkDestinationDirectory + "/Resources");
815 if (createdLibraries)
816 linkFilePrintStatus("Versions/Current/Libraries", frameworkDestinationDirectory + "/Libraries");
817 if (createdHelpers)
818 linkFilePrintStatus("Versions/Current/Helpers", frameworkDestinationDirectory + "/Helpers");
819 linkFilePrintStatus(framework.version, frameworkDestinationDirectory + "/Versions/Current");
820
821 // Correct Info.plist location for frameworks produced by older versions of qmake
822 // Contents/Info.plist should be Versions/5/Resources/Info.plist
823 const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist";
824 const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist";
825 if (QFile::exists(legacyInfoPlistPath)) {
826 copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
827 patch_debugInInfoPlist(correctInfoPlistPath);
828 }
829 return frameworkDestinationBinaryPath;
830}
831
832void runInstallNameTool(QStringList options)
833{
834 QProcess installNametool;
835 installNametool.start("install_name_tool", options);
836 installNametool.waitForFinished();
837 if (installNametool.exitCode() != 0) {
838 LogError() << installNametool.readAllStandardError();
839 LogError() << installNametool.readAllStandardOutput();
840 }
841}
842
843void changeIdentification(const QString &id, const QString &binaryPath)
844{
845 LogDebug() << "Using install_name_tool:";
846 LogDebug() << " change identification in" << binaryPath;
847 LogDebug() << " to" << id;
848 runInstallNameTool(QStringList() << "-id" << id << binaryPath);
849}
850
851void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework, const QStringList &binaryPaths, bool useLoaderPath)
852{
853 const QString absBundlePath = QFileInfo(bundlePath).absoluteFilePath();
854 for (const QString &binary : binaryPaths) {
855 QString deployedInstallName;
856 if (useLoaderPath) {
857 deployedInstallName = "@loader_path/"_L1
858 + QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + u'/' + framework.binaryDestinationDirectory + u'/' + framework.binaryName);
859 } else {
860 deployedInstallName = framework.deployedInstallName;
861 }
862 changeInstallName(framework.installName, deployedInstallName, binary);
863 // Workaround for the case when the library ID name is a symlink, while the dependencies
864 // specified using the canonical path to the library (QTBUG-56814)
865 QFileInfo fileInfo= QFileInfo(framework.installName);
866 QString canonicalInstallName = fileInfo.canonicalFilePath();
867 if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) {
868 changeInstallName(canonicalInstallName, deployedInstallName, binary);
869 // some libraries' inner dependencies (such as ffmpeg, nettle) use symbol link (QTBUG-100093)
870 QString innerDependency = fileInfo.canonicalPath() + "/" + fileInfo.fileName();
871 if (innerDependency != canonicalInstallName && innerDependency != framework.installName) {
872 changeInstallName(innerDependency, deployedInstallName, binary);
873 }
874 }
875 }
876}
877
878void addRPath(const QString &rpath, const QString &binaryPath)
879{
880 runInstallNameTool(QStringList() << "-add_rpath" << rpath << binaryPath);
881}
882
883void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QString &binaryPath, bool useLoaderPath)
884{
885 const QString absFrameworksPath = QFileInfo(bundlePath).absoluteFilePath()
886 + "/Contents/Frameworks"_L1;
887 const QString relativeFrameworkPath = QFileInfo(binaryPath).absoluteDir().relativeFilePath(absFrameworksPath);
888 const QString loaderPathToFrameworks = "@loader_path/"_L1 + relativeFrameworkPath;
889 bool rpathToFrameworksFound = false;
890 QStringList args;
891 QList<QString> binaryRPaths = getBinaryRPaths(binaryPath, false);
892 for (const QString &rpath : std::as_const(binaryRPaths)) {
893 if (rpath == "@executable_path/../Frameworks" ||
894 rpath == loaderPathToFrameworks) {
895 rpathToFrameworksFound = true;
896 continue;
897 }
898 if (rpaths.contains(resolveDyldPrefix(rpath, binaryPath, binaryPath))) {
899 if (!args.contains(rpath))
900 args << "-delete_rpath" << rpath;
901 }
902 }
903 if (!args.length()) {
904 return;
905 }
906 if (!rpathToFrameworksFound) {
907 if (!useLoaderPath) {
908 args << "-add_rpath" << "@executable_path/../Frameworks";
909 } else {
910 args << "-add_rpath" << loaderPathToFrameworks;
911 }
912 }
913 LogDebug() << "Using install_name_tool:";
914 LogDebug() << " change rpaths in" << binaryPath;
915 LogDebug() << " using" << args;
916 runInstallNameTool(QStringList() << args << binaryPath);
917}
918
919void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QStringList &binaryPaths, bool useLoaderPath)
920{
921 for (const QString &binary : binaryPaths) {
922 deployRPaths(bundlePath, rpaths, binary, useLoaderPath);
923 }
924}
925
926void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath)
927{
928 if (newName == oldName) {
929 LogDebug() << "Skipping install_name_tool for" << binaryPath
930 << "as name is already" << newName;
931 return;
932 }
933 LogDebug() << "Using install_name_tool:";
934 LogDebug() << " in" << binaryPath;
935 LogDebug() << " change reference" << oldName;
936 LogDebug() << " to" << newName;
937 runInstallNameTool(QStringList() << "-change" << oldName << newName << binaryPath);
938}
939
940void runStrip(const QString &binaryPath)
941{
942 if (runStripEnabled == false)
943 return;
944
945 LogDebug() << "Using strip:";
946 LogDebug() << " stripped" << binaryPath;
947 QProcess strip;
948 strip.start("strip", QStringList() << "-x" << binaryPath);
949 strip.waitForFinished();
950 if (strip.exitCode() != 0) {
951 LogError() << strip.readAllStandardError();
952 LogError() << strip.readAllStandardOutput();
953 }
954}
955
956void stripAppBinary(const QString &bundlePath)
957{
958 runStrip(findAppBinary(bundlePath));
959}
960
961bool DeploymentInfo::containsModule(const QString &module, const QString &libInFix) const
962{
963 // Check for framework first
964 if (deployedFrameworks.contains("Qt"_L1 + module + libInFix + ".framework"_L1))
965 return true;
966 // Check for dylib
967 const QRegularExpression dylibRegExp("libQt[0-9]+"_L1
968 + module + libInFix
969 + (isDebug ? "_debug" : "")
970 + ".[0-9]+.dylib"_L1);
971 return deployedFrameworks.filter(dylibRegExp).size() > 0;
972}
973
974/*
975 Deploys the listed frameworks into an app bundle.
976 The frameworks are searched for dependencies, which are also deployed.
977 (deploying Qt3Support will also deploy QtNetwork and QtSql for example.)
978 Returns a DeploymentInfo structure containing the Qt path used and a
979 a list of actually deployed frameworks.
980*/
981DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
982 const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs,
983 bool useLoaderPath)
984{
985 LogNormal();
986 LogNormal() << "Deploying Qt frameworks found inside:" << binaryPaths;
987 QStringList copiedFrameworks;
988 DeploymentInfo deploymentInfo;
989 deploymentInfo.useLoaderPath = useLoaderPath;
990 deploymentInfo.isFramework = bundlePath.contains(".framework");
991 deploymentInfo.isDebug = false;
992 QList<QString> rpathsUsed;
993
994 while (frameworks.isEmpty() == false) {
995 const FrameworkInfo framework = frameworks.takeFirst();
996 copiedFrameworks.append(framework.frameworkName);
997
998 // If a single dependency has the _debug suffix, we treat that as
999 // the whole deployment being a debug deployment, including deploying
1000 // the debug version of plugins.
1001 if (framework.isDebugLibrary())
1002 deploymentInfo.isDebug = true;
1003
1004 if (deploymentInfo.qtPath.isNull())
1005 deploymentInfo.qtPath = QLibraryInfo::paths(QLibraryInfo::PrefixPath).value(0);
1006
1007 if (framework.frameworkDirectory.startsWith(bundlePath)) {
1008 LogError() << framework.frameworkName << "already deployed, skipping.";
1009 continue;
1010 }
1011
1012 if (!framework.rpathUsed.isEmpty() && !rpathsUsed.contains(framework.rpathUsed)) {
1013 rpathsUsed.append(framework.rpathUsed);
1014 }
1015
1016 // To properly find all dependencies of the current framework / library further down in
1017 // getQtFrameworks, we need to get its rpaths, resolve them in the context of its original
1018 // location before it is copied, and add them as candidate rpaths.
1019 //
1020 // This is necessary to handle cases like
1021 // (1) QtNetwork.framework -> (2) libbrotlidec.dylib -> (3) libbrotlicommon.1.dylib
1022 // to correctly resolve the path to (3) when it is referenced as
1023 // '@rpath/libbrotlicommon.1.dylib' and (2) has an LC_RPATH of '@loader_path/../lib', and
1024 // no other absolute rpaths. So the '@loader_path/../lib' will be resolved relative
1025 // to (2)'s original location and its LC_RPATH.
1026 //
1027 // Otherwise we'd only have the Qt prefix and the current bundle app dir as rpath
1028 // candidates, and once (2) is copied into the app bundle, there's no way
1029 // '@rpath/libbrotlicommon.1.dylib' could resolve to the real path on disk from the two
1030 // candidates above.
1031 if (!framework.sourceFilePath.isEmpty()) {
1032 const QList<QString> sourceRPaths = getBinaryRPaths(framework.sourceFilePath, true);
1033 for (const QString &sourceRPath : sourceRPaths) {
1034 const QDir sourceRPathDir(sourceRPath);
1035 if (sourceRPathDir.exists() && !rpathsUsed.contains(sourceRPath)) {
1036 rpathsUsed.append(sourceRPath);
1037 }
1038 }
1039 }
1040
1041 // Copy the framework/dylib to the app bundle.
1042 const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
1043 : copyFramework(framework, bundlePath);
1044
1045 // Install_name_tool the new id into the binaries
1046 changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath);
1047
1048 // Skip the rest if already was deployed.
1049 if (deployedBinaryPath.isNull())
1050 continue;
1051
1052 runStrip(deployedBinaryPath);
1053
1054 // Install_name_tool it a new id.
1055 if (!framework.rpathUsed.length()) {
1056 changeIdentification(framework.deployedInstallName, deployedBinaryPath);
1057 }
1058
1059 // Check for framework dependencies
1060 QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
1061
1062 for (const FrameworkInfo &dependency : dependencies) {
1063 if (dependency.rpathUsed.isEmpty()) {
1064 changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath);
1065 } else if (!rpathsUsed.contains(dependency.rpathUsed)) {
1066 rpathsUsed.append(dependency.rpathUsed);
1067 }
1068
1069 // Deploy framework if necessary.
1070 if (copiedFrameworks.contains(dependency.frameworkName) == false && frameworks.contains(dependency) == false) {
1071 frameworks.append(dependency);
1072 }
1073 }
1074 }
1075 deploymentInfo.deployedFrameworks = copiedFrameworks;
1076 deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath);
1077 deploymentInfo.rpathsUsed += rpathsUsed;
1078 deploymentInfo.rpathsUsed.removeDuplicates();
1079 return deploymentInfo;
1080}
1081
1082DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs)
1083{
1084 ApplicationBundleInfo applicationBundle;
1085 applicationBundle.path = appBundlePath;
1086 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1087 applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
1088 QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
1089 << additionalExecutables;
1090
1091 QList<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true);
1092 allLibraryPaths.append(QLibraryInfo::paths(QLibraryInfo::LibrariesPath));
1093 allLibraryPaths.removeDuplicates();
1094
1095 QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
1096 if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) {
1097 LogWarning();
1098 LogWarning() << "Could not find any external Qt frameworks to deploy in" << appBundlePath;
1099 LogWarning() << "Perhaps macdeployqt was already used on" << appBundlePath << "?";
1100 LogWarning() << "If so, you will need to rebuild" << appBundlePath << "before trying again.";
1101 return DeploymentInfo();
1102 } else {
1103 return deployQtFrameworks(frameworks, applicationBundle.path, allBinaryPaths, useDebugLibs, !additionalExecutables.isEmpty());
1104 }
1105}
1106
1107QString getLibInfix(const QStringList &deployedFrameworks)
1108{
1109 QString libInfix;
1110 for (const QString &framework : deployedFrameworks) {
1111 if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework")) &&
1112 !framework.contains(QStringLiteral("5Compat"))) {
1113 Q_ASSERT(framework.length() >= 16);
1114 // 16 == "QtCore" + ".framework"
1115 const int lengthOfLibInfix = framework.length() - 16;
1116 if (lengthOfLibInfix)
1117 libInfix = framework.mid(6, lengthOfLibInfix);
1118 break;
1119 }
1120 }
1121 return libInfix;
1122}
1123
1124void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pluginSourcePath,
1125 const QString pluginDestinationPath, DeploymentInfo deploymentInfo, bool useDebugLibs)
1126{
1127 LogNormal() << "Deploying plugins from" << pluginSourcePath;
1128
1129 if (!pluginSourcePath.contains(deploymentInfo.pluginPath))
1130 return;
1131
1132 // Plugin white list:
1133 QStringList pluginList;
1134
1135 const auto addPlugins = [&pluginSourcePath,&pluginList,useDebugLibs](const QString &subDirectory,
1136 const std::function<bool(QString)> &predicate = std::function<bool(QString)>()) {
1137 const QStringList libs = QDir(pluginSourcePath + u'/' + subDirectory)
1138 .entryList({QStringLiteral("*.dylib")});
1139 for (const QString &lib : libs) {
1140 if (lib.endsWith(QStringLiteral("_debug.dylib")) != useDebugLibs)
1141 continue;
1142 if (!predicate || predicate(lib))
1143 pluginList.append(subDirectory + u'/' + lib);
1144 }
1145 };
1146
1147 // Platform plugin:
1148 addPlugins(QStringLiteral("platforms"), [](const QString &lib) {
1149 // Ignore minimal and offscreen platform plugins
1150 if (!lib.contains(QStringLiteral("cocoa")))
1151 return false;
1152 return true;
1153 });
1154
1155 // Cocoa print support
1156 addPlugins(QStringLiteral("printsupport"));
1157
1158 // Styles
1159 addPlugins(QStringLiteral("styles"));
1160
1161 // Check if Qt was configured with -libinfix
1162 const QString libInfix = getLibInfix(deploymentInfo.deployedFrameworks);
1163
1164 // Network
1165 if (deploymentInfo.containsModule("Network", libInfix)) {
1166 addPlugins(QStringLiteral("tls"));
1167 addPlugins(QStringLiteral("networkinformation"));
1168 }
1169
1170 // All image formats (svg if QtSvg is used)
1171 const bool usesSvg = deploymentInfo.containsModule("Svg", libInfix);
1172 addPlugins(QStringLiteral("imageformats"), [usesSvg](const QString &lib) {
1173 if (lib.contains(QStringLiteral("qsvg")) && !usesSvg)
1174 return false;
1175 return true;
1176 });
1177
1178 addPlugins(QStringLiteral("iconengines"));
1179
1180 // Platforminputcontext plugins if QtGui is in use
1181 if (deploymentInfo.containsModule("Gui", libInfix)) {
1182 addPlugins(QStringLiteral("platforminputcontexts"), [&addPlugins](const QString &lib) {
1183 // Deploy the virtual keyboard plugins if we have deployed virtualkeyboard
1184 if (lib.startsWith(QStringLiteral("libqtvirtualkeyboard")))
1185 addPlugins(QStringLiteral("virtualkeyboard"));
1186 return true;
1187 });
1188 }
1189
1190 // Sql plugins if QtSql is in use
1191 if (deploymentInfo.containsModule("Sql", libInfix)) {
1192 addPlugins(QStringLiteral("sqldrivers"), [](const QString &lib) {
1193 if (lib.startsWith(QStringLiteral("libqsqlodbc")) || lib.startsWith(QStringLiteral("libqsqlpsql"))) {
1194 LogWarning() << "Plugin" << lib << "uses private API and is not Mac App store compliant.";
1195 if (appstoreCompliant) {
1196 LogWarning() << "Skip plugin" << lib;
1197 return false;
1198 }
1199 }
1200 return true;
1201 });
1202 }
1203
1204 // WebView plugins if QtWebView is in use
1205 if (deploymentInfo.containsModule("WebView", libInfix)) {
1206 addPlugins(QStringLiteral("webview"), [](const QString &lib) {
1207 if (lib.startsWith(QStringLiteral("libqtwebview_webengine"))) {
1208 LogWarning() << "Plugin" << lib << "uses QtWebEngine and is not Mac App store compliant.";
1209 if (appstoreCompliant) {
1210 LogWarning() << "Skip plugin" << lib;
1211 return false;
1212 }
1213 }
1214 return true;
1215 });
1216 }
1217
1218 // FIXME: Parse modules/Foo.json's plugin_types instead
1219 static const std::map<QString, std::vector<QString>> map {
1220 {QStringLiteral("Multimedia"), {QStringLiteral("multimedia")}},
1221 {QStringLiteral("3DRender"), {QStringLiteral("sceneparsers"), QStringLiteral("geometryloaders"), QStringLiteral("renderers")}},
1222 {QStringLiteral("3DQuickRender"), {QStringLiteral("renderplugins")}},
1223 {QStringLiteral("Positioning"), {QStringLiteral("position")}},
1224 {QStringLiteral("Location"), {QStringLiteral("geoservices")}},
1225 {QStringLiteral("TextToSpeech"), {QStringLiteral("texttospeech")}},
1226 {QStringLiteral("SerialBus"), {QStringLiteral("canbus")}},
1227 };
1228
1229 for (const auto &it : map) {
1230 if (deploymentInfo.containsModule(it.first, libInfix)) {
1231 for (const auto &pluginType : it.second) {
1232 addPlugins(pluginType);
1233 }
1234 }
1235 }
1236
1237 for (const QString &plugin : pluginList) {
1238 QString sourcePath = pluginSourcePath + "/" + plugin;
1239 const QString destinationPath = pluginDestinationPath + "/" + plugin;
1240 QDir dir;
1241 dir.mkpath(QFileInfo(destinationPath).path());
1242
1243 if (copyFilePrintStatus(sourcePath, destinationPath)) {
1244 runStrip(destinationPath);
1245 QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
1246 deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
1247 }
1248 }
1249}
1250
1251void createQtConf(const QString &appBundlePath)
1252{
1253 // Set Plugins and imports paths. These are relative to App.app/Contents.
1254 QByteArray contents = "[Paths]\n"
1255 "Plugins = PlugIns\n"
1256 "QmlImports = Resources/qml\n";
1257
1258 QString filePath = appBundlePath + "/Contents/Resources/";
1259 QString fileName = filePath + "qt.conf";
1260
1261 QDir().mkpath(filePath);
1262
1263 if (QFile::exists(fileName) && !alwaysOwerwriteEnabled) {
1264 LogWarning();
1265 LogWarning() << fileName << "already exists, will not overwrite.";
1266 LogWarning() << "To make sure the plugins are loaded from the correct location,";
1267 LogWarning() << "please make sure qt.conf contains the following lines:";
1268 LogWarning() << "[Paths]";
1269 LogWarning() << " Plugins = PlugIns";
1270 return;
1271 }
1272
1273 if (QSaveFile qtconf(fileName); qtconf.open(QIODevice::WriteOnly)
1274 && qtconf.write(contents) != -1 && qtconf.commit()) {
1275 LogNormal() << "Created configuration file:" << fileName;
1276 LogNormal() << "This file sets the plugin search path to" << appBundlePath + "/Contents/PlugIns";
1277 }
1278}
1279
1280void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs)
1281{
1282 ApplicationBundleInfo applicationBundle;
1283 applicationBundle.path = appBundlePath;
1284 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1285
1286 const QString pluginDestinationPath = appBundlePath + "/" + "Contents/PlugIns";
1287 deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
1288}
1289
1290void deployQmlImport(const QString &appBundlePath, const QList<QString> &rpaths, const QString &importSourcePath, const QString &importName)
1291{
1292 QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName;
1293
1294 // Skip already deployed imports. This can happen in cases like "QtQuick.Controls.Styles",
1295 // where deploying QtQuick.Controls will also deploy the "Styles" sub-import.
1296 if (QDir().exists(importDestinationPath))
1297 return;
1298
1299 recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath);
1300}
1301
1302static bool importLessThan(const QVariant &v1, const QVariant &v2)
1303{
1304 QVariantMap import1 = v1.toMap();
1305 QVariantMap import2 = v2.toMap();
1306 QString path1 = import1["path"].toString();
1307 QString path2 = import2["path"].toString();
1308 return path1 < path2;
1309}
1310
1311// Scan qml files in qmldirs for import statements, deploy used imports from QmlImportsPath to Contents/Resources/qml.
1312bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths)
1313{
1314 LogNormal() << "";
1315 LogNormal() << "Deploying QML imports ";
1316 LogNormal() << "Application QML file path(s) is" << qmlDirs;
1317 LogNormal() << "QML module search path(s) is" << qmlImportPaths;
1318
1319 // Use qmlimportscanner from QLibraryInfo::LibraryExecutablesPath
1320 QString qmlImportScannerPath = [] {
1321 const QStringList libexecs = QLibraryInfo::paths(QLibraryInfo::LibraryExecutablesPath);
1322 for (const QString &dir : libexecs) {
1323 const QString p = QDir::cleanPath(dir + "/qmlimportscanner");
1324 if (QFile::exists(p))
1325 return p;
1326 }
1327 return QString();
1328 }();
1329
1330 // Fallback: Look relative to the macdeployqt binary
1331 if (qmlImportScannerPath.isEmpty())
1332 qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
1333
1334 // Verify that we found a qmlimportscanner binary
1335 if (!QFile::exists(qmlImportScannerPath)) {
1336 LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
1337 LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
1338 return false;
1339 }
1340
1341 // build argument list for qmlimportsanner: "-rootPath foo/ -rootPath bar/ -importPath path/to/qt/qml"
1342 // ("rootPath" points to a directory containing app qml, "importPath" is where the Qt imports are installed)
1343 QStringList argumentList;
1344 for (const QString &qmlDir : qmlDirs) {
1345 argumentList.append("-rootPath");
1346 argumentList.append(qmlDir);
1347 }
1348 for (const QString &importPath : qmlImportPaths)
1349 argumentList << "-importPath" << importPath;
1350 for (const QString &importPath : QLibraryInfo::paths(QLibraryInfo::QmlImportsPath))
1351 argumentList << "-importPath" << importPath;
1352
1353 // run qmlimportscanner
1354 QProcess qmlImportScanner;
1355 qmlImportScanner.start(qmlImportScannerPath, argumentList);
1356 if (!qmlImportScanner.waitForStarted()) {
1357 LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
1358 return false;
1359 }
1360 qmlImportScanner.waitForFinished(-1);
1361
1362 // log qmlimportscanner errors
1363 qmlImportScanner.setReadChannel(QProcess::StandardError);
1364 QByteArray errors = qmlImportScanner.readAll();
1365 if (!errors.isEmpty()) {
1366 LogWarning() << "QML file parse error (deployment will continue):";
1367 LogWarning() << errors;
1368 }
1369
1370 // parse qmlimportscanner json
1371 qmlImportScanner.setReadChannel(QProcess::StandardOutput);
1372 QByteArray json = qmlImportScanner.readAll();
1373 QJsonDocument doc = QJsonDocument::fromJson(json);
1374 if (!doc.isArray()) {
1375 LogError() << "qmlimportscanner output error. Expected json array, got:";
1376 LogError() << json;
1377 return false;
1378 }
1379
1380 // sort imports to deploy a module before its sub-modules (otherwise
1381 // deployQmlImports can consider the module deployed if it has already
1382 // deployed one of its sub-module)
1383 QVariantList array = doc.array().toVariantList();
1384 std::sort(array.begin(), array.end(), importLessThan);
1385
1386 // deploy each import
1387 for (const QVariant &importValue : array) {
1388 QVariantMap import = importValue.toMap();
1389 QString name = import["name"].toString();
1390 QString path = import["path"].toString();
1391 QString type = import["type"].toString();
1392
1393 LogNormal() << "Deploying QML import" << name;
1394
1395 // Skip imports with missing info - path will be empty if the import is not found.
1396 if (name.isEmpty() || path.isEmpty()) {
1397 LogNormal() << " Skip import: name or path is empty";
1398 LogNormal() << "";
1399 continue;
1400 }
1401
1402 // Deploy module imports only, skip directory (local/remote) and js imports. These
1403 // should be deployed as a part of the application build.
1404 if (type != QStringLiteral("module")) {
1405 LogNormal() << " Skip non-module import";
1406 LogNormal() << "";
1407 continue;
1408 }
1409
1410 // Create the destination path from the name
1411 // and version (grabbed from the source path)
1412 // ### let qmlimportscanner provide this.
1413 name.replace(u'.', u'/');
1414 int secondTolast = path.length() - 2;
1415 QString version = path.mid(secondTolast);
1416 if (version.startsWith(u'.'))
1417 name.append(version);
1418
1419 deployQmlImport(appBundlePath, deploymentInfo.rpathsUsed, path, name);
1420 LogNormal() << "";
1421 }
1422 return true;
1423}
1424
1425void codesignFile(const QString &identity, const QString &filePath)
1426{
1427 if (!runCodesign)
1428 return;
1429
1430 QString codeSignLogMessage = "codesign";
1431 if (hardenedRuntime)
1432 codeSignLogMessage += ", enable hardened runtime";
1433 if (secureTimestamp)
1434 codeSignLogMessage += ", include secure timestamp";
1435 LogNormal() << codeSignLogMessage << filePath;
1436
1437 QStringList codeSignOptions = { "--preserve-metadata=identifier,entitlements", "--force", "-s",
1438 identity, filePath };
1439 if (hardenedRuntime)
1440 codeSignOptions << "-o" << "runtime";
1441
1442 if (secureTimestamp)
1443 codeSignOptions << "--timestamp";
1444
1445 if (!extraEntitlements.isEmpty())
1446 codeSignOptions << "--entitlements" << extraEntitlements;
1447
1448 QProcess codesign;
1449 codesign.start("codesign", codeSignOptions);
1450 codesign.waitForFinished(-1);
1451
1452 QByteArray err = codesign.readAllStandardError();
1453 if (codesign.exitCode() > 0) {
1454 LogError() << "Codesign signing error:";
1455 LogError() << err;
1456 } else if (!err.isEmpty()) {
1457 LogDebug() << err;
1458 }
1459}
1460
1461QSet<QString> codesignBundle(const QString &identity,
1462 const QString &appBundlePath,
1463 QList<QString> additionalBinariesContainingRpaths)
1464{
1465 // Code sign all binaries in the app bundle. This needs to
1466 // be done inside-out, e.g sign framework dependencies
1467 // before the main app binary. The codesign tool itself has
1468 // a "--deep" option to do this, but usage when signing is
1469 // not recommended: "Signing with --deep is for emergency
1470 // repairs and temporary adjustments only."
1471
1472 LogNormal() << "";
1473 LogNormal() << "Signing" << appBundlePath << "with identity" << identity;
1474
1475 QStack<QString> pendingBinaries;
1476 QSet<QString> pendingBinariesSet;
1477 QSet<QString> signedBinaries;
1478
1479 // Create the root code-binary set. This set consists of the application
1480 // executable(s) and the plugins.
1481 QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath();
1482 QString rootBinariesPath = appBundleAbsolutePath + "/Contents/MacOS/";
1483 QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() << "*", QDir::Files);
1484
1485 // The app binary must be signed last.
1486 QString appBinary = findAppBinary(appBundleAbsolutePath);
1487 QString appBinaryName = QFileInfo(appBinary).fileName();
1488 if (int appBinaryIdx = foundRootBinaries.indexOf(appBinaryName); appBinaryIdx > 0) {
1489 foundRootBinaries.swapItemsAt(0, appBinaryIdx);
1490 LogDebug() << "swapped appBinary to start of list";
1491 }
1492 LogDebug() << "App binary is" << appBinaryName;
1493 LogDebug() << "Binaries in" << rootBinariesPath << "are" << foundRootBinaries;
1494
1495 for (const QString &binary : foundRootBinaries) {
1496 QString binaryPath = rootBinariesPath + binary;
1497 pendingBinaries.push(binaryPath);
1498 pendingBinariesSet.insert(binaryPath);
1499 additionalBinariesContainingRpaths.append(binaryPath);
1500 }
1501
1502 bool getAbsoltuePath = true;
1503 QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath + "/Contents/PlugIns/", getAbsoltuePath);
1504 for (const QString &binary : foundPluginBinaries) {
1505 pendingBinaries.push(binary);
1506 pendingBinariesSet.insert(binary);
1507 }
1508
1509 // Add frameworks for processing.
1510 QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath);
1511 for (const QString &frameworkPath : frameworkPaths) {
1512
1513 // Prioritise first to sign any additional inner bundles found in the Helpers folder (e.g
1514 // used by QtWebEngine).
1515 QDirIterator helpersIterator(frameworkPath, QStringList() << QString::fromLatin1("Helpers"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1516 while (helpersIterator.hasNext()) {
1517 helpersIterator.next();
1518 QString helpersPath = helpersIterator.filePath();
1519 QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() << "*.app", QDir::Dirs);
1520 for (const QString &innerBundleName : innerBundleNames)
1521 signedBinaries += codesignBundle(identity,
1522 helpersPath + "/" + innerBundleName,
1523 additionalBinariesContainingRpaths);
1524 }
1525
1526 // Also make sure to sign any libraries that will not be found by otool because they
1527 // are not linked and won't be seen as a dependency.
1528 QDirIterator librariesIterator(frameworkPath, QStringList() << QString::fromLatin1("Libraries"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1529 while (librariesIterator.hasNext()) {
1530 librariesIterator.next();
1531 QString librariesPath = librariesIterator.filePath();
1532 QStringList bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath);
1533 for (const QString &binary : bundleFiles) {
1534 pendingBinaries.push(binary);
1535 pendingBinariesSet.insert(binary);
1536 }
1537 }
1538 }
1539
1540 // Sign all binaries; use otool to find and sign dependencies first.
1541 while (!pendingBinaries.isEmpty()) {
1542 QString binary = pendingBinaries.pop();
1543 if (signedBinaries.contains(binary))
1544 continue;
1545
1546 // Check if there are unsigned dependencies, sign these first.
1547 QStringList dependencies = getBinaryDependencies(rootBinariesPath, binary,
1548 additionalBinariesContainingRpaths);
1549 dependencies = QSet<QString>(dependencies.begin(), dependencies.end())
1550 .subtract(signedBinaries)
1551 .subtract(pendingBinariesSet)
1552 .values();
1553
1554 if (!dependencies.isEmpty()) {
1555 pendingBinaries.push(binary);
1556 pendingBinariesSet.insert(binary);
1557 int dependenciesSkipped = 0;
1558 for (const QString &dependency : std::as_const(dependencies)) {
1559 // Skip dependencies that are outside the current app bundle, because this might
1560 // cause a codesign error if the current bundle is part of the dependency (e.g.
1561 // a bundle is part of a framework helper, and depends on that framework).
1562 // The dependencies will be taken care of after the current bundle is signed.
1563 if (!dependency.startsWith(appBundleAbsolutePath)) {
1564 ++dependenciesSkipped;
1565 LogNormal() << "Skipping outside dependency: " << dependency;
1566 continue;
1567 }
1568 pendingBinaries.push(dependency);
1569 pendingBinariesSet.insert(dependency);
1570 }
1571
1572 // If all dependencies were skipped, make sure the binary is actually signed, instead
1573 // of going into an infinite loop.
1574 if (dependenciesSkipped == dependencies.size()) {
1575 pendingBinaries.pop();
1576 } else {
1577 continue;
1578 }
1579 }
1580
1581 // Look for an entitlements file in the bundle to include when signing
1582 extraEntitlements = findEntitlementsFile(appBundleAbsolutePath + "/Contents/Resources/");
1583
1584 // All dependencies are signed, now sign this binary.
1585 codesignFile(identity, binary);
1586 signedBinaries.insert(binary);
1587 pendingBinariesSet.remove(binary);
1588 }
1589
1590 LogNormal() << "Finished codesigning " << appBundlePath << "with identity" << identity;
1591
1592 // Verify code signature
1593 QProcess codesign;
1594 codesign.start("codesign", QStringList() << "--deep" << "-v" << appBundlePath);
1595 codesign.waitForFinished(-1);
1596 QByteArray err = codesign.readAllStandardError();
1597 if (codesign.exitCode() > 0) {
1598 LogError() << "codesign verification error:";
1599 LogError() << err;
1600 } else if (!err.isEmpty()) {
1601 LogDebug() << err;
1602 }
1603
1604 return signedBinaries;
1605}
1606
1607void codesign(const QString &identity, const QString &appBundlePath) {
1608 codesignBundle(identity, appBundlePath, QList<QString>());
1609}
1610
1611void createDiskImage(const QString &appBundlePath, const QString &filesystemType)
1612{
1613 QString appBaseName = appBundlePath;
1614 appBaseName.chop(4); // remove ".app" from end
1615
1616 QString dmgName = appBaseName + ".dmg";
1617
1618 QFile dmg(dmgName);
1619
1620 if (dmg.exists() && alwaysOwerwriteEnabled)
1621 dmg.remove();
1622
1623 if (dmg.exists()) {
1624 LogNormal() << "Disk image already exists, skipping .dmg creation for" << dmg.fileName();
1625 } else {
1626 LogNormal() << "Creating disk image (.dmg) for" << appBundlePath;
1627 }
1628
1629 LogNormal() << "Image will use" << filesystemType;
1630
1631 // More dmg options can be found in the hdiutil man page.
1632 QStringList options = QStringList()
1633 << "create" << dmgName
1634 << "-srcfolder" << appBundlePath
1635 << "-format" << "UDZO"
1636 << "-fs" << filesystemType
1637 << "-volname" << appBaseName;
1638
1639 QProcess hdutil;
1640 hdutil.start("hdiutil", options);
1641 hdutil.waitForFinished(-1);
1642 if (hdutil.exitCode() != 0) {
1643 LogError() << "Bundle creation error:" << hdutil.readAllStandardError();
1644 }
1645}
1646
1647void fixupFramework(const QString &frameworkName)
1648{
1649 // Expected framework name looks like "Foo.framework"
1650 QStringList parts = frameworkName.split(".");
1651 if (parts.count() < 2) {
1652 LogError() << "fixupFramework: Unexpected framework name" << frameworkName;
1653 return;
1654 }
1655
1656 // Assume framework binary path is Foo.framework/Foo
1657 QString frameworkBinary = frameworkName + QStringLiteral("/") + parts[0];
1658
1659 // Xcode expects to find Foo.framework/Versions/A when code
1660 // signing, while qmake typically generates numeric versions.
1661 // Create symlink to the actual version in the framework.
1662 linkFilePrintStatus("Current", frameworkName + "/Versions/A");
1663
1664 // Set up @rpath structure.
1665 changeIdentification("@rpath/" + frameworkBinary, frameworkBinary);
1666 addRPath("@loader_path/../../Contents/Frameworks/", frameworkBinary);
1667}
bool useLoaderPath
Definition shared.h:83
bool containsModule(const QString &module, const QString &libInFix) const
Definition shared.cpp:961
bool isDebug
Definition shared.h:85
bool isFramework
Definition shared.h:84
bool isDebugLibrary() const
Definition shared.h:38
bool isDylib
Definition shared.h:23
bool recursiveCopy(const QString &sourcePath, const QString &destinationPath, const QRegularExpression &ignoreRegExp=QRegularExpression())
Definition shared.cpp:643
bool runCodesign
Definition shared.cpp:31
void addRPath(const QString &rpath, const QString &binaryPath)
Definition shared.cpp:878
QStringList getBinaryDependencies(const QString executablePath, const QString &path, const QList< QString > &additionalBinariesContainingRpaths)
Definition shared.cpp:592
QString extraEntitlements
Definition shared.cpp:33
QStringList librarySearchPath
Definition shared.cpp:32
const QString bundleFrameworkDirectory
Definition shared.cpp:67
bool deployFramework
Definition shared.cpp:38
QStringList findAppBundleFiles(const QString &appBundlePath, bool absolutePath=false)
Definition shared.cpp:456
static bool importLessThan(const QVariant &v1, const QVariant &v2)
Definition shared.cpp:1302
QList< FrameworkInfo > getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QList< QString > &rpaths, bool useDebugLibs)
Definition shared.cpp:577
bool copyFilePrintStatus(const QString &from, const QString &to)
Definition shared.cpp:77
void deployRPaths(const QString &bundlePath, const QList< QString > &rpaths, const QString &binaryPath, bool useLoaderPath)
Definition shared.cpp:883
QString getLibInfix(const QStringList &deployedFrameworks)
Definition shared.cpp:1107
QList< QString > getBinaryRPaths(const QString &path, bool resolve=true, QString executablePath=QString())
Definition shared.cpp:530
void patch_debugInInfoPlist(const QString &infoPlistPath)
Definition shared.cpp:133
void runInstallNameTool(QStringList options)
Definition shared.cpp:832
bool appstoreCompliant
Definition shared.cpp:36
bool alwaysOwerwriteEnabled
Definition shared.cpp:30
void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pluginSourcePath, const QString pluginDestinationPath, DeploymentInfo deploymentInfo, bool useDebugLibs)
Definition shared.cpp:1124
void deployQmlImport(const QString &appBundlePath, const QList< QString > &rpaths, const QString &importSourcePath, const QString &importName)
Definition shared.cpp:1290
void recursiveCopyAndDeploy(const QString &appBundlePath, const QList< QString > &rpaths, const QString &sourcePath, const QString &destinationPath)
Definition shared.cpp:670
QString resolveDyldPrefix(const QString &path, const QString &loaderPath, const QString &executablePath)
Definition shared.cpp:504
bool linkFilePrintStatus(const QString &file, const QString &link)
Definition shared.cpp:114
bool hardenedRuntime
Definition shared.cpp:34
void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework, const QStringList &binaryPaths, bool useLoaderPath)
Definition shared.cpp:851
QString findEntitlementsFile(const QString &path)
Definition shared.cpp:473
bool secureTimestamp
Definition shared.cpp:35
QString copyDylib(const FrameworkInfo &framework, const QString path)
Definition shared.cpp:737
QStringList findAppLibraries(const QString &appBundlePath)
Definition shared.cpp:443
QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
Definition shared.cpp:69
#define LogWarning()
Definition shared.h:14
QSet< QString > codesignBundle(const QString &identity, const QString &appBundlePath, QList< QString > additionalBinariesContainingRpaths)
Definition shared.cpp:1461
void codesignFile(const QString &identity, const QString &filePath)
Definition shared.cpp:1425
void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath)
Definition shared.cpp:926
bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths)
Definition shared.cpp:1312
void createQtConf(const QString &appBundlePath)
Definition shared.cpp:1251
void runStrip(const QString &binaryPath)
Definition shared.cpp:940
QString findAppBinary(const QString &appBundlePath)
Definition shared.cpp:376
bool operator==(const FrameworkInfo &a, const FrameworkInfo &b)
Definition shared.cpp:44
void fixupFramework(const QString &appBundlePath)
Definition shared.cpp:1647
void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs)
Definition shared.cpp:1280
void changeIdentification(const QString &id, const QString &binaryPath)
Definition shared.cpp:843
OtoolInfo findDependencyInfo(const QString &binaryPath)
Definition shared.cpp:155
DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs)
Definition shared.cpp:1082
void createDiskImage(const QString &appBundlePath, const QString &filesystemType)
Definition shared.cpp:1611
QString copyFramework(const FrameworkInfo &framework, const QString path)
Definition shared.cpp:764
bool runStripEnabled
Definition shared.cpp:29
void stripAppBinary(const QString &bundlePath)
Definition shared.cpp:956
#define LogError()
Definition shared.h:13
QStringList findAppFrameworkPaths(const QString &appBundlePath)
Definition shared.cpp:429
DeploymentInfo deployQtFrameworks(QList< FrameworkInfo > frameworks, const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath)
Definition shared.cpp:981
#define LogDebug()
Definition shared.h:16
QStringList findAppFrameworkNames(const QString &appBundlePath)
Definition shared.cpp:412
void codesign(const QString &identity, const QString &appBundlePath)
Definition shared.cpp:1607
FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList< QString > &rpaths, bool useDebugLibs)
Definition shared.cpp:219
QDebug operator<<(QDebug debug, const FrameworkInfo &info)
Definition shared.cpp:49
#define LogNormal()
Definition shared.h:15
QList< FrameworkInfo > getQtFrameworks(const QString &path, const QString &appBundlePath, const QList< QString > &rpaths, bool useDebugLibs)
Definition shared.cpp:569
int logLevel
Definition shared.cpp:37