Qt 6.x
The Qt SDK
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();
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.first());
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.first();
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);
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"),
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{
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("@rpath/")) {
602 if (!rpathsLoaded) {
603 rpaths = getBinaryRPaths(path, true, executablePath);
604 foreach (const QString &binaryPath, additionalBinariesContainingRpaths)
605 rpaths += getBinaryRPaths(binaryPath, true);
606 rpaths.removeDuplicates();
607 rpathsLoaded = true;
608 }
609 bool resolved = false;
610 for (const QString &rpath : std::as_const(rpaths)) {
611 QString binary = QDir::cleanPath(rpath + "/" + trimmedLine.mid(QStringLiteral("@rpath/").length()));
612 LogDebug() << "Checking for" << binary;
613 if (QFile::exists(binary)) {
614 binaries.append(binary);
615 resolved = true;
616 break;
617 }
618 }
619 if (!resolved && !rpaths.isEmpty()) {
620 LogError() << "Cannot resolve rpath" << trimmedLine;
621 LogError() << " using" << rpaths;
622 }
623 }
624 }
625
626 return binaries;
627}
628
629// copies everything _inside_ sourcePath to destinationPath
630bool recursiveCopy(const QString &sourcePath, const QString &destinationPath,
631 const QRegularExpression &ignoreRegExp = QRegularExpression())
632{
633 if (!QDir(sourcePath).exists())
634 return false;
635 QDir().mkpath(destinationPath);
636
637 LogNormal() << "copy:" << sourcePath << destinationPath;
638
640 const bool hasValidRegExp = ignoreRegExp.isValid() && ignoreRegExp.pattern().length() > 0;
641 foreach (QString file, files) {
642 if (hasValidRegExp && ignoreRegExp.match(file).hasMatch())
643 continue;
644 const QString fileSourcePath = sourcePath + "/" + file;
645 const QString fileDestinationPath = destinationPath + "/" + file;
646 copyFilePrintStatus(fileSourcePath, fileDestinationPath);
647 }
648
649 QStringList subdirs = QDir(sourcePath).entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot);
650 for (const QString &dir : subdirs) {
651 recursiveCopy(sourcePath + "/" + dir, destinationPath + "/" + dir);
652 }
653 return true;
654}
655
656void recursiveCopyAndDeploy(const QString &appBundlePath, const QList<QString> &rpaths, const QString &sourcePath, const QString &destinationPath)
657{
658 QDir().mkpath(destinationPath);
659
660 LogNormal() << "copy:" << sourcePath << destinationPath;
661 const bool isDwarfPath = sourcePath.endsWith("DWARF");
662
664 for (const QString &file : files) {
665 const QString fileSourcePath = sourcePath + u'/' + file;
666
667 if (file.endsWith("_debug.dylib")) {
668 continue; // Skip debug versions
669 } else if (!isDwarfPath && file.endsWith(QStringLiteral(".dylib"))) {
670 // App store code signing rules forbids code binaries in Contents/Resources/,
671 // which poses a problem for deploying mixed .qml/.dylib Qt Quick imports.
672 // Solve this by placing the dylibs in Contents/PlugIns/quick, and then
673 // creting a symlink to there from the Qt Quick import in Contents/Resources/.
674 //
675 // Example:
676 // MyApp.app/Contents/Resources/qml/QtQuick/Controls/libqtquickcontrolsplugin.dylib ->
677 // ../../../../PlugIns/quick/libqtquickcontrolsplugin.dylib
678 //
679
680 // The .dylib destination path:
681 QString fileDestinationDir = appBundlePath + QStringLiteral("/Contents/PlugIns/quick/");
682 QDir().mkpath(fileDestinationDir);
683 QString fileDestinationPath = fileDestinationDir + file;
684
685 // The .dylib symlink destination path:
686 QString linkDestinationPath = destinationPath + u'/' + file;
687
688 // The (relative) link; with a correct number of "../"'s.
689 QString linkPath = QStringLiteral("PlugIns/quick/") + file;
690 int cdupCount = linkDestinationPath.count(QStringLiteral("/")) - appBundlePath.count(QStringLiteral("/"));
691 for (int i = 0; i < cdupCount - 2; ++i)
692 linkPath.prepend("../");
693
694 if (copyFilePrintStatus(fileSourcePath, fileDestinationPath)) {
695 linkFilePrintStatus(linkPath, linkDestinationPath);
696
697 runStrip(fileDestinationPath);
698 bool useDebugLibs = false;
699 bool useLoaderPath = false;
700 QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, appBundlePath, rpaths, useDebugLibs);
701 deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath);
702 }
703 } else {
704 QString fileDestinationPath = destinationPath + u'/' + file;
705 copyFilePrintStatus(fileSourcePath, fileDestinationPath);
706 }
707 }
708
710 for (const QString &dir : subdirs) {
711 recursiveCopyAndDeploy(appBundlePath, rpaths, sourcePath + u'/' + dir, destinationPath + u'/' + dir);
712 }
713}
714
716{
717 if (!QFile::exists(framework.sourceFilePath)) {
718 LogError() << "no file at" << framework.sourceFilePath;
719 return QString();
720 }
721
722 // Construct destination paths. The full path typically looks like
723 // MyApp.app/Contents/Frameworks/libfoo.dylib
724 QString dylibDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory;
725 QString dylibDestinationBinaryPath = dylibDestinationDirectory + u'/' + framework.binaryName;
726
727 // Create destination directory
728 if (!QDir().mkpath(dylibDestinationDirectory)) {
729 LogError() << "could not create destination directory" << dylibDestinationDirectory;
730 return QString();
731 }
732
733 // Return if the dylib has already been deployed
734 if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled)
735 return dylibDestinationBinaryPath;
736
737 // Copy dylib binary
738 copyFilePrintStatus(framework.sourceFilePath, dylibDestinationBinaryPath);
739 return dylibDestinationBinaryPath;
740}
741
743{
744 if (!QFile::exists(framework.sourceFilePath)) {
745 LogError() << "no file at" << framework.sourceFilePath;
746 return QString();
747 }
748
749 // Construct destination paths. The full path typically looks like
750 // MyApp.app/Contents/Frameworks/Foo.framework/Versions/5/QtFoo
751 QString frameworkDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory;
752 QString frameworkBinaryDestinationDirectory = frameworkDestinationDirectory + u'/' + framework.binaryDirectory;
753 QString frameworkDestinationBinaryPath = frameworkBinaryDestinationDirectory + u'/' + framework.binaryName;
754
755 // Return if the framework has aleardy been deployed
756 if (QDir(frameworkDestinationDirectory).exists() && !alwaysOwerwriteEnabled)
757 return QString();
758
759 // Create destination directory
760 if (!QDir().mkpath(frameworkBinaryDestinationDirectory)) {
761 LogError() << "could not create destination directory" << frameworkBinaryDestinationDirectory;
762 return QString();
763 }
764
765 // Now copy the framework. Some parts should be left out (headers/, .prl files).
766 // Some parts should be included (Resources/, symlink structure). We want this
767 // function to make as few assumptions about the framework as possible while at
768 // the same time producing a codesign-compatible framework.
769
770 // Copy framework binary
771 copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath);
772
773 // Copy Resources/, Libraries/ and Helpers/
774 const QString resourcesSourcePath = framework.frameworkPath + "/Resources";
775 const QString resourcesDestinationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Resources";
776 // Ignore *.prl files that are in the Resources directory
777 recursiveCopy(resourcesSourcePath, resourcesDestinationPath,
778 QRegularExpression("\\A(?:[^/]*\\.prl)\\z"));
779 const QString librariesSourcePath = framework.frameworkPath + "/Libraries";
780 const QString librariesDestinationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Libraries";
781 bool createdLibraries = recursiveCopy(librariesSourcePath, librariesDestinationPath);
782 const QString helpersSourcePath = framework.frameworkPath + "/Helpers";
783 const QString helpersDestinationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Helpers";
784 bool createdHelpers = recursiveCopy(helpersSourcePath, helpersDestinationPath);
785
786 // Create symlink structure. Links at the framework root point to Versions/Current/
787 // which again points to the actual version:
788 // QtFoo.framework/QtFoo -> Versions/Current/QtFoo
789 // QtFoo.framework/Resources -> Versions/Current/Resources
790 // QtFoo.framework/Versions/Current -> 5
791 linkFilePrintStatus("Versions/Current/" + framework.binaryName, frameworkDestinationDirectory + "/" + framework.binaryName);
792 linkFilePrintStatus("Versions/Current/Resources", frameworkDestinationDirectory + "/Resources");
793 if (createdLibraries)
794 linkFilePrintStatus("Versions/Current/Libraries", frameworkDestinationDirectory + "/Libraries");
795 if (createdHelpers)
796 linkFilePrintStatus("Versions/Current/Helpers", frameworkDestinationDirectory + "/Helpers");
797 linkFilePrintStatus(framework.version, frameworkDestinationDirectory + "/Versions/Current");
798
799 // Correct Info.plist location for frameworks produced by older versions of qmake
800 // Contents/Info.plist should be Versions/5/Resources/Info.plist
801 const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist";
802 const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist";
803 if (QFile::exists(legacyInfoPlistPath)) {
804 copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
805 patch_debugInInfoPlist(correctInfoPlistPath);
806 }
807 return frameworkDestinationBinaryPath;
808}
809
811{
812 QProcess installNametool;
813 installNametool.start("install_name_tool", options);
814 installNametool.waitForFinished();
815 if (installNametool.exitCode() != 0) {
816 LogError() << installNametool.readAllStandardError();
817 LogError() << installNametool.readAllStandardOutput();
818 }
819}
820
821void changeIdentification(const QString &id, const QString &binaryPath)
822{
823 LogDebug() << "Using install_name_tool:";
824 LogDebug() << " change identification in" << binaryPath;
825 LogDebug() << " to" << id;
826 runInstallNameTool(QStringList() << "-id" << id << binaryPath);
827}
828
829void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework, const QStringList &binaryPaths, bool useLoaderPath)
830{
831 const QString absBundlePath = QFileInfo(bundlePath).absoluteFilePath();
832 for (const QString &binary : binaryPaths) {
833 QString deployedInstallName;
834 if (useLoaderPath) {
835 deployedInstallName = "@loader_path/"_L1
836 + QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + u'/' + framework.binaryDestinationDirectory + u'/' + framework.binaryName);
837 } else {
838 deployedInstallName = framework.deployedInstallName;
839 }
840 changeInstallName(framework.installName, deployedInstallName, binary);
841 // Workaround for the case when the library ID name is a symlink, while the dependencies
842 // specified using the canonical path to the library (QTBUG-56814)
843 QFileInfo fileInfo= QFileInfo(framework.installName);
844 QString canonicalInstallName = fileInfo.canonicalFilePath();
845 if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) {
846 changeInstallName(canonicalInstallName, deployedInstallName, binary);
847 // some libraries' inner dependencies (such as ffmpeg, nettle) use symbol link (QTBUG-100093)
848 QString innerDependency = fileInfo.canonicalPath() + "/" + fileInfo.fileName();
849 if (innerDependency != canonicalInstallName && innerDependency != framework.installName) {
850 changeInstallName(innerDependency, deployedInstallName, binary);
851 }
852 }
853 }
854}
855
856void addRPath(const QString &rpath, const QString &binaryPath)
857{
858 runInstallNameTool(QStringList() << "-add_rpath" << rpath << binaryPath);
859}
860
861void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QString &binaryPath, bool useLoaderPath)
862{
863 const QString absFrameworksPath = QFileInfo(bundlePath).absoluteFilePath()
864 + "/Contents/Frameworks"_L1;
865 const QString relativeFrameworkPath = QFileInfo(binaryPath).absoluteDir().relativeFilePath(absFrameworksPath);
866 const QString loaderPathToFrameworks = "@loader_path/"_L1 + relativeFrameworkPath;
867 bool rpathToFrameworksFound = false;
869 QList<QString> binaryRPaths = getBinaryRPaths(binaryPath, false);
870 for (const QString &rpath : std::as_const(binaryRPaths)) {
871 if (rpath == "@executable_path/../Frameworks" ||
872 rpath == loaderPathToFrameworks) {
873 rpathToFrameworksFound = true;
874 continue;
875 }
876 if (rpaths.contains(resolveDyldPrefix(rpath, binaryPath, binaryPath))) {
877 if (!args.contains(rpath))
878 args << "-delete_rpath" << rpath;
879 }
880 }
881 if (!args.length()) {
882 return;
883 }
884 if (!rpathToFrameworksFound) {
885 if (!useLoaderPath) {
886 args << "-add_rpath" << "@executable_path/../Frameworks";
887 } else {
888 args << "-add_rpath" << loaderPathToFrameworks;
889 }
890 }
891 LogDebug() << "Using install_name_tool:";
892 LogDebug() << " change rpaths in" << binaryPath;
893 LogDebug() << " using" << args;
894 runInstallNameTool(QStringList() << args << binaryPath);
895}
896
897void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QStringList &binaryPaths, bool useLoaderPath)
898{
899 for (const QString &binary : binaryPaths) {
900 deployRPaths(bundlePath, rpaths, binary, useLoaderPath);
901 }
902}
903
904void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath)
905{
906 LogDebug() << "Using install_name_tool:";
907 LogDebug() << " in" << binaryPath;
908 LogDebug() << " change reference" << oldName;
909 LogDebug() << " to" << newName;
910 runInstallNameTool(QStringList() << "-change" << oldName << newName << binaryPath);
911}
912
913void runStrip(const QString &binaryPath)
914{
915 if (runStripEnabled == false)
916 return;
917
918 LogDebug() << "Using strip:";
919 LogDebug() << " stripped" << binaryPath;
920 QProcess strip;
921 strip.start("strip", QStringList() << "-x" << binaryPath);
922 strip.waitForFinished();
923 if (strip.exitCode() != 0) {
924 LogError() << strip.readAllStandardError();
925 LogError() << strip.readAllStandardOutput();
926 }
927}
928
929void stripAppBinary(const QString &bundlePath)
930{
931 runStrip(findAppBinary(bundlePath));
932}
933
934bool DeploymentInfo::containsModule(const QString &module, const QString &libInFix) const
935{
936 // Check for framework first
937 if (deployedFrameworks.contains("Qt"_L1 + module + libInFix + ".framework"_L1))
938 return true;
939 // Check for dylib
940 const QRegularExpression dylibRegExp("libQt[0-9]+"_L1
941 + module + libInFix
942 + (isDebug ? "_debug" : "")
943 + ".[0-9]+.dylib"_L1);
944 return deployedFrameworks.filter(dylibRegExp).size() > 0;
945}
946
947/*
948 Deploys the listed frameworks into an app bundle.
949 The frameworks are searched for dependencies, which are also deployed.
950 (deploying Qt3Support will also deploy QtNetwork and QtSql for example.)
951 Returns a DeploymentInfo structure containing the Qt path used and a
952 a list of actually deployed frameworks.
953*/
955 const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs,
956 bool useLoaderPath)
957{
958 LogNormal();
959 LogNormal() << "Deploying Qt frameworks found inside:" << binaryPaths;
960 QStringList copiedFrameworks;
961 DeploymentInfo deploymentInfo;
962 deploymentInfo.useLoaderPath = useLoaderPath;
963 deploymentInfo.isFramework = bundlePath.contains(".framework");
964 deploymentInfo.isDebug = false;
965 QList<QString> rpathsUsed;
966
967 while (frameworks.isEmpty() == false) {
968 const FrameworkInfo framework = frameworks.takeFirst();
969 copiedFrameworks.append(framework.frameworkName);
970
971 // If a single dependency has the _debug suffix, we treat that as
972 // the whole deployment being a debug deployment, including deploying
973 // the debug version of plugins.
974 if (framework.isDebugLibrary())
975 deploymentInfo.isDebug = true;
976
977 if (deploymentInfo.qtPath.isNull())
979
980 if (framework.frameworkDirectory.startsWith(bundlePath)) {
981 LogError() << framework.frameworkName << "already deployed, skipping.";
982 continue;
983 }
984
985 if (!framework.rpathUsed.isEmpty() && !rpathsUsed.contains(framework.rpathUsed)) {
986 rpathsUsed.append(framework.rpathUsed);
987 }
988
989 // Copy the framework/dylib to the app bundle.
990 const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
991 : copyFramework(framework, bundlePath);
992
993 // Install_name_tool the new id into the binaries
994 changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath);
995
996 // Skip the rest if already was deployed.
997 if (deployedBinaryPath.isNull())
998 continue;
999
1000 runStrip(deployedBinaryPath);
1001
1002 // Install_name_tool it a new id.
1003 if (!framework.rpathUsed.length()) {
1004 changeIdentification(framework.deployedInstallName, deployedBinaryPath);
1005 }
1006
1007 // Check for framework dependencies
1008 QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
1009
1010 for (const FrameworkInfo &dependency : dependencies) {
1011 if (dependency.rpathUsed.isEmpty()) {
1012 changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath);
1013 } else {
1014 rpathsUsed.append(dependency.rpathUsed);
1015 }
1016
1017 // Deploy framework if necessary.
1018 if (copiedFrameworks.contains(dependency.frameworkName) == false && frameworks.contains(dependency) == false) {
1019 frameworks.append(dependency);
1020 }
1021 }
1022 }
1023 deploymentInfo.deployedFrameworks = copiedFrameworks;
1024 deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath);
1025 deploymentInfo.rpathsUsed += rpathsUsed;
1026 deploymentInfo.rpathsUsed.removeDuplicates();
1027 return deploymentInfo;
1028}
1029
1030DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs)
1031{
1032 ApplicationBundleInfo applicationBundle;
1033 applicationBundle.path = appBundlePath;
1034 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1035 applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
1036 QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
1037 << additionalExecutables;
1038
1039 QList<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true);
1041 allLibraryPaths.removeDuplicates();
1042
1043 QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
1044 if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) {
1045 LogWarning();
1046 LogWarning() << "Could not find any external Qt frameworks to deploy in" << appBundlePath;
1047 LogWarning() << "Perhaps macdeployqt was already used on" << appBundlePath << "?";
1048 LogWarning() << "If so, you will need to rebuild" << appBundlePath << "before trying again.";
1049 return DeploymentInfo();
1050 } else {
1051 return deployQtFrameworks(frameworks, applicationBundle.path, allBinaryPaths, useDebugLibs, !additionalExecutables.isEmpty());
1052 }
1053}
1054
1055QString getLibInfix(const QStringList &deployedFrameworks)
1056{
1057 QString libInfix;
1058 for (const QString &framework : deployedFrameworks) {
1059 if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework")) &&
1060 !framework.contains(QStringLiteral("5Compat"))) {
1061 Q_ASSERT(framework.length() >= 16);
1062 // 16 == "QtCore" + ".framework"
1063 const int lengthOfLibInfix = framework.length() - 16;
1064 if (lengthOfLibInfix)
1065 libInfix = framework.mid(6, lengthOfLibInfix);
1066 break;
1067 }
1068 }
1069 return libInfix;
1070}
1071
1072void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pluginSourcePath,
1073 const QString pluginDestinationPath, DeploymentInfo deploymentInfo, bool useDebugLibs)
1074{
1075 LogNormal() << "Deploying plugins from" << pluginSourcePath;
1076
1077 if (!pluginSourcePath.contains(deploymentInfo.pluginPath))
1078 return;
1079
1080 // Plugin white list:
1081 QStringList pluginList;
1082
1083 const auto addPlugins = [&pluginSourcePath,&pluginList,useDebugLibs](const QString &subDirectory,
1084 const std::function<bool(QString)> &predicate = std::function<bool(QString)>()) {
1085 const QStringList libs = QDir(pluginSourcePath + u'/' + subDirectory)
1086 .entryList({QStringLiteral("*.dylib")});
1087 for (const QString &lib : libs) {
1088 if (lib.endsWith(QStringLiteral("_debug.dylib")) != useDebugLibs)
1089 continue;
1090 if (!predicate || predicate(lib))
1091 pluginList.append(subDirectory + u'/' + lib);
1092 }
1093 };
1094
1095 // Platform plugin:
1096 addPlugins(QStringLiteral("platforms"), [](const QString &lib) {
1097 // Ignore minimal and offscreen platform plugins
1098 if (!lib.contains(QStringLiteral("cocoa")))
1099 return false;
1100 return true;
1101 });
1102
1103 // Cocoa print support
1104 addPlugins(QStringLiteral("printsupport"));
1105
1106 // Styles
1107 addPlugins(QStringLiteral("styles"));
1108
1109 // Check if Qt was configured with -libinfix
1110 const QString libInfix = getLibInfix(deploymentInfo.deployedFrameworks);
1111
1112 // Network
1113 if (deploymentInfo.containsModule("Network", libInfix)) {
1114 addPlugins(QStringLiteral("tls"));
1115 addPlugins(QStringLiteral("networkinformation"));
1116 }
1117
1118 // All image formats (svg if QtSvg is used)
1119 const bool usesSvg = deploymentInfo.containsModule("Svg", libInfix);
1120 addPlugins(QStringLiteral("imageformats"), [usesSvg](const QString &lib) {
1121 if (lib.contains(QStringLiteral("qsvg")) && !usesSvg)
1122 return false;
1123 return true;
1124 });
1125
1126 addPlugins(QStringLiteral("iconengines"));
1127
1128 // Platforminputcontext plugins if QtGui is in use
1129 if (deploymentInfo.containsModule("Gui", libInfix)) {
1130 addPlugins(QStringLiteral("platforminputcontexts"), [&addPlugins](const QString &lib) {
1131 // Deploy the virtual keyboard plugins if we have deployed virtualkeyboard
1132 if (lib.startsWith(QStringLiteral("libqtvirtualkeyboard")))
1133 addPlugins(QStringLiteral("virtualkeyboard"));
1134 return true;
1135 });
1136 }
1137
1138 // Sql plugins if QtSql is in use
1139 if (deploymentInfo.containsModule("Sql", libInfix)) {
1140 addPlugins(QStringLiteral("sqldrivers"), [](const QString &lib) {
1141 if (lib.startsWith(QStringLiteral("libqsqlodbc")) || lib.startsWith(QStringLiteral("libqsqlpsql"))) {
1142 LogWarning() << "Plugin" << lib << "uses private API and is not Mac App store compliant.";
1143 if (appstoreCompliant) {
1144 LogWarning() << "Skip plugin" << lib;
1145 return false;
1146 }
1147 }
1148 return true;
1149 });
1150 }
1151
1152 // WebView plugins if QtWebView is in use
1153 if (deploymentInfo.containsModule("WebView", libInfix)) {
1154 addPlugins(QStringLiteral("webview"), [](const QString &lib) {
1155 if (lib.startsWith(QStringLiteral("libqtwebview_webengine"))) {
1156 LogWarning() << "Plugin" << lib << "uses QtWebEngine and is not Mac App store compliant.";
1157 if (appstoreCompliant) {
1158 LogWarning() << "Skip plugin" << lib;
1159 return false;
1160 }
1161 }
1162 return true;
1163 });
1164 }
1165
1166 static const std::map<QString, std::vector<QString>> map {
1167 {QStringLiteral("Multimedia"), {QStringLiteral("multimedia")}},
1168 {QStringLiteral("3DRender"), {QStringLiteral("sceneparsers"), QStringLiteral("geometryloaders"), QStringLiteral("renderers")}},
1169 {QStringLiteral("3DQuickRender"), {QStringLiteral("renderplugins")}},
1170 {QStringLiteral("Positioning"), {QStringLiteral("position")}},
1171 {QStringLiteral("Location"), {QStringLiteral("geoservices")}},
1172 {QStringLiteral("TextToSpeech"), {QStringLiteral("texttospeech")}}
1173 };
1174
1175 for (const auto &it : map) {
1176 if (deploymentInfo.containsModule(it.first, libInfix)) {
1177 for (const auto &pluginType : it.second) {
1178 addPlugins(pluginType);
1179 }
1180 }
1181 }
1182
1183 for (const QString &plugin : pluginList) {
1184 QString sourcePath = pluginSourcePath + "/" + plugin;
1185 const QString destinationPath = pluginDestinationPath + "/" + plugin;
1186 QDir dir;
1187 dir.mkpath(QFileInfo(destinationPath).path());
1188
1189 if (copyFilePrintStatus(sourcePath, destinationPath)) {
1190 runStrip(destinationPath);
1191 QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
1192 deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
1193 }
1194 }
1195}
1196
1197void createQtConf(const QString &appBundlePath)
1198{
1199 // Set Plugins and imports paths. These are relative to App.app/Contents.
1200 QByteArray contents = "[Paths]\n"
1201 "Plugins = PlugIns\n"
1202 "Imports = Resources/qml\n"
1203 "QmlImports = Resources/qml\n";
1204
1205 QString filePath = appBundlePath + "/Contents/Resources/";
1206 QString fileName = filePath + "qt.conf";
1207
1208 QDir().mkpath(filePath);
1209
1210 QFile qtconf(fileName);
1211 if (qtconf.exists() && !alwaysOwerwriteEnabled) {
1212 LogWarning();
1213 LogWarning() << fileName << "already exists, will not overwrite.";
1214 LogWarning() << "To make sure the plugins are loaded from the correct location,";
1215 LogWarning() << "please make sure qt.conf contains the following lines:";
1216 LogWarning() << "[Paths]";
1217 LogWarning() << " Plugins = PlugIns";
1218 return;
1219 }
1220
1221 qtconf.open(QIODevice::WriteOnly);
1222 if (qtconf.write(contents) != -1) {
1223 LogNormal() << "Created configuration file:" << fileName;
1224 LogNormal() << "This file sets the plugin search path to" << appBundlePath + "/Contents/PlugIns";
1225 }
1226}
1227
1228void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs)
1229{
1230 ApplicationBundleInfo applicationBundle;
1231 applicationBundle.path = appBundlePath;
1232 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1233
1234 const QString pluginDestinationPath = appBundlePath + "/" + "Contents/PlugIns";
1235 deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
1236}
1237
1238void deployQmlImport(const QString &appBundlePath, const QList<QString> &rpaths, const QString &importSourcePath, const QString &importName)
1239{
1240 QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName;
1241
1242 // Skip already deployed imports. This can happen in cases like "QtQuick.Controls.Styles",
1243 // where deploying QtQuick.Controls will also deploy the "Styles" sub-import.
1244 if (QDir().exists(importDestinationPath))
1245 return;
1246
1247 recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath);
1248}
1249
1250static bool importLessThan(const QVariant &v1, const QVariant &v2)
1251{
1252 QVariantMap import1 = v1.toMap();
1253 QVariantMap import2 = v2.toMap();
1254 QString path1 = import1["path"].toString();
1255 QString path2 = import2["path"].toString();
1256 return path1 < path2;
1257}
1258
1259// Scan qml files in qmldirs for import statements, deploy used imports from QmlImportsPath to Contents/Resources/qml.
1260bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths)
1261{
1262 LogNormal() << "";
1263 LogNormal() << "Deploying QML imports ";
1264 LogNormal() << "Application QML file path(s) is" << qmlDirs;
1265 LogNormal() << "QML module search path(s) is" << qmlImportPaths;
1266
1267 // Use qmlimportscanner from QLibraryInfo::LibraryExecutablesPath
1268 QString qmlImportScannerPath =
1270 + "/qmlimportscanner");
1271
1272 // Fallback: Look relative to the macdeployqt binary
1273 if (!QFile::exists(qmlImportScannerPath))
1274 qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
1275
1276 // Verify that we found a qmlimportscanner binary
1277 if (!QFile::exists(qmlImportScannerPath)) {
1278 LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
1279 LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
1280 return false;
1281 }
1282
1283 // build argument list for qmlimportsanner: "-rootPath foo/ -rootPath bar/ -importPath path/to/qt/qml"
1284 // ("rootPath" points to a directory containing app qml, "importPath" is where the Qt imports are installed)
1285 QStringList argumentList;
1286 for (const QString &qmlDir : qmlDirs) {
1287 argumentList.append("-rootPath");
1288 argumentList.append(qmlDir);
1289 }
1290 for (const QString &importPath : qmlImportPaths)
1291 argumentList << "-importPath" << importPath;
1293 argumentList.append( "-importPath");
1294 argumentList.append(qmlImportsPath);
1295
1296 // run qmlimportscanner
1297 QProcess qmlImportScanner;
1298 qmlImportScanner.start(qmlImportScannerPath, argumentList);
1299 if (!qmlImportScanner.waitForStarted()) {
1300 LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
1301 return false;
1302 }
1303 qmlImportScanner.waitForFinished(-1);
1304
1305 // log qmlimportscanner errors
1306 qmlImportScanner.setReadChannel(QProcess::StandardError);
1307 QByteArray errors = qmlImportScanner.readAll();
1308 if (!errors.isEmpty()) {
1309 LogWarning() << "QML file parse error (deployment will continue):";
1310 LogWarning() << errors;
1311 }
1312
1313 // parse qmlimportscanner json
1314 qmlImportScanner.setReadChannel(QProcess::StandardOutput);
1315 QByteArray json = qmlImportScanner.readAll();
1317 if (!doc.isArray()) {
1318 LogError() << "qmlimportscanner output error. Expected json array, got:";
1319 LogError() << json;
1320 return false;
1321 }
1322
1323 // sort imports to deploy a module before its sub-modules (otherwise
1324 // deployQmlImports can consider the module deployed if it has already
1325 // deployed one of its sub-module)
1327 std::sort(array.begin(), array.end(), importLessThan);
1328
1329 // deploy each import
1330 for (const QVariant &importValue : array) {
1331 QVariantMap import = importValue.toMap();
1332 QString name = import["name"].toString();
1333 QString path = import["path"].toString();
1334 QString type = import["type"].toString();
1335
1336 LogNormal() << "Deploying QML import" << name;
1337
1338 // Skip imports with missing info - path will be empty if the import is not found.
1339 if (name.isEmpty() || path.isEmpty()) {
1340 LogNormal() << " Skip import: name or path is empty";
1341 LogNormal() << "";
1342 continue;
1343 }
1344
1345 // Deploy module imports only, skip directory (local/remote) and js imports. These
1346 // should be deployed as a part of the application build.
1347 if (type != QStringLiteral("module")) {
1348 LogNormal() << " Skip non-module import";
1349 LogNormal() << "";
1350 continue;
1351 }
1352
1353 // Create the destination path from the name
1354 // and version (grabbed from the source path)
1355 // ### let qmlimportscanner provide this.
1356 name.replace(u'.', u'/');
1357 int secondTolast = path.length() - 2;
1358 QString version = path.mid(secondTolast);
1359 if (version.startsWith(u'.'))
1360 name.append(version);
1361
1362 deployQmlImport(appBundlePath, deploymentInfo.rpathsUsed, path, name);
1363 LogNormal() << "";
1364 }
1365 return true;
1366}
1367
1368void codesignFile(const QString &identity, const QString &filePath)
1369{
1370 if (!runCodesign)
1371 return;
1372
1373 QString codeSignLogMessage = "codesign";
1374 if (hardenedRuntime)
1375 codeSignLogMessage += ", enable hardened runtime";
1376 if (secureTimestamp)
1377 codeSignLogMessage += ", include secure timestamp";
1378 LogNormal() << codeSignLogMessage << filePath;
1379
1380 QStringList codeSignOptions = { "--preserve-metadata=identifier,entitlements", "--force", "-s",
1381 identity, filePath };
1382 if (hardenedRuntime)
1383 codeSignOptions << "-o" << "runtime";
1384
1385 if (secureTimestamp)
1386 codeSignOptions << "--timestamp";
1387
1389 codeSignOptions << "--entitlements" << extraEntitlements;
1390
1391 QProcess codesign;
1392 codesign.start("codesign", codeSignOptions);
1393 codesign.waitForFinished(-1);
1394
1395 QByteArray err = codesign.readAllStandardError();
1396 if (codesign.exitCode() > 0) {
1397 LogError() << "Codesign signing error:";
1398 LogError() << err;
1399 } else if (!err.isEmpty()) {
1400 LogDebug() << err;
1401 }
1402}
1403
1405 const QString &appBundlePath,
1406 QList<QString> additionalBinariesContainingRpaths)
1407{
1408 // Code sign all binaries in the app bundle. This needs to
1409 // be done inside-out, e.g sign framework dependencies
1410 // before the main app binary. The codesign tool itself has
1411 // a "--deep" option to do this, but usage when signing is
1412 // not recommended: "Signing with --deep is for emergency
1413 // repairs and temporary adjustments only."
1414
1415 LogNormal() << "";
1416 LogNormal() << "Signing" << appBundlePath << "with identity" << identity;
1417
1418 QStack<QString> pendingBinaries;
1419 QSet<QString> pendingBinariesSet;
1420 QSet<QString> signedBinaries;
1421
1422 // Create the root code-binary set. This set consists of the application
1423 // executable(s) and the plugins.
1424 QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath();
1425 QString rootBinariesPath = appBundleAbsolutePath + "/Contents/MacOS/";
1426 QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() << "*", QDir::Files);
1427 for (const QString &binary : foundRootBinaries) {
1428 QString binaryPath = rootBinariesPath + binary;
1429 pendingBinaries.push(binaryPath);
1430 pendingBinariesSet.insert(binaryPath);
1431 additionalBinariesContainingRpaths.append(binaryPath);
1432 }
1433
1434 bool getAbsoltuePath = true;
1435 QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath + "/Contents/PlugIns/", getAbsoltuePath);
1436 for (const QString &binary : foundPluginBinaries) {
1437 pendingBinaries.push(binary);
1438 pendingBinariesSet.insert(binary);
1439 }
1440
1441 // Add frameworks for processing.
1442 QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath);
1443 for (const QString &frameworkPath : frameworkPaths) {
1444
1445 // Prioritise first to sign any additional inner bundles found in the Helpers folder (e.g
1446 // used by QtWebEngine).
1447 QDirIterator helpersIterator(frameworkPath, QStringList() << QString::fromLatin1("Helpers"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1448 while (helpersIterator.hasNext()) {
1449 helpersIterator.next();
1450 QString helpersPath = helpersIterator.filePath();
1451 QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() << "*.app", QDir::Dirs);
1452 for (const QString &innerBundleName : innerBundleNames)
1453 signedBinaries += codesignBundle(identity,
1454 helpersPath + "/" + innerBundleName,
1455 additionalBinariesContainingRpaths);
1456 }
1457
1458 // Also make sure to sign any libraries that will not be found by otool because they
1459 // are not linked and won't be seen as a dependency.
1460 QDirIterator librariesIterator(frameworkPath, QStringList() << QString::fromLatin1("Libraries"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1461 while (librariesIterator.hasNext()) {
1462 librariesIterator.next();
1463 QString librariesPath = librariesIterator.filePath();
1464 QStringList bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath);
1465 for (const QString &binary : bundleFiles) {
1466 pendingBinaries.push(binary);
1467 pendingBinariesSet.insert(binary);
1468 }
1469 }
1470 }
1471
1472 // Sign all binaries; use otool to find and sign dependencies first.
1473 while (!pendingBinaries.isEmpty()) {
1474 QString binary = pendingBinaries.pop();
1475 if (signedBinaries.contains(binary))
1476 continue;
1477
1478 // Check if there are unsigned dependencies, sign these first.
1479 QStringList dependencies = getBinaryDependencies(rootBinariesPath, binary,
1480 additionalBinariesContainingRpaths);
1481 dependencies = QSet<QString>(dependencies.begin(), dependencies.end())
1482 .subtract(signedBinaries)
1483 .subtract(pendingBinariesSet)
1484 .values();
1485
1486 if (!dependencies.isEmpty()) {
1487 pendingBinaries.push(binary);
1488 pendingBinariesSet.insert(binary);
1489 int dependenciesSkipped = 0;
1490 for (const QString &dependency : std::as_const(dependencies)) {
1491 // Skip dependencies that are outside the current app bundle, because this might
1492 // cause a codesign error if the current bundle is part of the dependency (e.g.
1493 // a bundle is part of a framework helper, and depends on that framework).
1494 // The dependencies will be taken care of after the current bundle is signed.
1495 if (!dependency.startsWith(appBundleAbsolutePath)) {
1496 ++dependenciesSkipped;
1497 LogNormal() << "Skipping outside dependency: " << dependency;
1498 continue;
1499 }
1500 pendingBinaries.push(dependency);
1501 pendingBinariesSet.insert(dependency);
1502 }
1503
1504 // If all dependencies were skipped, make sure the binary is actually signed, instead
1505 // of going into an infinite loop.
1506 if (dependenciesSkipped == dependencies.size()) {
1507 pendingBinaries.pop();
1508 } else {
1509 continue;
1510 }
1511 }
1512
1513 // Look for an entitlements file in the bundle to include when signing
1514 extraEntitlements = findEntitlementsFile(appBundleAbsolutePath + "/Contents/Resources/");
1515
1516 // All dependencies are signed, now sign this binary.
1517 codesignFile(identity, binary);
1518 signedBinaries.insert(binary);
1519 pendingBinariesSet.remove(binary);
1520 }
1521
1522 LogNormal() << "Finished codesigning " << appBundlePath << "with identity" << identity;
1523
1524 // Verify code signature
1525 QProcess codesign;
1526 codesign.start("codesign", QStringList() << "--deep" << "-v" << appBundlePath);
1527 codesign.waitForFinished(-1);
1528 QByteArray err = codesign.readAllStandardError();
1529 if (codesign.exitCode() > 0) {
1530 LogError() << "codesign verification error:";
1531 LogError() << err;
1532 } else if (!err.isEmpty()) {
1533 LogDebug() << err;
1534 }
1535
1536 return signedBinaries;
1537}
1538
1539void codesign(const QString &identity, const QString &appBundlePath) {
1540 codesignBundle(identity, appBundlePath, QList<QString>());
1541}
1542
1543void createDiskImage(const QString &appBundlePath, const QString &filesystemType)
1544{
1545 QString appBaseName = appBundlePath;
1546 appBaseName.chop(4); // remove ".app" from end
1547
1548 QString dmgName = appBaseName + ".dmg";
1549
1550 QFile dmg(dmgName);
1551
1552 if (dmg.exists() && alwaysOwerwriteEnabled)
1553 dmg.remove();
1554
1555 if (dmg.exists()) {
1556 LogNormal() << "Disk image already exists, skipping .dmg creation for" << dmg.fileName();
1557 } else {
1558 LogNormal() << "Creating disk image (.dmg) for" << appBundlePath;
1559 }
1560
1561 LogNormal() << "Image will use" << filesystemType;
1562
1563 // More dmg options can be found in the hdiutil man page.
1564 QStringList options = QStringList()
1565 << "create" << dmgName
1566 << "-srcfolder" << appBundlePath
1567 << "-format" << "UDZO"
1568 << "-fs" << filesystemType
1569 << "-volname" << appBaseName;
1570
1571 QProcess hdutil;
1572 hdutil.start("hdiutil", options);
1573 hdutil.waitForFinished(-1);
1574 if (hdutil.exitCode() != 0) {
1575 LogError() << "Bundle creation error:" << hdutil.readAllStandardError();
1576 }
1577}
1578
1579void fixupFramework(const QString &frameworkName)
1580{
1581 // Expected framework name looks like "Foo.framework"
1582 QStringList parts = frameworkName.split(".");
1583 if (parts.count() < 2) {
1584 LogError() << "fixupFramework: Unexpected framework name" << frameworkName;
1585 return;
1586 }
1587
1588 // Assume framework binary path is Foo.framework/Foo
1589 QString frameworkBinary = frameworkName + QStringLiteral("/") + parts[0];
1590
1591 // Xcode expects to find Foo.framework/Versions/A when code
1592 // signing, while qmake typically generates numeric versions.
1593 // Create symlink to the actual version in the framework.
1594 linkFilePrintStatus("Current", frameworkName + "/Versions/A");
1595
1596 // Set up @rpath structure.
1597 changeIdentification("@rpath/" + frameworkBinary, frameworkBinary);
1598 addRPath("@loader_path/../../Contents/Frameworks/", frameworkBinary);
1599}
QStringList libraryPaths
Definition shared.h:73
QStringList deployedFrameworks
Definition shared.h:81
bool useLoaderPath
Definition shared.h:83
bool containsModule(const QString &module, const QString &libInFix) const
Definition shared.cpp:934
bool isDebug
Definition shared.h:85
QList< QString > rpathsUsed
Definition shared.h:82
QString qtPath
Definition shared.h:79
QString pluginPath
Definition shared.h:80
bool isFramework
Definition shared.h:84
QVersionNumber compatibilityVersion
Definition shared.h:52
QString binaryPath
Definition shared.h:50
QVersionNumber currentVersion
Definition shared.h:51
QString frameworkDirectory
Definition shared.h:24
QString binaryName
Definition shared.h:28
QString rpathUsed
Definition shared.h:30
QString binaryDestinationDirectory
Definition shared.h:36
QString frameworkPath
Definition shared.h:26
bool isDebugLibrary() const
Definition shared.h:38
QString sourceFilePath
Definition shared.h:34
QString frameworkName
Definition shared.h:25
QString binaryDirectory
Definition shared.h:27
bool isDylib
Definition shared.h:23
QString version
Definition shared.h:31
QString frameworkDestinationDirectory
Definition shared.h:35
QString deployedInstallName
Definition shared.h:33
QString installName
Definition shared.h:32
QString installName
Definition shared.h:58
QList< DylibInfo > dependencies
Definition shared.h:62
bool contains(QByteArrayView a) const noexcept
bool startsWith(QByteArrayView other) const noexcept
constexpr QByteArrayView mid(qsizetype pos, qsizetype n=-1) const
constexpr bool isEmpty() const noexcept
\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:106
static QString applicationDirPath()
Returns the directory that contains the application executable.
\inmodule QtCore
The QDirIterator class provides an iterator for directory entrylists.
bool hasNext() const
Returns true if there is at least one more entry in the directory; otherwise, false is returned.
QString next()
Advances the iterator to the next entry, and returns the file path of this new entry.
QString filePath() const
Returns the full file path for the current directory entry.
\inmodule QtCore
Definition qdir.h:19
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:1364
static bool isAbsolutePath(const QString &path)
Returns true if path is absolute; returns false if it is relative.
Definition qdir.h:183
bool mkpath(const QString &dirPath) const
Creates the directory path dirPath.
Definition qdir.cpp:1579
static QString cleanPath(const QString &path)
Returns path with directory separators normalized (that is, platform-native separators converted to "...
Definition qdir.cpp:2395
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:2051
@ Files
Definition qdir.h:22
@ NoSymLinks
Definition qdir.h:24
@ NoDotAndDotDot
Definition qdir.h:43
@ Dirs
Definition qdir.h:21
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
QString fileName() const
Returns the name of the file, excluding the path.
QString absoluteFilePath() const
Returns an absolute path including the file name.
QString canonicalPath() const
Returns the file's path canonical path (excluding the file name), i.e.
QString canonicalFilePath() const
Returns the canonical path including the file name, i.e.
QDir absoluteDir() const
Returns the file's absolute path as a QDir object.
QString path() const
Returns the file's path.
bool exists() const
Returns true if the file exists; otherwise returns false.
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
bool setPermissions(Permissions permissionSpec) override
Sets the permissions for the file to the permissions specified.
Definition qfile.cpp:1136
bool link(const QString &newName)
Creates a link named linkName that points to the file currently specified by fileName().
Definition qfile.cpp:699
bool copy(const QString &newName)
Copies the file named fileName() to newName.
Definition qfile.cpp:744
bool remove()
Removes the file specified by fileName().
Definition qfile.cpp:419
QString fileName() const override
Returns the name set by setFileName() or to the QFile constructors.
Definition qfile.cpp:277
Permissions permissions() const override
\reimp
Definition qfile.cpp:1107
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qfile.cpp:351
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
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)
Definition qlist.h:74
bool isEmpty() const noexcept
Definition qlist.h:390
qsizetype length() const noexcept
Definition qlist.h:388
value_type takeFirst()
Definition qlist.h:549
void append(parameter_type t)
Definition qlist.h:441
\inmodule QtCore \reentrant
QRegularExpressionMatch match(const QString &subject, qsizetype offset=0, MatchType matchType=NormalMatch, MatchOptions matchOptions=NoMatchOption) const
Attempts to match the regular expression against the given subject string, starting at the position o...
Definition qset.h:18
bool remove(const T &value)
Definition qset.h:63
bool contains(const T &value) const
Definition qset.h:71
iterator insert(const T &value)
Definition qset.h:155
QSet< T > & subtract(const QSet< T > &other)
Definition qset.h:273
\inmodule QtCore
Definition qstack.h:13
T pop()
Removes the top item from the stack and returns it.
Definition qstack.h:18
void push(const T &t)
Adds element t to the top of the stack.
Definition qstack.h:17
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
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:5299
void chop(qsizetype n)
Removes n characters from the end of the string.
Definition qstring.cpp:6180
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5710
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:7956
bool isNull() const
Returns true if this string is null; otherwise returns false.
Definition qstring.h:898
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
Definition qstring.cpp:5204
QString simplified() const &
Definition qstring.h:384
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:5350
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
qsizetype count(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4732
bool contains(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.h:1217
QString & append(QChar c)
Definition qstring.cpp:3227
static QString static QString qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4420
QString trimmed() const &
Definition qstring.h:380
QString & prepend(QChar c)
Definition qstring.h:411
qsizetype length() const
Returns the number of characters in this string.
Definition qstring.h:187
\inmodule QtCore
Definition qvariant.h:64
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:127
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:160
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 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:630
bool runCodesign
Definition shared.cpp:30
QSet< QString > codesignBundle(const QString &identity, const QString &appBundlePath, QList< QString > additionalBinariesContainingRpaths)
Definition shared.cpp:1404
void addRPath(const QString &rpath, const QString &binaryPath)
Definition shared.cpp:856
void codesignFile(const QString &identity, const QString &filePath)
Definition shared.cpp:1368
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:1260
QString extraEntitlements
Definition shared.cpp:33
QStringList librarySearchPath
Definition shared.cpp:31
void createQtConf(const QString &appBundlePath)
Definition shared.cpp:1197
const QString bundleFrameworkDirectory
Definition shared.cpp:67
bool deployFramework
Definition shared.cpp:38
void runStrip(const QString &binaryPath)
Definition shared.cpp:913
void fixupFramework(const QString &frameworkName)
Definition shared.cpp:1579
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:1250
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:861
QString getLibInfix(const QStringList &deployedFrameworks)
Definition shared.cpp:1055
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:821
OtoolInfo findDependencyInfo(const QString &binaryPath)
Definition shared.cpp:146
void createDiskImage(const QString &appBundlePath, const QString &filesystemType)
Definition shared.cpp:1543
void runInstallNameTool(QStringList options)
Definition shared.cpp:810
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:742
bool runStripEnabled
Definition shared.cpp:28
void stripAppBinary(const QString &bundlePath)
Definition shared.cpp:929
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:1072
void deployQmlImport(const QString &appBundlePath, const QList< QString > &rpaths, const QString &importSourcePath, const QString &importName)
Definition shared.cpp:1238
DeploymentInfo deployQtFrameworks(QList< FrameworkInfo > frameworks, const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath)
Definition shared.cpp:954
void recursiveCopyAndDeploy(const QString &appBundlePath, const QList< QString > &rpaths, const QString &sourcePath, const QString &destinationPath)
Definition shared.cpp:656
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:829
QString findEntitlementsFile(const QString &path)
Definition shared.cpp:464
void codesign(const QString &identity, const QString &appBundlePath)
Definition shared.cpp:1539
bool secureTimestamp
Definition shared.cpp:35
QString copyDylib(const FrameworkInfo &framework, const QString path)
Definition shared.cpp:715
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 bundle
QFileInfo info(fileName)
[8]
QString dir
[11]
QStringList files
[8]
QJSValueList args
bool contains(const AT &t) const noexcept
Definition qlist.h:44