Macdeployqt: Deploy QML imports.
authorMorten Johan Sørvig <morten.sorvig@digia.com>
Thu, 15 Nov 2012 06:08:44 +0000 (07:08 +0100)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Mon, 7 Oct 2013 08:43:25 +0000 (10:43 +0200)
Use qmlimportscanner from QtDeclarative to find used
imports.

New option:
-qmldir=<path> : Deploy imports used by .qml files
in the given path. (defaults to ".")

Change-Id: I3efa47557a1b5aae0ad838024ba5fe1a98be3b16
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
Reviewed-by: Gabriel de Dietrich <gabriel.dedietrich@digia.com>
src/macdeployqt/macdeployqt/main.cpp
src/macdeployqt/shared/shared.cpp
src/macdeployqt/shared/shared.h

index 92465ae..811e5d6 100644 (file)
@@ -57,6 +57,7 @@ int main(int argc, char **argv)
         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=<path> : Let the given executable use the deployed frameworks too";
+        qDebug() << "   -qmldir=<path>     : Deploy imports used by .qml files in the given path";
         qDebug() << "";
         qDebug() << "macdeployqt takes an application bundle as input and makes it";
         qDebug() << "self-contained by copying in the Qt frameworks and plugins that";
@@ -85,6 +86,7 @@ int main(int argc, char **argv)
     bool useDebugLibs = false;
     extern bool runStripEnabled;
     QStringList additionalExecutables;
+    QStringList qmlDirs;
 
     for (int i = 2; i < argc; ++i) {
         QByteArray argument = QByteArray(argv[i]);
@@ -112,11 +114,18 @@ int main(int argc, char **argv)
                 logLevel = number;
         } else if (argument.startsWith(QByteArray("-executable"))) {
             LogDebug() << "Argument found:" << argument;
-            int index = argument.indexOf("=");
-            if (index < 0 || index >= argument.size())
+            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;
+            int index = argument.indexOf('=');
+            if (index == -1)
+                LogError() << "Missing qml directory path";
+            else
+                qmlDirs << argument.mid(index+1);
         } else if (argument.startsWith("-")) {
             LogError() << "Unknown argument" << argument << "\n";
             return 0;
@@ -136,6 +145,17 @@ int main(int argc, char **argv)
         createQtConf(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())
+        deployQmlImports(appBundlePath, qmlDirs);
+
     if (dmg) {
         LogNormal();
         createDiskImage(appBundlePath);
index af6de86..9ae9e10 100644 (file)
 #include <QRegExp>
 #include <QSet>
 #include <QDirIterator>
+#include <QLibraryInfo>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonValue>
 #include "shared.h"
 
 bool runStripEnabled = true;
@@ -90,7 +95,10 @@ inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
 
 bool copyFilePrintStatus(const QString &from, const QString &to)
 {
-    if (QFile::copy(from, to)) {
+    if (QFile(to).exists()) {
+        LogNormal() << "File exists, skip copy:" << to;
+        return false;
+    } else if (QFile::copy(from, to)) {
         LogNormal() << " copied:" << from;
         LogNormal() << " to" << to;
         return true;
@@ -279,6 +287,8 @@ void recursiveCopy(const QString &sourcePath, const QString &destinationPath)
 {
     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;
@@ -292,6 +302,39 @@ void recursiveCopy(const QString &sourcePath, const QString &destinationPath)
     }
 }
 
+void recursiveCopyAndDeploy(const QString &appBundlePath, const QString &sourcePath, const QString &destinationPath)
+{
+    QDir().mkpath(destinationPath);
+
+    LogNormal() << "copy:" << sourcePath << destinationPath;
+
+    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"))) {
+            if (copyFilePrintStatus(fileSourcePath, fileDestinationPath)) {
+                runStrip(fileDestinationPath);
+                bool useDebugLibs = false;
+                bool useLoaderPath = false;
+                QList<FrameworkInfo> frameworks = getQtFrameworks(fileDestinationPath, useDebugLibs);
+                deployQtFrameworks(frameworks, appBundlePath, QStringList(fileDestinationPath), useDebugLibs, useLoaderPath);
+            }
+        } else {
+            copyFilePrintStatus(fileSourcePath, fileDestinationPath);
+        }
+    }
+
+    QStringList subdirs = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Dirs | QDir::NoDotAndDotDot);
+    foreach (QString dir, subdirs) {
+        recursiveCopyAndDeploy(appBundlePath, sourcePath + QLatin1Char('/') + dir, destinationPath + QLatin1Char('/') + dir);
+    }
+}
+
+
 QString copyFramework(const FrameworkInfo &framework, const QString path)
 {
     QString from = framework.sourceFilePath;
@@ -304,7 +347,7 @@ QString copyFramework(const FrameworkInfo &framework, const QString path)
     QFileInfo fromDirInfo(framework.frameworkPath + QLatin1Char('/')
                       + framework.binaryDirectory);
     bool fromDirIsSymLink = fromDirInfo.isSymLink();
-    QString unresolvedToDir = path + "/" + framework.destinationDirectory;
+    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) {
@@ -560,7 +603,12 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
 
 void createQtConf(const QString &appBundlePath)
 {
-    QByteArray contents = "[Paths]\nPlugins = PlugIns\n";
+    // 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";
 
@@ -594,6 +642,65 @@ void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo,
     deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
 }
 
+void deployQmlImport(const QString &appBundlePath, const QString &importSourcePath, const QString &importName)
+{
+    QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName;
+    recursiveCopyAndDeploy(appBundlePath, importSourcePath, importDestinationPath);
+}
+
+// Scan qml files in qmldirs for import statements, deploy used imports from Qml2ImportsPath to Contents/Resources/qml.
+void deployQmlImports(const QString &appBundlePath, QStringList &qmlDirs)
+{
+    // verify that qmlimportscanner is in BinariesPath
+    QString qmlImportScannerPath = QDir::cleanPath(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlimportscanner");
+    if (!QFile(qmlImportScannerPath).exists()) {
+        LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
+        LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
+        return;
+    }
+
+    // run qmlimportscanner
+    QString qmlImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
+    QProcess qmlImportScanner;
+    qmlImportScanner.setProcessChannelMode(QProcess::MergedChannels);
+    qmlImportScanner.start(qmlImportScannerPath, QStringList() << qmlDirs << "-importPath" << qmlImportsPath);
+    if (!qmlImportScanner.waitForStarted()) {
+        LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
+        return;
+    }
+
+    qmlImportScanner.waitForFinished();
+    QByteArray json = qmlImportScanner.readAll();
+
+    // parse qmlimportscanner json
+    QJsonDocument doc = QJsonDocument::fromJson(json);
+    if (!doc.isArray()) {
+        LogError() << "qmlimportscanner output error. Expected json array, got:";
+        LogError() << json;
+        return;
+    }
+
+    // deploy each import
+    foreach (const QJsonValue &importValue, doc.array()) {
+        if (!importValue.isObject())
+            continue;
+
+        QJsonObject import = importValue.toObject();
+        QString name = import["name"].toString();
+        QString path = import["path"].toString();
+
+        // 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, path, name);
+    }
+}
 
 void changeQtFrameworks(const QList<FrameworkInfo> frameworks, const QStringList &binaryPaths, const QString &absoluteQtPath)
 {
index 788ac7d..8c6ea0b 100644 (file)
@@ -101,12 +101,15 @@ QList<FrameworkInfo> getQtFrameworks(const QString &path, bool useDebugLibs);
 QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, bool useDebugLibs);
 QString copyFramework(const FrameworkInfo &framework, const QString path);
 DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs);
-DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks, const QString &bundlePath, const QString &binaryPath);
+DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath);
 void createQtConf(const QString &appBundlePath);
 void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo, bool useDebugLibs);
+void deployQmlImports(const QString &appBundlePath, QStringList &qmlDirs);
 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 createDiskImage(const QString &appBundlePath);
 
+
 #endif