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() << ' '
461 << type_return->sourceUrl() << " TYPE/URL-SINGLETON";
462 } else if (type_return->isInlineComponent()) {
463 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' '
464 << type_return->sourceUrl() << " TYPE(INLINECOMPONENT)";
465 } else if (type_return->isComposite()) {
466 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' '
467 << type_return->sourceUrl() << " TYPE/URL";
468 } else {
469 RESOLVE_TYPE_DEBUG << type_return->typeName() << " TYPE";
470 }
471 }
472#undef RESOLVE_TYPE_DEBUG
473 }
474 return true;
475 }
476 }
477 return false;
478}
479
480const QQmlImportInstance *QQmlImports::findImportByModuleUri(const QString &uri,
481 QTypeRevision version) const
482{
483 if (const QQmlImportInstance *import = m_unqualifiedset.findImportByModuleUri(uri, version))
484 return import;
485
486 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) {
487 if (const QQmlImportInstance *import = ns->findImportByModuleUri(uri, version))
488 return import;
489 }
490
491 return nullptr;
492}
493
494bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl,
495 const QQmlTypeLoaderQmldirContent &qmldir,
496 QQmlImportNamespace *nameSpace, QList<QQmlError> *errors)
497{
498 Q_ASSERT(resolvedUrl.endsWith(Slash));
499 url = resolvedUrl;
500
501 qmlDirComponents = qmldir.components();
502
503 const QQmlDirScripts &scripts = qmldir.scripts();
504 if (!scripts.isEmpty()) {
505 // Verify that we haven't imported these scripts already
506 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
507 it != nameSpace->imports.constEnd(); ++it) {
508 if ((*it != this) && ((*it)->uri == uri)) {
509 QQmlError error;
510 error.setDescription(
511 QQmlImports::tr("\"%1\" is ambiguous. Found in %2 and in %3")
512 .arg(uri, url, (*it)->url));
513 errors->prepend(error);
514 return false;
515 }
516 }
517
518 qmlDirScripts = getVersionedScripts(scripts, version);
519 }
520
521 return true;
522}
523
524QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qmldirscripts,
525 QTypeRevision version)
526{
527 QMap<QString, QQmlDirParser::Script> versioned;
528
529 for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
530 sit != qmldirscripts.constEnd(); ++sit) {
531 // Only include scripts that match our requested version
532 if ((!version.hasMajorVersion() || (sit->version.majorVersion() == version.majorVersion()))
533 && (!version.hasMinorVersion()
534 || (sit->version.minorVersion() <= version.minorVersion()))) {
535 // Load the highest version that matches
536 const auto vit = versioned.constFind(sit->nameSpace);
537 if (vit == versioned.cend()
538 || (vit->version.minorVersion() < sit->version.minorVersion())) {
539 versioned.insert(sit->nameSpace, *sit);
540 }
541 }
542 }
543
544 return versioned.values();
545}
546
547/*!
548 \fn QQmlImports::resolveType(QQmlImportNamespace *ns, const QHashedStringRef &type, QQmlType *type_return, QTypeRevision *version_return, QQmlType::RegistrationType registrationType = QQmlType::AnyRegistrationType) const
549 \internal
550
551 Searching \e only in the namespace \a ns (previously returned in a call to
552 resolveType(), \a type is found and returned to
553 a QQmlType stored at \a type_return. If the type is from a QML file, the returned
554 type will be a CompositeType.
555
556 If the return pointer is 0, the corresponding search is not done.
557*/
558
559bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type,
560 QTypeRevision *version_return, QQmlType *type_return,
561 const QString *base, bool *typeRecursionDetected,
562 QQmlType::RegistrationType registrationType,
563 QQmlImport::RecursionRestriction recursionRestriction,
564 QList<QQmlError> *errors) const
565{
566 // QQmlMetaType assumes that without a URI, it should look for types in any module
567 // But QQmlImportInstance without a URI represents a directory import, and _must not_ find
568 // types from completely unrelated modules
569 QQmlType t = uri.isEmpty() ? QQmlType() : QQmlMetaType::qmlType(type, uri, version);
570 if (t.isValid()) {
571 if (version_return)
572 *version_return = version;
573 if (type_return)
574 *type_return = t;
575 return true;
576 }
577
578 const QString typeStr = type.toString();
579 if (isInlineComponent) {
580 Q_ASSERT(type_return);
581 bool ret = uri == typeStr;
582 if (ret) {
583 Q_ASSERT(!type_return->isValid());
584 *type_return = QQmlMetaType::findOrCreateSpeculativeInlineComponentType(QUrl(url));
585 // If the IC doesn't exist in an already-registered CU, we get an invalid type
586 if (!type_return->isValid())
587 ret = false;
588 }
589 return ret;
590 }
591 QQmlDirComponents::ConstIterator it = qmlDirComponents.find(typeStr), end = qmlDirComponents.end();
592 if (it != end) {
593 QString componentUrl;
594 QQmlMetaType::CompositeTypeLookupMode lookupMode = QQmlMetaType::NonSingleton;
595 QQmlDirComponents::ConstIterator candidate = end;
596 for ( ; it != end && it.key() == typeStr; ++it) {
597 const QQmlDirParser::Component &c = *it;
598 switch (registrationType) {
599 case QQmlType::AnyRegistrationType:
600 break;
601 case QQmlType::CompositeSingletonType:
602 if (!c.singleton)
603 continue;
604 break;
605 default:
606 if (c.singleton)
607 continue;
608 break;
609 }
610
611 // importing invalid version means import ALL versions
612 if (!version.hasMajorVersion() || (implicitlyImported && c.internal)
613 // allow the implicit import of internal types
614 || (c.version.majorVersion() == version.majorVersion()
615 && c.version.minorVersion() <= version.minorVersion())) {
616 // Is this better than the previous candidate?
617 if ((candidate == end)
618 || (c.version.majorVersion() > candidate->version.majorVersion())
619 || ((c.version.majorVersion() == candidate->version.majorVersion())
620 && (c.version.minorVersion() > candidate->version.minorVersion()))) {
621 if (base) {
622 componentUrl = resolveLocalUrl(QString(url + c.typeName + dotqml_string), c.fileName);
623 if (c.internal && resolveLocalUrl(*base, c.fileName) != componentUrl) {
624 // This can happen if the module is spread over multiple directories.
625 // We need to check that the accessing component belongs to the same
626 // module.
627 const auto componentsBegin = qmlDirComponents.cbegin();
628 const auto componentsEnd = qmlDirComponents.cend();
629 if (std::find_if(componentsBegin, componentsEnd,
630 [&](const QQmlDirParser::Component &component) {
631 return resolveLocalUrl(url, component.fileName)
632 == *base;
633 })
634 == componentsEnd) {
635 continue; // failed attempt to access an internal type
636 }
637 }
638
639 const bool recursion = *base == componentUrl;
640 if (typeRecursionDetected)
641 *typeRecursionDetected = recursion;
642
643 if (recursionRestriction == QQmlImport::PreventRecursion && recursion) {
644 continue; // no recursion
645 }
646 }
647
648 // This is our best candidate so far
649 candidate = it;
650 lookupMode = c.singleton ? QQmlMetaType::Singleton : QQmlMetaType::NonSingleton;
651 }
652 }
653 }
654
655 if (candidate != end) {
656 if (!base) // ensure we have a componentUrl
657 componentUrl = resolveLocalUrl(QString(url + candidate->typeName + dotqml_string), candidate->fileName);
658 QQmlType returnType = QQmlMetaType::typeForUrl(
659 typeLoader->interceptUrl(QUrl(componentUrl), QQmlAbstractUrlInterceptor::QmlFile),
660 type, lookupMode, nullptr, candidate->version);
661 if (version_return)
662 *version_return = candidate->version;
663 if (type_return)
664 *type_return = returnType;
665 return returnType.isValid();
666 }
667 } else if (!isLibrary) {
668 // the base path of the import if it's a local file
669 const QString localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url);
670 if (localDirectoryPath.isEmpty())
671 return false;
672
673 QString qmlUrl;
674
675 const QString urlsToTry[2] = {
676 typeStr + dotqml_string, // Type -> Type.qml
677 typeStr + dotuidotqml_string // Type -> Type.ui.qml
678 };
679 for (const QString &urlToTry : urlsToTry) {
680 if (typeLoader->fileExists(localDirectoryPath, urlToTry)) {
681 qmlUrl = url + urlToTry;
682 break;
683 }
684 }
685
686 if (!qmlUrl.isEmpty()) {
687 const bool recursion = base && *base == qmlUrl;
688 if (typeRecursionDetected)
689 *typeRecursionDetected = recursion;
690 if (recursionRestriction == QQmlImport::AllowRecursion || !recursion) {
691 QQmlType returnType = QQmlMetaType::typeForUrl(
692 typeLoader->interceptUrl(QUrl(qmlUrl), QQmlAbstractUrlInterceptor::QmlFile),
693 type, registrationType == QQmlType::CompositeSingletonType
694 ? QQmlMetaType::Singleton
695 : QQmlMetaType::NonSingleton,
696 errors);
697 if (type_return)
698 *type_return = returnType;
699 return returnType.isValid();
700 }
701 }
702 }
703
704 return false;
705}
706
707bool QQmlImports::resolveType(
708 QQmlTypeLoader *typeLoader, const QHashedStringRef &type, QTypeRevision *version_return,
709 QQmlType *type_return, QList<QQmlError> *errors,
710 QQmlType::RegistrationType registrationType, bool *typeRecursionDetected) const
711{
712 const QList<QHashedStringRef> splitName = type.split(Dot);
713 auto resolveTypeInNamespace = [&](
714 QHashedStringRef unqualifiedtype, QQmlImportNamespace *nameSpace,
715 QList<QQmlError> *errors) -> bool {
716 if (nameSpace->resolveType(
717 typeLoader, unqualifiedtype, version_return, type_return, &m_base, errors,
718 registrationType, typeRecursionDetected))
719 return true;
720 if (nameSpace->imports.size() == 1
721 && !nameSpace->imports.at(0)->isLibrary
722 && type_return
723 && nameSpace != &m_unqualifiedset) {
724 // qualified, and only 1 url
725 const QString urlString = resolveLocalUrl(
726 nameSpace->imports.at(0)->url, unqualifiedtype.toString() + dotqml_string);
727 *type_return = QQmlMetaType::typeForUrl(
728 typeLoader->interceptUrl(QUrl(urlString), QQmlAbstractUrlInterceptor::QmlFile),
729 type, QQmlMetaType::NonSingleton, errors);
730 return type_return->isValid();
731 }
732 return false;
733 };
734 switch (splitName.size()) {
735 case 1: {
736 // must be a simple type
737 return resolveTypeInNamespace(type, &m_unqualifiedset, errors);
738 }
739 case 2: {
740 // either namespace + simple type OR simple type + inline component OR failure
741 QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0));
742
743 if (s) {
744 // namespace + simple type
745 return resolveTypeInNamespace(splitName.at(1), s, errors);
746 }
747
748 if (resolveTypeInNamespace(splitName.at(0), &m_unqualifiedset, nullptr)) {
749 // simple type + inline component
750 *type_return = QQmlMetaType::findOrCreateSpeculativeInlineComponentType(
751 *type_return, splitName.at(1).toString());
752 if (type_return->isValid())
753 return true;
754 }
755
756 if (errors) {
757 // failure
758 QQmlError error;
759 error.setDescription(QQmlImports::tr("- %1 is neither a type nor a namespace")
760 .arg(splitName.at(0).toString()));
761 errors->prepend(error);
762 }
763 return false;
764 }
765 case 3: {
766 // must be namespace + simple type + inline component
767 QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0));
768 QQmlError error;
769 if (!s) {
770 error.setDescription(QQmlImports::tr("- %1 is not a namespace").arg(splitName.at(0).toString()));
771 } else {
772 if (resolveTypeInNamespace(splitName.at(1), s, nullptr)) {
773 *type_return = QQmlMetaType::findOrCreateSpeculativeInlineComponentType(
774 *type_return, splitName.at(2).toString());
775 if (type_return->isValid())
776 return true;
777
778 // IC doesn't exist in already-registered base type
779 error.setDescription(QQmlImports::tr(
780 "- %1 is not an inline component")
781 .arg(splitName.at(2).toString()));
782 } else {
783 error.setDescription(QQmlImports::tr("- %1 is not a type").arg(splitName.at(1).toString()));
784 }
785 }
786 if (errors) {
787 errors->prepend(error);
788 }
789 return false;
790 }
791 default: {
792 // all other numbers suggest a user error
793 if (errors) {
794 QQmlError error;
795 error.setDescription(QQmlImports::tr("- nested namespaces not allowed"));
796 errors->prepend(error);
797 }
798 return false;
799 }
800 }
801 Q_UNREACHABLE();
802}
803
805 const QString &moduleUri, QTypeRevision version) const
806{
807 const auto end = imports.cend();
808 const auto it = std::find_if(imports.cbegin(), end, [&](const QQmlImportInstance *import) {
809 return import->uri == moduleUri && import->version == version;
810 });
811 return it == end ? nullptr : *it;
812}
813
815 const QString &location, QTypeRevision version) const
816{
817 const auto end = imports.cend();
818 const auto it = std::find_if(imports.cbegin(), end, [&](const QQmlImportInstance *import) {
819 return import->url == location && import->version == version;
820 });
821 return it == end ? nullptr : *it;
822}
823
824bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type,
825 QTypeRevision *version_return, QQmlType *type_return,
826 const QString *base, QList<QQmlError> *errors,
827 QQmlType::RegistrationType registrationType,
828 bool *typeRecursionDetected)
829{
830 QQmlImport::RecursionRestriction recursionRestriction =
832
833 bool localTypeRecursionDetected = false;
834 if (!typeRecursionDetected)
835 typeRecursionDetected = &localTypeRecursionDetected;
836
837 // TODO: move the sorting somewhere else and make resolveType() const.
838 if (needsSorting()) {
839 std::stable_partition(imports.begin(), imports.end(), [](QQmlImportInstance *import) {
840 return import->isInlineComponent;
841 });
843 }
844 for (int i=0; i<imports.size(); ++i) {
845 const QQmlImportInstance *import = imports.at(i);
846 if (import->resolveType(typeLoader, type, version_return, type_return, base,
847 typeRecursionDetected, registrationType, recursionRestriction, errors)) {
848 if (qmlCheckTypes()) {
849 // check for type clashes
850 for (int j = i+1; j<imports.size(); ++j) {
851 const QQmlImportInstance *import2 = imports.at(j);
852 if (import2->resolveType(typeLoader, type, version_return, nullptr, base,
853 nullptr, registrationType)) {
854 if (errors) {
855 QString u1 = import->url;
856 QString u2 = import2->url;
857 if (base) {
858 QStringView b(*base);
859 int dot = b.lastIndexOf(Dot);
860 if (dot >= 0) {
861 b = b.left(dot+1);
862 QStringView l = b.left(dot);
863 if (u1.startsWith(b))
864 u1 = u1.mid(b.size());
865 else if (u1 == l)
866 u1 = QQmlImports::tr("local directory");
867 if (u2.startsWith(b))
868 u2 = u2.mid(b.size());
869 else if (u2 == l)
870 u2 = QQmlImports::tr("local directory");
871 }
872 }
873
874 QQmlError error;
875 if (u1 != u2) {
876 error.setDescription(
877 QQmlImports::tr(
878 "is ambiguous. Found in %1 and in %2")
879 .arg(u1, u2));
880 } else {
881 error.setDescription(
882 QQmlImports::tr(
883 "is ambiguous. Found in %1 in version "
884 "%2.%3 and %4.%5")
885 .arg(u1)
886 .arg(import->version.majorVersion())
887 .arg(import->version.minorVersion())
888 .arg(import2->version.majorVersion())
889 .arg(import2->version.minorVersion()));
890 }
891 errors->prepend(error);
892 }
893 return false;
894 }
895 }
896 }
897 return true;
898 }
899 }
900 if (errors) {
901 QQmlError error;
902 if (*typeRecursionDetected)
903 error.setDescription(QQmlImports::tr("is instantiated recursively"));
904 else
905 error.setDescription(QQmlImports::tr("is not a type"));
906 errors->prepend(error);
907 }
908 return false;
909}
910
911QQmlImportNamespace *QQmlImports::findQualifiedNamespace(const QHashedStringRef &prefix) const
912{
913 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(ns)) {
914 if (prefix == ns->prefix)
915 return ns;
916 }
917 return nullptr;
918}
919
920/*
921Import an extension defined by a qmldir file.
922*/
923QTypeRevision QQmlImports::importExtension(
924 QQmlTypeLoader *typeLoader, const QString &uri, QTypeRevision version,
925 const QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors)
926{
927 Q_ASSERT(qmldir->hasContent());
928
929 qCDebug(lcQmlImport)
930 << "importExtension:" << qPrintable(m_base) << "loaded" << qmldir->qmldirLocation();
931
932 if (designerSupportRequired && !qmldir->designerSupported()) {
933 if (errors) {
934 QQmlError error;
935 error.setDescription(
936 QQmlImports::tr("module does not support the designer \"%1\"")
937 .arg(qmldir->typeNamespace()));
938 error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation()));
939 errors->prepend(error);
940 }
941 return QTypeRevision();
942 }
943
944 if (qmldir->plugins().isEmpty())
945 return validVersion(version);
946
947 QQmlPluginImporter importer(uri, version, qmldir, typeLoader, errors);
948 return importer.importPlugins();
949}
950
951void QQmlImports::registerBuiltinModuleTypes(
952 const QQmlTypeLoaderQmldirContent &qmldir, QTypeRevision version)
953{
954 if (!qmldir.plugins().isEmpty())
955 return;
956
957 // If the qmldir does not register a plugin, we might still have declaratively
958 // registered types (if we are dealing with an application instead of a library)
959
960 const QString qmldirUri = qmldir.typeNamespace();
961 if (!QQmlMetaType::typeModule(qmldirUri, version))
962 QQmlMetaType::qmlRegisterModuleTypes(qmldirUri);
963}
964
965QString QQmlImports::redirectQmldirContent(
966 QQmlTypeLoader *typeLoader, QQmlTypeLoaderQmldirContent *qmldir)
967{
968 const QString preferredPath = qmldir->preferredPath();
969 const QString url = preferredPath.startsWith(u':')
970 ? QStringLiteral("qrc") + preferredPath
971 : QUrl::fromLocalFile(preferredPath).toString();
972
973 QQmlTypeLoaderQmldirContent redirected
974 = typeLoader->qmldirContent(url + QLatin1String("qmldir"));
975
976 // Ignore errors: If the qmldir doesn't exist, stick to the old one.
977 if (redirected.hasContent() && !redirected.hasError())
978 *qmldir = std::move(redirected);
979
980 return url;
981}
982
983bool QQmlImports::getQmldirContent(
984 QQmlTypeLoader *typeLoader, const QString &qmldirIdentifier, const QString &uri,
985 QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors)
986{
987 Q_ASSERT(errors);
988 Q_ASSERT(qmldir);
989
990 *qmldir = typeLoader->qmldirContent(qmldirIdentifier);
991 if (!qmldir->hasContent() || !qmldir->hasError())
992 return true;
993
994 errors->append(qmldir->errors(uri, QUrl::fromLocalFile(qmldirIdentifier)));
995 return false;
996}
997
998QString QQmlImports::resolvedUri(const QString &dir_arg, QQmlTypeLoader *typeLoader)
999{
1000 QString dir = dir_arg;
1001 if (dir.endsWith(Slash) || dir.endsWith(Backslash))
1002 dir.chop(1);
1003
1004 QStringList paths = typeLoader->importPathList();
1005 if (!paths.isEmpty())
1006 std::sort(paths.begin(), paths.end(), std::greater<QString>()); // Ensure subdirs preceed their parents.
1007
1008 QString stableRelativePath = dir;
1009 for (const QString &path : std::as_const(paths)) {
1010 if (dir.startsWith(path)) {
1011 stableRelativePath = dir.mid(path.size()+1);
1012 break;
1013 }
1014 }
1015
1016 stableRelativePath.replace(Backslash, Slash);
1017
1018 // remove optional versioning in dot notation from uri
1019 int versionDot = stableRelativePath.lastIndexOf(Dot);
1020 if (versionDot >= 0) {
1021 int nextSlash = stableRelativePath.indexOf(Slash, versionDot);
1022 if (nextSlash >= 0)
1023 stableRelativePath.remove(versionDot, nextSlash - versionDot);
1024 else
1025 stableRelativePath = stableRelativePath.left(versionDot);
1026 }
1027
1028 stableRelativePath.replace(Slash, Dot);
1029
1030 return stableRelativePath;
1031}
1032
1034 const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, QTypeRevision version,
1035 QList<QQmlError> *errors)
1036{
1037 int bestMajorVersion = -1;
1038 quint8 lowestMinorVersion = std::numeric_limits<quint8>::max();
1039 quint8 highestMinorVersion = 0;
1040
1041 auto addVersion = [&](QTypeRevision newVersion) {
1042 if (!newVersion.hasMajorVersion())
1043 return;
1044 if (!version.hasMajorVersion() || version.majorVersion() == newVersion.majorVersion()) {
1045 if (newVersion.majorVersion() > bestMajorVersion) {
1046 bestMajorVersion = newVersion.majorVersion();
1047 if (newVersion.hasMinorVersion()) {
1048 lowestMinorVersion = newVersion.minorVersion();
1049 highestMinorVersion = newVersion.minorVersion();
1050 }
1051 } else if (newVersion.majorVersion() == bestMajorVersion
1052 && newVersion.hasMinorVersion()) {
1053 lowestMinorVersion = qMin(lowestMinorVersion, newVersion.minorVersion());
1054 highestMinorVersion = qMax(highestMinorVersion, newVersion.minorVersion());
1055 }
1056 }
1057 };
1058
1059 typedef QQmlDirComponents::const_iterator ConstIterator;
1060 const QQmlDirComponents &components = qmldir.components();
1061
1062 ConstIterator cend = components.constEnd();
1063 for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
1064 for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) {
1065 if (cit2->typeName == cit->typeName && cit2->version == cit->version) {
1066 // This entry clashes with a predecessor
1067 QQmlError error;
1068 error.setDescription(
1069 QQmlImports::tr(
1070 "\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1071 .arg(cit->typeName).arg(cit->version.majorVersion())
1072 .arg(cit->version.minorVersion()).arg(uri));
1073 errors->prepend(error);
1074 return QTypeRevision();
1075 }
1076 }
1077
1078 addVersion(cit->version);
1079 }
1080
1081 typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
1082 const QQmlDirScripts &scripts = qmldir.scripts();
1083
1084 SConstIterator send = scripts.constEnd();
1085 for (SConstIterator sit = scripts.constBegin(); sit != send; ++sit) {
1086 for (SConstIterator sit2 = scripts.constBegin(); sit2 != sit; ++sit2) {
1087 if (sit2->nameSpace == sit->nameSpace && sit2->version == sit->version) {
1088 // This entry clashes with a predecessor
1089 QQmlError error;
1090 error.setDescription(QQmlImports::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1091 .arg(sit->nameSpace).arg(sit->version.majorVersion())
1092 .arg(sit->version.minorVersion()).arg(uri));
1093 errors->prepend(error);
1094 return QTypeRevision();
1095 }
1096 }
1097
1098 addVersion(sit->version);
1099 }
1100
1101 // Failure to find a match is only an error if we were asking for a specific version ...
1102 if (version.hasMajorVersion()
1103 && (bestMajorVersion < 0
1104 || (version.hasMinorVersion()
1105 && (lowestMinorVersion > version.minorVersion()
1106 || highestMinorVersion < version.minorVersion())))) {
1107 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
1108 return QTypeRevision();
1109 }
1110
1111 // ... otherwise, anything is valid.
1112 if (bestMajorVersion < 0)
1113 return QQmlImports::validVersion();
1114
1115 return QTypeRevision::fromVersion(
1116 bestMajorVersion,
1117 (version.hasMajorVersion() && version.hasMinorVersion())
1118 ? version.minorVersion()
1119 : highestMinorVersion);
1120}
1121
1122QQmlImportNamespace *QQmlImports::importNamespace(const QString &prefix)
1123{
1124 QQmlImportNamespace *nameSpace = nullptr;
1125
1126 if (prefix.isEmpty()) {
1127 nameSpace = &m_unqualifiedset;
1128 } else {
1129 nameSpace = findQualifiedNamespace(prefix);
1130
1131 if (!nameSpace) {
1132 nameSpace = new QQmlImportNamespace;
1133 nameSpace->prefix = prefix;
1134 m_qualifiedSets.append(nameSpace);
1135 }
1136 }
1137
1138 return nameSpace;
1139}
1140
1141static QString getVersionInfo(QTypeRevision version) {
1142 return version.isValid() ? QDebug::toString(version) : u"(latest)"_s;
1143}
1144
1146 const QString &uri, QTypeRevision version, QList<QQmlError> *errors)
1147{
1148 const QTypeRevision matchingVersion = QQmlMetaType::matchingModuleVersion(uri, version);
1149 if (!matchingVersion.isValid())
1150 errors->prepend(QQmlImports::moduleNotFoundError(uri, relevantVersion(uri, version)));
1151 return matchingVersion;
1152}
1153
1155 const QString &uri, QTypeRevision version, const QQmlTypeLoaderQmldirContent &qmldir,
1156 QQmlImportInstance *inserted, QList<QQmlError> *errors)
1157{
1158 // Ensure that we are actually providing something
1159 const QTypeRevision matchingVersion = QQmlMetaType::matchingModuleVersion(uri, version);
1160 if (matchingVersion.isValid())
1161 return matchingVersion;
1162
1163 if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) {
1164 if (qmldir.plugins().isEmpty()) {
1165 if (!qmldir.imports().isEmpty())
1166 return QQmlImports::validVersion(); // This is a pure redirection
1167 if (qmldir.hasTypeInfo())
1168 return QQmlImports::validVersion(); // A pure C++ module without plugin
1169 }
1170 errors->prepend(QQmlImports::moduleNotFoundError(uri, relevantVersion(uri, version)));
1171 return QTypeRevision();
1172 } else {
1173 // Verify that the qmldir content is valid for this version
1174 version = matchingQmldirVersion(qmldir, uri, version, errors);
1175 if (!version.isValid())
1176 return QTypeRevision();
1177 }
1178
1179 Q_ASSERT(version.isValid());
1180 return version;
1181}
1182
1183QTypeRevision QQmlImports::addLibraryImport(
1184 QQmlTypeLoader *typeLoader, const QString &uri, const QString &prefix,
1185 QTypeRevision requestedVersion, const QString &qmldirIdentifier, const QString &qmldirUrl,
1186 ImportFlags flags, quint8 precedence, QList<QQmlError> *errors)
1187{
1188 Q_ASSERT(typeLoader);
1189 Q_ASSERT(errors);
1190
1191 if (lcQmlImport().isDebugEnabled()) {
1192 qCDebug(lcQmlImport)
1193 << "addLibraryImport:" << qPrintable(baseUrl().toString())
1194 << uri << "version" << getVersionInfo(requestedVersion) << "as" << prefix;
1195 }
1196
1197 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1198 Q_ASSERT(nameSpace);
1199
1200 const bool noQmldir = qmldirIdentifier.isEmpty();
1201 const bool isIncomplete = (flags & QQmlImports::ImportIncomplete);
1202 if (noQmldir || isIncomplete) {
1203 QQmlImportInstance *inserted = addImportToNamespace<IsLibrary::Yes>(
1204 nameSpace, uri, qmldirUrl, requestedVersion, precedence);
1205 Q_ASSERT(inserted);
1206
1207 if (noQmldir && !isIncomplete) {
1208 // No need to wait for the qmldir to become available if we're not supposed to use it.
1209 if (!QQmlMetaType::typeModule(uri, requestedVersion))
1210 QQmlMetaType::qmlRegisterModuleTypes(uri);
1211 return matchingModuleVersionForLibraryImport(uri, requestedVersion, errors);
1212 }
1213
1214 return validVersion(requestedVersion);
1215 }
1216
1217 QQmlTypeLoaderQmldirContent qmldir;
1218 if (!getQmldirContent(typeLoader, qmldirIdentifier, uri, &qmldir, errors)) {
1219 // qmldir had errors.
1220 return QTypeRevision();
1221 }
1222
1223 // qmldir is remote and can't be loaded synchronously, but we may already know the module.
1224 if (!qmldir.hasContent())
1225 return matchingModuleVersionForLibraryImport(uri, requestedVersion, errors);
1226
1227 // Load the plugin before redirecting. Otherwise we might not find the qmldir we're looking for.
1228 const QTypeRevision importedVersion = importExtension(
1229 typeLoader, uri, requestedVersion, &qmldir, errors);
1230 if (!importedVersion.isValid())
1231 return QTypeRevision();
1232
1233 QString resolvedUrl;
1234 QString resolvedUri;
1235 if (qmldir.hasRedirection()) {
1236 resolvedUrl = redirectQmldirContent(typeLoader, &qmldir);
1237 resolvedUri = qmldir.typeNamespace();
1238 } else {
1239 resolvedUrl = qmldirUrl;
1240 resolvedUri = uri;
1241 }
1242
1243 if (QQmlImportInstance *existing
1244 = nameSpace->findImportByLocation(resolvedUrl, requestedVersion);
1245 existing && existing->isLibrary && existing->uri == resolvedUri) {
1246 // Even if the precedence stays the same we have to re-insert. The ordering wrt other
1247 // imports of the same precendence may change.
1248 nameSpace->imports.removeOne(existing);
1249 existing->precedence = std::min(precedence, existing->precedence);
1250 existing->implicitlyImported = existing->precedence >= QQmlImportInstance::Implicit;
1251 insertImport(nameSpace, existing);
1252 return finalizeLibraryImport(uri, importedVersion, qmldir, existing, errors);
1253 }
1254
1255 return finalizeImport<IsLibrary::Yes>(
1256 nameSpace, qmldir, resolvedUri, resolvedUrl, precedence, requestedVersion,
1257 importedVersion, errors, [&](QQmlImportInstance *inserted) {
1258 return finalizeLibraryImport(uri, importedVersion, qmldir, inserted, errors);
1259 });
1260}
1261
1262/*!
1263 \internal
1264
1265 Adds information to \a database such that subsequent calls to resolveType()
1266 will resolve types qualified by \a prefix by considering types found at the given \a uri.
1267
1268 The \a uri is either a directory (if importType is FileImport), or a URI resolved using paths
1269 added via addImportPath() (if importType is LibraryImport).
1270
1271 The \a prefix may be empty, in which case the import location is considered for
1272 unqualified types.
1273
1274 The base URL must already have been set with Import::setBaseUrl().
1275
1276 Optionally, the qmldir the import resolved to can be returned by providing the \a localQmldir
1277 parameter. Not all imports will have a local qmldir. If there is none, the \a localQmldir
1278 parameter won't be set.
1279
1280 Returns a valid QTypeRevision on success, and an invalid one on failure.
1281 In case of failure, the \a errors array will filled appropriately.
1282*/
1283QTypeRevision QQmlImports::addFileImport(
1284 QQmlTypeLoader *typeLoader, const QString &uri, const QString &prefix,
1285 QTypeRevision requestedVersion, ImportFlags flags, quint8 precedence, QString *localQmldir,
1286 QList<QQmlError> *errors)
1287{
1288 Q_ASSERT(typeLoader);
1289 Q_ASSERT(errors);
1290
1291 if (lcQmlImport().isDebugEnabled()) {
1292 qCDebug(lcQmlImport)
1293 << "addFileImport:" << qPrintable(baseUrl().toString())
1294 << uri << "version" << getVersionInfo(requestedVersion) << "as" << prefix;
1295 }
1296
1297 if (uri.startsWith(Slash) || uri.startsWith(Colon)) {
1298 QQmlError error;
1299 const QString fix = uri.startsWith(Slash) ? QLatin1String("file:") + uri
1300 : QLatin1String("qrc") + uri;
1301 error.setDescription(QQmlImports::tr(
1302 "\"%1\" is not a valid import URL. "
1303 "You can pass relative paths or URLs with schema, but not "
1304 "absolute paths or resource paths. Try \"%2\".").arg(uri, fix));
1305 errors->prepend(error);
1306 return QTypeRevision();
1307 }
1308
1309 Q_ASSERT(errors);
1310
1311 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1312 Q_ASSERT(nameSpace);
1313
1314 // The uri for this import. For library imports this is the same as uri
1315 // specified by the user, but it may be different in the case of file imports.
1316 QString importUri = uri;
1317 QString qmldirUrl = resolveLocalUrl(m_base, importUri + (importUri.endsWith(Slash)
1318 ? String_qmldir
1319 : Slash_qmldir));
1320 qmldirUrl = typeLoader->interceptUrl(
1321 QUrl(qmldirUrl), QQmlAbstractUrlInterceptor::QmldirFile).toString();
1322 QString qmldirIdentifier;
1323
1324 if (QQmlFile::isLocalFile(qmldirUrl)) {
1325
1326 const QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
1327 Q_ASSERT(!localFileOrQrc.isEmpty());
1328
1329 const QString dir = localFileOrQrc.left(localFileOrQrc.lastIndexOf(Slash) + 1);
1330 if (!typeLoader->directoryExists(dir)) {
1331 if (precedence < QQmlImportInstance::Implicit) {
1332 QQmlError error;
1333 error.setDescription(QQmlImports::tr("\"%1\": no such directory").arg(uri));
1334 error.setUrl(QUrl(qmldirUrl));
1335 errors->prepend(error);
1336 }
1337 return QTypeRevision();
1338 }
1339
1340 // Transforms the (possible relative) uri into our best guess relative to the
1341 // import paths.
1342 importUri = resolvedUri(dir, typeLoader);
1343 if (importUri.endsWith(Slash))
1344 importUri.chop(1);
1345
1346 if (QString cleanPath = typeLoader->cleanFilePath(localFileOrQrc); !cleanPath.isEmpty()) {
1347 qmldirIdentifier = std::move(cleanPath);
1348 if (localQmldir)
1349 *localQmldir = qmldirIdentifier;
1350 }
1351
1352 } else if (nameSpace->prefix.isEmpty() && !(flags & QQmlImports::ImportIncomplete)) {
1353
1354 if (precedence < QQmlImportInstance::Implicit) {
1355 QQmlError error;
1356 error.setDescription(QQmlImports::tr("import \"%1\" has no qmldir and no namespace").arg(importUri));
1357 error.setUrl(QUrl(qmldirUrl));
1358 errors->prepend(error);
1359 }
1360
1361 return QTypeRevision();
1362
1363 }
1364
1365 // The url for the path containing files for this import
1366 QString url = resolveLocalUrl(m_base, uri);
1367 if (url.isEmpty()) {
1368 QQmlError error;
1369 error.setDescription(
1370 QQmlImports::tr("Cannot resolve URL for import \"%1\"").arg(uri));
1371 error.setUrl(m_baseUrl);
1372 errors->prepend(error);
1373 return QTypeRevision();
1374 }
1375
1376 if (!url.endsWith(Slash) && !url.endsWith(Backslash))
1377 url += Slash;
1378
1379 // ### For enum support, we are now adding the implicit import always (and earlier). Bail early
1380 // if the implicit import has already been explicitly added, otherwise we can run into issues
1381 // with duplicate imports. However remember that we attempted to add this as implicit import, to
1382 // allow for the loading of internal types.
1383 if (precedence >= QQmlImportInstance::Implicit) {
1384 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
1385 it != nameSpace->imports.constEnd(); ++it) {
1386 if ((*it)->url == url) {
1387 (*it)->implicitlyImported = true;
1388 return validVersion(requestedVersion);
1389 }
1390 }
1391 }
1392
1393 if ((flags & QQmlImports::ImportIncomplete) || qmldirIdentifier.isEmpty()) {
1394 QQmlImportInstance *inserted = addImportToNamespace<IsLibrary::No>(
1395 nameSpace, importUri, url, requestedVersion, precedence);
1396 Q_ASSERT(inserted);
1397 return validVersion(requestedVersion);
1398 }
1399
1400 QQmlTypeLoaderQmldirContent qmldir;
1401 if (!getQmldirContent(typeLoader, qmldirIdentifier, importUri, &qmldir, errors))
1402 return QTypeRevision();
1403
1404 if (!qmldir.hasContent()) {
1405 QQmlImportInstance *inserted = addImportToNamespace<IsLibrary::No>(
1406 nameSpace, importUri, url, requestedVersion, precedence);
1407 Q_ASSERT(inserted);
1408 return validVersion(requestedVersion);
1409 }
1410
1411 // Prefer the qmldir URI. Unless it doesn't exist.
1412 if (qmldir.hasTypeNamespace())
1413 importUri = qmldir.typeNamespace();
1414
1415 // Load the plugin before redirecting. Otherwise we might not find the qmldir we're looking for.
1416 const QTypeRevision importedVersion
1417 = importExtension(typeLoader, importUri, requestedVersion, &qmldir, errors);
1418 if (!importedVersion.isValid())
1419 return QTypeRevision();
1420
1421 if (qmldir.hasRedirection()) {
1422 const QString resolvedUrl = redirectQmldirContent(typeLoader, &qmldir);
1423 importUri = qmldir.typeNamespace();
1424 if (resolvedUrl != url) {
1425 if (QQmlImportInstance *existing
1426 = nameSpace->findImportByLocation(resolvedUrl, requestedVersion)) {
1427 // We've alraedy seen this import. No need to add another entry.
1428 return validVersion(existing->version);
1429 }
1430 }
1431
1432 return finalizeImport<IsLibrary::No>(
1433 nameSpace, qmldir, importUri, resolvedUrl, precedence, requestedVersion,
1434 importedVersion, errors);
1435 }
1436
1437 return finalizeImport<IsLibrary::No>(
1438 nameSpace, qmldir, importUri, url, precedence, requestedVersion,
1439 importedVersion, errors);
1440}
1441
1442static QTypeRevision qmldirContentError(const QString &uri, QList<QQmlError> *errors)
1443{
1444 if (errors->isEmpty()) {
1445 QQmlError error;
1446 error.setDescription(QQmlTypeLoader::tr("Cannot update qmldir content for '%1'").arg(uri));
1447 errors->prepend(error);
1448 }
1449 return QTypeRevision();
1450}
1451
1452QTypeRevision QQmlImports::updateQmldirContent(
1453 QQmlTypeLoader *typeLoader, const QString &uri, QTypeRevision version,
1454 const QString &prefix, const QString &qmldirIdentifier, const QString &qmldirUrl,
1455 QList<QQmlError> *errors)
1456{
1457 Q_ASSERT(typeLoader);
1458 Q_ASSERT(errors);
1459
1460 qCDebug(lcQmlImport)
1461 << "updateQmldirContent:" << qPrintable(baseUrl().toString())
1462 << uri << "to" << qmldirUrl << "as" << prefix;
1463
1464 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1465 Q_ASSERT(nameSpace);
1466
1467 QQmlImportInstance *import = nameSpace->findImportByModuleUri(uri, version);
1468 if (!import)
1469 return qmldirContentError(uri, errors);
1470
1471 QQmlTypeLoaderQmldirContent qmldir;
1472 if (!getQmldirContent(typeLoader, qmldirIdentifier, uri, &qmldir, errors))
1473 return QTypeRevision();
1474
1475 if (!qmldir.hasContent())
1476 return qmldirContentError(uri, errors);
1477
1478 // Load the plugin before redirecting. Otherwise we might not find the qmldir we're looking for.
1479 version = importExtension(typeLoader, uri, import->version, &qmldir, errors);
1480 if (!version.isValid())
1481 return QTypeRevision();
1482
1483 QString resolvedUrl;
1484 if (qmldir.hasRedirection()) {
1485 resolvedUrl = redirectQmldirContent(typeLoader, &qmldir);
1486 if (resolvedUrl != import->url) {
1487 if (QQmlImportInstance *existing
1488 = nameSpace->findImportByLocation(resolvedUrl, import->version)) {
1489 // We've re-discovered the same module via a different redirect.
1490 // No need to import it again.
1491 nameSpace->imports.removeOne(import);
1492 delete import;
1493 return validVersion(existing->version);
1494 }
1495 import->url = resolvedUrl;
1496 }
1497 import->uri = qmldir.typeNamespace();
1498 } else {
1499 resolvedUrl = qmldirUrl;
1500 }
1501
1502 registerBuiltinModuleTypes(qmldir, version);
1503
1504 if (import->setQmldirContent(resolvedUrl, qmldir, nameSpace, errors)) {
1505 if (import->qmlDirComponents.isEmpty() && import->qmlDirScripts.isEmpty()) {
1506 // The implicit import qmldir can be empty, and plugins have no extra versions
1507 if (uri != QLatin1String(".")
1508 && !QQmlMetaType::matchingModuleVersion(import->uri, version).isValid()) {
1509 errors->prepend(moduleNotFoundError(uri, relevantVersion(uri, version)));
1510 return QTypeRevision();
1511 }
1512 } else {
1513 // Verify that the qmldir content is valid for this version
1514 version = matchingQmldirVersion(qmldir, import->uri, version, errors);
1515 if (!version.isValid())
1516 return QTypeRevision();
1517 }
1518 return validVersion(version);
1519 }
1520
1521 return qmldirContentError(uri, errors);
1522}
1523
1524/*!
1525 \fn QQmlImports::addImplicitImport(QQmlTypeLoader *typeLoader, QString *localQmldir, QList<QQmlError> *errors)
1526 \internal
1527
1528 Adds an implicit "." file import. This is equivalent to calling addFileImport(), but error
1529 messages related to the path or qmldir file not existing are suppressed.
1530
1531 Additionally, this will add the import with lowest instead of highest precedence.
1532*/
1533
1534
1535/*!
1536 \internal
1537 */
1538bool QQmlImports::addInlineComponentImport(
1539 QQmlImportInstance *const importInstance, const QString &name, const QUrl &importUrl)
1540{
1541 importInstance->url = importUrl.toString();
1542 importInstance->uri = name;
1543 importInstance->isInlineComponent = true;
1544 importInstance->version = QTypeRevision::zero();
1545 m_unqualifiedset.imports.push_back(importInstance);
1546 m_unqualifiedset.setNeedsSorting(true);
1547 return true;
1548}
1549
1550QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file)
1551{
1552 QUrl url(QLatin1String(file.at(0) == Colon ? "qrc" : "") + file);
1553
1554 // We don't support single character schemes as those conflict with windows drive letters.
1555 if (url.scheme().size() < 2)
1556 return QUrl::fromLocalFile(file);
1557 return url;
1558}
1559
1560void QQmlImports::setDesignerSupportRequired(bool b)
1561{
1562 designerSupportRequired = b;
1563}
1564
1565QT_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)
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)