From 58e7c3b04351e691164eeec2bcf5a30d83abdbc2 Mon Sep 17 00:00:00 2001 From: kleuter Date: Tue, 24 Nov 2020 16:11:26 +0100 Subject: [PATCH] macdeployqt too hard, abandon --- .../macdeployqt/macdeployqt/macdeployqt.pro | 8 - .../src/macdeployqt/macdeployqt/main.cpp | 260 --- .../qttools/src/macdeployqt/shared/shared.cpp | 1618 ----------------- 3 files changed, 1886 deletions(-) delete mode 100644 5.15.2/qttools/src/macdeployqt/macdeployqt/macdeployqt.pro delete mode 100644 5.15.2/qttools/src/macdeployqt/macdeployqt/main.cpp delete mode 100644 5.15.2/qttools/src/macdeployqt/shared/shared.cpp diff --git a/5.15.2/qttools/src/macdeployqt/macdeployqt/macdeployqt.pro b/5.15.2/qttools/src/macdeployqt/macdeployqt/macdeployqt.pro deleted file mode 100644 index d751e29..0000000 --- a/5.15.2/qttools/src/macdeployqt/macdeployqt/macdeployqt.pro +++ /dev/null @@ -1,8 +0,0 @@ -option(host_build) -CONFIG += force_bootstrap - -SOURCES += main.cpp ../shared/shared.cpp -QT = core -LIBS += -framework CoreFoundation - -load(qt_tool) \ No newline at end of file diff --git a/5.15.2/qttools/src/macdeployqt/macdeployqt/main.cpp b/5.15.2/qttools/src/macdeployqt/macdeployqt/main.cpp deleted file mode 100644 index 93c476b..0000000 --- a/5.15.2/qttools/src/macdeployqt/macdeployqt/main.cpp +++ /dev/null @@ -1,260 +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 "../shared/shared.h" - -int main(int argc, char **argv) -{ - QCoreApplication app(argc, argv); - - QString appBundlePath; - if (argc > 1) - appBundlePath = QString::fromLocal8Bit(argv[1]); - - if (argc < 2 || appBundlePath.startsWith("-")) { - qDebug() << "Usage: macdeployqt app-bundle [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() << " -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() << " -qmlimport= : Add the given path to the QML module search locations"; - qDebug() << " -always-overwrite : Copy files even if the target file exists"; - qDebug() << " -codesign= : Run codesign with the given identity on all executables"; - qDebug() << " -hardened-runtime : Enable Hardened Runtime when code signing"; - qDebug() << " -timestamp : Include a secure timestamp when code signing (requires internet connection)"; - qDebug() << " -sign-for-notarization=: Activate the necessary options for notarization (requires internet connection)"; - qDebug() << " -appstore-compliant : Skip deployment of components that use private API"; - qDebug() << " -libpath= : Add the given path to the library search path"; - qDebug() << " -fs= : Set the filesystem used for the .dmg disk image (defaults to HFS+)"; - 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() << "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 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."; - - return 1; - } - - appBundlePath = QDir::cleanPath(appBundlePath); - - if (QDir().exists(appBundlePath) == false) { - qDebug() << "Error: Could not find app bundle" << appBundlePath; - return 1; - } - - bool plugins = true; - bool dmg = false; - QByteArray filesystem("HFS+"); - bool useDebugLibs = false; - extern bool runStripEnabled; - extern bool alwaysOwerwriteEnabled; - extern QStringList librarySearchPath; - QStringList additionalExecutables; - bool qmldirArgumentUsed = false; - QStringList qmlDirs; - QStringList qmlImportPaths; - extern bool runCodesign; - extern QString codesignIdentiy; - extern bool hardenedRuntime; - extern bool appstoreCompliant; - extern bool deployFramework; - extern bool secureTimestamp; - - for (int i = 2; i < argc; ++i) { - QByteArray argument = QByteArray(argv[i]); - if (argument == QByteArray("-no-plugins")) { - LogDebug() << "Argument found:" << argument; - plugins = false; - } else if (argument == QByteArray("-dmg")) { - LogDebug() << "Argument found:" << argument; - dmg = true; - } else if (argument == QByteArray("-no-strip")) { - LogDebug() << "Argument found:" << argument; - runStripEnabled = false; - } else if (argument == QByteArray("-use-debug-libs")) { - LogDebug() << "Argument found:" << argument; - useDebugLibs = true; - runStripEnabled = false; - } else if (argument.startsWith(QByteArray("-verbose"))) { - LogDebug() << "Argument found:" << argument; - 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; - } else if (argument.startsWith(QByteArray("-executable"))) { - LogDebug() << "Argument found:" << argument; - int index = argument.indexOf('='); - if (index == -1) - LogError() << "Missing executable path"; - else - additionalExecutables << argument.mid(index+1); - } else if (argument.startsWith(QByteArray("-qmldir"))) { - LogDebug() << "Argument found:" << argument; - qmldirArgumentUsed = true; - int index = argument.indexOf('='); - if (index == -1) - LogError() << "Missing qml directory path"; - else - qmlDirs << argument.mid(index+1); - } else if (argument.startsWith(QByteArray("-qmlimport"))) { - LogDebug() << "Argument found:" << argument; - int index = argument.indexOf('='); - if (index == -1) - LogError() << "Missing qml import path"; - else - qmlImportPaths << argument.mid(index+1); - } else if (argument.startsWith(QByteArray("-libpath"))) { - LogDebug() << "Argument found:" << argument; - int index = argument.indexOf('='); - if (index == -1) - LogError() << "Missing library search path"; - else - librarySearchPath << argument.mid(index+1); - } 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.startsWith(QByteArray("-sign-for-notarization"))) { - LogDebug() << "Argument found:" << argument; - int index = argument.indexOf("="); - if (index < 0 || index >= argument.size()) { - LogError() << "Missing code signing identity"; - } else { - runCodesign = true; - hardenedRuntime = true; - secureTimestamp = true; - codesignIdentiy = argument.mid(index+1); - } - } else if (argument.startsWith(QByteArray("-hardened-runtime"))) { - LogDebug() << "Argument found:" << argument; - hardenedRuntime = true; - } else if (argument.startsWith(QByteArray("-timestamp"))) { - LogDebug() << "Argument found:" << argument; - secureTimestamp = true; - } 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(QByteArray("-fs"))) { - LogDebug() << "Argument found:" << argument; - int index = argument.indexOf('='); - if (index == -1) - LogError() << "Missing filesystem type"; - else - filesystem = argument.mid(index+1); - } else if (argument.startsWith("-")) { - LogError() << "Unknown argument" << argument << "\n"; - return 1; - } - } - - DeploymentInfo deploymentInfo = deployQtFrameworks(appBundlePath, additionalExecutables, useDebugLibs); - - if (deploymentInfo.isDebug) - useDebugLibs = true; - - if (deployFramework && deploymentInfo.isFramework) - fixupFramework(appBundlePath); - - // Convenience: Look for .qml files in the current directoty if no -qmldir specified. - if (qmlDirs.isEmpty()) { - QDir dir; - if (!dir.entryList(QStringList() << QStringLiteral("*.qml")).isEmpty()) { - qmlDirs += QStringLiteral("."); - } - } - - if (!qmlDirs.isEmpty()) { - bool ok = deployQmlImports(appBundlePath, deploymentInfo, qmlDirs, qmlImportPaths); - if (!ok && qmldirArgumentUsed) - return 1; // exit if the user explicitly asked for qml import deployment - - // Update deploymentInfo.deployedFrameworks - the QML imports - // may have brought in extra frameworks as dependencies. - deploymentInfo.deployedFrameworks += findAppFrameworkNames(appBundlePath); - QSet deployedFrameworks(deploymentInfo.deployedFrameworks.begin(), - deploymentInfo.deployedFrameworks.end()); - deploymentInfo.deployedFrameworks = deployedFrameworks.values(); - } - - if (plugins && !deploymentInfo.qtPath.isEmpty()) { - deploymentInfo.pluginPath = deploymentInfo.qtPath + "/plugins"; - LogNormal(); - deployPlugins(appBundlePath, deploymentInfo, useDebugLibs); - createQtConf(appBundlePath); - } - - if (runStripEnabled) - stripAppBinary(appBundlePath); - - if (runCodesign) - codesign(codesignIdentiy, appBundlePath); - - if (dmg) { - LogNormal(); - createDiskImage(appBundlePath, filesystem); - } - - return 0; -} - diff --git a/5.15.2/qttools/src/macdeployqt/shared/shared.cpp b/5.15.2/qttools/src/macdeployqt/shared/shared.cpp deleted file mode 100644 index aae1ae3..0000000 --- a/5.15.2/qttools/src/macdeployqt/shared/shared.cpp +++ /dev/null @@ -1,1618 +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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "shared.h" - -#ifdef Q_OS_DARWIN -#include -#endif - -bool runStripEnabled = true; -bool alwaysOwerwriteEnabled = false; -bool runCodesign = false; -QStringList librarySearchPath; -QString codesignIdentiy; -QString extraEntitlements; -bool hardenedRuntime = false; -bool secureTimestamp = false; -bool appstoreCompliant = false; -int logLevel = 1; -bool deployFramework = false; - -using std::cout; -using std::endl; - -bool operator==(const FrameworkInfo &a, const FrameworkInfo &b) -{ - return ((a.frameworkPath == b.frameworkPath) && (a.binaryPath == b.binaryPath)); -} - -QDebug operator<<(QDebug debug, const FrameworkInfo &info) -{ - debug << "Framework name" << info.frameworkName << "\n"; - debug << "Framework directory" << info.frameworkDirectory << "\n"; - debug << "Framework path" << info.frameworkPath << "\n"; - debug << "Binary directory" << info.binaryDirectory << "\n"; - debug << "Binary name" << info.binaryName << "\n"; - debug << "Binary path" << info.binaryPath << "\n"; - debug << "Version" << info.version << "\n"; - debug << "Install name" << info.installName << "\n"; - debug << "Deployed install name" << info.deployedInstallName << "\n"; - debug << "Source file Path" << info.sourceFilePath << "\n"; - debug << "Framework Destination Directory (relative to bundle)" << info.frameworkDestinationDirectory << "\n"; - debug << "Binary Destination Directory (relative to bundle)" << info.binaryDestinationDirectory << "\n"; - - return debug; -} - -const QString bundleFrameworkDirectory = "Contents/Frameworks"; - -inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info) -{ - debug << "Application bundle path" << info.path << "\n"; - debug << "Binary path" << info.binaryPath << "\n"; - debug << "Additional libraries" << info.libraryPaths << "\n"; - return debug; -} - -bool copyFilePrintStatus(const QString &from, const QString &to) -{ - if (QFile(to).exists()) { - if (alwaysOwerwriteEnabled) { - QFile(to).remove(); - } else { - qDebug() << "File exists, skip copy:" << to; - return false; - } - } - - if (QFile::copy(from, to)) { - QFile dest(to); - dest.setPermissions(dest.permissions() | QFile::WriteOwner | QFile::WriteUser); - LogNormal() << " copied:" << from; - LogNormal() << " to" << to; - - // The source file might not have write permissions set. Set the - // write permission on the target file to make sure we can use - // install_name_tool on it later. - QFile toFile(to); - if (toFile.permissions() & QFile::WriteOwner) - return true; - - if (!toFile.setPermissions(toFile.permissions() | QFile::WriteOwner)) { - LogError() << "Failed to set u+w permissions on target file: " << to; - return false; - } - - return true; - } else { - LogError() << "file copy failed from" << from; - LogError() << " to" << to; - return false; - } -} - -bool linkFilePrintStatus(const QString &file, const QString &link) -{ - if (QFile(link).exists()) { - if (QFile(link).symLinkTarget().isEmpty()) - LogError() << link << "exists but it's a file."; - else - LogNormal() << "Symlink exists, skipping:" << link; - return false; - } else if (QFile::link(file, link)) { - LogNormal() << " symlink" << link; - LogNormal() << " points to" << file; - return true; - } else { - LogError() << "failed to symlink" << link; - LogError() << " to" << file; - return false; - } -} - -void patch_debugInInfoPlist(const QString &infoPlistPath) -{ - // Older versions of qmake may have the "_debug" binary as - // the value for CFBundleExecutable. Remove it. - QFile infoPlist(infoPlistPath); - infoPlist.open(QIODevice::ReadOnly); - QByteArray contents = infoPlist.readAll(); - infoPlist.close(); - infoPlist.open(QIODevice::WriteOnly | QIODevice::Truncate); - contents.replace("_debug", ""); // surely there are no legit uses of "_debug" in an Info.plist - infoPlist.write(contents); -} - -OtoolInfo findDependencyInfo(const QString &binaryPath) -{ - OtoolInfo info; - info.binaryPath = binaryPath; - - LogDebug() << "Using otool:"; - LogDebug() << " inspecting" << binaryPath; - QProcess otool; - otool.start("otool", QStringList() << "-L" << binaryPath); - otool.waitForFinished(); - - if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) { - LogError() << otool.readAllStandardError(); - return info; - } - - static const QRegExp regexp(QStringLiteral( - "^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), " - "current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$")); - - QString output = otool.readAllStandardOutput(); - QStringList outputLines = output.split("\n", Qt::SkipEmptyParts); - if (outputLines.size() < 2) { - LogError() << "Could not parse otool output:" << output; - return info; - } - - outputLines.removeFirst(); // remove line containing the binary path - if (binaryPath.contains(".framework/") || binaryPath.endsWith(".dylib")) { - const auto match = regexp.match(outputLines.first()); - if (match.hasMatch()) { - info.installName = match.captured(1); - info.compatibilityVersion = QVersionNumber::fromString(match.captured(2)); - info.currentVersion = QVersionNumber::fromString(match.captured(3)); - } else { - LogError() << "Could not parse otool output line:" << outputLines.first(); - } - outputLines.removeFirst(); - } - - for (const QString &outputLine : outputLines) { - const auto match = regexp.match(outputLine); - if (match.hasMatch()) { - DylibInfo dylib; - dylib.binaryPath = match.captured(1); - dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2)); - dylib.currentVersion = QVersionNumber::fromString(match.captured(3)); - info.dependencies << dylib; - } else { - LogError() << "Could not parse otool output line:" << outputLine; - } - } - - return info; -} - -FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QSet &rpaths, bool useDebugLibs) -{ - FrameworkInfo info; - QString trimmed = line.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; - - // Resolve rpath relative libraries. - if (trimmed.startsWith("@rpath/")) { - QString rpathRelativePath = trimmed.mid(QStringLiteral("@rpath/").length()); - bool foundInsideBundle = false; - foreach (const QString &rpath, rpaths) { - QString path = QDir::cleanPath(rpath + "/" + rpathRelativePath); - // 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; - resolvedInfo.installName = trimmed; - return resolvedInfo; - } - } - if (!rpaths.isEmpty() && !foundInsideBundle) { - LogError() << "Cannot resolve rpath" << trimmed; - LogError() << " using" << rpaths; - } - return info; - } - - enum State {QtPath, FrameworkName, DylibName, Version, FrameworkBinary, End}; - State state = QtPath; - int part = 0; - QString name; - QString qtPath; - QString suffix = useDebugLibs ? "_debug" : ""; - - // Split the line into [Qt-path]/lib/qt[Module].framework/Versions/[Version]/ - QStringList parts = trimmed.split("/"); - while (part < parts.count()) { - const QString currentPart = parts.at(part).simplified() ; - ++part; - if (currentPart == "") - continue; - - if (state == QtPath) { - // Check for library name part - if (part < parts.count() && parts.at(part).contains(".dylib")) { - info.frameworkDirectory += "/" + (qtPath + currentPart + "/").simplified(); - state = DylibName; - continue; - } else if (part < parts.count() && parts.at(part).endsWith(".framework")) { - info.frameworkDirectory += "/" + (qtPath + "lib/").simplified(); - state = FrameworkName; - continue; - } else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package. - QStringList partsCopy = parts; - partsCopy.removeLast(); - foreach (QString path, librarySearchPath) { - if (!path.endsWith("/")) - path += '/'; - QString nameInPath = path + parts.join(QLatin1Char('/')); - if (QFile::exists(nameInPath)) { - info.frameworkDirectory = path + partsCopy.join(QLatin1Char('/')); - break; - } - } - if (currentPart.contains(".framework")) { - if (info.frameworkDirectory.isEmpty()) - info.frameworkDirectory = "/Library/Frameworks/" + partsCopy.join(QLatin1Char('/')); - if (!info.frameworkDirectory.endsWith("/")) - info.frameworkDirectory += "/"; - state = FrameworkName; - --part; - continue; - } else if (currentPart.contains(".dylib")) { - if (info.frameworkDirectory.isEmpty()) - info.frameworkDirectory = "/usr/lib/" + partsCopy.join(QLatin1Char('/')); - if (!info.frameworkDirectory.endsWith("/")) - info.frameworkDirectory += "/"; - state = DylibName; - --part; - continue; - } - } - qtPath += (currentPart + "/"); - - } if (state == FrameworkName) { - // remove ".framework" - name = currentPart; - name.chop(QString(".framework").length()); - info.isDylib = false; - info.frameworkName = currentPart; - state = Version; - ++part; - continue; - } if (state == DylibName) { - name = currentPart; - info.isDylib = true; - info.frameworkName = name; - info.binaryName = name.contains(suffix) ? name : name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.')); - info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName; - info.frameworkPath = info.frameworkDirectory + info.binaryName; - info.sourceFilePath = info.frameworkPath; - info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/"; - info.binaryDestinationDirectory = info.frameworkDestinationDirectory; - info.binaryDirectory = info.frameworkDirectory; - info.binaryPath = info.frameworkPath; - state = End; - ++part; - continue; - } else if (state == Version) { - info.version = currentPart; - info.binaryDirectory = "Versions/" + info.version; - info.frameworkPath = info.frameworkDirectory + info.frameworkName; - info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName; - info.binaryDestinationDirectory = info.frameworkDestinationDirectory + "/" + info.binaryDirectory; - state = FrameworkBinary; - } else if (state == FrameworkBinary) { - info.binaryName = currentPart.contains(suffix) ? currentPart : currentPart + suffix; - info.binaryPath = "/" + info.binaryDirectory + "/" + info.binaryName; - info.deployedInstallName = "@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath; - info.sourceFilePath = info.frameworkPath + info.binaryPath; - state = End; - } else if (state == End) { - break; - } - } - - if (!info.sourceFilePath.isEmpty() && QFile::exists(info.sourceFilePath)) { - info.installName = findDependencyInfo(info.sourceFilePath).installName; - if (info.installName.startsWith("@rpath/")) - info.deployedInstallName = info.installName; - } - - return info; -} - -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 - - if (QFile::exists(binaryPath)) - return binaryPath; - LogError() << "Could not find bundle binary for" << appBundlePath; - return QString(); -} - -QStringList findAppFrameworkNames(const QString &appBundlePath) -{ - QStringList frameworks; - - // populate the frameworks list with QtFoo.framework etc, - // as found in /Contents/Frameworks/ - QString searchPath = appBundlePath + "/Contents/Frameworks/"; - QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"), - QDir::Dirs | QDir::NoSymLinks); - while (iter.hasNext()) { - iter.next(); - frameworks << iter.fileInfo().fileName(); - } - - return frameworks; -} - -QStringList findAppFrameworkPaths(const QString &appBundlePath) -{ - QStringList frameworks; - QString searchPath = appBundlePath + "/Contents/Frameworks/"; - QDirIterator iter(searchPath, QStringList() << QString::fromLatin1("*.framework"), - QDir::Dirs | QDir::NoSymLinks); - 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"), - QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories); - while (iter.hasNext()) { - iter.next(); - result << iter.fileInfo().filePath(); - } - return result; -} - -QStringList findAppBundleFiles(const QString &appBundlePath, bool absolutePath = false) -{ - QStringList result; - - QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*"), - QDir::Files, QDirIterator::Subdirectories); - - while (iter.hasNext()) { - iter.next(); - if (iter.fileInfo().isSymLink()) - continue; - result << (absolutePath ? iter.fileInfo().absoluteFilePath() : iter.fileInfo().filePath()); - } - - return result; -} - -QString findEntitlementsFile(const QString& path) -{ - QDirIterator iter(path, QStringList() << QString::fromLatin1("*.entitlements"), - QDir::Files, QDirIterator::Subdirectories); - - while (iter.hasNext()) { - iter.next(); - if (iter.fileInfo().isSymLink()) - continue; - - //return the first entitlements file - only one is used for signing anyway - return iter.fileInfo().absoluteFilePath(); - } - - return QString(); -} - -QList getQtFrameworks(const QList &dependencies, const QString &appBundlePath, const QSet &rpaths, bool useDebugLibs) -{ - QList libraries; - for (const DylibInfo &dylibInfo : dependencies) { - FrameworkInfo info = parseOtoolLibraryLine(dylibInfo.binaryPath, appBundlePath, rpaths, useDebugLibs); - if (info.frameworkName.isEmpty() == false) { - LogDebug() << "Adding framework:"; - LogDebug() << info; - libraries.append(info); - } - } - 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.waitForFinished(); - - if (otool.exitCode() != 0) { - LogError() << otool.readAllStandardError(); - } - - if (resolve && executablePath.isEmpty()) { - executablePath = path; - } - - QString output = otool.readAllStandardOutput(); - QStringList outputLines = output.split("\n"); - - for (auto i = outputLines.cbegin(), end = outputLines.cend(); i != end; ++i) { - if (i->contains("cmd LC_RPATH") && ++i != end && - i->contains("cmdsize") && ++i != end) { - const QString &rpathCmd = *i; - int pathStart = rpathCmd.indexOf("path "); - int pathEnd = rpathCmd.indexOf(" ("); - if (pathStart >= 0 && pathEnd >= 0 && pathStart < pathEnd) { - QString rpath = rpathCmd.mid(pathStart + 5, pathEnd - pathStart - 5); - if (resolve) { - rpaths << resolveDyldPrefix(rpath, path, executablePath); - } else { - rpaths << rpath; - } - } - } - } - - return rpaths; -} - -QList getQtFrameworks(const QString &path, const QString &appBundlePath, const QSet &rpaths, bool useDebugLibs) -{ - const OtoolInfo info = findDependencyInfo(path); - return getQtFrameworks(info.dependencies, appBundlePath, rpaths + getBinaryRPaths(path), useDebugLibs); -} - -QList getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QSet &rpaths, bool useDebugLibs) -{ - 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 - existing.insert(info.frameworkPath); - result << info; - } - } - } - return result; -} - -QStringList getBinaryDependencies(const QString executablePath, - const QString &path, - const QList &additionalBinariesContainingRpaths) -{ - QStringList binaries; - - const auto dependencies = findDependencyInfo(path).dependencies; - - bool rpathsLoaded = false; - QSet rpaths; - - // return bundle-local dependencies. (those starting with @executable_path) - foreach (const DylibInfo &info, dependencies) { - QString trimmedLine = info.binaryPath; - if (trimmedLine.startsWith("@executable_path/")) { - QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length())); - if (binary != path) - binaries.append(binary); - } else if (trimmedLine.startsWith("@rpath/")) { - if (!rpathsLoaded) { - rpaths = getBinaryRPaths(path, true, executablePath); - foreach (const QString &binaryPath, additionalBinariesContainingRpaths) { - QSet binaryRpaths = getBinaryRPaths(binaryPath, true); - rpaths += binaryRpaths; - } - rpathsLoaded = true; - } - bool resolved = false; - foreach (const QString &rpath, rpaths) { - QString binary = QDir::cleanPath(rpath + "/" + trimmedLine.mid(QStringLiteral("@rpath/").length())); - LogDebug() << "Checking for" << binary; - if (QFile::exists(binary)) { - binaries.append(binary); - resolved = true; - break; - } - } - if (!resolved && !rpaths.isEmpty()) { - LogError() << "Cannot resolve rpath" << trimmedLine; - LogError() << " using" << rpaths; - } - } - } - - return binaries; -} - -// copies everything _inside_ sourcePath to destinationPath -bool recursiveCopy(const QString &sourcePath, const QString &destinationPath) -{ - if (!QDir(sourcePath).exists()) - return false; - QDir().mkpath(destinationPath); - - LogNormal() << "copy:" << sourcePath << destinationPath; - - QStringList files = QDir(sourcePath).entryList(QStringList() << "*", QDir::Files | QDir::NoDotAndDotDot); - foreach (QString file, files) { - const QString fileSourcePath = sourcePath + "/" + file; - const QString fileDestinationPath = destinationPath + "/" + file; - copyFilePrintStatus(fileSourcePath, fileDestinationPath); - } - - QStringList subdirs = QDir(sourcePath).entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot); - foreach (QString dir, subdirs) { - recursiveCopy(sourcePath + "/" + dir, destinationPath + "/" + dir); - } - return true; -} - -void recursiveCopyAndDeploy(const QString &appBundlePath, const QSet &rpaths, const QString &sourcePath, const QString &destinationPath) -{ - QDir().mkpath(destinationPath); - - LogNormal() << "copy:" << sourcePath << destinationPath; - const bool isDwarfPath = sourcePath.endsWith("DWARF"); - - QStringList files = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Files | QDir::NoDotAndDotDot); - foreach (QString file, files) { - const QString fileSourcePath = sourcePath + QLatin1Char('/') + file; - - if (file.endsWith("_debug.dylib")) { - continue; // Skip debug versions - } else if (!isDwarfPath && 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); - } - } - - QStringList subdirs = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Dirs | QDir::NoDotAndDotDot); - foreach (QString dir, subdirs) { - recursiveCopyAndDeploy(appBundlePath, rpaths, sourcePath + QLatin1Char('/') + dir, destinationPath + QLatin1Char('/') + dir); - } -} - -QString copyDylib(const FrameworkInfo &framework, const QString path) -{ - 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/libfoo.dylib - QString dylibDestinationDirectory = path + QLatin1Char('/') + framework.frameworkDestinationDirectory; - QString dylibDestinationBinaryPath = dylibDestinationDirectory + QLatin1Char('/') + framework.binaryName; - - // Create destination directory - if (!QDir().mkpath(dylibDestinationDirectory)) { - LogError() << "could not create destination directory" << dylibDestinationDirectory; - return QString(); - } - - // Retrun if the dylib has aleardy been deployed - if (QFileInfo(dylibDestinationBinaryPath).exists() && !alwaysOwerwriteEnabled) - return dylibDestinationBinaryPath; - - // Copy dylib binary - copyFilePrintStatus(framework.sourceFilePath, dylibDestinationBinaryPath); - return dylibDestinationBinaryPath; -} - -QString copyFramework(const FrameworkInfo &framework, const QString path) -{ - 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(); - } -} - -void changeIdentification(const QString &id, const QString &binaryPath) -{ - LogDebug() << "Using install_name_tool:"; - 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); - // Workaround for the case when the library ID name is a symlink, while the dependencies - // specified using the canonical path to the library (QTBUG-56814) - QString canonicalInstallName = QFileInfo(framework.installName).canonicalFilePath(); - if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) { - changeInstallName(canonicalInstallName, deployedInstallName, binary); - } - } -} - -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); -} - -void runStrip(const QString &binaryPath) -{ - if (runStripEnabled == false) - return; - - LogDebug() << "Using strip:"; - LogDebug() << " stripped" << binaryPath; - QProcess strip; - strip.start("strip", QStringList() << "-x" << binaryPath); - strip.waitForFinished(); - if (strip.exitCode() != 0) { - LogError() << strip.readAllStandardError(); - LogError() << strip.readAllStandardOutput(); - } -} - -void stripAppBinary(const QString &bundlePath) -{ - runStrip(findAppBinary(bundlePath)); -} - -bool DeploymentInfo::containsModule(const QString &module, const QString &libInFix) const -{ - // Check for framework first - if (deployedFrameworks.contains(QLatin1String("Qt") + module + libInFix + - QLatin1String(".framework"))) { - return true; - } - // Check for dylib - const QRegExp dylibRegExp(QLatin1String("libQt[0-9]+") + module + - libInFix + QLatin1String(".[0-9]+.dylib")); - return deployedFrameworks.filter(dylibRegExp).size() > 0; -} - -/* - Deploys the the listed frameworks listed into an app bundle. - The frameworks are searched for dependencies, which are also deployed. - (deploying Qt3Support will also deploy QtNetwork and QtSql for example.) - Returns a DeploymentInfo structure containing the Qt path used and a - a list of actually deployed frameworks. -*/ -DeploymentInfo deployQtFrameworks(QList frameworks, - const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, - bool useLoaderPath) -{ - LogNormal(); - LogNormal() << "Deploying Qt frameworks found inside:" << binaryPaths; - QStringList copiedFrameworks; - DeploymentInfo deploymentInfo; - deploymentInfo.useLoaderPath = useLoaderPath; - deploymentInfo.isFramework = bundlePath.contains(".framework"); - deploymentInfo.isDebug = false; - QSet rpathsUsed; - - while (frameworks.isEmpty() == false) { - const FrameworkInfo framework = frameworks.takeFirst(); - copiedFrameworks.append(framework.frameworkName); - - // If a single dependency has the _debug suffix, we treat that as - // the whole deployment being a debug deployment, including deploying - // the debug version of plugins. - if (framework.isDebugLibrary()) - deploymentInfo.isDebug = true; - - if (deploymentInfo.qtPath.isNull()) - deploymentInfo.qtPath = QLibraryInfo::location(QLibraryInfo::PrefixPath); - - if (framework.frameworkDirectory.startsWith(bundlePath)) { - LogError() << framework.frameworkName << "already deployed, skipping."; - continue; - } - - if (!framework.rpathUsed.isEmpty()) - rpathsUsed << framework.rpathUsed; - - // Copy the framework/dylib to the app bundle. - const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath) - : copyFramework(framework, bundlePath); - - // Install_name_tool the new id into the binaries - changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath); - - // 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); - } - - // Check for framework dependencies - QList dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs); - - foreach (FrameworkInfo dependency, dependencies) { - if (dependency.rpathUsed.isEmpty()) { - changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath); - } else { - rpathsUsed << dependency.rpathUsed; - } - - // Deploy framework if necessary. - if (copiedFrameworks.contains(dependency.frameworkName) == false && frameworks.contains(dependency) == false) { - frameworks.append(dependency); - } - } - } - deploymentInfo.deployedFrameworks = copiedFrameworks; - deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath); - deploymentInfo.rpathsUsed += rpathsUsed; - return deploymentInfo; -} - -DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs) -{ - ApplicationBundleInfo applicationBundle; - applicationBundle.path = appBundlePath; - applicationBundle.binaryPath = findAppBinary(appBundlePath); - applicationBundle.libraryPaths = findAppLibraries(appBundlePath); - QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths - << additionalExecutables; - QSet allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true); - allLibraryPaths.insert(QLibraryInfo::location(QLibraryInfo::LibrariesPath)); - 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() << "If so, you will need to rebuild" << appBundlePath << "before trying again."; - return DeploymentInfo(); - } else { - return deployQtFrameworks(frameworks, applicationBundle.path, allBinaryPaths, useDebugLibs, !additionalExecutables.isEmpty()); - } -} - -QString getLibInfix(const QStringList &deployedFrameworks) -{ - QString libInfix; - foreach (const QString &framework, deployedFrameworks) { - if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework"))) { - Q_ASSERT(framework.length() >= 16); - // 16 == "QtCore" + ".framework" - const int lengthOfLibInfix = framework.length() - 16; - if (lengthOfLibInfix) - libInfix = framework.mid(6, lengthOfLibInfix); - break; - } - } - return libInfix; -} - -void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pluginSourcePath, - const QString pluginDestinationPath, DeploymentInfo deploymentInfo, bool useDebugLibs) -{ - LogNormal() << "Deploying plugins from" << pluginSourcePath; - - if (!pluginSourcePath.contains(deploymentInfo.pluginPath)) - return; - - // Plugin white list: - QStringList pluginList; - - const auto addPlugins = [&pluginSourcePath,&pluginList,useDebugLibs](const QString &subDirectory, - const std::function &predicate = std::function()) { - const QStringList libs = QDir(pluginSourcePath + QLatin1Char('/') + subDirectory) - .entryList({QStringLiteral("*.dylib")}); - for (const QString &lib : libs) { - if (lib.endsWith(QStringLiteral("_debug.dylib")) != useDebugLibs) - continue; - if (!predicate || predicate(lib)) - pluginList.append(subDirectory + QLatin1Char('/') + lib); - } - }; - - // Platform plugin: - addPlugins(QStringLiteral("platforms"), [](const QString &lib) { - // Ignore minimal and offscreen platform plugins - if (!lib.contains(QStringLiteral("cocoa"))) - return false; - return true; - }); - - // Cocoa print support - addPlugins(QStringLiteral("printsupport")); - - // Styles - addPlugins(QStringLiteral("styles")); - - // Check if Qt was configured with -libinfix - const QString libInfix = getLibInfix(deploymentInfo.deployedFrameworks); - - // Network - if (deploymentInfo.containsModule("Network", libInfix)) - addPlugins(QStringLiteral("bearer")); - - // All image formats (svg if QtSvg is used) - const bool usesSvg = deploymentInfo.containsModule("Svg", libInfix); - addPlugins(QStringLiteral("imageformats"), [usesSvg](const QString &lib) { - if (lib.contains(QStringLiteral("qsvg")) && !usesSvg) - return false; - return true; - }); - - addPlugins(QStringLiteral("iconengines")); - - // Platforminputcontext plugins if QtGui is in use - if (deploymentInfo.containsModule("Gui", libInfix)) { - addPlugins(QStringLiteral("platforminputcontexts"), [&addPlugins](const QString &lib) { - // Deploy the virtual keyboard plugins if we have deployed virtualkeyboard - if (lib.startsWith(QStringLiteral("libqtvirtualkeyboard"))) - addPlugins(QStringLiteral("virtualkeyboard")); - return true; - }); - } - - // Sql plugins if QtSql is in use - if (deploymentInfo.containsModule("Sql", libInfix)) { - addPlugins(QStringLiteral("sqldrivers"), [](const QString &lib) { - if (lib.startsWith(QStringLiteral("libqsqlodbc")) || lib.startsWith(QStringLiteral("libqsqlpsql"))) { - LogWarning() << "Plugin" << lib << "uses private API and is not Mac App store compliant."; - if (appstoreCompliant) { - LogWarning() << "Skip plugin" << lib; - return false; - } - } - return true; - }); - } - - // WebView plugins if QtWebView is in use - if (deploymentInfo.containsModule("WebView", libInfix)) { - addPlugins(QStringLiteral("webview"), [](const QString &lib) { - if (lib.startsWith(QStringLiteral("libqtwebview_webengine"))) { - LogWarning() << "Plugin" << lib << "uses QtWebEngine and is not Mac App store compliant."; - if (appstoreCompliant) { - LogWarning() << "Skip plugin" << lib; - return false; - } - } - return true; - }); - } - - static const std::map> map { - {QStringLiteral("Multimedia"), {QStringLiteral("mediaservice"), QStringLiteral("audio")}}, - {QStringLiteral("3DRender"), {QStringLiteral("sceneparsers"), QStringLiteral("geometryloaders")}}, - {QStringLiteral("3DQuickRender"), {QStringLiteral("renderplugins")}}, - {QStringLiteral("Positioning"), {QStringLiteral("position")}}, - {QStringLiteral("Location"), {QStringLiteral("geoservices")}}, - {QStringLiteral("TextToSpeech"), {QStringLiteral("texttospeech")}} - }; - - for (const auto &it : map) { - if (deploymentInfo.containsModule(it.first, libInfix)) { - for (const auto &pluginType : it.second) { - addPlugins(pluginType); - } - } - } - - foreach (const QString &plugin, pluginList) { - QString sourcePath = pluginSourcePath + "/" + plugin; - const QString destinationPath = pluginDestinationPath + "/" + plugin; - QDir dir; - dir.mkpath(QFileInfo(destinationPath).path()); - - if (copyFilePrintStatus(sourcePath, destinationPath)) { - runStrip(destinationPath); - QList frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs); - deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath); - } - } -} - -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"; - - QString filePath = appBundlePath + "/Contents/Resources/"; - QString fileName = filePath + "qt.conf"; - - QDir().mkpath(filePath); - - QFile qtconf(fileName); - if (qtconf.exists() && !alwaysOwerwriteEnabled) { - LogWarning(); - LogWarning() << fileName << "already exists, will not overwrite."; - 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"; - 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"; - } -} - -void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs) -{ - ApplicationBundleInfo applicationBundle; - applicationBundle.path = appBundlePath; - applicationBundle.binaryPath = findAppBinary(appBundlePath); - - const QString pluginDestinationPath = appBundlePath + "/" + "Contents/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; - - // Skip already deployed imports. This can happen in cases like "QtQuick.Controls.Styles", - // where deploying QtQuick.Controls will also deploy the "Styles" sub-import. - if (QDir().exists(importDestinationPath)) - return; - - recursiveCopyAndDeploy(appBundlePath, rpaths, importSourcePath, importDestinationPath); -} - -static bool importLessThan(const QVariant &v1, const QVariant &v2) -{ - QVariantMap import1 = v1.toMap(); - QVariantMap import2 = v2.toMap(); - QString path1 = import1["path"].toString(); - QString path2 = import2["path"].toString(); - return path1 < path2; -} - -// Scan qml files in qmldirs for import statements, deploy used imports from Qml2ImportsPath to Contents/Resources/qml. -bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInfo, QStringList &qmlDirs, QStringList &qmlImportPaths) -{ - LogNormal() << ""; - LogNormal() << "Deploying QML imports "; - LogNormal() << "Application QML file path(s) is" << qmlDirs; - LogNormal() << "QML module search path(s) is" << qmlImportPaths; - - // Use qmlimportscanner from QLibraryInfo::BinariesPath - QString qmlImportScannerPath = QDir::cleanPath(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlimportscanner"); - - // Fallback: Look relative to the macdeployqt binary - if (!QFile(qmlImportScannerPath).exists()) - qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner"; - - // Verify that we found a qmlimportscanner binary - if (!QFile(qmlImportScannerPath).exists()) { - LogError() << "qmlimportscanner not found at" << qmlImportScannerPath; - LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner"; - return false; - } - - // build argument list for qmlimportsanner: "-rootPath foo/ -rootPath bar/ -importPath path/to/qt/qml" - // ("rootPath" points to a directory containing app qml, "importPath" is where the Qt imports are installed) - QStringList argumentList; - foreach (const QString &qmlDir, qmlDirs) { - argumentList.append("-rootPath"); - argumentList.append(qmlDir); - } - for (const QString &importPath : qmlImportPaths) - argumentList << "-importPath" << importPath; - QString qmlImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath); - argumentList.append( "-importPath"); - argumentList.append(qmlImportsPath); - - // run qmlimportscanner - QProcess qmlImportScanner; - qmlImportScanner.start(qmlImportScannerPath, argumentList); - if (!qmlImportScanner.waitForStarted()) { - LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString(); - return false; - } - qmlImportScanner.waitForFinished(); - - // log qmlimportscanner errors - qmlImportScanner.setReadChannel(QProcess::StandardError); - QByteArray errors = qmlImportScanner.readAll(); - if (!errors.isEmpty()) { - LogWarning() << "QML file parse error (deployment will continue):"; - LogWarning() << errors; - } - - // parse qmlimportscanner json - qmlImportScanner.setReadChannel(QProcess::StandardOutput); - QByteArray json = qmlImportScanner.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(json); - if (!doc.isArray()) { - LogError() << "qmlimportscanner output error. Expected json array, got:"; - LogError() << json; - return false; - } - - // sort imports to deploy a module before its sub-modules (otherwise - // deployQmlImports can consider the module deployed if it has already - // deployed one of its sub-module) - QVariantList array = doc.array().toVariantList(); - std::sort(array.begin(), array.end(), importLessThan); - - // deploy each import - foreach (const QVariant &importValue, array) { - QVariantMap import = importValue.toMap(); - QString name = import["name"].toString(); - QString path = import["path"].toString(); - QString type = import["type"].toString(); - - LogNormal() << "Deploying QML import" << name; - - // Skip imports with missing info - path will be empty if the import is not found. - if (name.isEmpty() || path.isEmpty()) { - LogNormal() << " Skip import: name or path is empty"; - LogNormal() << ""; - continue; - } - - // Deploy module imports only, skip directory (local/remote) and js imports. These - // should be deployed as a part of the application build. - if (type != QStringLiteral("module")) { - LogNormal() << " Skip non-module import"; - LogNormal() << ""; - continue; - } - - // Create the destination path from the name - // and version (grabbed from the source path) - // ### let qmlimportscanner provide this. - name.replace(QLatin1Char('.'), QLatin1Char('/')); - int secondTolast = path.length() - 2; - QString version = path.mid(secondTolast); - if (version.startsWith(QLatin1Char('.'))) - name.append(version); - - deployQmlImport(appBundlePath, deploymentInfo.rpathsUsed, path, name); - LogNormal() << ""; - } - return true; -} - -void changeQtFrameworks(const QList frameworks, const QStringList &binaryPaths, const QString &absoluteQtPath) -{ - LogNormal() << "Changing" << binaryPaths << "to link against"; - LogNormal() << "Qt in" << absoluteQtPath; - QString finalQtPath = absoluteQtPath; - - if (!absoluteQtPath.startsWith("/Library/Frameworks")) - finalQtPath += "/lib/"; - - 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); - } -} - -void changeQtFrameworks(const QString appPath, const QString &qtPath, bool useDebugLibs) -{ - const QString appBinaryPath = findAppBinary(appPath); - const QStringList libraryPaths = findAppLibraries(appPath); - const QList frameworks = getQtFrameworksForPaths(QStringList() << appBinaryPath << libraryPaths, appPath, getBinaryRPaths(appBinaryPath, true), useDebugLibs); - if (frameworks.isEmpty()) { - LogWarning(); - LogWarning() << "Could not find any _external_ Qt frameworks to change in" << appPath; - return; - } else { - const QString absoluteQtPath = QDir(qtPath).absolutePath(); - changeQtFrameworks(frameworks, QStringList() << appBinaryPath << libraryPaths, absoluteQtPath); - } -} - -void codesignFile(const QString &identity, const QString &filePath) -{ - if (!runCodesign) - return; - - QString codeSignLogMessage = "codesign"; - if (hardenedRuntime) - codeSignLogMessage += ", enable hardened runtime"; - if (secureTimestamp) - codeSignLogMessage += ", include secure timestamp"; - LogNormal() << codeSignLogMessage << filePath; - - QStringList codeSignOptions = { "--preserve-metadata=identifier,entitlements", "--force", "-s", - identity, filePath }; - if (hardenedRuntime) - codeSignOptions << "-o" << "runtime"; - - if (secureTimestamp) - codeSignOptions << "--timestamp"; - - if (!extraEntitlements.isEmpty()) - codeSignOptions << "--entitlements" << extraEntitlements; - - QProcess codesign; - codesign.start("codesign", codeSignOptions); - 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) { - - // 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(); - QStringList 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); - - QSet dependenciesSet(dependencies.begin(), dependencies.end()); - dependencies = dependenciesSet.subtract(signedBinaries).subtract(pendingBinariesSet).values(); - - 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; - } - } - - // Look for an entitlements file in the bundle to include when signing - extraEntitlements = findEntitlementsFile(appBundleAbsolutePath + "/Contents/Resources/"); - - // 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, const QString &filesystemType) -{ - QString appBaseName = appBundlePath; - appBaseName.chop(4); // remove ".app" from end - - QString dmgName = appBaseName + ".dmg"; - - QFile dmg(dmgName); - - if (dmg.exists() && alwaysOwerwriteEnabled) - dmg.remove(); - - if (dmg.exists()) { - LogNormal() << "Disk image already exists, skipping .dmg creation for" << dmg.fileName(); - } else { - LogNormal() << "Creating disk image (.dmg) for" << appBundlePath; - } - - LogNormal() << "Image will use" << filesystemType; - - // More dmg options can be found in the hdiutil man page. - QStringList options = QStringList() - << "create" << dmgName - << "-srcfolder" << appBundlePath - << "-format" << "UDZO" - << "-fs" << filesystemType - << "-volname" << appBaseName; - - QProcess hdutil; - hdutil.start("hdiutil", options); - hdutil.waitForFinished(-1); - if (hdutil.exitCode() != 0) { - LogError() << "Bundle creation error:" << hdutil.readAllStandardError(); - } -} - -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); -}