diff options
Diffstat (limited to 'src/qml/qml/qqmlimport.cpp')
-rw-r--r-- | src/qml/qml/qqmlimport.cpp | 2059 |
1 files changed, 755 insertions, 1304 deletions
diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index 5a1364473e..86380294ba 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -1,42 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Crimson AS <info@crimson.no> -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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 The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/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 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017 Crimson AS <info@crimson.no> +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqmlimport_p.h" @@ -46,23 +10,62 @@ #include <QtCore/qfileinfo.h> #include <QtCore/qpluginloader.h> #include <QtCore/qlibraryinfo.h> -#include <QtCore/qreadwritelock.h> +#include <QtCore/qloggingcategory.h> #include <QtQml/qqmlextensioninterface.h> #include <QtQml/qqmlextensionplugin.h> #include <private/qqmlextensionplugin_p.h> #include <private/qqmlglobal_p.h> #include <private/qqmltypenamecache_p.h> #include <private/qqmlengine_p.h> -#include <private/qfieldlist_p.h> +#include <private/qqmltypemodule_p.h> +#include <private/qqmltypeloaderqmldircontent_p.h> +#include <private/qqmlpluginimporter_p.h> #include <QtCore/qjsonobject.h> #include <QtCore/qjsonarray.h> +#include <QtQml/private/qqmltype_p_p.h> +#include <QtQml/private/qqmlimportresolver_p.h> + +#ifdef Q_OS_MACOS +#include "private/qcore_mac_p.h" +#endif #include <algorithm> #include <functional> +using namespace Qt::Literals::StringLiterals; + QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE) + +class QmlImportCategoryHolder +{ + Q_DISABLE_COPY_MOVE(QmlImportCategoryHolder); +public: + + QmlImportCategoryHolder() : m_category("qt.qml.import") + { + // We have to explicitly setEnabled() here because for categories that start with + // "qt." it won't accept QtDebugMsg as argument. Debug messages are off by default + // for all Qt logging categories. + if (qmlImportTrace()) + m_category.setEnabled(QtDebugMsg, true); + } + + ~QmlImportCategoryHolder() = default; + + const QLoggingCategory &category() const { return m_category; } + +private: + QLoggingCategory m_category; +}; + +const QLoggingCategory &lcQmlImport() +{ + static const QmlImportCategoryHolder holder; + return holder.category(); +} + DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES) static const QLatin1Char Dot('.'); @@ -77,6 +80,28 @@ static bool designerSupportRequired = false; namespace { +QTypeRevision relevantVersion(const QString &uri, QTypeRevision version) +{ + return QQmlMetaType::latestModuleVersion(uri).isValid() ? version : QTypeRevision(); +} + +QQmlError moduleNotFoundError(const QString &uri, QTypeRevision version) +{ + QQmlError error; + if (version.hasMajorVersion()) { + error.setDescription(QQmlImportDatabase::tr( + "module \"%1\" version %2.%3 is not installed") + .arg(uri).arg(version.majorVersion()) + .arg(version.hasMinorVersion() + ? QString::number(version.minorVersion()) + : QLatin1String("x"))); + } else { + error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed") + .arg(uri)); + } + return error; +} + QString resolveLocalUrl(const QString &url, const QString &relative) { if (relative.contains(Colon)) { @@ -87,14 +112,14 @@ QString resolveLocalUrl(const QString &url, const QString &relative) } else if (relative.at(0) == Slash || !url.contains(Slash)) { return relative; } else { - const QStringRef baseRef = url.leftRef(url.lastIndexOf(Slash) + 1); + const QStringView baseRef = QStringView{url}.left(url.lastIndexOf(Slash) + 1); if (relative == QLatin1String(".")) return baseRef.toString(); QString base = baseRef + relative; // Remove any relative directory elements in the path - int length = base.length(); + int length = base.size(); int index = 0; while ((index = base.indexOf(QLatin1String("/."), index)) != -1) { if ((length > (index + 2)) && (base.at(index + 2) == Dot) && @@ -131,115 +156,8 @@ bool isPathAbsolute(const QString &path) #endif } -/* - \internal - - Fetches the QQmlType instance registered for \a urlString, creating a - registration for it if it is not already registered, using the associated - \a typeName, \a isCompositeSingleton, \a majorVersion and \a minorVersion - details. - - Errors (if there are any) are placed into \a errors, if it is nonzero. Note - that errors are treated as fatal if \a errors is not set. -*/ -QQmlType fetchOrCreateTypeForUrl(const QString &urlString, const QHashedStringRef& typeName, - bool isCompositeSingleton, QList<QQmlError> *errors, - int majorVersion=-1, int minorVersion=-1) -{ - QUrl url(urlString); // ### unfortunate (costly) conversion - QQmlType ret = QQmlMetaType::qmlType(url); - if (ret.isValid()) - return ret; - - int dot = typeName.indexOf(QLatin1Char('.')); - QHashedStringRef unqualifiedtype = dot < 0 ? typeName : QHashedStringRef(typeName.constData() + dot + 1, typeName.length() - dot - 1); - - // We need a pointer, but we were passed a string. Take a copy so we - // can guarentee it will live long enough to reach qmlregister. - QByteArray buf(unqualifiedtype.toString().toUtf8()); - - QQmlMetaTypeRegistrationFailureRecorder failureRecorder; - - // Register the type. Note that the URI parameters here are empty; for - // file type imports, we do not place them in a URI as we don't - // necessarily have a good and unique one (picture a library import, - // which may be found in multiple plugin locations on disk), but there - // are other reasons for this too. - // - // By not putting them in a URI, we prevent the types from being - // registered on a QQmlTypeModule; this is important, as once types are - // placed on there, they cannot be easily removed, meaning if the - // developer subsequently loads a different import (meaning different - // types) with the same URI (using, say, a different plugin path), it is - // very undesirable that we continue to associate the types from the - // "old" URI with that new module. - // - // Not having URIs also means that the types cannot be found by name - // etc, the only way to look them up is through QQmlImports -- for - // better or worse. - if (isCompositeSingleton) { - QQmlPrivate::RegisterCompositeSingletonType reg = { - url, - "", // uri - majorVersion, - minorVersion, - buf.constData() - }; - ret = QQmlMetaType::registerCompositeSingletonType(reg); - } else { - QQmlPrivate::RegisterCompositeType reg = { - url, - "", // uri - majorVersion, - minorVersion, - buf.constData() - }; - ret = QQmlMetaType::registerCompositeType(reg); - } - - // This means that the type couldn't be found by URL, but could not be - // registered either, meaning we most likely were passed some kind of bad - // data. - if (!ret.isValid()) { - if (!errors) // Cannot list errors properly, just quit - qFatal("%s", failureRecorder.failures().join('\n').toLatin1().constData()); - QQmlError error; - error.setDescription(failureRecorder.failures().join('\n')); - errors->prepend(error); - } - return ret; -} - } // namespace -struct RegisteredPlugin { - QString uri; - QPluginLoader* loader; -}; - -struct StringRegisteredPluginMap : public QMap<QString, RegisteredPlugin> { - QMutex mutex; -}; - -Q_GLOBAL_STATIC(StringRegisteredPluginMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri and the PluginLoaders - -void qmlClearEnginePlugins() -{ - StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes(); - QMutexLocker lock(&plugins->mutex); -#if QT_CONFIG(library) - for (auto &plugin : qAsConst(*plugins)) { - QPluginLoader* loader = plugin.loader; - if (loader && !loader->unload()) - qWarning("Unloading %s failed: %s", qPrintable(plugin.uri), qPrintable(loader->errorString())); - delete loader; - } -#endif - plugins->clear(); -} - -typedef QPair<QStaticPlugin, QJsonArray> StaticPluginPair; - /*! \internal \class QQmlImportInstance @@ -273,106 +191,16 @@ typedef QPair<QStaticPlugin, QJsonArray> StaticPluginPair; import MyFoo 1.0 as Foo */ -class QQmlImportsPrivate -{ -public: - QQmlImportsPrivate(QQmlTypeLoader *loader); - ~QQmlImportsPrivate(); - - QQmlImportNamespace *importNamespace(const QString &prefix) const; - - bool addLibraryImport(const QString& uri, const QString &prefix, - int vmaj, int vmin, const QString &qmldirIdentifier, const QString &qmldirUrl, bool incomplete, - QQmlImportDatabase *database, - QList<QQmlError> *errors); - - bool addFileImport(const QString &uri, const QString &prefix, - int vmaj, int vmin, - bool isImplicitImport, bool incomplete, QQmlImportDatabase *database, - QList<QQmlError> *errors); - - bool updateQmldirContent(const QString &uri, const QString &prefix, - const QString &qmldirIdentifier, const QString& qmldirUrl, - QQmlImportDatabase *database, - QList<QQmlError> *errors); - - bool resolveType(const QHashedStringRef &type, int *vmajor, int *vminor, - QQmlType *type_return, QList<QQmlError> *errors, - QQmlType::RegistrationType registrationType, - QQmlImport::RecursionRestriction recursionRestriction - = QQmlImport::PreventRecursion); - - QUrl baseUrl; - QString base; - int ref; - - // storage of data related to imports without a namespace - mutable QQmlImportNamespace unqualifiedset; - - QQmlImportNamespace *findQualifiedNamespace(const QHashedStringRef &) const; - - // storage of data related to imports with a namespace - mutable QFieldList<QQmlImportNamespace, &QQmlImportNamespace::nextNamespace> qualifiedSets; - - QQmlTypeLoader *typeLoader; - - static bool locateQmldir(const QString &uri, int vmaj, int vmin, - QQmlImportDatabase *database, - QString *outQmldirFilePath, QString *outUrl); - - static bool validateQmldirVersion(const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, int vmaj, int vmin, - QList<QQmlError> *errors); - - bool importExtension(const QString &absoluteFilePath, const QString &uri, - int vmaj, int vmin, - QQmlImportDatabase *database, - const QQmlTypeLoaderQmldirContent &qmldir, - QList<QQmlError> *errors); - - bool getQmldirContent(const QString &qmldirIdentifier, const QString &uri, - QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors); - - QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database); - - QQmlImportInstance *addImportToNamespace(QQmlImportNamespace *nameSpace, - const QString &uri, const QString &url, - int vmaj, int vmin, QV4::CompiledData::Import::ImportType type, - QList<QQmlError> *errors, bool lowPrecedence = false); - - bool populatePluginPairVector(QVector<StaticPluginPair> &result, const QString &uri, const QStringList &versionUris, - const QString &qmldirPath, QList<QQmlError> *errors); -}; - /*! \class QQmlImports \brief The QQmlImports class encapsulates one QML document's import statements. \internal */ -QQmlImports::QQmlImports(const QQmlImports ©) -: d(copy.d) -{ - ++d->ref; -} - -QQmlImports & -QQmlImports::operator =(const QQmlImports ©) -{ - ++copy.d->ref; - if (--d->ref == 0) - delete d; - d = copy.d; - return *this; -} -QQmlImports::QQmlImports(QQmlTypeLoader *typeLoader) - : d(new QQmlImportsPrivate(typeLoader)) +QTypeRevision QQmlImports::validVersion(QTypeRevision version) { -} - -QQmlImports::~QQmlImports() -{ - if (--d->ref == 0) - delete d; + // If the given version is invalid, return a valid but useless version to signal "It's OK". + return version.isValid() ? version : QTypeRevision::fromMinorVersion(0); } /*! @@ -380,23 +208,20 @@ QQmlImports::~QQmlImports() */ void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString) { - d->baseUrl = url; + m_baseUrl = url; - if (urlString.isEmpty()) { - d->base = url.toString(); - } else { - //Q_ASSERT(url.toString() == urlString); - d->base = urlString; - } + if (urlString.isEmpty()) + m_base = url.toString(); + else + m_base = urlString; } /*! + \fn QQmlImports::baseUrl() + \internal + Returns the base URL to be used for all relative file imports added. */ -QUrl QQmlImports::baseUrl() const -{ - return d->baseUrl; -} /* \internal @@ -410,17 +235,17 @@ QUrl QQmlImports::baseUrl() const */ void QQmlImports::populateCache(QQmlTypeNameCache *cache) const { - const QQmlImportNamespace &set = d->unqualifiedset; + const QQmlImportNamespace &set = m_unqualifiedset; - for (int ii = set.imports.count() - 1; ii >= 0; --ii) { + for (int ii = set.imports.size() - 1; ii >= 0; --ii) { const QQmlImportInstance *import = set.imports.at(ii); - QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion); + QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->version); if (module) { - cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, import->minversion)); + cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, import->version)); } } - for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) { + for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) { const QQmlImportNamespace &set = *ns; @@ -428,12 +253,12 @@ void QQmlImports::populateCache(QQmlTypeNameCache *cache) const QQmlImportRef &typeimport = cache->m_namedImports[set.prefix]; typeimport.m_qualifier = set.prefix; - for (int ii = set.imports.count() - 1; ii >= 0; --ii) { + for (int ii = set.imports.size() - 1; ii >= 0; --ii) { const QQmlImportInstance *import = set.imports.at(ii); - QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion); + QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->version); if (module) { QQmlImportRef &typeimport = cache->m_namedImports[set.prefix]; - typeimport.modules.append(QQmlTypeModuleVersion(module, import->minversion)); + typeimport.modules.append(QQmlTypeModuleVersion(module, import->version)); } } } @@ -449,7 +274,7 @@ bool excludeBaseUrl(const QString &importUrl, const QString &fileName, const QSt if (baseUrl.startsWith(importUrl)) { - if (fileName == baseUrl.midRef(importUrl.size())) + if (fileName == QStringView{baseUrl}.mid(importUrl.size())) return false; } @@ -460,30 +285,40 @@ void findCompositeSingletons(const QQmlImportNamespace &set, QList<QQmlImports:: { typedef QQmlDirComponents::const_iterator ConstIterator; - for (int ii = set.imports.count() - 1; ii >= 0; --ii) { + for (int ii = set.imports.size() - 1; ii >= 0; --ii) { const QQmlImportInstance *import = set.imports.at(ii); const QQmlDirComponents &components = import->qmlDirComponents; + const QTypeRevision importVersion = import->version; + auto shouldSkipSingleton = [importVersion](QTypeRevision singletonVersion) -> bool { + return importVersion.hasMajorVersion() && + (singletonVersion.majorVersion() > importVersion.majorVersion() + || (singletonVersion.majorVersion() == importVersion.majorVersion() + && singletonVersion.minorVersion() > importVersion.minorVersion())); + }; + ConstIterator cend = components.constEnd(); for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) { if (cit->singleton && excludeBaseUrl(import->url, cit->fileName, baseUrl.toString())) { + if (shouldSkipSingleton(cit->version)) + continue; QQmlImports::CompositeSingletonReference ref; ref.typeName = cit->typeName; ref.prefix = set.prefix; - ref.majorVersion = cit->majorVersion; - ref.minorVersion = cit->minorVersion; + ref.version = cit->version; resultList.append(ref); } } - if (QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion)) { - module->walkCompositeSingletons([&resultList, &set](const QQmlType &singleton) { + if (QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->version)) { + module->walkCompositeSingletons([&resultList, &set, &shouldSkipSingleton](const QQmlType &singleton) { + if (shouldSkipSingleton(singleton.version())) + return; QQmlImports::CompositeSingletonReference ref; ref.typeName = singleton.elementName(); ref.prefix = set.prefix; - ref.majorVersion = singleton.majorVersion(); - ref.minorVersion = singleton.minorVersion(); + ref.version = singleton.version(); resultList.append(ref); }); } @@ -503,10 +338,10 @@ QList<QQmlImports::CompositeSingletonReference> QQmlImports::resolvedCompositeSi { QList<QQmlImports::CompositeSingletonReference> compositeSingletons; - const QQmlImportNamespace &set = d->unqualifiedset; + const QQmlImportNamespace &set = m_unqualifiedset; findCompositeSingletons(set, compositeSingletons, baseUrl()); - for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) { + for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) { const QQmlImportNamespace &set = *ns; findCompositeSingletons(set, compositeSingletons, baseUrl()); } @@ -520,9 +355,9 @@ QList<QQmlImports::CompositeSingletonReference> QQmlImports::resolvedCompositeSi if (lhs.typeName != rhs.typeName) return lhs.typeName < rhs.typeName; - return lhs.majorVersion != rhs.majorVersion - ? lhs.majorVersion < rhs.majorVersion - : lhs.minorVersion < rhs.minorVersion; + return lhs.version.majorVersion() != rhs.version.majorVersion() + ? lhs.version.majorVersion() < rhs.version.majorVersion() + : lhs.version.minorVersion() < rhs.version.minorVersion(); }); return compositeSingletons; @@ -538,9 +373,9 @@ QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const { QList<QQmlImports::ScriptReference> scripts; - const QQmlImportNamespace &set = d->unqualifiedset; + const QQmlImportNamespace &set = m_unqualifiedset; - for (int ii = set.imports.count() - 1; ii >= 0; --ii) { + for (int ii = set.imports.size() - 1; ii >= 0; --ii) { const QQmlImportInstance *import = set.imports.at(ii); for (const QQmlDirParser::Script &script : import->qmlDirScripts) { @@ -551,10 +386,10 @@ QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const } } - for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) { + for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) { const QQmlImportNamespace &set = *ns; - for (int ii = set.imports.count() - 1; ii >= 0; --ii) { + for (int ii = set.imports.size() - 1; ii >= 0; --ii) { const QQmlImportInstance *import = set.imports.at(ii); for (const QQmlDirParser::Script &script : import->qmlDirScripts) { @@ -570,17 +405,6 @@ QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const return scripts; } -static QString joinStringRefs(const QVector<QStringRef> &refs, const QChar &sep) -{ - QString str; - for (auto it = refs.cbegin(); it != refs.cend(); ++it) { - if (it != refs.cbegin()) - str += sep; - str += *it; - } - return str; -} - /*! Forms complete paths to a qmldir file, from a base URL, a module URI and version specification. @@ -591,47 +415,23 @@ static QString joinStringRefs(const QVector<QStringRef> &refs, const QChar &sep) - base/QtQml.2/Models/qmldir - base/QtQml/Models/qmldir */ -QStringList QQmlImports::completeQmldirPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin) +QStringList QQmlImports::completeQmldirPaths(const QString &uri, const QStringList &basePaths, + QTypeRevision version) { - const QVector<QStringRef> parts = uri.splitRef(Dot, QString::SkipEmptyParts); - - QStringList qmlDirPathsPaths; - // fully & partially versioned parts + 1 unversioned for each base path - qmlDirPathsPaths.reserve(basePaths.count() * (2 * parts.count() + 1)); - - for (int version = FullyVersioned; version <= Unversioned; ++version) { - const QString ver = versionString(vmaj, vmin, static_cast<QQmlImports::ImportVersion>(version)); - - for (const QString &path : basePaths) { - QString dir = path; - if (!dir.endsWith(Slash) && !dir.endsWith(Backslash)) - dir += Slash; - - // append to the end - qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver + Slash_qmldir; - - if (version != Unversioned) { - // insert in the middle - for (int index = parts.count() - 2; index >= 0; --index) { - qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash) - + ver + Slash - + joinStringRefs(parts.mid(index + 1), Slash) + Slash_qmldir; - } - } - } - } - - return qmlDirPathsPaths; + QStringList paths = qQmlResolveImportPaths(uri, basePaths, version); + for (QString &path : paths) + path += Slash_qmldir; + return paths; } -QString QQmlImports::versionString(int vmaj, int vmin, ImportVersion version) +QString QQmlImports::versionString(QTypeRevision version, ImportVersion versionMode) { - if (version == QQmlImports::FullyVersioned) { + if (versionMode == QQmlImports::FullyVersioned) { // extension with fully encoded version number (eg. MyModule.3.2) - return QString::asprintf(".%d.%d", vmaj, vmin); - } else if (version == QQmlImports::PartiallyVersioned) { + return QString::asprintf(".%d.%d", version.majorVersion(), version.minorVersion()); + } else if (versionMode == QQmlImports::PartiallyVersioned) { // extension with encoded version major (eg. MyModule.3) - return QString::asprintf(".%d", vmaj); + return QString::asprintf(".%d", version.majorVersion()); } // else extension without version number (eg. MyModule) return QString(); } @@ -649,30 +449,31 @@ QString QQmlImports::versionString(int vmaj, int vmin, ImportVersion version) \sa addFileImport(), addLibraryImport */ -bool QQmlImports::resolveType(const QHashedStringRef &type, - QQmlType *type_return, int *vmaj, int *vmin, - QQmlImportNamespace** ns_return, QList<QQmlError> *errors, - QQmlType::RegistrationType registrationType, - QQmlImport::RecursionRestriction recursionRestriction) const +bool QQmlImports::resolveType( + QQmlTypeLoader *typeLoader, const QHashedStringRef &type, QQmlType *type_return, + QTypeRevision *version_return, QQmlImportNamespace **ns_return, QList<QQmlError> *errors, + QQmlType::RegistrationType registrationType, bool *typeRecursionDetected) const { - QQmlImportNamespace* ns = d->findQualifiedNamespace(type); + QQmlImportNamespace *ns = findQualifiedNamespace(type); if (ns) { if (ns_return) *ns_return = ns; return true; } if (type_return) { - if (d->resolveType(type, vmaj, vmin, type_return, errors, registrationType, - recursionRestriction)) { - if (qmlImportTrace()) { -#define RESOLVE_TYPE_DEBUG qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) \ - << ')' << "::resolveType: " << type.toString() << " => " + if (resolveType(typeLoader, type, version_return, type_return, errors, registrationType, + typeRecursionDetected)) { + if (lcQmlImport().isDebugEnabled()) { +#define RESOLVE_TYPE_DEBUG qCDebug(lcQmlImport) \ + << "resolveType:" << qPrintable(baseUrl().toString()) << type.toString() << " => " if (type_return && type_return->isValid()) { if (type_return->isCompositeSingleton()) RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL-SINGLETON"; else if (type_return->isComposite()) RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL"; + else if (type_return->isInlineComponentType()) + RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE(INLINECOMPONENT)"; else RESOLVE_TYPE_DEBUG << type_return->typeName() << " TYPE"; } @@ -684,11 +485,12 @@ bool QQmlImports::resolveType(const QHashedStringRef &type, return false; } -bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl, const QQmlTypeLoaderQmldirContent &qmldir, QQmlImportNamespace *nameSpace, QList<QQmlError> *errors) +bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl, + const QQmlTypeLoaderQmldirContent &qmldir, + QQmlImportNamespace *nameSpace, QList<QQmlError> *errors) { Q_ASSERT(resolvedUrl.endsWith(Slash)); url = resolvedUrl; - localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url); qmlDirComponents = qmldir.components(); @@ -699,30 +501,35 @@ bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl, const QQml it != nameSpace->imports.constEnd(); ++it) { if ((*it != this) && ((*it)->uri == uri)) { QQmlError error; - error.setDescription(QQmlImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg((*it)->url)); + error.setDescription( + QQmlImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3") + .arg(uri, url, (*it)->url)); errors->prepend(error); return false; } } - qmlDirScripts = getVersionedScripts(scripts, majversion, minversion); + qmlDirScripts = getVersionedScripts(scripts, version); } return true; } -QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qmldirscripts, int vmaj, int vmin) +QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qmldirscripts, + QTypeRevision version) { QMap<QString, QQmlDirParser::Script> versioned; for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin(); sit != qmldirscripts.constEnd(); ++sit) { // Only include scripts that match our requested version - if (((vmaj == -1) || (sit->majorVersion == vmaj)) && - ((vmin == -1) || (sit->minorVersion <= vmin))) { + if ((!version.hasMajorVersion() || (sit->version.majorVersion() == version.majorVersion())) + && (!version.hasMinorVersion() + || (sit->version.minorVersion() <= version.minorVersion()))) { // Load the highest version that matches - QMap<QString, QQmlDirParser::Script>::iterator vit = versioned.find(sit->nameSpace); - if (vit == versioned.end() || (vit->minorVersion < sit->minorVersion)) { + const auto vit = versioned.constFind(sit->nameSpace); + if (vit == versioned.cend() + || (vit->version.minorVersion() < sit->version.minorVersion())) { versioned.insert(sit->nameSpace, *sit); } } @@ -732,6 +539,7 @@ QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qml } /*! + \fn QQmlImports::resolveType(QQmlImportNamespace *ns, const QHashedStringRef &type, QQmlType *type_return, QTypeRevision *version_return, QQmlType::RegistrationType registrationType = QQmlType::AnyRegistrationType) const \internal Searching \e only in the namespace \a ns (previously returned in a call to @@ -741,38 +549,37 @@ QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qml If the return pointer is 0, the corresponding search is not done. */ -bool QQmlImports::resolveType(QQmlImportNamespace *ns, const QHashedStringRef &type, - QQmlType *type_return, int *vmaj, int *vmin, - QQmlType::RegistrationType registrationType) const -{ - return ns->resolveType(d->typeLoader, type, vmaj, vmin, type_return, nullptr, nullptr, registrationType); -} bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type, - int *vmajor, int *vminor, QQmlType *type_return, QString *base, - bool *typeRecursionDetected, + QTypeRevision *version_return, QQmlType *type_return, + const QString *base, bool *typeRecursionDetected, QQmlType::RegistrationType registrationType, QQmlImport::RecursionRestriction recursionRestriction, QList<QQmlError> *errors) const { - if (majversion >= 0 && minversion >= 0) { - QQmlType t = QQmlMetaType::qmlType(type, uri, majversion, minversion); - if (t.isValid()) { - if (vmajor) - *vmajor = majversion; - if (vminor) - *vminor = minversion; - if (type_return) - *type_return = t; - return true; - } + QQmlType t = QQmlMetaType::qmlType(type, uri, version); + if (t.isValid()) { + if (version_return) + *version_return = version; + if (type_return) + *type_return = t; + return true; } const QString typeStr = type.toString(); + if (isInlineComponent) { + Q_ASSERT(type_return); + bool ret = uri == typeStr; + if (ret) { + Q_ASSERT(!type_return->isValid()); + *type_return = QQmlMetaType::fetchOrCreateInlineComponentTypeForUrl(QUrl(url)); + } + return ret; + } QQmlDirComponents::ConstIterator it = qmlDirComponents.find(typeStr), end = qmlDirComponents.end(); if (it != end) { QString componentUrl; - bool isCompositeSingleton = false; + QQmlMetaType::CompositeTypeLookupMode lookupMode = QQmlMetaType::NonSingleton; QQmlDirComponents::ConstIterator candidate = end; for ( ; it != end && it.key() == typeStr; ++it) { const QQmlDirParser::Component &c = *it; @@ -789,30 +596,35 @@ bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedSt break; } - // importing version -1 means import ALL versions - if ((majversion == -1) || - (implicitlyImported && c.internal) || // allow the implicit import of internal types - (c.majorVersion == majversion && c.minorVersion <= minversion)) { + // importing invalid version means import ALL versions + if (!version.hasMajorVersion() || (implicitlyImported && c.internal) + // allow the implicit import of internal types + || (c.version.majorVersion() == version.majorVersion() + && c.version.minorVersion() <= version.minorVersion())) { // Is this better than the previous candidate? - if ((candidate == end) || - (c.majorVersion > candidate->majorVersion) || - ((c.majorVersion == candidate->majorVersion) && (c.minorVersion > candidate->minorVersion))) { + if ((candidate == end) + || (c.version.majorVersion() > candidate->version.majorVersion()) + || ((c.version.majorVersion() == candidate->version.majorVersion()) + && (c.version.minorVersion() > candidate->version.minorVersion()))) { if (base) { componentUrl = resolveLocalUrl(QString(url + c.typeName + dotqml_string), c.fileName); if (c.internal) { if (resolveLocalUrl(*base, c.fileName) != componentUrl) continue; // failed attempt to access an internal type } - if (recursionRestriction == QQmlImport::PreventRecursion && *base == componentUrl) { - if (typeRecursionDetected) - *typeRecursionDetected = true; + + const bool recursion = *base == componentUrl; + if (typeRecursionDetected) + *typeRecursionDetected = recursion; + + if (recursionRestriction == QQmlImport::PreventRecursion && recursion) { continue; // no recursion } } // This is our best candidate so far candidate = it; - isCompositeSingleton = c.singleton; + lookupMode = c.singleton ? QQmlMetaType::Singleton : QQmlMetaType::NonSingleton; } } } @@ -820,18 +632,20 @@ bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedSt if (candidate != end) { if (!base) // ensure we have a componentUrl componentUrl = resolveLocalUrl(QString(url + candidate->typeName + dotqml_string), candidate->fileName); - QQmlType returnType = fetchOrCreateTypeForUrl(componentUrl, type, isCompositeSingleton, - nullptr, candidate->majorVersion, - candidate->minorVersion); - if (vmajor) - *vmajor = candidate->majorVersion; - if (vminor) - *vminor = candidate->minorVersion; + QQmlType returnType = QQmlMetaType::typeForUrl(componentUrl, type, lookupMode, + nullptr, candidate->version); + if (version_return) + *version_return = candidate->version; if (type_return) *type_return = returnType; return returnType.isValid(); } - } else if (!isLibrary && !localDirectoryPath.isEmpty()) { + } else if (!isLibrary) { + // the base path of the import if it's a local file + const QString localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url); + if (localDirectoryPath.isEmpty()) + return false; + QString qmlUrl; bool exists = false; @@ -839,12 +653,12 @@ bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedSt typeStr + dotqml_string, // Type -> Type.qml typeStr + dotuidotqml_string // Type -> Type.ui.qml }; - for (uint i = 0; i < sizeof(urlsToTry) / sizeof(urlsToTry[0]); ++i) { - exists = typeLoader->fileExists(localDirectoryPath, urlsToTry[i]); + for (const QString &urlToTry : urlsToTry) { + exists = typeLoader->fileExists(localDirectoryPath, urlToTry); if (exists) { #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) // don't let function.qml confuse the use of "new Function(...)" for example. - if (!QQml_isFileCaseCorrect(localDirectoryPath + urlsToTry[i])) { + if (!QQml_isFileCaseCorrect(localDirectoryPath + urlToTry)) { exists = false; if (errors) { QQmlError caseError; @@ -856,18 +670,21 @@ bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedSt #else Q_UNUSED(errors); #endif - qmlUrl = url + urlsToTry[i]; + qmlUrl = url + urlToTry; break; } } if (exists) { - if (recursionRestriction == QQmlImport::PreventRecursion && base && (*base == qmlUrl)) { // no recursion - if (typeRecursionDetected) - *typeRecursionDetected = true; - } else { - QQmlType returnType = fetchOrCreateTypeForUrl( - qmlUrl, type, registrationType == QQmlType::CompositeSingletonType, errors); + const bool recursion = base && *base == qmlUrl; + if (typeRecursionDetected) + *typeRecursionDetected = recursion; + if (recursionRestriction == QQmlImport::AllowRecursion || !recursion) { + QQmlType returnType = QQmlMetaType::typeForUrl( + qmlUrl, type, registrationType == QQmlType::CompositeSingletonType + ? QQmlMetaType::Singleton + : QQmlMetaType::NonSingleton, + errors); if (type_return) *type_return = returnType; return returnType.isValid(); @@ -878,49 +695,91 @@ bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedSt return false; } -bool QQmlImportsPrivate::resolveType(const QHashedStringRef& type, int *vmajor, int *vminor, - QQmlType *type_return, QList<QQmlError> *errors, - QQmlType::RegistrationType registrationType, - QQmlImport::RecursionRestriction recursionRestriction) +bool QQmlImports::resolveType( + QQmlTypeLoader *typeLoader, const QHashedStringRef &type, QTypeRevision *version_return, + QQmlType *type_return, QList<QQmlError> *errors, + QQmlType::RegistrationType registrationType, bool *typeRecursionDetected) const { - QQmlImportNamespace *s = nullptr; - int dot = type.indexOf(Dot); - if (dot >= 0) { - QHashedStringRef namespaceName(type.constData(), dot); - s = findQualifiedNamespace(namespaceName); - if (!s) { - if (errors) { - QQmlError error; - error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName.toString())); - errors->prepend(error); + const QVector<QHashedStringRef> splitName = type.split(Dot); + auto resolveTypeInNamespace = [&]( + QHashedStringRef unqualifiedtype, QQmlImportNamespace *nameSpace, + QList<QQmlError> *errors) -> bool { + if (nameSpace->resolveType( + typeLoader, unqualifiedtype, version_return, type_return, &m_base, errors, + registrationType, typeRecursionDetected)) + return true; + if (nameSpace->imports.size() == 1 + && !nameSpace->imports.at(0)->isLibrary + && type_return + && nameSpace != &m_unqualifiedset) { + // qualified, and only 1 url + *type_return = QQmlMetaType::typeForUrl( + resolveLocalUrl(nameSpace->imports.at(0)->url, + unqualifiedtype.toString() + QLatin1String(".qml")), + type, QQmlMetaType::NonSingleton, errors); + return type_return->isValid(); + } + return false; + }; + switch (splitName.size()) { + case 1: { + // must be a simple type + return resolveTypeInNamespace(type, &m_unqualifiedset, errors); + } + case 2: { + // either namespace + simple type OR simple type + inline component + QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0)); + if (s) { + // namespace + simple type + return resolveTypeInNamespace(splitName.at(1), s, errors); + } else { + if (resolveTypeInNamespace(splitName.at(0), &m_unqualifiedset, nullptr)) { + // either simple type + inline component + *type_return = QQmlMetaType::inlineComponentType( + *type_return, splitName.at(1).toString()); + return true; + } else { + // or a failure + if (errors) { + QQmlError error; + error.setDescription(QQmlImportDatabase::tr("- %1 is neither a type nor a namespace").arg(splitName.at(0).toString())); + errors->prepend(error); + } + return false; } - return false; } - int ndot = type.indexOf(Dot,dot+1); - if (ndot > 0) { - if (errors) { - QQmlError error; - error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed")); - errors->prepend(error); + } + case 3: { + // must be namespace + simple type + inline component + QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0)); + QQmlError error; + if (!s) { + error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(splitName.at(0).toString())); + } else { + if (resolveTypeInNamespace(splitName.at(1), s, nullptr)) { + *type_return = QQmlMetaType::inlineComponentType( + *type_return, splitName.at(2).toString()); + return true; + } else { + error.setDescription(QQmlImportDatabase::tr("- %1 is not a type").arg(splitName.at(1).toString())); } - return false; } - } else { - s = &unqualifiedset; + if (errors) { + errors->prepend(error); + } + return false; } - QHashedStringRef unqualifiedtype = dot < 0 ? type : QHashedStringRef(type.constData()+dot+1, type.length()-dot-1); - if (s) { - if (s->resolveType(typeLoader, unqualifiedtype, vmajor, vminor, type_return, &base, errors, - registrationType, recursionRestriction)) - return true; - if (s->imports.count() == 1 && !s->imports.at(0)->isLibrary && type_return && s != &unqualifiedset) { - // qualified, and only 1 url - *type_return = fetchOrCreateTypeForUrl(resolveLocalUrl(s->imports.at(0)->url, unqualifiedtype.toString() + QLatin1String(".qml")), type, false, errors); - return type_return->isValid(); + default: { + // all other numbers suggest a user error + if (errors) { + QQmlError error; + error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed")); + errors->prepend(error); } + return false; } - - return false; + } + Q_UNREACHABLE(); } QQmlImportInstance *QQmlImportNamespace::findImport(const QString &uri) const @@ -933,37 +792,50 @@ QQmlImportInstance *QQmlImportNamespace::findImport(const QString &uri) const } bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type, - int *vmajor, int *vminor, QQmlType *type_return, - QString *base, QList<QQmlError> *errors, + QTypeRevision *version_return, QQmlType *type_return, + const QString *base, QList<QQmlError> *errors, QQmlType::RegistrationType registrationType, - QQmlImport::RecursionRestriction recursionRestriction) + bool *typeRecursionDetected) { - bool typeRecursionDetected = false; - for (int i=0; i<imports.count(); ++i) { + QQmlImport::RecursionRestriction recursionRestriction = + typeRecursionDetected ? QQmlImport::AllowRecursion : QQmlImport::PreventRecursion; + + bool localTypeRecursionDetected = false; + if (!typeRecursionDetected) + typeRecursionDetected = &localTypeRecursionDetected; + + // TODO: move the sorting somewhere else and make resolveType() const. + if (needsSorting()) { + std::stable_partition(imports.begin(), imports.end(), [](QQmlImportInstance *import) { + return import->isInlineComponent; + }); + setNeedsSorting(false); + } + for (int i=0; i<imports.size(); ++i) { const QQmlImportInstance *import = imports.at(i); - if (import->resolveType(typeLoader, type, vmajor, vminor, type_return, base, - &typeRecursionDetected, registrationType, recursionRestriction, errors)) { + if (import->resolveType(typeLoader, type, version_return, type_return, base, + typeRecursionDetected, registrationType, recursionRestriction, errors)) { if (qmlCheckTypes()) { // check for type clashes - for (int j = i+1; j<imports.count(); ++j) { + for (int j = i+1; j<imports.size(); ++j) { const QQmlImportInstance *import2 = imports.at(j); - if (import2->resolveType(typeLoader, type, vmajor, vminor, nullptr, base, + if (import2->resolveType(typeLoader, type, version_return, nullptr, base, nullptr, registrationType)) { if (errors) { QString u1 = import->url; QString u2 = import2->url; if (base) { - QStringRef b(base); + QStringView b(*base); int dot = b.lastIndexOf(Dot); if (dot >= 0) { b = b.left(dot+1); - QStringRef l = b.left(dot); + QStringView l = b.left(dot); if (u1.startsWith(b)) - u1 = u1.mid(b.count()); + u1 = u1.mid(b.size()); else if (u1 == l) u1 = QQmlImportDatabase::tr("local directory"); if (u2.startsWith(b)) - u2 = u2.mid(b.count()); + u2 = u2.mid(b.size()); else if (u2 == l) u2 = QQmlImportDatabase::tr("local directory"); } @@ -971,12 +843,20 @@ bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedS QQmlError error; if (u1 != u2) { - error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2)); + error.setDescription( + QQmlImportDatabase::tr( + "is ambiguous. Found in %1 and in %2") + .arg(u1, u2)); } else { - error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5") - .arg(u1) - .arg(import->majversion).arg(import->minversion) - .arg(import2->majversion).arg(import2->minversion)); + error.setDescription( + QQmlImportDatabase::tr( + "is ambiguous. Found in %1 in version " + "%2.%3 and %4.%5") + .arg(u1) + .arg(import->version.majorVersion()) + .arg(import->version.minorVersion()) + .arg(import2->version.majorVersion()) + .arg(import2->version.minorVersion())); } errors->prepend(error); } @@ -989,7 +869,7 @@ bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedS } if (errors) { QQmlError error; - if (typeRecursionDetected) + if (*typeRecursionDetected) error.setDescription(QQmlImportDatabase::tr("is instantiated recursively")); else error.setDescription(QQmlImportDatabase::tr("is not a type")); @@ -998,19 +878,9 @@ bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedS return false; } -QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader) -: ref(1), typeLoader(loader) { -} - -QQmlImportsPrivate::~QQmlImportsPrivate() +QQmlImportNamespace *QQmlImports::findQualifiedNamespace(const QHashedStringRef &prefix) const { - while (QQmlImportNamespace *ns = qualifiedSets.takeFirst()) - delete ns; -} - -QQmlImportNamespace *QQmlImportsPrivate::findQualifiedNamespace(const QHashedStringRef &prefix) const -{ - for (QQmlImportNamespace *ns = qualifiedSets.first(); ns; ns = qualifiedSets.next(ns)) { + for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) { if (prefix == ns->prefix) return ns; } @@ -1018,235 +888,75 @@ QQmlImportNamespace *QQmlImportsPrivate::findQualifiedNamespace(const QHashedStr } /* - Returns the list of possible versioned URI combinations. For example, if \a uri is - QtQml.Models, \a vmaj is 2, and \a vmin is 0, this method returns the following: - [QtQml.Models.2.0, QtQml.2.0.Models, QtQml.Models.2, QtQml.2.Models, QtQml.Models] - */ -static QStringList versionUriList(const QString &uri, int vmaj, int vmin) -{ - QStringList result; - for (int version = QQmlImports::FullyVersioned; version <= QQmlImports::Unversioned; ++version) { - int index = uri.length(); - do { - QString versionUri = uri; - versionUri.insert(index, QQmlImports::versionString(vmaj, vmin, static_cast<QQmlImports::ImportVersion>(version))); - result += versionUri; - - index = uri.lastIndexOf(Dot, index - 1); - } while (index > 0 && version != QQmlImports::Unversioned); - } - return result; -} - -static QVector<QStaticPlugin> makePlugins() -{ - QVector<QStaticPlugin> plugins; - // To avoid traversing all static plugins for all imports, we cut down - // the list the first time called to only contain QML plugins: - const auto staticPlugins = QPluginLoader::staticPlugins(); - for (const QStaticPlugin &plugin : staticPlugins) { - const QString iid = plugin.metaData().value(QLatin1String("IID")).toString(); - if (iid == QLatin1String(QQmlExtensionInterface_iid) || iid == QLatin1String(QQmlExtensionInterface_iid_old)) { - plugins.append(plugin); - } - } - return plugins; -} - -/* - Get all static plugins that are QML plugins and has a meta data URI that matches with one of - \a versionUris, which is a list of all possible versioned URI combinations - see versionUriList() - above. - */ -bool QQmlImportsPrivate::populatePluginPairVector(QVector<StaticPluginPair> &result, const QString &uri, const QStringList &versionUris, - const QString &qmldirPath, QList<QQmlError> *errors) -{ - static const QVector<QStaticPlugin> plugins = makePlugins(); - for (const QStaticPlugin &plugin : plugins) { - // Since a module can list more than one plugin, we keep iterating even after we found a match. - if (QQmlExtensionPlugin *instance = qobject_cast<QQmlExtensionPlugin *>(plugin.instance())) { - const QJsonArray metaTagsUriList = plugin.metaData().value(QLatin1String("uri")).toArray(); - if (metaTagsUriList.isEmpty()) { - if (errors) { - QQmlError error; - error.setDescription(QQmlImportDatabase::tr("static plugin for module \"%1\" with name \"%2\" has no metadata URI") - .arg(uri).arg(QString::fromUtf8(instance->metaObject()->className()))); - error.setUrl(QUrl::fromLocalFile(qmldirPath)); - errors->prepend(error); - } - return false; - } - // A plugin can be set up to handle multiple URIs, so go through the list: - for (const QJsonValue &metaTagUri : metaTagsUriList) { - if (versionUris.contains(metaTagUri.toString())) { - result.append(qMakePair(plugin, metaTagsUriList)); - break; - } - } - } - } - return true; -} - -#if defined(QT_SHARED) || !QT_CONFIG(library) -static inline QString msgCannotLoadPlugin(const QString &uri, const QString &why) -{ - return QQmlImportDatabase::tr("plugin cannot be loaded for module \"%1\": %2").arg(uri, why); -} -#endif - -/* Import an extension defined by a qmldir file. - -\a qmldirFilePath is a raw file path. */ -bool QQmlImportsPrivate::importExtension(const QString &qmldirFilePath, - const QString &uri, - int vmaj, int vmin, - QQmlImportDatabase *database, - const QQmlTypeLoaderQmldirContent &qmldir, - QList<QQmlError> *errors) +QTypeRevision QQmlImports::importExtension( + QQmlTypeLoader *typeLoader, const QString &uri, QTypeRevision version, + const QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors) { - Q_ASSERT(qmldir.hasContent()); + Q_ASSERT(qmldir->hasContent()); - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImports(" << qPrintable(base) << ")::importExtension: " - << "loaded " << qmldirFilePath; + qCDebug(lcQmlImport) + << "importExtension:" << qPrintable(m_base) << "loaded" << qmldir->qmldirLocation(); - if (designerSupportRequired && !qmldir.designerSupported()) { + if (designerSupportRequired && !qmldir->designerSupported()) { if (errors) { QQmlError error; - error.setDescription(QQmlImportDatabase::tr("module does not support the designer \"%1\"").arg(qmldir.typeNamespace())); - error.setUrl(QUrl::fromLocalFile(qmldirFilePath)); + error.setDescription( + QQmlImportDatabase::tr("module does not support the designer \"%1\"") + .arg(qmldir->typeNamespace())); + error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation())); errors->prepend(error); } - return false; + return QTypeRevision(); } - int qmldirPluginCount = qmldir.plugins().count(); - if (qmldirPluginCount == 0) - return true; + if (qmldir->plugins().isEmpty()) { + // If the qmldir does not register a plugin, we might still have declaratively + // registered types (if we are dealing with an application instead of a library) + if (!QQmlMetaType::typeModule(uri, version)) + QQmlMetaType::qmlRegisterModuleTypes(uri); + return validVersion(version); + } - if (!database->qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(qmldirFilePath)) { - // First search for listed qmldir plugins dynamically. If we cannot resolve them all, we continue - // searching static plugins that has correct metadata uri. Note that since we only know the uri - // for a static plugin, and not the filename, we cannot know which static plugin belongs to which - // listed plugin inside qmldir. And for this reason, mixing dynamic and static plugins inside a - // single module is not recommended. - - QString typeNamespace = qmldir.typeNamespace(); - QString qmldirPath = qmldirFilePath; - int slash = qmldirPath.lastIndexOf(Slash); - if (slash > 0) - qmldirPath.truncate(slash); - - int dynamicPluginsFound = 0; - int staticPluginsFound = 0; - -#if defined(QT_SHARED) - const auto qmldirPlugins = qmldir.plugins(); - for (const QQmlDirParser::Plugin &plugin : qmldirPlugins) { - QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name); - if (!resolvedFilePath.isEmpty()) { - dynamicPluginsFound++; - if (!database->importDynamicPlugin(resolvedFilePath, uri, typeNamespace, vmaj, errors)) { - if (errors) { - // XXX TODO: should we leave the import plugin error alone? - // Here, we pop it off the top and coalesce it into this error's message. - // The reason is that the lower level may add url and line/column numbering information. - QQmlError poppedError = errors->takeFirst(); - QQmlError error; - error.setDescription(msgCannotLoadPlugin(uri, poppedError.description())); - error.setUrl(QUrl::fromLocalFile(qmldirFilePath)); - errors->prepend(error); - } - return false; - } - } - } -#endif // QT_SHARED - - if (dynamicPluginsFound < qmldirPluginCount) { - // Check if the missing plugins can be resolved statically. We do this by looking at - // the URIs embedded in a plugins meta data. Since those URIs can be anything from fully - // versioned to unversioned, we need to compare with differnt version strings. If a module - // has several plugins, they must all have the same version. Start by populating pluginPairs - // with relevant plugins to cut the list short early on: - const QStringList versionUris = versionUriList(uri, vmaj, vmin); - QVector<StaticPluginPair> pluginPairs; - if (!populatePluginPairVector(pluginPairs, uri, versionUris, qmldirFilePath, errors)) - return false; + QQmlPluginImporter importer( + uri, version, typeLoader->importDatabase(), qmldir, typeLoader, errors); + return importer.importPlugins(); +} - const QString basePath = QFileInfo(qmldirPath).absoluteFilePath(); - for (const QString &versionUri : versionUris) { - for (const StaticPluginPair &pair : qAsConst(pluginPairs)) { - for (const QJsonValue &metaTagUri : pair.second) { - if (versionUri == metaTagUri.toString()) { - staticPluginsFound++; - QObject *instance = pair.first.instance(); - if (!database->importStaticPlugin(instance, basePath, uri, typeNamespace, vmaj, errors)) { - if (errors) { - QQmlError poppedError = errors->takeFirst(); - QQmlError error; - error.setDescription(QQmlImportDatabase::tr("static plugin for module \"%1\" with name \"%2\" cannot be loaded: %3") - .arg(uri).arg(QString::fromUtf8(instance->metaObject()->className())).arg(poppedError.description())); - error.setUrl(QUrl::fromLocalFile(qmldirFilePath)); - errors->prepend(error); - } - return false; - } - break; - } - } - } - if (staticPluginsFound > 0) - break; - } - } +QString QQmlImports::redirectQmldirContent( + QQmlTypeLoader *typeLoader, QQmlTypeLoaderQmldirContent *qmldir) +{ + const QString preferredPath = qmldir->preferredPath(); + const QString url = preferredPath.startsWith(u':') + ? QStringLiteral("qrc") + preferredPath + : QUrl::fromLocalFile(preferredPath).toString(); - if ((dynamicPluginsFound + staticPluginsFound) < qmldirPluginCount) { - if (errors) { - QQmlError error; - if (qmldirPluginCount > 1 && staticPluginsFound > 0) - error.setDescription(QQmlImportDatabase::tr("could not resolve all plugins for module \"%1\"").arg(uri)); - else - error.setDescription(QQmlImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(qmldir.plugins()[dynamicPluginsFound].name)); - error.setUrl(QUrl::fromLocalFile(qmldirFilePath)); - errors->prepend(error); - } - return false; - } + QQmlTypeLoaderQmldirContent redirected + = typeLoader->qmldirContent(url + QLatin1String("qmldir")); - database->qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(qmldirFilePath); - } - return true; + // Ignore errors: If the qmldir doesn't exist, stick to the old one. + if (redirected.hasContent() && !redirected.hasError()) + *qmldir = std::move(redirected); + return url; } -bool QQmlImportsPrivate::getQmldirContent(const QString &qmldirIdentifier, const QString &uri, - QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors) +bool QQmlImports::getQmldirContent( + QQmlTypeLoader *typeLoader, const QString &qmldirIdentifier, const QString &uri, + QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors) { Q_ASSERT(errors); Q_ASSERT(qmldir); *qmldir = typeLoader->qmldirContent(qmldirIdentifier); - if ((*qmldir).hasContent()) { - // Ensure that parsing was successful - if ((*qmldir).hasError()) { - QUrl url = QUrl::fromLocalFile(qmldirIdentifier); - const QList<QQmlError> qmldirErrors = (*qmldir).errors(uri); - for (int i = 0; i < qmldirErrors.size(); ++i) { - QQmlError error = qmldirErrors.at(i); - error.setUrl(url); - errors->append(error); - } - return false; - } - } + if (!qmldir->hasContent() || !qmldir->hasError()) + return true; - return true; + errors->append(qmldir->errors(uri, QUrl::fromLocalFile(qmldirIdentifier))); + return false; } -QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database) +QString QQmlImports::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database) { QString dir = dir_arg; if (dir.endsWith(Slash) || dir.endsWith(Backslash)) @@ -1257,9 +967,9 @@ QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDataba std::sort(paths.begin(), paths.end(), std::greater<QString>()); // Ensure subdirs preceed their parents. QString stableRelativePath = dir; - for (const QString &path : qAsConst(paths)) { + for (const QString &path : std::as_const(paths)) { if (dir.startsWith(path)) { - stableRelativePath = dir.mid(path.length()+1); + stableRelativePath = dir.mid(path.size()+1); break; } } @@ -1281,89 +991,48 @@ QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDataba return stableRelativePath; } -/* -Locates the qmldir file for \a uri version \a vmaj.vmin. Returns true if found, -and fills in outQmldirFilePath and outQmldirUrl appropriately. Otherwise returns -false. -*/ -bool QQmlImportsPrivate::locateQmldir(const QString &uri, int vmaj, int vmin, QQmlImportDatabase *database, - QString *outQmldirFilePath, QString *outQmldirPathUrl) -{ - Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries - - // Check cache first - - QQmlImportDatabase::QmldirCache *cacheHead = nullptr; - { - QQmlImportDatabase::QmldirCache **cachePtr = database->qmldirCache.value(uri); - if (cachePtr) { - cacheHead = *cachePtr; - QQmlImportDatabase::QmldirCache *cache = cacheHead; - while (cache) { - if (cache->versionMajor == vmaj && cache->versionMinor == vmin) { - *outQmldirFilePath = cache->qmldirFilePath; - *outQmldirPathUrl = cache->qmldirPathUrl; - return !cache->qmldirFilePath.isEmpty(); - } - cache = cache->next; - } - } - } +/*! + \internal - QQmlTypeLoader &typeLoader = QQmlEnginePrivate::get(database->engine)->typeLoader; + \fn template<typename Callback> + QQmlImportDatabase::LocalQmldirResult QQmlImportDatabase::locateLocalQmldir( + const QString &uri, QTypeRevision version, const Callback &callback) - // Interceptor might redirect remote files to local ones. - QQmlAbstractUrlInterceptor *interceptor = typeLoader.engine()->urlInterceptor(); - QStringList localImportPaths = database->importPathList( - interceptor ? QQmlImportDatabase::LocalOrRemote : QQmlImportDatabase::Local); + Locates the qmldir files for \a uri version \a version. For each one, calls + the \a callback. If the \a callback returns \c true, returns QmldirFound. - // Search local import paths for a matching version - const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(uri, localImportPaths, vmaj, vmin); - for (QString qmldirPath : qmlDirPaths) { - if (interceptor) { - qmldirPath = QQmlFile::urlToLocalFileOrQrc( - interceptor->intercept(QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), - QQmlAbstractUrlInterceptor::QmldirFile)); - } + If at least one callback invocation returned \c false and there are no qmldir + files left to check, returns QmldirRejected. - QString absoluteFilePath = typeLoader.absoluteFilePath(qmldirPath); - if (!absoluteFilePath.isEmpty()) { - QString url; - const QStringRef absolutePath = absoluteFilePath.leftRef(absoluteFilePath.lastIndexOf(Slash) + 1); - if (absolutePath.at(0) == Colon) - url = QLatin1String("qrc") + absolutePath; - else - url = QUrl::fromLocalFile(absolutePath.toString()).toString(); - - QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache; - cache->versionMajor = vmaj; - cache->versionMinor = vmin; - cache->qmldirFilePath = absoluteFilePath; - cache->qmldirPathUrl = url; - cache->next = cacheHead; - database->qmldirCache.insert(uri, cache); - - *outQmldirFilePath = absoluteFilePath; - *outQmldirPathUrl = url; + Otherwise, if interception redirects a previously local qmldir URL to a remote + one, returns QmldirInterceptedToRemote. Otherwise, returns QmldirNotFound. +*/ - return true; +QTypeRevision QQmlImports::matchingQmldirVersion( + const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, QTypeRevision version, + QList<QQmlError> *errors) +{ + int bestMajorVersion = -1; + quint8 lowestMinorVersion = std::numeric_limits<quint8>::max(); + quint8 highestMinorVersion = 0; + + auto addVersion = [&](QTypeRevision newVersion) { + if (!newVersion.hasMajorVersion()) + return; + if (!version.hasMajorVersion() || version.majorVersion() == newVersion.majorVersion()) { + if (newVersion.majorVersion() > bestMajorVersion) { + bestMajorVersion = newVersion.majorVersion(); + if (newVersion.hasMinorVersion()) { + lowestMinorVersion = newVersion.minorVersion(); + highestMinorVersion = newVersion.minorVersion(); + } + } else if (newVersion.majorVersion() == bestMajorVersion + && newVersion.hasMinorVersion()) { + lowestMinorVersion = qMin(lowestMinorVersion, newVersion.minorVersion()); + highestMinorVersion = qMax(highestMinorVersion, newVersion.minorVersion()); + } } - } - - QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache; - cache->versionMajor = vmaj; - cache->versionMinor = vmin; - cache->next = cacheHead; - database->qmldirCache.insert(uri, cache); - - return false; -} - -bool QQmlImportsPrivate::validateQmldirVersion(const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, int vmaj, int vmin, - QList<QQmlError> *errors) -{ - int lowest_min = INT_MAX; - int highest_min = INT_MIN; + }; typedef QQmlDirComponents::const_iterator ConstIterator; const QQmlDirComponents &components = qmldir.components(); @@ -1371,22 +1040,20 @@ bool QQmlImportsPrivate::validateQmldirVersion(const QQmlTypeLoaderQmldirContent ConstIterator cend = components.constEnd(); for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) { for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) { - if ((cit2->typeName == cit->typeName) && - (cit2->majorVersion == cit->majorVersion) && - (cit2->minorVersion == cit->minorVersion)) { + if (cit2->typeName == cit->typeName && cit2->version == cit->version) { // This entry clashes with a predecessor QQmlError error; - error.setDescription(QQmlImportDatabase::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"") - .arg(cit->typeName).arg(cit->majorVersion).arg(cit->minorVersion).arg(uri)); + error.setDescription( + QQmlImportDatabase::tr( + "\"%1\" version %2.%3 is defined more than once in module \"%4\"") + .arg(cit->typeName).arg(cit->version.majorVersion()) + .arg(cit->version.minorVersion()).arg(uri)); errors->prepend(error); - return false; + return QTypeRevision(); } } - if (cit->majorVersion == vmaj) { - lowest_min = qMin(lowest_min, cit->minorVersion); - highest_min = qMax(highest_min, cit->minorVersion); - } + addVersion(cit->version); } typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator; @@ -1395,57 +1062,63 @@ bool QQmlImportsPrivate::validateQmldirVersion(const QQmlTypeLoaderQmldirContent SConstIterator send = scripts.constEnd(); for (SConstIterator sit = scripts.constBegin(); sit != send; ++sit) { for (SConstIterator sit2 = scripts.constBegin(); sit2 != sit; ++sit2) { - if ((sit2->nameSpace == sit->nameSpace) && - (sit2->majorVersion == sit->majorVersion) && - (sit2->minorVersion == sit->minorVersion)) { + if (sit2->nameSpace == sit->nameSpace && sit2->version == sit->version) { // This entry clashes with a predecessor QQmlError error; error.setDescription(QQmlImportDatabase::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"") - .arg(sit->nameSpace).arg(sit->majorVersion).arg(sit->minorVersion).arg(uri)); + .arg(sit->nameSpace).arg(sit->version.majorVersion()) + .arg(sit->version.minorVersion()).arg(uri)); errors->prepend(error); - return false; + return QTypeRevision(); } } - if (sit->majorVersion == vmaj) { - lowest_min = qMin(lowest_min, sit->minorVersion); - highest_min = qMax(highest_min, sit->minorVersion); - } + addVersion(sit->version); } - if (lowest_min > vmin || highest_min < vmin) { - QQmlError error; - error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin)); - errors->prepend(error); - return false; + // Failure to find a match is only an error if we were asking for a specific version ... + if (version.hasMajorVersion() + && (bestMajorVersion < 0 + || (version.hasMinorVersion() + && (lowestMinorVersion > version.minorVersion() + || highestMinorVersion < version.minorVersion())))) { + errors->prepend(moduleNotFoundError(uri, version)); + return QTypeRevision(); } - return true; + // ... otherwise, anything is valid. + if (bestMajorVersion < 0) + return validVersion(); + + return QTypeRevision::fromVersion( + bestMajorVersion, + (version.hasMajorVersion() && version.hasMinorVersion()) + ? version.minorVersion() + : highestMinorVersion); } -QQmlImportNamespace *QQmlImportsPrivate::importNamespace(const QString &prefix) const +QQmlImportNamespace *QQmlImports::importNamespace(const QString &prefix) { QQmlImportNamespace *nameSpace = nullptr; if (prefix.isEmpty()) { - nameSpace = &unqualifiedset; + nameSpace = &m_unqualifiedset; } else { nameSpace = findQualifiedNamespace(prefix); if (!nameSpace) { nameSpace = new QQmlImportNamespace; nameSpace->prefix = prefix; - qualifiedSets.append(nameSpace); + m_qualifiedSets.append(nameSpace); } } return nameSpace; } -QQmlImportInstance *QQmlImportsPrivate::addImportToNamespace(QQmlImportNamespace *nameSpace, - const QString &uri, const QString &url, int vmaj, int vmin, - QV4::CompiledData::Import::ImportType type, - QList<QQmlError> *errors, bool lowPrecedence) +static QQmlImportInstance *addImportToNamespace( + QQmlImportNamespace *nameSpace, const QString &uri, const QString &url, QTypeRevision version, + QV4::CompiledData::Import::ImportType type, QList<QQmlError> *errors, quint16 precedence) { Q_ASSERT(nameSpace); Q_ASSERT(errors); @@ -1455,75 +1128,135 @@ QQmlImportInstance *QQmlImportsPrivate::addImportToNamespace(QQmlImportNamespace QQmlImportInstance *import = new QQmlImportInstance; import->uri = uri; import->url = url; - import->localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url); - import->majversion = vmaj; - import->minversion = vmin; + import->version = version; import->isLibrary = (type == QV4::CompiledData::Import::ImportLibrary); + import->precedence = precedence; + import->implicitlyImported = precedence >= QQmlImportInstance::Implicit; - if (lowPrecedence) - nameSpace->imports.append(import); - else - nameSpace->imports.prepend(import); + for (auto it = nameSpace->imports.cbegin(), end = nameSpace->imports.cend(); + it != end; ++it) { + if ((*it)->precedence < precedence) + continue; + nameSpace->imports.insert(it, import); + return import; + } + nameSpace->imports.append(import); return import; } -bool QQmlImportsPrivate::addLibraryImport(const QString& uri, const QString &prefix, - int vmaj, int vmin, const QString &qmldirIdentifier, const QString &qmldirUrl, bool incomplete, - QQmlImportDatabase *database, - QList<QQmlError> *errors) +QTypeRevision QQmlImports::addLibraryImport( + QQmlTypeLoader *typeLoader, const QString &uri, const QString &prefix, + QTypeRevision version, const QString &qmldirIdentifier, const QString &qmldirUrl, + ImportFlags flags, quint16 precedence, QList<QQmlError> *errors) { - Q_ASSERT(database); + Q_ASSERT(typeLoader); Q_ASSERT(errors); + qCDebug(lcQmlImport) + << "addLibraryImport:" << qPrintable(baseUrl().toString()) + << uri << "version '" << version << "'" << "as" << prefix; + QQmlImportNamespace *nameSpace = importNamespace(prefix); Q_ASSERT(nameSpace); - QQmlImportInstance *inserted = addImportToNamespace(nameSpace, uri, qmldirUrl, vmaj, vmin, QV4::CompiledData::Import::ImportLibrary, errors); + QQmlImportInstance *inserted = addImportToNamespace( + nameSpace, uri, qmldirUrl, version, + QV4::CompiledData::Import::ImportLibrary, errors, + precedence); Q_ASSERT(inserted); - if (!incomplete) { + if (!(flags & QQmlImports::ImportIncomplete)) { QQmlTypeLoaderQmldirContent qmldir; if (!qmldirIdentifier.isEmpty()) { - if (!getQmldirContent(qmldirIdentifier, uri, &qmldir, errors)) - return false; + if (!getQmldirContent(typeLoader, qmldirIdentifier, uri, &qmldir, errors)) + return QTypeRevision(); if (qmldir.hasContent()) { - if (!importExtension(qmldir.pluginLocation(), uri, vmaj, vmin, database, qmldir, errors)) - return false; + version = importExtension(typeLoader, uri, version, &qmldir, errors); + if (!version.isValid()) + return QTypeRevision(); - if (!inserted->setQmldirContent(qmldirUrl, qmldir, nameSpace, errors)) - return false; + const QString resolvedUrl = qmldir.hasRedirection() + ? redirectQmldirContent(typeLoader, &qmldir) + : qmldirUrl; + + if (!inserted->setQmldirContent(resolvedUrl, qmldir, nameSpace, errors)) + return QTypeRevision(); } } // Ensure that we are actually providing something - if ((vmaj < 0) || (vmin < 0) || !QQmlMetaType::isModule(uri, vmaj, vmin)) { - if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) { - QQmlError error; - if (QQmlMetaType::isAnyModule(uri)) - error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin)); - else - error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri)); - errors->prepend(error); - return false; - } else if ((vmaj >= 0) && (vmin >= 0) && qmldir.hasContent()) { - // Verify that the qmldir content is valid for this version - if (!validateQmldirVersion(qmldir, uri, vmaj, vmin, errors)) - return false; + const QTypeRevision matchingVersion = QQmlMetaType::matchingModuleVersion(uri, version); + if (matchingVersion.isValid()) + return matchingVersion; + + if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) { + if (qmldir.plugins().isEmpty()) { + if (!qmldir.imports().isEmpty()) + return validVersion(); // This is a pure redirection + if (qmldir.hasTypeInfo()) + return validVersion(); // A pure C++ module without plugin } + errors->prepend(moduleNotFoundError(uri, relevantVersion(uri, version))); + return QTypeRevision(); + } else if (qmldir.hasContent()) { + // Verify that the qmldir content is valid for this version + version = matchingQmldirVersion(qmldir, uri, version, errors); + if (!version.isValid()) + return QTypeRevision(); } } - return true; + return validVersion(version); } -bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix, - int vmaj, int vmin, - bool isImplicitImport, bool incomplete, QQmlImportDatabase *database, - QList<QQmlError> *errors) +/*! + \internal + + Adds information to \a database such that subsequent calls to resolveType() + will resolve types qualified by \a prefix by considering types found at the given \a uri. + + The \a uri is either a directory (if importType is FileImport), or a URI resolved using paths + added via addImportPath() (if importType is LibraryImport). + + The \a prefix may be empty, in which case the import location is considered for + unqualified types. + + The base URL must already have been set with Import::setBaseUrl(). + + Optionally, the qmldir the import resolved to can be returned by providing the \a localQmldir + parameter. Not all imports will have a local qmldir. If there is none, the \a localQmldir + parameter won't be set. + + Returns a valid QTypeRevision on success, and an invalid one on failure. + In case of failure, the \a errors array will filled appropriately. +*/ +QTypeRevision QQmlImports::addFileImport( + QQmlTypeLoader *typeLoader, const QString &uri, const QString &prefix, + QTypeRevision version, ImportFlags flags, quint16 precedence, QString *localQmldir, + QList<QQmlError> *errors) { + Q_ASSERT(typeLoader); + Q_ASSERT(errors); + + qCDebug(lcQmlImport) + << "addFileImport:" << qPrintable(baseUrl().toString()) + << uri << version << "as" << prefix; + + if (uri.startsWith(Slash) || uri.startsWith(Colon)) { + QQmlError error; + const QString fix = uri.startsWith(Slash) ? QLatin1String("file:") + uri + : QLatin1String("qrc") + uri; + error.setDescription(QQmlImportDatabase::tr( + "\"%1\" is not a valid import URL. " + "You can pass relative paths or URLs with schema, but not " + "absolute paths or resource paths. Try \"%2\".").arg(uri, fix)); + errors->prepend(error); + return QTypeRevision(); + } + Q_ASSERT(errors); QQmlImportNamespace *nameSpace = importNamespace(prefix); @@ -1532,13 +1265,11 @@ bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix // The uri for this import. For library imports this is the same as uri // specified by the user, but it may be different in the case of file imports. QString importUri = uri; - QString qmldirUrl = resolveLocalUrl(base, importUri + (importUri.endsWith(Slash) + QString qmldirUrl = resolveLocalUrl(m_base, importUri + (importUri.endsWith(Slash) ? String_qmldir : Slash_qmldir)); - if (QQmlAbstractUrlInterceptor *interceptor = typeLoader->engine()->urlInterceptor()) { - qmldirUrl = interceptor->intercept(QUrl(qmldirUrl), - QQmlAbstractUrlInterceptor::QmldirFile).toString(); - } + qmldirUrl = typeLoader->engine()->interceptUrl( + QUrl(qmldirUrl), QQmlAbstractUrlInterceptor::QmldirFile).toString(); QString qmldirIdentifier; if (QQmlFile::isLocalFile(qmldirUrl)) { @@ -1548,39 +1279,51 @@ bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix const QString dir = localFileOrQrc.left(localFileOrQrc.lastIndexOf(Slash) + 1); if (!typeLoader->directoryExists(dir)) { - if (!isImplicitImport) { + if (precedence < QQmlImportInstance::Implicit) { QQmlError error; error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri)); error.setUrl(QUrl(qmldirUrl)); errors->prepend(error); } - return false; + return QTypeRevision(); } // Transforms the (possible relative) uri into our best guess relative to the // import paths. - importUri = resolvedUri(dir, database); + importUri = resolvedUri(dir, typeLoader->importDatabase()); if (importUri.endsWith(Slash)) importUri.chop(1); - if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) - qmldirIdentifier = localFileOrQrc; + if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) { + qmldirIdentifier = std::move(localFileOrQrc); + if (localQmldir) + *localQmldir = qmldirIdentifier; + } - } else if (nameSpace->prefix.isEmpty() && !incomplete) { + } else if (nameSpace->prefix.isEmpty() && !(flags & QQmlImports::ImportIncomplete)) { - if (!isImplicitImport) { + if (precedence < QQmlImportInstance::Implicit) { QQmlError error; error.setDescription(QQmlImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(importUri)); error.setUrl(QUrl(qmldirUrl)); errors->prepend(error); } - return false; + return QTypeRevision(); } // The url for the path containing files for this import - QString url = resolveLocalUrl(base, uri); + QString url = resolveLocalUrl(m_base, uri); + if (url.isEmpty()) { + QQmlError error; + error.setDescription( + QQmlImportDatabase::tr("Cannot resolve URL for import \"%1\"").arg(uri)); + error.setUrl(m_baseUrl); + errors->prepend(error); + return QTypeRevision(); + } + if (!url.endsWith(Slash) && !url.endsWith(Backslash)) url += Slash; @@ -1588,72 +1331,96 @@ bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix // if the implicit import has already been explicitly added, otherwise we can run into issues // with duplicate imports. However remember that we attempted to add this as implicit import, to // allow for the loading of internal types. - if (isImplicitImport) { + if (precedence >= QQmlImportInstance::Implicit) { for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin(); it != nameSpace->imports.constEnd(); ++it) { if ((*it)->url == url) { (*it)->implicitlyImported = true; - return true; + return validVersion(version); } } } - QQmlImportInstance *inserted = addImportToNamespace(nameSpace, importUri, url, vmaj, vmin, QV4::CompiledData::Import::ImportFile, errors, isImplicitImport); - Q_ASSERT(inserted); - - if (!incomplete && !qmldirIdentifier.isEmpty()) { + if (!(flags & QQmlImports::ImportIncomplete) && !qmldirIdentifier.isEmpty()) { QQmlTypeLoaderQmldirContent qmldir; - if (!getQmldirContent(qmldirIdentifier, importUri, &qmldir, errors)) - return false; + if (!getQmldirContent(typeLoader, qmldirIdentifier, importUri, &qmldir, errors)) + return QTypeRevision(); if (qmldir.hasContent()) { - if (!importExtension(qmldir.pluginLocation(), importUri, vmaj, vmin, database, qmldir, errors)) - return false; + // Prefer the qmldir URI. Unless it doesn't exist. + const QString qmldirUri = qmldir.typeNamespace(); + if (!qmldirUri.isEmpty()) + importUri = qmldirUri; + + QQmlImportInstance *inserted = addImportToNamespace( + nameSpace, importUri, url, version, QV4::CompiledData::Import::ImportFile, + errors, precedence); + Q_ASSERT(inserted); + + version = importExtension(typeLoader, importUri, version, &qmldir, errors); + if (!version.isValid()) + return QTypeRevision(); + + if (qmldir.hasRedirection()) + url = redirectQmldirContent(typeLoader, &qmldir); if (!inserted->setQmldirContent(url, qmldir, nameSpace, errors)) - return false; + return QTypeRevision(); + + return validVersion(version); } } - return true; + QQmlImportInstance *inserted = addImportToNamespace( + nameSpace, importUri, url, version, QV4::CompiledData::Import::ImportFile, + errors, precedence); + Q_ASSERT(inserted); + return validVersion(version); } -bool QQmlImportsPrivate::updateQmldirContent(const QString &uri, const QString &prefix, - const QString &qmldirIdentifier, const QString& qmldirUrl, - QQmlImportDatabase *database, QList<QQmlError> *errors) +QTypeRevision QQmlImports::updateQmldirContent( + QQmlTypeLoader *typeLoader, const QString &uri, const QString &prefix, + const QString &qmldirIdentifier, const QString &qmldirUrl, QList<QQmlError> *errors) { + Q_ASSERT(typeLoader); + Q_ASSERT(errors); + + qDebug(lcQmlImport) + << "updateQmldirContent:" << qPrintable(baseUrl().toString()) + << uri << "to" << qmldirUrl << "as" << prefix; + QQmlImportNamespace *nameSpace = importNamespace(prefix); Q_ASSERT(nameSpace); if (QQmlImportInstance *import = nameSpace->findImport(uri)) { QQmlTypeLoaderQmldirContent qmldir; - if (!getQmldirContent(qmldirIdentifier, uri, &qmldir, errors)) - return false; + if (!getQmldirContent(typeLoader, qmldirIdentifier, uri, &qmldir, errors)) + return QTypeRevision(); if (qmldir.hasContent()) { - int vmaj = import->majversion; - int vmin = import->minversion; - if (!importExtension(qmldir.pluginLocation(), uri, vmaj, vmin, database, qmldir, errors)) - return false; + QTypeRevision version = importExtension( + typeLoader, uri, import->version, &qmldir, errors); + if (!version.isValid()) + return QTypeRevision(); + + const QString resolvedUrl = qmldir.hasRedirection() + ? redirectQmldirContent(typeLoader, &qmldir) + : qmldirUrl; - if (import->setQmldirContent(qmldirUrl, qmldir, nameSpace, errors)) { + if (import->setQmldirContent(resolvedUrl, qmldir, nameSpace, errors)) { if (import->qmlDirComponents.isEmpty() && import->qmlDirScripts.isEmpty()) { // The implicit import qmldir can be empty, and plugins have no extra versions - if (uri != QLatin1String(".") && !QQmlMetaType::isModule(uri, vmaj, vmin)) { - QQmlError error; - if (QQmlMetaType::isAnyModule(uri)) - error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin)); - else - error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri)); - errors->prepend(error); - return false; + if (uri != QLatin1String(".") && !QQmlMetaType::matchingModuleVersion(uri, version).isValid()) { + errors->prepend(moduleNotFoundError(uri, relevantVersion(uri, version))); + return QTypeRevision(); } - } else if ((vmaj >= 0) && (vmin >= 0)) { + } else { // Verify that the qmldir content is valid for this version - if (!validateQmldirVersion(qmldir, uri, vmaj, vmin, errors)) - return false; + version = matchingQmldirVersion(qmldir, uri, version, errors); + if (!version.isValid()) + return QTypeRevision(); } - return true; + return validVersion(version); } } } @@ -1664,10 +1431,11 @@ bool QQmlImportsPrivate::updateQmldirContent(const QString &uri, const QString & errors->prepend(error); } - return false; + return QTypeRevision(); } /*! + \fn QQmlImports::addImplicitImport(QQmlTypeLoader *typeLoader, QString *localQmldir, QList<QQmlError> *errors) \internal Adds an implicit "." file import. This is equivalent to calling addFileImport(), but error @@ -1675,96 +1443,20 @@ bool QQmlImportsPrivate::updateQmldirContent(const QString &uri, const QString & Additionally, this will add the import with lowest instead of highest precedence. */ -bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb, QList<QQmlError> *errors) -{ - Q_ASSERT(errors); - - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) - << ")::addImplicitImport"; - bool incomplete = !isLocal(baseUrl()); - return d->addFileImport(QLatin1String("."), QString(), -1, -1, true, incomplete, importDb, errors); -} /*! - \internal - - Adds information to \a imports such that subsequent calls to resolveType() - will resolve types qualified by \a prefix by considering types found at the given \a uri. - - The uri is either a directory (if importType is FileImport), or a URI resolved using paths - added via addImportPath() (if importType is LibraryImport). - - The \a prefix may be empty, in which case the import location is considered for - unqualified types. - - The base URL must already have been set with Import::setBaseUrl(). - - Optionally, the url the import resolved to can be returned by providing the url parameter. - Not all imports will result in an output url being generated, in which case the url will - be set to an empty string. - - Returns true on success, and false on failure. In case of failure, the errors array will - filled appropriately. -*/ -bool QQmlImports::addFileImport(QQmlImportDatabase *importDb, - const QString& uri, const QString& prefix, int vmaj, int vmin, - bool incomplete, QList<QQmlError> *errors) -{ - Q_ASSERT(importDb); - Q_ASSERT(errors); - - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addFileImport: " - << uri << ' ' << vmaj << '.' << vmin << " as " << prefix; - - return d->addFileImport(uri, prefix, vmaj, vmin, false, incomplete, importDb, errors); -} - -bool QQmlImports::addLibraryImport(QQmlImportDatabase *importDb, - const QString &uri, const QString &prefix, int vmaj, int vmin, - const QString &qmldirIdentifier, const QString& qmldirUrl, bool incomplete, QList<QQmlError> *errors) -{ - Q_ASSERT(importDb); - Q_ASSERT(errors); - - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addLibraryImport: " - << uri << ' ' << vmaj << '.' << vmin << " as " << prefix; - - return d->addLibraryImport(uri, prefix, vmaj, vmin, qmldirIdentifier, qmldirUrl, incomplete, importDb, errors); -} - -bool QQmlImports::updateQmldirContent(QQmlImportDatabase *importDb, - const QString &uri, const QString &prefix, - const QString &qmldirIdentifier, const QString& qmldirUrl, QList<QQmlError> *errors) -{ - Q_ASSERT(importDb); - Q_ASSERT(errors); - - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::updateQmldirContent: " - << uri << " to " << qmldirUrl << " as " << prefix; - - return d->updateQmldirContent(uri, prefix, qmldirIdentifier, qmldirUrl, importDb, errors); -} - -bool QQmlImports::locateQmldir(QQmlImportDatabase *importDb, - const QString& uri, int vmaj, int vmin, - QString *qmldirFilePath, QString *url) -{ - return d->locateQmldir(uri, vmaj, vmin, importDb, qmldirFilePath, url); -} - -bool QQmlImports::isLocal(const QString &url) -{ - return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty(); -} - -bool QQmlImports::isLocal(const QUrl &url) -{ - return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty(); + \internal + */ +bool QQmlImports::addInlineComponentImport(QQmlImportInstance *const importInstance, const QString &name, const QUrl importUrl) +{ + importInstance->url = importUrl.toString(); + importInstance->uri = name; + importInstance->isInlineComponent = true; + importInstance->version = QTypeRevision::zero(); + m_unqualifiedset.imports.push_back(importInstance); + m_unqualifiedset.setNeedsSorting(true); + return true; } QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file) @@ -1772,7 +1464,7 @@ QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file) QUrl url(QLatin1String(file.at(0) == Colon ? "qrc" : "") + file); // We don't support single character schemes as those conflict with windows drive letters. - if (url.scheme().length() < 2) + if (url.scheme().size() < 2) return QUrl::fromLocalFile(file); return url; } @@ -1782,6 +1474,29 @@ void QQmlImports::setDesignerSupportRequired(bool b) designerSupportRequired = b; } +static QStringList parseEnvPath(const QString &envImportPath) +{ + if (QDir::listSeparator() == u':') { + // Double colons are interpreted as separator + resource path. + QStringList paths = envImportPath.split(u':'); + bool wasEmpty = false; + for (auto it = paths.begin(); it != paths.end();) { + if (it->isEmpty()) { + wasEmpty = true; + it = paths.erase(it); + } else { + if (wasEmpty) { + it->prepend(u':'); + wasEmpty = false; + } + ++it; + } + } + return paths; + } else { + return envImportPath.split(QDir::listSeparator(), Qt::SkipEmptyParts); + } +} /*! \class QQmlImportDatabase @@ -1792,141 +1507,64 @@ QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e) : engine(e) { filePluginPath << QLatin1String("."); - // Search order is applicationDirPath(), qrc:/qt-project.org/imports, $QML2_IMPORT_PATH, QLibraryInfo::Qml2ImportsPath - - QString installImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath); + // Search order is: + // 1. android or macos specific bundle paths. + // 2. applicationDirPath() + // 3. qrc:/qt-project.org/imports + // 4. qrc:/qt/qml + // 5. $QML2_IMPORT_PATH + // 6. $QML_IMPORT_PATH + // 7. QLibraryInfo::QmlImportsPath + + QString installImportsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath); addImportPath(installImportsPath); + auto addEnvImportPath = [this](const char *var) { + if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty(var))) { + const QStringList paths = parseEnvPath(qEnvironmentVariable(var)); + for (int ii = paths.size() - 1; ii >= 0; --ii) + addImportPath(paths.at(ii)); + } + }; + // env import paths - if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("QML2_IMPORT_PATH"))) { - const QString envImportPath = qEnvironmentVariable("QML2_IMPORT_PATH"); -#if defined(Q_OS_WIN) - QLatin1Char pathSep(';'); -#else - QLatin1Char pathSep(':'); -#endif - QStringList paths = envImportPath.split(pathSep, QString::SkipEmptyParts); - for (int ii = paths.count() - 1; ii >= 0; --ii) - addImportPath(paths.at(ii)); - } + addEnvImportPath("QML_IMPORT_PATH"); + addEnvImportPath("QML2_IMPORT_PATH"); + addImportPath(QStringLiteral("qrc:/qt/qml")); addImportPath(QStringLiteral("qrc:/qt-project.org/imports")); addImportPath(QCoreApplication::applicationDirPath()); -} - -QQmlImportDatabase::~QQmlImportDatabase() -{ - clearDirCache(); -} - -/*! - \internal - Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix. - The \a prefix must contain the dot. - - \a qmldirPath is the location of the qmldir file. - */ -QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader, - const QString &qmldirPath, - const QString &qmldirPluginPath, - const QString &baseName, const QStringList &suffixes, - const QString &prefix) -{ - QStringList searchPaths = filePluginPath; - bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath); - if (!qmldirPluginPathIsRelative) - searchPaths.prepend(qmldirPluginPath); - - for (const QString &pluginPath : qAsConst(searchPaths)) { - QString resolvedPath; - if (pluginPath == QLatin1String(".")) { - if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String(".")) - resolvedPath = QDir::cleanPath(qmldirPath + Slash + qmldirPluginPath); - else - resolvedPath = qmldirPath; - } else { - if (QDir::isRelativePath(pluginPath)) - resolvedPath = QDir::cleanPath(qmldirPath + Slash + pluginPath); - else - resolvedPath = pluginPath; + auto addEnvPluginPath = [this](const char *var) { + if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty(var))) { + const QStringList paths = parseEnvPath(qEnvironmentVariable(var)); + for (int ii = paths.size() - 1; ii >= 0; --ii) + addPluginPath(paths.at(ii)); } - - // hack for resources, should probably go away - if (resolvedPath.startsWith(Colon)) - resolvedPath = QCoreApplication::applicationDirPath(); - - if (!resolvedPath.endsWith(Slash)) - resolvedPath += Slash; - - resolvedPath += prefix + baseName; - for (const QString &suffix : suffixes) { - const QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix); - if (!absolutePath.isEmpty()) - return absolutePath; - } - } - - if (qmlImportTrace()) - qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName - << "in" << qmldirPath; - - return QString(); -} - -/*! - \internal - - Returns the result of the merge of \a baseName with \a dir and the platform suffix. - - \table - \header \li Platform \li Valid suffixes - \row \li Windows \li \c .dll - \row \li Unix/Linux \li \c .so - \row \li \macos \li \c .dylib, \c .bundle, \c .so - \endtable - - Version number on unix are ignored. -*/ -QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader, - const QString &qmldirPath, const QString &qmldirPluginPath, - const QString &baseName) -{ -#if defined(Q_OS_WIN) - static const QString prefix; - static const QStringList suffixes = { -# ifdef QT_DEBUG - QLatin1String("d.dll"), // try a qmake-style debug build first -# endif - QLatin1String(".dll") }; -#elif defined(Q_OS_DARWIN) - static const QString prefix = QLatin1String("lib"); - static const QStringList suffixes = { -# ifdef QT_DEBUG - QLatin1String("_debug.dylib"), // try a qmake-style debug build first - QLatin1String(".dylib"), -# else - QLatin1String(".dylib"), - QLatin1String("_debug.dylib"), // try a qmake-style debug build after -# endif - QLatin1String(".so"), - QLatin1String(".bundle") - }; -# else // Unix - static const QString prefix = QLatin1String("lib"); - static const QStringList suffixes = { QLatin1String(".so") }; -#endif - - return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, suffixes, prefix); -} -/*! - \internal -*/ -QStringList QQmlImportDatabase::pluginPathList() const -{ - return filePluginPath; + addEnvPluginPath("QML_PLUGIN_PATH"); +#if defined(Q_OS_ANDROID) + addImportPath(QStringLiteral("qrc:/android_rcc_bundle/qml")); + addEnvPluginPath("QT_BUNDLED_LIBS_PATH"); +#elif defined(Q_OS_MACOS) + // Add the main bundle's Resources/qml directory as an import path, so that QML modules are + // found successfully when running the app from its build dir. + // This is where macdeployqt and our CMake deployment logic puts Qt and user qmldir files. + if (CFBundleRef bundleRef = CFBundleGetMainBundle()) { + if (QCFType<CFURLRef> urlRef = CFBundleCopyResourceURL( + bundleRef, + QCFString(QLatin1String("qml")), 0, 0)) { + if (QCFType<CFURLRef> absoluteUrlRef = CFURLCopyAbsoluteURL(urlRef)) { + if (QCFString path = CFURLCopyFileSystemPath(absoluteUrlRef, kCFURLPOSIXPathStyle)) { + if (QFile::exists(path)) { + addImportPath(QDir(path).canonicalPath()); + } + } + } + } + } +#endif // Q_OS_DARWIN } /*! @@ -1934,9 +1572,7 @@ QStringList QQmlImportDatabase::pluginPathList() const */ void QQmlImportDatabase::setPluginPathList(const QStringList &paths) { - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImportDatabase::setPluginPathList: " << paths; - + qCDebug(lcQmlImport) << "setPluginPathList:" << paths; filePluginPath = paths; } @@ -1945,12 +1581,11 @@ void QQmlImportDatabase::setPluginPathList(const QStringList &paths) */ void QQmlImportDatabase::addPluginPath(const QString& path) { - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path; + qCDebug(lcQmlImport) << "addPluginPath:" << path; QUrl url = QUrl(path); if (url.isRelative() || url.scheme() == QLatin1String("file") - || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path + || (url.scheme().size() == 1 && QFile::exists(path)) ) { // windows path QDir dir = QDir(path); filePluginPath.prepend(dir.canonicalPath()); } else { @@ -1958,13 +1593,17 @@ void QQmlImportDatabase::addPluginPath(const QString& path) } } +QString QQmlImportDatabase::absoluteFilePath(const QString &path) const +{ + return QQmlEnginePrivate::get(engine)->typeLoader.absoluteFilePath(path); +} + /*! \internal */ void QQmlImportDatabase::addImportPath(const QString& path) { - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path; + qCDebug(lcQmlImport) << "addImportPath:" << path; if (path.isEmpty()) return; @@ -1980,7 +1619,7 @@ void QQmlImportDatabase::addImportPath(const QString& path) cPath = QLatin1String("qrc") + path; cPath.replace(Backslash, Slash); } else if (url.isRelative() || - (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path + (url.scheme().size() == 1 && QFile::exists(path)) ) { // windows path QDir dir = QDir(path); cPath = dir.canonicalPath(); } else { @@ -1988,9 +1627,12 @@ void QQmlImportDatabase::addImportPath(const QString& path) cPath.replace(Backslash, Slash); } - if (!cPath.isEmpty() - && !fileImportPath.contains(cPath)) - fileImportPath.prepend(cPath); + if (!cPath.isEmpty()) { + if (fileImportPath.contains(cPath)) + fileImportPath.move(fileImportPath.indexOf(cPath), 0); + else + fileImportPath.prepend(cPath); + } } /*! @@ -2016,10 +1658,11 @@ QStringList QQmlImportDatabase::importPathList(PathType type) const */ void QQmlImportDatabase::setImportPathList(const QStringList &paths) { - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImportDatabase::setImportPathList: " << paths; + qCDebug(lcQmlImport) << "setImportPathList:" << paths; - fileImportPath = paths; + fileImportPath.clear(); + for (auto it = paths.crbegin(); it != paths.crend(); ++it) + addImportPath(*it); // Our existing cached paths may have been invalidated clearDirCache(); @@ -2028,234 +1671,42 @@ void QQmlImportDatabase::setImportPathList(const QStringList &paths) /*! \internal */ -bool QQmlImportDatabase::registerPluginTypes(QObject *instance, const QString &basePath, - const QString &uri, const QString &typeNamespace, int vmaj, QList<QQmlError> *errors) +QTypeRevision QQmlImportDatabase::lockModule(const QString &uri, const QString &typeNamespace, + QTypeRevision version, QList<QQmlError> *errors) { - if (qmlImportTrace()) - qDebug().nospace() << "QQmlImportDatabase::registerPluginTypes: " << uri << " from " << basePath; - - QQmlTypesExtensionInterface *iface = qobject_cast<QQmlTypesExtensionInterface *>(instance); - if (!iface) { - if (errors) { - QQmlError error; - error.setDescription(tr("Module loaded for URI '%1' does not implement QQmlTypesExtensionInterface").arg(typeNamespace)); - errors->prepend(error); - } - return false; + if (!version.hasMajorVersion()) { + version = QQmlMetaType::latestModuleVersion(uri); + if (!version.isValid()) + errors->prepend(moduleNotFoundError(uri, version)); } - - const QByteArray bytes = uri.toUtf8(); - const char *moduleId = bytes.constData(); - - QQmlMetaTypeRegistrationFailureRecorder failureRecorder; - { - // Create a scope for QWriteLocker to keep it as narrow as possible, and - // to ensure that we release it before the call to initalizeEngine below - QMutexLocker lock(QQmlMetaType::typeRegistrationLock()); - - if (!typeNamespace.isEmpty()) { - // This is an 'identified' module - if (typeNamespace != uri) { - // The namespace for type registrations must match the URI for locating the module - if (errors) { - QQmlError error; - error.setDescription(tr("Module namespace '%1' does not match import URI '%2'").arg(typeNamespace).arg(uri)); - errors->prepend(error); - } - return false; - } - - if (QQmlMetaType::namespaceContainsRegistrations(typeNamespace, vmaj)) { - // Other modules have already installed to this namespace - if (errors) { - QQmlError error; - error.setDescription(tr("Namespace '%1' has already been used for type registration").arg(typeNamespace)); - errors->prepend(error); - } - return false; - } else { - QQmlMetaType::protectNamespace(typeNamespace); - } - } else { - // This is not an identified module - provide a warning - qWarning().nospace() << qPrintable(tr("Module '%1' does not contain a module identifier directive - it cannot be protected from external registrations.").arg(uri)); - } - - QQmlMetaType::setTypeRegistrationNamespace(typeNamespace); - - if (QQmlExtensionPlugin *plugin = qobject_cast<QQmlExtensionPlugin *>(instance)) { - // basepath should point to the directory of the module, not the plugin file itself: - QQmlExtensionPluginPrivate::get(plugin)->baseUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(basePath); - } - - iface->registerTypes(moduleId); - QQmlMetaType::setTypeRegistrationNamespace(QString()); - } // QWriteLocker lock(QQmlMetaType::typeRegistrationLock()) - - if (!failureRecorder.failures().isEmpty()) { - if (errors) { - for (const QString &failure : failureRecorder.failures()) { - QQmlError error; - error.setDescription(failure); - errors->prepend(error); - } - } - return false; + if (version.hasMajorVersion() && !typeNamespace.isEmpty() + && !QQmlMetaType::protectModule(uri, version, true)) { + // Not being able to protect the module means there are not types registered for it, + // means the plugin we loaded didn't provide any, means we didn't find the module. + // We output the generic error message as depending on the load order of imports we may + // hit this path or another one that only figures "plugin is already loaded but module + // unavailable" and doesn't try to protect it anymore. + errors->prepend(moduleNotFoundError(uri, version)); + return QTypeRevision(); } - return true; + return version; } -/*! - \internal -*/ -bool QQmlImportDatabase::importStaticPlugin(QObject *instance, const QString &basePath, - const QString &uri, const QString &typeNamespace, int vmaj, QList<QQmlError> *errors) +bool QQmlImportDatabase::removeDynamicPlugin(const QString &pluginId) { - // Dynamic plugins are differentiated by their filepath. For static plugins we - // don't have that information so we use their address as key instead. - const QString uniquePluginID = QString::asprintf("%p", instance); - { - StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes(); - QMutexLocker lock(&plugins->mutex); - - // Plugin types are global across all engines and should only be - // registered once. But each engine still needs to be initialized. - bool typesRegistered = plugins->contains(uniquePluginID); - - if (typesRegistered) { - Q_ASSERT_X(plugins->value(uniquePluginID).uri == uri, - "QQmlImportDatabase::importStaticPlugin", - "Internal error: Static plugin imported previously with different uri"); - } else { - RegisteredPlugin plugin; - plugin.uri = uri; - plugin.loader = nullptr; - plugins->insert(uniquePluginID, plugin); - - if (!registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors)) - return false; - } - - // Release the lock on plugins early as we're done with the global part. Releasing the lock - // also allows other QML loader threads to acquire the lock while this thread is blocking - // in the initializeEngine call to the gui thread (which in turn may be busy waiting for - // other QML loader threads and thus not process the initializeEngine call). - } - - // The plugin's per-engine initialization does not need lock protection, as this function is - // only called from the engine specific loader thread and importDynamicPlugin as well as - // importStaticPlugin are the only places of access. - if (!initializedPlugins.contains(uniquePluginID)) { - initializedPlugins.insert(uniquePluginID); - - if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) { - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); - ep->typeLoader.initializeEngine(eiface, uri.toUtf8().constData()); - } - } - - return true; + return QQmlPluginImporter::removePlugin(pluginId); } -/*! - \internal -*/ -bool QQmlImportDatabase::importDynamicPlugin(const QString &filePath, const QString &uri, - const QString &typeNamespace, int vmaj, QList<QQmlError> *errors) +QStringList QQmlImportDatabase::dynamicPlugins() const { -#if QT_CONFIG(library) - QFileInfo fileInfo(filePath); - const QString absoluteFilePath = fileInfo.absoluteFilePath(); - - QObject *instance = nullptr; - bool engineInitialized = initializedPlugins.contains(absoluteFilePath); - { - StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes(); - QMutexLocker lock(&plugins->mutex); - bool typesRegistered = plugins->contains(absoluteFilePath); - - if (typesRegistered) { - Q_ASSERT_X(plugins->value(absoluteFilePath).uri == uri, - "QQmlImportDatabase::importDynamicPlugin", - "Internal error: Plugin imported previously with different uri"); - } - - if (!engineInitialized || !typesRegistered) { - if (!QQml_isFileCaseCorrect(absoluteFilePath)) { - if (errors) { - QQmlError error; - error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath)); - errors->prepend(error); - } - return false; - } - - QPluginLoader* loader = nullptr; - if (!typesRegistered) { - loader = new QPluginLoader(absoluteFilePath); - - if (!loader->load()) { - if (errors) { - QQmlError error; - error.setDescription(loader->errorString()); - errors->prepend(error); - } - delete loader; - return false; - } - } else { - loader = plugins->value(absoluteFilePath).loader; - } - - instance = loader->instance(); - - if (!typesRegistered) { - RegisteredPlugin plugin; - plugin.uri = uri; - plugin.loader = loader; - plugins->insert(absoluteFilePath, plugin); - - // Continue with shared code path for dynamic and static plugins: - if (!registerPluginTypes(instance, fileInfo.absolutePath(), uri, typeNamespace, vmaj, errors)) - return false; - } - } - - // Release the lock on plugins early as we're done with the global part. Releasing the lock - // also allows other QML loader threads to acquire the lock while this thread is blocking - // in the initializeEngine call to the gui thread (which in turn may be busy waiting for - // other QML loader threads and thus not process the initializeEngine call). - } - - - if (!engineInitialized) { - // The plugin's per-engine initialization does not need lock protection, as this function is - // only called from the engine specific loader thread and importDynamicPlugin as well as - // importStaticPlugin are the only places of access. - initializedPlugins.insert(absoluteFilePath); - - if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) { - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); - ep->typeLoader.initializeEngine(eiface, uri.toUtf8().constData()); - } - } - - return true; -#else - Q_UNUSED(filePath); - Q_UNUSED(uri); - Q_UNUSED(typeNamespace); - Q_UNUSED(vmaj); - Q_UNUSED(errors); - return false; -#endif + return QQmlPluginImporter::plugins(); } void QQmlImportDatabase::clearDirCache() { - QStringHash<QmldirCache *>::ConstIterator itr = qmldirCache.begin(); - while (itr != qmldirCache.end()) { + QStringHash<QmldirCache *>::ConstIterator itr = qmldirCache.constBegin(); + while (itr != qmldirCache.constEnd()) { QmldirCache *cache = *itr; do { QmldirCache *nextCache = cache->next; |