Begin to adapt for Linux
This commit is contained in:
@@ -1,4 +0,0 @@
|
|||||||
SOURCES += main.cpp ../shared/shared.cpp
|
|
||||||
LIBS += -framework CoreFoundation
|
|
||||||
|
|
||||||
load(qt_app)
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
|
||||||
** Contact: https://www.qt.io/licensing/
|
|
||||||
**
|
|
||||||
** This file is part of the tools applications of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
|
||||||
** Commercial License Usage
|
|
||||||
** Licensees holding valid commercial Qt licenses may use this file in
|
|
||||||
** accordance with the commercial license agreement provided with the
|
|
||||||
** Software or, alternatively, in accordance with the terms contained in
|
|
||||||
** a written agreement between you and The Qt Company. For licensing terms
|
|
||||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
||||||
** information use the contact form at https://www.qt.io/contact-us.
|
|
||||||
**
|
|
||||||
** GNU General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU
|
|
||||||
** General Public License version 3 as published by the Free Software
|
|
||||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
||||||
** included in the packaging of this file. Please review the following
|
|
||||||
** information to ensure the GNU General Public License requirements will
|
|
||||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
#include "../shared/shared.h"
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
// useDebugLibs should always be false because even if set all Qt
|
|
||||||
// libraries inside a binary to point to debug versions, as soon as
|
|
||||||
// one of them loads a Qt plugin, the plugin itself will load the
|
|
||||||
// release version of Qt, and as such, the app will crash.
|
|
||||||
bool useDebugLibs = false;
|
|
||||||
|
|
||||||
int optionsSpecified = 0;
|
|
||||||
for (int i = 2; i < argc; ++i) {
|
|
||||||
QByteArray argument = QByteArray(argv[i]);
|
|
||||||
if (argument.startsWith(QByteArray("-verbose="))) {
|
|
||||||
LogDebug() << "Argument found:" << argument;
|
|
||||||
optionsSpecified++;
|
|
||||||
int index = argument.indexOf("=");
|
|
||||||
bool ok = false;
|
|
||||||
int number = argument.mid(index+1).toInt(&ok);
|
|
||||||
if (!ok)
|
|
||||||
LogError() << "Could not parse verbose level";
|
|
||||||
else
|
|
||||||
logLevel = number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argc != (3 + optionsSpecified)) {
|
|
||||||
qDebug() << "Changeqt: changes which Qt frameworks an application links against.";
|
|
||||||
qDebug() << "Usage: changeqt app-bundle qt-dir <-verbose=[0-3]>";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString appPath = QString::fromLocal8Bit(argv[1]);
|
|
||||||
const QString qtPath = QString::fromLocal8Bit(argv[2]);
|
|
||||||
changeQtFrameworks(appPath, qtPath, useDebugLibs);
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
TEMPLATE = subdirs
|
TEMPLATE = subdirs
|
||||||
SUBDIRS = macdeployqt macchangeqt
|
SUBDIRS = linuxdeployqt
|
||||||
|
|||||||
@@ -1,4 +1 @@
|
|||||||
SOURCES += main.cpp ../shared/shared.cpp
|
SOURCES += main.cpp ../shared/shared.cpp
|
||||||
LIBS += -framework CoreFoundation
|
|
||||||
|
|
||||||
load(qt_app)
|
|
||||||
|
|||||||
+40
-55
@@ -34,53 +34,67 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
QString appBundlePath;
|
QString appBinaryPath;
|
||||||
if (argc > 1)
|
|
||||||
appBundlePath = QString::fromLocal8Bit(argv[1]);
|
|
||||||
|
|
||||||
if (argc < 2 || appBundlePath.startsWith("-")) {
|
if (argc > 1)
|
||||||
qDebug() << "Usage: macdeployqt app-bundle [options]";
|
appBinaryPath = QString::fromLocal8Bit(argv[1]);
|
||||||
|
appBinaryPath = QDir::cleanPath(appBinaryPath);
|
||||||
|
|
||||||
|
if (argc < 2 || appBinaryPath.startsWith("-")) {
|
||||||
|
qDebug() << "Usage: linuxdeployqt app-binary [options]";
|
||||||
qDebug() << "";
|
qDebug() << "";
|
||||||
qDebug() << "Options:";
|
qDebug() << "Options:";
|
||||||
qDebug() << " -verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug";
|
qDebug() << " -verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug";
|
||||||
qDebug() << " -no-plugins : Skip plugin deployment";
|
qDebug() << " -no-plugins : Skip plugin deployment";
|
||||||
qDebug() << " -dmg : Create a .dmg disk image";
|
qDebug() << " -appimage : Create an AppImage";
|
||||||
qDebug() << " -no-strip : Don't run 'strip' on the binaries";
|
qDebug() << " -no-strip : Don't run 'strip' on the binaries";
|
||||||
qDebug() << " -use-debug-libs : Deploy with debug versions of frameworks and plugins (implies -no-strip)";
|
qDebug() << " -use-debug-libs : Deploy with debug versions of frameworks and plugins (implies -no-strip)";
|
||||||
qDebug() << " -executable=<path> : Let the given executable use the deployed frameworks too";
|
qDebug() << " -executable=<path> : Let the given executable use the deployed frameworks too";
|
||||||
qDebug() << " -qmldir=<path> : Scan for QML imports in the given path";
|
qDebug() << " -qmldir=<path> : Scan for QML imports in the given path";
|
||||||
qDebug() << " -always-overwrite : Copy files even if the target file exists";
|
qDebug() << " -always-overwrite : Copy files even if the target file exists";
|
||||||
qDebug() << " -codesign=<ident> : Run codesign with the given identity on all executables";
|
|
||||||
qDebug() << " -appstore-compliant: Skip deployment of components that use private API";
|
|
||||||
qDebug() << " -libpath=<path> : Add the given path to the library search path";
|
qDebug() << " -libpath=<path> : Add the given path to the library search path";
|
||||||
qDebug() << "";
|
qDebug() << "";
|
||||||
qDebug() << "macdeployqt takes an application bundle as input and makes it";
|
qDebug() << "linuxdeployqt takes an application as input and makes it";
|
||||||
qDebug() << "self-contained by copying in the Qt frameworks and plugins that";
|
qDebug() << "self-contained by copying in the Qt libraries and plugins that";
|
||||||
qDebug() << "the application uses.";
|
qDebug() << "the application uses.";
|
||||||
qDebug() << "";
|
qDebug() << "";
|
||||||
qDebug() << "Plugins related to a framework are copied in with the";
|
qDebug() << "Plugins related to a Qt library are copied in with the";
|
||||||
qDebug() << "framework. The accessibility, image formats, and text codec";
|
qDebug() << "library. The accessibility, image formats, and text codec";
|
||||||
qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified.";
|
qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified.";
|
||||||
qDebug() << "";
|
qDebug() << "";
|
||||||
qDebug() << "Qt plugins may use private API and will cause the app to be";
|
qDebug() << "See the \"Deploying Applications on Linux\" topic in the";
|
||||||
qDebug() << "rejected from the Mac App store. MacDeployQt will print a warning";
|
qDebug() << "documentation for more information about deployment on Linux.";
|
||||||
qDebug() << "when known incompatible plugins are deployed. Use -appstore-compliant ";
|
|
||||||
qDebug() << "to skip these plugins. Currently two SQL plugins are known to";
|
|
||||||
qDebug() << "be incompatible: qsqlodbc and qsqlpsql.";
|
|
||||||
qDebug() << "";
|
|
||||||
qDebug() << "See the \"Deploying Applications on OS X\" topic in the";
|
|
||||||
qDebug() << "documentation for more information about deployment on OS X.";
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
appBundlePath = QDir::cleanPath(appBundlePath);
|
QString appName = QDir::cleanPath(QFileInfo(appBinaryPath).baseName());
|
||||||
|
|
||||||
if (QDir().exists(appBundlePath) == false) {
|
if (QDir().exists(appBinaryPath)) {
|
||||||
qDebug() << "Error: Could not find app bundle" << appBundlePath;
|
qDebug() << "app-binary:" << appBinaryPath;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Error: Could not find app-binary" << appBinaryPath;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDir dir;
|
||||||
|
// QString appDir = QDir::cleanPath(appFile + "/../" + appName + ".AppDir");
|
||||||
|
QString appDir = QDir::cleanPath(appBinaryPath + "/../");
|
||||||
|
|
||||||
|
if (QDir().exists(appDir) == false) {
|
||||||
|
qDebug() << "Error: Could not find AppDir" << appDir;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString appBundlePath = appDir;
|
||||||
|
|
||||||
|
QFile appRun(appDir + "/AppRun");
|
||||||
|
|
||||||
|
if(appRun.exists()){
|
||||||
|
appRun.remove();
|
||||||
|
}
|
||||||
|
QFile::link(appBinaryPath, appDir + "/AppRun");
|
||||||
|
|
||||||
bool plugins = true;
|
bool plugins = true;
|
||||||
bool dmg = false;
|
bool dmg = false;
|
||||||
bool useDebugLibs = false;
|
bool useDebugLibs = false;
|
||||||
@@ -90,14 +104,10 @@ int main(int argc, char **argv)
|
|||||||
QStringList additionalExecutables;
|
QStringList additionalExecutables;
|
||||||
bool qmldirArgumentUsed = false;
|
bool qmldirArgumentUsed = false;
|
||||||
QStringList qmlDirs;
|
QStringList qmlDirs;
|
||||||
extern bool runCodesign;
|
|
||||||
extern QString codesignIdentiy;
|
|
||||||
extern bool appstoreCompliant;
|
|
||||||
extern bool deployFramework;
|
|
||||||
|
|
||||||
for (int i = 2; i < argc; ++i) {
|
for (int i = 2; i < argc; ++i) {
|
||||||
QByteArray argument = QByteArray(argv[i]);
|
QByteArray argument = QByteArray(argv[i]);
|
||||||
if (argument == QByteArray("-no-plugins")) {
|
if (argument == QByteArray("-no-pluginss")) {
|
||||||
LogDebug() << "Argument found:" << argument;
|
LogDebug() << "Argument found:" << argument;
|
||||||
plugins = false;
|
plugins = false;
|
||||||
} else if (argument == QByteArray("-dmg")) {
|
} else if (argument == QByteArray("-dmg")) {
|
||||||
@@ -144,24 +154,6 @@ int main(int argc, char **argv)
|
|||||||
} else if (argument == QByteArray("-always-overwrite")) {
|
} else if (argument == QByteArray("-always-overwrite")) {
|
||||||
LogDebug() << "Argument found:" << argument;
|
LogDebug() << "Argument found:" << argument;
|
||||||
alwaysOwerwriteEnabled = true;
|
alwaysOwerwriteEnabled = true;
|
||||||
} else if (argument.startsWith(QByteArray("-codesign"))) {
|
|
||||||
LogDebug() << "Argument found:" << argument;
|
|
||||||
int index = argument.indexOf("=");
|
|
||||||
if (index < 0 || index >= argument.size()) {
|
|
||||||
LogError() << "Missing code signing identity";
|
|
||||||
} else {
|
|
||||||
runCodesign = true;
|
|
||||||
codesignIdentiy = argument.mid(index+1);
|
|
||||||
}
|
|
||||||
} else if (argument == QByteArray("-appstore-compliant")) {
|
|
||||||
LogDebug() << "Argument found:" << argument;
|
|
||||||
appstoreCompliant = true;
|
|
||||||
|
|
||||||
// Undocumented option, may not work as intented
|
|
||||||
} else if (argument == QByteArray("-deploy-framework")) {
|
|
||||||
LogDebug() << "Argument found:" << argument;
|
|
||||||
deployFramework = true;
|
|
||||||
|
|
||||||
} else if (argument.startsWith("-")) {
|
} else if (argument.startsWith("-")) {
|
||||||
LogError() << "Unknown argument" << argument << "\n";
|
LogError() << "Unknown argument" << argument << "\n";
|
||||||
return 1;
|
return 1;
|
||||||
@@ -170,9 +162,6 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
DeploymentInfo deploymentInfo = deployQtFrameworks(appBundlePath, additionalExecutables, useDebugLibs);
|
DeploymentInfo deploymentInfo = deployQtFrameworks(appBundlePath, additionalExecutables, useDebugLibs);
|
||||||
|
|
||||||
if (deployFramework && deploymentInfo.isFramework)
|
|
||||||
fixupFramework(appBundlePath);
|
|
||||||
|
|
||||||
// Convenience: Look for .qml files in the current directoty if no -qmldir specified.
|
// Convenience: Look for .qml files in the current directoty if no -qmldir specified.
|
||||||
if (qmlDirs.isEmpty()) {
|
if (qmlDirs.isEmpty()) {
|
||||||
QDir dir;
|
QDir dir;
|
||||||
@@ -193,8 +182,7 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (plugins && !deploymentInfo.qtPath.isEmpty()) {
|
if (plugins && !deploymentInfo.qtPath.isEmpty()) {
|
||||||
deploymentInfo.pluginPath = deploymentInfo.qtPath + "/plugins";
|
deploymentInfo.pluginPath = QDir::cleanPath(deploymentInfo.qtPath + "/../plugins");
|
||||||
LogNormal();
|
|
||||||
deployPlugins(appBundlePath, deploymentInfo, useDebugLibs);
|
deployPlugins(appBundlePath, deploymentInfo, useDebugLibs);
|
||||||
createQtConf(appBundlePath);
|
createQtConf(appBundlePath);
|
||||||
}
|
}
|
||||||
@@ -202,12 +190,9 @@ int main(int argc, char **argv)
|
|||||||
if (runStripEnabled)
|
if (runStripEnabled)
|
||||||
stripAppBinary(appBundlePath);
|
stripAppBinary(appBundlePath);
|
||||||
|
|
||||||
if (runCodesign)
|
|
||||||
codesign(codesignIdentiy, appBundlePath);
|
|
||||||
|
|
||||||
if (dmg) {
|
if (dmg) {
|
||||||
LogNormal();
|
LogNormal();
|
||||||
createDiskImage(appBundlePath);
|
createAppImage(appBundlePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+107
-527
@@ -44,15 +44,10 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include "shared.h"
|
#include "shared.h"
|
||||||
|
|
||||||
#ifdef Q_OS_DARWIN
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool runStripEnabled = true;
|
bool runStripEnabled = true;
|
||||||
bool alwaysOwerwriteEnabled = false;
|
bool alwaysOwerwriteEnabled = false;
|
||||||
bool runCodesign = false;
|
|
||||||
QStringList librarySearchPath;
|
QStringList librarySearchPath;
|
||||||
QString codesignIdentiy;
|
|
||||||
bool appstoreCompliant = false;
|
bool appstoreCompliant = false;
|
||||||
int logLevel = 1;
|
int logLevel = 1;
|
||||||
bool deployFramework = false;
|
bool deployFramework = false;
|
||||||
@@ -83,7 +78,7 @@ QDebug operator<<(QDebug debug, const FrameworkInfo &info)
|
|||||||
return debug;
|
return debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString bundleFrameworkDirectory = "Contents/Frameworks";
|
const QString bundleFrameworkDirectory = "lib"; // the same directory as the main executable; could define a relative subdirectory here
|
||||||
|
|
||||||
inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
|
inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
|
||||||
{
|
{
|
||||||
@@ -167,37 +162,45 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
|
|||||||
OtoolInfo info;
|
OtoolInfo info;
|
||||||
info.binaryPath = binaryPath;
|
info.binaryPath = binaryPath;
|
||||||
|
|
||||||
LogDebug() << "Using otool:";
|
LogDebug() << "Using ldd:";
|
||||||
LogDebug() << " inspecting" << binaryPath;
|
LogDebug() << " inspecting" << binaryPath;
|
||||||
QProcess otool;
|
QProcess otool;
|
||||||
otool.start("otool", QStringList() << "-L" << binaryPath);
|
otool.start("ldd", QStringList() << binaryPath);
|
||||||
otool.waitForFinished();
|
otool.waitForFinished();
|
||||||
|
|
||||||
if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) {
|
if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) {
|
||||||
LogError() << otool.readAllStandardError();
|
LogError() << "findDependencyInfo:" << otool.readAllStandardError();
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const QRegularExpression regexp(QStringLiteral(
|
||||||
|
"^.+ => (.+) \\("));
|
||||||
|
|
||||||
|
/*
|
||||||
static const QRegularExpression regexp(QStringLiteral(
|
static const QRegularExpression regexp(QStringLiteral(
|
||||||
"^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "
|
"^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "
|
||||||
"current version (\\d+\\.\\d+\\.\\d+)\\)$"));
|
"current version (\\d+\\.\\d+\\.\\d+)\\)$"));
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
QString output = otool.readAllStandardOutput();
|
QString output = otool.readAllStandardOutput();
|
||||||
QStringList outputLines = output.split("\n", QString::SkipEmptyParts);
|
QStringList outputLines = output.split("\n", QString::SkipEmptyParts);
|
||||||
if (outputLines.size() < 2) {
|
if (outputLines.size() < 2) {
|
||||||
LogError() << "Could not parse otool output:" << output;
|
if (output.contains("statically linked") == false){
|
||||||
|
LogError() << "Could not parse objdump output:" << output;
|
||||||
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
outputLines.removeFirst(); // remove line containing the binary path
|
outputLines.removeFirst(); // remove line containing the binary path
|
||||||
if (binaryPath.contains(".framework/") || binaryPath.endsWith(".dylib")) {
|
if (binaryPath.contains(".so.") || binaryPath.endsWith(".so")) {
|
||||||
const auto match = regexp.match(outputLines.first());
|
const auto match = regexp.match(outputLines.first());
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
info.installName = match.captured(1);
|
info.installName = match.captured(1);
|
||||||
info.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
|
info.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
|
||||||
info.currentVersion = QVersionNumber::fromString(match.captured(3));
|
info.currentVersion = QVersionNumber::fromString(match.captured(3));
|
||||||
} else {
|
} else {
|
||||||
LogError() << "Could not parse otool output line:" << outputLines.first();
|
LogError() << "Could not parse objdump output line:" << outputLines.first();
|
||||||
}
|
}
|
||||||
outputLines.removeFirst();
|
outputLines.removeFirst();
|
||||||
}
|
}
|
||||||
@@ -206,12 +209,13 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
|
|||||||
const auto match = regexp.match(outputLine);
|
const auto match = regexp.match(outputLine);
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
DylibInfo dylib;
|
DylibInfo dylib;
|
||||||
dylib.binaryPath = match.captured(1);
|
dylib.binaryPath = match.captured(1).trimmed();
|
||||||
dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
|
LogDebug() << " dylib.binaryPath" << dylib.binaryPath;
|
||||||
dylib.currentVersion = QVersionNumber::fromString(match.captured(3));
|
/*
|
||||||
|
dylib.compatibilityVersion = 0;
|
||||||
|
dylib.currentVersion = 0;
|
||||||
|
*/
|
||||||
info.dependencies << dylib;
|
info.dependencies << dylib;
|
||||||
} else {
|
|
||||||
LogError() << "Could not parse otool output line:" << outputLine;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,48 +227,18 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
FrameworkInfo info;
|
FrameworkInfo info;
|
||||||
QString trimmed = line.trimmed();
|
QString trimmed = line.trimmed();
|
||||||
|
|
||||||
|
LogDebug() << "parsing" << trimmed;
|
||||||
|
|
||||||
if (trimmed.isEmpty())
|
if (trimmed.isEmpty())
|
||||||
return info;
|
return info;
|
||||||
|
|
||||||
// Don't deploy system libraries.
|
// Don't deploy low-level libraries in /lib because these tend to break if moved to a system with a different glibc.
|
||||||
if (trimmed.startsWith("/System/Library/") ||
|
// TODO: Could make bundling these low-level libraries optional but then the bundles might need to
|
||||||
(trimmed.startsWith("/usr/lib/") && trimmed.contains("libQt") == false) // exception for libQtuitools and libQtlucene
|
// use something like patchelf --set-interpreter or http://bitwagon.com/rtldi/rtldi.html
|
||||||
|| trimmed.startsWith("@executable_path") || trimmed.startsWith("@loader_path"))
|
|
||||||
|
if (trimmed.startsWith("/lib"))
|
||||||
return info;
|
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};
|
enum State {QtPath, FrameworkName, DylibName, Version, End};
|
||||||
State state = QtPath;
|
State state = QtPath;
|
||||||
@@ -283,8 +257,9 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
|
|
||||||
if (state == QtPath) {
|
if (state == QtPath) {
|
||||||
// Check for library name part
|
// Check for library name part
|
||||||
if (part < parts.count() && parts.at(part).contains(".dylib")) {
|
if (part < parts.count() && parts.at(part).contains(".so")) {
|
||||||
info.frameworkDirectory += "/" + (qtPath + currentPart + "/").simplified();
|
info.frameworkDirectory += "/" + (qtPath + currentPart + "/").simplified();
|
||||||
|
LogDebug() << "info.frameworkDirectory:" << info.frameworkDirectory;
|
||||||
state = DylibName;
|
state = DylibName;
|
||||||
continue;
|
continue;
|
||||||
} else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
|
} else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
|
||||||
@@ -303,6 +278,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
if (currentPart.contains(".framework")) {
|
if (currentPart.contains(".framework")) {
|
||||||
if (info.frameworkDirectory.isEmpty())
|
if (info.frameworkDirectory.isEmpty())
|
||||||
info.frameworkDirectory = "/Library/Frameworks/" + partsCopy.join("/");
|
info.frameworkDirectory = "/Library/Frameworks/" + partsCopy.join("/");
|
||||||
@@ -311,7 +287,9 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
state = FrameworkName;
|
state = FrameworkName;
|
||||||
--part;
|
--part;
|
||||||
continue;
|
continue;
|
||||||
} else if (currentPart.contains(".dylib")) {
|
|
||||||
|
} else if (currentPart.contains(".so")) {
|
||||||
|
*/
|
||||||
if (info.frameworkDirectory.isEmpty())
|
if (info.frameworkDirectory.isEmpty())
|
||||||
info.frameworkDirectory = "/usr/lib/" + partsCopy.join("/");
|
info.frameworkDirectory = "/usr/lib/" + partsCopy.join("/");
|
||||||
if (!info.frameworkDirectory.endsWith("/"))
|
if (!info.frameworkDirectory.endsWith("/"))
|
||||||
@@ -319,7 +297,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
state = DylibName;
|
state = DylibName;
|
||||||
--part;
|
--part;
|
||||||
continue;
|
continue;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
qtPath += (currentPart + "/");
|
qtPath += (currentPart + "/");
|
||||||
|
|
||||||
@@ -337,7 +315,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
info.isDylib = true;
|
info.isDylib = true;
|
||||||
info.frameworkName = name;
|
info.frameworkName = name;
|
||||||
info.binaryName = name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.'));
|
info.binaryName = name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.'));
|
||||||
info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName;
|
info.deployedInstallName = "$ORIGIN"; // + info.binaryName;
|
||||||
info.frameworkPath = info.frameworkDirectory + info.binaryName;
|
info.frameworkPath = info.frameworkDirectory + info.binaryName;
|
||||||
info.sourceFilePath = info.frameworkPath;
|
info.sourceFilePath = info.frameworkPath;
|
||||||
info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/";
|
info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/";
|
||||||
@@ -352,7 +330,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
info.binaryDirectory = "Versions/" + info.version;
|
info.binaryDirectory = "Versions/" + info.version;
|
||||||
info.binaryName = name + suffix;
|
info.binaryName = name + suffix;
|
||||||
info.binaryPath = "/" + info.binaryDirectory + "/" + info.binaryName;
|
info.binaryPath = "/" + info.binaryDirectory + "/" + info.binaryName;
|
||||||
info.deployedInstallName = "@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath;
|
info.deployedInstallName = "$ORIGIN"; // + info.frameworkName + info.binaryPath;
|
||||||
info.frameworkPath = info.frameworkDirectory + info.frameworkName;
|
info.frameworkPath = info.frameworkDirectory + info.frameworkName;
|
||||||
info.sourceFilePath = info.frameworkPath + info.binaryPath;
|
info.sourceFilePath = info.frameworkPath + info.binaryPath;
|
||||||
info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName;
|
info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName;
|
||||||
@@ -376,35 +354,16 @@ QString findAppBinary(const QString &appBundlePath)
|
|||||||
{
|
{
|
||||||
QString binaryPath;
|
QString binaryPath;
|
||||||
|
|
||||||
#ifdef Q_OS_DARWIN
|
// FIXME: Do without the need for an AppRun symlink
|
||||||
CFStringRef bundlePath = appBundlePath.toCFString();
|
// by passing appBinaryPath from main.cpp here
|
||||||
CFURLRef bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath,
|
QString parentDir = QDir::cleanPath(QFileInfo(appBundlePath).path());
|
||||||
kCFURLPOSIXPathStyle, true);
|
QString appDir = QDir::cleanPath(QFileInfo(appBundlePath).baseName());
|
||||||
CFRelease(bundlePath);
|
binaryPath = parentDir + "/" + appDir + "/AppRun";
|
||||||
CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, bundleURL);
|
|
||||||
if (bundle) {
|
|
||||||
CFURLRef executableURL = CFBundleCopyExecutableURL(bundle);
|
|
||||||
if (executableURL) {
|
|
||||||
CFURLRef absoluteExecutableURL = CFURLCopyAbsoluteURL(executableURL);
|
|
||||||
if (absoluteExecutableURL) {
|
|
||||||
CFStringRef executablePath = CFURLCopyFileSystemPath(absoluteExecutableURL,
|
|
||||||
kCFURLPOSIXPathStyle);
|
|
||||||
if (executablePath) {
|
|
||||||
binaryPath = QString::fromCFString(executablePath);
|
|
||||||
CFRelease(executablePath);
|
|
||||||
}
|
|
||||||
CFRelease(absoluteExecutableURL);
|
|
||||||
}
|
|
||||||
CFRelease(executableURL);
|
|
||||||
}
|
|
||||||
CFRelease(bundle);
|
|
||||||
}
|
|
||||||
CFRelease(bundleURL);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (QFile::exists(binaryPath))
|
if (QFile::exists(binaryPath))
|
||||||
return binaryPath;
|
return binaryPath;
|
||||||
LogError() << "Could not find bundle binary for" << appBundlePath;
|
LogError() << "Could not find bundle binary for" << appBundlePath;
|
||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +373,7 @@ QStringList findAppFrameworkNames(const QString &appBundlePath)
|
|||||||
|
|
||||||
// populate the frameworks list with QtFoo.framework etc,
|
// populate the frameworks list with QtFoo.framework etc,
|
||||||
// as found in /Contents/Frameworks/
|
// as found in /Contents/Frameworks/
|
||||||
QString searchPath = appBundlePath + "/Contents/Frameworks/";
|
QString searchPath = appBundlePath + bundleFrameworkDirectory;
|
||||||
QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"), QDir::Dirs);
|
QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"), QDir::Dirs);
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
iter.next();
|
iter.next();
|
||||||
@@ -424,30 +383,27 @@ QStringList findAppFrameworkNames(const QString &appBundlePath)
|
|||||||
return frameworks;
|
return frameworks;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList findAppFrameworkPaths(const QString &appBundlePath)
|
|
||||||
{
|
|
||||||
QStringList frameworks;
|
|
||||||
QString searchPath = appBundlePath + "/Contents/Frameworks/";
|
|
||||||
QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"), QDir::Dirs);
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
iter.next();
|
|
||||||
frameworks << iter.fileInfo().filePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
return frameworks;
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList findAppLibraries(const QString &appBundlePath)
|
QStringList findAppLibraries(const QString &appBundlePath)
|
||||||
{
|
{
|
||||||
QStringList result;
|
QStringList result;
|
||||||
// dylibs
|
// .so
|
||||||
QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*.dylib"),
|
QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*.so"),
|
||||||
QDir::Files, QDirIterator::Subdirectories);
|
QDir::Files, QDirIterator::Subdirectories);
|
||||||
|
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
iter.next();
|
iter.next();
|
||||||
result << iter.fileInfo().filePath();
|
result << iter.fileInfo().filePath();
|
||||||
}
|
}
|
||||||
|
// .so.*
|
||||||
|
QDirIterator iter2(appBundlePath, QStringList() << QString::fromLatin1("*.so.*"),
|
||||||
|
QDir::Files, QDirIterator::Subdirectories);
|
||||||
|
|
||||||
|
while (iter2.hasNext()) {
|
||||||
|
iter2.next();
|
||||||
|
result << iter2.fileInfo().filePath();
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,42 +438,17 @@ QList<FrameworkInfo> getQtFrameworks(const QList<DylibInfo> &dependencies, const
|
|||||||
return libraries;
|
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> getBinaryRPaths(const QString &path, bool resolve = true, QString executablePath = QString())
|
||||||
{
|
{
|
||||||
QSet<QString> rpaths;
|
QSet<QString> rpaths;
|
||||||
|
|
||||||
QProcess otool;
|
QProcess otool;
|
||||||
otool.start("otool", QStringList() << "-l" << path);
|
otool.start("patchelf", QStringList() << "--print-rpath" << path);
|
||||||
otool.waitForFinished();
|
otool.waitForFinished();
|
||||||
|
|
||||||
if (otool.exitCode() != 0) {
|
if (otool.exitCode() != 0) {
|
||||||
LogError() << otool.readAllStandardError();
|
LogError() << "getBinaryRPaths:" << otool.readAllStandardError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolve && executablePath.isEmpty()) {
|
if (resolve && executablePath.isEmpty()) {
|
||||||
@@ -525,25 +456,16 @@ QSet<QString> getBinaryRPaths(const QString &path, bool resolve = true, QString
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString output = otool.readAllStandardOutput();
|
QString output = otool.readAllStandardOutput();
|
||||||
|
LogDebug() << "output:" << output;
|
||||||
QStringList outputLines = output.split("\n");
|
QStringList outputLines = output.split("\n");
|
||||||
QStringListIterator i(outputLines);
|
QStringListIterator i(outputLines);
|
||||||
|
|
||||||
while (i.hasNext()) {
|
while (i.hasNext()) {
|
||||||
if (i.next().contains("cmd LC_RPATH") && i.hasNext() &&
|
|
||||||
i.next().contains("cmdsize") && i.hasNext()) {
|
|
||||||
const QString &rpathCmd = i.next();
|
const QString &rpathCmd = i.next();
|
||||||
int pathStart = rpathCmd.indexOf("path ");
|
QString rpath = rpathCmd.trimmed();
|
||||||
int pathEnd = rpathCmd.indexOf(" (");
|
LogDebug() << "rpath:" << rpath;
|
||||||
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;
|
rpaths << rpath;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rpaths;
|
return rpaths;
|
||||||
}
|
}
|
||||||
@@ -558,6 +480,7 @@ QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, const QSt
|
|||||||
{
|
{
|
||||||
QList<FrameworkInfo> result;
|
QList<FrameworkInfo> result;
|
||||||
QSet<QString> existing;
|
QSet<QString> existing;
|
||||||
|
|
||||||
foreach (const QString &path, paths) {
|
foreach (const QString &path, paths) {
|
||||||
foreach (const FrameworkInfo &info, getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) {
|
foreach (const FrameworkInfo &info, getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) {
|
||||||
if (!existing.contains(info.frameworkPath)) { // avoid duplicates
|
if (!existing.contains(info.frameworkPath)) { // avoid duplicates
|
||||||
@@ -651,40 +574,6 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QSet<QString> &r
|
|||||||
|
|
||||||
if (file.endsWith("_debug.dylib")) {
|
if (file.endsWith("_debug.dylib")) {
|
||||||
continue; // Skip debug versions
|
continue; // Skip debug versions
|
||||||
} else if (file.endsWith(QStringLiteral(".dylib"))) {
|
|
||||||
// App store code signing rules forbids code binaries in Contents/Resources/,
|
|
||||||
// which poses a problem for deploying mixed .qml/.dylib Qt Quick imports.
|
|
||||||
// Solve this by placing the dylibs in Contents/PlugIns/quick, and then
|
|
||||||
// creting a symlink to there from the Qt Quick import in Contents/Resources/.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
// MyApp.app/Contents/Resources/qml/QtQuick/Controls/libqtquickcontrolsplugin.dylib ->
|
|
||||||
// ../../../../PlugIns/quick/libqtquickcontrolsplugin.dylib
|
|
||||||
//
|
|
||||||
|
|
||||||
// The .dylib destination path:
|
|
||||||
QString fileDestinationDir = appBundlePath + QStringLiteral("/Contents/PlugIns/quick/");
|
|
||||||
QDir().mkpath(fileDestinationDir);
|
|
||||||
QString fileDestinationPath = fileDestinationDir + file;
|
|
||||||
|
|
||||||
// The .dylib symlink destination path:
|
|
||||||
QString linkDestinationPath = destinationPath + QLatin1Char('/') + file;
|
|
||||||
|
|
||||||
// The (relative) link; with a correct number of "../"'s.
|
|
||||||
QString linkPath = QStringLiteral("PlugIns/quick/") + file;
|
|
||||||
int cdupCount = linkDestinationPath.count(QStringLiteral("/")) - appBundlePath.count(QStringLiteral("/"));
|
|
||||||
for (int i = 0; i < cdupCount - 2; ++i)
|
|
||||||
linkPath.prepend("../");
|
|
||||||
|
|
||||||
if (copyFilePrintStatus(fileSourcePath, fileDestinationPath)) {
|
|
||||||
linkFilePrintStatus(linkPath, linkDestinationPath);
|
|
||||||
|
|
||||||
runStrip(fileDestinationPath);
|
|
||||||
bool useDebugLibs = false;
|
|
||||||
bool useLoaderPath = false;
|
|
||||||
QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, appBundlePath, rpaths, useDebugLibs);
|
|
||||||
deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
QString fileDestinationPath = destinationPath + QLatin1Char('/') + file;
|
QString fileDestinationPath = destinationPath + QLatin1Char('/') + file;
|
||||||
copyFilePrintStatus(fileSourcePath, fileDestinationPath);
|
copyFilePrintStatus(fileSourcePath, fileDestinationPath);
|
||||||
@@ -724,159 +613,29 @@ QString copyDylib(const FrameworkInfo &framework, const QString path)
|
|||||||
return dylibDestinationBinaryPath;
|
return dylibDestinationBinaryPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString copyFramework(const FrameworkInfo &framework, const QString path)
|
void runPatchelf(QStringList options)
|
||||||
{
|
{
|
||||||
if (!QFile::exists(framework.sourceFilePath)) {
|
QProcess patchelftool;
|
||||||
LogError() << "no file at" << framework.sourceFilePath;
|
patchelftool.start("patchelf", options);
|
||||||
return QString();
|
patchelftool.waitForFinished();
|
||||||
}
|
if (patchelftool.exitCode() != 0) {
|
||||||
|
LogError() << "FIXME: Check whether patchelf is on the $PATH and otherwise inform the user where to get it from";
|
||||||
// Construct destination paths. The full path typically looks like
|
LogError() << "runPatchelf:" << patchelftool.readAllStandardError();
|
||||||
// MyApp.app/Contents/Frameworks/Foo.framework/Versions/5/QtFoo
|
LogError() << "runPatchelf:" << patchelftool.readAllStandardOutput();
|
||||||
QString frameworkDestinationDirectory = path + QLatin1Char('/') + framework.frameworkDestinationDirectory;
|
|
||||||
QString frameworkBinaryDestinationDirectory = frameworkDestinationDirectory + QLatin1Char('/') + framework.binaryDirectory;
|
|
||||||
QString frameworkDestinationBinaryPath = frameworkBinaryDestinationDirectory + QLatin1Char('/') + framework.binaryName;
|
|
||||||
|
|
||||||
// Return if the framework has aleardy been deployed
|
|
||||||
if (QDir(frameworkDestinationDirectory).exists() && !alwaysOwerwriteEnabled)
|
|
||||||
return QString();
|
|
||||||
|
|
||||||
// Create destination directory
|
|
||||||
if (!QDir().mkpath(frameworkBinaryDestinationDirectory)) {
|
|
||||||
LogError() << "could not create destination directory" << frameworkBinaryDestinationDirectory;
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now copy the framework. Some parts should be left out (headers/, .prl files).
|
|
||||||
// Some parts should be included (Resources/, symlink structure). We want this
|
|
||||||
// function to make as few assumtions about the framework as possible while at
|
|
||||||
// the same time producing a codesign-compatible framework.
|
|
||||||
|
|
||||||
// Copy framework binary
|
|
||||||
copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath);
|
|
||||||
|
|
||||||
// Copy Resouces/, Libraries/ and Helpers/
|
|
||||||
const QString resourcesSourcePath = framework.frameworkPath + "/Resources";
|
|
||||||
const QString resourcesDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Resources";
|
|
||||||
recursiveCopy(resourcesSourcePath, resourcesDestianationPath);
|
|
||||||
const QString librariesSourcePath = framework.frameworkPath + "/Libraries";
|
|
||||||
const QString librariesDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Libraries";
|
|
||||||
bool createdLibraries = recursiveCopy(librariesSourcePath, librariesDestianationPath);
|
|
||||||
const QString helpersSourcePath = framework.frameworkPath + "/Helpers";
|
|
||||||
const QString helpersDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Helpers";
|
|
||||||
bool createdHelpers = recursiveCopy(helpersSourcePath, helpersDestianationPath);
|
|
||||||
|
|
||||||
// Create symlink structure. Links at the framework root point to Versions/Current/
|
|
||||||
// which again points to the actual version:
|
|
||||||
// QtFoo.framework/QtFoo -> Versions/Current/QtFoo
|
|
||||||
// QtFoo.framework/Resources -> Versions/Current/Resources
|
|
||||||
// QtFoo.framework/Versions/Current -> 5
|
|
||||||
linkFilePrintStatus("Versions/Current/" + framework.binaryName, frameworkDestinationDirectory + "/" + framework.binaryName);
|
|
||||||
linkFilePrintStatus("Versions/Current/Resources", frameworkDestinationDirectory + "/Resources");
|
|
||||||
if (createdLibraries)
|
|
||||||
linkFilePrintStatus("Versions/Current/Libraries", frameworkDestinationDirectory + "/Libraries");
|
|
||||||
if (createdHelpers)
|
|
||||||
linkFilePrintStatus("Versions/Current/Helpers", frameworkDestinationDirectory + "/Helpers");
|
|
||||||
linkFilePrintStatus(framework.version, frameworkDestinationDirectory + "/Versions/Current");
|
|
||||||
|
|
||||||
// Correct Info.plist location for frameworks produced by older versions of qmake
|
|
||||||
// Contents/Info.plist should be Versions/5/Resources/Info.plist
|
|
||||||
const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist";
|
|
||||||
const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist";
|
|
||||||
if (QFile(legacyInfoPlistPath).exists()) {
|
|
||||||
copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
|
|
||||||
patch_debugInInfoPlist(correctInfoPlistPath);
|
|
||||||
}
|
|
||||||
return frameworkDestinationBinaryPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
void runInstallNameTool(QStringList options)
|
|
||||||
{
|
|
||||||
QProcess installNametool;
|
|
||||||
installNametool.start("install_name_tool", options);
|
|
||||||
installNametool.waitForFinished();
|
|
||||||
if (installNametool.exitCode() != 0) {
|
|
||||||
LogError() << installNametool.readAllStandardError();
|
|
||||||
LogError() << installNametool.readAllStandardOutput();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeIdentification(const QString &id, const QString &binaryPath)
|
void changeIdentification(const QString &id, const QString &binaryPath)
|
||||||
{
|
{
|
||||||
LogDebug() << "Using install_name_tool:";
|
LogDebug() << "Using patchelf:";
|
||||||
LogDebug() << " change identification in" << binaryPath;
|
LogDebug() << " change identification in" << binaryPath;
|
||||||
LogDebug() << " to" << id;
|
LogDebug() << " to" << id;
|
||||||
runInstallNameTool(QStringList() << "-id" << id << binaryPath);
|
runPatchelf(QStringList() << "--set-rpath" << id << binaryPath);
|
||||||
}
|
|
||||||
|
|
||||||
void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework, const QStringList &binaryPaths, bool useLoaderPath)
|
|
||||||
{
|
|
||||||
const QString absBundlePath = QFileInfo(bundlePath).absoluteFilePath();
|
|
||||||
foreach (const QString &binary, binaryPaths) {
|
|
||||||
QString deployedInstallName;
|
|
||||||
if (useLoaderPath) {
|
|
||||||
deployedInstallName = QLatin1String("@loader_path/")
|
|
||||||
+ QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + QLatin1Char('/') + framework.binaryDestinationDirectory + QLatin1Char('/') + framework.binaryName);
|
|
||||||
} else {
|
|
||||||
deployedInstallName = framework.deployedInstallName;
|
|
||||||
}
|
|
||||||
changeInstallName(framework.installName, deployedInstallName, binary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void addRPath(const QString &rpath, const QString &binaryPath)
|
void addRPath(const QString &rpath, const QString &binaryPath)
|
||||||
{
|
{
|
||||||
runInstallNameTool(QStringList() << "-add_rpath" << rpath << binaryPath);
|
runPatchelf(QStringList() << "-add_rpath" << rpath << binaryPath);
|
||||||
}
|
|
||||||
|
|
||||||
void deployRPaths(const QString &bundlePath, const QSet<QString> &rpaths, const QString &binaryPath, bool useLoaderPath)
|
|
||||||
{
|
|
||||||
const QString absFrameworksPath = QFileInfo(bundlePath).absoluteFilePath()
|
|
||||||
+ QLatin1String("/Contents/Frameworks");
|
|
||||||
const QString relativeFrameworkPath = QFileInfo(binaryPath).absoluteDir().relativeFilePath(absFrameworksPath);
|
|
||||||
const QString loaderPathToFrameworks = QLatin1String("@loader_path/") + relativeFrameworkPath;
|
|
||||||
bool rpathToFrameworksFound = false;
|
|
||||||
QStringList args;
|
|
||||||
foreach (const QString &rpath, getBinaryRPaths(binaryPath, false)) {
|
|
||||||
if (rpath == "@executable_path/../Frameworks" ||
|
|
||||||
rpath == loaderPathToFrameworks) {
|
|
||||||
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" << loaderPathToFrameworks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LogDebug() << "Using install_name_tool:";
|
|
||||||
LogDebug() << " change rpaths in" << binaryPath;
|
|
||||||
LogDebug() << " using" << args;
|
|
||||||
runInstallNameTool(QStringList() << args << binaryPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
void deployRPaths(const QString &bundlePath, const QSet<QString> &rpaths, const QStringList &binaryPaths, bool useLoaderPath)
|
|
||||||
{
|
|
||||||
foreach (const QString &binary, binaryPaths) {
|
|
||||||
deployRPaths(bundlePath, rpaths, binary, useLoaderPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath)
|
|
||||||
{
|
|
||||||
LogDebug() << "Using install_name_tool:";
|
|
||||||
LogDebug() << " in" << binaryPath;
|
|
||||||
LogDebug() << " change reference" << oldName;
|
|
||||||
LogDebug() << " to" << newName;
|
|
||||||
runInstallNameTool(QStringList() << "-change" << oldName << newName << binaryPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void runStrip(const QString &binaryPath)
|
void runStrip(const QString &binaryPath)
|
||||||
@@ -916,38 +675,35 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
|
|||||||
QStringList copiedFrameworks;
|
QStringList copiedFrameworks;
|
||||||
DeploymentInfo deploymentInfo;
|
DeploymentInfo deploymentInfo;
|
||||||
deploymentInfo.useLoaderPath = useLoaderPath;
|
deploymentInfo.useLoaderPath = useLoaderPath;
|
||||||
deploymentInfo.isFramework = bundlePath.contains(".framework");
|
|
||||||
QSet<QString> rpathsUsed;
|
QSet<QString> rpathsUsed;
|
||||||
|
|
||||||
while (frameworks.isEmpty() == false) {
|
while (frameworks.isEmpty() == false) {
|
||||||
const FrameworkInfo framework = frameworks.takeFirst();
|
const FrameworkInfo framework = frameworks.takeFirst();
|
||||||
copiedFrameworks.append(framework.frameworkName);
|
copiedFrameworks.append(framework.frameworkName);
|
||||||
|
|
||||||
if (deploymentInfo.qtPath.isNull())
|
if(framework.frameworkName.contains("libQt") and framework.frameworkName.contains("Core.so")) {
|
||||||
deploymentInfo.qtPath = QLibraryInfo::location(QLibraryInfo::PrefixPath);
|
LogNormal() << "Setting deploymentInfo.qtPath to:" << framework.frameworkDirectory;
|
||||||
|
deploymentInfo.qtPath = framework.frameworkDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (framework.frameworkDirectory.startsWith(bundlePath)) {
|
if (framework.frameworkDirectory.startsWith(bundlePath)) {
|
||||||
LogError() << framework.frameworkName << "already deployed, skipping.";
|
LogError() << framework.frameworkName << "already deployed, skipping.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install_name_tool the new id into the binaries
|
if (framework.rpathUsed.isEmpty() != true) {
|
||||||
if (framework.rpathUsed.isEmpty()) {
|
|
||||||
changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath);
|
|
||||||
} else {
|
|
||||||
rpathsUsed << framework.rpathUsed;
|
rpathsUsed << framework.rpathUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the framework/dylib to the app bundle.
|
// Copy the framework/dylib to the app bundle.
|
||||||
const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
|
const QString deployedBinaryPath = copyDylib(framework, bundlePath);
|
||||||
: copyFramework(framework, bundlePath);
|
|
||||||
// Skip the rest if already was deployed.
|
// Skip the rest if already was deployed.
|
||||||
if (deployedBinaryPath.isNull())
|
if (deployedBinaryPath.isNull())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
runStrip(deployedBinaryPath);
|
runStrip(deployedBinaryPath);
|
||||||
|
|
||||||
// Install_name_tool it a new id.
|
|
||||||
if (!framework.rpathUsed.length()) {
|
if (!framework.rpathUsed.length()) {
|
||||||
changeIdentification(framework.deployedInstallName, deployedBinaryPath);
|
changeIdentification(framework.deployedInstallName, deployedBinaryPath);
|
||||||
}
|
}
|
||||||
@@ -956,9 +712,7 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
|
|||||||
QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
|
QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
|
||||||
|
|
||||||
foreach (FrameworkInfo dependency, dependencies) {
|
foreach (FrameworkInfo dependency, dependencies) {
|
||||||
if (dependency.rpathUsed.isEmpty()) {
|
if (dependency.rpathUsed.isEmpty() != true) {
|
||||||
changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath);
|
|
||||||
} else {
|
|
||||||
rpathsUsed << dependency.rpathUsed;
|
rpathsUsed << dependency.rpathUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,8 +723,9 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
deploymentInfo.deployedFrameworks = copiedFrameworks;
|
deploymentInfo.deployedFrameworks = copiedFrameworks;
|
||||||
deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath);
|
|
||||||
deploymentInfo.rpathsUsed += rpathsUsed;
|
deploymentInfo.rpathsUsed += rpathsUsed;
|
||||||
|
|
||||||
return deploymentInfo;
|
return deploymentInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -978,17 +733,29 @@ DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringLis
|
|||||||
{
|
{
|
||||||
ApplicationBundleInfo applicationBundle;
|
ApplicationBundleInfo applicationBundle;
|
||||||
applicationBundle.path = appBundlePath;
|
applicationBundle.path = appBundlePath;
|
||||||
|
LogDebug() << "applicationBundle.path:" << applicationBundle.path;
|
||||||
applicationBundle.binaryPath = findAppBinary(appBundlePath);
|
applicationBundle.binaryPath = findAppBinary(appBundlePath);
|
||||||
|
LogDebug() << "applicationBundle.binaryPath:" << applicationBundle.binaryPath;
|
||||||
|
changeIdentification("$ORIGIN/" + bundleFrameworkDirectory, applicationBundle.binaryPath);
|
||||||
applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
|
applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
|
||||||
|
LogDebug() << "applicationBundle.libraryPaths:" << applicationBundle.libraryPaths;
|
||||||
|
|
||||||
|
LogDebug() << "additionalExecutables:" << additionalExecutables;
|
||||||
|
|
||||||
QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
|
QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
|
||||||
<< additionalExecutables;
|
<< additionalExecutables;
|
||||||
|
LogDebug() << "allBinaryPaths:" << allBinaryPaths;
|
||||||
|
|
||||||
QSet<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true);
|
QSet<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true);
|
||||||
allLibraryPaths.insert(QLibraryInfo::location(QLibraryInfo::LibrariesPath));
|
allLibraryPaths.insert(QLibraryInfo::location(QLibraryInfo::LibrariesPath));
|
||||||
|
|
||||||
|
LogDebug() << "allLibraryPaths:" << allLibraryPaths;
|
||||||
|
|
||||||
QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
|
QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
|
||||||
if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) {
|
if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) {
|
||||||
LogWarning();
|
LogWarning();
|
||||||
LogWarning() << "Could not find any external Qt frameworks to deploy in" << appBundlePath;
|
LogWarning() << "Could not find any external Qt frameworks to deploy in" << appBundlePath;
|
||||||
LogWarning() << "Perhaps macdeployqt was already used on" << appBundlePath << "?";
|
LogWarning() << "Perhaps linuxdeployqt was already used on" << appBundlePath << "?";
|
||||||
LogWarning() << "If so, you will need to rebuild" << appBundlePath << "before trying again.";
|
LogWarning() << "If so, you will need to rebuild" << appBundlePath << "before trying again.";
|
||||||
return DeploymentInfo();
|
return DeploymentInfo();
|
||||||
} else {
|
} else {
|
||||||
@@ -1008,10 +775,10 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
|||||||
QStringList pluginList;
|
QStringList pluginList;
|
||||||
|
|
||||||
// Platform plugin:
|
// Platform plugin:
|
||||||
pluginList.append("platforms/libqcocoa.dylib");
|
pluginList.append("platforms/libqxcb.so");
|
||||||
|
|
||||||
// Cocoa print support
|
// Cocoa print support
|
||||||
pluginList.append("printsupport/libcocoaprintersupport.dylib");
|
pluginList.append("printsupport/libcupsprintersupport.so");
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
if (deploymentInfo.deployedFrameworks.contains(QStringLiteral("QtNetwork.framework"))) {
|
if (deploymentInfo.deployedFrameworks.contains(QStringLiteral("QtNetwork.framework"))) {
|
||||||
@@ -1092,11 +859,11 @@ void createQtConf(const QString &appBundlePath)
|
|||||||
{
|
{
|
||||||
// Set Plugins and imports paths. These are relative to App.app/Contents.
|
// Set Plugins and imports paths. These are relative to App.app/Contents.
|
||||||
QByteArray contents = "[Paths]\n"
|
QByteArray contents = "[Paths]\n"
|
||||||
"Plugins = PlugIns\n"
|
"Plugins = plugins\n"
|
||||||
"Imports = Resources/qml\n"
|
"Imports = qml\n"
|
||||||
"Qml2Imports = Resources/qml\n";
|
"Qml2Imports = qml\n";
|
||||||
|
|
||||||
QString filePath = appBundlePath + "/Contents/Resources/";
|
QString filePath = appBundlePath + "/"; // Is picked up when placed next to the main executable
|
||||||
QString fileName = filePath + "qt.conf";
|
QString fileName = filePath + "qt.conf";
|
||||||
|
|
||||||
QDir().mkpath(filePath);
|
QDir().mkpath(filePath);
|
||||||
@@ -1108,14 +875,14 @@ void createQtConf(const QString &appBundlePath)
|
|||||||
LogWarning() << "To make sure the plugins are loaded from the correct location,";
|
LogWarning() << "To make sure the plugins are loaded from the correct location,";
|
||||||
LogWarning() << "please make sure qt.conf contains the following lines:";
|
LogWarning() << "please make sure qt.conf contains the following lines:";
|
||||||
LogWarning() << "[Paths]";
|
LogWarning() << "[Paths]";
|
||||||
LogWarning() << " Plugins = PlugIns";
|
LogWarning() << " Plugins = plugins";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qtconf.open(QIODevice::WriteOnly);
|
qtconf.open(QIODevice::WriteOnly);
|
||||||
if (qtconf.write(contents) != -1) {
|
if (qtconf.write(contents) != -1) {
|
||||||
LogNormal() << "Created configuration file:" << fileName;
|
LogNormal() << "Created configuration file:" << fileName;
|
||||||
LogNormal() << "This file sets the plugin search path to" << appBundlePath + "/Contents/PlugIns";
|
LogNormal() << "This file sets the plugin search path to" << appBundlePath + "/plugins";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1125,13 +892,13 @@ void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo,
|
|||||||
applicationBundle.path = appBundlePath;
|
applicationBundle.path = appBundlePath;
|
||||||
applicationBundle.binaryPath = findAppBinary(appBundlePath);
|
applicationBundle.binaryPath = findAppBinary(appBundlePath);
|
||||||
|
|
||||||
const QString pluginDestinationPath = appBundlePath + "/" + "Contents/PlugIns";
|
const QString pluginDestinationPath = appBundlePath + "/" + "plugins";
|
||||||
deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
|
deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void deployQmlImport(const QString &appBundlePath, const QSet<QString> &rpaths, const QString &importSourcePath, const QString &importName)
|
void deployQmlImport(const QString &appBundlePath, const QSet<QString> &rpaths, const QString &importSourcePath, const QString &importName)
|
||||||
{
|
{
|
||||||
QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName;
|
QString importDestinationPath = appBundlePath + "/qml/" + importName;
|
||||||
|
|
||||||
// Skip already deployed imports. This can happen in cases like "QtQuick.Controls.Styles",
|
// Skip already deployed imports. This can happen in cases like "QtQuick.Controls.Styles",
|
||||||
// where deploying QtQuick.Controls will also deploy the "Styles" sub-import.
|
// where deploying QtQuick.Controls will also deploy the "Styles" sub-import.
|
||||||
@@ -1141,7 +908,7 @@ void deployQmlImport(const QString &appBundlePath, const QSet<QString> &rpaths,
|
|||||||
recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath);
|
recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan qml files in qmldirs for import statements, deploy used imports from Qml2ImportsPath to Contents/Resources/qml.
|
// Scan qml files in qmldirs for import statements, deploy used imports from Qml2ImportsPath to ./qml.
|
||||||
bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs)
|
bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs)
|
||||||
{
|
{
|
||||||
LogNormal() << "";
|
LogNormal() << "";
|
||||||
@@ -1151,7 +918,7 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
|||||||
// Use qmlimportscanner from QLibraryInfo::BinariesPath
|
// Use qmlimportscanner from QLibraryInfo::BinariesPath
|
||||||
QString qmlImportScannerPath = QDir::cleanPath(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlimportscanner");
|
QString qmlImportScannerPath = QDir::cleanPath(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlimportscanner");
|
||||||
|
|
||||||
// Fallback: Look relative to the macdeployqt binary
|
// Fallback: Look relative to the linuxdeployqt binary
|
||||||
if (!QFile(qmlImportScannerPath).exists())
|
if (!QFile(qmlImportScannerPath).exists())
|
||||||
qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
|
qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
|
||||||
|
|
||||||
@@ -1274,8 +1041,6 @@ void changeQtFrameworks(const QList<FrameworkInfo> frameworks, const QStringList
|
|||||||
foreach (FrameworkInfo framework, frameworks) {
|
foreach (FrameworkInfo framework, frameworks) {
|
||||||
const QString oldBinaryId = framework.installName;
|
const QString oldBinaryId = framework.installName;
|
||||||
const QString newBinaryId = finalQtPath + framework.frameworkName + framework.binaryPath;
|
const QString newBinaryId = finalQtPath + framework.frameworkName + framework.binaryPath;
|
||||||
foreach (const QString &binary, binaryPaths)
|
|
||||||
changeInstallName(oldBinaryId, newBinaryId, binary);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1294,170 +1059,7 @@ void changeQtFrameworks(const QString appPath, const QString &qtPath, bool useDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void codesignFile(const QString &identity, const QString &filePath)
|
void createAppImage(const QString &appBundlePath)
|
||||||
{
|
|
||||||
if (!runCodesign)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LogNormal() << "codesign" << filePath;
|
|
||||||
|
|
||||||
QProcess codesign;
|
|
||||||
codesign.start("codesign", QStringList() << "--preserve-metadata=identifier,entitlements"
|
|
||||||
<< "--force" << "-s" << identity << filePath);
|
|
||||||
codesign.waitForFinished(-1);
|
|
||||||
|
|
||||||
QByteArray err = codesign.readAllStandardError();
|
|
||||||
if (codesign.exitCode() > 0) {
|
|
||||||
LogError() << "Codesign signing error:";
|
|
||||||
LogError() << err;
|
|
||||||
} else if (!err.isEmpty()) {
|
|
||||||
LogDebug() << err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QSet<QString> codesignBundle(const QString &identity,
|
|
||||||
const QString &appBundlePath,
|
|
||||||
QList<QString> additionalBinariesContainingRpaths)
|
|
||||||
{
|
|
||||||
// Code sign all binaries in the app bundle. This needs to
|
|
||||||
// be done inside-out, e.g sign framework dependencies
|
|
||||||
// before the main app binary. The codesign tool itself has
|
|
||||||
// a "--deep" option to do this, but usage when signing is
|
|
||||||
// not recommended: "Signing with --deep is for emergency
|
|
||||||
// repairs and temporary adjustments only."
|
|
||||||
|
|
||||||
LogNormal() << "";
|
|
||||||
LogNormal() << "Signing" << appBundlePath << "with identity" << identity;
|
|
||||||
|
|
||||||
QStack<QString> pendingBinaries;
|
|
||||||
QSet<QString> pendingBinariesSet;
|
|
||||||
QSet<QString> signedBinaries;
|
|
||||||
|
|
||||||
// Create the root code-binary set. This set consists of the application
|
|
||||||
// executable(s) and the plugins.
|
|
||||||
QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath();
|
|
||||||
QString rootBinariesPath = appBundleAbsolutePath + "/Contents/MacOS/";
|
|
||||||
QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() << "*", QDir::Files);
|
|
||||||
foreach (const QString &binary, foundRootBinaries) {
|
|
||||||
QString binaryPath = rootBinariesPath + binary;
|
|
||||||
pendingBinaries.push(binaryPath);
|
|
||||||
pendingBinariesSet.insert(binaryPath);
|
|
||||||
additionalBinariesContainingRpaths.append(binaryPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getAbsoltuePath = true;
|
|
||||||
QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath + "/Contents/PlugIns/", getAbsoltuePath);
|
|
||||||
foreach (const QString &binary, foundPluginBinaries) {
|
|
||||||
pendingBinaries.push(binary);
|
|
||||||
pendingBinariesSet.insert(binary);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add frameworks for processing.
|
|
||||||
QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath);
|
|
||||||
foreach (const QString &frameworkPath, frameworkPaths) {
|
|
||||||
|
|
||||||
// Add all files for a framework as a catch all.
|
|
||||||
QStringList bundleFiles = findAppBundleFiles(frameworkPath, getAbsoltuePath);
|
|
||||||
foreach (const QString &binary, bundleFiles) {
|
|
||||||
pendingBinaries.push(binary);
|
|
||||||
pendingBinariesSet.insert(binary);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prioritise first to sign any additional inner bundles found in the Helpers folder (e.g
|
|
||||||
// used by QtWebEngine).
|
|
||||||
QDirIterator helpersIterator(frameworkPath, QStringList() << QString::fromLatin1("Helpers"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
|
|
||||||
while (helpersIterator.hasNext()) {
|
|
||||||
helpersIterator.next();
|
|
||||||
QString helpersPath = helpersIterator.filePath();
|
|
||||||
QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() << "*.app", QDir::Dirs);
|
|
||||||
foreach (const QString &innerBundleName, innerBundleNames)
|
|
||||||
signedBinaries += codesignBundle(identity,
|
|
||||||
helpersPath + "/" + innerBundleName,
|
|
||||||
additionalBinariesContainingRpaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also make sure to sign any libraries that will not be found by otool because they
|
|
||||||
// are not linked and won't be seen as a dependency.
|
|
||||||
QDirIterator librariesIterator(frameworkPath, QStringList() << QString::fromLatin1("Libraries"), QDir::Dirs | QDir::NoSymLinks, QDirIterator::Subdirectories);
|
|
||||||
while (librariesIterator.hasNext()) {
|
|
||||||
librariesIterator.next();
|
|
||||||
QString librariesPath = librariesIterator.filePath();
|
|
||||||
bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath);
|
|
||||||
foreach (const QString &binary, bundleFiles) {
|
|
||||||
pendingBinaries.push(binary);
|
|
||||||
pendingBinariesSet.insert(binary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign all binaries; use otool to find and sign dependencies first.
|
|
||||||
while (!pendingBinaries.isEmpty()) {
|
|
||||||
QString binary = pendingBinaries.pop();
|
|
||||||
if (signedBinaries.contains(binary))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Check if there are unsigned dependencies, sign these first.
|
|
||||||
QStringList dependencies =
|
|
||||||
getBinaryDependencies(rootBinariesPath, binary, additionalBinariesContainingRpaths).toSet()
|
|
||||||
.subtract(signedBinaries)
|
|
||||||
.subtract(pendingBinariesSet)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (!dependencies.isEmpty()) {
|
|
||||||
pendingBinaries.push(binary);
|
|
||||||
pendingBinariesSet.insert(binary);
|
|
||||||
int dependenciesSkipped = 0;
|
|
||||||
foreach (const QString &dependency, dependencies) {
|
|
||||||
// Skip dependencies that are outside the current app bundle, because this might
|
|
||||||
// cause a codesign error if the current bundle is part of the dependency (e.g.
|
|
||||||
// a bundle is part of a framework helper, and depends on that framework).
|
|
||||||
// The dependencies will be taken care of after the current bundle is signed.
|
|
||||||
if (!dependency.startsWith(appBundleAbsolutePath)) {
|
|
||||||
++dependenciesSkipped;
|
|
||||||
LogNormal() << "Skipping outside dependency: " << dependency;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
pendingBinaries.push(dependency);
|
|
||||||
pendingBinariesSet.insert(dependency);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all dependencies were skipped, make sure the binary is actually signed, instead
|
|
||||||
// of going into an infinite loop.
|
|
||||||
if (dependenciesSkipped == dependencies.size()) {
|
|
||||||
pendingBinaries.pop();
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All dependencies are signed, now sign this binary.
|
|
||||||
codesignFile(identity, binary);
|
|
||||||
signedBinaries.insert(binary);
|
|
||||||
pendingBinariesSet.remove(binary);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogNormal() << "Finished codesigning " << appBundlePath << "with identity" << identity;
|
|
||||||
|
|
||||||
// Verify code signature
|
|
||||||
QProcess codesign;
|
|
||||||
codesign.start("codesign", QStringList() << "--deep" << "-v" << appBundlePath);
|
|
||||||
codesign.waitForFinished(-1);
|
|
||||||
QByteArray err = codesign.readAllStandardError();
|
|
||||||
if (codesign.exitCode() > 0) {
|
|
||||||
LogError() << "codesign verification error:";
|
|
||||||
LogError() << err;
|
|
||||||
} else if (!err.isEmpty()) {
|
|
||||||
LogDebug() << err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return signedBinaries;
|
|
||||||
}
|
|
||||||
|
|
||||||
void codesign(const QString &identity, const QString &appBundlePath) {
|
|
||||||
codesignBundle(identity, appBundlePath, QList<QString>());
|
|
||||||
}
|
|
||||||
|
|
||||||
void createDiskImage(const QString &appBundlePath)
|
|
||||||
{
|
{
|
||||||
QString appBaseName = appBundlePath;
|
QString appBaseName = appBundlePath;
|
||||||
appBaseName.chop(4); // remove ".app" from end
|
appBaseName.chop(4); // remove ".app" from end
|
||||||
@@ -1486,25 +1088,3 @@ void createDiskImage(const QString &appBundlePath)
|
|||||||
hdutil.start("hdiutil", options);
|
hdutil.start("hdiutil", options);
|
||||||
hdutil.waitForFinished(-1);
|
hdutil.waitForFinished(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fixupFramework(const QString &frameworkName)
|
|
||||||
{
|
|
||||||
// Expected framework name looks like "Foo.framework"
|
|
||||||
QStringList parts = frameworkName.split(".");
|
|
||||||
if (parts.count() < 2) {
|
|
||||||
LogError() << "fixupFramework: Unexpected framework name" << frameworkName;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assume framework binary path is Foo.framework/Foo
|
|
||||||
QString frameworkBinary = frameworkName + QStringLiteral("/") + parts[0];
|
|
||||||
|
|
||||||
// Xcode expects to find Foo.framework/Versions/A when code
|
|
||||||
// signing, while qmake typically generates numeric versions.
|
|
||||||
// Create symlink to the actual version in the framework.
|
|
||||||
linkFilePrintStatus("Current", frameworkName + "/Versions/A");
|
|
||||||
|
|
||||||
// Set up @rpath structure.
|
|
||||||
changeIdentification("@rpath/" + frameworkBinary, frameworkBinary);
|
|
||||||
addRPath("@loader_path/../../Contents/Frameworks/", frameworkBinary);
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-9
@@ -25,8 +25,9 @@
|
|||||||
** $QT_END_LICENSE$
|
** $QT_END_LICENSE$
|
||||||
**
|
**
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
#ifndef MAC_DEPLOMYMENT_SHARED_H
|
|
||||||
#define MAC_DEPLOMYMENT_SHARED_H
|
#ifndef LINUX_DEPLOMYMENT_SHARED_H
|
||||||
|
#define LINUX_DEPLOMYMENT_SHARED_H
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
@@ -124,13 +125,7 @@ void stripAppBinary(const QString &bundlePath);
|
|||||||
QString findAppBinary(const QString &appBundlePath);
|
QString findAppBinary(const QString &appBundlePath);
|
||||||
QStringList findAppFrameworkNames(const QString &appBundlePath);
|
QStringList findAppFrameworkNames(const QString &appBundlePath);
|
||||||
QStringList findAppFrameworkPaths(const QString &appBundlePath);
|
QStringList findAppFrameworkPaths(const QString &appBundlePath);
|
||||||
void codesignFile(const QString &identity, const QString &filePath);
|
void createAppImage(const QString &appBundlePath);
|
||||||
QSet<QString> codesignBundle(const QString &identity,
|
|
||||||
const QString &appBundlePath,
|
|
||||||
QList<QString> additionalBinariesContainingRpaths);
|
|
||||||
void codesign(const QString &identity, const QString &appBundlePath);
|
|
||||||
void createDiskImage(const QString &appBundlePath);
|
|
||||||
void fixupFramework(const QString &appBundlePath);
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
TEMPLATE = app
|
|
||||||
INCLUDEPATH += . ../shared/
|
|
||||||
TARGET=tst_deployment_mac
|
|
||||||
CONFIG += qtestlib
|
|
||||||
|
|
||||||
SOURCES += tst_deployment_mac.cpp ../shared/shared.cpp
|
|
||||||
HEADERS += ../shared/shared.h
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
|
||||||
** Contact: https://www.qt.io/licensing/
|
|
||||||
**
|
|
||||||
** This file is part of the tools applications of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
|
||||||
** Commercial License Usage
|
|
||||||
** Licensees holding valid commercial Qt licenses may use this file in
|
|
||||||
** accordance with the commercial license agreement provided with the
|
|
||||||
** Software or, alternatively, in accordance with the terms contained in
|
|
||||||
** a written agreement between you and The Qt Company. For licensing terms
|
|
||||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
||||||
** information use the contact form at https://www.qt.io/contact-us.
|
|
||||||
**
|
|
||||||
** GNU General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU
|
|
||||||
** General Public License version 3 as published by the Free Software
|
|
||||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
||||||
** included in the packaging of this file. Please review the following
|
|
||||||
** information to ensure the GNU General Public License requirements will
|
|
||||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
#include <QtCore>
|
|
||||||
#include <QtTest/QTest>
|
|
||||||
#include <shared.h>
|
|
||||||
|
|
||||||
class tst_deployment_mac : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
private slots:
|
|
||||||
void testParseOtoolLibraryLine();
|
|
||||||
void testgetQtFrameworks();
|
|
||||||
void testFindAppBinarty();
|
|
||||||
};
|
|
||||||
|
|
||||||
void tst_deployment_mac::testParseOtoolLibraryLine()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
QString line = " /Users/foo/build/qt-4.4/lib/QtGui.framework/Versions/4/QtGui (compatibility version 4.4.0, current version 4.4.0)";
|
|
||||||
FrameworkInfo info = parseOtoolLibraryLine(line, false);
|
|
||||||
// qDebug() << info;
|
|
||||||
QCOMPARE(info.frameworkDirectory, QLatin1String("/Users/foo/build/qt-4.4/lib/"));
|
|
||||||
QCOMPARE(info.frameworkName, QLatin1String("QtGui.framework"));
|
|
||||||
QCOMPARE(info.frameworkPath, QLatin1String("/Users/foo/build/qt-4.4/lib/QtGui.framework"));
|
|
||||||
QCOMPARE(info.binaryDirectory, QLatin1String("Versions/4"));
|
|
||||||
QCOMPARE(info.binaryName, QLatin1String("QtGui"));
|
|
||||||
QCOMPARE(info.binaryPath, QLatin1String("/Versions/4/QtGui"));
|
|
||||||
QCOMPARE(info.version, QLatin1String("4"));
|
|
||||||
QCOMPARE(info.installName, QLatin1String("/Users/foo/build/qt-4.4/lib/QtGui.framework/Versions/4/QtGui"));
|
|
||||||
QCOMPARE(info.deployedInstallName, QLatin1String("@executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui"));
|
|
||||||
QCOMPARE(info.sourceFilePath, QLatin1String("/Users/foo/build/qt-4.4/lib/QtGui.framework/Versions/4/QtGui"));
|
|
||||||
QCOMPARE(info.destinationDirectory, QLatin1String("Contents/Frameworks/QtGui.framework/Versions/4"));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
QString line = " /Users/foo/build/qt-4.4/lib/phonon.framework/Versions/4/phonon (compatibility version 4.1.0, current version 4.1.0)";
|
|
||||||
FrameworkInfo info = parseOtoolLibraryLine(line, false);
|
|
||||||
// qDebug() << info;
|
|
||||||
QCOMPARE(info.frameworkDirectory, QLatin1String("/Users/foo/build/qt-4.4/lib/"));
|
|
||||||
QCOMPARE(info.frameworkName, QLatin1String("phonon.framework"));
|
|
||||||
QCOMPARE(info.frameworkPath, QLatin1String("/Users/foo/build/qt-4.4/lib/phonon.framework"));
|
|
||||||
QCOMPARE(info.binaryDirectory, QLatin1String("Versions/4"));
|
|
||||||
QCOMPARE(info.binaryName, QLatin1String("phonon"));
|
|
||||||
QCOMPARE(info.binaryPath, QLatin1String("/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.version, QLatin1String("4"));
|
|
||||||
QCOMPARE(info.installName, QLatin1String("/Users/foo/build/qt-4.4/lib/phonon.framework/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.deployedInstallName, QLatin1String("@executable_path/../Frameworks/phonon.framework/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.sourceFilePath, QLatin1String("/Users/foo/build/qt-4.4/lib/phonon.framework/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.destinationDirectory, QLatin1String("Contents/Frameworks/phonon.framework/Versions/4"));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QString line = " /usr/local/Qt-4.4.0/lib/phonon.framework/Versions/4/phonon (compatibility version 4.1.0, current version 4.1.0)";
|
|
||||||
FrameworkInfo info = parseOtoolLibraryLine(line, false);
|
|
||||||
// qDebug() << info;
|
|
||||||
QCOMPARE(info.frameworkDirectory, QLatin1String("/usr/local/Qt-4.4.0/lib/"));
|
|
||||||
QCOMPARE(info.frameworkName, QLatin1String("phonon.framework"));
|
|
||||||
QCOMPARE(info.frameworkPath, QLatin1String("/usr/local/Qt-4.4.0/lib/phonon.framework"));
|
|
||||||
QCOMPARE(info.binaryDirectory, QLatin1String("Versions/4"));
|
|
||||||
QCOMPARE(info.binaryName, QLatin1String("phonon"));
|
|
||||||
QCOMPARE(info.binaryPath, QLatin1String("/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.version, QLatin1String("4"));
|
|
||||||
QCOMPARE(info.installName, QLatin1String("/usr/local/Qt-4.4.0/lib/phonon.framework/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.deployedInstallName, QLatin1String("@executable_path/../Frameworks/phonon.framework/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.sourceFilePath, QLatin1String("/usr/local/Qt-4.4.0/lib/phonon.framework/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.destinationDirectory, QLatin1String("Contents/Frameworks/phonon.framework/Versions/4"));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QString line = " QtGui.framework/Versions/4/QtGui (compatibility version 4.1.0, current version 4.1.0)";
|
|
||||||
FrameworkInfo info = parseOtoolLibraryLine(line, false);
|
|
||||||
// qDebug() << info;
|
|
||||||
QCOMPARE(info.frameworkDirectory, QLatin1String("/Library/Frameworks/"));
|
|
||||||
QCOMPARE(info.frameworkName, QLatin1String("QtGui.framework"));
|
|
||||||
QCOMPARE(info.frameworkPath, QLatin1String("/Library/Frameworks/QtGui.framework"));
|
|
||||||
QCOMPARE(info.binaryDirectory, QLatin1String("Versions/4"));
|
|
||||||
QCOMPARE(info.binaryName, QLatin1String("QtGui"));
|
|
||||||
QCOMPARE(info.binaryPath, QLatin1String("/Versions/4/QtGui"));
|
|
||||||
QCOMPARE(info.version, QLatin1String("4"));
|
|
||||||
QCOMPARE(info.installName, QLatin1String("QtGui.framework/Versions/4/QtGui"));
|
|
||||||
QCOMPARE(info.deployedInstallName, QLatin1String("@executable_path/../Frameworks/QtGui.framework/Versions/4/QtGui"));
|
|
||||||
QCOMPARE(info.sourceFilePath, QLatin1String("/Library/Frameworks/QtGui.framework/Versions/4/QtGui"));
|
|
||||||
QCOMPARE(info.destinationDirectory, QLatin1String("Contents/Frameworks/QtGui.framework/Versions/4"));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QString line = " phonon.framework/Versions/4/QtGui (compatibility version 4.1.0, current version 4.1.0)";
|
|
||||||
FrameworkInfo info = parseOtoolLibraryLine(line, false);
|
|
||||||
// qDebug() << info;
|
|
||||||
QCOMPARE(info.frameworkDirectory, QLatin1String("/Library/Frameworks/"));
|
|
||||||
QCOMPARE(info.frameworkName, QLatin1String("phonon.framework"));
|
|
||||||
QCOMPARE(info.frameworkPath, QLatin1String("/Library/Frameworks/phonon.framework"));
|
|
||||||
QCOMPARE(info.binaryDirectory, QLatin1String("Versions/4"));
|
|
||||||
QCOMPARE(info.binaryName, QLatin1String("phonon"));
|
|
||||||
QCOMPARE(info.binaryPath, QLatin1String("/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.version, QLatin1String("4"));
|
|
||||||
QCOMPARE(info.installName, QLatin1String("phonon.framework/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.deployedInstallName, QLatin1String("@executable_path/../Frameworks/phonon.framework/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.sourceFilePath, QLatin1String("/Library/Frameworks/phonon.framework/Versions/4/phonon"));
|
|
||||||
QCOMPARE(info.destinationDirectory, QLatin1String("Contents/Frameworks/phonon.framework/Versions/4"));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QString line = " /Users/foo/build/qt-4.4/lib/libQtCLucene.4.dylib (compatibility version 4.4.0, current version 4.4.0)";
|
|
||||||
FrameworkInfo info = parseOtoolLibraryLine(line, false);
|
|
||||||
// qDebug() << info;
|
|
||||||
QCOMPARE(info.frameworkDirectory, QLatin1String("/Users/foo/build/qt-4.4/lib/"));
|
|
||||||
QCOMPARE(info.binaryName, QLatin1String("libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.frameworkName, QLatin1String("libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.frameworkPath, QLatin1String("/Users/foo/build/qt-4.4/lib/libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.installName, QLatin1String("/Users/foo/build/qt-4.4/lib/libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.deployedInstallName, QLatin1String("@executable_path/../Frameworks/libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.sourceFilePath, QLatin1String("/Users/foo/build/qt-4.4/lib/libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.destinationDirectory, QLatin1String("Contents/Frameworks/"));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
QString line = "libQtCLucene.4.dylib (compatibility version 4.4.0, current version 4.4.0)";
|
|
||||||
FrameworkInfo info = parseOtoolLibraryLine(line, false);
|
|
||||||
// qDebug() << info;
|
|
||||||
QCOMPARE(info.frameworkDirectory, QLatin1String("/usr/lib/"));
|
|
||||||
QCOMPARE(info.binaryName, QLatin1String("libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.frameworkName, QLatin1String("libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.frameworkPath, QLatin1String("/usr/lib/libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.installName, QLatin1String("libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.deployedInstallName, QLatin1String("@executable_path/../Frameworks/libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.sourceFilePath, QLatin1String("/usr/lib/libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(info.destinationDirectory, QLatin1String("Contents/Frameworks/"));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
QString line = "/foo"; //invalid
|
|
||||||
FrameworkInfo info = parseOtoolLibraryLine(line, false);
|
|
||||||
QCOMPARE(info.frameworkName, QString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void tst_deployment_mac::testgetQtFrameworks()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
QStringList otool = QStringList()
|
|
||||||
<< "/Users/foo/build/qt-4.4/lib/phonon.framework/Versions/4/phonon (compatibility version 4.1.0, current version 4.1.0)"
|
|
||||||
<< "/Users/foo/build/qt-4.4/lib/QtGui.framework/Versions/4/QtGui (compatibility version 4.4.0, current version 4.4.0)"
|
|
||||||
<< "/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon (compatibility version 2.0.0, current version 136.0.0)"
|
|
||||||
<< "/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 949.27.0)"
|
|
||||||
<< "/Users/foo/build/qt-4.4/lib/QtCore.framework/Versions/4/QtCore (compatibility version 4.4.0, current version 4.4.0)"
|
|
||||||
<< "/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.3)"
|
|
||||||
<< "/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 111.0.0)"
|
|
||||||
<< "/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.4.0)"
|
|
||||||
<< "/usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)"
|
|
||||||
<< " "
|
|
||||||
;
|
|
||||||
|
|
||||||
QList<FrameworkInfo> frameworks = getQtFrameworks(otool, false);
|
|
||||||
QCOMPARE(frameworks.count(), 3);
|
|
||||||
QCOMPARE(frameworks.at(0).binaryName, QLatin1String("phonon"));
|
|
||||||
QCOMPARE(frameworks.at(1).binaryName, QLatin1String("QtGui"));
|
|
||||||
QCOMPARE(frameworks.at(2).binaryName, QLatin1String("QtCore"));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
QStringList otool = QStringList()
|
|
||||||
<< "QtHelp.framework/Versions/4/QtHelp (compatibility version 4.4.0, current version 4.4.0)"
|
|
||||||
<< "libQtCLucene.4.dylib (compatibility version 4.4.0, current version 4.4.0)"
|
|
||||||
<< "QtSql.framework/Versions/4/QtSql (compatibility version 4.4.0, current version 4.4.0)"
|
|
||||||
<< "QtXml.framework/Versions/4/QtXml (compatibility version 4.4.0, current version 4.4.0)"
|
|
||||||
<< "QtGui.framework/Versions/4/QtGui (compatibility version 4.4.0, current version 4.4.0)"
|
|
||||||
<< "/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon (compatibility version 2.0.0, current version 128.0.0)"
|
|
||||||
<< "/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 824.42.0)"
|
|
||||||
<< "QtNetwork.framework/Versions/4/QtNetwork (compatibility version 4.4.0, current version 4.4.0)"
|
|
||||||
<< "QtCore.framework/Versions/4/QtCore (compatibility version 4.4.0, current version 4.4.0)"
|
|
||||||
<< "/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.3)"
|
|
||||||
<< "/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 88.3.6)"
|
|
||||||
<< "/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.4.0)"
|
|
||||||
<< "/usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)"
|
|
||||||
;
|
|
||||||
|
|
||||||
QList<FrameworkInfo> frameworks = getQtFrameworks(otool, false);
|
|
||||||
QCOMPARE(frameworks.count(), 7);
|
|
||||||
QCOMPARE(frameworks.at(0).binaryName, QLatin1String("QtHelp"));
|
|
||||||
QCOMPARE(frameworks.at(1).binaryName, QLatin1String("libQtCLucene.4.dylib"));
|
|
||||||
QCOMPARE(frameworks.at(2).binaryName, QLatin1String("QtSql"));
|
|
||||||
QCOMPARE(frameworks.at(3).binaryName, QLatin1String("QtXml"));
|
|
||||||
QCOMPARE(frameworks.at(4).binaryName, QLatin1String("QtGui"));
|
|
||||||
QCOMPARE(frameworks.at(5).binaryName, QLatin1String("QtNetwork"));
|
|
||||||
QCOMPARE(frameworks.at(6).binaryName, QLatin1String("QtCore"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void tst_deployment_mac::testFindAppBinarty()
|
|
||||||
{
|
|
||||||
QCOMPARE(findAppBinary("tst_deployment_mac.app"), QLatin1String("tst_deployment_mac.app/Contents/MacOS/tst_deployment_mac"));
|
|
||||||
}
|
|
||||||
|
|
||||||
QTEST_MAIN(tst_deployment_mac)
|
|
||||||
|
|
||||||
#include "tst_deployment_mac.moc"
|
|
||||||
Reference in New Issue
Block a user