diff options
author | Adam Strzelecki <ono@java.pl> | 2014-11-01 20:46:26 +0100 |
---|---|---|
committer | Morten Johan Sørvig <morten.sorvig@theqtcompany.com> | 2015-04-10 11:07:08 +0000 |
commit | ad31b989bff4ef0c616b1beccd4b30933683dd98 (patch) | |
tree | 638a9108fc09477b606649be378e752f4438a531 /src/macdeployqt/shared | |
parent | 6d0c86d65b6fa0f37ba696de28fea4e977d0a282 (diff) |
macdeployqt: Support Qt frameworks using rpath
This makes macdeployqt understand dependencies expressed with @rpath
prefix which are resolved using Mach-O LC_RPATH commands.
Follows up c0a54efc4091b365ffac09fc2827cf92f849d698 from qtbase.
Task-number: QTBUG-31814
Change-Id: I83156815e236ae52306aaecfc84f99d097284fa8
Reviewed-by: Morten Johan Sørvig <morten.sorvig@theqtcompany.com>
Diffstat (limited to 'src/macdeployqt/shared')
-rw-r--r-- | src/macdeployqt/shared/shared.cpp | 215 | ||||
-rw-r--r-- | src/macdeployqt/shared/shared.h | 8 |
2 files changed, 200 insertions, 23 deletions
diff --git a/src/macdeployqt/shared/shared.cpp b/src/macdeployqt/shared/shared.cpp index f372dfeff..1c9b319ad 100644 --- a/src/macdeployqt/shared/shared.cpp +++ b/src/macdeployqt/shared/shared.cpp @@ -162,7 +162,7 @@ void patch_debugInInfoPlist(const QString &infoPlistPath) infoPlist.write(contents); } -FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs) +FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs) { FrameworkInfo info; QString trimmed = line.trimmed(); @@ -173,9 +173,43 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs) // Don't deploy system libraries. if (trimmed.startsWith("/System/Library/") || (trimmed.startsWith("/usr/lib/") && trimmed.contains("libQt") == false) // exception for libQtuitools and libQtlucene - || trimmed.startsWith("@executable_path") || trimmed.startsWith("@loader_path") || trimmed.startsWith("@rpath")) + || trimmed.startsWith("@executable_path") || trimmed.startsWith("@loader_path")) return info; + // Resolve rpath relative libraries. + if (trimmed.startsWith("@rpath/")) { + trimmed = trimmed.mid(QStringLiteral("@rpath/").length()); + bool foundInsideBundle = false; + foreach (const QString &rpath, rpaths) { + QString path = QDir::cleanPath(rpath + "/" + trimmed); + // Skip paths already inside the bundle. + if (!appBundlePath.isEmpty()) { + if (QDir::isAbsolutePath(appBundlePath)) { + if (path.startsWith(QDir::cleanPath(appBundlePath) + "/")) { + foundInsideBundle = true; + continue; + } + } else { + if (path.startsWith(QDir::cleanPath(QDir::currentPath() + "/" + appBundlePath) + "/")) { + foundInsideBundle = true; + continue; + } + } + } + // Try again with substituted rpath. + FrameworkInfo resolvedInfo = parseOtoolLibraryLine(path, appBundlePath, rpaths, useDebugLibs); + if (!resolvedInfo.frameworkName.isEmpty() && QFile::exists(resolvedInfo.frameworkPath)) { + resolvedInfo.rpathUsed = rpath; + return resolvedInfo; + } + } + if (!rpaths.isEmpty() && !foundInsideBundle) { + LogError() << "Cannot resolve rpath" << trimmed; + LogError() << " using" << rpaths; + } + return info; + } + enum State {QtPath, FrameworkName, DylibName, Version, End}; State state = QtPath; int part = 0; @@ -329,11 +363,11 @@ QStringList findAppBundleFiles(const QString &appBundlePath) return result; } -QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, bool useDebugLibs) +QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs) { QList<FrameworkInfo> libraries; foreach(const QString line, otoolLines) { - FrameworkInfo info = parseOtoolLibraryLine(line, useDebugLibs); + FrameworkInfo info = parseOtoolLibraryLine(line, appBundlePath, rpaths, useDebugLibs); if (info.frameworkName.isEmpty() == false) { LogDebug() << "Adding framework:"; LogDebug() << info; @@ -341,9 +375,75 @@ QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, bool useDebu } } return libraries; -}; +} + +QString resolveDyldPrefix(const QString &path, const QString &loaderPath, const QString &executablePath) +{ + if (path.startsWith("@")) { + if (path.startsWith(QStringLiteral("@executable_path/"))) { + // path relative to bundle executable dir + if (QDir::isAbsolutePath(executablePath)) { + return QDir::cleanPath(QFileInfo(executablePath).path() + path.mid(QStringLiteral("@executable_path").length())); + } else { + return QDir::cleanPath(QDir::currentPath() + "/" + + QFileInfo(executablePath).path() + path.mid(QStringLiteral("@executable_path").length())); + } + } else if (path.startsWith(QStringLiteral("@loader_path/"))) { + // path relative to loader dir + if (QDir::isAbsolutePath(loaderPath)) { + return QDir::cleanPath(QFileInfo(loaderPath).path() + path.mid(QStringLiteral("@loader_path").length())); + } else { + return QDir::cleanPath(QDir::currentPath() + "/" + + QFileInfo(loaderPath).path() + path.mid(QStringLiteral("@loader_path").length())); + } + } else { + LogError() << "Unexpected prefix" << path; + } + } + return path; +} + +QSet<QString> getBinaryRPaths(const QString &path, bool resolve = true, QString executablePath = QString()) +{ + QSet<QString> rpaths; + + QProcess otool; + otool.start("otool", QStringList() << "-l" << path); + otool.waitForFinished(); + + if (otool.exitCode() != 0) { + LogError() << otool.readAllStandardError(); + } + + if (resolve && executablePath.isEmpty()) { + executablePath = path; + } + + QString output = otool.readAllStandardOutput(); + QStringList outputLines = output.split("\n"); + QStringListIterator i(outputLines); + + while (i.hasNext()) { + if (i.next().contains("cmd LC_RPATH") && i.hasNext() && + i.next().contains("cmdsize") && i.hasNext()) { + const QString &rpathCmd = i.next(); + int pathStart = rpathCmd.indexOf("path "); + int pathEnd = rpathCmd.indexOf(" ("); + if (pathStart >= 0 && pathEnd >= 0 && pathStart < pathEnd) { + QString rpath = rpathCmd.mid(pathStart + 5, pathEnd - pathStart - 5); + if (resolve) { + rpaths << resolveDyldPrefix(rpath, path, executablePath); + } else { + rpaths << rpath; + } + } + } + } + + return rpaths; +} -QList<FrameworkInfo> getQtFrameworks(const QString &path, bool useDebugLibs) +QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs) { LogDebug() << "Using otool:"; LogDebug() << " inspecting" << path; @@ -361,15 +461,15 @@ QList<FrameworkInfo> getQtFrameworks(const QString &path, bool useDebugLibs) if (path.contains(".framework") || path.contains(".dylib")) outputLines.removeFirst(); // frameworks and dylibs print install name of themselves first. - return getQtFrameworks(outputLines, useDebugLibs); + return getQtFrameworks(outputLines, appBundlePath, rpaths + getBinaryRPaths(path), useDebugLibs); } -QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, bool useDebugLibs) +QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs) { QList<FrameworkInfo> result; QSet<QString> existing; foreach (const QString &path, paths) { - foreach (const FrameworkInfo &info, getQtFrameworks(path, useDebugLibs)) { + foreach (const FrameworkInfo &info, getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) { if (!existing.contains(info.frameworkPath)) { // avoid duplicates existing.insert(info.frameworkPath); result << info; @@ -395,6 +495,9 @@ QStringList getBinaryDependencies(const QString executablePath, const QString &p QStringList outputLines = output.split("\n"); outputLines.removeFirst(); // remove line containing the binary path + bool rpathsLoaded = false; + QSet<QString> rpaths; + // return bundle-local dependencies. (those starting with @executable_path) foreach (const QString &line, outputLines) { QString trimmedLine = line.mid(0, line.indexOf("(")).trimmed(); // remove "(compatibility version ...)" and whitespace @@ -402,6 +505,25 @@ QStringList getBinaryDependencies(const QString executablePath, const QString &p QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length())); if (binary != path) binaries.append(binary); + } else if (trimmedLine.startsWith("@rpath/")) { + if (!rpathsLoaded) { + rpaths = getBinaryRPaths(path, true, executablePath); + rpathsLoaded = true; + } + bool resolved = false; + foreach (const QString &rpath, rpaths) { + QString binary = QDir::cleanPath(rpath + "/" + trimmedLine.mid(QStringLiteral("@rpath/").length())); + LogDebug() << "Checking for" << binary; + if (QFile::exists(binary)) { + binaries.append(binary); + resolved = true; + break; + } + } + if (!resolved && !rpaths.isEmpty()) { + LogError() << "Cannot resolve rpath" << trimmedLine; + LogError() << " using" << rpaths; + } } } @@ -437,6 +559,7 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QString &sourceP LogNormal() << "copy:" << sourcePath << destinationPath; + QSet<QString> rpaths = getBinaryRPaths(findAppBinary(appBundlePath), true); QStringList files = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Files | QDir::NoDotAndDotDot); foreach (QString file, files) { const QString fileSourcePath = sourcePath + QLatin1Char('/') + file; @@ -474,7 +597,7 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QString &sourceP runStrip(fileDestinationPath); bool useDebugLibs = false; bool useLoaderPath = false; - QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, useDebugLibs); + QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, appBundlePath, rpaths, useDebugLibs); deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath); } } else { @@ -616,6 +739,43 @@ void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework } } +void deployRPaths(const QSet<QString> &rpaths, const QString &binaryPath, bool useLoaderPath) +{ + bool rpathToFrameworksFound = false; + QStringList args; + foreach (const QString &rpath, getBinaryRPaths(binaryPath, false)) { + if (rpath == "@executable_path/../Frameworks" || + rpath == "@loader_path/../Frameworks") { + rpathToFrameworksFound = true; + continue; + } + if (rpaths.contains(resolveDyldPrefix(rpath, binaryPath, binaryPath))) { + args << "-delete_rpath" << rpath; + } + } + if (!args.length()) { + return; + } + if (!rpathToFrameworksFound) { + if (!useLoaderPath) { + args << "-add_rpath" << "@executable_path/../Frameworks"; + } else { + args << "-add_rpath" << "@loader_path/../Frameworks"; + } + } + LogDebug() << "Using install_name_tool:"; + LogDebug() << " change rpaths in" << binaryPath; + LogDebug() << " using" << args; + runInstallNameTool(QStringList() << args << binaryPath); +} + +void deployRPaths(const QSet<QString> &rpaths, const QStringList &binaryPaths, bool useLoaderPath) +{ + foreach (const QString &binary, binaryPaths) { + deployRPaths(rpaths, binary, useLoaderPath); + } +} + void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath) { LogDebug() << "Using install_name_tool:"; @@ -657,6 +817,7 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks, QStringList copiedFrameworks; DeploymentInfo deploymentInfo; deploymentInfo.useLoaderPath = useLoaderPath; + QSet<QString> rpathsUsed; while (frameworks.isEmpty() == false) { const FrameworkInfo framework = frameworks.takeFirst(); @@ -670,13 +831,17 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks, deploymentInfo.qtPath.chop(5); // remove "/lib/" } - if (framework.installName.startsWith("@executable_path/")) { + if (framework.installName.startsWith("@executable_path/") || framework.installName.startsWith("@rpath/")) { LogError() << framework.frameworkName << "already deployed, skipping."; continue; } // Install_name_tool the new id into the binaries - changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath); + if (framework.rpathUsed.isEmpty()) { + changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath); + } else { + rpathsUsed << framework.rpathUsed; + } // Copy the framework/dylib to the app bundle. const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath) @@ -688,14 +853,19 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks, runStrip(deployedBinaryPath); // Install_name_tool it a new id. - changeIdentification(framework.deployedInstallName, deployedBinaryPath); - + if (!framework.rpathUsed.length()) { + changeIdentification(framework.deployedInstallName, deployedBinaryPath); + } // Check for framework dependencies - QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, useDebugLibs); + QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs); foreach (FrameworkInfo dependency, dependencies) { - changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath); + if (dependency.rpathUsed.isEmpty()) { + changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath); + } else { + rpathsUsed << dependency.rpathUsed; + } // Deploy framework if necessary. if (copiedFrameworks.contains(dependency.frameworkName) == false && frameworks.contains(dependency) == false) { @@ -703,7 +873,8 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks, } } } - deploymentInfo.deployedFrameworks = copiedFrameworks; + deployRPaths(rpathsUsed, binaryPaths, useLoaderPath); + deploymentInfo.rpathsUsed += rpathsUsed; return deploymentInfo; } @@ -715,7 +886,7 @@ DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringLis applicationBundle.libraryPaths = findAppLibraries(appBundlePath); QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths << additionalExecutables; - QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, useDebugLibs); + QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, getBinaryRPaths(applicationBundle.binaryPath, true), useDebugLibs); if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) { LogWarning(); LogWarning() << "Could not find any external Qt frameworks to deploy in" << appBundlePath; @@ -804,7 +975,7 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl if (copyFilePrintStatus(sourcePath, destinationPath)) { runStrip(destinationPath); - QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, useDebugLibs); + QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs); deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath); } @@ -979,7 +1150,7 @@ void changeQtFrameworks(const QString appPath, const QString &qtPath, bool useDe { const QString appBinaryPath = findAppBinary(appPath); const QStringList libraryPaths = findAppLibraries(appPath); - const QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(QStringList() << appBinaryPath << libraryPaths, useDebugLibs); + const QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(QStringList() << appBinaryPath << libraryPaths, appPath, getBinaryRPaths(appBinaryPath, true), useDebugLibs); if (frameworks.isEmpty()) { LogWarning(); LogWarning() << "Could not find any _external_ Qt frameworks to change in" << appPath; @@ -1037,6 +1208,10 @@ void codesign(const QString &identity, const QString &appBundlePath) foreach (const QString &binary, foundPluginBinaries) pendingBinaries.push(binary); + QStringList foundLibraries = findAppBundleFiles(appBundlePath + "/Contents/Frameworks/"); + foreach (const QString &binary, foundLibraries) + pendingBinaries.push(binary); + // Sign all binares; use otool to find and sign dependencies first. while (!pendingBinaries.isEmpty()) { diff --git a/src/macdeployqt/shared/shared.h b/src/macdeployqt/shared/shared.h index 5ebf69502..43f1c24c5 100644 --- a/src/macdeployqt/shared/shared.h +++ b/src/macdeployqt/shared/shared.h @@ -55,6 +55,7 @@ public: QString binaryDirectory; QString binaryName; QString binaryPath; + QString rpathUsed; QString version; QString installName; QString deployedInstallName; @@ -80,6 +81,7 @@ public: QString qtPath; QString pluginPath; QStringList deployedFrameworks; + QSet<QString> rpathsUsed; bool useLoaderPath; }; @@ -89,10 +91,10 @@ inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info); void changeQtFrameworks(const QString appPath, const QString &qtPath, bool useDebugLibs); void changeQtFrameworks(const QList<FrameworkInfo> frameworks, const QStringList &binaryPaths, const QString &qtPath); -FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs); +FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs); QString findAppBinary(const QString &appBundlePath); -QList<FrameworkInfo> getQtFrameworks(const QString &path, bool useDebugLibs); -QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, bool useDebugLibs); +QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs); +QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs); QString copyFramework(const FrameworkInfo &framework, const QString path); DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs); DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath); |