From 6aa4c83bdbd9a8df05c14ac1aaad2f1d44053ec4 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 30 Jun 2020 15:25:51 +0200 Subject: qmlimportscanner: Use QmlDirParser This gives us a reliable way to parse imports and versions. Apparently we can also have multiple classname entries in the same qmldir file. Reflect that in the parser. Also, drop the version field from the output. Nobody uses it and maintaining it while allowing partial, missing and auto versions would be rather difficult. Fixes: QTBUG-85304 Change-Id: Iab89a6d505a3c58174e623f02f0418899cb5fa2f Reviewed-by: Fabian Kosmale --- tools/qmlimportscanner/main.cpp | 162 +++++++++++++++++++++++----------------- 1 file changed, 95 insertions(+), 67 deletions(-) (limited to 'tools/qmlimportscanner') diff --git a/tools/qmlimportscanner/main.cpp b/tools/qmlimportscanner/main.cpp index 348328f6a4..d1fa0991e8 100644 --- a/tools/qmlimportscanner/main.cpp +++ b/tools/qmlimportscanner/main.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -128,7 +129,8 @@ QVariantList findImportsInAst(QQmlJS::AST::UiHeaderItemList *headerItemList, con + QLatin1Char('.') + QString::number(importNode->version->version.minorVersion()) : QString(); - import[versionLiteral()] = versionString; + if (!versionString.isEmpty()) + import[versionLiteral()] = versionString; } imports.append(import); @@ -137,43 +139,90 @@ QVariantList findImportsInAst(QQmlJS::AST::UiHeaderItemList *headerItemList, con return imports; } +QVariantList findQmlImportsInQmlFile(const QString &filePath); +QVariantList findQmlImportsInJavascriptFile(const QString &filePath); + +static QString versionSuffix(QTypeRevision version) +{ + return QLatin1Char(' ') + QString::number(version.majorVersion()) + QLatin1Char('.') + + QString::number(version.minorVersion()); +} + // Read the qmldir file, extract a list of plugins by -// parsing the "plugin" and "classname" lines. -QVariantMap pluginsForModulePath(const QString &modulePath) { +// parsing the "plugin", "import", and "classname" directives. +QVariantMap pluginsForModulePath(const QString &modulePath, const QString &version) { QFile qmldirFile(modulePath + QLatin1String("/qmldir")); - if (!qmldirFile.exists()) + if (!qmldirFile.exists()) { + qWarning() << "qmldir file not found at" << modulePath; return QVariantMap(); + } - qmldirFile.open(QIODevice::ReadOnly | QIODevice::Text); - - // A qml import may contain several plugins - QString plugins; - QString classnames; - QStringList dependencies; - QByteArray line; - do { - line = qmldirFile.readLine(); - if (line.startsWith("plugin")) { - plugins += QString::fromUtf8(line.split(' ').at(1)); - plugins += QLatin1Char(' '); - } else if (line.startsWith("classname")) { - classnames += QString::fromUtf8(line.split(' ').at(1)); - classnames += QLatin1Char(' '); - } else if (line.startsWith("depends")) { - const QList dep = line.split(' '); - if (dep.length() != 3) - std::cerr << "depends: expected 2 arguments: module identifier and version" << std::endl; - else - dependencies << QString::fromUtf8(dep[1]) + QLatin1Char(' ') + QString::fromUtf8(dep[2]).simplified(); - } + if (!qmldirFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "qmldir file not found at" << modulePath; + return QVariantMap(); + } - } while (line.length() > 0); + QQmlDirParser parser; + parser.parse(QString::fromUtf8(qmldirFile.readAll())); + if (parser.hasError()) { + qWarning() << "qmldir file malformed at" << modulePath; + for (const auto error : parser.errors(QLatin1String("qmldir"))) + qWarning() << error.message; + return QVariantMap(); + } QVariantMap pluginInfo; - pluginInfo[pluginsLiteral()] = plugins.simplified(); - pluginInfo[classnamesLiteral()] = classnames.simplified(); - if (dependencies.length()) - pluginInfo[dependenciesLiteral()] = dependencies; + + QStringList pluginNameList; + const auto plugins = parser.plugins(); + for (const auto plugin : plugins) + pluginNameList.append(plugin.name); + pluginInfo[pluginsLiteral()] = pluginNameList.join(QLatin1Char(' ')); + + pluginInfo[classnamesLiteral()] = parser.classNames().join(QLatin1Char(' ')); + + QStringList importsAndDependencies; + const auto dependencies = parser.dependencies(); + for (const auto &dependency : dependencies) + importsAndDependencies.append(dependency.typeName + versionSuffix(dependency.version)); + + const auto imports = parser.imports(); + for (const auto &import : imports) { + if (import.isAutoImport) { + importsAndDependencies.append( + import.module + QLatin1Char(' ') + + (version.isEmpty() ? QString::fromLatin1("auto") : version)); + } else if (import.version.isValid()) { + importsAndDependencies.append(import.module + versionSuffix(import.version)); + } else { + importsAndDependencies.append(import.module); + } + } + + QVariantList importsFromFiles; + const auto components = parser.components(); + for (const auto component : components) { + importsFromFiles + += findQmlImportsInQmlFile(modulePath + QLatin1Char('/') + component.fileName); + } + const auto scripts = parser.scripts(); + for (const auto script : scripts) { + importsFromFiles + += findQmlImportsInJavascriptFile(modulePath + QLatin1Char('/') + script.fileName); + } + + for (const QVariant &import : importsFromFiles) { + const QVariantMap details = qvariant_cast(import); + if (details.value(typeLiteral()) != moduleLiteral()) + continue; + const QString name = details.value(nameLiteral()).toString(); + const QString version = details.value(versionLiteral()).toString(); + importsAndDependencies.append( + version.isEmpty() ? name : (name + QLatin1Char(' ') + version)); + } + + if (!importsAndDependencies.isEmpty()) + pluginInfo[dependenciesLiteral()] = importsAndDependencies; return pluginInfo; } @@ -238,13 +287,15 @@ QVariantList findPathsForModuleImports(const QVariantList &imports) for (int i = 0; i < importsCopy.length(); ++i) { QVariantMap import = qvariant_cast(importsCopy.at(i)); if (import.value(typeLiteral()) == moduleLiteral()) { + const QString version = import.value(versionLiteral()).toString(); const QPair paths = - resolveImportPath(import.value(nameLiteral()).toString(), import.value(versionLiteral()).toString()); + resolveImportPath(import.value(nameLiteral()).toString(), version); + QVariantMap plugininfo; if (!paths.first.isEmpty()) { import.insert(pathLiteral(), paths.first); import.insert(relativePathLiteral(), paths.second); + plugininfo = pluginsForModulePath(paths.first, version); } - QVariantMap plugininfo = pluginsForModulePath(import.value(pathLiteral()).toString()); QString plugins = plugininfo.value(pluginsLiteral()).toString(); QString classnames = plugininfo.value(classnamesLiteral()).toString(); if (!plugins.isEmpty()) @@ -254,15 +305,20 @@ QVariantList findPathsForModuleImports(const QVariantList &imports) if (plugininfo.contains(dependenciesLiteral())) { const QStringList dependencies = plugininfo.value(dependenciesLiteral()).toStringList(); for (const QString &line : dependencies) { - const auto dep = QStringView{line}.split(QLatin1Char(' ')); + const auto dep = QStringView{line}.split(QLatin1Char(' '), Qt::SkipEmptyParts); + const QString name = dep[0].toString(); QVariantMap depImport; depImport[typeLiteral()] = moduleLiteral(); - depImport[nameLiteral()] = dep[0].toString(); - depImport[versionLiteral()] = dep[1].toString(); - importsCopy.append(depImport); + depImport[nameLiteral()] = name; + if (dep.length() > 1) + depImport[versionLiteral()] = dep[1].toString(); + + if (!importsCopy.contains(depImport)) + importsCopy.append(depImport); } } } + import.remove(versionLiteral()); done.append(import); } return done; @@ -326,7 +382,8 @@ struct ImportCollector : public QQmlJS::Directives } else { entry[typeLiteral()] = moduleLiteral(); entry[nameLiteral()] = uri; - entry[versionLiteral()] = version; + if (!version.isEmpty()) + entry[versionLiteral()] = version; } imports << entry; @@ -452,18 +509,6 @@ QVariantList findQmlImportsInDirectory(const QString &qmlDir) return ret; } -QSet importModulePaths(const QVariantList &imports) { - QSet ret; - for (const QVariant &importVariant : imports) { - QVariantMap import = qvariant_cast(importVariant); - QString path = import.value(pathLiteral()).toString(); - QString type = import.value(typeLiteral()).toString(); - if (type == moduleLiteral() && !path.isEmpty()) - ret.insert(QDir(path).canonicalPath()); - } - return ret; -} - // Find qml imports recursively from a root set of qml files. // The directories in qmlDirs are searched recursively. // The files in qmlFiles parsed directly. @@ -483,23 +528,6 @@ QVariantList findQmlImportsRecursively(const QStringList &qmlDirs, const QString ret = mergeImports(ret, imports); } - // Get the paths to the imports found in the app qml - QSet toVisit = importModulePaths(ret); - - // Recursively scan for import dependencies. - QSet visited; - while (!toVisit.isEmpty()) { - QString qmlDir = *toVisit.begin(); - toVisit.erase(toVisit.begin()); - visited.insert(qmlDir); - - QVariantList imports = findQmlImportsInDirectory(qmlDir); - ret = mergeImports(ret, imports); - - QSet candidatePaths = importModulePaths(ret); - candidatePaths.subtract(visited); - toVisit.unite(candidatePaths); - } return ret; } -- cgit v1.2.3