1// Copyright (C) 2017 Crimson AS <info@crimson.no>
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qqmlimport_p.h"
6
7#include <QtCore/qdebug.h>
8#include <QtCore/qdir.h>
9#include <QtQml/qqmlfile.h>
10#include <QtCore/qfileinfo.h>
11#include <QtCore/qpluginloader.h>
12#include <QtCore/qlibraryinfo.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#ifdef Q_OS_MACOS
29#include "private/qcore_mac_p.h"
30#endif
31
32#include <algorithm>
33#include <functional>
34
35using namespace Qt::Literals::StringLiterals;
36
37QT_BEGIN_NAMESPACE
38
39DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
40
41class QmlImportCategoryHolder
42{
43 Q_DISABLE_COPY_MOVE(QmlImportCategoryHolder);
44public:
45
46 QmlImportCategoryHolder() : m_category("qt.qml.import")
47 {
48 // We have to explicitly setEnabled() here because for categories that start with
49 // "qt." it won't accept QtDebugMsg as argument. Debug messages are off by default
50 // for all Qt logging categories.
51 if (qmlImportTrace())
52 m_category.setEnabled(type: QtDebugMsg, enable: true);
53 }
54
55 ~QmlImportCategoryHolder() = default;
56
57 const QLoggingCategory &category() const { return m_category; }
58
59private:
60 QLoggingCategory m_category;
61};
62
63const QLoggingCategory &lcQmlImport()
64{
65 static const QmlImportCategoryHolder holder;
66 return holder.category();
67}
68
69DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
70
71static const QLatin1Char Dot('.');
72static const QLatin1Char Slash('/');
73static const QLatin1Char Backslash('\\');
74static const QLatin1Char Colon(':');
75static const QLatin1String Slash_qmldir("/qmldir");
76static const QLatin1String String_qmldir("qmldir");
77static const QString dotqml_string(QStringLiteral(".qml"));
78static const QString dotuidotqml_string(QStringLiteral(".ui.qml"));
79static bool designerSupportRequired = false;
80
81namespace {
82
83QTypeRevision relevantVersion(const QString &uri, QTypeRevision version)
84{
85 return QQmlMetaType::latestModuleVersion(uri).isValid() ? version : QTypeRevision();
86}
87
88QQmlError moduleNotFoundError(const QString &uri, QTypeRevision version)
89{
90 QQmlError error;
91 if (version.hasMajorVersion()) {
92 error.setDescription(QQmlImportDatabase::tr(
93 sourceText: "module \"%1\" version %2.%3 is not installed")
94 .arg(a: uri).arg(a: version.majorVersion())
95 .arg(a: version.hasMinorVersion()
96 ? QString::number(version.minorVersion())
97 : QLatin1String("x")));
98 } else {
99 error.setDescription(QQmlImportDatabase::tr(sourceText: "module \"%1\" is not installed")
100 .arg(a: uri));
101 }
102 return error;
103}
104
105QString resolveLocalUrl(const QString &url, const QString &relative)
106{
107 if (relative.contains(c: Colon)) {
108 // contains a host name
109 return QUrl(url).resolved(relative: QUrl(relative)).toString();
110 } else if (relative.isEmpty()) {
111 return url;
112 } else if (relative.at(i: 0) == Slash || !url.contains(c: Slash)) {
113 return relative;
114 } else {
115 const QStringView baseRef = QStringView{url}.left(n: url.lastIndexOf(c: Slash) + 1);
116 if (relative == QLatin1String("."))
117 return baseRef.toString();
118
119 QString base = baseRef + relative;
120
121 // Remove any relative directory elements in the path
122 int length = base.size();
123 int index = 0;
124 while ((index = base.indexOf(s: QLatin1String("/."), from: index)) != -1) {
125 if ((length > (index + 2)) && (base.at(i: index + 2) == Dot) &&
126 (length == (index + 3) || (base.at(i: index + 3) == Slash))) {
127 // Either "/../" or "/..<END>"
128 int previous = base.lastIndexOf(c: Slash, from: index - 1);
129 if (previous == -1)
130 break;
131
132 int removeLength = (index - previous) + 3;
133 base.remove(i: previous + 1, len: removeLength);
134 length -= removeLength;
135 index = previous;
136 } else if ((length == (index + 2)) || (base.at(i: index + 2) == Slash)) {
137 // Either "/./" or "/.<END>"
138 base.remove(i: index, len: 2);
139 length -= 2;
140 } else {
141 ++index;
142 }
143 }
144
145 return base;
146 }
147}
148
149bool isPathAbsolute(const QString &path)
150{
151#if defined(Q_OS_UNIX)
152 return (path.at(i: 0) == Slash);
153#else
154 QFileInfo fi(path);
155 return fi.isAbsolute();
156#endif
157}
158
159} // namespace
160
161/*!
162 \internal
163 \class QQmlImportInstance
164
165 A QQmlImportType represents a single import of a document, held within a
166 namespace.
167
168 \note The uri here may not necessarily be unique (e.g. for file imports).
169
170 \note Version numbers may be -1 for file imports: this means that no
171 version was specified as part of the import. Type resolution will be
172 responsible for attempting to find the "best" possible version.
173*/
174
175/*!
176 \internal
177 \class QQmlImportNamespace
178
179 A QQmlImportNamespace is a way of seperating imports into a local namespace.
180
181 Within a QML document, there is at least one namespace (the
182 "unqualified set") where imports without a qualifier are placed, i.e:
183
184 import QtQuick 2.6
185
186 will have a single namespace (the unqualified set) containing a single import
187 for QtQuick 2.6. However, there may be others if an import statement gives
188 a qualifier, i.e the following will result in an additional new
189 QQmlImportNamespace in the qualified set:
190
191 import MyFoo 1.0 as Foo
192*/
193
194/*!
195\class QQmlImports
196\brief The QQmlImports class encapsulates one QML document's import statements.
197\internal
198*/
199
200QTypeRevision QQmlImports::validVersion(QTypeRevision version)
201{
202 // If the given version is invalid, return a valid but useless version to signal "It's OK".
203 return version.isValid() ? version : QTypeRevision::fromMinorVersion(minorVersion: 0);
204}
205
206/*!
207 Sets the base URL to be used for all relative file imports added.
208*/
209void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString)
210{
211 m_baseUrl = url;
212
213 if (urlString.isEmpty())
214 m_base = url.toString();
215 else
216 m_base = urlString;
217}
218
219/*!
220 \fn QQmlImports::baseUrl()
221 \internal
222
223 Returns the base URL to be used for all relative file imports added.
224*/
225
226/*
227 \internal
228
229 This method is responsible for populating data of all types visible in this
230 document's imports into the \a cache for resolution elsewhere (e.g. in JS,
231 or when loading additional types).
232
233 \note This is for C++ types only. Composite types are handled separately,
234 as they do not have a QQmlTypeModule.
235*/
236void QQmlImports::populateCache(QQmlTypeNameCache *cache) const
237{
238 const QQmlImportNamespace &set = m_unqualifiedset;
239
240 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
241 const QQmlImportInstance *import = set.imports.at(i: ii);
242 QQmlTypeModule *module = QQmlMetaType::typeModule(uri: import->uri, version: import->version);
243 if (module) {
244 cache->m_anonymousImports.append(t: QQmlTypeModuleVersion(module, import->version));
245 }
246 }
247
248 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(v: ns)) {
249
250 const QQmlImportNamespace &set = *ns;
251
252 // positioning is important; we must create the namespace even if there is no module.
253 QQmlImportRef &typeimport = cache->m_namedImports[set.prefix];
254 typeimport.m_qualifier = set.prefix;
255
256 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
257 const QQmlImportInstance *import = set.imports.at(i: ii);
258 QQmlTypeModule *module = QQmlMetaType::typeModule(uri: import->uri, version: import->version);
259 if (module) {
260 QQmlImportRef &typeimport = cache->m_namedImports[set.prefix];
261 typeimport.modules.append(t: QQmlTypeModuleVersion(module, import->version));
262 }
263 }
264 }
265}
266
267// We need to exclude the entry for the current baseUrl. This can happen for example
268// when handling qmldir files on the remote dir case and the current type is marked as
269// singleton.
270bool excludeBaseUrl(const QString &importUrl, const QString &fileName, const QString &baseUrl)
271{
272 if (importUrl.isEmpty())
273 return false;
274
275 if (baseUrl.startsWith(s: importUrl))
276 {
277 if (fileName == QStringView{baseUrl}.mid(pos: importUrl.size()))
278 return false;
279 }
280
281 return true;
282}
283
284void findCompositeSingletons(const QQmlImportNamespace &set, QList<QQmlImports::CompositeSingletonReference> &resultList, const QUrl &baseUrl)
285{
286 typedef QQmlDirComponents::const_iterator ConstIterator;
287
288 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
289 const QQmlImportInstance *import = set.imports.at(i: ii);
290
291 const QQmlDirComponents &components = import->qmlDirComponents;
292
293 const QTypeRevision importVersion = import->version;
294 auto shouldSkipSingleton = [importVersion](QTypeRevision singletonVersion) -> bool {
295 return importVersion.hasMajorVersion() &&
296 (singletonVersion.majorVersion() > importVersion.majorVersion()
297 || (singletonVersion.majorVersion() == importVersion.majorVersion()
298 && singletonVersion.minorVersion() > importVersion.minorVersion()));
299 };
300
301 ConstIterator cend = components.constEnd();
302 for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
303 if (cit->singleton && excludeBaseUrl(importUrl: import->url, fileName: cit->fileName, baseUrl: baseUrl.toString())) {
304 if (shouldSkipSingleton(cit->version))
305 continue;
306 QQmlImports::CompositeSingletonReference ref;
307 ref.typeName = cit->typeName;
308 ref.prefix = set.prefix;
309 ref.version = cit->version;
310 resultList.append(t: ref);
311 }
312 }
313
314 if (QQmlTypeModule *module = QQmlMetaType::typeModule(uri: import->uri, version: import->version)) {
315 module->walkCompositeSingletons(callback: [&resultList, &set, &shouldSkipSingleton](const QQmlType &singleton) {
316 if (shouldSkipSingleton(singleton.version()))
317 return;
318 QQmlImports::CompositeSingletonReference ref;
319 ref.typeName = singleton.elementName();
320 ref.prefix = set.prefix;
321 ref.version = singleton.version();
322 resultList.append(t: ref);
323 });
324 }
325 }
326}
327
328/*
329 \internal
330
331 Returns a list of all composite singletons present in this document's
332 imports.
333
334 This information is used by QQmlTypeLoader to ensure that composite singletons
335 are marked as dependencies during type loading.
336*/
337QList<QQmlImports::CompositeSingletonReference> QQmlImports::resolvedCompositeSingletons() const
338{
339 QList<QQmlImports::CompositeSingletonReference> compositeSingletons;
340
341 const QQmlImportNamespace &set = m_unqualifiedset;
342 findCompositeSingletons(set, resultList&: compositeSingletons, baseUrl: baseUrl());
343
344 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(v: ns)) {
345 const QQmlImportNamespace &set = *ns;
346 findCompositeSingletons(set, resultList&: compositeSingletons, baseUrl: baseUrl());
347 }
348
349 std::stable_sort(first: compositeSingletons.begin(), last: compositeSingletons.end(),
350 comp: [](const QQmlImports::CompositeSingletonReference &lhs,
351 const QQmlImports::CompositeSingletonReference &rhs) {
352 if (lhs.prefix != rhs.prefix)
353 return lhs.prefix < rhs.prefix;
354
355 if (lhs.typeName != rhs.typeName)
356 return lhs.typeName < rhs.typeName;
357
358 return lhs.version.majorVersion() != rhs.version.majorVersion()
359 ? lhs.version.majorVersion() < rhs.version.majorVersion()
360 : lhs.version.minorVersion() < rhs.version.minorVersion();
361 });
362
363 return compositeSingletons;
364}
365
366/*
367 \internal
368
369 Returns a list of scripts imported by this document. This is used by
370 QQmlTypeLoader to properly handle dependencies on imported scripts.
371*/
372QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
373{
374 QList<QQmlImports::ScriptReference> scripts;
375
376 const QQmlImportNamespace &set = m_unqualifiedset;
377
378 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
379 const QQmlImportInstance *import = set.imports.at(i: ii);
380
381 for (const QQmlDirParser::Script &script : import->qmlDirScripts) {
382 ScriptReference ref;
383 ref.nameSpace = script.nameSpace;
384 ref.location = QUrl(import->url).resolved(relative: QUrl(script.fileName));
385 scripts.append(t: ref);
386 }
387 }
388
389 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(v: ns)) {
390 const QQmlImportNamespace &set = *ns;
391
392 for (int ii = set.imports.size() - 1; ii >= 0; --ii) {
393 const QQmlImportInstance *import = set.imports.at(i: ii);
394
395 for (const QQmlDirParser::Script &script : import->qmlDirScripts) {
396 ScriptReference ref;
397 ref.nameSpace = script.nameSpace;
398 ref.qualifier = set.prefix;
399 ref.location = QUrl(import->url).resolved(relative: QUrl(script.fileName));
400 scripts.append(t: ref);
401 }
402 }
403 }
404
405 return scripts;
406}
407
408/*!
409 Forms complete paths to a qmldir file, from a base URL, a module URI and version specification.
410
411 For example, QtQml.Models 2.0:
412 - base/QtQml/Models.2.0/qmldir
413 - base/QtQml.2.0/Models/qmldir
414 - base/QtQml/Models.2/qmldir
415 - base/QtQml.2/Models/qmldir
416 - base/QtQml/Models/qmldir
417*/
418QStringList QQmlImports::completeQmldirPaths(const QString &uri, const QStringList &basePaths,
419 QTypeRevision version)
420{
421 QStringList paths = qQmlResolveImportPaths(uri, basePaths, version);
422 for (QString &path : paths)
423 path += Slash_qmldir;
424 return paths;
425}
426
427QString QQmlImports::versionString(QTypeRevision version, ImportVersion versionMode)
428{
429 if (versionMode == QQmlImports::FullyVersioned) {
430 // extension with fully encoded version number (eg. MyModule.3.2)
431 return QString::asprintf(format: ".%d.%d", version.majorVersion(), version.minorVersion());
432 } else if (versionMode == QQmlImports::PartiallyVersioned) {
433 // extension with encoded version major (eg. MyModule.3)
434 return QString::asprintf(format: ".%d", version.majorVersion());
435 } // else extension without version number (eg. MyModule)
436 return QString();
437}
438
439/*!
440 \internal
441
442 The given (namespace qualified) \a type is resolved to either
443 \list
444 \li a QQmlImportNamespace stored at \a ns_return, or
445 \li a QQmlType stored at \a type_return,
446 \endlist
447
448 If any return pointer is 0, the corresponding search is not done.
449
450 \sa addFileImport(), addLibraryImport
451*/
452bool QQmlImports::resolveType(
453 const QHashedStringRef &type, QQmlType *type_return, QTypeRevision *version_return,
454 QQmlImportNamespace **ns_return, QList<QQmlError> *errors,
455 QQmlType::RegistrationType registrationType, bool *typeRecursionDetected) const
456{
457 QQmlImportNamespace *ns = findQualifiedNamespace(type);
458 if (ns) {
459 if (ns_return)
460 *ns_return = ns;
461 return true;
462 }
463 if (type_return) {
464 if (resolveType(type, version_return, type_return, errors, registrationType,
465 typeRecursionDetected)) {
466 if (lcQmlImport().isDebugEnabled()) {
467#define RESOLVE_TYPE_DEBUG qCDebug(lcQmlImport) \
468 << "resolveType:" << qPrintable(baseUrl().toString()) << type.toString() << " => "
469
470 if (type_return && type_return->isValid()) {
471 if (type_return->isCompositeSingleton())
472 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL-SINGLETON";
473 else if (type_return->isComposite())
474 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL";
475 else if (type_return->isInlineComponentType())
476 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE(INLINECOMPONENT)";
477 else
478 RESOLVE_TYPE_DEBUG << type_return->typeName() << " TYPE";
479 }
480#undef RESOLVE_TYPE_DEBUG
481 }
482 return true;
483 }
484 }
485 return false;
486}
487
488bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl,
489 const QQmlTypeLoaderQmldirContent &qmldir,
490 QQmlImportNamespace *nameSpace, QList<QQmlError> *errors)
491{
492
493 const QString preferredPath = qmldir.preferredPath();
494 if (preferredPath.isEmpty()) {
495 Q_ASSERT(resolvedUrl.endsWith(Slash));
496 url = resolvedUrl;
497 } else {
498 Q_ASSERT(preferredPath.endsWith(Slash));
499 if (preferredPath.startsWith(c: u':'))
500 url = QStringLiteral("qrc") + preferredPath;
501 else
502 url = QUrl::fromLocalFile(localfile: preferredPath).toString();
503 }
504
505 qmlDirComponents = qmldir.components();
506
507 const QQmlDirScripts &scripts = qmldir.scripts();
508 if (!scripts.isEmpty()) {
509 // Verify that we haven't imported these scripts already
510 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
511 it != nameSpace->imports.constEnd(); ++it) {
512 if ((*it != this) && ((*it)->uri == uri)) {
513 QQmlError error;
514 error.setDescription(
515 QQmlImportDatabase::tr(sourceText: "\"%1\" is ambiguous. Found in %2 and in %3")
516 .arg(args&: uri, args&: url, args&: (*it)->url));
517 errors->prepend(t: error);
518 return false;
519 }
520 }
521
522 qmlDirScripts = getVersionedScripts(qmldirscripts: scripts, version);
523 }
524
525 return true;
526}
527
528QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qmldirscripts,
529 QTypeRevision version)
530{
531 QMap<QString, QQmlDirParser::Script> versioned;
532
533 for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
534 sit != qmldirscripts.constEnd(); ++sit) {
535 // Only include scripts that match our requested version
536 if ((!version.hasMajorVersion() || (sit->version.majorVersion() == version.majorVersion()))
537 && (!version.hasMinorVersion()
538 || (sit->version.minorVersion() <= version.minorVersion()))) {
539 // Load the highest version that matches
540 QMap<QString, QQmlDirParser::Script>::iterator vit = versioned.find(key: sit->nameSpace);
541 if (vit == versioned.end()
542 || (vit->version.minorVersion() < sit->version.minorVersion())) {
543 versioned.insert(key: sit->nameSpace, value: *sit);
544 }
545 }
546 }
547
548 return versioned.values();
549}
550
551/*!
552 \fn QQmlImports::resolveType(QQmlImportNamespace *ns, const QHashedStringRef &type, QQmlType *type_return, QTypeRevision *version_return, QQmlType::RegistrationType registrationType = QQmlType::AnyRegistrationType) const
553 \internal
554
555 Searching \e only in the namespace \a ns (previously returned in a call to
556 resolveType(), \a type is found and returned to
557 a QQmlType stored at \a type_return. If the type is from a QML file, the returned
558 type will be a CompositeType.
559
560 If the return pointer is 0, the corresponding search is not done.
561*/
562
563bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type,
564 QTypeRevision *version_return, QQmlType *type_return,
565 const QString *base, bool *typeRecursionDetected,
566 QQmlType::RegistrationType registrationType,
567 QQmlImport::RecursionRestriction recursionRestriction,
568 QList<QQmlError> *errors) const
569{
570 QQmlType t = QQmlMetaType::qmlType(name: type, module: uri, version);
571 if (t.isValid()) {
572 if (version_return)
573 *version_return = version;
574 if (type_return)
575 *type_return = t;
576 return true;
577 }
578
579 const QString typeStr = type.toString();
580 if (isInlineComponent) {
581 Q_ASSERT(type_return);
582 bool ret = uri == typeStr;
583 if (ret) {
584 Q_ASSERT(!type_return->isValid());
585 auto createICType = [&]() {
586 auto typePriv = new QQmlTypePrivate {QQmlType::RegistrationType::InlineComponentType};
587 const QUrl ownUrl = QUrl(url);
588 typePriv->elementName = ownUrl.fragment();
589 Q_ASSERT(!typePriv->elementName.isEmpty());
590 typePriv->extraData.id->url = ownUrl;
591 auto icType = QQmlType(typePriv);
592 typePriv->release();
593 return icType;
594 };
595 if (containingType.isValid()) {
596 // we currently cannot reference a Singleton inside itself
597 // in that case, containingType is still invalid
598 if (QQmlType ic = QQmlMetaType::inlineComponentType(containingType, name: typeStr);
599 ic.isValid()) {
600 *type_return = ic;
601 } else {
602 auto icType = createICType();
603 QQmlMetaType::associateInlineComponent(
604 containingType, name: typeStr, metaTypeIds: CompositeMetaTypeIds {}, existingType: icType);
605 *type_return = QQmlType(icType);
606 }
607 } else {
608 *type_return = createICType();
609 }
610 }
611 return ret;
612 }
613 QQmlDirComponents::ConstIterator it = qmlDirComponents.find(key: typeStr), end = qmlDirComponents.end();
614 if (it != end) {
615 QString componentUrl;
616 bool isCompositeSingleton = false;
617 QQmlDirComponents::ConstIterator candidate = end;
618 for ( ; it != end && it.key() == typeStr; ++it) {
619 const QQmlDirParser::Component &c = *it;
620 switch (registrationType) {
621 case QQmlType::AnyRegistrationType:
622 break;
623 case QQmlType::CompositeSingletonType:
624 if (!c.singleton)
625 continue;
626 break;
627 default:
628 if (c.singleton)
629 continue;
630 break;
631 }
632
633 // importing invalid version means import ALL versions
634 if (!version.hasMajorVersion() || (implicitlyImported && c.internal)
635 // allow the implicit import of internal types
636 || (c.version.majorVersion() == version.majorVersion()
637 && c.version.minorVersion() <= version.minorVersion())) {
638 // Is this better than the previous candidate?
639 if ((candidate == end)
640 || (c.version.majorVersion() > candidate->version.majorVersion())
641 || ((c.version.majorVersion() == candidate->version.majorVersion())
642 && (c.version.minorVersion() > candidate->version.minorVersion()))) {
643 if (base) {
644 componentUrl = resolveLocalUrl(url: QString(url + c.typeName + dotqml_string), relative: c.fileName);
645 if (c.internal) {
646 if (resolveLocalUrl(url: *base, relative: c.fileName) != componentUrl)
647 continue; // failed attempt to access an internal type
648 }
649
650 const bool recursion = *base == componentUrl;
651 if (typeRecursionDetected)
652 *typeRecursionDetected = recursion;
653
654 if (recursionRestriction == QQmlImport::PreventRecursion && recursion) {
655 continue; // no recursion
656 }
657 }
658
659 // This is our best candidate so far
660 candidate = it;
661 isCompositeSingleton = c.singleton;
662 }
663 }
664 }
665
666 if (candidate != end) {
667 if (!base) // ensure we have a componentUrl
668 componentUrl = resolveLocalUrl(url: QString(url + candidate->typeName + dotqml_string), relative: candidate->fileName);
669 QQmlType returnType = QQmlMetaType::typeForUrl(urlString: componentUrl, typeName: type, isCompositeSingleton,
670 errors: nullptr, version: candidate->version);
671 if (version_return)
672 *version_return = candidate->version;
673 if (type_return)
674 *type_return = returnType;
675 return returnType.isValid();
676 }
677 } else if (!isLibrary) {
678 // the base path of the import if it's a local file
679 const QString localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url);
680 if (localDirectoryPath.isEmpty())
681 return false;
682
683 QString qmlUrl;
684 bool exists = false;
685
686 const QString urlsToTry[2] = {
687 typeStr + dotqml_string, // Type -> Type.qml
688 typeStr + dotuidotqml_string // Type -> Type.ui.qml
689 };
690 for (const QString &urlToTry : urlsToTry) {
691 exists = typeLoader->fileExists(path: localDirectoryPath, file: urlToTry);
692 if (exists) {
693#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
694 // don't let function.qml confuse the use of "new Function(...)" for example.
695 if (!QQml_isFileCaseCorrect(localDirectoryPath + urlToTry)) {
696 exists = false;
697 if (errors) {
698 QQmlError caseError;
699 caseError.setDescription(QLatin1String("File name case mismatch"));
700 errors->append(caseError);
701 }
702 break;
703 }
704#else
705 Q_UNUSED(errors);
706#endif
707 qmlUrl = url + urlToTry;
708 break;
709 }
710 }
711
712 if (exists) {
713 const bool recursion = base && *base == qmlUrl;
714 if (typeRecursionDetected)
715 *typeRecursionDetected = recursion;
716 if (recursionRestriction == QQmlImport::AllowRecursion || !recursion) {
717 QQmlType returnType = QQmlMetaType::typeForUrl(
718 urlString: qmlUrl, typeName: type, isCompositeSingleton: registrationType == QQmlType::CompositeSingletonType, errors);
719 if (type_return)
720 *type_return = returnType;
721 return returnType.isValid();
722 }
723 }
724 }
725
726 return false;
727}
728
729bool QQmlImports::resolveType(
730 const QHashedStringRef &type, QTypeRevision *version_return, QQmlType *type_return,
731 QList<QQmlError> *errors, QQmlType::RegistrationType registrationType,
732 bool *typeRecursionDetected) const
733{
734 const QVector<QHashedStringRef> splitName = type.split(sep: Dot);
735 auto resolveTypeInNamespace = [&](
736 QHashedStringRef unqualifiedtype, QQmlImportNamespace *nameSpace,
737 QList<QQmlError> *errors) -> bool {
738 if (nameSpace->resolveType(
739 typeLoader: m_typeLoader, type: unqualifiedtype, version_return, type_return, base: &m_base, errors,
740 registrationType, typeRecursionDeteced: typeRecursionDetected))
741 return true;
742 if (nameSpace->imports.size() == 1
743 && !nameSpace->imports.at(i: 0)->isLibrary
744 && type_return
745 && nameSpace != &m_unqualifiedset) {
746 // qualified, and only 1 url
747 *type_return = QQmlMetaType::typeForUrl(
748 urlString: resolveLocalUrl(url: nameSpace->imports.at(i: 0)->url,
749 relative: unqualifiedtype.toString() + QLatin1String(".qml")),
750 typeName: type, isCompositeSingleton: false, errors);
751 return type_return->isValid();
752 }
753 return false;
754 };
755 switch (splitName.size()) {
756 case 1: {
757 // must be a simple type
758 return resolveTypeInNamespace(type, &m_unqualifiedset, errors);
759 }
760 case 2: {
761 // either namespace + simple type OR simple type + inline component
762 QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(i: 0));
763 if (s) {
764 // namespace + simple type
765 return resolveTypeInNamespace(splitName.at(i: 1), s, errors);
766 } else {
767 if (resolveTypeInNamespace(splitName.at(i: 0), &m_unqualifiedset, nullptr)) {
768 // either simple type + inline component
769 const QString icName = splitName.at(i: 1).toString();
770 const QQmlType ic = QQmlMetaType::inlineComponentType(containingType: *type_return, name: icName);
771 if (ic.isValid()) {
772 *type_return = ic;
773 } else {
774 auto icTypePriv = new QQmlTypePrivate(QQmlType::RegistrationType::InlineComponentType);
775 icTypePriv->setContainingType(type_return);
776 icTypePriv->extraData.id->url = type_return->sourceUrl();
777 icTypePriv->extraData.id->url.setFragment(fragment: icName);
778 auto icType = QQmlType(icTypePriv);
779 icTypePriv->release();
780 QQmlMetaType::associateInlineComponent(
781 containingType: *type_return, name: icName, metaTypeIds: CompositeMetaTypeIds {}, existingType: icType);
782 *type_return = icType;
783 }
784 Q_ASSERT(type_return->containingType().isValid());
785 return true;
786 } else {
787 // or a failure
788 if (errors) {
789 QQmlError error;
790 error.setDescription(QQmlImportDatabase::tr(sourceText: "- %1 is neither a type nor a namespace").arg(a: splitName.at(i: 0).toString()));
791 errors->prepend(t: error);
792 }
793 return false;
794 }
795 }
796 }
797 case 3: {
798 // must be namespace + simple type + inline component
799 QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(i: 0));
800 QQmlError error;
801 if (!s) {
802 error.setDescription(QQmlImportDatabase::tr(sourceText: "- %1 is not a namespace").arg(a: splitName.at(i: 0).toString()));
803 } else {
804 if (resolveTypeInNamespace(splitName.at(i: 1), s, nullptr)) {
805 auto const icName = splitName.at(i: 2).toString();
806 QQmlType ic = QQmlMetaType::inlineComponentType(containingType: *type_return, name: icName);
807 if (ic.isValid())
808 *type_return = ic;
809 else {
810 auto icTypePriv = new QQmlTypePrivate(QQmlType::RegistrationType::InlineComponentType);
811 icTypePriv->setContainingType(type_return);
812 icTypePriv->extraData.id->url = type_return->sourceUrl();
813 icTypePriv->extraData.id->url.setFragment(fragment: icName);
814 auto icType = QQmlType(icTypePriv);
815 icTypePriv->release();
816 QQmlMetaType::associateInlineComponent(
817 containingType: *type_return, name: icName, metaTypeIds: CompositeMetaTypeIds {}, existingType: icType);
818 *type_return = icType;
819 }
820 return true;
821 } else {
822 error.setDescription(QQmlImportDatabase::tr(sourceText: "- %1 is not a type").arg(a: splitName.at(i: 1).toString()));
823 }
824 }
825 if (errors) {
826 errors->prepend(t: error);
827 }
828 return false;
829 }
830 default: {
831 // all other numbers suggest a user error
832 if (errors) {
833 QQmlError error;
834 error.setDescription(QQmlImportDatabase::tr(sourceText: "- nested namespaces not allowed"));
835 errors->prepend(t: error);
836 }
837 return false;
838 }
839 }
840 Q_UNREACHABLE();
841}
842
843QQmlImportInstance *QQmlImportNamespace::findImport(const QString &uri) const
844{
845 for (QQmlImportInstance *import : imports) {
846 if (import->uri == uri)
847 return import;
848 }
849 return nullptr;
850}
851
852bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type,
853 QTypeRevision *version_return, QQmlType *type_return,
854 const QString *base, QList<QQmlError> *errors,
855 QQmlType::RegistrationType registrationType,
856 bool *typeRecursionDetected)
857{
858 QQmlImport::RecursionRestriction recursionRestriction =
859 typeRecursionDetected ? QQmlImport::AllowRecursion : QQmlImport::PreventRecursion;
860
861 bool localTypeRecursionDetected = false;
862 if (!typeRecursionDetected)
863 typeRecursionDetected = &localTypeRecursionDetected;
864
865 // TODO: move the sorting somewhere else and make resolveType() const.
866 if (needsSorting()) {
867 std::stable_partition(first: imports.begin(), last: imports.end(), pred: [](QQmlImportInstance *import) {
868 return import->isInlineComponent;
869 });
870 setNeedsSorting(false);
871 }
872 for (int i=0; i<imports.size(); ++i) {
873 const QQmlImportInstance *import = imports.at(i);
874 if (import->resolveType(typeLoader, type, version_return, type_return, base,
875 typeRecursionDetected, registrationType, recursionRestriction, errors)) {
876 if (qmlCheckTypes()) {
877 // check for type clashes
878 for (int j = i+1; j<imports.size(); ++j) {
879 const QQmlImportInstance *import2 = imports.at(i: j);
880 if (import2->resolveType(typeLoader, type, version_return, type_return: nullptr, base,
881 typeRecursionDetected: nullptr, registrationType)) {
882 if (errors) {
883 QString u1 = import->url;
884 QString u2 = import2->url;
885 if (base) {
886 QStringView b(*base);
887 int dot = b.lastIndexOf(c: Dot);
888 if (dot >= 0) {
889 b = b.left(n: dot+1);
890 QStringView l = b.left(n: dot);
891 if (u1.startsWith(s: b))
892 u1 = u1.mid(position: b.size());
893 else if (u1 == l)
894 u1 = QQmlImportDatabase::tr(sourceText: "local directory");
895 if (u2.startsWith(s: b))
896 u2 = u2.mid(position: b.size());
897 else if (u2 == l)
898 u2 = QQmlImportDatabase::tr(sourceText: "local directory");
899 }
900 }
901
902 QQmlError error;
903 if (u1 != u2) {
904 error.setDescription(
905 QQmlImportDatabase::tr(
906 sourceText: "is ambiguous. Found in %1 and in %2")
907 .arg(args&: u1, args&: u2));
908 } else {
909 error.setDescription(
910 QQmlImportDatabase::tr(
911 sourceText: "is ambiguous. Found in %1 in version "
912 "%2.%3 and %4.%5")
913 .arg(a: u1)
914 .arg(a: import->version.majorVersion())
915 .arg(a: import->version.minorVersion())
916 .arg(a: import2->version.majorVersion())
917 .arg(a: import2->version.minorVersion()));
918 }
919 errors->prepend(t: error);
920 }
921 return false;
922 }
923 }
924 }
925 return true;
926 }
927 }
928 if (errors) {
929 QQmlError error;
930 if (*typeRecursionDetected)
931 error.setDescription(QQmlImportDatabase::tr(sourceText: "is instantiated recursively"));
932 else
933 error.setDescription(QQmlImportDatabase::tr(sourceText: "is not a type"));
934 errors->prepend(t: error);
935 }
936 return false;
937}
938
939QQmlImportNamespace *QQmlImports::findQualifiedNamespace(const QHashedStringRef &prefix) const
940{
941 for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(v: ns)) {
942 if (prefix == ns->prefix)
943 return ns;
944 }
945 return nullptr;
946}
947
948/*
949Import an extension defined by a qmldir file.
950*/
951QTypeRevision QQmlImports::importExtension(
952 const QString &uri, QTypeRevision version, QQmlImportDatabase *database,
953 const QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors)
954{
955 Q_ASSERT(qmldir->hasContent());
956
957 qCDebug(lcQmlImport)
958 << "importExtension:" << qPrintable(m_base) << "loaded" << qmldir->qmldirLocation();
959
960 if (designerSupportRequired && !qmldir->designerSupported()) {
961 if (errors) {
962 QQmlError error;
963 error.setDescription(
964 QQmlImportDatabase::tr(sourceText: "module does not support the designer \"%1\"")
965 .arg(a: qmldir->typeNamespace()));
966 error.setUrl(QUrl::fromLocalFile(localfile: qmldir->qmldirLocation()));
967 errors->prepend(t: error);
968 }
969 return QTypeRevision();
970 }
971
972 if (qmldir->plugins().isEmpty())
973 return validVersion(version);
974
975 QQmlPluginImporter importer(uri, version, database, qmldir, m_typeLoader, errors);
976 return importer.importPlugins();
977}
978
979bool QQmlImports::getQmldirContent(const QString &qmldirIdentifier, const QString &uri,
980 QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors)
981{
982 Q_ASSERT(errors);
983 Q_ASSERT(qmldir);
984
985 *qmldir = m_typeLoader->qmldirContent(filePath: qmldirIdentifier);
986 if ((*qmldir).hasContent()) {
987 // Ensure that parsing was successful
988 if ((*qmldir).hasError()) {
989 QUrl url = QUrl::fromLocalFile(localfile: qmldirIdentifier);
990 const QList<QQmlError> qmldirErrors = (*qmldir).errors(uri);
991 for (int i = 0; i < qmldirErrors.size(); ++i) {
992 QQmlError error = qmldirErrors.at(i);
993 error.setUrl(url);
994 errors->append(t: error);
995 }
996 return false;
997 }
998 }
999
1000 return true;
1001}
1002
1003QString QQmlImports::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database)
1004{
1005 QString dir = dir_arg;
1006 if (dir.endsWith(c: Slash) || dir.endsWith(c: Backslash))
1007 dir.chop(n: 1);
1008
1009 QStringList paths = database->fileImportPath;
1010 if (!paths.isEmpty())
1011 std::sort(first: paths.begin(), last: paths.end(), comp: std::greater<QString>()); // Ensure subdirs preceed their parents.
1012
1013 QString stableRelativePath = dir;
1014 for (const QString &path : std::as_const(t&: paths)) {
1015 if (dir.startsWith(s: path)) {
1016 stableRelativePath = dir.mid(position: path.size()+1);
1017 break;
1018 }
1019 }
1020
1021 stableRelativePath.replace(before: Backslash, after: Slash);
1022
1023 // remove optional versioning in dot notation from uri
1024 int versionDot = stableRelativePath.lastIndexOf(c: Dot);
1025 if (versionDot >= 0) {
1026 int nextSlash = stableRelativePath.indexOf(c: Slash, from: versionDot);
1027 if (nextSlash >= 0)
1028 stableRelativePath.remove(i: versionDot, len: nextSlash - versionDot);
1029 else
1030 stableRelativePath = stableRelativePath.left(n: versionDot);
1031 }
1032
1033 stableRelativePath.replace(before: Slash, after: Dot);
1034
1035 return stableRelativePath;
1036}
1037
1038/*!
1039 \internal
1040
1041 \fn template<typename Callback>
1042 QQmlImportDatabase::LocalQmldirResult QQmlImportDatabase::locateLocalQmldir(
1043 const QString &uri, QTypeRevision version, const Callback &callback)
1044
1045 Locates the qmldir files for \a uri version \a version. For each one, calls
1046 the \a callback. If the \a callback returns \c true, returns QmldirFound.
1047
1048 If at least one callback invocation returned \c false and there are no qmldir
1049 files left to check, returns QmldirRejected.
1050
1051 Otherwise, if interception redirects a previously local qmldir URL to a remote
1052 one, returns QmldirInterceptedToRemote. Otherwise, returns QmldirNotFound.
1053*/
1054
1055QTypeRevision QQmlImports::matchingQmldirVersion(
1056 const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, QTypeRevision version,
1057 QList<QQmlError> *errors)
1058{
1059 int bestMajorVersion = -1;
1060 quint8 lowestMinorVersion = std::numeric_limits<quint8>::max();
1061 quint8 highestMinorVersion = 0;
1062
1063 auto addVersion = [&](QTypeRevision newVersion) {
1064 if (!newVersion.hasMajorVersion())
1065 return;
1066 if (!version.hasMajorVersion() || version.majorVersion() == newVersion.majorVersion()) {
1067 if (newVersion.majorVersion() > bestMajorVersion) {
1068 bestMajorVersion = newVersion.majorVersion();
1069 if (newVersion.hasMinorVersion()) {
1070 lowestMinorVersion = newVersion.minorVersion();
1071 highestMinorVersion = newVersion.minorVersion();
1072 }
1073 } else if (newVersion.majorVersion() == bestMajorVersion
1074 && newVersion.hasMinorVersion()) {
1075 lowestMinorVersion = qMin(a: lowestMinorVersion, b: newVersion.minorVersion());
1076 highestMinorVersion = qMax(a: highestMinorVersion, b: newVersion.minorVersion());
1077 }
1078 }
1079 };
1080
1081 typedef QQmlDirComponents::const_iterator ConstIterator;
1082 const QQmlDirComponents &components = qmldir.components();
1083
1084 ConstIterator cend = components.constEnd();
1085 for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
1086 for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) {
1087 if (cit2->typeName == cit->typeName && cit2->version == cit->version) {
1088 // This entry clashes with a predecessor
1089 QQmlError error;
1090 error.setDescription(
1091 QQmlImportDatabase::tr(
1092 sourceText: "\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1093 .arg(a: cit->typeName).arg(a: cit->version.majorVersion())
1094 .arg(a: cit->version.minorVersion()).arg(a: uri));
1095 errors->prepend(t: error);
1096 return QTypeRevision();
1097 }
1098 }
1099
1100 addVersion(cit->version);
1101 }
1102
1103 typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
1104 const QQmlDirScripts &scripts = qmldir.scripts();
1105
1106 SConstIterator send = scripts.constEnd();
1107 for (SConstIterator sit = scripts.constBegin(); sit != send; ++sit) {
1108 for (SConstIterator sit2 = scripts.constBegin(); sit2 != sit; ++sit2) {
1109 if (sit2->nameSpace == sit->nameSpace && sit2->version == sit->version) {
1110 // This entry clashes with a predecessor
1111 QQmlError error;
1112 error.setDescription(QQmlImportDatabase::tr(sourceText: "\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1113 .arg(a: sit->nameSpace).arg(a: sit->version.majorVersion())
1114 .arg(a: sit->version.minorVersion()).arg(a: uri));
1115 errors->prepend(t: error);
1116 return QTypeRevision();
1117 }
1118 }
1119
1120 addVersion(sit->version);
1121 }
1122
1123 // Failure to find a match is only an error if we were asking for a specific version ...
1124 if (version.hasMajorVersion()
1125 && (bestMajorVersion < 0
1126 || (version.hasMinorVersion()
1127 && (lowestMinorVersion > version.minorVersion()
1128 || highestMinorVersion < version.minorVersion())))) {
1129 errors->prepend(t: moduleNotFoundError(uri, version));
1130 return QTypeRevision();
1131 }
1132
1133 // ... otherwise, anything is valid.
1134 if (bestMajorVersion < 0)
1135 return validVersion();
1136
1137 return QTypeRevision::fromVersion(
1138 majorVersion: bestMajorVersion,
1139 minorVersion: (version.hasMajorVersion() && version.hasMinorVersion())
1140 ? version.minorVersion()
1141 : highestMinorVersion);
1142}
1143
1144QQmlImportNamespace *QQmlImports::importNamespace(const QString &prefix)
1145{
1146 QQmlImportNamespace *nameSpace = nullptr;
1147
1148 if (prefix.isEmpty()) {
1149 nameSpace = &m_unqualifiedset;
1150 } else {
1151 nameSpace = findQualifiedNamespace(prefix);
1152
1153 if (!nameSpace) {
1154 nameSpace = new QQmlImportNamespace;
1155 nameSpace->prefix = prefix;
1156 m_qualifiedSets.append(v: nameSpace);
1157 }
1158 }
1159
1160 return nameSpace;
1161}
1162
1163QQmlImportInstance *QQmlImports::addImportToNamespace(
1164 QQmlImportNamespace *nameSpace, const QString &uri, const QString &url, QTypeRevision version,
1165 QV4::CompiledData::Import::ImportType type, QList<QQmlError> *errors, quint16 precedence)
1166{
1167 Q_ASSERT(nameSpace);
1168 Q_ASSERT(errors);
1169 Q_UNUSED(errors);
1170 Q_ASSERT(url.isEmpty() || url.endsWith(Slash));
1171
1172 QQmlImportInstance *import = new QQmlImportInstance;
1173 import->uri = uri;
1174 import->url = url;
1175 import->version = version;
1176 import->isLibrary = (type == QV4::CompiledData::Import::ImportLibrary);
1177 import->precedence = precedence;
1178 import->implicitlyImported = precedence >= QQmlImportInstance::Implicit;
1179
1180 for (auto it = nameSpace->imports.cbegin(), end = nameSpace->imports.cend();
1181 it != end; ++it) {
1182 if ((*it)->precedence < precedence)
1183 continue;
1184
1185 nameSpace->imports.insert(before: it, t: import);
1186 return import;
1187 }
1188 nameSpace->imports.append(t: import);
1189 return import;
1190}
1191
1192QTypeRevision QQmlImports::addLibraryImport(
1193 QQmlImportDatabase *database, const QString &uri, const QString &prefix,
1194 QTypeRevision version, const QString &qmldirIdentifier, const QString &qmldirUrl,
1195 ImportFlags flags, quint16 precedence, QList<QQmlError> *errors)
1196{
1197 Q_ASSERT(database);
1198 Q_ASSERT(errors);
1199
1200 qCDebug(lcQmlImport)
1201 << "addLibraryImport:" << qPrintable(baseUrl().toString())
1202 << uri << "version '" << version << "'" << "as" << prefix;
1203
1204 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1205 Q_ASSERT(nameSpace);
1206
1207 QQmlImportInstance *inserted = addImportToNamespace(
1208 nameSpace, uri, url: qmldirUrl, version,
1209 type: QV4::CompiledData::Import::ImportLibrary, errors,
1210 precedence);
1211 Q_ASSERT(inserted);
1212
1213 if (!(flags & QQmlImports::ImportIncomplete)) {
1214 QQmlTypeLoaderQmldirContent qmldir;
1215
1216 if (!qmldirIdentifier.isEmpty()) {
1217 if (!getQmldirContent(qmldirIdentifier, uri, qmldir: &qmldir, errors))
1218 return QTypeRevision();
1219
1220 if (qmldir.hasContent()) {
1221 version = importExtension(uri, version, database, qmldir: &qmldir, errors);
1222 if (!version.isValid())
1223 return QTypeRevision();
1224
1225 if (!inserted->setQmldirContent(resolvedUrl: qmldirUrl, qmldir, nameSpace, errors))
1226 return QTypeRevision();
1227 }
1228 }
1229
1230 // Ensure that we are actually providing something
1231 const QTypeRevision matchingVersion = QQmlMetaType::matchingModuleVersion(module: uri, version);
1232 if (matchingVersion.isValid())
1233 return matchingVersion;
1234
1235 if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) {
1236 if (qmldir.plugins().isEmpty()) {
1237 if (!qmldir.imports().isEmpty())
1238 return validVersion(); // This is a pure redirection
1239 if (qmldir.hasTypeInfo())
1240 return validVersion(); // A pure C++ module without plugin
1241 }
1242 errors->prepend(t: moduleNotFoundError(uri, version: relevantVersion(uri, version)));
1243 return QTypeRevision();
1244 } else if (qmldir.hasContent()) {
1245 // Verify that the qmldir content is valid for this version
1246 version = matchingQmldirVersion(qmldir, uri, version, errors);
1247 if (!version.isValid())
1248 return QTypeRevision();
1249 }
1250 }
1251
1252 return validVersion(version);
1253}
1254
1255/*!
1256 \internal
1257
1258 Adds information to \a database such that subsequent calls to resolveType()
1259 will resolve types qualified by \a prefix by considering types found at the given \a uri.
1260
1261 The \a uri is either a directory (if importType is FileImport), or a URI resolved using paths
1262 added via addImportPath() (if importType is LibraryImport).
1263
1264 The \a prefix may be empty, in which case the import location is considered for
1265 unqualified types.
1266
1267 The base URL must already have been set with Import::setBaseUrl().
1268
1269 Optionally, the qmldir the import resolved to can be returned by providing the \a localQmldir
1270 parameter. Not all imports will have a local qmldir. If there is none, the \a localQmldir
1271 parameter won't be set.
1272
1273 Returns a valid QTypeRevision on success, and an invalid one on failure.
1274 In case of failure, the \a errors array will filled appropriately.
1275*/
1276QTypeRevision QQmlImports::addFileImport(
1277 QQmlImportDatabase *database, const QString &uri, const QString &prefix,
1278 QTypeRevision version, ImportFlags flags, quint16 precedence,
1279 QString *localQmldir, QList<QQmlError> *errors)
1280{
1281 Q_ASSERT(database);
1282 Q_ASSERT(errors);
1283
1284 qCDebug(lcQmlImport)
1285 << "addFileImport:" << qPrintable(baseUrl().toString())
1286 << uri << version << "as" << prefix;
1287
1288 if (uri.startsWith(c: Slash) || uri.startsWith(c: Colon)) {
1289 QQmlError error;
1290 const QString fix = uri.startsWith(c: Slash) ? QLatin1String("file:") + uri
1291 : QLatin1String("qrc") + uri;
1292 error.setDescription(QQmlImportDatabase::tr(
1293 sourceText: "\"%1\" is not a valid import URL. "
1294 "You can pass relative paths or URLs with schema, but not "
1295 "absolute paths or resource paths. Try \"%2\".").arg(args: uri, args: fix));
1296 errors->prepend(t: error);
1297 return QTypeRevision();
1298 }
1299
1300 Q_ASSERT(errors);
1301
1302 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1303 Q_ASSERT(nameSpace);
1304
1305 // The uri for this import. For library imports this is the same as uri
1306 // specified by the user, but it may be different in the case of file imports.
1307 QString importUri = uri;
1308 QString qmldirUrl = resolveLocalUrl(url: m_base, relative: importUri + (importUri.endsWith(c: Slash)
1309 ? String_qmldir
1310 : Slash_qmldir));
1311 qmldirUrl = m_typeLoader->engine()->interceptUrl(
1312 url: QUrl(qmldirUrl), type: QQmlAbstractUrlInterceptor::QmldirFile).toString();
1313 QString qmldirIdentifier;
1314
1315 if (QQmlFile::isLocalFile(url: qmldirUrl)) {
1316
1317 QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
1318 Q_ASSERT(!localFileOrQrc.isEmpty());
1319
1320 const QString dir = localFileOrQrc.left(n: localFileOrQrc.lastIndexOf(c: Slash) + 1);
1321 if (!m_typeLoader->directoryExists(path: dir)) {
1322 if (precedence < QQmlImportInstance::Implicit) {
1323 QQmlError error;
1324 error.setDescription(QQmlImportDatabase::tr(sourceText: "\"%1\": no such directory").arg(a: uri));
1325 error.setUrl(QUrl(qmldirUrl));
1326 errors->prepend(t: error);
1327 }
1328 return QTypeRevision();
1329 }
1330
1331 // Transforms the (possible relative) uri into our best guess relative to the
1332 // import paths.
1333 importUri = resolvedUri(dir_arg: dir, database);
1334 if (importUri.endsWith(c: Slash))
1335 importUri.chop(n: 1);
1336
1337 if (!m_typeLoader->absoluteFilePath(path: localFileOrQrc).isEmpty()) {
1338 qmldirIdentifier = localFileOrQrc;
1339 if (localQmldir)
1340 *localQmldir = qmldirIdentifier;
1341 }
1342
1343 } else if (nameSpace->prefix.isEmpty() && !(flags & QQmlImports::ImportIncomplete)) {
1344
1345 if (precedence < QQmlImportInstance::Implicit) {
1346 QQmlError error;
1347 error.setDescription(QQmlImportDatabase::tr(sourceText: "import \"%1\" has no qmldir and no namespace").arg(a: importUri));
1348 error.setUrl(QUrl(qmldirUrl));
1349 errors->prepend(t: error);
1350 }
1351
1352 return QTypeRevision();
1353
1354 }
1355
1356 // The url for the path containing files for this import
1357 QString url = resolveLocalUrl(url: m_base, relative: uri);
1358 if (url.isEmpty()) {
1359 QQmlError error;
1360 error.setDescription(
1361 QQmlImportDatabase::tr(sourceText: "Cannot resolve URL for import \"%1\"").arg(a: uri));
1362 error.setUrl(m_baseUrl);
1363 errors->prepend(t: error);
1364 return QTypeRevision();
1365 }
1366
1367 if (!url.endsWith(c: Slash) && !url.endsWith(c: Backslash))
1368 url += Slash;
1369
1370 // ### For enum support, we are now adding the implicit import always (and earlier). Bail early
1371 // if the implicit import has already been explicitly added, otherwise we can run into issues
1372 // with duplicate imports. However remember that we attempted to add this as implicit import, to
1373 // allow for the loading of internal types.
1374 if (precedence >= QQmlImportInstance::Implicit) {
1375 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
1376 it != nameSpace->imports.constEnd(); ++it) {
1377 if ((*it)->url == url) {
1378 (*it)->implicitlyImported = true;
1379 return validVersion(version);
1380 }
1381 }
1382 }
1383
1384 if (!(flags & QQmlImports::ImportIncomplete) && !qmldirIdentifier.isEmpty()) {
1385 QQmlTypeLoaderQmldirContent qmldir;
1386 if (!getQmldirContent(qmldirIdentifier, uri: importUri, qmldir: &qmldir, errors))
1387 return QTypeRevision();
1388
1389 if (qmldir.hasContent()) {
1390 // Prefer the qmldir URI. Unless it doesn't exist.
1391 const QString qmldirUri = qmldir.typeNamespace();
1392 if (!qmldirUri.isEmpty())
1393 importUri = qmldirUri;
1394
1395 QQmlImportInstance *inserted = addImportToNamespace(
1396 nameSpace, uri: importUri, url, version, type: QV4::CompiledData::Import::ImportFile,
1397 errors, precedence);
1398 Q_ASSERT(inserted);
1399
1400 version = importExtension(uri: importUri, version, database, qmldir: &qmldir, errors);
1401 if (!version.isValid())
1402 return QTypeRevision();
1403
1404 if (!inserted->setQmldirContent(resolvedUrl: url, qmldir, nameSpace, errors))
1405 return QTypeRevision();
1406
1407 return validVersion(version);
1408 }
1409 }
1410
1411 QQmlImportInstance *inserted = addImportToNamespace(
1412 nameSpace, uri: importUri, url, version, type: QV4::CompiledData::Import::ImportFile,
1413 errors, precedence);
1414 Q_ASSERT(inserted);
1415 return validVersion(version);
1416}
1417
1418QTypeRevision QQmlImports::updateQmldirContent(
1419 QQmlImportDatabase *database, const QString &uri, const QString &prefix,
1420 const QString &qmldirIdentifier, const QString &qmldirUrl, QList<QQmlError> *errors)
1421{
1422 Q_ASSERT(database);
1423 Q_ASSERT(errors);
1424
1425 qDebug(catFunc: lcQmlImport)
1426 << "updateQmldirContent:" << qPrintable(baseUrl().toString())
1427 << uri << "to" << qmldirUrl << "as" << prefix;
1428
1429 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1430 Q_ASSERT(nameSpace);
1431
1432 if (QQmlImportInstance *import = nameSpace->findImport(uri)) {
1433 QQmlTypeLoaderQmldirContent qmldir;
1434 if (!getQmldirContent(qmldirIdentifier, uri, qmldir: &qmldir, errors))
1435 return QTypeRevision();
1436
1437 if (qmldir.hasContent()) {
1438 QTypeRevision version = importExtension(
1439 uri, version: import->version, database, qmldir: &qmldir, errors);
1440 if (!version.isValid())
1441 return QTypeRevision();
1442
1443 if (import->setQmldirContent(resolvedUrl: qmldirUrl, qmldir, nameSpace, errors)) {
1444 if (import->qmlDirComponents.isEmpty() && import->qmlDirScripts.isEmpty()) {
1445 // The implicit import qmldir can be empty, and plugins have no extra versions
1446 if (uri != QLatin1String(".") && !QQmlMetaType::matchingModuleVersion(module: uri, version).isValid()) {
1447 errors->prepend(t: moduleNotFoundError(uri, version: relevantVersion(uri, version)));
1448 return QTypeRevision();
1449 }
1450 } else {
1451 // Verify that the qmldir content is valid for this version
1452 version = matchingQmldirVersion(qmldir, uri, version, errors);
1453 if (!version.isValid())
1454 return QTypeRevision();
1455 }
1456 return validVersion(version);
1457 }
1458 }
1459 }
1460
1461 if (errors->isEmpty()) {
1462 QQmlError error;
1463 error.setDescription(QQmlTypeLoader::tr(sourceText: "Cannot update qmldir content for '%1'").arg(a: uri));
1464 errors->prepend(t: error);
1465 }
1466
1467 return QTypeRevision();
1468}
1469
1470/*!
1471 \fn QQmlImports::addImplicitImport(QQmlImportDatabase *importDb, QString *localQmldir, QList<QQmlError> *errors)
1472 \internal
1473
1474 Adds an implicit "." file import. This is equivalent to calling addFileImport(), but error
1475 messages related to the path or qmldir file not existing are suppressed.
1476
1477 Additionally, this will add the import with lowest instead of highest precedence.
1478*/
1479
1480
1481/*!
1482 \internal
1483 */
1484bool QQmlImports::addInlineComponentImport(QQmlImportInstance *const importInstance, const QString &name, const QUrl importUrl, QQmlType containingType)
1485{
1486 importInstance->url = importUrl.toString();
1487 importInstance->uri = name;
1488 importInstance->isInlineComponent = true;
1489 importInstance->version = QTypeRevision::zero();
1490 importInstance->containingType = containingType;
1491 m_unqualifiedset.imports.push_back(t: importInstance);
1492 m_unqualifiedset.setNeedsSorting(true);
1493 return true;
1494}
1495
1496QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file)
1497{
1498 QUrl url(QLatin1String(file.at(i: 0) == Colon ? "qrc" : "") + file);
1499
1500 // We don't support single character schemes as those conflict with windows drive letters.
1501 if (url.scheme().size() < 2)
1502 return QUrl::fromLocalFile(localfile: file);
1503 return url;
1504}
1505
1506void QQmlImports::setDesignerSupportRequired(bool b)
1507{
1508 designerSupportRequired = b;
1509}
1510
1511static QStringList parseEnvPath(const QString &envImportPath)
1512{
1513 if (QDir::listSeparator() == u':') {
1514 // Double colons are interpreted as separator + resource path.
1515 QStringList paths = envImportPath.split(sep: u':');
1516 bool wasEmpty = false;
1517 for (auto it = paths.begin(); it != paths.end();) {
1518 if (it->isEmpty()) {
1519 wasEmpty = true;
1520 it = paths.erase(pos: it);
1521 } else {
1522 if (wasEmpty) {
1523 it->prepend(c: u':');
1524 wasEmpty = false;
1525 }
1526 ++it;
1527 }
1528 }
1529 return paths;
1530 } else {
1531 return envImportPath.split(sep: QDir::listSeparator(), behavior: Qt::SkipEmptyParts);
1532 }
1533}
1534
1535/*!
1536\class QQmlImportDatabase
1537\brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine.
1538\internal
1539*/
1540QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
1541: engine(e)
1542{
1543 filePluginPath << QLatin1String(".");
1544 // Search order is:
1545 // 1. android or macos specific bundle paths.
1546 // 2. applicationDirPath()
1547 // 3. qrc:/qt-project.org/imports
1548 // 4. qrc:/qt/qml
1549 // 5. $QML2_IMPORT_PATH
1550 // 6. $QML_IMPORT_PATH
1551 // 7. QLibraryInfo::QmlImportsPath
1552
1553 QString installImportsPath = QLibraryInfo::path(p: QLibraryInfo::QmlImportsPath);
1554 addImportPath(dir: installImportsPath);
1555
1556 auto addEnvImportPath = [this](const char *var) {
1557 if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty(var))) {
1558 const QStringList paths = parseEnvPath(envImportPath: qEnvironmentVariable(varName: var));
1559 for (int ii = paths.size() - 1; ii >= 0; --ii)
1560 addImportPath(dir: paths.at(i: ii));
1561 }
1562 };
1563
1564 // env import paths
1565 addEnvImportPath("QML_IMPORT_PATH");
1566 addEnvImportPath("QML2_IMPORT_PATH");
1567
1568 addImportPath(QStringLiteral("qrc:/qt/qml"));
1569 addImportPath(QStringLiteral("qrc:/qt-project.org/imports"));
1570 addImportPath(dir: QCoreApplication::applicationDirPath());
1571
1572 auto addEnvPluginPath = [this](const char *var) {
1573 if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty(var))) {
1574 const QStringList paths = parseEnvPath(envImportPath: qEnvironmentVariable(varName: var));
1575 for (int ii = paths.size() - 1; ii >= 0; --ii)
1576 addPluginPath(path: paths.at(i: ii));
1577 }
1578 };
1579
1580 addEnvPluginPath("QML_PLUGIN_PATH");
1581#if defined(Q_OS_ANDROID)
1582 addImportPath(QStringLiteral("qrc:/android_rcc_bundle/qml"));
1583 addEnvPluginPath("QT_BUNDLED_LIBS_PATH");
1584#elif defined(Q_OS_MACOS)
1585 // Add the main bundle's Resources/qml directory as an import path, so that QML modules are
1586 // found successfully when running the app from its build dir.
1587 // This is where macdeployqt and our CMake deployment logic puts Qt and user qmldir files.
1588 if (CFBundleRef bundleRef = CFBundleGetMainBundle()) {
1589 if (QCFType<CFURLRef> urlRef = CFBundleCopyResourceURL(
1590 bundleRef,
1591 QCFString(QLatin1String("qml")), 0, 0)) {
1592 if (QCFType<CFURLRef> absoluteUrlRef = CFURLCopyAbsoluteURL(urlRef)) {
1593 if (QCFString path = CFURLCopyFileSystemPath(absoluteUrlRef, kCFURLPOSIXPathStyle)) {
1594 if (QFile::exists(path)) {
1595 addImportPath(QDir(path).canonicalPath());
1596 }
1597 }
1598 }
1599 }
1600 }
1601#endif // Q_OS_DARWIN
1602}
1603
1604/*!
1605 \internal
1606*/
1607void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
1608{
1609 qCDebug(lcQmlImport) << "setPluginPathList:" << paths;
1610 filePluginPath = paths;
1611}
1612
1613/*!
1614 \internal
1615*/
1616void QQmlImportDatabase::addPluginPath(const QString& path)
1617{
1618 qCDebug(lcQmlImport) << "addPluginPath:" << path;
1619
1620 QUrl url = QUrl(path);
1621 if (url.isRelative() || url.scheme() == QLatin1String("file")
1622 || (url.scheme().size() == 1 && QFile::exists(fileName: path)) ) { // windows path
1623 QDir dir = QDir(path);
1624 filePluginPath.prepend(t: dir.canonicalPath());
1625 } else {
1626 filePluginPath.prepend(t: path);
1627 }
1628}
1629
1630QString QQmlImportDatabase::absoluteFilePath(const QString &path) const
1631{
1632 return QQmlEnginePrivate::get(e: engine)->typeLoader.absoluteFilePath(path);
1633}
1634
1635/*!
1636 \internal
1637*/
1638void QQmlImportDatabase::addImportPath(const QString& path)
1639{
1640 qCDebug(lcQmlImport) << "addImportPath:" << path;
1641
1642 if (path.isEmpty())
1643 return;
1644
1645 QUrl url = QUrl(path);
1646 QString cPath;
1647
1648 if (url.scheme() == QLatin1String("file")) {
1649 cPath = QQmlFile::urlToLocalFileOrQrc(url);
1650 } else if (path.startsWith(c: QLatin1Char(':'))) {
1651 // qrc directory, e.g. :/foo
1652 // need to convert to a qrc url, e.g. qrc:/foo
1653 cPath = QLatin1String("qrc") + path;
1654 cPath.replace(before: Backslash, after: Slash);
1655 } else if (url.isRelative() ||
1656 (url.scheme().size() == 1 && QFile::exists(fileName: path)) ) { // windows path
1657 QDir dir = QDir(path);
1658 cPath = dir.canonicalPath();
1659 } else {
1660 cPath = path;
1661 cPath.replace(before: Backslash, after: Slash);
1662 }
1663
1664 if (!cPath.isEmpty()) {
1665 if (fileImportPath.contains(str: cPath))
1666 fileImportPath.move(from: fileImportPath.indexOf(str: cPath), to: 0);
1667 else
1668 fileImportPath.prepend(t: cPath);
1669 }
1670}
1671
1672/*!
1673 \internal
1674*/
1675QStringList QQmlImportDatabase::importPathList(PathType type) const
1676{
1677 if (type == LocalOrRemote)
1678 return fileImportPath;
1679
1680 QStringList list;
1681 for (const QString &path : fileImportPath) {
1682 bool localPath = isPathAbsolute(path) || QQmlFile::isLocalFile(url: path);
1683 if (localPath == (type == Local))
1684 list.append(t: path);
1685 }
1686
1687 return list;
1688}
1689
1690/*!
1691 \internal
1692*/
1693void QQmlImportDatabase::setImportPathList(const QStringList &paths)
1694{
1695 qCDebug(lcQmlImport) << "setImportPathList:" << paths;
1696
1697 fileImportPath.clear();
1698 for (auto it = paths.crbegin(); it != paths.crend(); ++it)
1699 addImportPath(path: *it);
1700
1701 // Our existing cached paths may have been invalidated
1702 clearDirCache();
1703}
1704
1705/*!
1706 \internal
1707*/
1708QTypeRevision QQmlImportDatabase::lockModule(const QString &uri, const QString &typeNamespace,
1709 QTypeRevision version, QList<QQmlError> *errors)
1710{
1711 if (!version.hasMajorVersion()) {
1712 version = QQmlMetaType::latestModuleVersion(uri);
1713 if (!version.isValid())
1714 errors->prepend(t: moduleNotFoundError(uri, version));
1715 }
1716 if (version.hasMajorVersion() && !typeNamespace.isEmpty()
1717 && !QQmlMetaType::protectModule(uri, version, weakProtectAllVersions: true)) {
1718 // Not being able to protect the module means there are not types registered for it,
1719 // means the plugin we loaded didn't provide any, means we didn't find the module.
1720 // We output the generic error message as depending on the load order of imports we may
1721 // hit this path or another one that only figures "plugin is already loaded but module
1722 // unavailable" and doesn't try to protect it anymore.
1723 errors->prepend(t: moduleNotFoundError(uri, version));
1724 return QTypeRevision();
1725 }
1726
1727 return version;
1728}
1729
1730bool QQmlImportDatabase::removeDynamicPlugin(const QString &pluginId)
1731{
1732 return QQmlPluginImporter::removePlugin(pluginId);
1733}
1734
1735QStringList QQmlImportDatabase::dynamicPlugins() const
1736{
1737 return QQmlPluginImporter::plugins();
1738}
1739
1740void QQmlImportDatabase::clearDirCache()
1741{
1742 QStringHash<QmldirCache *>::ConstIterator itr = qmldirCache.constBegin();
1743 while (itr != qmldirCache.constEnd()) {
1744 QmldirCache *cache = *itr;
1745 do {
1746 QmldirCache *nextCache = cache->next;
1747 delete cache;
1748 cache = nextCache;
1749 } while (cache);
1750
1751 ++itr;
1752 }
1753 qmldirCache.clear();
1754}
1755
1756QT_END_NAMESPACE
1757

source code of qtdeclarative/src/qml/qml/qqmlimport.cpp