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