Use qmlimportscanner in windeployqt.
authorFriedemann Kleint <Friedemann.Kleint@digia.com>
Fri, 25 Oct 2013 14:35:17 +0000 (16:35 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Tue, 26 Nov 2013 18:27:01 +0000 (19:27 +0100)
Run qmlimportscanner to find the QML modules and scan their
plugins for additional dependencies.

Change-Id: Ia35e71441c4b97be0561b42fee21e8e6e961d1e5
Reviewed-by: Andrew Knight <andrew.knight@digia.com>
Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
src/windeployqt/main.cpp
src/windeployqt/qmlutils.cpp [new file with mode: 0644]
src/windeployqt/qmlutils.h [new file with mode: 0644]
src/windeployqt/utils.cpp
src/windeployqt/utils.h
src/windeployqt/windeployqt.pro

index b790f25..2ce6fcd 100644 (file)
@@ -40,6 +40,7 @@
 ****************************************************************************/
 
 #include "utils.h"
+#include "qmlutils.h"
 
 #include <QtCore/QDir>
 #include <QtCore/QFileInfo>
@@ -191,6 +192,7 @@ struct Options {
     unsigned additionalLibraries;
     unsigned disabledLibraries;
     unsigned updateFileFlags;
+    QString qmlDirectory; // Project's QML files.
     QString directory;
     QString libraryDirectory;
     QString binary;
@@ -249,6 +251,11 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse
                                        QStringLiteral("Skip library deployment."));
     parser->addOption(noLibraryOption);
 
+    QCommandLineOption qmlDirOption(QStringLiteral("qmldir"),
+                                    QStringLiteral("Scan for QML-imports starting from directory."),
+                                    QStringLiteral("directory"));
+    parser->addOption(qmlDirOption);
+
     QCommandLineOption noQuickImportOption(QStringLiteral("no-quick-import"),
                                            QStringLiteral("Skip deployment of Qt Quick imports."));
     parser->addOption(noQuickImportOption);
@@ -348,6 +355,9 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse
     if (parser->isSet(dirOption))
         options->directory = parser->value(dirOption);
 
+    if (parser->isSet(qmlDirOption))
+        options->qmlDirectory = parser->value(qmlDirOption);
+
     const QString &file = posArgs.front();
     const QFileInfo fi(QDir::cleanPath(file));
     if (!fi.exists()) {
@@ -653,9 +663,20 @@ static DeployResult deploy(const Options &options,
     if (!findDependentQtLibraries(libraryLocation, options.binary, options.platform, errorMessage, &dependentQtLibs, &wordSize, &isDebug, &directDependencyCount))
         return result;
 
+    // Determine application type, check Quick2 is used by looking at the
+    // direct dependencies (do not be fooled by QtWebKit depending on it).
+    for (int m = 0; m < directDependencyCount; ++m)
+        result.directlyUsedQtLibraries |= qtModule(dependentQtLibs.at(m));
+    const bool usesQml2 = !(options.disabledLibraries & QtQmlModule)
+                            && ((result.directlyUsedQtLibraries & QtQmlModule)
+                                || (options.additionalLibraries & QtQmlModule));
+
     if (optVerboseLevel) {
-        std::printf("%s: %ubit, %s executable.\n", qPrintable(QDir::toNativeSeparators(options.binary)),
+        std::printf("%s: %ubit, %s executable", qPrintable(QDir::toNativeSeparators(options.binary)),
                     wordSize, isDebug ? "debug" : "release");
+        if (usesQml2)
+            std::fputs("[QML]", stdout);
+        std::fputc('\n', stdout);
     }
 
     if (dependentQtLibs.isEmpty()) {
@@ -692,19 +713,42 @@ static DeployResult deploy(const Options &options,
         } // Qt5Core
     } // Windows
 
+    // Scan Quick2 imports
+    QmlImportScanResult qmlScanResult;
+    if (options.quickImports && usesQml2) {
+        const QString qmlDirectory = options.qmlDirectory.isEmpty() ? findQmlDirectory(options.platform, options.directory) : options.qmlDirectory;
+        if (!qmlDirectory.isEmpty()) {
+            qmlScanResult = runQmlImportScanner(qmlDirectory, qmakeVariables.value(QStringLiteral("QT_INSTALL_QML")), options.platform, isDebug, errorMessage);
+            if (!qmlScanResult.ok)
+                return result;
+            // Additional dependencies of QML plugins.
+            foreach (const QString &plugin, qmlScanResult.plugins) {
+                if (!findDependentQtLibraries(libraryLocation, plugin, options.platform, errorMessage, &dependentQtLibs, &wordSize, &isDebug))
+                    return result;
+            }
+            if (optVerboseLevel >= 1) {
+                std::fputs("QML imports:\n", stdout);
+                foreach (const QString &mod, qmlScanResult.modulesDirectories)
+                    std::printf("  %s\n",  qPrintable(QDir::toNativeSeparators(mod)));
+                if (optVerboseLevel >= 2) {
+                std::fputs("QML plugins:\n", stdout);
+                foreach (const QString &p, qmlScanResult.plugins)
+                    std::printf("  %s\n",  qPrintable(QDir::toNativeSeparators(p)));
+                }
+            }
+        }
+    }
+
     // Find the plugins and check whether ANGLE, D3D are required on the platform plugin.
     QString platformPlugin;
     // Sort apart Qt 5 libraries in the ones that are represented by the
     // QtModule enumeration (and thus controlled by flags) and others.
     QStringList deployedQtLibraries;
     for (int i = 0 ; i < dependentQtLibs.size(); ++i)  {
-        if (const unsigned qtm = qtModule(dependentQtLibs.at(i))) {
+        if (const unsigned qtm = qtModule(dependentQtLibs.at(i)))
             result.usedQtLibraries |= qtm;
-            if (i < directDependencyCount)
-                result.directlyUsedQtLibraries |= qtm;
-        } else {
+        else
             deployedQtLibraries.push_back(dependentQtLibs.at(i)); // Not represented by flag.
-        }
     }
     result.deployedQtLibraries = (result.usedQtLibraries | options.additionalLibraries) & ~options.disabledLibraries;
     // Apply options flags and re-add library names.
@@ -786,23 +830,11 @@ static DeployResult deploy(const Options &options,
     const bool usesQuick1 = result.deployedQtLibraries & QtDeclarativeModule;
     // Do not be fooled by QtWebKit.dll depending on Quick into always installing Quick imports
     // for WebKit1-applications. Check direct dependency only.
-    const bool usesQuick2 = (result.directlyUsedQtLibraries & QtQuickModule)
-                            || (options.additionalLibraries & QtQuickModule);
-    if (options.quickImports && (usesQuick1 || usesQuick2)) {
+    if (options.quickImports && (usesQuick1 || usesQml2)) {
         const QmlDirectoryFileEntryFunction qmlFileEntryFunction(options.platform, isDebug);
-        if (usesQuick2) {
-            const QString quick2ImportPath = qmakeVariables.value(QStringLiteral("QT_INSTALL_QML"));
-            QStringList quick2Imports;
-            quick2Imports << QStringLiteral("QtQml") << QStringLiteral("QtQuick") << QStringLiteral("QtQuick.2");
-            if (result.deployedQtLibraries & QtMultimediaModule)
-                quick2Imports << QStringLiteral("QtMultimedia");
-            if (result.deployedQtLibraries & QtSensorsModule)
-                quick2Imports << QStringLiteral("QtSensors");
-            if (result.deployedQtLibraries & QtWebKitModule)
-                quick2Imports << QStringLiteral("QtWebKit");
-            foreach (const QString &quick2Import, quick2Imports) {
-                const QString sourceFile = quick2ImportPath + slash + quick2Import;
-                if (!updateFile(sourceFile, qmlFileEntryFunction, options.directory, options.updateFileFlags, options.json, errorMessage))
+        if (usesQml2) {
+            foreach (const QString &module, qmlScanResult.modulesDirectories) {
+                if (!updateFile(module, qmlFileEntryFunction, options.directory, options.updateFileFlags, options.json, errorMessage))
                     return result;
             }
         } // Quick 2
diff --git a/src/windeployqt/qmlutils.cpp b/src/windeployqt/qmlutils.cpp
new file mode 100644 (file)
index 0000000..c2593cd
--- /dev/null
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmlutils.h"
+#include "utils.h"
+
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+#include <QtCore/QJsonParseError>
+
+QT_BEGIN_NAMESPACE
+
+static QString qmlDirectoryRecursion(Platform platform, const QString &path)
+{
+    QDir dir(path);
+    if (!dir.entryList(QStringList(QStringLiteral("*.qml")), QDir::Files, QDir::NoSort).isEmpty())
+        return dir.path();
+    foreach (const QString &subDir, dir.entryList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort)) {
+        if (!isBuildDirectory(platform, subDir)) {
+            const QString subPath = qmlDirectoryRecursion(platform, dir.path() +  QLatin1Char('/') + subDir);
+            if (!subPath.isEmpty())
+                return subPath;
+        }
+    }
+    return QString();
+}
+
+// Find a directory containing QML files in the project
+QString findQmlDirectory(int platform, const QString &startDirectoryName)
+{
+    QDir startDirectory(startDirectoryName);
+    if (isBuildDirectory(Platform(platform), startDirectory.dirName()))
+        startDirectory.cdUp();
+    return qmlDirectoryRecursion(Platform(platform), startDirectory.path());
+}
+
+static void findFileRecursion(const QDir &directory, Platform platform, bool debug, QStringList *matches)
+{
+    foreach (const QString &dll, findSharedLibraries(directory, platform, debug))
+        matches->append(directory.filePath(dll));
+    foreach (const QString &subDir, directory.entryList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks)) {
+        QDir subDirectory = directory;
+        if (subDirectory.cd(subDir))
+            findFileRecursion(subDirectory, platform, debug, matches);
+    }
+}
+
+QmlImportScanResult runQmlImportScanner(const QString &directory, const QString &qmlImportPath,
+                                        int platform, bool debug, QString *errorMessage)
+{
+    QmlImportScanResult result;
+    QStringList arguments;
+    arguments << QStringLiteral("-importPath") << qmlImportPath << QStringLiteral("-rootPath") << directory;
+    unsigned long exitCode;
+    QByteArray stdOut;
+    QByteArray stdErr;
+    const QString binary = QStringLiteral("qmlimportscanner");
+    if (!runProcess(binary, arguments, directory, &exitCode, &stdOut, &stdErr, errorMessage))
+        return result;
+    if (exitCode) {
+        *errorMessage = binary + QStringLiteral(" returned ") + QString::number(exitCode)
+                        + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr);
+        return result;
+    }
+    QJsonParseError jsonParseError;
+    const QJsonDocument data = QJsonDocument::fromJson(stdOut, &jsonParseError);
+    if (data.isNull() ) {
+        *errorMessage = binary + QStringLiteral(" returned invalid JSON output: ")
+                        + jsonParseError.errorString() + QStringLiteral(" :\"")
+                        + QString::fromLocal8Bit(stdOut) + QLatin1Char('"');
+        return result;
+    }
+    const QJsonArray array = data.array();
+    const int childCount = array.count();
+    for (int c = 0; c < childCount; ++c) {
+        const QJsonObject object = array.at(c).toObject();
+        if (object.value(QStringLiteral("type")).toString() == QLatin1String("module")) {
+            const QString path = object.value(QStringLiteral("path")).toString();
+            result.modulesDirectories.append(path);
+            findFileRecursion(QDir(path), Platform(platform), debug, &result.plugins);
+        }
+    }
+    result.ok = true;
+    return result;
+}
+
+QT_END_NAMESPACE
diff --git a/src/windeployqt/qmlutils.h b/src/windeployqt/qmlutils.h
new file mode 100644 (file)
index 0000000..0d7c6cc
--- /dev/null
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMLUTILS_H
+#define QMLUTILS_H
+
+#include <QStringList>
+
+QT_BEGIN_NAMESPACE
+
+QString findQmlDirectory(int platform, const QString &startDirectoryName);
+
+struct QmlImportScanResult {
+    QmlImportScanResult() : ok(false) {}
+
+    bool ok;
+    QStringList modulesDirectories;
+    QStringList plugins;
+};
+
+QmlImportScanResult runQmlImportScanner(const QString &directory, const QString &qmlImportPath,
+                                        int platform, bool debug, QString *errorMessage);
+
+QT_END_NAMESPACE
+
+#endif // QMLUTILS_H
index 51deba9..36edf5e 100644 (file)
@@ -69,6 +69,11 @@ QT_BEGIN_NAMESPACE
 
 int optVerboseLevel = 1;
 
+bool isBuildDirectory(Platform platform, const QString &dirName)
+{
+    return (platform & WindowsBased) && (dirName == QLatin1String("debug") || dirName == QLatin1String("release"));
+}
+
 // Create a symbolic link by changing to the source directory to make sure the
 // link uses relative paths only (QFile::link() otherwise uses the absolute path).
 bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage)
index da95123..4519e9d 100644 (file)
@@ -105,6 +105,7 @@ static const char windowsSharedLibrarySuffix[] = ".dll";
 static const char unixSharedLibrarySuffix[] = ".so";
 
 inline QString sharedLibrarySuffix(Platform platform) { return QLatin1String((platform & WindowsBased) ? windowsSharedLibrarySuffix : unixSharedLibrarySuffix); }
+bool isBuildDirectory(Platform platform, const QString &dirName);
 
 bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage);
 bool createDirectory(const QString &directory, QString *errorMessage);
index fb4e47b..ef5534a 100644 (file)
@@ -2,8 +2,8 @@ option(host_build)
 QT = core-private
 DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII
 
-SOURCES += main.cpp utils.cpp elfreader.cpp
-HEADERS += utils.h elfreader.h
+SOURCES += main.cpp utils.cpp qmlutils.cpp elfreader.cpp
+HEADERS += utils.h qmlutils.h elfreader.h
 
 CONFIG += force_bootstrap