summaryrefslogtreecommitdiffstats
path: root/src/macdeployqt/shared
diff options
context:
space:
mode:
authorAdam Strzelecki <ono@java.pl>2014-11-01 20:46:26 +0100
committerMorten Johan Sørvig <morten.sorvig@theqtcompany.com>2015-04-10 11:07:08 +0000
commitad31b989bff4ef0c616b1beccd4b30933683dd98 (patch)
tree638a9108fc09477b606649be378e752f4438a531 /src/macdeployqt/shared
parent6d0c86d65b6fa0f37ba696de28fea4e977d0a282 (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.cpp215
-rw-r--r--src/macdeployqt/shared/shared.h8
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);