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