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);
1012 const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
1013 : copyFramework(framework, bundlePath);
1016 changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath);
1019 if (deployedBinaryPath.isNull())
1022 runStrip(deployedBinaryPath);
1025 if (!framework.rpathUsed.length()) {
1026 changeIdentification(framework.deployedInstallName, deployedBinaryPath);
1030 QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
1032 for (
const FrameworkInfo &dependency : dependencies) {
1033 if (dependency.rpathUsed.isEmpty()) {
1034 changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath);
1036 rpathsUsed.append(dependency.rpathUsed);
1040 if (copiedFrameworks.contains(dependency.frameworkName) ==
false && frameworks.contains(dependency) ==
false) {
1041 frameworks.append(dependency);
1045 deploymentInfo.deployedFrameworks = copiedFrameworks;
1046 deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath);
1047 deploymentInfo.rpathsUsed += rpathsUsed;
1048 deploymentInfo.rpathsUsed.removeDuplicates();
1049 return deploymentInfo;
1055 applicationBundle.path = appBundlePath;
1056 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1057 applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
1058 QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
1059 << additionalExecutables;
1061 QList<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath,
true);
1062 allLibraryPaths.append(QLibraryInfo::path(QLibraryInfo::LibrariesPath));
1063 allLibraryPaths.removeDuplicates();
1065 QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
1068 LogWarning() <<
"Could not find any external Qt frameworks to deploy in" << appBundlePath;
1069 LogWarning() <<
"Perhaps macdeployqt was already used on" << appBundlePath <<
"?";
1070 LogWarning() <<
"If so, you will need to rebuild" << appBundlePath <<
"before trying again.";
1073 return deployQtFrameworks(frameworks, applicationBundle.path, allBinaryPaths, useDebugLibs, !additionalExecutables.isEmpty());
1080 for (
const QString &framework : deployedFrameworks) {
1081 if (framework.startsWith(QStringLiteral(
"QtCore")) && framework.endsWith(QStringLiteral(
".framework")) &&
1082 !framework.contains(QStringLiteral(
"5Compat"))) {
1083 Q_ASSERT(framework.length() >= 16);
1085 const int lengthOfLibInfix = framework.length() - 16;
1086 if (lengthOfLibInfix)
1087 libInfix = framework.mid(6, lengthOfLibInfix);
1095 const QString pluginDestinationPath,
DeploymentInfo deploymentInfo,
bool useDebugLibs)
1097 LogNormal() <<
"Deploying plugins from" << pluginSourcePath;
1099 if (!pluginSourcePath.contains(deploymentInfo.pluginPath))
1103 QStringList pluginList;
1105 const auto addPlugins = [&pluginSourcePath,&pluginList,useDebugLibs](
const QString &subDirectory,
1106 const std::function<
bool(QString)> &predicate = std::function<
bool(QString)>()) {
1107 const QStringList libs = QDir(pluginSourcePath + u'/' + subDirectory)
1108 .entryList({QStringLiteral(
"*.dylib")});
1109 for (
const QString &lib : libs) {
1110 if (lib.endsWith(QStringLiteral(
"_debug.dylib")) != useDebugLibs)
1112 if (!predicate || predicate(lib))
1113 pluginList.append(subDirectory + u'/' + lib);
1118 addPlugins(QStringLiteral(
"platforms"), [](
const QString &lib) {
1120 if (!lib.contains(QStringLiteral(
"cocoa")))
1126 addPlugins(QStringLiteral(
"printsupport"));
1129 addPlugins(QStringLiteral(
"styles"));
1132 const QString libInfix = getLibInfix(deploymentInfo.deployedFrameworks);
1135 if (deploymentInfo.containsModule(
"Network", libInfix)) {
1136 addPlugins(QStringLiteral(
"tls"));
1137 addPlugins(QStringLiteral(
"networkinformation"));
1141 const bool usesSvg = deploymentInfo.containsModule(
"Svg", libInfix);
1142 addPlugins(QStringLiteral(
"imageformats"), [usesSvg](
const QString &lib) {
1143 if (lib.contains(QStringLiteral(
"qsvg")) && !usesSvg)
1148 addPlugins(QStringLiteral(
"iconengines"));
1151 if (deploymentInfo.containsModule(
"Gui", libInfix)) {
1152 addPlugins(QStringLiteral(
"platforminputcontexts"), [&addPlugins](
const QString &lib) {
1154 if (lib.startsWith(QStringLiteral(
"libqtvirtualkeyboard")))
1155 addPlugins(QStringLiteral(
"virtualkeyboard"));
1161 if (deploymentInfo.containsModule(
"Sql", libInfix)) {
1162 addPlugins(QStringLiteral(
"sqldrivers"), [](
const QString &lib) {
1163 if (lib.startsWith(QStringLiteral(
"libqsqlodbc")) || lib.startsWith(QStringLiteral(
"libqsqlpsql"))) {
1164 LogWarning() <<
"Plugin" << lib <<
"uses private API and is not Mac App store compliant.";
1175 if (deploymentInfo.containsModule(
"WebView", libInfix)) {
1176 addPlugins(QStringLiteral(
"webview"), [](
const QString &lib) {
1177 if (lib.startsWith(QStringLiteral(
"libqtwebview_webengine"))) {
1178 LogWarning() <<
"Plugin" << lib <<
"uses QtWebEngine and is not Mac App store compliant.";
1189 static const std::map<QString, std::vector<QString>> map {
1190 {QStringLiteral(
"Multimedia"), {QStringLiteral(
"multimedia")}},
1191 {QStringLiteral(
"3DRender"), {QStringLiteral(
"sceneparsers"), QStringLiteral(
"geometryloaders"), QStringLiteral(
"renderers")}},
1192 {QStringLiteral(
"3DQuickRender"), {QStringLiteral(
"renderplugins")}},
1193 {QStringLiteral(
"Positioning"), {QStringLiteral(
"position")}},
1194 {QStringLiteral(
"Location"), {QStringLiteral(
"geoservices")}},
1195 {QStringLiteral(
"TextToSpeech"), {QStringLiteral(
"texttospeech")}},
1196 {QStringLiteral(
"SerialBus"), {QStringLiteral(
"canbus")}},
1199 for (
const auto &it : map) {
1200 if (deploymentInfo.containsModule(it.first, libInfix)) {
1201 for (
const auto &pluginType : it.second) {
1202 addPlugins(pluginType);
1207 for (
const QString &plugin : pluginList) {
1208 QString sourcePath = pluginSourcePath +
"/" + plugin;
1209 const QString destinationPath = pluginDestinationPath +
"/" + plugin;
1211 dir.mkpath(QFileInfo(destinationPath).path());
1213 if (copyFilePrintStatus(sourcePath, destinationPath)) {
1214 runStrip(destinationPath);
1215 QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
1216 deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
1224 QByteArray contents =
"[Paths]\n"
1225 "Plugins = PlugIns\n"
1226 "Imports = Resources/qml\n"
1227 "QmlImports = Resources/qml\n";
1229 QString filePath = appBundlePath +
"/Contents/Resources/";
1230 QString fileName = filePath +
"qt.conf";
1232 QDir().mkpath(filePath);
1234 if (QFile::exists(fileName) && !alwaysOwerwriteEnabled) {
1236 LogWarning() << fileName <<
"already exists, will not overwrite.";
1237 LogWarning() <<
"To make sure the plugins are loaded from the correct location,";
1238 LogWarning() <<
"please make sure qt.conf contains the following lines:";
1244 if (QSaveFile qtconf(fileName); qtconf.open(QIODevice::WriteOnly)
1245 && qtconf.write(contents) != -1 && qtconf.commit()) {
1246 LogNormal() <<
"Created configuration file:" << fileName;
1247 LogNormal() <<
"This file sets the plugin search path to" << appBundlePath +
"/Contents/PlugIns";
1254 applicationBundle.path = appBundlePath;
1255 applicationBundle.binaryPath = findAppBinary(appBundlePath);
1257 const QString pluginDestinationPath = appBundlePath +
"/" +
"Contents/PlugIns";
1258 deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
1261void deployQmlImport(
const QString &appBundlePath,
const QList<QString> &rpaths,
const QString &importSourcePath,
const QString &importName)
1263 QString importDestinationPath = appBundlePath +
"/Contents/Resources/qml/" + importName;
1267 if (QDir().exists(importDestinationPath))
1270 recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath);
1275 QVariantMap import1 = v1.toMap();
1276 QVariantMap import2 = v2.toMap();
1277 QString path1 = import1[
"path"].toString();
1278 QString path2 = import2[
"path"].toString();
1279 return path1 < path2;
1286 LogNormal() <<
"Deploying QML imports ";
1287 LogNormal() <<
"Application QML file path(s) is" << qmlDirs;
1288 LogNormal() <<
"QML module search path(s) is" << qmlImportPaths;
1291 QString qmlImportScannerPath =
1292 QDir::cleanPath(QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath)
1293 +
"/qmlimportscanner");
1296 if (!QFile::exists(qmlImportScannerPath))
1297 qmlImportScannerPath = QCoreApplication::applicationDirPath() +
"/qmlimportscanner";
1300 if (!QFile::exists(qmlImportScannerPath)) {
1301 LogError() <<
"qmlimportscanner not found at" << qmlImportScannerPath;
1302 LogError() <<
"Rebuild qtdeclarative/tools/qmlimportscanner";
1308 QStringList argumentList;
1309 for (
const QString &qmlDir : qmlDirs) {
1310 argumentList.append(
"-rootPath");
1311 argumentList.append(qmlDir);
1313 for (
const QString &importPath : qmlImportPaths)
1314 argumentList <<
"-importPath" << importPath;
1315 QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
1316 argumentList.append(
"-importPath");
1317 argumentList.append(qmlImportsPath);
1320 QProcess qmlImportScanner;
1321 qmlImportScanner.start(qmlImportScannerPath, argumentList);
1322 if (!qmlImportScanner.waitForStarted()) {
1323 LogError() <<
"Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
1326 qmlImportScanner.waitForFinished(-1);
1329 qmlImportScanner.setReadChannel(QProcess::StandardError);
1330 QByteArray errors = qmlImportScanner.readAll();
1331 if (!errors.isEmpty()) {
1332 LogWarning() <<
"QML file parse error (deployment will continue):";
1337 qmlImportScanner.setReadChannel(QProcess::StandardOutput);
1338 QByteArray json = qmlImportScanner.readAll();
1339 QJsonDocument doc = QJsonDocument::fromJson(json);
1340 if (!doc.isArray()) {
1341 LogError() <<
"qmlimportscanner output error. Expected json array, got:";
1349 QVariantList array = doc.array().toVariantList();
1350 std::sort(array.begin(), array.end(), importLessThan);
1353 for (
const QVariant &importValue : array) {
1354 QVariantMap import = importValue.toMap();
1355 QString name = import[
"name"].toString();
1356 QString path = import[
"path"].toString();
1357 QString type = import[
"type"].toString();
1359 LogNormal() <<
"Deploying QML import" << name;
1362 if (name.isEmpty() || path.isEmpty()) {
1363 LogNormal() <<
" Skip import: name or path is empty";
1370 if (type != QStringLiteral(
"module")) {
1371 LogNormal() <<
" Skip non-module import";
1379 name.replace(u'.', u'/');
1380 int secondTolast = path.length() - 2;
1381 QString version = path.mid(secondTolast);
1382 if (version.startsWith(u'.'))
1383 name.append(version);
1385 deployQmlImport(appBundlePath, deploymentInfo.rpathsUsed, path, name);
1396 QString codeSignLogMessage =
"codesign";
1398 codeSignLogMessage +=
", enable hardened runtime";
1400 codeSignLogMessage +=
", include secure timestamp";
1401 LogNormal() << codeSignLogMessage << filePath;
1403 QStringList codeSignOptions = {
"--preserve-metadata=identifier,entitlements",
"--force",
"-s",
1404 identity, filePath };
1406 codeSignOptions <<
"-o" <<
"runtime";
1409 codeSignOptions <<
"--timestamp";
1411 if (!extraEntitlements.isEmpty())
1412 codeSignOptions <<
"--entitlements" << extraEntitlements;
1415 codesign.start(
"codesign", codeSignOptions);
1416 codesign.waitForFinished(-1);
1418 QByteArray err = codesign.readAllStandardError();
1419 if (codesign.exitCode() > 0) {
1420 LogError() <<
"Codesign signing error:";
1422 }
else if (!err.isEmpty()) {
1428 const QString &appBundlePath,
1429 QList<QString> additionalBinariesContainingRpaths)
1439 LogNormal() <<
"Signing" << appBundlePath <<
"with identity" << identity;
1441 QStack<QString> pendingBinaries;
1442 QSet<QString> pendingBinariesSet;
1443 QSet<QString> signedBinaries;
1447 QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath();
1448 QString rootBinariesPath = appBundleAbsolutePath +
"/Contents/MacOS/";
1449 QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() <<
"*", QDir::Files);
1450 for (
const QString &binary : foundRootBinaries) {
1451 QString binaryPath = rootBinariesPath + binary;
1452 pendingBinaries.push(binaryPath);
1453 pendingBinariesSet.insert(binaryPath);
1454 additionalBinariesContainingRpaths.append(binaryPath);
1457 bool getAbsoltuePath =
true;
1458 QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath +
"/Contents/PlugIns/", getAbsoltuePath);
1459 for (
const QString &binary : foundPluginBinaries) {
1460 pendingBinaries.push(binary);
1461 pendingBinariesSet.insert(binary);
1465 QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath);
1466 for (
const QString &frameworkPath : frameworkPaths) {
1470 QDirIterator helpersIterator(frameworkPath, QStringList() << QString::fromLatin1(
"Helpers"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1471 while (helpersIterator.hasNext()) {
1472 helpersIterator.next();
1473 QString helpersPath = helpersIterator.filePath();
1474 QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() <<
"*.app", QDir::Dirs);
1475 for (
const QString &innerBundleName : innerBundleNames)
1476 signedBinaries += codesignBundle(identity,
1477 helpersPath +
"/" + innerBundleName,
1478 additionalBinariesContainingRpaths);
1483 QDirIterator librariesIterator(frameworkPath, QStringList() << QString::fromLatin1(
"Libraries"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
1484 while (librariesIterator.hasNext()) {
1485 librariesIterator.next();
1486 QString librariesPath = librariesIterator.filePath();
1487 QStringList bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath);
1488 for (
const QString &binary : bundleFiles) {
1489 pendingBinaries.push(binary);
1490 pendingBinariesSet.insert(binary);
1496 while (!pendingBinaries.isEmpty()) {
1497 QString binary = pendingBinaries.pop();
1498 if (signedBinaries.contains(binary))
1502 QStringList dependencies = getBinaryDependencies(rootBinariesPath, binary,
1503 additionalBinariesContainingRpaths);
1504 dependencies = QSet<QString>(dependencies.begin(), dependencies.end())
1505 .subtract(signedBinaries)
1506 .subtract(pendingBinariesSet)
1509 if (!dependencies.isEmpty()) {
1510 pendingBinaries.push(binary);
1511 pendingBinariesSet.insert(binary);
1512 int dependenciesSkipped = 0;
1513 for (
const QString &dependency : std::as_const(dependencies)) {
1518 if (!dependency.startsWith(appBundleAbsolutePath)) {
1519 ++dependenciesSkipped;
1520 LogNormal() <<
"Skipping outside dependency: " << dependency;
1523 pendingBinaries.push(dependency);
1524 pendingBinariesSet.insert(dependency);
1529 if (dependenciesSkipped == dependencies.size()) {
1530 pendingBinaries.pop();
1537 extraEntitlements = findEntitlementsFile(appBundleAbsolutePath +
"/Contents/Resources/");
1540 codesignFile(identity, binary);
1541 signedBinaries.insert(binary);
1542 pendingBinariesSet.remove(binary);
1545 LogNormal() <<
"Finished codesigning " << appBundlePath <<
"with identity" << identity;
1549 codesign.start(
"codesign", QStringList() <<
"--deep" <<
"-v" << appBundlePath);
1550 codesign.waitForFinished(-1);
1551 QByteArray err = codesign.readAllStandardError();
1552 if (codesign.exitCode() > 0) {
1553 LogError() <<
"codesign verification error:";
1555 }
else if (!err.isEmpty()) {
1559 return signedBinaries;
1562void codesign(
const QString &identity,
const QString &appBundlePath) {
1563 codesignBundle(identity, appBundlePath, QList<QString>());
1568 QString appBaseName = appBundlePath;
1569 appBaseName.chop(4);
1571 QString dmgName = appBaseName +
".dmg";
1579 LogNormal() <<
"Disk image already exists, skipping .dmg creation for" << dmg.fileName();
1581 LogNormal() <<
"Creating disk image (.dmg) for" << appBundlePath;
1584 LogNormal() <<
"Image will use" << filesystemType;
1587 QStringList options = QStringList()
1588 <<
"create" << dmgName
1589 <<
"-srcfolder" << appBundlePath
1590 <<
"-format" <<
"UDZO"
1591 <<
"-fs" << filesystemType
1592 <<
"-volname" << appBaseName;
1595 hdutil.start(
"hdiutil", options);
1596 hdutil.waitForFinished(-1);
1597 if (hdutil.exitCode() != 0) {
1598 LogError() <<
"Bundle creation error:" << hdutil.readAllStandardError();
1605 QStringList parts = frameworkName.split(
".");
1606 if (parts.count() < 2) {
1607 LogError() <<
"fixupFramework: Unexpected framework name" << frameworkName;
1612 QString frameworkBinary = frameworkName + QStringLiteral(
"/") + parts[0];
1617 linkFilePrintStatus(
"Current", frameworkName +
"/Versions/A");
1620 changeIdentification(
"@rpath/" + frameworkBinary, frameworkBinary);
1621 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)