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