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