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 const QQmlDirExports exports = qmldir.exports();
1063 for (QQmlDirParser::Export exp : exports) {
1064 Q_ASSERT(exp.hasMajorVersion()); // Can't specify a minor-only version in qmldir
1065 addVersion(QTypeRevision::fromVersion(exp.majorVersion(), 0));
1066 addVersion(exp.hasMinorVersion() ? exp
1067 : QTypeRevision::fromVersion(exp.majorVersion(), 254));
1068 }
1069
1070 ConstIterator cend = components.constEnd();
1071 for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
1072 for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) {
1073 if (cit2->typeName == cit->typeName && cit2->version == cit->version) {
1074 // This entry clashes with a predecessor
1075 QQmlError error;
1076 error.setDescription(
1077 QQmlImports::tr(
1078 "\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1079 .arg(cit->typeName).arg(cit->version.majorVersion())
1080 .arg(cit->version.minorVersion()).arg(uri));
1081 errors->prepend(error);
1082 return QTypeRevision();
1083 }
1084 }
1085
1086 addVersion(cit->version);
1087 }
1088
1089 typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
1090 const QQmlDirScripts &scripts = qmldir.scripts();
1091
1092 SConstIterator send = scripts.constEnd();
1093 for (SConstIterator sit = scripts.constBegin(); sit != send; ++sit) {
1094 for (SConstIterator sit2 = scripts.constBegin(); sit2 != sit; ++sit2) {
1095 if (sit2->nameSpace == sit->nameSpace && sit2->version == sit->version) {
1096 // This entry clashes with a predecessor
1097 QQmlError error;
1098 error.setDescription(QQmlImports::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1099 .arg(sit->nameSpace).arg(sit->version.majorVersion())
1100 .arg(sit->version.minorVersion()).arg(uri));
1101 errors->prepend(error);
1102 return QTypeRevision();
1103 }
1104 }
1105
1106 addVersion(sit->version);
1107 }
1108
1109 // Failure to find a match is only an error if we were asking for a specific version ...
1110 if (version.hasMajorVersion()
1111 && (bestMajorVersion < 0
1112 || (version.hasMinorVersion()
1113 && (lowestMinorVersion > version.minorVersion()
1114 || highestMinorVersion < version.minorVersion())))) {
1115 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
1116 return QTypeRevision();
1117 }
1118
1119 // ... otherwise, anything is valid.
1120 if (bestMajorVersion < 0)
1121 return QQmlImports::validVersion();
1122
1123 return QTypeRevision::fromVersion(
1124 bestMajorVersion,
1125 (version.hasMajorVersion() && version.hasMinorVersion())
1126 ? version.minorVersion()
1127 : highestMinorVersion);
1128}
1129
1130QQmlImportNamespace *QQmlImports::importNamespace(const QString &prefix)
1131{
1132 QQmlImportNamespace *nameSpace = nullptr;
1133
1134 if (prefix.isEmpty()) {
1135 nameSpace = &m_unqualifiedset;
1136 } else {
1137 nameSpace = findQualifiedNamespace(prefix);
1138
1139 if (!nameSpace) {
1140 nameSpace = new QQmlImportNamespace;
1141 nameSpace->prefix = prefix;
1142 m_qualifiedSets.append(nameSpace);
1143 }
1144 }
1145
1146 return nameSpace;
1147}
1148
1149static QString getVersionInfo(QTypeRevision version) {
1150 return version.isValid() ? QDebug::toString(version) : u"(latest)"_s;
1151}
1152
1154 const QString &uri, QTypeRevision version, QList<QQmlError> *errors)
1155{
1156 const QTypeRevision matchingVersion = QQmlMetaType::matchingModuleVersion(uri, version);
1157 if (!matchingVersion.isValid())
1158 errors->prepend(QQmlImports::moduleNotFoundError(uri, relevantVersion(uri, version)));
1159 return matchingVersion;
1160}
1161
1163 const QString &uri, QTypeRevision version, const QQmlTypeLoaderQmldirContent &qmldir,
1164 QQmlImportInstance *inserted, QList<QQmlError> *errors)
1165{
1166 // Ensure that we are actually providing something
1167 const QTypeRevision matchingVersion = QQmlMetaType::matchingModuleVersion(uri, version);
1168 if (matchingVersion.isValid())
1169 return matchingVersion;
1170
1171 if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) {
1172 if (qmldir.plugins().isEmpty()) {
1173 if (!qmldir.imports().isEmpty())
1174 return QQmlImports::validVersion(); // This is a pure redirection
1175 if (qmldir.hasTypeInfo())
1176 return QQmlImports::validVersion(); // A pure C++ module without plugin
1177 }
1178 errors->prepend(QQmlImports::moduleNotFoundError(uri, relevantVersion(uri, version)));
1179 return QTypeRevision();
1180 } else {
1181 // Verify that the qmldir content is valid for this version
1182 version = matchingQmldirVersion(qmldir, uri, version, errors);
1183 if (!version.isValid())
1184 return QTypeRevision();
1185 }
1186
1187 Q_ASSERT(version.isValid());
1188 return version;
1189}
1190
1191QTypeRevision QQmlImports::addLibraryImport(
1192 QQmlTypeLoader *typeLoader, const QString &uri, const QString &prefix,
1193 QTypeRevision requestedVersion, const QString &qmldirIdentifier, const QString &qmldirUrl,
1194 ImportFlags flags, quint8 precedence, QList<QQmlError> *errors)
1195{
1196 Q_ASSERT(typeLoader);
1197 Q_ASSERT(errors);
1198
1199 if (lcQmlImport().isDebugEnabled()) {
1200 qCDebug(lcQmlImport)
1201 << "addLibraryImport:" << qPrintable(baseUrl().toString())
1202 << uri << "version" << getVersionInfo(requestedVersion) << "as" << prefix;
1203 }
1204
1205 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1206 Q_ASSERT(nameSpace);
1207
1208 const bool noQmldir = qmldirIdentifier.isEmpty();
1209 const bool isIncomplete = (flags & QQmlImports::ImportIncomplete);
1210 if (noQmldir || isIncomplete) {
1211 QQmlImportInstance *inserted = addImportToNamespace<IsLibrary::Yes>(
1212 nameSpace, uri, qmldirUrl, requestedVersion, precedence);
1213 Q_ASSERT(inserted);
1214
1215 if (noQmldir && !isIncomplete) {
1216 // No need to wait for the qmldir to become available if we're not supposed to use it.
1217 if (!QQmlMetaType::typeModule(uri, requestedVersion))
1218 QQmlMetaType::qmlRegisterModuleTypes(uri);
1219 return matchingModuleVersionForLibraryImport(uri, requestedVersion, errors);
1220 }
1221
1222 return validVersion(requestedVersion);
1223 }
1224
1225 QQmlTypeLoaderQmldirContent qmldir;
1226 if (!getQmldirContent(typeLoader, qmldirIdentifier, uri, &qmldir, errors)) {
1227 // qmldir had errors.
1228 return QTypeRevision();
1229 }
1230
1231 // qmldir is remote and can't be loaded synchronously, but we may already know the module.
1232 if (!qmldir.hasContent())
1233 return matchingModuleVersionForLibraryImport(uri, requestedVersion, errors);
1234
1235 // Load the plugin before redirecting. Otherwise we might not find the qmldir we're looking for.
1236 const QTypeRevision importedVersion = importExtension(
1237 typeLoader, uri, requestedVersion, &qmldir, errors);
1238 if (!importedVersion.isValid())
1239 return QTypeRevision();
1240
1241 QString resolvedUrl;
1242 QString resolvedUri;
1243 if (qmldir.hasRedirection()) {
1244 resolvedUrl = redirectQmldirContent(typeLoader, &qmldir);
1245 resolvedUri = qmldir.typeNamespace();
1246 } else {
1247 resolvedUrl = qmldirUrl;
1248 resolvedUri = uri;
1249 }
1250
1251 if (QQmlImportInstance *existing
1252 = nameSpace->findImportByLocation(resolvedUrl, requestedVersion);
1253 existing && existing->isLibrary && existing->uri == resolvedUri) {
1254 // Even if the precedence stays the same we have to re-insert. The ordering wrt other
1255 // imports of the same precendence may change.
1256 nameSpace->imports.removeOne(existing);
1257 existing->precedence = std::min(precedence, existing->precedence);
1258 existing->implicitlyImported = existing->precedence >= QQmlImportInstance::Implicit;
1259 insertImport(nameSpace, existing);
1260 return finalizeLibraryImport(uri, importedVersion, qmldir, existing, errors);
1261 }
1262
1263 return finalizeImport<IsLibrary::Yes>(
1264 nameSpace, qmldir, resolvedUri, resolvedUrl, precedence, requestedVersion,
1265 importedVersion, errors, [&](QQmlImportInstance *inserted) {
1266 return finalizeLibraryImport(uri, importedVersion, qmldir, inserted, errors);
1267 });
1268}
1269
1270/*!
1271 \internal
1272
1273 Adds information to \a database such that subsequent calls to resolveType()
1274 will resolve types qualified by \a prefix by considering types found at the given \a uri.
1275
1276 The \a uri is either a directory (if importType is FileImport), or a URI resolved using paths
1277 added via addImportPath() (if importType is LibraryImport).
1278
1279 The \a prefix may be empty, in which case the import location is considered for
1280 unqualified types.
1281
1282 The base URL must already have been set with Import::setBaseUrl().
1283
1284 Optionally, the qmldir the import resolved to can be returned by providing the \a localQmldir
1285 parameter. Not all imports will have a local qmldir. If there is none, the \a localQmldir
1286 parameter won't be set.
1287
1288 Returns a valid QTypeRevision on success, and an invalid one on failure.
1289 In case of failure, the \a errors array will filled appropriately.
1290*/
1291QTypeRevision QQmlImports::addFileImport(
1292 QQmlTypeLoader *typeLoader, const QString &uri, const QString &prefix,
1293 QTypeRevision requestedVersion, ImportFlags flags, quint8 precedence, QString *localQmldir,
1294 QList<QQmlError> *errors)
1295{
1296 Q_ASSERT(typeLoader);
1297 Q_ASSERT(errors);
1298
1299 if (lcQmlImport().isDebugEnabled()) {
1300 qCDebug(lcQmlImport)
1301 << "addFileImport:" << qPrintable(baseUrl().toString())
1302 << uri << "version" << getVersionInfo(requestedVersion) << "as" << prefix;
1303 }
1304
1305 if (uri.startsWith(Slash) || uri.startsWith(Colon)) {
1306 QQmlError error;
1307 const QString fix = uri.startsWith(Slash) ? QLatin1String("file:") + uri
1308 : QLatin1String("qrc") + uri;
1309 error.setDescription(QQmlImports::tr(
1310 "\"%1\" is not a valid import URL. "
1311 "You can pass relative paths or URLs with schema, but not "
1312 "absolute paths or resource paths. Try \"%2\".").arg(uri, fix));
1313 errors->prepend(error);
1314 return QTypeRevision();
1315 }
1316
1317 Q_ASSERT(errors);
1318
1319 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1320 Q_ASSERT(nameSpace);
1321
1322 // The uri for this import. For library imports this is the same as uri
1323 // specified by the user, but it may be different in the case of file imports.
1324 QString importUri = uri;
1325 QString qmldirUrl = resolveLocalUrl(m_base, importUri + (importUri.endsWith(Slash)
1326 ? String_qmldir
1327 : Slash_qmldir));
1328 qmldirUrl = typeLoader->interceptUrl(
1329 QUrl(qmldirUrl), QQmlAbstractUrlInterceptor::QmldirFile).toString();
1330 QString qmldirIdentifier;
1331
1332 if (QQmlFile::isLocalFile(qmldirUrl)) {
1333
1334 const QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
1335 Q_ASSERT(!localFileOrQrc.isEmpty());
1336
1337 const QString dir = localFileOrQrc.left(localFileOrQrc.lastIndexOf(Slash) + 1);
1338 if (!typeLoader->directoryExists(dir)) {
1339 if (precedence < QQmlImportInstance::Implicit) {
1340 QQmlError error;
1341 error.setDescription(QQmlImports::tr("\"%1\": no such directory").arg(uri));
1342 error.setUrl(QUrl(qmldirUrl));
1343 errors->prepend(error);
1344 }
1345 return QTypeRevision();
1346 }
1347
1348 // Transforms the (possible relative) uri into our best guess relative to the
1349 // import paths.
1350 importUri = resolvedUri(dir, typeLoader);
1351 if (importUri.endsWith(Slash))
1352 importUri.chop(1);
1353
1354 if (QString cleanPath = typeLoader->cleanFilePath(localFileOrQrc); !cleanPath.isEmpty()) {
1355 qmldirIdentifier = std::move(cleanPath);
1356 if (localQmldir)
1357 *localQmldir = qmldirIdentifier;
1358 }
1359
1360 } else if (nameSpace->prefix.isEmpty() && !(flags & QQmlImports::ImportIncomplete)) {
1361
1362 if (precedence < QQmlImportInstance::Implicit) {
1363 QQmlError error;
1364 error.setDescription(QQmlImports::tr("import \"%1\" has no qmldir and no namespace").arg(importUri));
1365 error.setUrl(QUrl(qmldirUrl));
1366 errors->prepend(error);
1367 }
1368
1369 return QTypeRevision();
1370
1371 }
1372
1373 // The url for the path containing files for this import
1374 QString url = resolveLocalUrl(m_base, uri);
1375 if (url.isEmpty()) {
1376 QQmlError error;
1377 error.setDescription(
1378 QQmlImports::tr("Cannot resolve URL for import \"%1\"").arg(uri));
1379 error.setUrl(m_baseUrl);
1380 errors->prepend(error);
1381 return QTypeRevision();
1382 }
1383
1384 if (!url.endsWith(Slash) && !url.endsWith(Backslash))
1385 url += Slash;
1386
1387 // ### For enum support, we are now adding the implicit import always (and earlier). Bail early
1388 // if the implicit import has already been explicitly added, otherwise we can run into issues
1389 // with duplicate imports. However remember that we attempted to add this as implicit import, to
1390 // allow for the loading of internal types.
1391 if (precedence >= QQmlImportInstance::Implicit) {
1392 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
1393 it != nameSpace->imports.constEnd(); ++it) {
1394 if ((*it)->url == url) {
1395 (*it)->implicitlyImported = true;
1396 return validVersion(requestedVersion);
1397 }
1398 }
1399 }
1400
1401 if ((flags & QQmlImports::ImportIncomplete) || qmldirIdentifier.isEmpty()) {
1402 QQmlImportInstance *inserted = addImportToNamespace<IsLibrary::No>(
1403 nameSpace, importUri, url, requestedVersion, precedence);
1404 Q_ASSERT(inserted);
1405 return validVersion(requestedVersion);
1406 }
1407
1408 QQmlTypeLoaderQmldirContent qmldir;
1409 if (!getQmldirContent(typeLoader, qmldirIdentifier, importUri, &qmldir, errors))
1410 return QTypeRevision();
1411
1412 if (!qmldir.hasContent()) {
1413 QQmlImportInstance *inserted = addImportToNamespace<IsLibrary::No>(
1414 nameSpace, importUri, url, requestedVersion, precedence);
1415 Q_ASSERT(inserted);
1416 return validVersion(requestedVersion);
1417 }
1418
1419 // Prefer the qmldir URI. Unless it doesn't exist.
1420 if (qmldir.hasTypeNamespace())
1421 importUri = qmldir.typeNamespace();
1422
1423 // Load the plugin before redirecting. Otherwise we might not find the qmldir we're looking for.
1424 const QTypeRevision importedVersion
1425 = importExtension(typeLoader, importUri, requestedVersion, &qmldir, errors);
1426 if (!importedVersion.isValid())
1427 return QTypeRevision();
1428
1429 if (qmldir.hasRedirection()) {
1430 const QString resolvedUrl = redirectQmldirContent(typeLoader, &qmldir);
1431 importUri = qmldir.typeNamespace();
1432 if (resolvedUrl != url) {
1433 if (QQmlImportInstance *existing
1434 = nameSpace->findImportByLocation(resolvedUrl, requestedVersion)) {
1435 // We've alraedy seen this import. No need to add another entry.
1436 return validVersion(existing->version);
1437 }
1438 }
1439
1440 return finalizeImport<IsLibrary::No>(
1441 nameSpace, qmldir, importUri, resolvedUrl, precedence, requestedVersion,
1442 importedVersion, errors);
1443 }
1444
1445 return finalizeImport<IsLibrary::No>(
1446 nameSpace, qmldir, importUri, url, precedence, requestedVersion,
1447 importedVersion, errors);
1448}
1449
1450static QTypeRevision qmldirContentError(const QString &uri, QList<QQmlError> *errors)
1451{
1452 if (errors->isEmpty()) {
1453 QQmlError error;
1454 error.setDescription(QQmlTypeLoader::tr("Cannot update qmldir content for '%1'").arg(uri));
1455 errors->prepend(error);
1456 }
1457 return QTypeRevision();
1458}
1459
1460QTypeRevision QQmlImports::updateQmldirContent(
1461 QQmlTypeLoader *typeLoader, const QString &uri, QTypeRevision version,
1462 const QString &prefix, const QString &qmldirIdentifier, const QString &qmldirUrl,
1463 QList<QQmlError> *errors)
1464{
1465 Q_ASSERT(typeLoader);
1466 Q_ASSERT(errors);
1467
1468 qCDebug(lcQmlImport)
1469 << "updateQmldirContent:" << qPrintable(baseUrl().toString())
1470 << uri << "to" << qmldirUrl << "as" << prefix;
1471
1472 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1473 Q_ASSERT(nameSpace);
1474
1475 QQmlImportInstance *import = nameSpace->findImportByModuleUri(uri, version);
1476 if (!import)
1477 return qmldirContentError(uri, errors);
1478
1479 QQmlTypeLoaderQmldirContent qmldir;
1480 if (!getQmldirContent(typeLoader, qmldirIdentifier, uri, &qmldir, errors))
1481 return QTypeRevision();
1482
1483 if (!qmldir.hasContent())
1484 return qmldirContentError(uri, errors);
1485
1486 // Load the plugin before redirecting. Otherwise we might not find the qmldir we're looking for.
1487 version = importExtension(typeLoader, uri, import->version, &qmldir, errors);
1488 if (!version.isValid())
1489 return QTypeRevision();
1490
1491 QString resolvedUrl;
1492 if (qmldir.hasRedirection()) {
1493 resolvedUrl = redirectQmldirContent(typeLoader, &qmldir);
1494 if (resolvedUrl != import->url) {
1495 if (QQmlImportInstance *existing
1496 = nameSpace->findImportByLocation(resolvedUrl, import->version)) {
1497 // We've re-discovered the same module via a different redirect.
1498 // No need to import it again.
1499 nameSpace->imports.removeOne(import);
1500 delete import;
1501 return validVersion(existing->version);
1502 }
1503 import->url = resolvedUrl;
1504 }
1505 import->uri = qmldir.typeNamespace();
1506 } else {
1507 resolvedUrl = qmldirUrl;
1508 }
1509
1510 registerBuiltinModuleTypes(qmldir, version);
1511
1512 if (import->setQmldirContent(resolvedUrl, qmldir, nameSpace, errors)) {
1513 if (import->qmlDirComponents.isEmpty() && import->qmlDirScripts.isEmpty()) {
1514 // The implicit import qmldir can be empty, and plugins have no extra versions
1515 if (uri != QLatin1String(".")
1516 && !QQmlMetaType::matchingModuleVersion(import->uri, version).isValid()) {
1517 errors->prepend(moduleNotFoundError(uri, relevantVersion(uri, version)));
1518 return QTypeRevision();
1519 }
1520 } else {
1521 // Verify that the qmldir content is valid for this version
1522 version = matchingQmldirVersion(qmldir, import->uri, version, errors);
1523 if (!version.isValid())
1524 return QTypeRevision();
1525 }
1526 return validVersion(version);
1527 }
1528
1529 return qmldirContentError(uri, errors);
1530}
1531
1532/*!
1533 \fn QQmlImports::addImplicitImport(QQmlTypeLoader *typeLoader, QString *localQmldir, QList<QQmlError> *errors)
1534 \internal
1535
1536 Adds an implicit "." file import. This is equivalent to calling addFileImport(), but error
1537 messages related to the path or qmldir file not existing are suppressed.
1538
1539 Additionally, this will add the import with lowest instead of highest precedence.
1540*/
1541
1542
1543/*!
1544 \internal
1545 */
1546bool QQmlImports::addInlineComponentImport(
1547 QQmlImportInstance *const importInstance, const QString &name, const QUrl &importUrl)
1548{
1549 importInstance->url = importUrl.toString();
1550 importInstance->uri = name;
1551 importInstance->isInlineComponent = true;
1552 importInstance->version = QTypeRevision::zero();
1553 m_unqualifiedset.imports.push_back(importInstance);
1554 m_unqualifiedset.setNeedsSorting(true);
1555 return true;
1556}
1557
1558QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file)
1559{
1560 QUrl url(QLatin1String(file.at(0) == Colon ? "qrc" : "") + file);
1561
1562 // We don't support single character schemes as those conflict with windows drive letters.
1563 if (url.scheme().size() < 2)
1564 return QUrl::fromLocalFile(file);
1565 return url;
1566}
1567
1568void QQmlImports::setDesignerSupportRequired(bool b)
1569{
1570 designerSupportRequired = b;
1571}
1572
1573QT_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)