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