diff --git a/macchangeqt/macchangeqt.pro b/macchangeqt/macchangeqt.pro deleted file mode 100644 index 2eafd3e..0000000 --- a/macchangeqt/macchangeqt.pro +++ /dev/null @@ -1,4 +0,0 @@ -SOURCES += main.cpp ../shared/shared.cpp -LIBS += -framework CoreFoundation - -load(qt_app) diff --git a/macchangeqt/main.cpp b/macchangeqt/main.cpp deleted file mode 100644 index 8030123..0000000 --- a/macchangeqt/main.cpp +++ /dev/null @@ -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); -} diff --git a/macdeployqt.pro b/macdeployqt.pro index a0d90b6..1e2f2d5 100644 --- a/macdeployqt.pro +++ b/macdeployqt.pro @@ -1,2 +1,2 @@ TEMPLATE = subdirs -SUBDIRS = macdeployqt macchangeqt +SUBDIRS = linuxdeployqt diff --git a/macdeployqt/macdeployqt.pro b/macdeployqt/macdeployqt.pro index 2eafd3e..ce237c6 100644 --- a/macdeployqt/macdeployqt.pro +++ b/macdeployqt/macdeployqt.pro @@ -1,4 +1 @@ SOURCES += main.cpp ../shared/shared.cpp -LIBS += -framework CoreFoundation - -load(qt_app) diff --git a/macdeployqt/main.cpp b/macdeployqt/main.cpp index 5488a5f..f6f132d 100644 --- a/macdeployqt/main.cpp +++ b/macdeployqt/main.cpp @@ -34,53 +34,67 @@ int main(int argc, char **argv) { QCoreApplication app(argc, argv); - QString appBundlePath; + QString appBinaryPath; + if (argc > 1) - appBundlePath = QString::fromLocal8Bit(argv[1]); + appBinaryPath = QString::fromLocal8Bit(argv[1]); + appBinaryPath = QDir::cleanPath(appBinaryPath); - if (argc < 2 || appBundlePath.startsWith("-")) { - qDebug() << "Usage: macdeployqt app-bundle [options]"; + if (argc < 2 || appBinaryPath.startsWith("-")) { + qDebug() << "Usage: linuxdeployqt app-binary [options]"; qDebug() << ""; qDebug() << "Options:"; qDebug() << " -verbose=<0-3> : 0 = no output, 1 = error/warning (default), 2 = normal, 3 = debug"; 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() << " -use-debug-libs : Deploy with debug versions of frameworks and plugins (implies -no-strip)"; qDebug() << " -executable= : Let the given executable use the deployed frameworks too"; qDebug() << " -qmldir= : Scan for QML imports in the given path"; qDebug() << " -always-overwrite : Copy files even if the target file exists"; - qDebug() << " -codesign= : Run codesign with the given identity on all executables"; - qDebug() << " -appstore-compliant: Skip deployment of components that use private API"; qDebug() << " -libpath= : Add the given path to the library search path"; qDebug() << ""; - qDebug() << "macdeployqt takes an application bundle as input and makes it"; - qDebug() << "self-contained by copying in the Qt frameworks and plugins that"; + qDebug() << "linuxdeployqt takes an application as input and makes it"; + qDebug() << "self-contained by copying in the Qt libraries and plugins that"; qDebug() << "the application uses."; qDebug() << ""; - qDebug() << "Plugins related to a framework are copied in with the"; - qDebug() << "framework. The accessibility, image formats, and text codec"; + qDebug() << "Plugins related to a Qt library are copied in with the"; + qDebug() << "library. The accessibility, image formats, and text codec"; qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified."; qDebug() << ""; - qDebug() << "Qt plugins may use private API and will cause the app to be"; - qDebug() << "rejected from the Mac App store. MacDeployQt will print a warning"; - 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."; + qDebug() << "See the \"Deploying Applications on Linux\" topic in the"; + qDebug() << "documentation for more information about deployment on Linux."; + + return 1; + } + QString appName = QDir::cleanPath(QFileInfo(appBinaryPath).baseName()); + + if (QDir().exists(appBinaryPath)) { + qDebug() << "app-binary:" << appBinaryPath; + } else { + qDebug() << "Error: Could not find app-binary" << appBinaryPath; return 1; } - appBundlePath = QDir::cleanPath(appBundlePath); + QDir dir; + // QString appDir = QDir::cleanPath(appFile + "/../" + appName + ".AppDir"); + QString appDir = QDir::cleanPath(appBinaryPath + "/../"); - if (QDir().exists(appBundlePath) == false) { - qDebug() << "Error: Could not find app bundle" << appBundlePath; + 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 dmg = false; bool useDebugLibs = false; @@ -90,14 +104,10 @@ int main(int argc, char **argv) QStringList additionalExecutables; bool qmldirArgumentUsed = false; QStringList qmlDirs; - extern bool runCodesign; - extern QString codesignIdentiy; - extern bool appstoreCompliant; - extern bool deployFramework; for (int i = 2; i < argc; ++i) { QByteArray argument = QByteArray(argv[i]); - if (argument == QByteArray("-no-plugins")) { + if (argument == QByteArray("-no-pluginss")) { LogDebug() << "Argument found:" << argument; plugins = false; } else if (argument == QByteArray("-dmg")) { @@ -144,24 +154,6 @@ int main(int argc, char **argv) } else if (argument == QByteArray("-always-overwrite")) { LogDebug() << "Argument found:" << argument; 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("-")) { LogError() << "Unknown argument" << argument << "\n"; return 1; @@ -170,9 +162,6 @@ int main(int argc, char **argv) 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. if (qmlDirs.isEmpty()) { QDir dir; @@ -193,8 +182,7 @@ int main(int argc, char **argv) } if (plugins && !deploymentInfo.qtPath.isEmpty()) { - deploymentInfo.pluginPath = deploymentInfo.qtPath + "/plugins"; - LogNormal(); + deploymentInfo.pluginPath = QDir::cleanPath(deploymentInfo.qtPath + "/../plugins"); deployPlugins(appBundlePath, deploymentInfo, useDebugLibs); createQtConf(appBundlePath); } @@ -202,12 +190,9 @@ int main(int argc, char **argv) if (runStripEnabled) stripAppBinary(appBundlePath); - if (runCodesign) - codesign(codesignIdentiy, appBundlePath); - if (dmg) { LogNormal(); - createDiskImage(appBundlePath); + createAppImage(appBundlePath); } return 0; diff --git a/shared/shared.cpp b/shared/shared.cpp index 47bd70e..7e629f8 100644 --- a/shared/shared.cpp +++ b/shared/shared.cpp @@ -44,15 +44,10 @@ #include #include "shared.h" -#ifdef Q_OS_DARWIN -#include -#endif bool runStripEnabled = true; bool alwaysOwerwriteEnabled = false; -bool runCodesign = false; QStringList librarySearchPath; -QString codesignIdentiy; bool appstoreCompliant = false; int logLevel = 1; bool deployFramework = false; @@ -83,7 +78,7 @@ QDebug operator<<(QDebug debug, const FrameworkInfo &info) 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) { @@ -167,37 +162,45 @@ OtoolInfo findDependencyInfo(const QString &binaryPath) OtoolInfo info; info.binaryPath = binaryPath; - LogDebug() << "Using otool:"; + LogDebug() << "Using ldd:"; LogDebug() << " inspecting" << binaryPath; QProcess otool; - otool.start("otool", QStringList() << "-L" << binaryPath); + otool.start("ldd", QStringList() << binaryPath); otool.waitForFinished(); if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) { - LogError() << otool.readAllStandardError(); + LogError() << "findDependencyInfo:" << otool.readAllStandardError(); return info; } + static const QRegularExpression regexp(QStringLiteral( + "^.+ => (.+) \\(")); + + /* 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); 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; } 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()); 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(); + LogError() << "Could not parse objdump output line:" << outputLines.first(); } outputLines.removeFirst(); } @@ -206,12 +209,13 @@ OtoolInfo findDependencyInfo(const QString &binaryPath) 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)); + dylib.binaryPath = match.captured(1).trimmed(); + LogDebug() << " dylib.binaryPath" << dylib.binaryPath; + /* + dylib.compatibilityVersion = 0; + dylib.currentVersion = 0; + */ 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; QString trimmed = line.trimmed(); + LogDebug() << "parsing" << trimmed; + if (trimmed.isEmpty()) return info; - // Don't deploy system libraries. - if (trimmed.startsWith("/System/Library/") || - (trimmed.startsWith("/usr/lib/") && trimmed.contains("libQt") == false) // exception for libQtuitools and libQtlucene - || trimmed.startsWith("@executable_path") || trimmed.startsWith("@loader_path")) - return info; + // Don't deploy low-level libraries in /lib because these tend to break if moved to a system with a different glibc. + // TODO: Could make bundling these low-level libraries optional but then the bundles might need to + // use something like patchelf --set-interpreter or http://bitwagon.com/rtldi/rtldi.html - // 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; - } + if (trimmed.startsWith("/lib")) return info; - } + enum State {QtPath, FrameworkName, DylibName, Version, End}; State state = QtPath; @@ -283,8 +257,9 @@ 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")) { + if (part < parts.count() && parts.at(part).contains(".so")) { info.frameworkDirectory += "/" + (qtPath + currentPart + "/").simplified(); + LogDebug() << "info.frameworkDirectory:" << info.frameworkDirectory; state = DylibName; continue; } else if (part < parts.count() && parts.at(part).endsWith(".framework")) { @@ -303,6 +278,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl break; } } + /* if (currentPart.contains(".framework")) { if (info.frameworkDirectory.isEmpty()) info.frameworkDirectory = "/Library/Frameworks/" + partsCopy.join("/"); @@ -311,7 +287,9 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl state = FrameworkName; --part; continue; - } else if (currentPart.contains(".dylib")) { + + } else if (currentPart.contains(".so")) { + */ if (info.frameworkDirectory.isEmpty()) info.frameworkDirectory = "/usr/lib/" + partsCopy.join("/"); if (!info.frameworkDirectory.endsWith("/")) @@ -319,7 +297,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl state = DylibName; --part; continue; - } + // } } qtPath += (currentPart + "/"); @@ -337,7 +315,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl info.isDylib = true; info.frameworkName = name; 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.sourceFilePath = info.frameworkPath; info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/"; @@ -352,7 +330,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl info.binaryDirectory = "Versions/" + info.version; info.binaryName = name + suffix; 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.sourceFilePath = info.frameworkPath + info.binaryPath; info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName; @@ -376,35 +354,16 @@ QString findAppBinary(const QString &appBundlePath) { QString binaryPath; -#ifdef Q_OS_DARWIN - CFStringRef bundlePath = appBundlePath.toCFString(); - CFURLRef bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath, - kCFURLPOSIXPathStyle, true); - CFRelease(bundlePath); - 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 + // FIXME: Do without the need for an AppRun symlink + // by passing appBinaryPath from main.cpp here + QString parentDir = QDir::cleanPath(QFileInfo(appBundlePath).path()); + QString appDir = QDir::cleanPath(QFileInfo(appBundlePath).baseName()); + binaryPath = parentDir + "/" + appDir + "/AppRun"; if (QFile::exists(binaryPath)) return binaryPath; LogError() << "Could not find bundle binary for" << appBundlePath; + return QString(); } @@ -414,7 +373,7 @@ QStringList findAppFrameworkNames(const QString &appBundlePath) // populate the frameworks list with QtFoo.framework etc, // as found in /Contents/Frameworks/ - QString searchPath = appBundlePath + "/Contents/Frameworks/"; + QString searchPath = appBundlePath + bundleFrameworkDirectory; QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"), QDir::Dirs); while (iter.hasNext()) { iter.next(); @@ -424,30 +383,27 @@ QStringList findAppFrameworkNames(const QString &appBundlePath) 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 result; - // dylibs - QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*.dylib"), + // .so + QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*.so"), QDir::Files, QDirIterator::Subdirectories); while (iter.hasNext()) { iter.next(); 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; } @@ -482,42 +438,17 @@ QList getQtFrameworks(const QList &dependencies, const 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 getBinaryRPaths(const QString &path, bool resolve = true, QString executablePath = QString()) { QSet rpaths; QProcess otool; - otool.start("otool", QStringList() << "-l" << path); + otool.start("patchelf", QStringList() << "--print-rpath" << path); otool.waitForFinished(); if (otool.exitCode() != 0) { - LogError() << otool.readAllStandardError(); + LogError() << "getBinaryRPaths:" << otool.readAllStandardError(); } if (resolve && executablePath.isEmpty()) { @@ -525,24 +456,15 @@ QSet getBinaryRPaths(const QString &path, bool resolve = true, QString } QString output = otool.readAllStandardOutput(); + LogDebug() << "output:" << output; QStringList outputLines = output.split("\n"); QStringListIterator i(outputLines); while (i.hasNext()) { - if (i.next().contains("cmd LC_RPATH") && i.hasNext() && - i.next().contains("cmdsize") && i.hasNext()) { - const QString &rpathCmd = i.next(); - int pathStart = rpathCmd.indexOf("path "); - int pathEnd = rpathCmd.indexOf(" ("); - if (pathStart >= 0 && pathEnd >= 0 && pathStart < pathEnd) { - QString rpath = rpathCmd.mid(pathStart + 5, pathEnd - pathStart - 5); - if (resolve) { - rpaths << resolveDyldPrefix(rpath, path, executablePath); - } else { - rpaths << rpath; - } - } - } + const QString &rpathCmd = i.next(); + QString rpath = rpathCmd.trimmed(); + LogDebug() << "rpath:" << rpath; + rpaths << rpath; } return rpaths; @@ -558,6 +480,7 @@ QList getQtFrameworksForPaths(const QStringList &paths, const QSt { QList result; QSet existing; + foreach (const QString &path, paths) { foreach (const FrameworkInfo &info, getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) { if (!existing.contains(info.frameworkPath)) { // avoid duplicates @@ -651,40 +574,6 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QSet &r if (file.endsWith("_debug.dylib")) { 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 frameworks = getQtFrameworks(fileDestinationPath, appBundlePath, rpaths, useDebugLibs); - deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath); - } } else { QString fileDestinationPath = destinationPath + QLatin1Char('/') + file; copyFilePrintStatus(fileSourcePath, fileDestinationPath); @@ -724,159 +613,29 @@ QString copyDylib(const FrameworkInfo &framework, const QString path) return dylibDestinationBinaryPath; } -QString copyFramework(const FrameworkInfo &framework, const QString path) +void runPatchelf(QStringList options) { - if (!QFile::exists(framework.sourceFilePath)) { - LogError() << "no file at" << framework.sourceFilePath; - return QString(); - } - - // Construct destination paths. The full path typically looks like - // MyApp.app/Contents/Frameworks/Foo.framework/Versions/5/QtFoo - 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(); + QProcess patchelftool; + patchelftool.start("patchelf", options); + 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"; + LogError() << "runPatchelf:" << patchelftool.readAllStandardError(); + LogError() << "runPatchelf:" << patchelftool.readAllStandardOutput(); } } void changeIdentification(const QString &id, const QString &binaryPath) { - LogDebug() << "Using install_name_tool:"; + LogDebug() << "Using patchelf:"; LogDebug() << " change identification in" << binaryPath; LogDebug() << " to" << id; - runInstallNameTool(QStringList() << "-id" << 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); - } + runPatchelf(QStringList() << "--set-rpath" << id << binaryPath); } void addRPath(const QString &rpath, const QString &binaryPath) { - runInstallNameTool(QStringList() << "-add_rpath" << rpath << binaryPath); -} - -void deployRPaths(const QString &bundlePath, const QSet &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 &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); + runPatchelf(QStringList() << "-add_rpath" << rpath << binaryPath); } void runStrip(const QString &binaryPath) @@ -916,38 +675,35 @@ DeploymentInfo deployQtFrameworks(QList frameworks, QStringList copiedFrameworks; DeploymentInfo deploymentInfo; deploymentInfo.useLoaderPath = useLoaderPath; - deploymentInfo.isFramework = bundlePath.contains(".framework"); QSet rpathsUsed; while (frameworks.isEmpty() == false) { const FrameworkInfo framework = frameworks.takeFirst(); copiedFrameworks.append(framework.frameworkName); - if (deploymentInfo.qtPath.isNull()) - deploymentInfo.qtPath = QLibraryInfo::location(QLibraryInfo::PrefixPath); + if(framework.frameworkName.contains("libQt") and framework.frameworkName.contains("Core.so")) { + LogNormal() << "Setting deploymentInfo.qtPath to:" << framework.frameworkDirectory; + deploymentInfo.qtPath = framework.frameworkDirectory; + } + if (framework.frameworkDirectory.startsWith(bundlePath)) { LogError() << framework.frameworkName << "already deployed, skipping."; continue; } - // Install_name_tool the new id into the binaries - if (framework.rpathUsed.isEmpty()) { - changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath); - } else { + if (framework.rpathUsed.isEmpty() != true) { rpathsUsed << framework.rpathUsed; } // Copy the framework/dylib to the app bundle. - const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath) - : copyFramework(framework, bundlePath); + const QString deployedBinaryPath = copyDylib(framework, bundlePath); // Skip the rest if already was deployed. if (deployedBinaryPath.isNull()) continue; runStrip(deployedBinaryPath); - // Install_name_tool it a new id. if (!framework.rpathUsed.length()) { changeIdentification(framework.deployedInstallName, deployedBinaryPath); } @@ -956,9 +712,7 @@ DeploymentInfo deployQtFrameworks(QList frameworks, QList dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs); foreach (FrameworkInfo dependency, dependencies) { - if (dependency.rpathUsed.isEmpty()) { - changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath); - } else { + if (dependency.rpathUsed.isEmpty() != true) { rpathsUsed << dependency.rpathUsed; } @@ -969,8 +723,9 @@ DeploymentInfo deployQtFrameworks(QList frameworks, } } deploymentInfo.deployedFrameworks = copiedFrameworks; - deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath); + deploymentInfo.rpathsUsed += rpathsUsed; + return deploymentInfo; } @@ -978,17 +733,29 @@ DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringLis { ApplicationBundleInfo applicationBundle; applicationBundle.path = appBundlePath; + LogDebug() << "applicationBundle.path:" << applicationBundle.path; applicationBundle.binaryPath = findAppBinary(appBundlePath); + LogDebug() << "applicationBundle.binaryPath:" << applicationBundle.binaryPath; + changeIdentification("$ORIGIN/" + bundleFrameworkDirectory, applicationBundle.binaryPath); applicationBundle.libraryPaths = findAppLibraries(appBundlePath); + LogDebug() << "applicationBundle.libraryPaths:" << applicationBundle.libraryPaths; + + LogDebug() << "additionalExecutables:" << additionalExecutables; + QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths << additionalExecutables; + LogDebug() << "allBinaryPaths:" << allBinaryPaths; + QSet allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true); allLibraryPaths.insert(QLibraryInfo::location(QLibraryInfo::LibrariesPath)); + + LogDebug() << "allLibraryPaths:" << allLibraryPaths; + QList frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs); if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) { LogWarning(); 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."; return DeploymentInfo(); } else { @@ -1008,10 +775,10 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl QStringList pluginList; // Platform plugin: - pluginList.append("platforms/libqcocoa.dylib"); + pluginList.append("platforms/libqxcb.so"); // Cocoa print support - pluginList.append("printsupport/libcocoaprintersupport.dylib"); + pluginList.append("printsupport/libcupsprintersupport.so"); // Network 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. QByteArray contents = "[Paths]\n" - "Plugins = PlugIns\n" - "Imports = Resources/qml\n" - "Qml2Imports = Resources/qml\n"; + "Plugins = plugins\n" + "Imports = 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"; 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() << "please make sure qt.conf contains the following lines:"; LogWarning() << "[Paths]"; - LogWarning() << " Plugins = PlugIns"; + LogWarning() << " Plugins = plugins"; return; } qtconf.open(QIODevice::WriteOnly); if (qtconf.write(contents) != -1) { 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.binaryPath = findAppBinary(appBundlePath); - const QString pluginDestinationPath = appBundlePath + "/" + "Contents/PlugIns"; + const QString pluginDestinationPath = appBundlePath + "/" + "plugins"; deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs); } void deployQmlImport(const QString &appBundlePath, const QSet &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", // where deploying QtQuick.Controls will also deploy the "Styles" sub-import. @@ -1141,7 +908,7 @@ void deployQmlImport(const QString &appBundlePath, const QSet &rpaths, 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) { LogNormal() << ""; @@ -1151,7 +918,7 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf // Use qmlimportscanner from QLibraryInfo::BinariesPath 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()) qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner"; @@ -1274,8 +1041,6 @@ void changeQtFrameworks(const QList frameworks, const QStringList foreach (FrameworkInfo framework, frameworks) { const QString oldBinaryId = framework.installName; 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) -{ - 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 codesignBundle(const QString &identity, - const QString &appBundlePath, - QList 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 pendingBinaries; - QSet pendingBinariesSet; - QSet 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()); -} - -void createDiskImage(const QString &appBundlePath) +void createAppImage(const QString &appBundlePath) { QString appBaseName = appBundlePath; appBaseName.chop(4); // remove ".app" from end @@ -1486,25 +1088,3 @@ void createDiskImage(const QString &appBundlePath) hdutil.start("hdiutil", options); 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); -} diff --git a/shared/shared.h b/shared/shared.h index c173846..1fa1bf1 100644 --- a/shared/shared.h +++ b/shared/shared.h @@ -25,8 +25,9 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#ifndef MAC_DEPLOMYMENT_SHARED_H -#define MAC_DEPLOMYMENT_SHARED_H + +#ifndef LINUX_DEPLOMYMENT_SHARED_H +#define LINUX_DEPLOMYMENT_SHARED_H #include #include @@ -124,13 +125,7 @@ void stripAppBinary(const QString &bundlePath); QString findAppBinary(const QString &appBundlePath); QStringList findAppFrameworkNames(const QString &appBundlePath); QStringList findAppFrameworkPaths(const QString &appBundlePath); -void codesignFile(const QString &identity, const QString &filePath); -QSet codesignBundle(const QString &identity, - const QString &appBundlePath, - QList additionalBinariesContainingRpaths); -void codesign(const QString &identity, const QString &appBundlePath); -void createDiskImage(const QString &appBundlePath); -void fixupFramework(const QString &appBundlePath); +void createAppImage(const QString &appBundlePath); #endif diff --git a/tests/deployment_mac.pro b/tests/deployment_mac.pro deleted file mode 100644 index 59f6071..0000000 --- a/tests/deployment_mac.pro +++ /dev/null @@ -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 diff --git a/tests/tst_deployment_mac.cpp b/tests/tst_deployment_mac.cpp deleted file mode 100644 index ffbe974..0000000 --- a/tests/tst_deployment_mac.cpp +++ /dev/null @@ -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 -#include -#include - -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 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 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"