3#include <QCoreApplication>
15#include <QDirIterator>
16#include <QLibraryInfo>
17#include <QJsonDocument>
21#include <QRegularExpression>
26#include <CoreFoundation/CoreFoundation.h>
46using namespace Qt::StringLiterals;
50 return ((a.frameworkPath == b.frameworkPath) && (a.binaryPath == b.binaryPath));
55 debug <<
"Framework name" << info.frameworkName <<
"\n";
56 debug <<
"Framework directory" << info.frameworkDirectory <<
"\n";
57 debug <<
"Framework path" << info.frameworkPath <<
"\n";
58 debug <<
"Binary directory" << info.binaryDirectory <<
"\n";
59 debug <<
"Binary name" << info.binaryName <<
"\n";
60 debug <<
"Binary path" << info.binaryPath <<
"\n";
61 debug <<
"Version" << info.version <<
"\n";
62 debug <<
"Install name" << info.installName <<
"\n";
63 debug <<
"Deployed install name" << info.deployedInstallName <<
"\n";
64 debug <<
"Source file Path" << info.sourceFilePath <<
"\n";
65 debug <<
"Framework Destination Directory (relative to bundle)" << info.frameworkDestinationDirectory <<
"\n";
66 debug <<
"Binary Destination Directory (relative to bundle)" << info.binaryDestinationDirectory <<
"\n";
75 debug <<
"Application bundle path" << info.path <<
"\n";
76 debug <<
"Binary path" << info.binaryPath <<
"\n";
77 debug <<
"Additional libraries" << info.libraryPaths <<
"\n";
83 if (QFile::exists(to)) {
87 qDebug() <<
"File exists, skip copy:" << to;
92 if (QFile::copy(from, to)) {
94 dest.setPermissions(dest.permissions() | QFile::WriteOwner | QFile::WriteUser);
102 if (toFile.permissions() & QFile::WriteOwner)
105 if (!toFile.setPermissions(toFile.permissions() | QFile::WriteOwner)) {
106 LogError() <<
"Failed to set u+w permissions on target file: " << to;
112 LogError() <<
"file copy failed from" << from;
120 if (QFile::exists(link)) {
121 if (QFile(link).symLinkTarget().isEmpty())
122 LogError() << link <<
"exists but it's a file.";
124 LogNormal() <<
"Symlink exists, skipping:" << link;
126 }
else if (QFile::link(file, link)) {
131 LogError() <<
"failed to symlink" << link;
141 if (QFile infoPlist(infoPlistPath); infoPlist.open(QIODevice::ReadOnly)) {
142 QByteArray contents = infoPlist.readAll();
144 QSaveFile writableInfoPlist(infoPlistPath);
145 bool success = writableInfoPlist.open(QIODevice::WriteOnly | QIODevice::Truncate);
147 contents.replace(
"_debug",
"");
148 writableInfoPlist.write(contents);
149 success = writableInfoPlist.commit();
152 LogError() <<
"Failed to write Info.plist file" << infoPlistPath;
155 LogError() <<
"Failed to read Info.plist file" << infoPlistPath;
162 info.binaryPath = binaryPath;
165 LogDebug() <<
" inspecting" << binaryPath;
167 otool.start(
"otool", QStringList() <<
"-L" << binaryPath);
168 otool.waitForFinished(-1);
170 if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) {
171 LogError() << otool.readAllStandardError();
175 static const QRegularExpression regexp(QStringLiteral(
176 "^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "
177 "current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\)$"));
179 QString output = otool.readAllStandardOutput();
180 QStringList outputLines = output.split(
"\n", Qt::SkipEmptyParts);
181 if (outputLines.size() < 2) {
182 LogError() <<
"Could not parse otool output:" << output;
186 outputLines.removeFirst();
187 if (binaryPath.contains(
".framework/") || binaryPath.endsWith(
".dylib")) {
188 const auto match = regexp.match(outputLines.constFirst());
189 if (match.hasMatch()) {
190 QString installname = match.captured(1);
191 if (QFileInfo(binaryPath).fileName() == QFileInfo(installname).fileName()) {
192 info.installName = installname;
193 info.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
194 info.currentVersion = QVersionNumber::fromString(match.captured(3));
195 outputLines.removeFirst();
197 info.installName = binaryPath;
200 LogDebug() <<
"Could not parse otool output line:" << outputLines.constFirst();
201 outputLines.removeFirst();
205 for (
const QString &outputLine : outputLines) {
206 const auto match = regexp.match(outputLine);
207 if (match.hasMatch()) {
208 if (match.captured(1) == info.installName)
211 dylib.binaryPath = match.captured(1);
212 dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
213 dylib.currentVersion = QVersionNumber::fromString(match.captured(3));
214 info.dependencies << dylib;
216 LogDebug() <<
"Could not parse otool output line:" << outputLine;
226 QString trimmed = line.trimmed();
228 if (trimmed.isEmpty())
232 if (trimmed.startsWith(
"/System/Library/") ||
233 (trimmed.startsWith(
"/usr/lib/") && trimmed.contains(
"libQt") ==
false)
234 || trimmed.startsWith(
"@executable_path") || trimmed.startsWith(
"@loader_path"))
238 if (trimmed.startsWith(
"@rpath/")) {
239 QString rpathRelativePath = trimmed.mid(QStringLiteral(
"@rpath/").length());
240 bool foundInsideBundle =
false;
241 for (
const QString &rpath : std::as_const(rpaths)) {
242 QString path = QDir::cleanPath(rpath +
"/" + rpathRelativePath);
244 if (!appBundlePath.isEmpty()) {
245 if (QDir::isAbsolutePath(appBundlePath)) {
246 if (path.startsWith(QDir::cleanPath(appBundlePath) +
"/")) {
247 foundInsideBundle =
true;
251 if (path.startsWith(QDir::cleanPath(QDir::currentPath() +
"/" + appBundlePath) +
"/")) {
252 foundInsideBundle =
true;
258 FrameworkInfo resolvedInfo = parseOtoolLibraryLine(path, appBundlePath, rpaths, useDebugLibs);
259 if (!resolvedInfo.frameworkName.isEmpty() && QFile::exists(resolvedInfo.frameworkPath)) {
260 resolvedInfo.rpathUsed = rpath;
261 resolvedInfo.installName = trimmed;
265 if (!rpaths.isEmpty() && !foundInsideBundle) {
266 LogError() <<
"Cannot resolve rpath" << trimmed;
272 enum State {QtPath, FrameworkName, DylibName, Version, FrameworkBinary, End};
273 State state = QtPath;
277 QString suffix = useDebugLibs ?
"_debug" :
"";
280 QStringList parts = trimmed.split(
"/");
281 while (part < parts.count()) {
282 const QString currentPart = parts.at(part).simplified();
284 if (currentPart ==
"")
287 if (state == QtPath) {
289 if (part < parts.count() && parts.at(part).contains(
".dylib")) {
290 info.frameworkDirectory +=
"/" + QString(qtPath + currentPart +
"/").simplified();
293 }
else if (part < parts.count() && parts.at(part).endsWith(
".framework")) {
294 info.frameworkDirectory +=
"/" + QString(qtPath +
"lib/").simplified();
295 state = FrameworkName;
297 }
else if (trimmed.startsWith(
'/') ==
false) {
298 QStringList partsCopy = parts;
299 partsCopy.removeLast();
300 for (QString &path : librarySearchPath) {
301 if (!path.endsWith(
"/"))
303 QString nameInPath = path + parts.join(u'/');
304 if (QFile::exists(nameInPath)) {
305 info.frameworkDirectory = path + partsCopy.join(u'/');
309 if (currentPart.contains(
".framework")) {
310 if (info.frameworkDirectory.isEmpty())
311 info.frameworkDirectory =
"/Library/Frameworks/" + partsCopy.join(u'/');
312 if (!info.frameworkDirectory.endsWith(
"/"))
313 info.frameworkDirectory +=
"/";
314 state = FrameworkName;
317 }
else if (currentPart.contains(
".dylib")) {
318 if (info.frameworkDirectory.isEmpty())
319 info.frameworkDirectory =
"/usr/lib/" + partsCopy.join(u'/');
320 if (!info.frameworkDirectory.endsWith(
"/"))
321 info.frameworkDirectory +=
"/";
327 qtPath += (currentPart +
"/");
329 }
if (state == FrameworkName) {
332 name.chop(QString(
".framework").length());
334 info.frameworkName = currentPart;
338 }
if (state == DylibName) {
341 info.frameworkName = name;
342 info.binaryName = name.contains(suffix) ? name : name.left(name.indexOf(
'.')) + suffix + name.mid(name.indexOf(
'.'));
343 info.deployedInstallName =
"@executable_path/../Frameworks/" + info.binaryName;
344 info.frameworkPath = info.frameworkDirectory + info.binaryName;
345 info.sourceFilePath = info.frameworkPath;
346 info.frameworkDestinationDirectory = bundleFrameworkDirectory +
"/";
347 info.binaryDestinationDirectory = info.frameworkDestinationDirectory;
348 info.binaryDirectory = info.frameworkDirectory;
349 info.binaryPath = info.frameworkPath;
353 }
else if (state == Version) {
354 info.version = currentPart;
355 info.binaryDirectory =
"Versions/" + info.version;
356 info.frameworkPath = info.frameworkDirectory + info.frameworkName;
357 info.frameworkDestinationDirectory = bundleFrameworkDirectory +
"/" + info.frameworkName;
358 info.binaryDestinationDirectory = info.frameworkDestinationDirectory +
"/" + info.binaryDirectory;
359 state = FrameworkBinary;
360 }
else if (state == FrameworkBinary) {
361 info.binaryName = currentPart.contains(suffix) ? currentPart : currentPart + suffix;
362 info.binaryPath =
"/" + info.binaryDirectory +
"/" + info.binaryName;
363 info.deployedInstallName =
"@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath;
364 info.sourceFilePath = info.frameworkPath + info.binaryPath;
366 }
else if (state == End) {
371 if (!info.sourceFilePath.isEmpty() && QFile::exists(info.sourceFilePath)) {
372 info.installName = findDependencyInfo(info.sourceFilePath).installName;
373 if (info.installName.startsWith(
"@rpath/"))
374 info.deployedInstallName = info.installName;
385 CFStringRef bundlePath = appBundlePath.toCFString();
386 CFURLRef bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath,
387 kCFURLPOSIXPathStyle,
true);
388 CFRelease(bundlePath);
389 CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, bundleURL);
391 CFURLRef executableURL = CFBundleCopyExecutableURL(bundle);
393 CFURLRef absoluteExecutableURL = CFURLCopyAbsoluteURL(executableURL);
394 if (absoluteExecutableURL) {
395 CFStringRef executablePath = CFURLCopyFileSystemPath(absoluteExecutableURL,
396 kCFURLPOSIXPathStyle);
397 if (executablePath) {
398 binaryPath = QString::fromCFString(executablePath);
399 CFRelease(executablePath);
401 CFRelease(absoluteExecutableURL);
403 CFRelease(executableURL);
407 CFRelease(bundleURL);
410 if (QFile::exists(binaryPath))
412 LogError() <<
"Could not find bundle binary for" << appBundlePath;
418 QStringList frameworks;
422 QString searchPath = appBundlePath +
"/Contents/Frameworks/";
423 QDirIterator iter(searchPath, QStringList() << QString::fromLatin1(
"*.framework"),
424 QDir::Dirs | QDir::NoSymLinks);
425 while (iter.hasNext()) {
427 frameworks << iter.fileInfo().fileName();
435 QStringList frameworks;
436 QString searchPath = appBundlePath +
"/Contents/Frameworks/";
437 QDirIterator iter(searchPath, QStringList() << QString::fromLatin1(
"*.framework"),
438 QDir::Dirs | QDir::NoSymLinks);
439 while (iter.hasNext()) {
441 frameworks << iter.fileInfo().filePath();
451 QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1(
"*.dylib") << QString::fromLatin1(
"*.so"),
452 QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
453 while (iter.hasNext()) {
455 result << iter.fileInfo().filePath();
464 QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1(
"*"),
465 QDir::Files, QDirIterator::Subdirectories);
467 while (iter.hasNext()) {
469 if (iter.fileInfo().isSymLink())
471 result << (absolutePath ? iter.fileInfo().absoluteFilePath() : iter.fileInfo().filePath());
479 QDirIterator iter(path, QStringList() << QString::fromLatin1(
"*.entitlements"),
480 QDir::Files, QDirIterator::Subdirectories);
482 while (iter.hasNext()) {
484 if (iter.fileInfo().isSymLink())
488 return iter.fileInfo().absoluteFilePath();
496 QList<FrameworkInfo> libraries;
497 for (
const DylibInfo &dylibInfo : dependencies) {
498 FrameworkInfo info = parseOtoolLibraryLine(dylibInfo.binaryPath, appBundlePath, rpaths, useDebugLibs);
499 if (info.frameworkName.isEmpty() ==
false) {
502 libraries.append(info);
510 if (path.startsWith(
'@')) {
511 if (path.startsWith(QStringLiteral(
"@executable_path/"))) {
513 if (QDir::isAbsolutePath(executablePath)) {
514 return QDir::cleanPath(QFileInfo(executablePath).path() + path.mid(QStringLiteral(
"@executable_path").length()));
516 return QDir::cleanPath(QDir::currentPath() +
"/" +
517 QFileInfo(executablePath).path() + path.mid(QStringLiteral(
"@executable_path").length()));
519 }
else if (path.startsWith(QStringLiteral(
"@loader_path"))) {
521 if (QDir::isAbsolutePath(loaderPath)) {
522 return QDir::cleanPath(QFileInfo(loaderPath).path() + path.mid(QStringLiteral(
"@loader_path").length()));
524 return QDir::cleanPath(QDir::currentPath() +
"/" +
525 QFileInfo(loaderPath).path() + path.mid(QStringLiteral(
"@loader_path").length()));
528 LogError() <<
"Unexpected prefix" << path;
536 QList<QString> rpaths;
539 otool.start(
"otool", QStringList() <<
"-l" << path);
540 otool.waitForFinished();
542 if (otool.exitCode() != 0) {
543 LogError() << otool.readAllStandardError();
546 if (resolve && executablePath.isEmpty()) {
547 executablePath = path;
550 QString output = otool.readAllStandardOutput();
551 QStringList outputLines = output.split(
"\n");
553 for (
auto i = outputLines.cbegin(), end = outputLines.cend(); i != end; ++i) {
554 if (i->contains(
"cmd LC_RPATH") && ++i != end &&
555 i->contains(
"cmdsize") && ++i != end) {
556 const QString &rpathCmd = *i;
557 int pathStart = rpathCmd.indexOf(
"path ");
558 int pathEnd = rpathCmd.indexOf(
" (");
559 if (pathStart >= 0 && pathEnd >= 0 && pathStart < pathEnd) {
560 QString rpath = rpathCmd.mid(pathStart + 5, pathEnd - pathStart - 5);
562 rpaths << resolveDyldPrefix(rpath, path, executablePath);
575 const OtoolInfo info = findDependencyInfo(path);
576 QList<QString> allRPaths = rpaths + getBinaryRPaths(path);
577 allRPaths.removeDuplicates();
578 return getQtFrameworks(info.dependencies, appBundlePath, allRPaths, useDebugLibs);
583 QList<FrameworkInfo> result;
584 QSet<QString> existing;
585 for (
const QString &path : paths) {
586 for (
const FrameworkInfo &info : getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) {
587 if (!existing.contains(info.frameworkPath)) {
588 existing.insert(info.frameworkPath);
598 const QList<QString> &additionalBinariesContainingRpaths)
600 QStringList binaries;
602 const auto dependencies = findDependencyInfo(path).dependencies;
604 bool rpathsLoaded =
false;
605 QList<QString> rpaths;
608 for (
const DylibInfo &info : dependencies) {
609 QString trimmedLine = info.binaryPath;
610 if (trimmedLine.startsWith(
"@executable_path/")) {
611 QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral(
"@executable_path/").length()));
613 binaries.append(binary);
614 }
else if (trimmedLine.startsWith(
"@loader_path/")) {
615 QString binary = QDir::cleanPath(QFileInfo(path).path() +
"/" + trimmedLine.mid(QStringLiteral(
"@loader_path/").length()));
617 binaries.append(binary);
618 }
else if (trimmedLine.startsWith(
"@rpath/")) {
620 rpaths = getBinaryRPaths(path,
true, executablePath);
621 for (
const QString &binaryPath : additionalBinariesContainingRpaths)
622 rpaths += getBinaryRPaths(binaryPath,
true);
623 rpaths.removeDuplicates();
626 bool resolved =
false;
627 for (
const QString &rpath : std::as_const(rpaths)) {
628 QString binary = QDir::cleanPath(rpath +
"/" + trimmedLine.mid(QStringLiteral(
"@rpath/").length()));
629 LogDebug() <<
"Checking for" << binary;
630 if (QFile::exists(binary)) {
631 binaries.append(binary);
636 if (!resolved && !rpaths.isEmpty()) {
637 LogError() <<
"Cannot resolve rpath" << trimmedLine;
647bool recursiveCopy(
const QString &sourcePath,
const QString &destinationPath,
648 const QRegularExpression &ignoreRegExp = QRegularExpression())
650 const QDir sourceDir(sourcePath);
651 if (!sourceDir.exists())
653 QDir().mkpath(destinationPath);
655 LogNormal() <<
"copy:" << sourcePath << destinationPath;
657 const QStringList files = sourceDir.entryList(QStringList() <<
"*", QDir::Files | QDir::NoDotAndDotDot);
658 const bool hasValidRegExp = ignoreRegExp.isValid() && ignoreRegExp.pattern().length() > 0;
659 for (
const QString &file : files) {
660 if (hasValidRegExp && ignoreRegExp.match(file).hasMatch())
662 const QString fileSourcePath = sourcePath +
"/" + file;
663 const QString fileDestinationPath = destinationPath +
"/" + file;
664 copyFilePrintStatus(fileSourcePath, fileDestinationPath);
667 const QStringList subdirs = sourceDir.entryList(QStringList() <<
"*", QDir::Dirs | QDir::NoDotAndDotDot);
668 for (
const QString &dir : subdirs) {
669 recursiveCopy(sourcePath +
"/" + dir, destinationPath +
"/" + dir);
674void recursiveCopyAndDeploy(
const QString &appBundlePath,
const QList<QString> &rpaths,
const QString &sourcePath,
const QString &destinationPath)
676 QDir().mkpath(destinationPath);
678 LogNormal() <<
"copy:" << sourcePath << destinationPath;
680 const QDir sourceDir(sourcePath);
682 const QStringList files = sourceDir.entryList(QStringList() << QStringLiteral(
"*"), QDir::Files | QDir::NoDotAndDotDot);
683 for (
const QString &file : files) {
684 if (file.endsWith(
"_debug.dylib"))
687 if (file.endsWith(
".qrc"))
690 const QString fileSourcePath = sourcePath + u'/' + file;
692 if (file.endsWith(QStringLiteral(
".dylib"))) {
704 QString fileDestinationDir = appBundlePath + QStringLiteral(
"/Contents/PlugIns/quick/");
705 QDir().mkpath(fileDestinationDir);
706 QString fileDestinationPath = fileDestinationDir + file;
709 QString linkDestinationPath = destinationPath + u'/' + file;
712 QString linkPath = QStringLiteral(
"PlugIns/quick/") + file;
713 int cdupCount = linkDestinationPath.count(QStringLiteral(
"/")) - appBundlePath.count(QStringLiteral(
"/"));
714 for (
int i = 0; i < cdupCount - 2; ++i)
715 linkPath.prepend(
"../");
717 if (copyFilePrintStatus(fileSourcePath, fileDestinationPath)) {
718 linkFilePrintStatus(linkPath, linkDestinationPath);
720 runStrip(fileDestinationPath);
721 bool useDebugLibs =
false;
722 bool useLoaderPath =
false;
723 QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, appBundlePath, rpaths, useDebugLibs);
724 deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath);
727 QString fileDestinationPath = destinationPath + u'/' + file;
728 copyFilePrintStatus(fileSourcePath, fileDestinationPath);
732 const QStringList subdirs = sourceDir.entryList(QStringList() << QStringLiteral(
"*"), QDir::Dirs | QDir::NoDotAndDotDot);
733 for (
const QString &dir : subdirs) {
734 if (dir.endsWith(
".dSYM"))
737 recursiveCopyAndDeploy(appBundlePath, rpaths, sourcePath + u'/' + dir, destinationPath + u'/' + dir);
743 if (!QFile::exists(framework.sourceFilePath)) {
744 LogError() <<
"no file at" << framework.sourceFilePath;
750 QString dylibDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory;
751 QString dylibDestinationBinaryPath = dylibDestinationDirectory + u'/' + framework.binaryName;
754 if (!QDir().mkpath(dylibDestinationDirectory)) {
755 LogError() <<
"could not create destination directory" << dylibDestinationDirectory;
760 if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled)
761 return dylibDestinationBinaryPath;
764 copyFilePrintStatus(framework.sourceFilePath, dylibDestinationBinaryPath);
765 return dylibDestinationBinaryPath;
770 if (!QFile::exists(framework.sourceFilePath)) {
771 LogError() <<
"no file at" << framework.sourceFilePath;
777 QString frameworkDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory;
778 QString frameworkBinaryDestinationDirectory = frameworkDestinationDirectory + u'/' + framework.binaryDirectory;
779 QString frameworkDestinationBinaryPath = frameworkBinaryDestinationDirectory + u'/' + framework.binaryName;
786 if (!QDir().mkpath(frameworkBinaryDestinationDirectory)) {
787 LogError() <<
"could not create destination directory" << frameworkBinaryDestinationDirectory;
797 copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath);
800 const QString resourcesSourcePath = framework.frameworkPath +
"/Resources";
801 const QString resourcesDestinationPath = frameworkDestinationDirectory +
"/Versions/" + framework.version +
"/Resources";
803 recursiveCopy(resourcesSourcePath, resourcesDestinationPath,
804 QRegularExpression(
"\\A(?:[^/]*\\.prl)\\z"));
805 const QString librariesSourcePath = framework.frameworkPath +
"/Libraries";
806 const QString librariesDestinationPath = frameworkDestinationDirectory +
"/Versions/" + framework.version +
"/Libraries";
807 bool createdLibraries = recursiveCopy(librariesSourcePath, librariesDestinationPath);
808 const QString helpersSourcePath = framework.frameworkPath +
"/Helpers";
809 const QString helpersDestinationPath = frameworkDestinationDirectory +
"/Versions/" + framework.version +
"/Helpers";
810 bool createdHelpers = recursiveCopy(helpersSourcePath, helpersDestinationPath);
817 linkFilePrintStatus(
"Versions/Current/" + framework.binaryName, frameworkDestinationDirectory +
"/" + framework.binaryName);
818 linkFilePrintStatus(
"Versions/Current/Resources", frameworkDestinationDirectory +
"/Resources");
819 if (createdLibraries)
820 linkFilePrintStatus(
"Versions/Current/Libraries", frameworkDestinationDirectory +
"/Libraries");
822 linkFilePrintStatus(
"Versions/Current/Helpers", frameworkDestinationDirectory +
"/Helpers");
823 linkFilePrintStatus(framework.version, frameworkDestinationDirectory +
"/Versions/Current");
827 const QString legacyInfoPlistPath = framework.frameworkPath +
"/Contents/Info.plist";
828 const QString correctInfoPlistPath = frameworkDestinationDirectory +
"/Resources/Info.plist";
829 if (QFile::exists(legacyInfoPlistPath)) {
830 copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
831 patch_debugInInfoPlist(correctInfoPlistPath);
833 return frameworkDestinationBinaryPath;
838 QProcess installNametool;
839 installNametool.start(
"install_name_tool", options);
840 installNametool.waitForFinished();
841 if (installNametool.exitCode() != 0) {
842 LogError() << installNametool.readAllStandardError();
843 LogError() << installNametool.readAllStandardOutput();
849 LogDebug() <<
"Using install_name_tool:";
850 LogDebug() <<
" change identification in" << binaryPath;
852 runInstallNameTool(QStringList() <<
"-id" << id << binaryPath);
857 const QString absBundlePath = QFileInfo(bundlePath).absoluteFilePath();
858 for (
const QString &binary : binaryPaths) {
859 QString deployedInstallName;
861 deployedInstallName =
"@loader_path/"_L1
862 + QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + u'/' + framework.binaryDestinationDirectory + u'/' + framework.binaryName);
864 deployedInstallName = framework.deployedInstallName;
866 changeInstallName(framework.installName, deployedInstallName, binary);
869 QFileInfo fileInfo= QFileInfo(framework.installName);
870 QString canonicalInstallName = fileInfo.canonicalFilePath();
871 if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) {
872 changeInstallName(canonicalInstallName, deployedInstallName, binary);
874 QString innerDependency = fileInfo.canonicalPath() +
"/" + fileInfo.fileName();
875 if (innerDependency != canonicalInstallName && innerDependency != framework.installName) {
876 changeInstallName(innerDependency, deployedInstallName, binary);
882void addRPath(
const QString &rpath,
const QString &binaryPath)
884 runInstallNameTool(QStringList() <<
"-add_rpath" << rpath << binaryPath);
887void deployRPaths(
const QString &bundlePath,
const QList<QString> &rpaths,
const QString &binaryPath,
bool useLoaderPath)
889 const QString absFrameworksPath = QFileInfo(bundlePath).absoluteFilePath()
890 +
"/Contents/Frameworks"_L1;
891 const QString relativeFrameworkPath = QFileInfo(binaryPath).absoluteDir().relativeFilePath(absFrameworksPath);
892 const QString loaderPathToFrameworks =
"@loader_path/"_L1 + relativeFrameworkPath;
893 bool rpathToFrameworksFound =
false;
895 QList<QString> binaryRPaths = getBinaryRPaths(binaryPath,
false);
896 for (
const QString &rpath : std::as_const(binaryRPaths)) {
897 if (rpath ==
"@executable_path/../Frameworks" ||
898 rpath == loaderPathToFrameworks) {
899 rpathToFrameworksFound =
true;
902 if (rpaths.contains(resolveDyldPrefix(rpath, binaryPath, binaryPath))) {
903 if (!args.contains(rpath))
904 args <<
"-delete_rpath" << rpath;
907 if (!args.length()) {
910 if (!rpathToFrameworksFound) {
911 if (!useLoaderPath) {
912 args <<
"-add_rpath" <<
"@executable_path/../Frameworks";
914 args <<
"-add_rpath" << loaderPathToFrameworks;
917 LogDebug() <<
"Using install_name_tool:";
918 LogDebug() <<
" change rpaths in" << binaryPath;
920 runInstallNameTool(QStringList() << args << binaryPath);
923void deployRPaths(
const QString &bundlePath,
const QList<QString> &rpaths,
const QStringList &binaryPaths,
bool useLoaderPath)
925 for (
const QString &binary : binaryPaths) {
926 deployRPaths(bundlePath, rpaths, binary, useLoaderPath);
930void changeInstallName(
const QString &oldName,
const QString &newName,
const QString &binaryPath)
932 if (newName == oldName) {
933 LogDebug() <<
"Skipping install_name_tool for" << binaryPath
934 <<
"as name is already" << newName;
937 LogDebug() <<
"Using install_name_tool:";
939 LogDebug() <<
" change reference" << oldName;
941 runInstallNameTool(QStringList() <<
"-change" << oldName << newName << binaryPath);
950 LogDebug() <<
" stripped" << binaryPath;
952 strip.start(
"strip", QStringList() <<
"-x" << binaryPath);
953 strip.waitForFinished();
954 if (strip.exitCode() != 0) {
955 LogError() << strip.readAllStandardError();
956 LogError() << strip.readAllStandardOutput();
962 runStrip(findAppBinary(bundlePath));
968 if (deployedFrameworks.contains(
"Qt"_L1 + module + libInFix +
".framework"_L1))
971 const QRegularExpression dylibRegExp(
"libQt[0-9]+"_L1
973 + (isDebug ?
"_debug" :
"")
974 +
".[0-9]+.dylib"_L1);
975 return deployedFrameworks.filter(dylibRegExp).size() > 0;
979
980
981
982
983
984
986 const QString &bundlePath,
const QStringList &binaryPaths,
bool useDebugLibs,
990 LogNormal() <<
"Deploying Qt frameworks found inside:" << binaryPaths;
991 QStringList copiedFrameworks;
994 deploymentInfo
.isFramework = bundlePath.contains(
".framework");
996 QList<QString> rpathsUsed;
998 while (frameworks.isEmpty() ==
false) {
1000 copiedFrameworks.append(framework.frameworkName);
1008 if (deploymentInfo.qtPath.isNull())
1009 deploymentInfo.qtPath = QLibraryInfo::paths(QLibraryInfo::PrefixPath).value(0);
1011 if (framework.frameworkDirectory.startsWith(bundlePath)) {
1012 LogError() << framework.frameworkName <<
"already deployed, skipping.";
1016 if (!framework.rpathUsed.isEmpty() && !rpathsUsed.contains(framework.rpathUsed)) {
1017 rpathsUsed.append(framework.rpathUsed);
1035 if (!framework.sourceFilePath.isEmpty()) {
1036 const QList<QString> sourceRPaths = getBinaryRPaths(framework.sourceFilePath,
true);
1037 for (
const QString &sourceRPath : sourceRPaths) {
1038 const QDir sourceRPathDir(sourceRPath);
1039 if (sourceRPathDir.exists() && !rpathsUsed.contains(sourceRPath)) {
1040 rpathsUsed.append(sourceRPath);
1046 const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
1047 : copyFramework(framework, bundlePath);
1050 changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath);
1053 if (deployedBinaryPath.isNull())
1056 runStrip(deployedBinaryPath);
1059 if (!framework.rpathUsed.length()) {
1060 changeIdentification(framework.deployedInstallName, deployedBinaryPath);
1064 QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
1066 for (
const FrameworkInfo &dependency : dependencies) {
1067 if (dependency.rpathUsed.isEmpty()) {
1068 changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath);
1069 }
else if (!rpathsUsed.contains(dependency.rpathUsed)) {
1070 rpathsUsed.append(dependency.rpathUsed);
1074 if (copiedFrameworks.contains(dependency.frameworkName) ==
false && frameworks.contains(dependency) ==
false) {
1075 frameworks.append(dependency);
1079 deploymentInfo.deployedFrameworks = copiedFrameworks;
1080 deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath);
1081 deploymentInfo.rpathsUsed += rpathsUsed;
1082 deploymentInfo.rpathsUsed.removeDuplicates();
1083 return deploymentInfo;
1089 applicationBundle.path = appBundlePath;
1090 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1091 applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
1092 QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
1093 << additionalExecutables;
1095 QList<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath,
true);
1096 allLibraryPaths.append(QLibraryInfo::paths(QLibraryInfo::LibrariesPath));
1097 allLibraryPaths.removeDuplicates();
1099 QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
1102 LogWarning() <<
"Could not find any external Qt frameworks to deploy in" << appBundlePath;
1103 LogWarning() <<
"Perhaps macdeployqt was already used on" << appBundlePath <<
"?";
1104 LogWarning() <<
"If so, you will need to rebuild" << appBundlePath <<
"before trying again.";
1107 return deployQtFrameworks(frameworks, applicationBundle.path, allBinaryPaths, useDebugLibs, !additionalExecutables.isEmpty());
1114 for (
const QString &framework : deployedFrameworks) {
1115 if (framework.startsWith(QStringLiteral(
"QtCore")) && framework.endsWith(QStringLiteral(
".framework")) &&
1116 !framework.contains(QStringLiteral(
"5Compat"))) {
1117 Q_ASSERT(framework.length() >= 16);
1119 const int lengthOfLibInfix = framework.length() - 16;
1120 if (lengthOfLibInfix)
1121 libInfix = framework.mid(6, lengthOfLibInfix);
1129 const QString pluginDestinationPath,
DeploymentInfo deploymentInfo,
bool useDebugLibs)
1131 LogNormal() <<
"Deploying plugins from" << pluginSourcePath;
1133 if (!pluginSourcePath.contains(deploymentInfo.pluginPath))
1137 QStringList pluginList;
1139 const auto addPlugins = [&pluginSourcePath,&pluginList,useDebugLibs](
const QString &subDirectory,
1140 const std::function<
bool(QString)> &predicate = std::function<
bool(QString)>()) {
1142 LogNormal() <<
"Skipping excluded plugin type" << subDirectory;
1145 const QStringList libs = QDir(pluginSourcePath + u'/' + subDirectory)
1146 .entryList({QStringLiteral(
"*.dylib")});
1147 for (
const QString &lib : libs) {
1148 if (lib.endsWith(QStringLiteral(
"_debug.dylib")) != useDebugLibs)
1151 QString pluginName = lib;
1152 pluginName.chop(useDebugLibs ? 12 : 6);
1153 if (pluginName.startsWith(
"lib"_L1))
1154 pluginName.remove(0, 3);
1155 if (excludedPlugins.contains(pluginName)) {
1156 LogNormal() <<
"Skipping excluded plugin" << subDirectory + u'/' + lib;
1159 const QString plugin = subDirectory + u'/' + lib;
1160 if ((!predicate || predicate(lib)) && !pluginList.contains(plugin))
1161 pluginList.append(plugin);
1166 addPlugins(QStringLiteral(
"platforms"), [](
const QString &lib) {
1168 if (!lib.contains(QStringLiteral(
"cocoa")))
1174 addPlugins(QStringLiteral(
"printsupport"));
1177 addPlugins(QStringLiteral(
"styles"));
1180 const QString libInfix = getLibInfix(deploymentInfo.deployedFrameworks);
1183 if (deploymentInfo.containsModule(
"Network", libInfix)) {
1184 addPlugins(QStringLiteral(
"tls"));
1185 addPlugins(QStringLiteral(
"networkinformation"));
1189 const bool usesSvg = deploymentInfo.containsModule(
"Svg", libInfix);
1190 addPlugins(QStringLiteral(
"imageformats"), [usesSvg](
const QString &lib) {
1191 if (lib.contains(QStringLiteral(
"qsvg")) && !usesSvg)
1196 addPlugins(QStringLiteral(
"iconengines"));
1199 if (deploymentInfo.containsModule(
"Gui", libInfix)) {
1200 addPlugins(QStringLiteral(
"platforminputcontexts"), [&addPlugins](
const QString &lib) {
1202 if (lib.startsWith(QStringLiteral(
"libqtvirtualkeyboard")))
1203 addPlugins(QStringLiteral(
"virtualkeyboard"));
1209 if (deploymentInfo.containsModule(
"Sql", libInfix)) {
1210 addPlugins(QStringLiteral(
"sqldrivers"), [](
const QString &lib) {
1211 if (lib.startsWith(QStringLiteral(
"libqsqlodbc")) || lib.startsWith(QStringLiteral(
"libqsqlpsql"))) {
1212 LogWarning() <<
"Plugin" << lib <<
"uses private API and is not Mac App store compliant.";
1223 if (deploymentInfo.containsModule(
"WebView", libInfix)) {
1224 addPlugins(QStringLiteral(
"webview"), [](
const QString &lib) {
1225 if (lib.startsWith(QStringLiteral(
"libqtwebview_webengine"))) {
1226 LogWarning() <<
"Plugin" << lib <<
"uses QtWebEngine and is not Mac App store compliant.";
1237 static const std::map<QString, std::vector<QString>> map {
1238 {QStringLiteral(
"Multimedia"), {QStringLiteral(
"multimedia")}},
1239 {QStringLiteral(
"3DRender"), {QStringLiteral(
"sceneparsers"), QStringLiteral(
"geometryloaders"), QStringLiteral(
"renderers")}},
1240 {QStringLiteral(
"3DQuickRender"), {QStringLiteral(
"renderplugins")}},
1241 {QStringLiteral(
"Positioning"), {QStringLiteral(
"position")}},
1242 {QStringLiteral(
"Location"), {QStringLiteral(
"geoservices")}},
1243 {QStringLiteral(
"TextToSpeech"), {QStringLiteral(
"texttospeech")}},
1244 {QStringLiteral(
"SerialBus"), {QStringLiteral(
"canbus")}},
1247 for (
const auto &it : map) {
1248 if (deploymentInfo.containsModule(it.first, libInfix)) {
1249 for (
const auto &pluginType : it.second) {
1250 addPlugins(pluginType);
1256 for (
const QString &pluginType : std::as_const(includedPluginTypes)) {
1257 if (!QDir(pluginSourcePath + u'/' + pluginType).exists()) {
1258 LogWarning() <<
"Could not find the included plugin type" << pluginType;
1261 addPlugins(pluginType);
1265 const QString libSuffix = useDebugLibs ? u"_debug.dylib"_s : u".dylib"_s;
1266 for (
const QString &pluginName : std::as_const(includedPlugins)) {
1267 const QStringList types = QDir(pluginSourcePath).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
1269 for (
const QString &type : types) {
1270 if (excludedPluginTypes.contains(type))
1272 const QString plugin = type +
"/lib"_L1 + pluginName + libSuffix;
1273 if (!QFile::exists(pluginSourcePath + u'/' + plugin))
1276 if (!pluginList.contains(plugin))
1277 pluginList.append(plugin);
1280 LogWarning() <<
"Could not find the included plugin" << pluginName;
1284 for (
const QString &plugin : pluginList) {
1285 QString sourcePath = pluginSourcePath +
"/" + plugin;
1286 const QString destinationPath = pluginDestinationPath +
"/" + plugin;
1288 dir.mkpath(QFileInfo(destinationPath).path());
1290 if (copyFilePrintStatus(sourcePath, destinationPath)) {
1291 runStrip(destinationPath);
1292 QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
1293 deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
1301 applicationBundle.path = appBundlePath;
1302 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1304 const QString pluginDestinationPath = appBundlePath +
"/" +
"Contents/PlugIns";
1305 deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
1308void deployQmlImport(
const QString &appBundlePath,
const QList<QString> &rpaths,
const QString &importSourcePath,
const QString &importName)
1310 QString importDestinationPath = appBundlePath +
"/Contents/Resources/qml/" + importName;
1314 if (QDir().exists(importDestinationPath))
1317 recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath);
1322 QVariantMap import1 = v1.toMap();
1323 QVariantMap import2 = v2.toMap();
1324 QString path1 = import1[
"path"].toString();
1325 QString path2 = import2[
"path"].toString();
1326 return path1 < path2;
1333 LogNormal() <<
"Deploying QML imports ";
1334 LogNormal() <<
"Application QML file path(s) is" << qmlDirs;
1335 LogNormal() <<
"QML module search path(s) is" << qmlImportPaths;
1338 QString qmlImportScannerPath = [] {
1339 const QStringList libexecs = QLibraryInfo::paths(QLibraryInfo::LibraryExecutablesPath);
1340 for (
const QString &dir : libexecs) {
1341 const QString p = QDir::cleanPath(dir +
"/qmlimportscanner");
1342 if (QFile::exists(p))
1349 if (qmlImportScannerPath.isEmpty())
1350 qmlImportScannerPath = QCoreApplication::applicationDirPath() +
"/qmlimportscanner";
1353 if (!QFile::exists(qmlImportScannerPath)) {
1354 LogError() <<
"qmlimportscanner not found at" << qmlImportScannerPath;
1355 LogError() <<
"Rebuild qtdeclarative/tools/qmlimportscanner";
1361 QStringList argumentList;
1362 for (
const QString &qmlDir : qmlDirs) {
1363 argumentList.append(
"-rootPath");
1364 argumentList.append(qmlDir);
1366 for (
const QString &importPath : qmlImportPaths)
1367 argumentList <<
"-importPath" << importPath;
1368 for (
const QString &importPath : QLibraryInfo::paths(QLibraryInfo::QmlImportsPath))
1369 argumentList <<
"-importPath" << importPath;
1372 QProcess qmlImportScanner;
1373 qmlImportScanner.start(qmlImportScannerPath, argumentList);
1374 if (!qmlImportScanner.waitForStarted()) {
1375 LogError() <<
"Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
1378 qmlImportScanner.waitForFinished(-1);
1381 qmlImportScanner.setReadChannel(QProcess::StandardError);
1382 QByteArray errors = qmlImportScanner.readAll();
1383 if (!errors.isEmpty()) {
1384 LogWarning() <<
"QML file parse error (deployment will continue):";
1389 qmlImportScanner.setReadChannel(QProcess::StandardOutput);
1390 QByteArray json = qmlImportScanner.readAll();
1391 QJsonDocument doc = QJsonDocument::fromJson(json);
1392 if (!doc.isArray()) {
1393 LogError() <<
"qmlimportscanner output error. Expected json array, got:";
1401 QVariantList array = doc.array().toVariantList();
1402 std::sort(array.begin(), array.end(), importLessThan);
1405 for (
const QVariant &importValue : array) {
1406 QVariantMap import = importValue.toMap();
1407 QString name = import[
"name"].toString();
1408 QString path = import[
"path"].toString();
1409 QString type = import[
"type"].toString();
1411 LogNormal() <<
"Deploying QML import" << name;
1414 if (name.isEmpty() || path.isEmpty()) {
1415 LogNormal() <<
" Skip import: name or path is empty";
1422 if (type != QStringLiteral(
"module")) {
1423 LogNormal() <<
" Skip non-module import";
1431 name.replace(u'.', u'/');
1432 int secondTolast = path.length() - 2;
1433 QString version = path.mid(secondTolast);
1434 if (version.startsWith(u'.'))
1435 name.append(version);
1437 deployQmlImport(appBundlePath, deploymentInfo.rpathsUsed, path, name);
1448 QString codeSignLogMessage =
"codesign";
1450 codeSignLogMessage +=
", enable hardened runtime";
1452 codeSignLogMessage +=
", include secure timestamp";
1453 if (!extraEntitlements.isEmpty())
1454 codeSignLogMessage += QStringLiteral(
", include entitlements (%1)").arg(extraEntitlements);
1455 LogNormal() << codeSignLogMessage << filePath;
1457 QStringList codeSignOptions = {
"--preserve-metadata=identifier,entitlements",
"--force",
"-s",
1458 identity, filePath };
1460 codeSignOptions <<
"-o" <<
"runtime";
1463 codeSignOptions <<
"--timestamp";
1465 if (!extraEntitlements.isEmpty())
1466 codeSignOptions <<
"--entitlements" << extraEntitlements;
1469 codesign.start(
"codesign", codeSignOptions);
1470 codesign.waitForFinished(-1);
1472 QByteArray err = codesign.readAllStandardError();
1473 if (codesign.exitCode() > 0) {
1474 LogError() <<
"Codesign signing error:";
1476 }
else if (!err.isEmpty()) {
1482 const QString &appBundlePath,
1483 QList<QString> additionalBinariesContainingRpaths)
1493 LogNormal() <<
"Signing" << appBundlePath <<
"with identity" << identity;
1495 QStack<QString> pendingBinaries;
1496 QSet<QString> pendingBinariesSet;
1497 QSet<QString> signedBinaries;
1501 QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath();
1502 QString rootBinariesPath = appBundleAbsolutePath +
"/Contents/MacOS/";
1503 QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() <<
"*", QDir::Files);
1506 QString appBinary = findAppBinary(appBundleAbsolutePath);
1507 QString appBinaryName = QFileInfo(appBinary).fileName();
1508 if (
int appBinaryIdx = foundRootBinaries.indexOf(appBinaryName); appBinaryIdx > 0) {
1509 foundRootBinaries.swapItemsAt(0, appBinaryIdx);
1510 LogDebug() <<
"swapped appBinary to start of list";
1512 LogDebug() <<
"App binary is" << appBinaryName;
1513 LogDebug() <<
"Binaries in" << rootBinariesPath <<
"are" << foundRootBinaries;
1515 for (
const QString &binary : foundRootBinaries) {
1516 QString binaryPath = rootBinariesPath + binary;
1517 pendingBinaries.push(binaryPath);
1518 pendingBinariesSet.insert(binaryPath);
1519 additionalBinariesContainingRpaths.append(binaryPath);
1522 bool getAbsoltuePath =
true;
1523 QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath +
"/Contents/PlugIns/", getAbsoltuePath);
1524 for (
const QString &binary : foundPluginBinaries) {
1525 pendingBinaries.push(binary);
1526 pendingBinariesSet.insert(binary);
1530 QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath);
1531 for (
const QString &frameworkPath : frameworkPaths) {
1535 QDirIterator helpersIterator(frameworkPath, QStringList() << QString::fromLatin1(
"Helpers"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1536 while (helpersIterator.hasNext()) {
1537 helpersIterator.next();
1538 QString helpersPath = helpersIterator.filePath();
1539 QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() <<
"*.app", QDir::Dirs);
1540 for (
const QString &innerBundleName : innerBundleNames)
1541 signedBinaries += codesignBundle(identity,
1542 helpersPath +
"/" + innerBundleName,
1543 additionalBinariesContainingRpaths);
1548 QDirIterator librariesIterator(frameworkPath, QStringList() << QString::fromLatin1(
"Libraries"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1549 while (librariesIterator.hasNext()) {
1550 librariesIterator.next();
1551 QString librariesPath = librariesIterator.filePath();
1552 QStringList bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath);
1553 for (
const QString &binary : bundleFiles) {
1554 pendingBinaries.push(binary);
1555 pendingBinariesSet.insert(binary);
1561 while (!pendingBinaries.isEmpty()) {
1562 QString binary = pendingBinaries.pop();
1563 if (signedBinaries.contains(binary))
1567 QStringList dependencies = getBinaryDependencies(rootBinariesPath, binary,
1568 additionalBinariesContainingRpaths);
1569 dependencies = QSet<QString>(dependencies.begin(), dependencies.end())
1570 .subtract(signedBinaries)
1571 .subtract(pendingBinariesSet)
1574 if (!dependencies.isEmpty()) {
1575 pendingBinaries.push(binary);
1576 pendingBinariesSet.insert(binary);
1577 int dependenciesSkipped = 0;
1578 for (
const QString &dependency : std::as_const(dependencies)) {
1583 if (!dependency.startsWith(appBundleAbsolutePath)) {
1584 ++dependenciesSkipped;
1585 LogNormal() <<
"Skipping outside dependency: " << dependency;
1588 pendingBinaries.push(dependency);
1589 pendingBinariesSet.insert(dependency);
1594 if (dependenciesSkipped == dependencies.size()) {
1595 pendingBinaries.pop();
1602 extraEntitlements = findEntitlementsFile(appBundleAbsolutePath +
"/Contents/Resources/");
1605 codesignFile(identity, binary);
1606 signedBinaries.insert(binary);
1607 pendingBinariesSet.remove(binary);
1610 LogNormal() <<
"Finished codesigning " << appBundlePath <<
"with identity" << identity;
1614 codesign.start(
"codesign", QStringList() <<
"--deep" <<
"-v" << appBundlePath);
1615 codesign.waitForFinished(-1);
1616 QByteArray err = codesign.readAllStandardError();
1617 if (codesign.exitCode() > 0) {
1618 LogError() <<
"codesign verification error:";
1620 }
else if (!err.isEmpty()) {
1624 return signedBinaries;
1627void codesign(
const QString &identity,
const QString &appBundlePath) {
1628 codesignBundle(identity, appBundlePath, QList<QString>());
1633 QString appBaseName = appBundlePath;
1634 appBaseName.chop(4);
1636 QString dmgName = appBaseName +
".dmg";
1644 LogNormal() <<
"Disk image already exists, skipping .dmg creation for" << dmg.fileName();
1646 LogNormal() <<
"Creating disk image (.dmg) for" << appBundlePath;
1649 LogNormal() <<
"Image will use" << filesystemType;
1652 QStringList options = QStringList()
1653 <<
"create" << dmgName
1654 <<
"-srcfolder" << appBundlePath
1655 <<
"-format" <<
"UDZO"
1656 <<
"-fs" << filesystemType
1657 <<
"-volname" << appBaseName;
1660 hdutil.start(
"hdiutil", options);
1661 hdutil.waitForFinished(-1);
1662 if (hdutil.exitCode() != 0) {
1663 LogError() <<
"Bundle creation error:" << hdutil.readAllStandardError();
1670 QStringList parts = frameworkName.split(
".");
1671 if (parts.count() < 2) {
1672 LogError() <<
"fixupFramework: Unexpected framework name" << frameworkName;
1677 QString frameworkBinary = frameworkName + QStringLiteral(
"/") + parts[0];
1682 linkFilePrintStatus(
"Current", frameworkName +
"/Versions/A");
1685 changeIdentification(
"@rpath/" + frameworkBinary, frameworkBinary);
1686 addRPath(
"@loader_path/../../Contents/Frameworks/", frameworkBinary);
bool containsModule(const QString &module, const QString &libInFix) const
bool isDebugLibrary() const
bool recursiveCopy(const QString &sourcePath, const QString &destinationPath, const QRegularExpression &ignoreRegExp=QRegularExpression())
void addRPath(const QString &rpath, const QString &binaryPath)
QStringList getBinaryDependencies(const QString executablePath, const QString &path, const QList< QString > &additionalBinariesContainingRpaths)
QString extraEntitlements
QStringList librarySearchPath
const QString bundleFrameworkDirectory
QStringList findAppBundleFiles(const QString &appBundlePath, bool absolutePath=false)
static bool importLessThan(const QVariant &v1, const QVariant &v2)
QList< FrameworkInfo > getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QList< QString > &rpaths, bool useDebugLibs)
bool copyFilePrintStatus(const QString &from, const QString &to)
void deployRPaths(const QString &bundlePath, const QList< QString > &rpaths, const QString &binaryPath, bool useLoaderPath)
QString getLibInfix(const QStringList &deployedFrameworks)
QList< QString > getBinaryRPaths(const QString &path, bool resolve=true, QString executablePath=QString())
void patch_debugInInfoPlist(const QString &infoPlistPath)
void runInstallNameTool(QStringList options)
bool alwaysOwerwriteEnabled
void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pluginSourcePath, const QString pluginDestinationPath, DeploymentInfo deploymentInfo, bool useDebugLibs)
void deployQmlImport(const QString &appBundlePath, const QList< QString > &rpaths, const QString &importSourcePath, const QString &importName)
void recursiveCopyAndDeploy(const QString &appBundlePath, const QList< QString > &rpaths, const QString &sourcePath, const QString &destinationPath)
QString resolveDyldPrefix(const QString &path, const QString &loaderPath, const QString &executablePath)
bool linkFilePrintStatus(const QString &file, const QString &link)
void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework, const QStringList &binaryPaths, bool useLoaderPath)
QString findEntitlementsFile(const QString &path)
QString copyDylib(const FrameworkInfo &framework, const QString path)
QStringList findAppLibraries(const QString &appBundlePath)
QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
QSet< QString > codesignBundle(const QString &identity, const QString &appBundlePath, QList< QString > additionalBinariesContainingRpaths)
void codesignFile(const QString &identity, const QString &filePath)
void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath)
bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths)
void runStrip(const QString &binaryPath)
QString findAppBinary(const QString &appBundlePath)
bool operator==(const FrameworkInfo &a, const FrameworkInfo &b)
void fixupFramework(const QString &appBundlePath)
void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs)
QStringList includedPlugins
void changeIdentification(const QString &id, const QString &binaryPath)
OtoolInfo findDependencyInfo(const QString &binaryPath)
DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs)
void createDiskImage(const QString &appBundlePath, const QString &filesystemType)
QString copyFramework(const FrameworkInfo &framework, const QString path)
void stripAppBinary(const QString &bundlePath)
QStringList excludedPlugins
QStringList includedPluginTypes
QStringList findAppFrameworkPaths(const QString &appBundlePath)
DeploymentInfo deployQtFrameworks(QList< FrameworkInfo > frameworks, const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath)
QStringList findAppFrameworkNames(const QString &appBundlePath)
void codesign(const QString &identity, const QString &appBundlePath)
QStringList excludedPluginTypes
FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList< QString > &rpaths, bool useDebugLibs)
QDebug operator<<(QDebug debug, const FrameworkInfo &info)
QList< FrameworkInfo > getQtFrameworks(const QString &path, const QString &appBundlePath, const QList< QString > &rpaths, bool useDebugLibs)