3#include <QCoreApplication>
15#include <QDirIterator>
16#include <QLibraryInfo>
17#include <QJsonDocument>
21#include <QRegularExpression>
26#include <CoreFoundation/CoreFoundation.h>
42using namespace Qt::StringLiterals;
46 return ((a.frameworkPath == b.frameworkPath) && (a.binaryPath == b.binaryPath));
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";
71 debug <<
"Application bundle path" << info.path <<
"\n";
72 debug <<
"Binary path" << info.binaryPath <<
"\n";
73 debug <<
"Additional libraries" << info.libraryPaths <<
"\n";
79 if (QFile::exists(to)) {
83 qDebug() <<
"File exists, skip copy:" << to;
88 if (QFile::copy(from, to)) {
90 dest.setPermissions(dest.permissions() | QFile::WriteOwner | QFile::WriteUser);
98 if (toFile.permissions() & QFile::WriteOwner)
101 if (!toFile.setPermissions(toFile.permissions() | QFile::WriteOwner)) {
102 LogError() <<
"Failed to set u+w permissions on target file: " << to;
108 LogError() <<
"file copy failed from" << from;
116 if (QFile::exists(link)) {
117 if (QFile(link).symLinkTarget().isEmpty())
118 LogError() << link <<
"exists but it's a file.";
120 LogNormal() <<
"Symlink exists, skipping:" << link;
122 }
else if (QFile::link(file, link)) {
127 LogError() <<
"failed to symlink" << link;
137 if (QFile infoPlist(infoPlistPath); infoPlist.open(QIODevice::ReadOnly)) {
138 QByteArray contents = infoPlist.readAll();
140 QSaveFile writableInfoPlist(infoPlistPath);
141 bool success = writableInfoPlist.open(QIODevice::WriteOnly | QIODevice::Truncate);
143 contents.replace(
"_debug",
"");
144 writableInfoPlist.write(contents);
145 success = writableInfoPlist.commit();
148 LogError() <<
"Failed to write Info.plist file" << infoPlistPath;
151 LogError() <<
"Failed to read Info.plist file" << infoPlistPath;
158 info.binaryPath = binaryPath;
161 LogDebug() <<
" inspecting" << binaryPath;
163 otool.start(
"otool", QStringList() <<
"-L" << binaryPath);
164 otool.waitForFinished(-1);
166 if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) {
167 LogError() << otool.readAllStandardError();
171 static const QRegularExpression regexp(QStringLiteral(
172 "^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "
173 "current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\)$"));
175 QString output = otool.readAllStandardOutput();
176 QStringList outputLines = output.split(
"\n", Qt::SkipEmptyParts);
177 if (outputLines.size() < 2) {
178 LogError() <<
"Could not parse otool output:" << output;
182 outputLines.removeFirst();
183 if (binaryPath.contains(
".framework/") || binaryPath.endsWith(
".dylib")) {
184 const auto match = regexp.match(outputLines.constFirst());
185 if (match.hasMatch()) {
186 QString installname = match.captured(1);
187 if (QFileInfo(binaryPath).fileName() == QFileInfo(installname).fileName()) {
188 info.installName = installname;
189 info.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
190 info.currentVersion = QVersionNumber::fromString(match.captured(3));
191 outputLines.removeFirst();
193 info.installName = binaryPath;
196 LogDebug() <<
"Could not parse otool output line:" << outputLines.constFirst();
197 outputLines.removeFirst();
201 for (
const QString &outputLine : outputLines) {
202 const auto match = regexp.match(outputLine);
203 if (match.hasMatch()) {
204 if (match.captured(1) == info.installName)
207 dylib.binaryPath = match.captured(1);
208 dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
209 dylib.currentVersion = QVersionNumber::fromString(match.captured(3));
210 info.dependencies << dylib;
212 LogDebug() <<
"Could not parse otool output line:" << outputLine;
222 QString trimmed = line.trimmed();
224 if (trimmed.isEmpty())
228 if (trimmed.startsWith(
"/System/Library/") ||
229 (trimmed.startsWith(
"/usr/lib/") && trimmed.contains(
"libQt") ==
false)
230 || trimmed.startsWith(
"@executable_path") || trimmed.startsWith(
"@loader_path"))
234 if (trimmed.startsWith(
"@rpath/")) {
235 QString rpathRelativePath = trimmed.mid(QStringLiteral(
"@rpath/").length());
236 bool foundInsideBundle =
false;
237 for (
const QString &rpath : std::as_const(rpaths)) {
238 QString path = QDir::cleanPath(rpath +
"/" + rpathRelativePath);
240 if (!appBundlePath.isEmpty()) {
241 if (QDir::isAbsolutePath(appBundlePath)) {
242 if (path.startsWith(QDir::cleanPath(appBundlePath) +
"/")) {
243 foundInsideBundle =
true;
247 if (path.startsWith(QDir::cleanPath(QDir::currentPath() +
"/" + appBundlePath) +
"/")) {
248 foundInsideBundle =
true;
254 FrameworkInfo resolvedInfo = parseOtoolLibraryLine(path, appBundlePath, rpaths, useDebugLibs);
255 if (!resolvedInfo.frameworkName.isEmpty() && QFile::exists(resolvedInfo.frameworkPath)) {
256 resolvedInfo.rpathUsed = rpath;
257 resolvedInfo.installName = trimmed;
261 if (!rpaths.isEmpty() && !foundInsideBundle) {
262 LogError() <<
"Cannot resolve rpath" << trimmed;
268 enum State {QtPath, FrameworkName, DylibName, Version, FrameworkBinary, End};
269 State state = QtPath;
273 QString suffix = useDebugLibs ?
"_debug" :
"";
276 QStringList parts = trimmed.split(
"/");
277 while (part < parts.count()) {
278 const QString currentPart = parts.at(part).simplified();
280 if (currentPart ==
"")
283 if (state == QtPath) {
285 if (part < parts.count() && parts.at(part).contains(
".dylib")) {
286 info.frameworkDirectory +=
"/" + QString(qtPath + currentPart +
"/").simplified();
289 }
else if (part < parts.count() && parts.at(part).endsWith(
".framework")) {
290 info.frameworkDirectory +=
"/" + QString(qtPath +
"lib/").simplified();
291 state = FrameworkName;
293 }
else if (trimmed.startsWith(
"/") ==
false) {
294 QStringList partsCopy = parts;
295 partsCopy.removeLast();
296 for (QString &path : librarySearchPath) {
297 if (!path.endsWith(
"/"))
299 QString nameInPath = path + parts.join(u'/');
300 if (QFile::exists(nameInPath)) {
301 info.frameworkDirectory = path + partsCopy.join(u'/');
305 if (currentPart.contains(
".framework")) {
306 if (info.frameworkDirectory.isEmpty())
307 info.frameworkDirectory =
"/Library/Frameworks/" + partsCopy.join(u'/');
308 if (!info.frameworkDirectory.endsWith(
"/"))
309 info.frameworkDirectory +=
"/";
310 state = FrameworkName;
313 }
else if (currentPart.contains(
".dylib")) {
314 if (info.frameworkDirectory.isEmpty())
315 info.frameworkDirectory =
"/usr/lib/" + partsCopy.join(u'/');
316 if (!info.frameworkDirectory.endsWith(
"/"))
317 info.frameworkDirectory +=
"/";
323 qtPath += (currentPart +
"/");
325 }
if (state == FrameworkName) {
328 name.chop(QString(
".framework").length());
330 info.frameworkName = currentPart;
334 }
if (state == DylibName) {
337 info.frameworkName = name;
338 info.binaryName = name.contains(suffix) ? name : name.left(name.indexOf(
'.')) + suffix + name.mid(name.indexOf(
'.'));
339 info.deployedInstallName =
"@executable_path/../Frameworks/" + info.binaryName;
340 info.frameworkPath = info.frameworkDirectory + info.binaryName;
341 info.sourceFilePath = info.frameworkPath;
342 info.frameworkDestinationDirectory = bundleFrameworkDirectory +
"/";
343 info.binaryDestinationDirectory = info.frameworkDestinationDirectory;
344 info.binaryDirectory = info.frameworkDirectory;
345 info.binaryPath = info.frameworkPath;
349 }
else if (state == Version) {
350 info.version = currentPart;
351 info.binaryDirectory =
"Versions/" + info.version;
352 info.frameworkPath = info.frameworkDirectory + info.frameworkName;
353 info.frameworkDestinationDirectory = bundleFrameworkDirectory +
"/" + info.frameworkName;
354 info.binaryDestinationDirectory = info.frameworkDestinationDirectory +
"/" + info.binaryDirectory;
355 state = FrameworkBinary;
356 }
else if (state == FrameworkBinary) {
357 info.binaryName = currentPart.contains(suffix) ? currentPart : currentPart + suffix;
358 info.binaryPath =
"/" + info.binaryDirectory +
"/" + info.binaryName;
359 info.deployedInstallName =
"@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath;
360 info.sourceFilePath = info.frameworkPath + info.binaryPath;
362 }
else if (state == End) {
367 if (!info.sourceFilePath.isEmpty() && QFile::exists(info.sourceFilePath)) {
368 info.installName = findDependencyInfo(info.sourceFilePath).installName;
369 if (info.installName.startsWith(
"@rpath/"))
370 info.deployedInstallName = info.installName;
381 CFStringRef bundlePath = appBundlePath.toCFString();
382 CFURLRef bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath,
383 kCFURLPOSIXPathStyle,
true);
384 CFRelease(bundlePath);
385 CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, bundleURL);
387 CFURLRef executableURL = CFBundleCopyExecutableURL(bundle);
389 CFURLRef absoluteExecutableURL = CFURLCopyAbsoluteURL(executableURL);
390 if (absoluteExecutableURL) {
391 CFStringRef executablePath = CFURLCopyFileSystemPath(absoluteExecutableURL,
392 kCFURLPOSIXPathStyle);
393 if (executablePath) {
394 binaryPath = QString::fromCFString(executablePath);
395 CFRelease(executablePath);
397 CFRelease(absoluteExecutableURL);
399 CFRelease(executableURL);
403 CFRelease(bundleURL);
406 if (QFile::exists(binaryPath))
408 LogError() <<
"Could not find bundle binary for" << appBundlePath;
414 QStringList frameworks;
418 QString searchPath = appBundlePath +
"/Contents/Frameworks/";
419 QDirIterator iter(searchPath, QStringList() << QString::fromLatin1(
"*.framework"),
420 QDir::Dirs | QDir::NoSymLinks);
421 while (iter.hasNext()) {
423 frameworks << iter.fileInfo().fileName();
431 QStringList frameworks;
432 QString searchPath = appBundlePath +
"/Contents/Frameworks/";
433 QDirIterator iter(searchPath, QStringList() << QString::fromLatin1(
"*.framework"),
434 QDir::Dirs | QDir::NoSymLinks);
435 while (iter.hasNext()) {
437 frameworks << iter.fileInfo().filePath();
447 QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1(
"*.dylib") << QString::fromLatin1(
"*.so"),
448 QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
449 while (iter.hasNext()) {
451 result << iter.fileInfo().filePath();
460 QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1(
"*"),
461 QDir::Files, QDirIterator::Subdirectories);
463 while (iter.hasNext()) {
465 if (iter.fileInfo().isSymLink())
467 result << (absolutePath ? iter.fileInfo().absoluteFilePath() : iter.fileInfo().filePath());
475 QDirIterator iter(path, QStringList() << QString::fromLatin1(
"*.entitlements"),
476 QDir::Files, QDirIterator::Subdirectories);
478 while (iter.hasNext()) {
480 if (iter.fileInfo().isSymLink())
484 return iter.fileInfo().absoluteFilePath();
492 QList<FrameworkInfo> libraries;
493 for (
const DylibInfo &dylibInfo : dependencies) {
494 FrameworkInfo info = parseOtoolLibraryLine(dylibInfo.binaryPath, appBundlePath, rpaths, useDebugLibs);
495 if (info.frameworkName.isEmpty() ==
false) {
498 libraries.append(info);
506 if (path.startsWith(
"@")) {
507 if (path.startsWith(QStringLiteral(
"@executable_path/"))) {
509 if (QDir::isAbsolutePath(executablePath)) {
510 return QDir::cleanPath(QFileInfo(executablePath).path() + path.mid(QStringLiteral(
"@executable_path").length()));
512 return QDir::cleanPath(QDir::currentPath() +
"/" +
513 QFileInfo(executablePath).path() + path.mid(QStringLiteral(
"@executable_path").length()));
515 }
else if (path.startsWith(QStringLiteral(
"@loader_path"))) {
517 if (QDir::isAbsolutePath(loaderPath)) {
518 return QDir::cleanPath(QFileInfo(loaderPath).path() + path.mid(QStringLiteral(
"@loader_path").length()));
520 return QDir::cleanPath(QDir::currentPath() +
"/" +
521 QFileInfo(loaderPath).path() + path.mid(QStringLiteral(
"@loader_path").length()));
524 LogError() <<
"Unexpected prefix" << path;
532 QList<QString> rpaths;
535 otool.start(
"otool", QStringList() <<
"-l" << path);
536 otool.waitForFinished();
538 if (otool.exitCode() != 0) {
539 LogError() << otool.readAllStandardError();
542 if (resolve && executablePath.isEmpty()) {
543 executablePath = path;
546 QString output = otool.readAllStandardOutput();
547 QStringList outputLines = output.split(
"\n");
549 for (
auto i = outputLines.cbegin(), end = outputLines.cend(); i != end; ++i) {
550 if (i->contains(
"cmd LC_RPATH") && ++i != end &&
551 i->contains(
"cmdsize") && ++i != end) {
552 const QString &rpathCmd = *i;
553 int pathStart = rpathCmd.indexOf(
"path ");
554 int pathEnd = rpathCmd.indexOf(
" (");
555 if (pathStart >= 0 && pathEnd >= 0 && pathStart < pathEnd) {
556 QString rpath = rpathCmd.mid(pathStart + 5, pathEnd - pathStart - 5);
558 rpaths << resolveDyldPrefix(rpath, path, executablePath);
571 const OtoolInfo info = findDependencyInfo(path);
572 QList<QString> allRPaths = rpaths + getBinaryRPaths(path);
573 allRPaths.removeDuplicates();
574 return getQtFrameworks(info.dependencies, appBundlePath, allRPaths, useDebugLibs);
579 QList<FrameworkInfo> result;
580 QSet<QString> existing;
581 for (
const QString &path : paths) {
582 for (
const FrameworkInfo &info : getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) {
583 if (!existing.contains(info.frameworkPath)) {
584 existing.insert(info.frameworkPath);
594 const QList<QString> &additionalBinariesContainingRpaths)
596 QStringList binaries;
598 const auto dependencies = findDependencyInfo(path).dependencies;
600 bool rpathsLoaded =
false;
601 QList<QString> rpaths;
604 for (
const DylibInfo &info : dependencies) {
605 QString trimmedLine = info.binaryPath;
606 if (trimmedLine.startsWith(
"@executable_path/")) {
607 QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral(
"@executable_path/").length()));
609 binaries.append(binary);
610 }
else if (trimmedLine.startsWith(
"@loader_path/")) {
611 QString binary = QDir::cleanPath(QFileInfo(path).path() +
"/" + trimmedLine.mid(QStringLiteral(
"@loader_path/").length()));
613 binaries.append(binary);
614 }
else if (trimmedLine.startsWith(
"@rpath/")) {
616 rpaths = getBinaryRPaths(path,
true, executablePath);
617 for (
const QString &binaryPath : additionalBinariesContainingRpaths)
618 rpaths += getBinaryRPaths(binaryPath,
true);
619 rpaths.removeDuplicates();
622 bool resolved =
false;
623 for (
const QString &rpath : std::as_const(rpaths)) {
624 QString binary = QDir::cleanPath(rpath +
"/" + trimmedLine.mid(QStringLiteral(
"@rpath/").length()));
625 LogDebug() <<
"Checking for" << binary;
626 if (QFile::exists(binary)) {
627 binaries.append(binary);
632 if (!resolved && !rpaths.isEmpty()) {
633 LogError() <<
"Cannot resolve rpath" << trimmedLine;
643bool recursiveCopy(
const QString &sourcePath,
const QString &destinationPath,
644 const QRegularExpression &ignoreRegExp = QRegularExpression())
646 const QDir sourceDir(sourcePath);
647 if (!sourceDir.exists())
649 QDir().mkpath(destinationPath);
651 LogNormal() <<
"copy:" << sourcePath << destinationPath;
653 const QStringList files = sourceDir.entryList(QStringList() <<
"*", QDir::Files | QDir::NoDotAndDotDot);
654 const bool hasValidRegExp = ignoreRegExp.isValid() && ignoreRegExp.pattern().length() > 0;
655 for (
const QString &file : files) {
656 if (hasValidRegExp && ignoreRegExp.match(file).hasMatch())
658 const QString fileSourcePath = sourcePath +
"/" + file;
659 const QString fileDestinationPath = destinationPath +
"/" + file;
660 copyFilePrintStatus(fileSourcePath, fileDestinationPath);
663 const QStringList subdirs = sourceDir.entryList(QStringList() <<
"*", QDir::Dirs | QDir::NoDotAndDotDot);
664 for (
const QString &dir : subdirs) {
665 recursiveCopy(sourcePath +
"/" + dir, destinationPath +
"/" + dir);
670void recursiveCopyAndDeploy(
const QString &appBundlePath,
const QList<QString> &rpaths,
const QString &sourcePath,
const QString &destinationPath)
672 QDir().mkpath(destinationPath);
674 LogNormal() <<
"copy:" << sourcePath << destinationPath;
676 const QDir sourceDir(sourcePath);
678 const QStringList files = sourceDir.entryList(QStringList() << QStringLiteral(
"*"), QDir::Files | QDir::NoDotAndDotDot);
679 for (
const QString &file : files) {
680 if (file.endsWith(
"_debug.dylib"))
683 if (file.endsWith(
".qrc"))
686 const QString fileSourcePath = sourcePath + u'/' + file;
688 if (file.endsWith(QStringLiteral(
".dylib"))) {
700 QString fileDestinationDir = appBundlePath + QStringLiteral(
"/Contents/PlugIns/quick/");
701 QDir().mkpath(fileDestinationDir);
702 QString fileDestinationPath = fileDestinationDir + file;
705 QString linkDestinationPath = destinationPath + u'/' + file;
708 QString linkPath = QStringLiteral(
"PlugIns/quick/") + file;
709 int cdupCount = linkDestinationPath.count(QStringLiteral(
"/")) - appBundlePath.count(QStringLiteral(
"/"));
710 for (
int i = 0; i < cdupCount - 2; ++i)
711 linkPath.prepend(
"../");
713 if (copyFilePrintStatus(fileSourcePath, fileDestinationPath)) {
714 linkFilePrintStatus(linkPath, linkDestinationPath);
716 runStrip(fileDestinationPath);
717 bool useDebugLibs =
false;
718 bool useLoaderPath =
false;
719 QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, appBundlePath, rpaths, useDebugLibs);
720 deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath);
723 QString fileDestinationPath = destinationPath + u'/' + file;
724 copyFilePrintStatus(fileSourcePath, fileDestinationPath);
728 const QStringList subdirs = sourceDir.entryList(QStringList() << QStringLiteral(
"*"), QDir::Dirs | QDir::NoDotAndDotDot);
729 for (
const QString &dir : subdirs) {
730 if (dir.endsWith(
".dSYM"))
733 recursiveCopyAndDeploy(appBundlePath, rpaths, sourcePath + u'/' + dir, destinationPath + u'/' + dir);
739 if (!QFile::exists(framework.sourceFilePath)) {
740 LogError() <<
"no file at" << framework.sourceFilePath;
746 QString dylibDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory;
747 QString dylibDestinationBinaryPath = dylibDestinationDirectory + u'/' + framework.binaryName;
750 if (!QDir().mkpath(dylibDestinationDirectory)) {
751 LogError() <<
"could not create destination directory" << dylibDestinationDirectory;
756 if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled)
757 return dylibDestinationBinaryPath;
760 copyFilePrintStatus(framework.sourceFilePath, dylibDestinationBinaryPath);
761 return dylibDestinationBinaryPath;
766 if (!QFile::exists(framework.sourceFilePath)) {
767 LogError() <<
"no file at" << framework.sourceFilePath;
773 QString frameworkDestinationDirectory = path + u'/' + framework.frameworkDestinationDirectory;
774 QString frameworkBinaryDestinationDirectory = frameworkDestinationDirectory + u'/' + framework.binaryDirectory;
775 QString frameworkDestinationBinaryPath = frameworkBinaryDestinationDirectory + u'/' + framework.binaryName;
782 if (!QDir().mkpath(frameworkBinaryDestinationDirectory)) {
783 LogError() <<
"could not create destination directory" << frameworkBinaryDestinationDirectory;
793 copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath);
796 const QString resourcesSourcePath = framework.frameworkPath +
"/Resources";
797 const QString resourcesDestinationPath = frameworkDestinationDirectory +
"/Versions/" + framework.version +
"/Resources";
799 recursiveCopy(resourcesSourcePath, resourcesDestinationPath,
800 QRegularExpression(
"\\A(?:[^/]*\\.prl)\\z"));
801 const QString librariesSourcePath = framework.frameworkPath +
"/Libraries";
802 const QString librariesDestinationPath = frameworkDestinationDirectory +
"/Versions/" + framework.version +
"/Libraries";
803 bool createdLibraries = recursiveCopy(librariesSourcePath, librariesDestinationPath);
804 const QString helpersSourcePath = framework.frameworkPath +
"/Helpers";
805 const QString helpersDestinationPath = frameworkDestinationDirectory +
"/Versions/" + framework.version +
"/Helpers";
806 bool createdHelpers = recursiveCopy(helpersSourcePath, helpersDestinationPath);
813 linkFilePrintStatus(
"Versions/Current/" + framework.binaryName, frameworkDestinationDirectory +
"/" + framework.binaryName);
814 linkFilePrintStatus(
"Versions/Current/Resources", frameworkDestinationDirectory +
"/Resources");
815 if (createdLibraries)
816 linkFilePrintStatus(
"Versions/Current/Libraries", frameworkDestinationDirectory +
"/Libraries");
818 linkFilePrintStatus(
"Versions/Current/Helpers", frameworkDestinationDirectory +
"/Helpers");
819 linkFilePrintStatus(framework.version, frameworkDestinationDirectory +
"/Versions/Current");
823 const QString legacyInfoPlistPath = framework.frameworkPath +
"/Contents/Info.plist";
824 const QString correctInfoPlistPath = frameworkDestinationDirectory +
"/Resources/Info.plist";
825 if (QFile::exists(legacyInfoPlistPath)) {
826 copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
827 patch_debugInInfoPlist(correctInfoPlistPath);
829 return frameworkDestinationBinaryPath;
834 QProcess installNametool;
835 installNametool.start(
"install_name_tool", options);
836 installNametool.waitForFinished();
837 if (installNametool.exitCode() != 0) {
838 LogError() << installNametool.readAllStandardError();
839 LogError() << installNametool.readAllStandardOutput();
845 LogDebug() <<
"Using install_name_tool:";
846 LogDebug() <<
" change identification in" << binaryPath;
848 runInstallNameTool(QStringList() <<
"-id" << id << binaryPath);
853 const QString absBundlePath = QFileInfo(bundlePath).absoluteFilePath();
854 for (
const QString &binary : binaryPaths) {
855 QString deployedInstallName;
857 deployedInstallName =
"@loader_path/"_L1
858 + QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + u'/' + framework.binaryDestinationDirectory + u'/' + framework.binaryName);
860 deployedInstallName = framework.deployedInstallName;
862 changeInstallName(framework.installName, deployedInstallName, binary);
865 QFileInfo fileInfo= QFileInfo(framework.installName);
866 QString canonicalInstallName = fileInfo.canonicalFilePath();
867 if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) {
868 changeInstallName(canonicalInstallName, deployedInstallName, binary);
870 QString innerDependency = fileInfo.canonicalPath() +
"/" + fileInfo.fileName();
871 if (innerDependency != canonicalInstallName && innerDependency != framework.installName) {
872 changeInstallName(innerDependency, deployedInstallName, binary);
878void addRPath(
const QString &rpath,
const QString &binaryPath)
880 runInstallNameTool(QStringList() <<
"-add_rpath" << rpath << binaryPath);
883void deployRPaths(
const QString &bundlePath,
const QList<QString> &rpaths,
const QString &binaryPath,
bool useLoaderPath)
885 const QString absFrameworksPath = QFileInfo(bundlePath).absoluteFilePath()
886 +
"/Contents/Frameworks"_L1;
887 const QString relativeFrameworkPath = QFileInfo(binaryPath).absoluteDir().relativeFilePath(absFrameworksPath);
888 const QString loaderPathToFrameworks =
"@loader_path/"_L1 + relativeFrameworkPath;
889 bool rpathToFrameworksFound =
false;
891 QList<QString> binaryRPaths = getBinaryRPaths(binaryPath,
false);
892 for (
const QString &rpath : std::as_const(binaryRPaths)) {
893 if (rpath ==
"@executable_path/../Frameworks" ||
894 rpath == loaderPathToFrameworks) {
895 rpathToFrameworksFound =
true;
898 if (rpaths.contains(resolveDyldPrefix(rpath, binaryPath, binaryPath))) {
899 if (!args.contains(rpath))
900 args <<
"-delete_rpath" << rpath;
903 if (!args.length()) {
906 if (!rpathToFrameworksFound) {
907 if (!useLoaderPath) {
908 args <<
"-add_rpath" <<
"@executable_path/../Frameworks";
910 args <<
"-add_rpath" << loaderPathToFrameworks;
913 LogDebug() <<
"Using install_name_tool:";
914 LogDebug() <<
" change rpaths in" << binaryPath;
916 runInstallNameTool(QStringList() << args << binaryPath);
919void deployRPaths(
const QString &bundlePath,
const QList<QString> &rpaths,
const QStringList &binaryPaths,
bool useLoaderPath)
921 for (
const QString &binary : binaryPaths) {
922 deployRPaths(bundlePath, rpaths, binary, useLoaderPath);
926void changeInstallName(
const QString &oldName,
const QString &newName,
const QString &binaryPath)
928 LogDebug() <<
"Using install_name_tool:";
930 LogDebug() <<
" change reference" << oldName;
932 runInstallNameTool(QStringList() <<
"-change" << oldName << newName << binaryPath);
941 LogDebug() <<
" stripped" << binaryPath;
943 strip.start(
"strip", QStringList() <<
"-x" << binaryPath);
944 strip.waitForFinished();
945 if (strip.exitCode() != 0) {
946 LogError() << strip.readAllStandardError();
947 LogError() << strip.readAllStandardOutput();
953 runStrip(findAppBinary(bundlePath));
959 if (deployedFrameworks.contains(
"Qt"_L1 + module + libInFix +
".framework"_L1))
962 const QRegularExpression dylibRegExp(
"libQt[0-9]+"_L1
964 + (isDebug ?
"_debug" :
"")
965 +
".[0-9]+.dylib"_L1);
966 return deployedFrameworks.filter(dylibRegExp).size() > 0;
970
971
972
973
974
975
977 const QString &bundlePath,
const QStringList &binaryPaths,
bool useDebugLibs,
981 LogNormal() <<
"Deploying Qt frameworks found inside:" << binaryPaths;
982 QStringList copiedFrameworks;
985 deploymentInfo
.isFramework = bundlePath.contains(
".framework");
987 QList<QString> rpathsUsed;
989 while (frameworks.isEmpty() ==
false) {
991 copiedFrameworks.append(framework.frameworkName);
999 if (deploymentInfo.qtPath.isNull())
1000 deploymentInfo.qtPath = QLibraryInfo::path(QLibraryInfo::PrefixPath);
1002 if (framework.frameworkDirectory.startsWith(bundlePath)) {
1003 LogError() << framework.frameworkName <<
"already deployed, skipping.";
1007 if (!framework.rpathUsed.isEmpty() && !rpathsUsed.contains(framework.rpathUsed)) {
1008 rpathsUsed.append(framework.rpathUsed);
1026 if (!framework.sourceFilePath.isEmpty()) {
1027 const QList<QString> sourceRPaths = getBinaryRPaths(framework.sourceFilePath,
true);
1028 for (
const QString &sourceRPath : sourceRPaths) {
1029 const QDir sourceRPathDir(sourceRPath);
1030 if (sourceRPathDir.exists() && !rpathsUsed.contains(sourceRPath)) {
1031 rpathsUsed.append(sourceRPath);
1037 const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
1038 : copyFramework(framework, bundlePath);
1041 changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath);
1044 if (deployedBinaryPath.isNull())
1047 runStrip(deployedBinaryPath);
1050 if (!framework.rpathUsed.length()) {
1051 changeIdentification(framework.deployedInstallName, deployedBinaryPath);
1055 QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
1057 for (
const FrameworkInfo &dependency : dependencies) {
1058 if (dependency.rpathUsed.isEmpty()) {
1059 changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath);
1060 }
else if (!rpathsUsed.contains(dependency.rpathUsed)) {
1061 rpathsUsed.append(dependency.rpathUsed);
1065 if (copiedFrameworks.contains(dependency.frameworkName) ==
false && frameworks.contains(dependency) ==
false) {
1066 frameworks.append(dependency);
1070 deploymentInfo.deployedFrameworks = copiedFrameworks;
1071 deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath);
1072 deploymentInfo.rpathsUsed += rpathsUsed;
1073 deploymentInfo.rpathsUsed.removeDuplicates();
1074 return deploymentInfo;
1080 applicationBundle.path = appBundlePath;
1081 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1082 applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
1083 QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
1084 << additionalExecutables;
1086 QList<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath,
true);
1087 allLibraryPaths.append(QLibraryInfo::path(QLibraryInfo::LibrariesPath));
1088 allLibraryPaths.removeDuplicates();
1090 QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
1093 LogWarning() <<
"Could not find any external Qt frameworks to deploy in" << appBundlePath;
1094 LogWarning() <<
"Perhaps macdeployqt was already used on" << appBundlePath <<
"?";
1095 LogWarning() <<
"If so, you will need to rebuild" << appBundlePath <<
"before trying again.";
1098 return deployQtFrameworks(frameworks, applicationBundle.path, allBinaryPaths, useDebugLibs, !additionalExecutables.isEmpty());
1105 for (
const QString &framework : deployedFrameworks) {
1106 if (framework.startsWith(QStringLiteral(
"QtCore")) && framework.endsWith(QStringLiteral(
".framework")) &&
1107 !framework.contains(QStringLiteral(
"5Compat"))) {
1108 Q_ASSERT(framework.length() >= 16);
1110 const int lengthOfLibInfix = framework.length() - 16;
1111 if (lengthOfLibInfix)
1112 libInfix = framework.mid(6, lengthOfLibInfix);
1120 const QString pluginDestinationPath,
DeploymentInfo deploymentInfo,
bool useDebugLibs)
1122 LogNormal() <<
"Deploying plugins from" << pluginSourcePath;
1124 if (!pluginSourcePath.contains(deploymentInfo.pluginPath))
1128 QStringList pluginList;
1130 const auto addPlugins = [&pluginSourcePath,&pluginList,useDebugLibs](
const QString &subDirectory,
1131 const std::function<
bool(QString)> &predicate = std::function<
bool(QString)>()) {
1132 const QStringList libs = QDir(pluginSourcePath + u'/' + subDirectory)
1133 .entryList({QStringLiteral(
"*.dylib")});
1134 for (
const QString &lib : libs) {
1135 if (lib.endsWith(QStringLiteral(
"_debug.dylib")) != useDebugLibs)
1137 if (!predicate || predicate(lib))
1138 pluginList.append(subDirectory + u'/' + lib);
1143 addPlugins(QStringLiteral(
"platforms"), [](
const QString &lib) {
1145 if (!lib.contains(QStringLiteral(
"cocoa")))
1151 addPlugins(QStringLiteral(
"printsupport"));
1154 addPlugins(QStringLiteral(
"styles"));
1157 const QString libInfix = getLibInfix(deploymentInfo.deployedFrameworks);
1160 if (deploymentInfo.containsModule(
"Network", libInfix)) {
1161 addPlugins(QStringLiteral(
"tls"));
1162 addPlugins(QStringLiteral(
"networkinformation"));
1166 const bool usesSvg = deploymentInfo.containsModule(
"Svg", libInfix);
1167 addPlugins(QStringLiteral(
"imageformats"), [usesSvg](
const QString &lib) {
1168 if (lib.contains(QStringLiteral(
"qsvg")) && !usesSvg)
1173 addPlugins(QStringLiteral(
"iconengines"));
1176 if (deploymentInfo.containsModule(
"Gui", libInfix)) {
1177 addPlugins(QStringLiteral(
"platforminputcontexts"), [&addPlugins](
const QString &lib) {
1179 if (lib.startsWith(QStringLiteral(
"libqtvirtualkeyboard")))
1180 addPlugins(QStringLiteral(
"virtualkeyboard"));
1186 if (deploymentInfo.containsModule(
"Sql", libInfix)) {
1187 addPlugins(QStringLiteral(
"sqldrivers"), [](
const QString &lib) {
1188 if (lib.startsWith(QStringLiteral(
"libqsqlodbc")) || lib.startsWith(QStringLiteral(
"libqsqlpsql"))) {
1189 LogWarning() <<
"Plugin" << lib <<
"uses private API and is not Mac App store compliant.";
1200 if (deploymentInfo.containsModule(
"WebView", libInfix)) {
1201 addPlugins(QStringLiteral(
"webview"), [](
const QString &lib) {
1202 if (lib.startsWith(QStringLiteral(
"libqtwebview_webengine"))) {
1203 LogWarning() <<
"Plugin" << lib <<
"uses QtWebEngine and is not Mac App store compliant.";
1214 static const std::map<QString, std::vector<QString>> map {
1215 {QStringLiteral(
"Multimedia"), {QStringLiteral(
"multimedia")}},
1216 {QStringLiteral(
"3DRender"), {QStringLiteral(
"sceneparsers"), QStringLiteral(
"geometryloaders"), QStringLiteral(
"renderers")}},
1217 {QStringLiteral(
"3DQuickRender"), {QStringLiteral(
"renderplugins")}},
1218 {QStringLiteral(
"Positioning"), {QStringLiteral(
"position")}},
1219 {QStringLiteral(
"Location"), {QStringLiteral(
"geoservices")}},
1220 {QStringLiteral(
"TextToSpeech"), {QStringLiteral(
"texttospeech")}},
1221 {QStringLiteral(
"SerialBus"), {QStringLiteral(
"canbus")}},
1224 for (
const auto &it : map) {
1225 if (deploymentInfo.containsModule(it.first, libInfix)) {
1226 for (
const auto &pluginType : it.second) {
1227 addPlugins(pluginType);
1232 for (
const QString &plugin : pluginList) {
1233 QString sourcePath = pluginSourcePath +
"/" + plugin;
1234 const QString destinationPath = pluginDestinationPath +
"/" + plugin;
1236 dir.mkpath(QFileInfo(destinationPath).path());
1238 if (copyFilePrintStatus(sourcePath, destinationPath)) {
1239 runStrip(destinationPath);
1240 QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
1241 deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
1249 QByteArray contents =
"[Paths]\n"
1250 "Plugins = PlugIns\n"
1251 "Imports = Resources/qml\n"
1252 "QmlImports = Resources/qml\n";
1254 QString filePath = appBundlePath +
"/Contents/Resources/";
1255 QString fileName = filePath +
"qt.conf";
1257 QDir().mkpath(filePath);
1259 if (QFile::exists(fileName) && !alwaysOwerwriteEnabled) {
1261 LogWarning() << fileName <<
"already exists, will not overwrite.";
1262 LogWarning() <<
"To make sure the plugins are loaded from the correct location,";
1263 LogWarning() <<
"please make sure qt.conf contains the following lines:";
1269 if (QSaveFile qtconf(fileName); qtconf.open(QIODevice::WriteOnly)
1270 && qtconf.write(contents) != -1 && qtconf.commit()) {
1271 LogNormal() <<
"Created configuration file:" << fileName;
1272 LogNormal() <<
"This file sets the plugin search path to" << appBundlePath +
"/Contents/PlugIns";
1279 applicationBundle.path = appBundlePath;
1280 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1282 const QString pluginDestinationPath = appBundlePath +
"/" +
"Contents/PlugIns";
1283 deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
1286void deployQmlImport(
const QString &appBundlePath,
const QList<QString> &rpaths,
const QString &importSourcePath,
const QString &importName)
1288 QString importDestinationPath = appBundlePath +
"/Contents/Resources/qml/" + importName;
1292 if (QDir().exists(importDestinationPath))
1295 recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath);
1300 QVariantMap import1 = v1.toMap();
1301 QVariantMap import2 = v2.toMap();
1302 QString path1 = import1[
"path"].toString();
1303 QString path2 = import2[
"path"].toString();
1304 return path1 < path2;
1311 LogNormal() <<
"Deploying QML imports ";
1312 LogNormal() <<
"Application QML file path(s) is" << qmlDirs;
1313 LogNormal() <<
"QML module search path(s) is" << qmlImportPaths;
1316 QString qmlImportScannerPath =
1317 QDir::cleanPath(QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath)
1318 +
"/qmlimportscanner");
1321 if (!QFile::exists(qmlImportScannerPath))
1322 qmlImportScannerPath = QCoreApplication::applicationDirPath() +
"/qmlimportscanner";
1325 if (!QFile::exists(qmlImportScannerPath)) {
1326 LogError() <<
"qmlimportscanner not found at" << qmlImportScannerPath;
1327 LogError() <<
"Rebuild qtdeclarative/tools/qmlimportscanner";
1333 QStringList argumentList;
1334 for (
const QString &qmlDir : qmlDirs) {
1335 argumentList.append(
"-rootPath");
1336 argumentList.append(qmlDir);
1338 for (
const QString &importPath : qmlImportPaths)
1339 argumentList <<
"-importPath" << importPath;
1340 QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
1341 argumentList.append(
"-importPath");
1342 argumentList.append(qmlImportsPath);
1345 QProcess qmlImportScanner;
1346 qmlImportScanner.start(qmlImportScannerPath, argumentList);
1347 if (!qmlImportScanner.waitForStarted()) {
1348 LogError() <<
"Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
1351 qmlImportScanner.waitForFinished(-1);
1354 qmlImportScanner.setReadChannel(QProcess::StandardError);
1355 QByteArray errors = qmlImportScanner.readAll();
1356 if (!errors.isEmpty()) {
1357 LogWarning() <<
"QML file parse error (deployment will continue):";
1362 qmlImportScanner.setReadChannel(QProcess::StandardOutput);
1363 QByteArray json = qmlImportScanner.readAll();
1364 QJsonDocument doc = QJsonDocument::fromJson(json);
1365 if (!doc.isArray()) {
1366 LogError() <<
"qmlimportscanner output error. Expected json array, got:";
1374 QVariantList array = doc.array().toVariantList();
1375 std::sort(array.begin(), array.end(), importLessThan);
1378 for (
const QVariant &importValue : array) {
1379 QVariantMap import = importValue.toMap();
1380 QString name = import[
"name"].toString();
1381 QString path = import[
"path"].toString();
1382 QString type = import[
"type"].toString();
1384 LogNormal() <<
"Deploying QML import" << name;
1387 if (name.isEmpty() || path.isEmpty()) {
1388 LogNormal() <<
" Skip import: name or path is empty";
1395 if (type != QStringLiteral(
"module")) {
1396 LogNormal() <<
" Skip non-module import";
1404 name.replace(u'.', u'/');
1405 int secondTolast = path.length() - 2;
1406 QString version = path.mid(secondTolast);
1407 if (version.startsWith(u'.'))
1408 name.append(version);
1410 deployQmlImport(appBundlePath, deploymentInfo.rpathsUsed, path, name);
1421 QString codeSignLogMessage =
"codesign";
1423 codeSignLogMessage +=
", enable hardened runtime";
1425 codeSignLogMessage +=
", include secure timestamp";
1426 LogNormal() << codeSignLogMessage << filePath;
1428 QStringList codeSignOptions = {
"--preserve-metadata=identifier,entitlements",
"--force",
"-s",
1429 identity, filePath };
1431 codeSignOptions <<
"-o" <<
"runtime";
1434 codeSignOptions <<
"--timestamp";
1436 if (!extraEntitlements.isEmpty())
1437 codeSignOptions <<
"--entitlements" << extraEntitlements;
1440 codesign.start(
"codesign", codeSignOptions);
1441 codesign.waitForFinished(-1);
1443 QByteArray err = codesign.readAllStandardError();
1444 if (codesign.exitCode() > 0) {
1445 LogError() <<
"Codesign signing error:";
1447 }
else if (!err.isEmpty()) {
1453 const QString &appBundlePath,
1454 QList<QString> additionalBinariesContainingRpaths)
1464 LogNormal() <<
"Signing" << appBundlePath <<
"with identity" << identity;
1466 QStack<QString> pendingBinaries;
1467 QSet<QString> pendingBinariesSet;
1468 QSet<QString> signedBinaries;
1472 QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath();
1473 QString rootBinariesPath = appBundleAbsolutePath +
"/Contents/MacOS/";
1474 QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() <<
"*", QDir::Files);
1477 QString appBinary = findAppBinary(appBundleAbsolutePath);
1478 QString appBinaryName = QFileInfo(appBinary).fileName();
1479 if (
int appBinaryIdx = foundRootBinaries.indexOf(appBinaryName); appBinaryIdx > 0) {
1480 foundRootBinaries.swapItemsAt(0, appBinaryIdx);
1481 LogDebug() <<
"swapped appBinary to start of list";
1483 LogDebug() <<
"App binary is" << appBinaryName;
1484 LogDebug() <<
"Binaries in" << rootBinariesPath <<
"are" << foundRootBinaries;
1486 for (
const QString &binary : foundRootBinaries) {
1487 QString binaryPath = rootBinariesPath + binary;
1488 pendingBinaries.push(binaryPath);
1489 pendingBinariesSet.insert(binaryPath);
1490 additionalBinariesContainingRpaths.append(binaryPath);
1493 bool getAbsoltuePath =
true;
1494 QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath +
"/Contents/PlugIns/", getAbsoltuePath);
1495 for (
const QString &binary : foundPluginBinaries) {
1496 pendingBinaries.push(binary);
1497 pendingBinariesSet.insert(binary);
1501 QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath);
1502 for (
const QString &frameworkPath : frameworkPaths) {
1506 QDirIterator helpersIterator(frameworkPath, QStringList() << QString::fromLatin1(
"Helpers"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1507 while (helpersIterator.hasNext()) {
1508 helpersIterator.next();
1509 QString helpersPath = helpersIterator.filePath();
1510 QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() <<
"*.app", QDir::Dirs);
1511 for (
const QString &innerBundleName : innerBundleNames)
1512 signedBinaries += codesignBundle(identity,
1513 helpersPath +
"/" + innerBundleName,
1514 additionalBinariesContainingRpaths);
1519 QDirIterator librariesIterator(frameworkPath, QStringList() << QString::fromLatin1(
"Libraries"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1520 while (librariesIterator.hasNext()) {
1521 librariesIterator.next();
1522 QString librariesPath = librariesIterator.filePath();
1523 QStringList bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath);
1524 for (
const QString &binary : bundleFiles) {
1525 pendingBinaries.push(binary);
1526 pendingBinariesSet.insert(binary);
1532 while (!pendingBinaries.isEmpty()) {
1533 QString binary = pendingBinaries.pop();
1534 if (signedBinaries.contains(binary))
1538 QStringList dependencies = getBinaryDependencies(rootBinariesPath, binary,
1539 additionalBinariesContainingRpaths);
1540 dependencies = QSet<QString>(dependencies.begin(), dependencies.end())
1541 .subtract(signedBinaries)
1542 .subtract(pendingBinariesSet)
1545 if (!dependencies.isEmpty()) {
1546 pendingBinaries.push(binary);
1547 pendingBinariesSet.insert(binary);
1548 int dependenciesSkipped = 0;
1549 for (
const QString &dependency : std::as_const(dependencies)) {
1554 if (!dependency.startsWith(appBundleAbsolutePath)) {
1555 ++dependenciesSkipped;
1556 LogNormal() <<
"Skipping outside dependency: " << dependency;
1559 pendingBinaries.push(dependency);
1560 pendingBinariesSet.insert(dependency);
1565 if (dependenciesSkipped == dependencies.size()) {
1566 pendingBinaries.pop();
1573 extraEntitlements = findEntitlementsFile(appBundleAbsolutePath +
"/Contents/Resources/");
1576 codesignFile(identity, binary);
1577 signedBinaries.insert(binary);
1578 pendingBinariesSet.remove(binary);
1581 LogNormal() <<
"Finished codesigning " << appBundlePath <<
"with identity" << identity;
1585 codesign.start(
"codesign", QStringList() <<
"--deep" <<
"-v" << appBundlePath);
1586 codesign.waitForFinished(-1);
1587 QByteArray err = codesign.readAllStandardError();
1588 if (codesign.exitCode() > 0) {
1589 LogError() <<
"codesign verification error:";
1591 }
else if (!err.isEmpty()) {
1595 return signedBinaries;
1598void codesign(
const QString &identity,
const QString &appBundlePath) {
1599 codesignBundle(identity, appBundlePath, QList<QString>());
1604 QString appBaseName = appBundlePath;
1605 appBaseName.chop(4);
1607 QString dmgName = appBaseName +
".dmg";
1615 LogNormal() <<
"Disk image already exists, skipping .dmg creation for" << dmg.fileName();
1617 LogNormal() <<
"Creating disk image (.dmg) for" << appBundlePath;
1620 LogNormal() <<
"Image will use" << filesystemType;
1623 QStringList options = QStringList()
1624 <<
"create" << dmgName
1625 <<
"-srcfolder" << appBundlePath
1626 <<
"-format" <<
"UDZO"
1627 <<
"-fs" << filesystemType
1628 <<
"-volname" << appBaseName;
1631 hdutil.start(
"hdiutil", options);
1632 hdutil.waitForFinished(-1);
1633 if (hdutil.exitCode() != 0) {
1634 LogError() <<
"Bundle creation error:" << hdutil.readAllStandardError();
1641 QStringList parts = frameworkName.split(
".");
1642 if (parts.count() < 2) {
1643 LogError() <<
"fixupFramework: Unexpected framework name" << frameworkName;
1648 QString frameworkBinary = frameworkName + QStringLiteral(
"/") + parts[0];
1653 linkFilePrintStatus(
"Current", frameworkName +
"/Versions/A");
1656 changeIdentification(
"@rpath/" + frameworkBinary, frameworkBinary);
1657 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 createQtConf(const QString &appBundlePath)
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)
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 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)
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)