diff --git a/src/assistant/help/qhelpcontentwidget.cpp b/src/assistant/help/qhelpcontentwidget.cpp index aa06e29d84f4667d59b253430e652b6f85ee13a8..8f38bcfd3de322f82852543a2f3289056a459400 100644 --- a/src/assistant/help/qhelpcontentwidget.cpp +++ b/src/assistant/help/qhelpcontentwidget.cpp @@ -65,6 +65,7 @@ public: class QHelpContentProvider : public QThread { + Q_OBJECT public: QHelpContentProvider(QHelpEnginePrivate *helpEngine); ~QHelpContentProvider(); @@ -73,11 +74,13 @@ public: QHelpContentItem *rootItem(); int nextChildCount() const; +signals: + void finishedSuccessFully(); + private: void run(); QHelpEnginePrivate *m_helpEngine; - QHelpContentItem *m_rootItem; QStringList m_filterAttributes; QQueue<QHelpContentItem*> m_rootItems; QMutex m_mutex; @@ -188,7 +191,6 @@ QHelpContentProvider::QHelpContentProvider(QHelpEnginePrivate *helpEngine) : QThread(helpEngine) { m_helpEngine = helpEngine; - m_rootItem = 0; m_abort = false; } @@ -212,22 +214,28 @@ void QHelpContentProvider::collectContents(const QString &customFilterName) void QHelpContentProvider::stopCollecting() { - if (!isRunning()) - return; - m_mutex.lock(); - m_abort = true; - m_mutex.unlock(); - wait(); + if (isRunning()) { + m_mutex.lock(); + m_abort = true; + m_mutex.unlock(); + wait(); + } + qDeleteAll(m_rootItems); + m_rootItems.clear(); } QHelpContentItem *QHelpContentProvider::rootItem() { QMutexLocker locker(&m_mutex); + if (m_rootItems.isEmpty()) + return 0; return m_rootItems.dequeue(); } int QHelpContentProvider::nextChildCount() const { + if (m_rootItems.isEmpty()) + return 0; return m_rootItems.head()->childCount(); } @@ -239,8 +247,7 @@ void QHelpContentProvider::run() QHelpContentItem *item = 0; m_mutex.lock(); - m_rootItem = new QHelpContentItem(QString(), QString(), 0); - m_rootItems.enqueue(m_rootItem); + QHelpContentItem * const rootItem = new QHelpContentItem(QString(), QString(), 0); QStringList atts = m_filterAttributes; const QStringList fileNames = m_helpEngine->orderedFileNameList; m_mutex.unlock(); @@ -248,9 +255,10 @@ void QHelpContentProvider::run() foreach (const QString &dbFileName, fileNames) { m_mutex.lock(); if (m_abort) { + delete rootItem; m_abort = false; m_mutex.unlock(); - break; + return; } m_mutex.unlock(); QHelpDBReader reader(dbFileName, @@ -278,8 +286,8 @@ CHECK_DEPTH: if (depth == 0) { m_mutex.lock(); item = new QHelpContentItem(title, link, - m_helpEngine->fileNameReaderMap.value(dbFileName), m_rootItem); - m_rootItem->appendChild(item); + m_helpEngine->fileNameReaderMap.value(dbFileName), rootItem); + rootItem->appendChild(item); m_mutex.unlock(); stack.push(item); _depth = 1; @@ -303,8 +311,10 @@ CHECK_DEPTH: } } m_mutex.lock(); + m_rootItems.enqueue(rootItem); m_abort = false; m_mutex.unlock(); + emit finishedSuccessFully(); } @@ -339,9 +349,9 @@ QHelpContentModel::QHelpContentModel(QHelpEnginePrivate *helpEngine) d->rootItem = 0; d->qhelpContentProvider = new QHelpContentProvider(helpEngine); - connect(d->qhelpContentProvider, SIGNAL(finished()), + connect(d->qhelpContentProvider, SIGNAL(finishedSuccessFully()), this, SLOT(insertContents()), Qt::QueuedConnection); - connect(helpEngine->q, SIGNAL(setupStarted()), this, SLOT(invalidateContents())); + connect(helpEngine->q, SIGNAL(readersAboutToBeInvalidated()), this, SLOT(invalidateContents())); } /*! @@ -381,6 +391,9 @@ void QHelpContentModel::createContents(const QString &customFilterName) void QHelpContentModel::insertContents() { + QHelpContentItem * const newRootItem = d->qhelpContentProvider->rootItem(); + if (!newRootItem) + return; int count; if (d->rootItem) { count = d->rootItem->childCount() - 1; @@ -392,7 +405,7 @@ void QHelpContentModel::insertContents() count = d->qhelpContentProvider->nextChildCount() - 1; beginInsertRows(QModelIndex(), 0, count > 0 ? count : 0); - d->rootItem = d->qhelpContentProvider->rootItem(); + d->rootItem = newRootItem; endInsertRows(); emit contentsCreated(); } @@ -572,3 +585,5 @@ void QHelpContentWidget::showLink(const QModelIndex &index) } QT_END_NAMESPACE + +#include "qhelpcontentwidget.moc" diff --git a/src/assistant/help/qhelpenginecore.cpp b/src/assistant/help/qhelpenginecore.cpp index 00b55d078c7b4a3399854e8422b1268fa9132809..f502e1df458b630ca69c01e6d8caa872d993ec16 100644 --- a/src/assistant/help/qhelpenginecore.cpp +++ b/src/assistant/help/qhelpenginecore.cpp @@ -71,6 +71,7 @@ QHelpEngineCorePrivate::~QHelpEngineCorePrivate() void QHelpEngineCorePrivate::clearMaps() { + emit q->readersAboutToBeInvalidated(); QMap<QString, QHelpDBReader*>::iterator it = readerMap.begin(); while (it != readerMap.end()) { delete it.value(); diff --git a/src/assistant/help/qhelpenginecore.h b/src/assistant/help/qhelpenginecore.h index bab84ee4ed0e73d437d63f1e469fef71f0c4c93f..8579165ff99fedb8804782e57a904f0ba8011424 100644 --- a/src/assistant/help/qhelpenginecore.h +++ b/src/assistant/help/qhelpenginecore.h @@ -108,6 +108,7 @@ Q_SIGNALS: void setupFinished(); void currentFilterChanged(const QString &newFilter); void warning(const QString &msg); + void readersAboutToBeInvalidated(); protected: QHelpEngineCore(QHelpEngineCorePrivate *helpEngineCorePrivate, diff --git a/src/assistant/help/qhelpindexwidget.cpp b/src/assistant/help/qhelpindexwidget.cpp index 7da09de53438d981961e32d1cecd416706a6caf5..33e83101770b22782c902043901cdfcad21d2035 100644 --- a/src/assistant/help/qhelpindexwidget.cpp +++ b/src/assistant/help/qhelpindexwidget.cpp @@ -224,7 +224,7 @@ QHelpIndexModel::QHelpIndexModel(QHelpEnginePrivate *helpEngine) d = new QHelpIndexModelPrivate(helpEngine); connect(d->indexProvider, SIGNAL(finished()), this, SLOT(insertIndices())); - connect(helpEngine->q, SIGNAL(setupStarted()), this, SLOT(invalidateIndex())); + connect(helpEngine->q, SIGNAL(readersAboutToBeInvalidated()), this, SLOT(invalidateIndex())); } QHelpIndexModel::~QHelpIndexModel() diff --git a/src/macdeployqt/macdeployqt/main.cpp b/src/macdeployqt/macdeployqt/main.cpp index 865a22f03c238f54f634907330af9ee6c341f523..0f38cdfaf9579bc21c01396caaee84e17a8a56ba 100644 --- a/src/macdeployqt/macdeployqt/main.cpp +++ b/src/macdeployqt/macdeployqt/main.cpp @@ -51,6 +51,7 @@ int main(int argc, char **argv) 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"; @@ -81,6 +82,8 @@ int main(int argc, char **argv) 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]); @@ -123,6 +126,15 @@ 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.startsWith("-")) { LogError() << "Unknown argument" << argument << "\n"; return 0; @@ -149,6 +161,9 @@ int main(int argc, char **argv) if (!qmlDirs.isEmpty()) deployQmlImports(appBundlePath, qmlDirs); + if (runCodesign) + codesign(codesignIdentiy, appBundlePath); + if (dmg) { LogNormal(); createDiskImage(appBundlePath); diff --git a/src/macdeployqt/shared/shared.cpp b/src/macdeployqt/shared/shared.cpp index 314bc2cc5757751871ee45a70f747c34c888552f..a8716d753d6a36a7b92eee8430c7904fbd2e2ece 100644 --- a/src/macdeployqt/shared/shared.cpp +++ b/src/macdeployqt/shared/shared.cpp @@ -38,6 +38,7 @@ #include <QDir> #include <QRegExp> #include <QSet> +#include <QStack> #include <QDirIterator> #include <QLibraryInfo> #include <QJsonDocument> @@ -48,6 +49,8 @@ bool runStripEnabled = true; bool alwaysOwerwriteEnabled = false; +bool runCodesign = false; +QString codesignIdentiy; int logLevel = 1; using std::cout; @@ -70,7 +73,8 @@ QDebug operator<<(QDebug debug, const FrameworkInfo &info) debug << "Install name" << info.installName << "\n"; debug << "Deployed install name" << info.deployedInstallName << "\n"; debug << "Source file Path" << info.sourceFilePath << "\n"; - debug << "Destination Directory (relative to bundle)" << info.destinationDirectory << "\n"; + debug << "Framework Destination Directory (relative to bundle)" << info.frameworkDestinationDirectory << "\n"; + debug << "Binary Destination Directory (relative to bundle)" << info.binaryDestinationDirectory << "\n"; return debug; } @@ -123,6 +127,38 @@ bool copyFilePrintStatus(const QString &from, const QString &to) } } +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); +} + FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs) { FrameworkInfo info; @@ -182,19 +218,22 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs) // remove ".framework" name = currentPart; name.chop(QString(".framework").length()); + info.isDylib = false; info.frameworkName = currentPart; state = Version; ++part; continue; } if (state == DylibName) { name = currentPart.split(" (compatibility").at(0); + info.isDylib = true; info.frameworkName = name; info.binaryName = name.left(name.indexOf('.')) + suffix + name.mid(name.indexOf('.')); info.installName += name; info.deployedInstallName = "@executable_path/../Frameworks/" + info.binaryName; info.frameworkPath = info.frameworkDirectory + info.binaryName; info.sourceFilePath = info.frameworkPath; - info.destinationDirectory = bundleFrameworkDirectory + "/"; + info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/"; + info.binaryDestinationDirectory = info.frameworkDestinationDirectory; info.binaryDirectory = info.frameworkDirectory; info.binaryPath = info.frameworkPath; state = End; @@ -209,7 +248,8 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, bool useDebugLibs) info.deployedInstallName = "@executable_path/../Frameworks/" + info.frameworkName + info.binaryPath; info.frameworkPath = info.frameworkDirectory + info.frameworkName; info.sourceFilePath = info.frameworkPath + info.binaryPath; - info.destinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName + "/" + info.binaryDirectory; + info.frameworkDestinationDirectory = bundleFrameworkDirectory + "/" + info.frameworkName; + info.binaryDestinationDirectory = info.frameworkDestinationDirectory + "/" + info.binaryDirectory; state = End; } else if (state == End) { break; @@ -244,6 +284,22 @@ QStringList findAppLibraries(const QString &appBundlePath) 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) { @@ -257,7 +313,7 @@ QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, bool useDebu } } return libraries; -} +}; QList<FrameworkInfo> getQtFrameworks(const QString &path, bool useDebugLibs) { @@ -295,6 +351,35 @@ QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, bool useD 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) { @@ -324,12 +409,37 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QString &sourceP QStringList files = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Files | QDir::NoDotAndDotDot); foreach (QString file, files) { const QString fileSourcePath = sourcePath + QLatin1Char('/') + file; - const QString fileDestinationPath = destinationPath + QLatin1Char('/') + file; 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("/")); + 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; @@ -337,6 +447,7 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QString &sourceP deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath); } } else { + QString fileDestinationPath = destinationPath + QLatin1Char('/') + file; copyFilePrintStatus(fileSourcePath, fileDestinationPath); } } @@ -347,56 +458,87 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QString &sourceP } } - -QString copyFramework(const FrameworkInfo &framework, const QString path) +QString copyDylib(const FrameworkInfo &framework, const QString path) { - QString from = framework.sourceFilePath; - - if (!QFile::exists(from)) { - LogError() << "no file at" << from; + if (!QFile::exists(framework.sourceFilePath)) { + LogError() << "no file at" << framework.sourceFilePath; return QString(); } - QFileInfo fromDirInfo(framework.frameworkPath + QLatin1Char('/') - + framework.binaryDirectory); - bool fromDirIsSymLink = fromDirInfo.isSymLink(); - QString unresolvedToDir = path + QLatin1Char('/') + framework.destinationDirectory; - QString resolvedToDir; - QString relativeLinkTarget; // will contain the link from Current to e.g. 4 in the Versions directory - if (fromDirIsSymLink) { - // handle the case where framework is referenced with Versions/Current - // which is a symbolic link, so copy to target and recreate as symbolic link - relativeLinkTarget = QDir(fromDirInfo.canonicalPath()) - .relativeFilePath(QFileInfo(fromDirInfo.symLinkTarget()).canonicalFilePath()); - resolvedToDir = QFileInfo(unresolvedToDir).path() + QLatin1Char('/') + relativeLinkTarget; - } else { - resolvedToDir = unresolvedToDir; + // 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(); } - QString to = resolvedToDir + "/" + framework.binaryName; + // Retrun if the dylib has aleardy been deployed + if (QFileInfo(dylibDestinationBinaryPath).exists() && !alwaysOwerwriteEnabled) + return dylibDestinationBinaryPath; + + // Copy dylib binary + copyFilePrintStatus(framework.sourceFilePath, dylibDestinationBinaryPath); + return dylibDestinationBinaryPath; +} - // create the (non-symlink) dir - QDir dir; - if (!dir.mkpath(resolvedToDir)) { - LogError() << "could not create destination directory" << to; +QString copyFramework(const FrameworkInfo &framework, const QString path) +{ + if (!QFile::exists(framework.sourceFilePath)) { + LogError() << "no file at" << framework.sourceFilePath; return QString(); } - if (!QFile::exists(to) || alwaysOwerwriteEnabled) { // copy the binary and resources if that wasn't done before - copyFilePrintStatus(from, to); + // 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; - const QString resourcesSourcePath = framework.frameworkPath + "/Resources"; - const QString resourcesDestianationPath = path + "/Contents/Frameworks/" + framework.frameworkName + "/Resources"; - recursiveCopy(resourcesSourcePath, resourcesDestianationPath); + // 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(); } - // create the Versions/Current symlink dir if necessary - if (fromDirIsSymLink) { - QFile::link(relativeLinkTarget, unresolvedToDir); - LogNormal() << " linked:" << unresolvedToDir; - LogNormal() << " to" << resolvedToDir << "(" << relativeLinkTarget << ")"; + // 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/ + const QString resourcesSourcePath = framework.frameworkPath + "/Resources"; + const QString resourcesDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Resources"; + recursiveCopy(resourcesSourcePath, resourcesDestianationPath); + + // 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"); + 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 to; + return frameworkDestinationBinaryPath; } void runInstallNameTool(QStringList options) @@ -425,7 +567,7 @@ void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework QString deployedInstallName; if (useLoaderPath) { deployedInstallName = QLatin1String("@loader_path/") - + QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + QLatin1Char('/') + framework.destinationDirectory + QLatin1Char('/') + framework.binaryName); + + QFileInfo(binary).absoluteDir().relativeFilePath(absBundlePath + QLatin1Char('/') + framework.binaryDestinationDirectory + QLatin1Char('/') + framework.binaryName); } else { deployedInstallName = framework.deployedInstallName; } @@ -495,8 +637,9 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks, // Install_name_tool the new id into the binaries changeInstallName(bundlePath, framework, binaryPaths, useLoaderPath); - // Copy farmework to app bundle. - const QString deployedBinaryPath = copyFramework(framework, bundlePath); + // Copy the framework/dylib to the app bundle. + const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath) + : copyFramework(framework, bundlePath); // Skip the rest if already was deployed. if (deployedBinaryPath.isNull()) continue; @@ -505,6 +648,8 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks, // Install_name_tool it a new id. changeIdentification(framework.deployedInstallName, deployedBinaryPath); + + // Check for framework dependencies QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, useDebugLibs); @@ -617,8 +762,10 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl if (copyFilePrintStatus(sourcePath, destinationPath)) { runStrip(destinationPath); + QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, useDebugLibs); deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath); + } } } @@ -667,6 +814,12 @@ void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, void deployQmlImport(const QString &appBundlePath, 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, importSourcePath, importDestinationPath); } @@ -796,6 +949,85 @@ 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,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) { diff --git a/src/macdeployqt/shared/shared.h b/src/macdeployqt/shared/shared.h index 9a8ff1786e8ccdc3fc2deea54228c7a52c68563c..07e4914c5d5a171a6d4b4e73f8504e8228369da8 100644 --- a/src/macdeployqt/shared/shared.h +++ b/src/macdeployqt/shared/shared.h @@ -48,6 +48,7 @@ extern bool runStripEnabled; class FrameworkInfo { public: + bool isDylib; QString frameworkDirectory; QString frameworkName; QString frameworkPath; @@ -58,7 +59,8 @@ public: QString installName; QString deployedInstallName; QString sourceFilePath; - QString destinationDirectory; + QString frameworkDestinationDirectory; + QString binaryDestinationDirectory; }; bool operator==(const FrameworkInfo &a, const FrameworkInfo &b); @@ -101,6 +103,8 @@ void changeIdentification(const QString &id, const QString &binaryPath); void changeInstallName(const QString &oldName, const QString &newName, const QString &binaryPath); void runStrip(const QString &binaryPath); QString findAppBinary(const QString &appBundlePath); +void codesignFile(const QString &identity, const QString &filePath); +void codesign(const QString &identity, const QString &appBundlePath); void createDiskImage(const QString &appBundlePath); diff --git a/src/qconfig/main.cpp b/src/qconfig/main.cpp index 7d0d8e8f6e733a4113f3a0b5669e10a696598b21..db6b0a673e9206130c265050f6b3222d5899db9c 100644 --- a/src/qconfig/main.cpp +++ b/src/qconfig/main.cpp @@ -511,7 +511,7 @@ void Main::about() "<p><b><font size=\"+2\">Qtopia Core build configuration</font></b></p>" "<p></p>" "<p>Version 2.0</p>" - "<p>Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).</p>" + "<p>Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).</p>" "<p></p>" ); }