summaryrefslogtreecommitdiffstats
path: root/src/macdeployqt
diff options
context:
space:
mode:
authorJake Petroules <jake.petroules@qt.io>2016-04-20 15:41:48 -0700
committerJake Petroules <jake.petroules@qt.io>2016-05-10 16:31:39 +0000
commit75b595a6867652cc2b3ff8b2749f91236b6cb58c (patch)
treef14b6da75c8e19623496ce03d2e9fbf0da2bffd5 /src/macdeployqt
parente126ccfa0c8f06bd52f0175766dd5a5321d74325 (diff)
Fix macdeployqt usage when client application has no rpath to Qt libs.
macdeployqt used to determine the Qt library path from one of the Qt libraries linked to by the application (by searching its rpath list). However, if an application does not have an rpath pointing to the Qt library directory, macdeployqt cannot find the Qt libraries. This is likely when the application is built with a non-qmake build system like CMake or qbs. This patch adds the Qt library path (from QLibraryInfo) as a fallback search path to allow macdeployqt to find the Qt libraries and deploy them. Task-number: QTBUG-47868 Change-Id: Ie1d005955826b416c223a1bc6cac911d2ae44b20 Reviewed-by: Guilherme Steinmann <guidefloripa@gmail.com> Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@theqtcompany.com>
Diffstat (limited to 'src/macdeployqt')
-rw-r--r--src/macdeployqt/shared/shared.cpp120
-rw-r--r--src/macdeployqt/shared/shared.h20
2 files changed, 91 insertions, 49 deletions
diff --git a/src/macdeployqt/shared/shared.cpp b/src/macdeployqt/shared/shared.cpp
index 6fb5f6778..c0396ab7f 100644
--- a/src/macdeployqt/shared/shared.cpp
+++ b/src/macdeployqt/shared/shared.cpp
@@ -46,6 +46,7 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
+#include <QRegularExpression>
#include "shared.h"
#ifdef Q_OS_DARWIN
@@ -164,6 +165,56 @@ void patch_debugInInfoPlist(const QString &infoPlistPath)
infoPlist.write(contents);
}
+OtoolInfo findDependencyInfo(const QString &binaryPath)
+{
+ OtoolInfo info;
+ info.binaryPath = binaryPath;
+
+ LogDebug() << "Using otool:";
+ LogDebug() << " inspecting" << binaryPath;
+ QProcess otool;
+ otool.start("otool", QStringList() << "-L" << binaryPath);
+ otool.waitForFinished();
+
+ if (otool.exitCode() != 0) {
+ LogError() << otool.readAllStandardError();
+ }
+
+ static const QRegularExpression regexp(QStringLiteral(
+ "^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "
+ "current version (\\d+\\.\\d+\\.\\d+)\\)$"));
+
+ QString output = otool.readAllStandardOutput();
+ QStringList outputLines = output.split("\n", QString::SkipEmptyParts);
+ outputLines.removeFirst(); // remove line containing the binary path
+ if (binaryPath.contains(".framework/") || binaryPath.endsWith(".dylib")) {
+ const auto match = regexp.match(outputLines.first());
+ if (match.hasMatch()) {
+ info.installName = match.captured(1);
+ info.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
+ info.currentVersion = QVersionNumber::fromString(match.captured(3));
+ } else {
+ LogError() << "Could not parse otool output line:" << outputLines.first();
+ }
+ outputLines.removeFirst();
+ }
+
+ for (const QString &outputLine : outputLines) {
+ const auto match = regexp.match(outputLine);
+ if (match.hasMatch()) {
+ DylibInfo dylib;
+ dylib.binaryPath = match.captured(1);
+ dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
+ dylib.currentVersion = QVersionNumber::fromString(match.captured(3));
+ info.dependencies << dylib;
+ } else {
+ LogError() << "Could not parse otool output line:" << outputLine;
+ }
+ }
+
+ return info;
+}
+
FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs)
{
FrameworkInfo info;
@@ -230,13 +281,11 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
if (state == QtPath) {
// Check for library name part
if (part < parts.count() && parts.at(part).contains(".dylib ")) {
- info.installName += "/" + (qtPath + currentPart + "/").simplified();
- info.frameworkDirectory = info.installName;
+ info.frameworkDirectory += "/" + (qtPath + currentPart + "/").simplified();
state = DylibName;
continue;
} else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
- info.installName += "/" + (qtPath + "lib/").simplified();
- info.frameworkDirectory = info.installName;
+ info.frameworkDirectory += "/" + (qtPath + "lib/").simplified();
state = FrameworkName;
continue;
} else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package.
@@ -263,11 +312,10 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
++part;
continue;
} if (state == DylibName) {
- name = currentPart.split(" (compatibility").at(0);
+ name = currentPart;
info.isDylib = true;
info.frameworkName = name;
info.binaryName = name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.'));
- info.installName += name;
info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName;
info.frameworkPath = info.frameworkDirectory + info.binaryName;
info.sourceFilePath = info.frameworkPath;
@@ -283,7 +331,6 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
info.binaryDirectory = "Versions/" + info.version;
info.binaryName = name + suffix;
info.binaryPath = "/" + info.binaryDirectory + "/" + info.binaryName;
- info.installName += info.frameworkName + "/" + info.binaryDirectory + "/" + name;
info.deployedInstallName = "@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath;
info.frameworkPath = info.frameworkDirectory + info.frameworkName;
info.sourceFilePath = info.frameworkPath + info.binaryPath;
@@ -295,6 +342,9 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
}
}
+ info.installName = findDependencyInfo(info.sourceFilePath).installName;
+ if (info.installName.startsWith("@rpath/"))
+ info.deployedInstallName = info.installName;
return info;
}
@@ -394,11 +444,11 @@ QStringList findAppBundleFiles(const QString &appBundlePath, bool absolutePath =
return result;
}
-QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs)
+QList<FrameworkInfo> getQtFrameworks(const QList<DylibInfo> &dependencies, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs)
{
QList<FrameworkInfo> libraries;
- foreach(const QString line, otoolLines) {
- FrameworkInfo info = parseOtoolLibraryLine(line, appBundlePath, rpaths, useDebugLibs);
+ for (const DylibInfo &dylibInfo : dependencies) {
+ FrameworkInfo info = parseOtoolLibraryLine(dylibInfo.binaryPath, appBundlePath, rpaths, useDebugLibs);
if (info.frameworkName.isEmpty() == false) {
LogDebug() << "Adding framework:";
LogDebug() << info;
@@ -476,23 +526,8 @@ QSet<QString> getBinaryRPaths(const QString &path, bool resolve = true, QString
QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs)
{
- LogDebug() << "Using otool:";
- LogDebug() << " inspecting" << path;
- QProcess otool;
- otool.start("otool", QStringList() << "-L" << path);
- otool.waitForFinished();
-
- if (otool.exitCode() != 0) {
- LogError() << otool.readAllStandardError();
- }
-
- QString output = otool.readAllStandardOutput();
- QStringList outputLines = output.split("\n");
- outputLines.removeFirst(); // remove line containing the binary path
- if (path.contains(".framework") || path.contains(".dylib"))
- outputLines.removeFirst(); // frameworks and dylibs print install name of themselves first.
-
- return getQtFrameworks(outputLines, appBundlePath, rpaths + getBinaryRPaths(path), useDebugLibs);
+ const OtoolInfo info = findDependencyInfo(path);
+ return getQtFrameworks(info.dependencies, appBundlePath, rpaths + getBinaryRPaths(path), useDebugLibs);
}
QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs)
@@ -516,24 +551,14 @@ QStringList getBinaryDependencies(const QString executablePath,
{
QStringList binaries;
- QProcess otool;
- otool.start("otool", QStringList() << "-L" << path);
- otool.waitForFinished();
-
- if (otool.exitCode() != 0) {
- LogError() << otool.readAllStandardError();
- }
-
- QString output = otool.readAllStandardOutput();
- QStringList outputLines = output.split("\n");
- outputLines.removeFirst(); // remove line containing the binary path
+ const auto dependencies = findDependencyInfo(path).dependencies;
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
+ foreach (const DylibInfo &info, dependencies) {
+ QString trimmedLine = info.binaryPath;
if (trimmedLine.startsWith("@executable_path/")) {
QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length()));
if (binary != path)
@@ -868,15 +893,10 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
const FrameworkInfo framework = frameworks.takeFirst();
copiedFrameworks.append(framework.frameworkName);
- // Get the qt path from one of the Qt frameworks;
- if (deploymentInfo.qtPath.isNull() && framework.frameworkName.contains("Qt")
- && framework.frameworkDirectory.contains("/lib"))
- {
- deploymentInfo.qtPath = framework.frameworkDirectory;
- deploymentInfo.qtPath.chop(5); // remove "/lib/"
- }
+ if (deploymentInfo.qtPath.isNull())
+ deploymentInfo.qtPath = QLibraryInfo::location(QLibraryInfo::PrefixPath);
- if (framework.installName.startsWith("@executable_path/") || framework.installName.startsWith("@rpath/")) {
+ if (framework.frameworkDirectory.startsWith(bundlePath)) {
LogError() << framework.frameworkName << "already deployed, skipping.";
continue;
}
@@ -932,7 +952,9 @@ DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringLis
applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
<< additionalExecutables;
- QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, getBinaryRPaths(applicationBundle.binaryPath, true), useDebugLibs);
+ QSet<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true);
+ allLibraryPaths.insert(QLibraryInfo::location(QLibraryInfo::LibrariesPath));
+ QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) {
LogWarning();
LogWarning() << "Could not find any external Qt frameworks to deploy in" << appBundlePath;
diff --git a/src/macdeployqt/shared/shared.h b/src/macdeployqt/shared/shared.h
index 604e3ef4a..47b93f42b 100644
--- a/src/macdeployqt/shared/shared.h
+++ b/src/macdeployqt/shared/shared.h
@@ -37,6 +37,7 @@
#include <QStringList>
#include <QDebug>
#include <QSet>
+#include <QVersionNumber>
extern int logLevel;
#define LogError() if (logLevel < 0) {} else qDebug() << "ERROR:"
@@ -65,6 +66,24 @@ public:
QString binaryDestinationDirectory;
};
+class DylibInfo
+{
+public:
+ QString binaryPath;
+ QVersionNumber currentVersion;
+ QVersionNumber compatibilityVersion;
+};
+
+class OtoolInfo
+{
+public:
+ QString installName;
+ QString binaryPath;
+ QVersionNumber currentVersion;
+ QVersionNumber compatibilityVersion;
+ QList<DylibInfo> dependencies;
+};
+
bool operator==(const FrameworkInfo &a, const FrameworkInfo &b);
QDebug operator<<(QDebug debug, const FrameworkInfo &info);
@@ -91,6 +110,7 @@ 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);
+OtoolInfo findDependencyInfo(const QString &binaryPath);
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, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs);