diff options
author | Jake Petroules <jake.petroules@qt.io> | 2016-04-20 15:41:48 -0700 |
---|---|---|
committer | Jake Petroules <jake.petroules@qt.io> | 2016-05-10 16:31:39 +0000 |
commit | 75b595a6867652cc2b3ff8b2749f91236b6cb58c (patch) | |
tree | f14b6da75c8e19623496ce03d2e9fbf0da2bffd5 /src | |
parent | e126ccfa0c8f06bd52f0175766dd5a5321d74325 (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')
-rw-r--r-- | src/macdeployqt/shared/shared.cpp | 120 | ||||
-rw-r--r-- | src/macdeployqt/shared/shared.h | 20 |
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); |