qDebug() << " -executable=<path> : Let the given executable use the deployed frameworks too";
qDebug() << " -qmldir=<path> : Deploy imports used by .qml files in the given path";
qDebug() << " -always-overwrite : Copy files enven if the target file exists";
+ qDebug() << " -codesign=<ident> : Run codesing with the given identity on all executables";
qDebug() << "";
qDebug() << "macdeployqt takes an application bundle as input and makes it";
qDebug() << "self-contained by copying in the Qt frameworks and plugins that";
extern bool alwaysOwerwriteEnabled;
QStringList additionalExecutables;
QStringList qmlDirs;
+ extern bool runCodesign;
+ extern QString codesignIdentiy;
for (int i = 2; i < argc; ++i) {
QByteArray argument = QByteArray(argv[i]);
} 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("-")) {
LogError() << "Unknown argument" << argument << "\n";
return 0;
if (!qmlDirs.isEmpty())
deployQmlImports(appBundlePath, qmlDirs);
+ if (runCodesign)
+ codesign(codesignIdentiy, appBundlePath);
+
if (dmg) {
LogNormal();
createDiskImage(appBundlePath);
#include <QDir>
#include <QRegExp>
#include <QSet>
+#include <QStack>
#include <QDirIterator>
#include <QLibraryInfo>
#include <QJsonDocument>
bool runStripEnabled = true;
bool alwaysOwerwriteEnabled = false;
+bool runCodesign = false;
+QString codesignIdentiy;
int logLevel = 1;
using std::cout;
return result;
}
+QStringList findAppBundleFiles(const QString &appBundlePath)
+{
+ QStringList result;
+
+ QDirIterator iter(appBundlePath, QStringList() << QString::fromLatin1("*"),
+ QDir::Files, QDirIterator::Subdirectories);
+
+ while (iter.hasNext()) {
+ iter.next();
+ if (iter.fileInfo().isSymLink())
+ continue;
+ result << iter.fileInfo().filePath();
+ }
+
+ return result;
+}
QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, bool useDebugLibs)
{
}
}
return libraries;
-}
+};
QList<FrameworkInfo> getQtFrameworks(const QString &path, bool useDebugLibs)
{
return result;
}
+QStringList getBinaryDependencies(const QString executablePath, const QString &path)
+{
+ QStringList binaries;
+
+ QProcess otool;
+ otool.start("otool", QStringList() << "-L" << path);
+ otool.waitForFinished();
+
+ if (otool.exitCode() != 0) {
+ LogError() << otool.readAllStandardError();
+ }
+
+ QString output = otool.readAllStandardOutput();
+ QStringList outputLines = output.split("\n");
+ outputLines.removeFirst(); // remove line containing the binary path
+
+ // return bundle-local dependencies. (those starting with @executable_path)
+ foreach (const QString &line, outputLines) {
+ QString trimmedLine = line.mid(0, line.indexOf("(")).trimmed(); // remove "(compatibility version ...)" and whitespace
+ if (trimmedLine.startsWith("@executable_path/")) {
+ QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length()));
+ if (binary != path)
+ binaries.append(binary);
+ }
+ }
+
+ return binaries;
+}
+
// copies everything _inside_ sourcePath to destinationPath
void recursiveCopy(const QString &sourcePath, const QString &destinationPath)
{
// Install_name_tool it a new id.
changeIdentification(framework.deployedInstallName, deployedBinaryPath);
+
+
// Check for framework dependencies
QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, useDebugLibs);
if (copyFilePrintStatus(sourcePath, destinationPath)) {
runStrip(destinationPath);
+
QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, useDebugLibs);
deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
+
}
}
}
}
}
+void codesignFile(const QString &identity, const QString &filePath)
+{
+ if (!runCodesign)
+ return;
+
+ LogNormal() << "codesign" << filePath;
+
+ QProcess codesign;
+ codesign.start("codesign", QStringList() << "--preserve-metadata=identifier,entitlements,resource-rules"
+ << "--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;
+ }
+}
+
+void codesign(const QString &identity, const QString &appBundlePath)
+{
+ // Code sign all binaries in the app bundle. This needs to
+ // be done inside-out, e.g sign framework dependencies
+ // before the main app binary. The codesign tool itself has
+ // a "--deep" option to do this, but usage when signing is
+ // not recommended: "Signing with --deep is for emergency
+ // repairs and temporary adjustments only."
+
+ LogNormal() << "";
+ LogNormal() << "Signing" << appBundlePath << "with identity" << identity;
+
+ QStack<QString> pendingBinaries;
+ QSet<QString> signedBinaries;
+
+ // Create the root code-binary set. This set consists of the application
+ // executable(s) and the plugins.
+ QString rootBinariesPath = appBundlePath + "/Contents/MacOS/";
+ QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() << "*", QDir::Files);
+ foreach (const QString &binary, foundRootBinaries)
+ pendingBinaries.push(rootBinariesPath + binary);
+
+ QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath + "/Contents/PlugIns/");
+ foreach (const QString &binary, foundPluginBinaries)
+ pendingBinaries.push(binary);
+
+
+ // Sign all binares; 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).toSet().subtract(signedBinaries).toList();
+ if (!dependencies.isEmpty()) {
+ pendingBinaries.push(binary);
+ foreach (const QString &dependency, dependencies)
+ pendingBinaries.push(dependency);
+ continue;
+ }
+ // All dependencies are signed, now sign this binary
+ codesignFile(identity, binary);
+ signedBinaries.insert(binary);
+ }
+
+ // 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;
+ }
+}
void createDiskImage(const QString &appBundlePath)
{