From c03c51424b25c1a508a6562dc4edb282577bc964 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Fri, 25 Oct 2013 16:35:17 +0200 Subject: [PATCH] Use qmlimportscanner in windeployqt. Run qmlimportscanner to find the QML modules and scan their plugins for additional dependencies. Change-Id: Ia35e71441c4b97be0561b42fee21e8e6e961d1e5 Reviewed-by: Andrew Knight Reviewed-by: Joerg Bornemann --- src/windeployqt/main.cpp | 76 ++++++++++++++++------- src/windeployqt/qmlutils.cpp | 129 ++++++++++++++++++++++++++++++++++++++++ src/windeployqt/qmlutils.h | 64 ++++++++++++++++++++ src/windeployqt/utils.cpp | 5 ++ src/windeployqt/utils.h | 1 + src/windeployqt/windeployqt.pro | 4 +- 6 files changed, 255 insertions(+), 24 deletions(-) create mode 100644 src/windeployqt/qmlutils.cpp create mode 100644 src/windeployqt/qmlutils.h diff --git a/src/windeployqt/main.cpp b/src/windeployqt/main.cpp index b790f25..2ce6fcd 100644 --- a/src/windeployqt/main.cpp +++ b/src/windeployqt/main.cpp @@ -40,6 +40,7 @@ ****************************************************************************/ #include "utils.h" +#include "qmlutils.h" #include #include @@ -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 index 0000000..c2593cd --- /dev/null +++ b/src/windeployqt/qmlutils.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +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 index 0000000..0d7c6cc --- /dev/null +++ b/src/windeployqt/qmlutils.h @@ -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 + +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 diff --git a/src/windeployqt/utils.cpp b/src/windeployqt/utils.cpp index 51deba9..36edf5e 100644 --- a/src/windeployqt/utils.cpp +++ b/src/windeployqt/utils.cpp @@ -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) diff --git a/src/windeployqt/utils.h b/src/windeployqt/utils.h index da95123..4519e9d 100644 --- a/src/windeployqt/utils.h +++ b/src/windeployqt/utils.h @@ -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); diff --git a/src/windeployqt/windeployqt.pro b/src/windeployqt/windeployqt.pro index fb4e47b..ef5534a 100644 --- a/src/windeployqt/windeployqt.pro +++ b/src/windeployqt/windeployqt.pro @@ -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 -- 2.7.4