Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlimport.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 Crimson AS <info@crimson.no>
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qqmlimport_p.h"
6
7#include <QtCore/qdebug.h>
8#include <QtCore/qdir.h>
9#include <QtQml/qqmlfile.h>
10#include <QtCore/qfileinfo.h>
11#include <QtCore/qpluginloader.h>
12#include <QtCore/qloggingcategory.h>
13#include <QtQml/qqmlextensioninterface.h>
14#include <QtQml/qqmlextensionplugin.h>
15#include <private/qqmlextensionplugin_p.h>
16#include <private/qqmlglobal_p.h>
17#include <private/qqmltypenamecache_p.h>
18#include <private/qqmlengine_p.h>
19#include <private/qqmltypemodule_p.h>
20#include <private/qqmltypeloaderqmldircontent_p.h>
21#include <private/qqmlpluginimporter_p.h>
22#include <QtCore/qjsonobject.h>
23#include <QtCore/qjsonarray.h>
24#include <QtQml/private/qqmltype_p_p.h>
25#include <QtQml/private/qqmlimportresolver_p.h>
26
27#include <algorithm>
28#include <functional>
29
30using namespace Qt::Literals::StringLiterals;
31
33
35
37{
39public:
40
42 {
43 // We have to explicitly setEnabled() here because for categories that start with
44 // "qt." it won't accept QtDebugMsg as argument. Debug messages are off by default
45 // for all Qt logging categories.
46 if (qmlImportTrace())
47 m_category.setEnabled(QtDebugMsg, true);
48 }
49
51
52 const QLoggingCategory &category() const { return m_category; }
53
54private:
55 QLoggingCategory m_category;
56};
57
59{
60 static const QmlImportCategoryHolder holder;
61 return holder.category();
62}
63
65
66static const QLatin1Char Dot('.');
67static const QLatin1Char Slash('/');
68static const QLatin1Char Backslash('\\');
69static const QLatin1Char Colon(':');
70static const QLatin1String Slash_qmldir("/qmldir");
71static const QLatin1String String_qmldir("qmldir");
72static const QString dotqml_string(QStringLiteral(".qml"));
73static const QString dotuidotqml_string(QStringLiteral(".ui.qml"));
74static bool designerSupportRequired = false;
75
76namespace {
77
78QTypeRevision relevantVersion(const QString &uri, QTypeRevision version)
79{
80 return QQmlMetaType::latestModuleVersion(uri).isValid() ? version : QTypeRevision();
81}
82
83QString resolveLocalUrl(const QString &url, const QString &relative)
84{
85 if (relative.contains(Colon)) {
86 // contains a host name
87 return QUrl(url).resolved(QUrl(relative)).toString();
88 } else if (relative.isEmpty()) {
89 return url;
90 } else if (relative.at(0) == Slash || !url.contains(Slash)) {
91 return relative;
92 } else {
93 const QStringView baseRef = QStringView{url}.left(url.lastIndexOf(Slash) + 1);
94 if (relative == QLatin1String("."))
95 return baseRef.toString();
96
97 QString base = baseRef + relative;
98
99 // Remove any relative directory elements in the path
100 int length = base.size();
101 int index = 0;
102 while ((index = base.indexOf(QLatin1String("/."), index)) != -1) {
103 if ((length > (index + 2)) && (base.at(index + 2) == Dot) &&
104 (length == (index + 3) || (base.at(index + 3) == Slash))) {
105 // Either "/../" or "/..<END>"
106 int previous = base.lastIndexOf(Slash, index - 1);
107 if (previous == -1)
108 break;
109
110 int removeLength = (index - previous) + 3;
111 base.remove(previous + 1, removeLength);
112 length -= removeLength;
113 index = previous;
114 } else if ((length == (index + 2)) || (base.at(index + 2) == Slash)) {
115 // Either "/./" or "/.<END>"
116 base.remove(index, 2);
117 length -= 2;
118 } else {
119 ++index;
120 }
121 }
122
123 return base;
124 }
125}
126
127} // namespace
128
129/*!
130 \internal
131 \class QQmlImportInstance
132
133 A QQmlImportType represents a single import of a document, held within a
134 namespace.
135
136 \note The uri here may not necessarily be unique (e.g. for file imports).
137
138 \note Version numbers may be -1 for file imports: this means that no
139 version was specified as part of the import. Type resolution will be
140 responsible for attempting to find the "best" possible version.
141*/
142
143/*!
144 \internal
145 \class QQmlImportNamespace
146
147 A QQmlImportNamespace is a way of seperating imports into a local namespace.
148
149 Within a QML document, there is at least one namespace (the
150 "unqualified set") where imports without a qualifier are placed, i.e:
151
152 import QtQuick 2.6
153
154 will have a single namespace (the unqualified set) containing a single import
155 for QtQuick 2.6. However, there may be others if an import statement gives
156 a qualifier, i.e the following will result in an additional new
157 QQmlImportNamespace in the qualified set:
158
159 import MyFoo 1.0 as Foo
160*/
161
162/*!
163\class QQmlImports
164\brief The QQmlImports class encapsulates one QML document's import statements.
165\internal
166*/
167
168QQmlError QQmlImports::moduleNotFoundError(const QString &uri, QTypeRevision version)
169{
170 QQmlError error;
171 if (version.hasMajorVersion()) {
172 error.setDescription(QQmlImports::tr(
173 "module \"%1\" version %2.%3 is not installed")
174 .arg(uri).arg(version.majorVersion())
175 .arg(version.hasMinorVersion()
176 ? QString::number(version.minorVersion())
177 : QLatin1String("x")));
178 } else {
179 error.setDescription(QQmlImports::tr("module \"%1\" is not installed")
180 .arg(uri));
181 }
182 return error;
183}
184
185QTypeRevision QQmlImports::validVersion(QTypeRevision version)
186{
187 // If the given version is invalid, return a valid but useless version to signal "It's OK".
188 return version.isValid() ? version : QTypeRevision::fromMinorVersion(0);
189}
190
191/*!
192 Sets the base URL to be used for all relative file imports added.
193*/
194void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString)
195{
196 m_baseUrl = url;
197
198 if (urlString.isEmpty())
199 m_base = url.toString();
200 else
201 m_base = urlString;
202}
203
204/*!
205 \fn QQmlImports::baseUrl()
206 \internal
207
208 Returns the base URL to be used for all relative file imports added.
209*/
210
211/*
212 \internal
213
214 This method is responsible for populating data of all types visible in this
215 document's imports into the \a cache for resolution elsewhere (e.g. in JS,
216 or when loading additional types).
217
218 \note This is for C++ types only. Composite types are handled separately,
219 as they do not have a QQmlTypeModule.
220*/
221void QQmlImports::populateCache(QQmlTypeNameCache *cache) const
222{
223 const QQmlImportNamespace &set = m_unqualifiedset;
224
225 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
226 const QQmlImportInstance *import = set.imports.at(ii);
227 QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->version);
228 if (module) {
229 cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, import->version));
230 }
231 }
232
233 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) {
234
235 const QQmlImportNamespace &set = *ns;
236
237 // positioning is important; we must create the namespace even if there is no module.
238 QQmlImportRef &typeimport = cache->m_namedImports[set.prefix];
239 typeimport.m_qualifier = set.prefix;
240
241 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
242 const QQmlImportInstance *import = set.imports.at(ii);
243 QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->version);
244 if (module) {
245 QQmlImportRef &typeimport = cache->m_namedImports[set.prefix];
246 typeimport.modules.append(QQmlTypeModuleVersion(module, import->version));
247 }
248 }
249 }
250}
251
252// We need to exclude the entry for the current baseUrl. This can happen for example
253// when handling qmldir files on the remote dir case and the current type is marked as
254// singleton.
255bool excludeBaseUrl(const QString &importUrl, const QString &fileName, const QString &baseUrl)
256{
257 if (importUrl.isEmpty())
258 return false;
259
260 if (baseUrl.startsWith(importUrl))
261 {
262 if (fileName == QStringView{baseUrl}.mid(importUrl.size()))
263 return false;
264 }
265
266 return true;
267}
268
269void findCompositeSingletons(const QQmlImportNamespace &set, QList<QQmlImports::CompositeSingletonReference> &resultList, const QUrl &baseUrl)
270{
271 typedef QQmlDirComponents::const_iterator ConstIterator;
272
273 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
274 const QQmlImportInstance *import = set.imports.at(ii);
275
276 const QQmlDirComponents &components = import->qmlDirComponents;
277
278 const QTypeRevision importVersion = import->version;
279 auto shouldSkipSingleton = [importVersion](QTypeRevision singletonVersion) -> bool {
280 return importVersion.hasMajorVersion() &&
281 (singletonVersion.majorVersion() > importVersion.majorVersion()
282 || (singletonVersion.majorVersion() == importVersion.majorVersion()
283 && singletonVersion.minorVersion() > importVersion.minorVersion()));
284 };
285
286 ConstIterator cend = components.constEnd();
287 for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
288 if (cit->singleton && excludeBaseUrl(import->url, cit->fileName, baseUrl.toString())) {
289 if (shouldSkipSingleton(cit->version))
290 continue;
291 QQmlImports::CompositeSingletonReference ref;
292 ref.typeName = cit->typeName;
293 ref.prefix = set.prefix;
294 ref.version = cit->version;
295 resultList.append(ref);
296 }
297 }
298
299 if (QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->version)) {
300 module->walkCompositeSingletons([&resultList, &set, &shouldSkipSingleton](const QQmlType &singleton) {
301 if (shouldSkipSingleton(singleton.version()))
302 return;
303 QQmlImports::CompositeSingletonReference ref;
304 ref.typeName = singleton.elementName();
305 ref.prefix = set.prefix;
306 ref.version = singleton.version();
307 resultList.append(ref);
308 });
309 }
310 }
311}
312
313/*
314 \internal
315
316 Returns a list of all composite singletons present in this document's
317 imports.
318
319 This information is used by QQmlTypeLoader to ensure that composite singletons
320 are marked as dependencies during type loading.
321*/
322QList<QQmlImports::CompositeSingletonReference> QQmlImports::resolvedCompositeSingletons() const
323{
324 QList<QQmlImports::CompositeSingletonReference> compositeSingletons;
325
326 const QQmlImportNamespace &set = m_unqualifiedset;
327 findCompositeSingletons(set, compositeSingletons, baseUrl());
328
329 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) {
330 const QQmlImportNamespace &set = *ns;
331 findCompositeSingletons(set, compositeSingletons, baseUrl());
332 }
333
334 std::stable_sort(compositeSingletons.begin(), compositeSingletons.end(),
335 [](const QQmlImports::CompositeSingletonReference &lhs,
336 const QQmlImports::CompositeSingletonReference &rhs) {
337 if (lhs.prefix != rhs.prefix)
338 return lhs.prefix < rhs.prefix;
339
340 if (lhs.typeName != rhs.typeName)
341 return lhs.typeName < rhs.typeName;
342
343 return lhs.version.majorVersion() != rhs.version.majorVersion()
344 ? lhs.version.majorVersion() < rhs.version.majorVersion()
345 : lhs.version.minorVersion() < rhs.version.minorVersion();
346 });
347
348 return compositeSingletons;
349}
350
351/*
352 \internal
353
354 Returns a list of scripts imported by this document. This is used by
355 QQmlTypeLoader to properly handle dependencies on imported scripts.
356*/
357QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
358{
359 QList<QQmlImports::ScriptReference> scripts;
360
361 const QQmlImportNamespace &set = m_unqualifiedset;
362
363 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
364 const QQmlImportInstance *import = set.imports.at(ii);
365
366 for (const QQmlDirParser::Script &script : import->qmlDirScripts) {
367 ScriptReference ref;
368 ref.nameSpace = script.nameSpace;
369 ref.fileName = QUrl(script.fileName);
370 ref.location = QUrl(import->url).resolved(ref.fileName);
371 scripts.append(ref);
372 }
373 }
374
375 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) {
376 const QQmlImportNamespace &set = *ns;
377
378 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
379 const QQmlImportInstance *import = set.imports.at(ii);
380
381 for (const QQmlDirParser::Script &script : import->qmlDirScripts) {
382 ScriptReference ref;
383 ref.nameSpace = script.nameSpace;
384 ref.qualifier = set.prefix;
385 ref.fileName = QUrl(script.fileName);
386 ref.location = QUrl(import->url).resolved(ref.fileName);
387 scripts.append(ref);
388 }
389 }
390 }
391
392 return scripts;
393}
394
395/*!
396 Forms complete paths to a qmldir file, from a base URL, a module URI and version specification.
397
398 For example, QtQml.Models 2.0:
399 - base/QtQml/Models.2.0/qmldir
400 - base/QtQml.2.0/Models/qmldir
401 - base/QtQml/Models.2/qmldir
402 - base/QtQml.2/Models/qmldir
403 - base/QtQml/Models/qmldir
404*/
405QStringList QQmlImports::completeQmldirPaths(const QString &uri, const QStringList &basePaths,
406 QTypeRevision version)
407{
408 QStringList paths = qQmlResolveImportPaths(uri, basePaths, version);
409 for (QString &path : paths)
410 path += Slash_qmldir;
411 return paths;
412}
413
414QString QQmlImports::versionString(QTypeRevision version, ImportVersion versionMode)
415{
416 if (versionMode == QQmlImports::FullyVersioned) {
417 // extension with fully encoded version number (eg. MyModule.3.2)
418 return QString::asprintf(".%d.%d", version.majorVersion(), version.minorVersion());
419 } else if (versionMode == QQmlImports::PartiallyVersioned) {
420 // extension with encoded version major (eg. MyModule.3)
421 return QString::asprintf(".%d", version.majorVersion());
422 } // else extension without version number (eg. MyModule)
423 return QString();
424}
425
426/*!
427 \internal
428
429 The given (namespace qualified) \a type is resolved to either
430 \list
431 \li a QQmlImportNamespace stored at \a ns_return, or
432 \li a QQmlType stored at \a type_return,
433 \endlist
434
435 If any return pointer is 0, the corresponding search is not done.
436
437 \sa addFileImport(), addLibraryImport
438*/
439bool QQmlImports::resolveType(
440 QQmlTypeLoader *typeLoader, const QHashedStringRef &type, QQmlType *type_return,
441 QTypeRevision *version_return, QQmlImportNamespace **ns_return, QList<QQmlError> *errors,
442 QQmlType::RegistrationType registrationType, bool *typeRecursionDetected) const
443{
444 QQmlImportNamespace *ns = findQualifiedNamespace(type);
445 if (ns) {
446 if (ns_return)
447 *ns_return = ns;
448 return true;
449 }
450 if (type_return) {
451 if (resolveType(typeLoader, type, version_return, type_return, errors, registrationType,
452 typeRecursionDetected)) {
453 if (lcQmlImport().isDebugEnabled()) {
454#define RESOLVE_TYPE_DEBUG qCDebug(lcQmlImport)
455 << "resolveType:" << qPrintable(baseUrl().toString()) << type.toString() << " => "
456
457 if (type_return && type_return->isValid()) {
458 if (type_return->isCompositeSingleton())
459 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL-SINGLETON";
460 else if (type_return->isComposite())
461 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL";
462 else if (type_return->isInlineComponentType())
463 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE(INLINECOMPONENT)";
464 else
465 RESOLVE_TYPE_DEBUG << type_return->typeName() << " TYPE";
466 }
467#undef RESOLVE_TYPE_DEBUG
468 }
469 return true;
470 }
471 }
472 return false;
473}
474
475bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl,
476 const QQmlTypeLoaderQmldirContent &qmldir,
477 QQmlImportNamespace *nameSpace, QList<QQmlError> *errors)
478{
479 Q_ASSERT(resolvedUrl.endsWith(Slash));
480 url = resolvedUrl;
481
482 qmlDirComponents = qmldir.components();
483
484 const QQmlDirScripts &scripts = qmldir.scripts();
485 if (!scripts.isEmpty()) {
486 // Verify that we haven't imported these scripts already
487 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
488 it != nameSpace->imports.constEnd(); ++it) {
489 if ((*it != this) && ((*it)->uri == uri)) {
490 QQmlError error;
491 error.setDescription(
492 QQmlImports::tr("\"%1\" is ambiguous. Found in %2 and in %3")
493 .arg(uri, url, (*it)->url));
494 errors->prepend(error);
495 return false;
496 }
497 }
498
499 qmlDirScripts = getVersionedScripts(scripts, version);
500 }
501
502 return true;
503}
504
505QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qmldirscripts,
506 QTypeRevision version)
507{
508 QMap<QString, QQmlDirParser::Script> versioned;
509
510 for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
511 sit != qmldirscripts.constEnd(); ++sit) {
512 // Only include scripts that match our requested version
513 if ((!version.hasMajorVersion() || (sit->version.majorVersion() == version.majorVersion()))
514 && (!version.hasMinorVersion()
515 || (sit->version.minorVersion() <= version.minorVersion()))) {
516 // Load the highest version that matches
517 const auto vit = versioned.constFind(sit->nameSpace);
518 if (vit == versioned.cend()
519 || (vit->version.minorVersion() < sit->version.minorVersion())) {
520 versioned.insert(sit->nameSpace, *sit);
521 }
522 }
523 }
524
525 return versioned.values();
526}
527
528/*!
529 \fn QQmlImports::resolveType(QQmlImportNamespace *ns, const QHashedStringRef &type, QQmlType *type_return, QTypeRevision *version_return, QQmlType::RegistrationType registrationType = QQmlType::AnyRegistrationType) const
530 \internal
531
532 Searching \e only in the namespace \a ns (previously returned in a call to
533 resolveType(), \a type is found and returned to
534 a QQmlType stored at \a type_return. If the type is from a QML file, the returned
535 type will be a CompositeType.
536
537 If the return pointer is 0, the corresponding search is not done.
538*/
539
540bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type,
541 QTypeRevision *version_return, QQmlType *type_return,
542 const QString *base, bool *typeRecursionDetected,
543 QQmlType::RegistrationType registrationType,
544 QQmlImport::RecursionRestriction recursionRestriction,
545 QList<QQmlError> *errors) const
546{
547 // QQmlMetaType assumes that without a URI, it should look for types in any module
548 // But QQmlImportInstance without a URI represents a directory import, and _must not_ find
549 // types from completely unrelated modules
550 QQmlType t = uri.isEmpty() ? QQmlType() : QQmlMetaType::qmlType(type, uri, version);
551 if (t.isValid()) {
552 if (version_return)
553 *version_return = version;
554 if (type_return)
555 *type_return = t;
556 return true;
557 }
558
559 const QString typeStr = type.toString();
560 if (isInlineComponent) {
561 Q_ASSERT(type_return);
562 bool ret = uri == typeStr;
563 if (ret) {
564 Q_ASSERT(!type_return->isValid());
565 *type_return = QQmlMetaType::fetchOrCreateInlineComponentTypeForUrl(QUrl(url));
566 }
567 return ret;
568 }
569 QQmlDirComponents::ConstIterator it = qmlDirComponents.find(typeStr), end = qmlDirComponents.end();
570 if (it != end) {
571 QString componentUrl;
572 QQmlMetaType::CompositeTypeLookupMode lookupMode = QQmlMetaType::NonSingleton;
573 QQmlDirComponents::ConstIterator candidate = end;
574 for ( ; it != end && it.key() == typeStr; ++it) {
575 const QQmlDirParser::Component &c = *it;
576 switch (registrationType) {
577 case QQmlType::AnyRegistrationType:
578 break;
579 case QQmlType::CompositeSingletonType:
580 if (!c.singleton)
581 continue;
582 break;
583 default:
584 if (c.singleton)
585 continue;
586 break;
587 }
588
589 // importing invalid version means import ALL versions
590 if (!version.hasMajorVersion() || (implicitlyImported && c.internal)
591 // allow the implicit import of internal types
592 || (c.version.majorVersion() == version.majorVersion()
593 && c.version.minorVersion() <= version.minorVersion())) {
594 // Is this better than the previous candidate?
595 if ((candidate == end)
596 || (c.version.majorVersion() > candidate->version.majorVersion())
597 || ((c.version.majorVersion() == candidate->version.majorVersion())
598 && (c.version.minorVersion() > candidate->version.minorVersion()))) {
599 if (base) {
600 componentUrl = resolveLocalUrl(QString(url + c.typeName + dotqml_string), c.fileName);
601 if (c.internal) {
602 if (resolveLocalUrl(*base, c.fileName) != componentUrl)
603 continue; // failed attempt to access an internal type
604 }
605
606 const bool recursion = *base == componentUrl;
607 if (typeRecursionDetected)
608 *typeRecursionDetected = recursion;
609
610 if (recursionRestriction == QQmlImport::PreventRecursion && recursion) {
611 continue; // no recursion
612 }
613 }
614
615 // This is our best candidate so far
616 candidate = it;
617 lookupMode = c.singleton ? QQmlMetaType::Singleton : QQmlMetaType::NonSingleton;
618 }
619 }
620 }
621
622 if (candidate != end) {
623 if (!base) // ensure we have a componentUrl
624 componentUrl = resolveLocalUrl(QString(url + candidate->typeName + dotqml_string), candidate->fileName);
625 QQmlType returnType = QQmlMetaType::typeForUrl(
626 typeLoader->interceptUrl(QUrl(componentUrl), QQmlAbstractUrlInterceptor::QmlFile),
627 type, lookupMode, nullptr, candidate->version);
628 if (version_return)
629 *version_return = candidate->version;
630 if (type_return)
631 *type_return = returnType;
632 return returnType.isValid();
633 }
634 } else if (!isLibrary) {
635 // the base path of the import if it's a local file
636 const QString localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url);
637 if (localDirectoryPath.isEmpty())
638 return false;
639
640 QString qmlUrl;
641
642 const QString urlsToTry[2] = {
643 typeStr + dotqml_string, // Type -> Type.qml
644 typeStr + dotuidotqml_string // Type -> Type.ui.qml
645 };
646 for (const QString &urlToTry : urlsToTry) {
647 if (typeLoader->fileExists(localDirectoryPath, urlToTry)) {
648 qmlUrl = url + urlToTry;
649 break;
650 }
651 }
652
653 if (!qmlUrl.isEmpty()) {
654 const bool recursion = base && *base == qmlUrl;
655 if (typeRecursionDetected)
656 *typeRecursionDetected = recursion;
657 if (recursionRestriction == QQmlImport::AllowRecursion || !recursion) {
658 QQmlType returnType = QQmlMetaType::typeForUrl(
659 typeLoader->interceptUrl(QUrl(qmlUrl), QQmlAbstractUrlInterceptor::QmlFile),
660 type, registrationType == QQmlType::CompositeSingletonType
661 ? QQmlMetaType::Singleton
662 : QQmlMetaType::NonSingleton,
663 errors);
664 if (type_return)
665 *type_return = returnType;
666 return returnType.isValid();
667 }
668 }
669 }
670
671 return false;
672}
673
674bool QQmlImports::resolveType(
675 QQmlTypeLoader *typeLoader, const QHashedStringRef &type, QTypeRevision *version_return,
676 QQmlType *type_return, QList<QQmlError> *errors,
677 QQmlType::RegistrationType registrationType, bool *typeRecursionDetected) const
678{
679 const QList<QHashedStringRef> splitName = type.split(Dot);
680 auto resolveTypeInNamespace = [&](
681 QHashedStringRef unqualifiedtype, QQmlImportNamespace *nameSpace,
682 QList<QQmlError> *errors) -> bool {
683 if (nameSpace->resolveType(
684 typeLoader, unqualifiedtype, version_return, type_return, &m_base, errors,
685 registrationType, typeRecursionDetected))
686 return true;
687 if (nameSpace->imports.size() == 1
688 && !nameSpace->imports.at(0)->isLibrary
689 && type_return
690 && nameSpace != &m_unqualifiedset) {
691 // qualified, and only 1 url
692 const QString urlString = resolveLocalUrl(
693 nameSpace->imports.at(0)->url, unqualifiedtype.toString() + dotqml_string);
694 *type_return = QQmlMetaType::typeForUrl(
695 typeLoader->interceptUrl(QUrl(urlString), QQmlAbstractUrlInterceptor::QmlFile),
696 type, QQmlMetaType::NonSingleton, errors);
697 return type_return->isValid();
698 }
699 return false;
700 };
701 switch (splitName.size()) {
702 case 1: {
703 // must be a simple type
704 return resolveTypeInNamespace(type, &m_unqualifiedset, errors);
705 }
706 case 2: {
707 // either namespace + simple type OR simple type + inline component
708 QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0));
709 if (s) {
710 // namespace + simple type
711 return resolveTypeInNamespace(splitName.at(1), s, errors);
712 } else {
713 if (resolveTypeInNamespace(splitName.at(0), &m_unqualifiedset, nullptr)) {
714 // either simple type + inline component
715 *type_return = QQmlMetaType::inlineComponentType(
716 *type_return, splitName.at(1).toString());
717 return true;
718 } else {
719 // or a failure
720 if (errors) {
721 QQmlError error;
722 error.setDescription(QQmlImports::tr("- %1 is neither a type nor a namespace").arg(splitName.at(0).toString()));
723 errors->prepend(error);
724 }
725 return false;
726 }
727 }
728 }
729 case 3: {
730 // must be namespace + simple type + inline component
731 QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0));
732 QQmlError error;
733 if (!s) {
734 error.setDescription(QQmlImports::tr("- %1 is not a namespace").arg(splitName.at(0).toString()));
735 } else {
736 if (resolveTypeInNamespace(splitName.at(1), s, nullptr)) {
737 *type_return = QQmlMetaType::inlineComponentType(
738 *type_return, splitName.at(2).toString());
739 return true;
740 } else {
741 error.setDescription(QQmlImports::tr("- %1 is not a type").arg(splitName.at(1).toString()));
742 }
743 }
744 if (errors) {
745 errors->prepend(error);
746 }
747 return false;
748 }
749 default: {
750 // all other numbers suggest a user error
751 if (errors) {
752 QQmlError error;
753 error.setDescription(QQmlImports::tr("- nested namespaces not allowed"));
754 errors->prepend(error);
755 }
756 return false;
757 }
758 }
759 Q_UNREACHABLE();
760}
761
763 const QString &moduleUri, QTypeRevision version) const
764{
765 const auto end = imports.cend();
766 const auto it = std::find_if(imports.cbegin(), end, [&](const QQmlImportInstance *import) {
767 return import->uri == moduleUri && import->version == version;
768 });
769 return it == end ? nullptr : *it;
770}
771
773 const QString &location, QTypeRevision version) const
774{
775 const auto end = imports.cend();
776 const auto it = std::find_if(imports.cbegin(), end, [&](const QQmlImportInstance *import) {
777 return import->url == location && import->version == version;
778 });
779 return it == end ? nullptr : *it;
780}
781
782bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type,
783 QTypeRevision *version_return, QQmlType *type_return,
784 const QString *base, QList<QQmlError> *errors,
785 QQmlType::RegistrationType registrationType,
786 bool *typeRecursionDetected)
787{
788 QQmlImport::RecursionRestriction recursionRestriction =
790
791 bool localTypeRecursionDetected = false;
792 if (!typeRecursionDetected)
793 typeRecursionDetected = &localTypeRecursionDetected;
794
795 // TODO: move the sorting somewhere else and make resolveType() const.
796 if (needsSorting()) {
797 std::stable_partition(imports.begin(), imports.end(), [](QQmlImportInstance *import) {
798 return import->isInlineComponent;
799 });
801 }
802 for (int i=0; i<imports.size(); ++i) {
803 const QQmlImportInstance *import = imports.at(i);
804 if (import->resolveType(typeLoader, type, version_return, type_return, base,
805 typeRecursionDetected, registrationType, recursionRestriction, errors)) {
806 if (qmlCheckTypes()) {
807 // check for type clashes
808 for (int j = i+1; j<imports.size(); ++j) {
809 const QQmlImportInstance *import2 = imports.at(j);
810 if (import2->resolveType(typeLoader, type, version_return, nullptr, base,
811 nullptr, registrationType)) {
812 if (errors) {
813 QString u1 = import->url;
814 QString u2 = import2->url;
815 if (base) {
816 QStringView b(*base);
817 int dot = b.lastIndexOf(Dot);
818 if (dot >= 0) {
819 b = b.left(dot+1);
820 QStringView l = b.left(dot);
821 if (u1.startsWith(b))
822 u1 = u1.mid(b.size());
823 else if (u1 == l)
824 u1 = QQmlImports::tr("local directory");
825 if (u2.startsWith(b))
826 u2 = u2.mid(b.size());
827 else if (u2 == l)
828 u2 = QQmlImports::tr("local directory");
829 }
830 }
831
832 QQmlError error;
833 if (u1 != u2) {
834 error.setDescription(
835 QQmlImports::tr(
836 "is ambiguous. Found in %1 and in %2")
837 .arg(u1, u2));
838 } else {
839 error.setDescription(
840 QQmlImports::tr(
841 "is ambiguous. Found in %1 in version "
842 "%2.%3 and %4.%5")
843 .arg(u1)
844 .arg(import->version.majorVersion())
845 .arg(import->version.minorVersion())
846 .arg(import2->version.majorVersion())
847 .arg(import2->version.minorVersion()));
848 }
849 errors->prepend(error);
850 }
851 return false;
852 }
853 }
854 }
855 return true;
856 }
857 }
858 if (errors) {
859 QQmlError error;
860 if (*typeRecursionDetected)
861 error.setDescription(QQmlImports::tr("is instantiated recursively"));
862 else
863 error.setDescription(QQmlImports::tr("is not a type"));
864 errors->prepend(error);
865 }
866 return false;
867}
868
869QQmlImportNamespace *QQmlImports::findQualifiedNamespace(const QHashedStringRef &prefix) const
870{
871 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) {
872 if (prefix == ns->prefix)
873 return ns;
874 }
875 return nullptr;
876}
877
878/*
879Import an extension defined by a qmldir file.
880*/
881QTypeRevision QQmlImports::importExtension(
882 QQmlTypeLoader *typeLoader, const QString &uri, QTypeRevision version,
883 const QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors)
884{
885 Q_ASSERT(qmldir->hasContent());
886
887 qCDebug(lcQmlImport)
888 << "importExtension:" << qPrintable(m_base) << "loaded" << qmldir->qmldirLocation();
889
890 if (designerSupportRequired && !qmldir->designerSupported()) {
891 if (errors) {
892 QQmlError error;
893 error.setDescription(
894 QQmlImports::tr("module does not support the designer \"%1\"")
895 .arg(qmldir->typeNamespace()));
896 error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation()));
897 errors->prepend(error);
898 }
899 return QTypeRevision();
900 }
901
902 if (qmldir->plugins().isEmpty())
903 return validVersion(version);
904
905 QQmlPluginImporter importer(uri, version, qmldir, typeLoader, errors);
906 return importer.importPlugins();
907}
908
909void QQmlImports::registerBuiltinModuleTypes(
910 const QQmlTypeLoaderQmldirContent &qmldir, QTypeRevision version)
911{
912 if (!qmldir.plugins().isEmpty())
913 return;
914
915 // If the qmldir does not register a plugin, we might still have declaratively
916 // registered types (if we are dealing with an application instead of a library)
917
918 const QString qmldirUri = qmldir.typeNamespace();
919 if (!QQmlMetaType::typeModule(qmldirUri, version))
920 QQmlMetaType::qmlRegisterModuleTypes(qmldirUri);
921}
922
923QString QQmlImports::redirectQmldirContent(
924 QQmlTypeLoader *typeLoader, QQmlTypeLoaderQmldirContent *qmldir)
925{
926 const QString preferredPath = qmldir->preferredPath();
927 const QString url = preferredPath.startsWith(u':')
928 ? QStringLiteral("qrc") + preferredPath
929 : QUrl::fromLocalFile(preferredPath).toString();
930
931 QQmlTypeLoaderQmldirContent redirected
932 = typeLoader->qmldirContent(url + QLatin1String("qmldir"));
933
934 // Ignore errors: If the qmldir doesn't exist, stick to the old one.
935 if (redirected.hasContent() && !redirected.hasError())
936 *qmldir = std::move(redirected);
937
938 return url;
939}
940
941bool QQmlImports::getQmldirContent(
942 QQmlTypeLoader *typeLoader, const QString &qmldirIdentifier, const QString &uri,
943 QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors)
944{
945 Q_ASSERT(errors);
946 Q_ASSERT(qmldir);
947
948 *qmldir = typeLoader->qmldirContent(qmldirIdentifier);
949 if (!qmldir->hasContent() || !qmldir->hasError())
950 return true;
951
952 errors->append(qmldir->errors(uri, QUrl::fromLocalFile(qmldirIdentifier)));
953 return false;
954}
955
956QString QQmlImports::resolvedUri(const QString &dir_arg, QQmlTypeLoader *typeLoader)
957{
958 QString dir = dir_arg;
959 if (dir.endsWith(Slash) || dir.endsWith(Backslash))
960 dir.chop(1);
961
962 QStringList paths = typeLoader->importPathList();
963 if (!paths.isEmpty())
964 std::sort(paths.begin(), paths.end(), std::greater<QString>()); // Ensure subdirs preceed their parents.
965
966 QString stableRelativePath = dir;
967 for (const QString &path : std::as_const(paths)) {
968 if (dir.startsWith(path)) {
969 stableRelativePath = dir.mid(path.size()+1);
970 break;
971 }
972 }
973
974 stableRelativePath.replace(Backslash, Slash);
975
976 // remove optional versioning in dot notation from uri
977 int versionDot = stableRelativePath.lastIndexOf(Dot);
978 if (versionDot >= 0) {
979 int nextSlash = stableRelativePath.indexOf(Slash, versionDot);
980 if (nextSlash >= 0)
981 stableRelativePath.remove(versionDot, nextSlash - versionDot);
982 else
983 stableRelativePath = stableRelativePath.left(versionDot);
984 }
985
986 stableRelativePath.replace(Slash, Dot);
987
988 return stableRelativePath;
989}
990
992 const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, QTypeRevision version,
993 QList<QQmlError> *errors)
994{
995 int bestMajorVersion = -1;
996 quint8 lowestMinorVersion = std::numeric_limits<quint8>::max();
997 quint8 highestMinorVersion = 0;
998
999 auto addVersion = [&](QTypeRevision newVersion) {
1000 if (!newVersion.hasMajorVersion())
1001 return;
1002 if (!version.hasMajorVersion() || version.majorVersion() == newVersion.majorVersion()) {
1003 if (newVersion.majorVersion() > bestMajorVersion) {
1004 bestMajorVersion = newVersion.majorVersion();
1005 if (newVersion.hasMinorVersion()) {
1006 lowestMinorVersion = newVersion.minorVersion();
1007 highestMinorVersion = newVersion.minorVersion();
1008 }
1009 } else if (newVersion.majorVersion() == bestMajorVersion
1010 && newVersion.hasMinorVersion()) {
1011 lowestMinorVersion = qMin(lowestMinorVersion, newVersion.minorVersion());
1012 highestMinorVersion = qMax(highestMinorVersion, newVersion.minorVersion());
1013 }
1014 }
1015 };
1016
1017 typedef QQmlDirComponents::const_iterator ConstIterator;
1018 const QQmlDirComponents &components = qmldir.components();
1019
1020 ConstIterator cend = components.constEnd();
1021 for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
1022 for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) {
1023 if (cit2->typeName == cit->typeName && cit2->version == cit->version) {
1024 // This entry clashes with a predecessor
1025 QQmlError error;
1026 error.setDescription(
1027 QQmlImports::tr(
1028 "\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1029 .arg(cit->typeName).arg(cit->version.majorVersion())
1030 .arg(cit->version.minorVersion()).arg(uri));
1031 errors->prepend(error);
1032 return QTypeRevision();
1033 }
1034 }
1035
1036 addVersion(cit->version);
1037 }
1038
1039 typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
1040 const QQmlDirScripts &scripts = qmldir.scripts();
1041
1042 SConstIterator send = scripts.constEnd();
1043 for (SConstIterator sit = scripts.constBegin(); sit != send; ++sit) {
1044 for (SConstIterator sit2 = scripts.constBegin(); sit2 != sit; ++sit2) {
1045 if (sit2->nameSpace == sit->nameSpace && sit2->version == sit->version) {
1046 // This entry clashes with a predecessor
1047 QQmlError error;
1048 error.setDescription(QQmlImports::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1049 .arg(sit->nameSpace).arg(sit->version.majorVersion())
1050 .arg(sit->version.minorVersion()).arg(uri));
1051 errors->prepend(error);
1052 return QTypeRevision();
1053 }
1054 }
1055
1056 addVersion(sit->version);
1057 }
1058
1059 // Failure to find a match is only an error if we were asking for a specific version ...
1060 if (version.hasMajorVersion()
1061 && (bestMajorVersion < 0
1062 || (version.hasMinorVersion()
1063 && (lowestMinorVersion > version.minorVersion()
1064 || highestMinorVersion < version.minorVersion())))) {
1065 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
1066 return QTypeRevision();
1067 }
1068
1069 // ... otherwise, anything is valid.
1070 if (bestMajorVersion < 0)
1071 return QQmlImports::validVersion();
1072
1073 return QTypeRevision::fromVersion(
1074 bestMajorVersion,
1075 (version.hasMajorVersion() && version.hasMinorVersion())
1076 ? version.minorVersion()
1077 : highestMinorVersion);
1078}
1079
1080QQmlImportNamespace *QQmlImports::importNamespace(const QString &prefix)
1081{
1082 QQmlImportNamespace *nameSpace = nullptr;
1083
1084 if (prefix.isEmpty()) {
1085 nameSpace = &m_unqualifiedset;
1086 } else {
1087 nameSpace = findQualifiedNamespace(prefix);
1088
1089 if (!nameSpace) {
1090 nameSpace = new QQmlImportNamespace;
1091 nameSpace->prefix = prefix;
1092 m_qualifiedSets.append(nameSpace);
1093 }
1094 }
1095
1096 return nameSpace;
1097}
1098
1100 return version.isValid() ? QDebug::toString(version) : u"(latest)"_s;
1101}
1102
1104 const QString &uri, QTypeRevision version, QList<QQmlError> *errors)
1105{
1106 const QTypeRevision matchingVersion = QQmlMetaType::matchingModuleVersion(uri, version);
1107 if (!matchingVersion.isValid())
1108 errors->prepend(QQmlImports::moduleNotFoundError(uri, relevantVersion(uri, version)));
1109 return matchingVersion;
1110}
1111
1113 const QString &uri, QTypeRevision version, const QQmlTypeLoaderQmldirContent &qmldir,
1114 QQmlImportInstance *inserted, QList<QQmlError> *errors)
1115{
1116 // Ensure that we are actually providing something
1117 const QTypeRevision matchingVersion = QQmlMetaType::matchingModuleVersion(uri, version);
1118 if (matchingVersion.isValid())
1119 return matchingVersion;
1120
1121 if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) {
1122 if (qmldir.plugins().isEmpty()) {
1123 if (!qmldir.imports().isEmpty())
1124 return QQmlImports::validVersion(); // This is a pure redirection
1125 if (qmldir.hasTypeInfo())
1126 return QQmlImports::validVersion(); // A pure C++ module without plugin
1127 }
1128 errors->prepend(QQmlImports::moduleNotFoundError(uri, relevantVersion(uri, version)));
1129 return QTypeRevision();
1130 } else {
1131 // Verify that the qmldir content is valid for this version
1132 version = matchingQmldirVersion(qmldir, uri, version, errors);
1133 if (!version.isValid())
1134 return QTypeRevision();
1135 }
1136
1137 Q_ASSERT(version.isValid());
1138 return version;
1139}
1140
1141QTypeRevision QQmlImports::addLibraryImport(
1142 QQmlTypeLoader *typeLoader, const QString &uri, const QString &prefix,
1143 QTypeRevision requestedVersion, const QString &qmldirIdentifier, const QString &qmldirUrl,
1144 ImportFlags flags, quint8 precedence, QList<QQmlError> *errors)
1145{
1146 Q_ASSERT(typeLoader);
1147 Q_ASSERT(errors);
1148
1149 if (lcQmlImport().isDebugEnabled()) {
1150 qCDebug(lcQmlImport)
1151 << "addLibraryImport:" << qPrintable(baseUrl().toString())
1152 << uri << "version" << getVersionInfo(requestedVersion) << "as" << prefix;
1153 }
1154
1155 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1156 Q_ASSERT(nameSpace);
1157
1158 const bool noQmldir = qmldirIdentifier.isEmpty();
1159 const bool isIncomplete = (flags & QQmlImports::ImportIncomplete);
1160 if (noQmldir || isIncomplete) {
1161 QQmlImportInstance *inserted = addImportToNamespace<IsLibrary::Yes>(
1162 nameSpace, uri, qmldirUrl, requestedVersion, precedence);
1163 Q_ASSERT(inserted);
1164
1165 if (noQmldir && !isIncomplete) {
1166 // No need to wait for the qmldir to become available if we're not supposed to use it.
1167 if (!QQmlMetaType::typeModule(uri, requestedVersion))
1168 QQmlMetaType::qmlRegisterModuleTypes(uri);
1169 return matchingModuleVersionForLibraryImport(uri, requestedVersion, errors);
1170 }
1171
1172 return validVersion(requestedVersion);
1173 }
1174
1175 QQmlTypeLoaderQmldirContent qmldir;
1176 if (!getQmldirContent(typeLoader, qmldirIdentifier, uri, &qmldir, errors)) {
1177 // qmldir had errors.
1178 return QTypeRevision();
1179 }
1180
1181 // qmldir is remote and can't be loaded synchronously, but we may already know the module.
1182 if (!qmldir.hasContent())
1183 return matchingModuleVersionForLibraryImport(uri, requestedVersion, errors);
1184
1185 // Load the plugin before redirecting. Otherwise we might not find the qmldir we're looking for.
1186 const QTypeRevision importedVersion = importExtension(
1187 typeLoader, uri, requestedVersion, &qmldir, errors);
1188 if (!importedVersion.isValid())
1189 return QTypeRevision();
1190
1191 QString resolvedUrl;
1192 QString resolvedUri;
1193 if (qmldir.hasRedirection()) {
1194 resolvedUrl = redirectQmldirContent(typeLoader, &qmldir);
1195 resolvedUri = qmldir.typeNamespace();
1196 } else {
1197 resolvedUrl = qmldirUrl;
1198 resolvedUri = uri;
1199 }
1200
1201 if (QQmlImportInstance *existing
1202 = nameSpace->findImportByLocation(resolvedUrl, requestedVersion);
1203 existing && existing->isLibrary && existing->uri == resolvedUri) {
1204 // Even if the precedence stays the same we have to re-insert. The ordering wrt other
1205 // imports of the same precendence may change.
1206 nameSpace->imports.removeOne(existing);
1207 existing->precedence = std::min(precedence, existing->precedence);
1208 existing->implicitlyImported = existing->precedence >= QQmlImportInstance::Implicit;
1209 insertImport(nameSpace, existing);
1210 return finalizeLibraryImport(uri, importedVersion, qmldir, existing, errors);
1211 }
1212
1213 return finalizeImport<IsLibrary::Yes>(
1214 nameSpace, qmldir, resolvedUri, resolvedUrl, precedence, requestedVersion,
1215 importedVersion, errors, [&](QQmlImportInstance *inserted) {
1216 return finalizeLibraryImport(uri, importedVersion, qmldir, inserted, errors);
1217 });
1218}
1219
1220/*!
1221 \internal
1222
1223 Adds information to \a database such that subsequent calls to resolveType()
1224 will resolve types qualified by \a prefix by considering types found at the given \a uri.
1225
1226 The \a uri is either a directory (if importType is FileImport), or a URI resolved using paths
1227 added via addImportPath() (if importType is LibraryImport).
1228
1229 The \a prefix may be empty, in which case the import location is considered for
1230 unqualified types.
1231
1232 The base URL must already have been set with Import::setBaseUrl().
1233
1234 Optionally, the qmldir the import resolved to can be returned by providing the \a localQmldir
1235 parameter. Not all imports will have a local qmldir. If there is none, the \a localQmldir
1236 parameter won't be set.
1237
1238 Returns a valid QTypeRevision on success, and an invalid one on failure.
1239 In case of failure, the \a errors array will filled appropriately.
1240*/
1241QTypeRevision QQmlImports::addFileImport(
1242 QQmlTypeLoader *typeLoader, const QString &uri, const QString &prefix,
1243 QTypeRevision requestedVersion, ImportFlags flags, quint8 precedence, QString *localQmldir,
1244 QList<QQmlError> *errors)
1245{
1246 Q_ASSERT(typeLoader);
1247 Q_ASSERT(errors);
1248
1249 if (lcQmlImport().isDebugEnabled()) {
1250 qCDebug(lcQmlImport)
1251 << "addFileImport:" << qPrintable(baseUrl().toString())
1252 << uri << "version" << getVersionInfo(requestedVersion) << "as" << prefix;
1253 }
1254
1255 if (uri.startsWith(Slash) || uri.startsWith(Colon)) {
1256 QQmlError error;
1257 const QString fix = uri.startsWith(Slash) ? QLatin1String("file:") + uri
1258 : QLatin1String("qrc") + uri;
1259 error.setDescription(QQmlImports::tr(
1260 "\"%1\" is not a valid import URL. "
1261 "You can pass relative paths or URLs with schema, but not "
1262 "absolute paths or resource paths. Try \"%2\".").arg(uri, fix));
1263 errors->prepend(error);
1264 return QTypeRevision();
1265 }
1266
1267 Q_ASSERT(errors);
1268
1269 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1270 Q_ASSERT(nameSpace);
1271
1272 // The uri for this import. For library imports this is the same as uri
1273 // specified by the user, but it may be different in the case of file imports.
1274 QString importUri = uri;
1275 QString qmldirUrl = resolveLocalUrl(m_base, importUri + (importUri.endsWith(Slash)
1276 ? String_qmldir
1277 : Slash_qmldir));
1278 qmldirUrl = typeLoader->interceptUrl(
1279 QUrl(qmldirUrl), QQmlAbstractUrlInterceptor::QmldirFile).toString();
1280 QString qmldirIdentifier;
1281
1282 if (QQmlFile::isLocalFile(qmldirUrl)) {
1283
1284 QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
1285 Q_ASSERT(!localFileOrQrc.isEmpty());
1286
1287 const QString dir = localFileOrQrc.left(localFileOrQrc.lastIndexOf(Slash) + 1);
1288 if (!typeLoader->directoryExists(dir)) {
1289 if (precedence < QQmlImportInstance::Implicit) {
1290 QQmlError error;
1291 error.setDescription(QQmlImports::tr("\"%1\": no such directory").arg(uri));
1292 error.setUrl(QUrl(qmldirUrl));
1293 errors->prepend(error);
1294 }
1295 return QTypeRevision();
1296 }
1297
1298 // Transforms the (possible relative) uri into our best guess relative to the
1299 // import paths.
1300 importUri = resolvedUri(dir, typeLoader);
1301 if (importUri.endsWith(Slash))
1302 importUri.chop(1);
1303
1304 if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) {
1305 qmldirIdentifier = std::move(localFileOrQrc);
1306 if (localQmldir)
1307 *localQmldir = qmldirIdentifier;
1308 }
1309
1310 } else if (nameSpace->prefix.isEmpty() && !(flags & QQmlImports::ImportIncomplete)) {
1311
1312 if (precedence < QQmlImportInstance::Implicit) {
1313 QQmlError error;
1314 error.setDescription(QQmlImports::tr("import \"%1\" has no qmldir and no namespace").arg(importUri));
1315 error.setUrl(QUrl(qmldirUrl));
1316 errors->prepend(error);
1317 }
1318
1319 return QTypeRevision();
1320
1321 }
1322
1323 // The url for the path containing files for this import
1324 QString url = resolveLocalUrl(m_base, uri);
1325 if (url.isEmpty()) {
1326 QQmlError error;
1327 error.setDescription(
1328 QQmlImports::tr("Cannot resolve URL for import \"%1\"").arg(uri));
1329 error.setUrl(m_baseUrl);
1330 errors->prepend(error);
1331 return QTypeRevision();
1332 }
1333
1334 if (!url.endsWith(Slash) && !url.endsWith(Backslash))
1335 url += Slash;
1336
1337 // ### For enum support, we are now adding the implicit import always (and earlier). Bail early
1338 // if the implicit import has already been explicitly added, otherwise we can run into issues
1339 // with duplicate imports. However remember that we attempted to add this as implicit import, to
1340 // allow for the loading of internal types.
1341 if (precedence >= QQmlImportInstance::Implicit) {
1342 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
1343 it != nameSpace->imports.constEnd(); ++it) {
1344 if ((*it)->url == url) {
1345 (*it)->implicitlyImported = true;
1346 return validVersion(requestedVersion);
1347 }
1348 }
1349 }
1350
1351 if ((flags & QQmlImports::ImportIncomplete) || qmldirIdentifier.isEmpty()) {
1352 QQmlImportInstance *inserted = addImportToNamespace<IsLibrary::No>(
1353 nameSpace, importUri, url, requestedVersion, precedence);
1354 Q_ASSERT(inserted);
1355 return validVersion(requestedVersion);
1356 }
1357
1358 QQmlTypeLoaderQmldirContent qmldir;
1359 if (!getQmldirContent(typeLoader, qmldirIdentifier, importUri, &qmldir, errors))
1360 return QTypeRevision();
1361
1362 if (!qmldir.hasContent()) {
1363 QQmlImportInstance *inserted = addImportToNamespace<IsLibrary::No>(
1364 nameSpace, importUri, url, requestedVersion, precedence);
1365 Q_ASSERT(inserted);
1366 return validVersion(requestedVersion);
1367 }
1368
1369 // Prefer the qmldir URI. Unless it doesn't exist.
1370 if (qmldir.hasTypeNamespace())
1371 importUri = qmldir.typeNamespace();
1372
1373 // Load the plugin before redirecting. Otherwise we might not find the qmldir we're looking for.
1374 const QTypeRevision importedVersion
1375 = importExtension(typeLoader, importUri, requestedVersion, &qmldir, errors);
1376 if (!importedVersion.isValid())
1377 return QTypeRevision();
1378
1379 if (qmldir.hasRedirection()) {
1380 const QString resolvedUrl = redirectQmldirContent(typeLoader, &qmldir);
1381 importUri = qmldir.typeNamespace();
1382 if (resolvedUrl != url) {
1383 if (QQmlImportInstance *existing
1384 = nameSpace->findImportByLocation(resolvedUrl, requestedVersion)) {
1385 // We've alraedy seen this import. No need to add another entry.
1386 return validVersion(existing->version);
1387 }
1388 }
1389
1390 return finalizeImport<IsLibrary::No>(
1391 nameSpace, qmldir, importUri, resolvedUrl, precedence, requestedVersion,
1392 importedVersion, errors);
1393 }
1394
1395 return finalizeImport<IsLibrary::No>(
1396 nameSpace, qmldir, importUri, url, precedence, requestedVersion,
1397 importedVersion, errors);
1398}
1399
1400static QTypeRevision qmldirContentError(const QString &uri, QList<QQmlError> *errors)
1401{
1402 if (errors->isEmpty()) {
1403 QQmlError error;
1404 error.setDescription(QQmlTypeLoader::tr("Cannot update qmldir content for '%1'").arg(uri));
1405 errors->prepend(error);
1406 }
1407 return QTypeRevision();
1408}
1409
1410QTypeRevision QQmlImports::updateQmldirContent(
1411 QQmlTypeLoader *typeLoader, const QString &uri, QTypeRevision version,
1412 const QString &prefix, const QString &qmldirIdentifier, const QString &qmldirUrl,
1413 QList<QQmlError> *errors)
1414{
1415 Q_ASSERT(typeLoader);
1416 Q_ASSERT(errors);
1417
1418 qCDebug(lcQmlImport)
1419 << "updateQmldirContent:" << qPrintable(baseUrl().toString())
1420 << uri << "to" << qmldirUrl << "as" << prefix;
1421
1422 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1423 Q_ASSERT(nameSpace);
1424
1425 QQmlImportInstance *import = nameSpace->findImportByModuleUri(uri, version);
1426 if (!import)
1427 return qmldirContentError(uri, errors);
1428
1429 QQmlTypeLoaderQmldirContent qmldir;
1430 if (!getQmldirContent(typeLoader, qmldirIdentifier, uri, &qmldir, errors))
1431 return QTypeRevision();
1432
1433 if (!qmldir.hasContent())
1434 return qmldirContentError(uri, errors);
1435
1436 // Load the plugin before redirecting. Otherwise we might not find the qmldir we're looking for.
1437 version = importExtension(typeLoader, uri, import->version, &qmldir, errors);
1438 if (!version.isValid())
1439 return QTypeRevision();
1440
1441 QString resolvedUrl;
1442 if (qmldir.hasRedirection()) {
1443 resolvedUrl = redirectQmldirContent(typeLoader, &qmldir);
1444 if (resolvedUrl != import->url) {
1445 if (QQmlImportInstance *existing
1446 = nameSpace->findImportByLocation(resolvedUrl, import->version)) {
1447 // We've re-discovered the same module via a different redirect.
1448 // No need to import it again.
1449 nameSpace->imports.removeOne(import);
1450 delete import;
1451 return validVersion(existing->version);
1452 }
1453 import->url = resolvedUrl;
1454 }
1455 import->uri = qmldir.typeNamespace();
1456 } else {
1457 resolvedUrl = qmldirUrl;
1458 }
1459
1460 registerBuiltinModuleTypes(qmldir, version);
1461
1462 if (import->setQmldirContent(resolvedUrl, qmldir, nameSpace, errors)) {
1463 if (import->qmlDirComponents.isEmpty() && import->qmlDirScripts.isEmpty()) {
1464 // The implicit import qmldir can be empty, and plugins have no extra versions
1465 if (uri != QLatin1String(".")
1466 && !QQmlMetaType::matchingModuleVersion(import->uri, version).isValid()) {
1467 errors->prepend(moduleNotFoundError(uri, relevantVersion(uri, version)));
1468 return QTypeRevision();
1469 }
1470 } else {
1471 // Verify that the qmldir content is valid for this version
1472 version = matchingQmldirVersion(qmldir, import->uri, version, errors);
1473 if (!version.isValid())
1474 return QTypeRevision();
1475 }
1476 return validVersion(version);
1477 }
1478
1479 return qmldirContentError(uri, errors);
1480}
1481
1482/*!
1483 \fn QQmlImports::addImplicitImport(QQmlTypeLoader *typeLoader, QString *localQmldir, QList<QQmlError> *errors)
1484 \internal
1485
1486 Adds an implicit "." file import. This is equivalent to calling addFileImport(), but error
1487 messages related to the path or qmldir file not existing are suppressed.
1488
1489 Additionally, this will add the import with lowest instead of highest precedence.
1490*/
1491
1492
1493/*!
1494 \internal
1495 */
1496bool QQmlImports::addInlineComponentImport(
1497 QQmlImportInstance *const importInstance, const QString &name, const QUrl &importUrl)
1498{
1499 importInstance->url = importUrl.toString();
1500 importInstance->uri = name;
1501 importInstance->isInlineComponent = true;
1502 importInstance->version = QTypeRevision::zero();
1503 m_unqualifiedset.imports.push_back(importInstance);
1504 m_unqualifiedset.setNeedsSorting(true);
1505 return true;
1506}
1507
1508QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file)
1509{
1510 QUrl url(QLatin1String(file.at(0) == Colon ? "qrc" : "") + file);
1511
1512 // We don't support single character schemes as those conflict with windows drive letters.
1513 if (url.scheme().size() < 2)
1514 return QUrl::fromLocalFile(file);
1515 return url;
1516}
1517
1518void QQmlImports::setDesignerSupportRequired(bool b)
1519{
1520 designerSupportRequired = b;
1521}
1522
1523QT_END_NAMESPACE
QQmlImportInstance * findImportByModuleUri(const QString &moduleUri, QTypeRevision version) const
bool needsSorting() const
QQmlImportInstance * findImportByLocation(const QString &location, QTypeRevision version) const
bool resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type, QTypeRevision *version_return, QQmlType *type_return, const QString *base=nullptr, QList< QQmlError > *errors=nullptr, QQmlType::RegistrationType registrationType=QQmlType::AnyRegistrationType, bool *typeRecursionDeteced=nullptr)
void setNeedsSorting(bool needsSorting)
\inmodule QtCore
const QLoggingCategory & category() const
~QmlImportCategoryHolder()=default
static const QLatin1String String_qmldir("qmldir")
#define RESOLVE_TYPE_DEBUG
static const QString dotuidotqml_string(QStringLiteral(".ui.qml"))
static const QLatin1Char Colon(':')
static QTypeRevision qmldirContentError(const QString &uri, QList< QQmlError > *errors)
static const QString dotqml_string(QStringLiteral(".qml"))
static bool designerSupportRequired
void findCompositeSingletons(const QQmlImportNamespace &set, QList< QQmlImports::CompositeSingletonReference > &resultList, const QUrl &baseUrl)
static QTypeRevision matchingModuleVersionForLibraryImport(const QString &uri, QTypeRevision version, QList< QQmlError > *errors)
static QString getVersionInfo(QTypeRevision version)
static const QLatin1Char Dot('.')
static QTypeRevision matchingQmldirVersion(const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, QTypeRevision version, QList< QQmlError > *errors)
bool excludeBaseUrl(const QString &importUrl, const QString &fileName, const QString &baseUrl)
static const QLatin1Char Slash('/')
static const QLatin1Char Backslash('\\')
static const QLatin1String Slash_qmldir("/qmldir")
static QTypeRevision finalizeLibraryImport(const QString &uri, QTypeRevision version, const QQmlTypeLoaderQmldirContent &qmldir, QQmlImportInstance *inserted, QList< QQmlError > *errors)
const QLoggingCategory & lcQmlImport()
DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE)
bool resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type, QTypeRevision *version_return, QQmlType *type_return, const QString *base=nullptr, bool *typeRecursionDetected=nullptr, QQmlType::RegistrationType=QQmlType::AnyRegistrationType, QQmlImport::RecursionRestriction recursionRestriction=QQmlImport::PreventRecursion, QList< QQmlError > *errors=nullptr) const
bool setQmldirContent(const QString &resolvedUrl, const QQmlTypeLoaderQmldirContent &qmldir, QQmlImportNamespace *nameSpace, QList< QQmlError > *errors)