1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 Crimson AS <info@crimson.no>
4 ** Copyright (C) 2016 The Qt Company Ltd.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the QtQml module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 #include "qqmlimport_p.h"
42
43 #include <QtCore/qdebug.h>
44 #include <QtCore/qdir.h>
45 #include <QtQml/qqmlfile.h>
46 #include <QtCore/qfileinfo.h>
47 #include <QtCore/qpluginloader.h>
48 #include <QtCore/qlibraryinfo.h>
49 #include <QtCore/qreadwritelock.h>
50 #include <QtQml/qqmlextensioninterface.h>
51 #include <QtQml/qqmlextensionplugin.h>
52 #include <private/qqmlextensionplugin_p.h>
53 #include <private/qqmlglobal_p.h>
54 #include <private/qqmltypenamecache_p.h>
55 #include <private/qqmlengine_p.h>
56 #include <private/qfieldlist_p.h>
57 #include <private/qqmltypemodule_p.h>
58 #include <private/qqmltypeloaderqmldircontent_p.h>
59 #include <QtCore/qjsonobject.h>
60 #include <QtCore/qjsonarray.h>
61 #include <QtQml/private/qqmltype_p_p.h>
62
63 #include <algorithm>
64 #include <functional>
65
66 QT_BEGIN_NAMESPACE
67
68 DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
69 DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
70
71 static const QLatin1Char Dot('.');
72 static const QLatin1Char Slash('/');
73 static const QLatin1Char Backslash('\\');
74 static const QLatin1Char Colon(':');
75 static const QLatin1String Slash_qmldir("/qmldir");
76 static const QLatin1String String_qmldir("qmldir");
77 static const QString dotqml_string(QStringLiteral(".qml"));
78 static const QString dotuidotqml_string(QStringLiteral(".ui.qml"));
79 static bool designerSupportRequired = false;
80
81 namespace {
82
resolveLocalUrl(const QString & url,const QString & relative)83 QString resolveLocalUrl(const QString &url, const QString &relative)
84 {
85 if (relative.contains(Colon)) {
86 // contains a host name
87 return QUrl(url).resolved(QUrl(relative)).toString();
88 } else if (relative.isEmpty()) {
89 return url;
90 } else if (relative.at(0) == Slash || !url.contains(Slash)) {
91 return relative;
92 } else {
93 const QStringRef baseRef = url.leftRef(url.lastIndexOf(Slash) + 1);
94 if (relative == QLatin1String("."))
95 return baseRef.toString();
96
97 QString base = baseRef + relative;
98
99 // Remove any relative directory elements in the path
100 int length = base.length();
101 int index = 0;
102 while ((index = base.indexOf(QLatin1String("/."), index)) != -1) {
103 if ((length > (index + 2)) && (base.at(index + 2) == Dot) &&
104 (length == (index + 3) || (base.at(index + 3) == Slash))) {
105 // Either "/../" or "/..<END>"
106 int previous = base.lastIndexOf(Slash, index - 1);
107 if (previous == -1)
108 break;
109
110 int removeLength = (index - previous) + 3;
111 base.remove(previous + 1, removeLength);
112 length -= removeLength;
113 index = previous;
114 } else if ((length == (index + 2)) || (base.at(index + 2) == Slash)) {
115 // Either "/./" or "/.<END>"
116 base.remove(index, 2);
117 length -= 2;
118 } else {
119 ++index;
120 }
121 }
122
123 return base;
124 }
125 }
126
isPathAbsolute(const QString & path)127 bool isPathAbsolute(const QString &path)
128 {
129 #if defined(Q_OS_UNIX)
130 return (path.at(0) == Slash);
131 #else
132 QFileInfo fi(path);
133 return fi.isAbsolute();
134 #endif
135 }
136
137 } // namespace
138
139 struct RegisteredPlugin {
140 QString uri;
141 QPluginLoader* loader;
142 };
143
144 struct StringRegisteredPluginMap : public QMap<QString, RegisteredPlugin> {
145 QMutex mutex;
146
~StringRegisteredPluginMapStringRegisteredPluginMap147 ~StringRegisteredPluginMap()
148 {
149 QMutexLocker lock(&mutex);
150 for (const RegisteredPlugin &plugin : qAsConst(*this))
151 delete plugin.loader;
152 }
153 };
154
155 Q_GLOBAL_STATIC(StringRegisteredPluginMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri and the PluginLoaders
156
qmlClearEnginePlugins()157 void qmlClearEnginePlugins()
158 {
159 StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
160 QMutexLocker lock(&plugins->mutex);
161 #if QT_CONFIG(library)
162 for (auto &plugin : qAsConst(*plugins)) {
163 QPluginLoader* loader = plugin.loader;
164 if (loader && !loader->unload())
165 qWarning("Unloading %s failed: %s", qPrintable(plugin.uri), qPrintable(loader->errorString()));
166 delete loader;
167 }
168 #endif
169 plugins->clear();
170 }
171
172 typedef QPair<QStaticPlugin, QJsonArray> StaticPluginPair;
173
174 /*!
175 \internal
176 \class QQmlImportInstance
177
178 A QQmlImportType represents a single import of a document, held within a
179 namespace.
180
181 \note The uri here may not necessarily be unique (e.g. for file imports).
182
183 \note Version numbers may be -1 for file imports: this means that no
184 version was specified as part of the import. Type resolution will be
185 responsible for attempting to find the "best" possible version.
186 */
187
188 /*!
189 \internal
190 \class QQmlImportNamespace
191
192 A QQmlImportNamespace is a way of seperating imports into a local namespace.
193
194 Within a QML document, there is at least one namespace (the
195 "unqualified set") where imports without a qualifier are placed, i.e:
196
197 import QtQuick 2.6
198
199 will have a single namespace (the unqualified set) containing a single import
200 for QtQuick 2.6. However, there may be others if an import statement gives
201 a qualifier, i.e the following will result in an additional new
202 QQmlImportNamespace in the qualified set:
203
204 import MyFoo 1.0 as Foo
205 */
206
207 class QQmlImportsPrivate
208 {
209 public:
210 QQmlImportsPrivate(QQmlTypeLoader *loader);
211 ~QQmlImportsPrivate();
212
213 QQmlImportNamespace *importNamespace(const QString &prefix) const;
214
215 bool addLibraryImport(const QString& uri, const QString &prefix,
216 int vmaj, int vmin, const QString &qmldirIdentifier, const QString &qmldirUrl, bool incomplete,
217 QQmlImportDatabase *database,
218 QList<QQmlError> *errors);
219
220 bool addFileImport(const QString &uri, const QString &prefix,
221 int vmaj, int vmin,
222 bool isImplicitImport, bool incomplete, QQmlImportDatabase *database,
223 QList<QQmlError> *errors);
224
225 bool updateQmldirContent(const QString &uri, const QString &prefix,
226 const QString &qmldirIdentifier, const QString& qmldirUrl,
227 QQmlImportDatabase *database,
228 QList<QQmlError> *errors);
229
230 bool resolveType(const QHashedStringRef &type, int *vmajor, int *vminor,
231 QQmlType *type_return, QList<QQmlError> *errors,
232 QQmlType::RegistrationType registrationType,
233 bool *typeRecursionDetected = nullptr);
234
235 QUrl baseUrl;
236 QString base;
237 int ref;
238
239 // storage of data related to imports without a namespace
240 mutable QQmlImportNamespace unqualifiedset;
241
242 QQmlImportNamespace *findQualifiedNamespace(const QHashedStringRef &) const;
243
244 // storage of data related to imports with a namespace
245 mutable QFieldList<QQmlImportNamespace, &QQmlImportNamespace::nextNamespace> qualifiedSets;
246
247 QQmlTypeLoader *typeLoader;
248
249 static QQmlImports::LocalQmldirResult locateLocalQmldir(
250 const QString &uri, int vmaj, int vmin, QQmlImportDatabase *database,
251 QString *outQmldirFilePath, QString *outUrl);
252
253 static bool validateQmldirVersion(const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, int vmaj, int vmin,
254 QList<QQmlError> *errors);
255
256 bool importExtension(const QString &absoluteFilePath, const QString &uri,
257 int vmaj, int vmin,
258 QQmlImportDatabase *database,
259 const QQmlTypeLoaderQmldirContent &qmldir,
260 QList<QQmlError> *errors);
261
262 bool getQmldirContent(const QString &qmldirIdentifier, const QString &uri,
263 QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors);
264
265 QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database);
266
267 QQmlImportInstance *addImportToNamespace(QQmlImportNamespace *nameSpace,
268 const QString &uri, const QString &url,
269 int vmaj, int vmin, QV4::CompiledData::Import::ImportType type,
270 QList<QQmlError> *errors, bool lowPrecedence = false);
271
272 bool populatePluginPairVector(QVector<StaticPluginPair> &result, const QString &uri, const QStringList &versionUris,
273 const QString &qmldirPath, QList<QQmlError> *errors);
274 };
275
276 /*!
277 \class QQmlImports
278 \brief The QQmlImports class encapsulates one QML document's import statements.
279 \internal
280 */
QQmlImports(const QQmlImports & copy)281 QQmlImports::QQmlImports(const QQmlImports ©)
282 : d(copy.d)
283 {
284 ++d->ref;
285 }
286
287 QQmlImports &
operator =(const QQmlImports & copy)288 QQmlImports::operator =(const QQmlImports ©)
289 {
290 ++copy.d->ref;
291 if (--d->ref == 0)
292 delete d;
293 d = copy.d;
294 return *this;
295 }
296
QQmlImports(QQmlTypeLoader * typeLoader)297 QQmlImports::QQmlImports(QQmlTypeLoader *typeLoader)
298 : d(new QQmlImportsPrivate(typeLoader))
299 {
300 }
301
~QQmlImports()302 QQmlImports::~QQmlImports()
303 {
304 if (--d->ref == 0)
305 delete d;
306 }
307
308 /*!
309 Sets the base URL to be used for all relative file imports added.
310 */
setBaseUrl(const QUrl & url,const QString & urlString)311 void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString)
312 {
313 d->baseUrl = url;
314
315 if (urlString.isEmpty()) {
316 d->base = url.toString();
317 } else {
318 //Q_ASSERT(url.toString() == urlString);
319 d->base = urlString;
320 }
321 }
322
323 /*!
324 Returns the base URL to be used for all relative file imports added.
325 */
baseUrl() const326 QUrl QQmlImports::baseUrl() const
327 {
328 return d->baseUrl;
329 }
330
331 /*
332 \internal
333
334 This method is responsible for populating data of all types visible in this
335 document's imports into the \a cache for resolution elsewhere (e.g. in JS,
336 or when loading additional types).
337
338 \note This is for C++ types only. Composite types are handled separately,
339 as they do not have a QQmlTypeModule.
340 */
populateCache(QQmlTypeNameCache * cache) const341 void QQmlImports::populateCache(QQmlTypeNameCache *cache) const
342 {
343 const QQmlImportNamespace &set = d->unqualifiedset;
344
345 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
346 const QQmlImportInstance *import = set.imports.at(ii);
347 QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion);
348 if (module) {
349 cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, import->minversion));
350 }
351 }
352
353 for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) {
354
355 const QQmlImportNamespace &set = *ns;
356
357 // positioning is important; we must create the namespace even if there is no module.
358 QQmlImportRef &typeimport = cache->m_namedImports[set.prefix];
359 typeimport.m_qualifier = set.prefix;
360
361 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
362 const QQmlImportInstance *import = set.imports.at(ii);
363 QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion);
364 if (module) {
365 QQmlImportRef &typeimport = cache->m_namedImports[set.prefix];
366 typeimport.modules.append(QQmlTypeModuleVersion(module, import->minversion));
367 }
368 }
369 }
370 }
371
372 // We need to exclude the entry for the current baseUrl. This can happen for example
373 // when handling qmldir files on the remote dir case and the current type is marked as
374 // singleton.
excludeBaseUrl(const QString & importUrl,const QString & fileName,const QString & baseUrl)375 bool excludeBaseUrl(const QString &importUrl, const QString &fileName, const QString &baseUrl)
376 {
377 if (importUrl.isEmpty())
378 return false;
379
380 if (baseUrl.startsWith(importUrl))
381 {
382 if (fileName == baseUrl.midRef(importUrl.size()))
383 return false;
384 }
385
386 return true;
387 }
388
findCompositeSingletons(const QQmlImportNamespace & set,QList<QQmlImports::CompositeSingletonReference> & resultList,const QUrl & baseUrl)389 void findCompositeSingletons(const QQmlImportNamespace &set, QList<QQmlImports::CompositeSingletonReference> &resultList, const QUrl &baseUrl)
390 {
391 typedef QQmlDirComponents::const_iterator ConstIterator;
392
393 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
394 const QQmlImportInstance *import = set.imports.at(ii);
395
396 const QQmlDirComponents &components = import->qmlDirComponents;
397
398 const int importMajorVersion = import->majversion;
399 const int importMinorVersion = import->minversion;
400 auto shouldSkipSingleton = [importMajorVersion, importMinorVersion](int singletonMajorVersion, int singletonMinorVersion) -> bool {
401 return importMajorVersion != -1 &&
402 (singletonMajorVersion > importMajorVersion || (singletonMajorVersion == importMajorVersion && singletonMinorVersion > importMinorVersion));
403 };
404
405 ConstIterator cend = components.constEnd();
406 for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
407 if (cit->singleton && excludeBaseUrl(import->url, cit->fileName, baseUrl.toString())) {
408 if (shouldSkipSingleton(cit->majorVersion, cit->minorVersion))
409 continue;
410 QQmlImports::CompositeSingletonReference ref;
411 ref.typeName = cit->typeName;
412 ref.prefix = set.prefix;
413 ref.majorVersion = cit->majorVersion;
414 ref.minorVersion = cit->minorVersion;
415 resultList.append(ref);
416 }
417 }
418
419 if (QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion)) {
420 module->walkCompositeSingletons([&resultList, &set, &shouldSkipSingleton](const QQmlType &singleton) {
421 if (shouldSkipSingleton(singleton.majorVersion(), singleton.minorVersion()))
422 return;
423 QQmlImports::CompositeSingletonReference ref;
424 ref.typeName = singleton.elementName();
425 ref.prefix = set.prefix;
426 ref.majorVersion = singleton.majorVersion();
427 ref.minorVersion = singleton.minorVersion();
428 resultList.append(ref);
429 });
430 }
431 }
432 }
433
434 /*
435 \internal
436
437 Returns a list of all composite singletons present in this document's
438 imports.
439
440 This information is used by QQmlTypeLoader to ensure that composite singletons
441 are marked as dependencies during type loading.
442 */
resolvedCompositeSingletons() const443 QList<QQmlImports::CompositeSingletonReference> QQmlImports::resolvedCompositeSingletons() const
444 {
445 QList<QQmlImports::CompositeSingletonReference> compositeSingletons;
446
447 const QQmlImportNamespace &set = d->unqualifiedset;
448 findCompositeSingletons(set, compositeSingletons, baseUrl());
449
450 for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) {
451 const QQmlImportNamespace &set = *ns;
452 findCompositeSingletons(set, compositeSingletons, baseUrl());
453 }
454
455 std::stable_sort(compositeSingletons.begin(), compositeSingletons.end(),
456 [](const QQmlImports::CompositeSingletonReference &lhs,
457 const QQmlImports::CompositeSingletonReference &rhs) {
458 if (lhs.prefix != rhs.prefix)
459 return lhs.prefix < rhs.prefix;
460
461 if (lhs.typeName != rhs.typeName)
462 return lhs.typeName < rhs.typeName;
463
464 return lhs.majorVersion != rhs.majorVersion
465 ? lhs.majorVersion < rhs.majorVersion
466 : lhs.minorVersion < rhs.minorVersion;
467 });
468
469 return compositeSingletons;
470 }
471
472 /*
473 \internal
474
475 Returns a list of scripts imported by this document. This is used by
476 QQmlTypeLoader to properly handle dependencies on imported scripts.
477 */
resolvedScripts() const478 QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
479 {
480 QList<QQmlImports::ScriptReference> scripts;
481
482 const QQmlImportNamespace &set = d->unqualifiedset;
483
484 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
485 const QQmlImportInstance *import = set.imports.at(ii);
486
487 for (const QQmlDirParser::Script &script : import->qmlDirScripts) {
488 ScriptReference ref;
489 ref.nameSpace = script.nameSpace;
490 ref.location = QUrl(import->url).resolved(QUrl(script.fileName));
491 scripts.append(ref);
492 }
493 }
494
495 for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) {
496 const QQmlImportNamespace &set = *ns;
497
498 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
499 const QQmlImportInstance *import = set.imports.at(ii);
500
501 for (const QQmlDirParser::Script &script : import->qmlDirScripts) {
502 ScriptReference ref;
503 ref.nameSpace = script.nameSpace;
504 ref.qualifier = set.prefix;
505 ref.location = QUrl(import->url).resolved(QUrl(script.fileName));
506 scripts.append(ref);
507 }
508 }
509 }
510
511 return scripts;
512 }
513
joinStringRefs(const QVector<QStringRef> & refs,const QChar & sep)514 static QString joinStringRefs(const QVector<QStringRef> &refs, const QChar &sep)
515 {
516 QString str;
517 for (auto it = refs.cbegin(); it != refs.cend(); ++it) {
518 if (it != refs.cbegin())
519 str += sep;
520 str += *it;
521 }
522 return str;
523 }
524
525 /*!
526 Forms complete paths to a qmldir file, from a base URL, a module URI and version specification.
527
528 For example, QtQml.Models 2.0:
529 - base/QtQml/Models.2.0/qmldir
530 - base/QtQml.2.0/Models/qmldir
531 - base/QtQml/Models.2/qmldir
532 - base/QtQml.2/Models/qmldir
533 - base/QtQml/Models/qmldir
534 */
completeQmldirPaths(const QString & uri,const QStringList & basePaths,int vmaj,int vmin)535 QStringList QQmlImports::completeQmldirPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin)
536 {
537 const QVector<QStringRef> parts = uri.splitRef(Dot, Qt::SkipEmptyParts);
538
539 QStringList qmlDirPathsPaths;
540 // fully & partially versioned parts + 1 unversioned for each base path
541 qmlDirPathsPaths.reserve(basePaths.count() * (2 * parts.count() + 1));
542
543 for (int version = FullyVersioned; version <= Unversioned; ++version) {
544 const QString ver = versionString(vmaj, vmin, static_cast<QQmlImports::ImportVersion>(version));
545
546 for (const QString &path : basePaths) {
547 QString dir = path;
548 if (!dir.endsWith(Slash) && !dir.endsWith(Backslash))
549 dir += Slash;
550
551 // append to the end
552 qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver + Slash_qmldir;
553
554 if (version != Unversioned) {
555 // insert in the middle
556 for (int index = parts.count() - 2; index >= 0; --index) {
557 qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash)
558 + ver + Slash
559 + joinStringRefs(parts.mid(index + 1), Slash) + Slash_qmldir;
560 }
561 }
562 }
563 }
564
565 return qmlDirPathsPaths;
566 }
567
versionString(int vmaj,int vmin,ImportVersion version)568 QString QQmlImports::versionString(int vmaj, int vmin, ImportVersion version)
569 {
570 if (version == QQmlImports::FullyVersioned) {
571 // extension with fully encoded version number (eg. MyModule.3.2)
572 return QString::asprintf(".%d.%d", vmaj, vmin);
573 } else if (version == QQmlImports::PartiallyVersioned) {
574 // extension with encoded version major (eg. MyModule.3)
575 return QString::asprintf(".%d", vmaj);
576 } // else extension without version number (eg. MyModule)
577 return QString();
578 }
579
580 /*!
581 \internal
582
583 The given (namespace qualified) \a type is resolved to either
584 \list
585 \li a QQmlImportNamespace stored at \a ns_return, or
586 \li a QQmlType stored at \a type_return,
587 \endlist
588
589 If any return pointer is 0, the corresponding search is not done.
590
591 \sa addFileImport(), addLibraryImport
592 */
resolveType(const QHashedStringRef & type,QQmlType * type_return,int * vmaj,int * vmin,QQmlImportNamespace ** ns_return,QList<QQmlError> * errors,QQmlType::RegistrationType registrationType,bool * typeRecursionDetected) const593 bool QQmlImports::resolveType(const QHashedStringRef &type,
594 QQmlType *type_return, int *vmaj, int *vmin,
595 QQmlImportNamespace** ns_return, QList<QQmlError> *errors,
596 QQmlType::RegistrationType registrationType,
597 bool *typeRecursionDetected) const
598 {
599 QQmlImportNamespace* ns = d->findQualifiedNamespace(type);
600 if (ns) {
601 if (ns_return)
602 *ns_return = ns;
603 return true;
604 }
605 if (type_return) {
606 if (d->resolveType(type, vmaj, vmin, type_return, errors, registrationType,
607 typeRecursionDetected)) {
608 if (qmlImportTrace()) {
609 #define RESOLVE_TYPE_DEBUG qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) \
610 << ')' << "::resolveType: " << type.toString() << " => "
611
612 if (type_return && type_return->isValid()) {
613 if (type_return->isCompositeSingleton())
614 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL-SINGLETON";
615 else if (type_return->isComposite())
616 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL";
617 else if (type_return->isInlineComponentType())
618 RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE(INLINECOMPONENT)";
619 else
620 RESOLVE_TYPE_DEBUG << type_return->typeName() << " TYPE";
621 }
622 #undef RESOLVE_TYPE_DEBUG
623 }
624 return true;
625 }
626 }
627 return false;
628 }
629
setQmldirContent(const QString & resolvedUrl,const QQmlTypeLoaderQmldirContent & qmldir,QQmlImportNamespace * nameSpace,QList<QQmlError> * errors)630 bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl, const QQmlTypeLoaderQmldirContent &qmldir, QQmlImportNamespace *nameSpace, QList<QQmlError> *errors)
631 {
632 Q_ASSERT(resolvedUrl.endsWith(Slash));
633 url = resolvedUrl;
634 localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url);
635
636 qmlDirComponents = qmldir.components();
637
638 const QQmlDirScripts &scripts = qmldir.scripts();
639 if (!scripts.isEmpty()) {
640 // Verify that we haven't imported these scripts already
641 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
642 it != nameSpace->imports.constEnd(); ++it) {
643 if ((*it != this) && ((*it)->uri == uri)) {
644 QQmlError error;
645 error.setDescription(QQmlImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg((*it)->url));
646 errors->prepend(error);
647 return false;
648 }
649 }
650
651 qmlDirScripts = getVersionedScripts(scripts, majversion, minversion);
652 }
653
654 return true;
655 }
656
getVersionedScripts(const QQmlDirScripts & qmldirscripts,int vmaj,int vmin)657 QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qmldirscripts, int vmaj, int vmin)
658 {
659 QMap<QString, QQmlDirParser::Script> versioned;
660
661 for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
662 sit != qmldirscripts.constEnd(); ++sit) {
663 // Only include scripts that match our requested version
664 if (((vmaj == -1) || (sit->majorVersion == vmaj)) &&
665 ((vmin == -1) || (sit->minorVersion <= vmin))) {
666 // Load the highest version that matches
667 QMap<QString, QQmlDirParser::Script>::iterator vit = versioned.find(sit->nameSpace);
668 if (vit == versioned.end() || (vit->minorVersion < sit->minorVersion)) {
669 versioned.insert(sit->nameSpace, *sit);
670 }
671 }
672 }
673
674 return versioned.values();
675 }
676
677 /*!
678 \internal
679
680 Searching \e only in the namespace \a ns (previously returned in a call to
681 resolveType(), \a type is found and returned to
682 a QQmlType stored at \a type_return. If the type is from a QML file, the returned
683 type will be a CompositeType.
684
685 If the return pointer is 0, the corresponding search is not done.
686 */
resolveType(QQmlImportNamespace * ns,const QHashedStringRef & type,QQmlType * type_return,int * vmaj,int * vmin,QQmlType::RegistrationType registrationType) const687 bool QQmlImports::resolveType(QQmlImportNamespace *ns, const QHashedStringRef &type,
688 QQmlType *type_return, int *vmaj, int *vmin,
689 QQmlType::RegistrationType registrationType) const
690 {
691 return ns->resolveType(d->typeLoader, type, vmaj, vmin, type_return, nullptr, nullptr, registrationType);
692 }
693
resolveType(QQmlTypeLoader * typeLoader,const QHashedStringRef & type,int * vmajor,int * vminor,QQmlType * type_return,QString * base,bool * typeRecursionDetected,QQmlType::RegistrationType registrationType,QQmlImport::RecursionRestriction recursionRestriction,QList<QQmlError> * errors) const694 bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type,
695 int *vmajor, int *vminor, QQmlType *type_return, QString *base,
696 bool *typeRecursionDetected,
697 QQmlType::RegistrationType registrationType,
698 QQmlImport::RecursionRestriction recursionRestriction,
699 QList<QQmlError> *errors) const
700 {
701 if (majversion >= 0 && minversion >= 0) {
702 QQmlType t = QQmlMetaType::qmlType(type, uri, majversion, minversion);
703 if (t.isValid()) {
704 if (vmajor)
705 *vmajor = majversion;
706 if (vminor)
707 *vminor = minversion;
708 if (type_return)
709 *type_return = t;
710 return true;
711 }
712 }
713
714 const QString typeStr = type.toString();
715 if (isInlineComponent) {
716 Q_ASSERT(type_return);
717 bool ret = uri == typeStr;
718 if (ret) {
719 Q_ASSERT(!type_return->isValid());
720 auto createICType = [&]() {
721 auto typePriv = new QQmlTypePrivate {QQmlType::RegistrationType::InlineComponentType};
722 bool ok = false;
723 typePriv->extraData.id->objectId = QUrl(this->url).fragment().toInt(&ok);
724 Q_ASSERT(ok);
725 typePriv->extraData.id->url = QUrl(this->url);
726 auto icType = QQmlType(typePriv);
727 typePriv->release();
728 return icType;
729 };
730 if (containingType.isValid()) {
731 // we currently cannot reference a Singleton inside itself
732 // in that case, containingType is still invalid
733 if (int icID = containingType.lookupInlineComponentIdByName(typeStr) != -1) {
734 *type_return = containingType.lookupInlineComponentById(icID);
735 } else {
736 auto icType = createICType();
737 int placeholderId = containingType.generatePlaceHolderICId();
738 const_cast<QQmlImportInstance*>(this)->containingType.associateInlineComponent(typeStr, placeholderId, CompositeMetaTypeIds {}, icType);
739 *type_return = QQmlType(icType);
740 }
741 } else {
742 *type_return = createICType();
743 }
744 }
745 return ret;
746 }
747 QQmlDirComponents::ConstIterator it = qmlDirComponents.find(typeStr), end = qmlDirComponents.end();
748 if (it != end) {
749 QString componentUrl;
750 bool isCompositeSingleton = false;
751 QQmlDirComponents::ConstIterator candidate = end;
752 for ( ; it != end && it.key() == typeStr; ++it) {
753 const QQmlDirParser::Component &c = *it;
754 switch (registrationType) {
755 case QQmlType::AnyRegistrationType:
756 break;
757 case QQmlType::CompositeSingletonType:
758 if (!c.singleton)
759 continue;
760 break;
761 default:
762 if (c.singleton)
763 continue;
764 break;
765 }
766
767 // importing version -1 means import ALL versions
768 if ((majversion == -1) ||
769 (implicitlyImported && c.internal) || // allow the implicit import of internal types
770 (c.majorVersion == majversion && c.minorVersion <= minversion)) {
771 // Is this better than the previous candidate?
772 if ((candidate == end) ||
773 (c.majorVersion > candidate->majorVersion) ||
774 ((c.majorVersion == candidate->majorVersion) && (c.minorVersion > candidate->minorVersion))) {
775 if (base) {
776 componentUrl = resolveLocalUrl(QString(url + c.typeName + dotqml_string), c.fileName);
777 if (c.internal) {
778 if (resolveLocalUrl(*base, c.fileName) != componentUrl)
779 continue; // failed attempt to access an internal type
780 }
781
782 const bool recursion = *base == componentUrl;
783 if (typeRecursionDetected)
784 *typeRecursionDetected = recursion;
785
786 if (recursionRestriction == QQmlImport::PreventRecursion && recursion) {
787 continue; // no recursion
788 }
789 }
790
791 // This is our best candidate so far
792 candidate = it;
793 isCompositeSingleton = c.singleton;
794 }
795 }
796 }
797
798 if (candidate != end) {
799 if (!base) // ensure we have a componentUrl
800 componentUrl = resolveLocalUrl(QString(url + candidate->typeName + dotqml_string), candidate->fileName);
801 QQmlType returnType = QQmlMetaType::typeForUrl(componentUrl, type, isCompositeSingleton,
802 nullptr, candidate->majorVersion,
803 candidate->minorVersion);
804 if (vmajor)
805 *vmajor = candidate->majorVersion;
806 if (vminor)
807 *vminor = candidate->minorVersion;
808 if (type_return)
809 *type_return = returnType;
810 return returnType.isValid();
811 }
812 } else if (!isLibrary && !localDirectoryPath.isEmpty()) {
813 QString qmlUrl;
814 bool exists = false;
815
816 const QString urlsToTry[2] = {
817 typeStr + dotqml_string, // Type -> Type.qml
818 typeStr + dotuidotqml_string // Type -> Type.ui.qml
819 };
820 for (const QString &urlToTry : urlsToTry) {
821 exists = typeLoader->fileExists(localDirectoryPath, urlToTry);
822 if (exists) {
823 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
824 // don't let function.qml confuse the use of "new Function(...)" for example.
825 if (!QQml_isFileCaseCorrect(localDirectoryPath + urlToTry)) {
826 exists = false;
827 if (errors) {
828 QQmlError caseError;
829 caseError.setDescription(QLatin1String("File name case mismatch"));
830 errors->append(caseError);
831 }
832 break;
833 }
834 #else
835 Q_UNUSED(errors);
836 #endif
837 qmlUrl = url + urlToTry;
838 break;
839 }
840 }
841
842 if (exists) {
843 const bool recursion = base && *base == qmlUrl;
844 if (typeRecursionDetected)
845 *typeRecursionDetected = recursion;
846 if (recursionRestriction == QQmlImport::AllowRecursion || !recursion) {
847 QQmlType returnType = QQmlMetaType::typeForUrl(
848 qmlUrl, type, registrationType == QQmlType::CompositeSingletonType, errors);
849 if (type_return)
850 *type_return = returnType;
851 return returnType.isValid();
852 }
853 }
854 }
855
856 return false;
857 }
858
resolveType(const QHashedStringRef & type,int * vmajor,int * vminor,QQmlType * type_return,QList<QQmlError> * errors,QQmlType::RegistrationType registrationType,bool * typeRecursionDetected)859 bool QQmlImportsPrivate::resolveType(const QHashedStringRef& type, int *vmajor, int *vminor,
860 QQmlType *type_return, QList<QQmlError> *errors,
861 QQmlType::RegistrationType registrationType,
862 bool *typeRecursionDetected)
863 {
864 const QVector<QHashedStringRef> splitName = type.split(Dot);
865 auto resolveTypeInNamespace = [&](QHashedStringRef unqualifiedtype, QQmlImportNamespace *nameSpace, QList<QQmlError> *errors) -> bool {
866 if (nameSpace->resolveType(typeLoader, unqualifiedtype, vmajor, vminor, type_return, &base, errors,
867 registrationType, typeRecursionDetected))
868 return true;
869 if (nameSpace->imports.count() == 1 && !nameSpace->imports.at(0)->isLibrary && type_return && nameSpace != &unqualifiedset) {
870 // qualified, and only 1 url
871 *type_return = QQmlMetaType::typeForUrl(
872 resolveLocalUrl(nameSpace->imports.at(0)->url,
873 unqualifiedtype.toString() + QLatin1String(".qml")),
874 type, false, errors);
875 return type_return->isValid();
876 }
877 return false;
878 };
879 switch (splitName.size()) {
880 case 1: {
881 // must be a simple type
882 return resolveTypeInNamespace(type, &unqualifiedset, errors);
883 }
884 case 2: {
885 // either namespace + simple type OR simple type + inline component
886 QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0));
887 if (s) {
888 // namespace + simple type
889 return resolveTypeInNamespace(splitName.at(1), s, errors);
890 } else {
891 if (resolveTypeInNamespace(splitName.at(0), &unqualifiedset, nullptr)) {
892 // either simple type + inline component
893 auto const icName = splitName.at(1).toString();
894 auto objectIndex = type_return->lookupInlineComponentIdByName(icName);
895 if (objectIndex != -1) {
896 *type_return = type_return->lookupInlineComponentById(objectIndex);
897 } else {
898 auto icTypePriv = new QQmlTypePrivate(QQmlType::RegistrationType::InlineComponentType);
899 icTypePriv->setContainingType(type_return);
900 icTypePriv->extraData.id->url = type_return->sourceUrl();
901 int placeholderId = type_return->generatePlaceHolderICId();
902 icTypePriv->extraData.id->url.setFragment(QString::number(placeholderId));
903 auto icType = QQmlType(icTypePriv);
904 icTypePriv->release();
905 type_return->associateInlineComponent(icName, placeholderId, CompositeMetaTypeIds {}, icType);
906 *type_return = icType;
907 }
908 Q_ASSERT(type_return->containingType().isValid());
909 type_return->setPendingResolutionName(icName);
910 return true;
911 } else {
912 // or a failure
913 if (errors) {
914 QQmlError error;
915 error.setDescription(QQmlImportDatabase::tr("- %1 is neither a type nor a namespace").arg(splitName.at(0).toString()));
916 errors->prepend(error);
917 }
918 return false;
919 }
920 }
921 }
922 case 3: {
923 // must be namespace + simple type + inline component
924 QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(0));
925 QQmlError error;
926 if (!s) {
927 error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(splitName.at(0).toString()));
928 } else {
929 if (resolveTypeInNamespace(splitName.at(1), s, nullptr)) {
930 auto const icName = splitName.at(2).toString();
931 auto objectIndex = type_return->lookupInlineComponentIdByName(icName);
932 if (objectIndex != -1)
933 *type_return = type_return->lookupInlineComponentById(objectIndex);
934 else {
935 auto icTypePriv = new QQmlTypePrivate(QQmlType::RegistrationType::InlineComponentType);
936 icTypePriv->setContainingType(type_return);
937 icTypePriv->extraData.id->url = type_return->sourceUrl();
938 int placeholderId = type_return->generatePlaceHolderICId();
939 icTypePriv->extraData.id->url.setFragment(QString::number(placeholderId));
940 auto icType = QQmlType(icTypePriv);
941 icTypePriv->release();
942 type_return->associateInlineComponent(icName, placeholderId, CompositeMetaTypeIds {}, icType);
943 *type_return = icType;
944 }
945 type_return->setPendingResolutionName(icName);
946 return true;
947 } else {
948 error.setDescription(QQmlImportDatabase::tr("- %1 is not a type").arg(splitName.at(1).toString()));
949 }
950 }
951 if (errors) {
952 errors->prepend(error);
953 }
954 return false;
955 }
956 default: {
957 // all other numbers suggest a user error
958 if (errors) {
959 QQmlError error;
960 error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed"));
961 errors->prepend(error);
962 }
963 return false;
964 }
965 }
966 Q_UNREACHABLE();
967 }
968
findImport(const QString & uri) const969 QQmlImportInstance *QQmlImportNamespace::findImport(const QString &uri) const
970 {
971 for (QQmlImportInstance *import : imports) {
972 if (import->uri == uri)
973 return import;
974 }
975 return nullptr;
976 }
977
resolveType(QQmlTypeLoader * typeLoader,const QHashedStringRef & type,int * vmajor,int * vminor,QQmlType * type_return,QString * base,QList<QQmlError> * errors,QQmlType::RegistrationType registrationType,bool * typeRecursionDetected)978 bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type,
979 int *vmajor, int *vminor, QQmlType *type_return,
980 QString *base, QList<QQmlError> *errors,
981 QQmlType::RegistrationType registrationType,
982 bool *typeRecursionDetected)
983 {
984 QQmlImport::RecursionRestriction recursionRestriction =
985 typeRecursionDetected ? QQmlImport::AllowRecursion : QQmlImport::PreventRecursion;
986
987 bool localTypeRecursionDetected = false;
988 if (!typeRecursionDetected)
989 typeRecursionDetected = &localTypeRecursionDetected;
990
991 if (needsSorting()) {
992 std::stable_partition(imports.begin(), imports.end(), [](QQmlImportInstance *import) {
993 return import->isInlineComponent;
994 });
995 setNeedsSorting(false);
996 }
997 for (int i=0; i<imports.count(); ++i) {
998 const QQmlImportInstance *import = imports.at(i);
999 if (import->resolveType(typeLoader, type, vmajor, vminor, type_return, base,
1000 typeRecursionDetected, registrationType, recursionRestriction, errors)) {
1001 if (qmlCheckTypes()) {
1002 // check for type clashes
1003 for (int j = i+1; j<imports.count(); ++j) {
1004 const QQmlImportInstance *import2 = imports.at(j);
1005 if (import2->resolveType(typeLoader, type, vmajor, vminor, nullptr, base,
1006 nullptr, registrationType)) {
1007 if (errors) {
1008 QString u1 = import->url;
1009 QString u2 = import2->url;
1010 if (base) {
1011 QStringRef b(base);
1012 int dot = b.lastIndexOf(Dot);
1013 if (dot >= 0) {
1014 b = b.left(dot+1);
1015 QStringRef l = b.left(dot);
1016 if (u1.startsWith(b))
1017 u1 = u1.mid(b.count());
1018 else if (u1 == l)
1019 u1 = QQmlImportDatabase::tr("local directory");
1020 if (u2.startsWith(b))
1021 u2 = u2.mid(b.count());
1022 else if (u2 == l)
1023 u2 = QQmlImportDatabase::tr("local directory");
1024 }
1025 }
1026
1027 QQmlError error;
1028 if (u1 != u2) {
1029 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2));
1030 } else {
1031 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
1032 .arg(u1)
1033 .arg(import->majversion).arg(import->minversion)
1034 .arg(import2->majversion).arg(import2->minversion));
1035 }
1036 errors->prepend(error);
1037 }
1038 return false;
1039 }
1040 }
1041 }
1042 return true;
1043 }
1044 }
1045 if (errors) {
1046 QQmlError error;
1047 if (*typeRecursionDetected)
1048 error.setDescription(QQmlImportDatabase::tr("is instantiated recursively"));
1049 else
1050 error.setDescription(QQmlImportDatabase::tr("is not a type"));
1051 errors->prepend(error);
1052 }
1053 return false;
1054 }
1055
needsSorting() const1056 bool QQmlImportNamespace::needsSorting() const
1057 {
1058 return nextNamespace == this;
1059 }
1060
setNeedsSorting(bool needsSorting)1061 void QQmlImportNamespace::setNeedsSorting(bool needsSorting)
1062 {
1063 Q_ASSERT(nextNamespace == this || nextNamespace == nullptr);
1064 nextNamespace = needsSorting ? this : nullptr;
1065 }
1066
QQmlImportsPrivate(QQmlTypeLoader * loader)1067 QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader)
1068 : ref(1), typeLoader(loader) {
1069 }
1070
~QQmlImportsPrivate()1071 QQmlImportsPrivate::~QQmlImportsPrivate()
1072 {
1073 while (QQmlImportNamespace *ns = qualifiedSets.takeFirst())
1074 delete ns;
1075 }
1076
findQualifiedNamespace(const QHashedStringRef & prefix) const1077 QQmlImportNamespace *QQmlImportsPrivate::findQualifiedNamespace(const QHashedStringRef &prefix) const
1078 {
1079 for (QQmlImportNamespace *ns = qualifiedSets.first(); ns; ns = qualifiedSets.next(ns)) {
1080 if (prefix == ns->prefix)
1081 return ns;
1082 }
1083 return nullptr;
1084 }
1085
1086 /*
1087 Returns the list of possible versioned URI combinations. For example, if \a uri is
1088 QtQml.Models, \a vmaj is 2, and \a vmin is 0, this method returns the following:
1089 [QtQml.Models.2.0, QtQml.2.0.Models, QtQml.Models.2, QtQml.2.Models, QtQml.Models]
1090 */
versionUriList(const QString & uri,int vmaj,int vmin)1091 static QStringList versionUriList(const QString &uri, int vmaj, int vmin)
1092 {
1093 QStringList result;
1094 for (int version = QQmlImports::FullyVersioned; version <= QQmlImports::Unversioned; ++version) {
1095 int index = uri.length();
1096 do {
1097 QString versionUri = uri;
1098 versionUri.insert(index, QQmlImports::versionString(vmaj, vmin, static_cast<QQmlImports::ImportVersion>(version)));
1099 result += versionUri;
1100
1101 index = uri.lastIndexOf(Dot, index - 1);
1102 } while (index > 0 && version != QQmlImports::Unversioned);
1103 }
1104 return result;
1105 }
1106
makePlugins()1107 static QVector<QStaticPlugin> makePlugins()
1108 {
1109 QVector<QStaticPlugin> plugins;
1110 // To avoid traversing all static plugins for all imports, we cut down
1111 // the list the first time called to only contain QML plugins:
1112 const auto staticPlugins = QPluginLoader::staticPlugins();
1113 for (const QStaticPlugin &plugin : staticPlugins) {
1114 const QString iid = plugin.metaData().value(QLatin1String("IID")).toString();
1115 if (iid == QLatin1String(QQmlEngineExtensionInterface_iid)
1116 || iid == QLatin1String(QQmlExtensionInterface_iid)
1117 || iid == QLatin1String(QQmlExtensionInterface_iid_old)) {
1118 plugins.append(plugin);
1119 }
1120 }
1121 return plugins;
1122 }
1123
1124 /*
1125 Get all static plugins that are QML plugins and has a meta data URI that matches with one of
1126 \a versionUris, which is a list of all possible versioned URI combinations - see versionUriList()
1127 above.
1128 */
populatePluginPairVector(QVector<StaticPluginPair> & result,const QString & uri,const QStringList & versionUris,const QString & qmldirPath,QList<QQmlError> * errors)1129 bool QQmlImportsPrivate::populatePluginPairVector(QVector<StaticPluginPair> &result, const QString &uri, const QStringList &versionUris,
1130 const QString &qmldirPath, QList<QQmlError> *errors)
1131 {
1132 static const QVector<QStaticPlugin> plugins = makePlugins();
1133 for (const QStaticPlugin &plugin : plugins) {
1134 // Since a module can list more than one plugin, we keep iterating even after we found a match.
1135 QObject *instance = plugin.instance();
1136 if (qobject_cast<QQmlEngineExtensionPlugin *>(instance)
1137 || qobject_cast<QQmlExtensionPlugin *>(instance)) {
1138 const QJsonArray metaTagsUriList = plugin.metaData().value(QLatin1String("uri")).toArray();
1139 if (metaTagsUriList.isEmpty()) {
1140 if (errors) {
1141 QQmlError error;
1142 error.setDescription(QQmlImportDatabase::tr("static plugin for module \"%1\" with name \"%2\" has no metadata URI")
1143 .arg(uri).arg(QString::fromUtf8(instance->metaObject()->className())));
1144 error.setUrl(QUrl::fromLocalFile(qmldirPath));
1145 errors->prepend(error);
1146 }
1147 return false;
1148 }
1149 // A plugin can be set up to handle multiple URIs, so go through the list:
1150 for (const QJsonValue &metaTagUri : metaTagsUriList) {
1151 if (versionUris.contains(metaTagUri.toString())) {
1152 result.append(qMakePair(plugin, metaTagsUriList));
1153 break;
1154 }
1155 }
1156 }
1157 }
1158 return true;
1159 }
1160
1161 /*
1162 Import an extension defined by a qmldir file.
1163
1164 \a qmldirFilePath is a raw file path.
1165 */
importExtension(const QString & qmldirFilePath,const QString & uri,int vmaj,int vmin,QQmlImportDatabase * database,const QQmlTypeLoaderQmldirContent & qmldir,QList<QQmlError> * errors)1166 bool QQmlImportsPrivate::importExtension(const QString &qmldirFilePath,
1167 const QString &uri,
1168 int vmaj, int vmin,
1169 QQmlImportDatabase *database,
1170 const QQmlTypeLoaderQmldirContent &qmldir,
1171 QList<QQmlError> *errors)
1172 {
1173 Q_ASSERT(qmldir.hasContent());
1174
1175 if (qmlImportTrace())
1176 qDebug().nospace() << "QQmlImports(" << qPrintable(base) << ")::importExtension: "
1177 << "loaded " << qmldirFilePath;
1178
1179 if (designerSupportRequired && !qmldir.designerSupported()) {
1180 if (errors) {
1181 QQmlError error;
1182 error.setDescription(QQmlImportDatabase::tr("module does not support the designer \"%1\"").arg(qmldir.typeNamespace()));
1183 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1184 errors->prepend(error);
1185 }
1186 return false;
1187 }
1188
1189 int qmldirPluginCount = qmldir.plugins().count();
1190 if (qmldirPluginCount == 0)
1191 return true;
1192
1193 if (database->qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(qmldirFilePath)) {
1194 if ((vmaj >= 0 && vmin >= 0)
1195 ? !QQmlMetaType::isModule(uri, vmaj, vmin)
1196 : !QQmlMetaType::isAnyModule(uri)) {
1197 QQmlMetaType::qmlRegisterModuleTypes(uri, vmaj);
1198 }
1199 } else {
1200 // First search for listed qmldir plugins dynamically. If we cannot resolve them all, we continue
1201 // searching static plugins that has correct metadata uri. Note that since we only know the uri
1202 // for a static plugin, and not the filename, we cannot know which static plugin belongs to which
1203 // listed plugin inside qmldir. And for this reason, mixing dynamic and static plugins inside a
1204 // single module is not recommended.
1205
1206 QString typeNamespace = qmldir.typeNamespace();
1207 QString qmldirPath = qmldirFilePath;
1208 int slash = qmldirPath.lastIndexOf(Slash);
1209 if (slash > 0)
1210 qmldirPath.truncate(slash);
1211
1212 int dynamicPluginsFound = 0;
1213 int staticPluginsFound = 0;
1214
1215 #if QT_CONFIG(library)
1216 const auto qmldirPlugins = qmldir.plugins();
1217 for (const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
1218 QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name);
1219 if (!resolvedFilePath.isEmpty()) {
1220 dynamicPluginsFound++;
1221 if (!database->importDynamicPlugin(resolvedFilePath, uri, typeNamespace, vmaj, errors)) {
1222 if (errors) {
1223 // XXX TODO: should we leave the import plugin error alone?
1224 // Here, we pop it off the top and coalesce it into this error's message.
1225 // The reason is that the lower level may add url and line/column numbering information.
1226 QQmlError error;
1227 error.setDescription(
1228 QQmlImportDatabase::tr(
1229 "plugin cannot be loaded for module \"%1\": %2")
1230 .arg(uri, errors->takeFirst().description()));
1231 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1232 errors->prepend(error);
1233 }
1234 return false;
1235 }
1236 }
1237 }
1238 #endif // QT_CONFIG(library)
1239
1240 if (dynamicPluginsFound < qmldirPluginCount) {
1241 // Check if the missing plugins can be resolved statically. We do this by looking at
1242 // the URIs embedded in a plugins meta data. Since those URIs can be anything from fully
1243 // versioned to unversioned, we need to compare with differnt version strings. If a module
1244 // has several plugins, they must all have the same version. Start by populating pluginPairs
1245 // with relevant plugins to cut the list short early on:
1246 const QStringList versionUris = versionUriList(uri, vmaj, vmin);
1247 QVector<StaticPluginPair> pluginPairs;
1248 if (!populatePluginPairVector(pluginPairs, uri, versionUris, qmldirFilePath, errors))
1249 return false;
1250
1251 const QString basePath = QFileInfo(qmldirPath).absoluteFilePath();
1252 for (const QString &versionUri : versionUris) {
1253 for (const StaticPluginPair &pair : qAsConst(pluginPairs)) {
1254 for (const QJsonValue &metaTagUri : pair.second) {
1255 if (versionUri == metaTagUri.toString()) {
1256 staticPluginsFound++;
1257 QObject *instance = pair.first.instance();
1258 if (!database->importStaticPlugin(instance, basePath, uri, typeNamespace, vmaj, errors)) {
1259 if (errors) {
1260 QQmlError poppedError = errors->takeFirst();
1261 QQmlError error;
1262 error.setDescription(QQmlImportDatabase::tr("static plugin for module \"%1\" with name \"%2\" cannot be loaded: %3")
1263 .arg(uri).arg(QString::fromUtf8(instance->metaObject()->className())).arg(poppedError.description()));
1264 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1265 errors->prepend(error);
1266 }
1267 return false;
1268 }
1269 break;
1270 }
1271 }
1272 }
1273 if (staticPluginsFound > 0)
1274 break;
1275 }
1276 }
1277
1278 if ((dynamicPluginsFound + staticPluginsFound) < qmldirPluginCount) {
1279 if (errors) {
1280 QQmlError error;
1281 if (qmldirPluginCount > 1 && staticPluginsFound > 0)
1282 error.setDescription(QQmlImportDatabase::tr("could not resolve all plugins for module \"%1\"").arg(uri));
1283 else
1284 error.setDescription(QQmlImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(qmldir.plugins()[dynamicPluginsFound].name));
1285 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1286 errors->prepend(error);
1287 }
1288 return false;
1289 }
1290
1291 database->qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(qmldirFilePath);
1292 }
1293 return true;
1294 }
1295
getQmldirContent(const QString & qmldirIdentifier,const QString & uri,QQmlTypeLoaderQmldirContent * qmldir,QList<QQmlError> * errors)1296 bool QQmlImportsPrivate::getQmldirContent(const QString &qmldirIdentifier, const QString &uri,
1297 QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors)
1298 {
1299 Q_ASSERT(errors);
1300 Q_ASSERT(qmldir);
1301
1302 *qmldir = typeLoader->qmldirContent(qmldirIdentifier);
1303 if ((*qmldir).hasContent()) {
1304 // Ensure that parsing was successful
1305 if ((*qmldir).hasError()) {
1306 QUrl url = QUrl::fromLocalFile(qmldirIdentifier);
1307 const QList<QQmlError> qmldirErrors = (*qmldir).errors(uri);
1308 for (int i = 0; i < qmldirErrors.size(); ++i) {
1309 QQmlError error = qmldirErrors.at(i);
1310 error.setUrl(url);
1311 errors->append(error);
1312 }
1313 return false;
1314 }
1315 }
1316
1317 return true;
1318 }
1319
resolvedUri(const QString & dir_arg,QQmlImportDatabase * database)1320 QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database)
1321 {
1322 QString dir = dir_arg;
1323 if (dir.endsWith(Slash) || dir.endsWith(Backslash))
1324 dir.chop(1);
1325
1326 QStringList paths = database->fileImportPath;
1327 if (!paths.isEmpty())
1328 std::sort(paths.begin(), paths.end(), std::greater<QString>()); // Ensure subdirs preceed their parents.
1329
1330 QString stableRelativePath = dir;
1331 for (const QString &path : qAsConst(paths)) {
1332 if (dir.startsWith(path)) {
1333 stableRelativePath = dir.mid(path.length()+1);
1334 break;
1335 }
1336 }
1337
1338 stableRelativePath.replace(Backslash, Slash);
1339
1340 // remove optional versioning in dot notation from uri
1341 int versionDot = stableRelativePath.lastIndexOf(Dot);
1342 if (versionDot >= 0) {
1343 int nextSlash = stableRelativePath.indexOf(Slash, versionDot);
1344 if (nextSlash >= 0)
1345 stableRelativePath.remove(versionDot, nextSlash - versionDot);
1346 else
1347 stableRelativePath = stableRelativePath.left(versionDot);
1348 }
1349
1350 stableRelativePath.replace(Slash, Dot);
1351
1352 return stableRelativePath;
1353 }
1354
1355 /*
1356 Locates the qmldir file for \a uri version \a vmaj.vmin. Returns true if found,
1357 and fills in outQmldirFilePath and outQmldirUrl appropriately. Otherwise returns
1358 false.
1359 */
locateLocalQmldir(const QString & uri,int vmaj,int vmin,QQmlImportDatabase * database,QString * outQmldirFilePath,QString * outQmldirPathUrl)1360 QQmlImports::LocalQmldirResult QQmlImportsPrivate::locateLocalQmldir(
1361 const QString &uri, int vmaj, int vmin, QQmlImportDatabase *database,
1362 QString *outQmldirFilePath, QString *outQmldirPathUrl)
1363 {
1364 Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries
1365
1366 // Check cache first
1367
1368 QQmlImportDatabase::QmldirCache *cacheHead = nullptr;
1369 {
1370 QQmlImportDatabase::QmldirCache **cachePtr = database->qmldirCache.value(uri);
1371 if (cachePtr) {
1372 cacheHead = *cachePtr;
1373 QQmlImportDatabase::QmldirCache *cache = cacheHead;
1374 while (cache) {
1375 if (cache->versionMajor == vmaj && cache->versionMinor == vmin) {
1376 *outQmldirFilePath = cache->qmldirFilePath;
1377 *outQmldirPathUrl = cache->qmldirPathUrl;
1378 return cache->qmldirFilePath.isEmpty() ? QQmlImports::QmldirNotFound
1379 : QQmlImports::QmldirFound;
1380 }
1381 cache = cache->next;
1382 }
1383 }
1384 }
1385
1386 QQmlTypeLoader &typeLoader = QQmlEnginePrivate::get(database->engine)->typeLoader;
1387
1388 // Interceptor might redirect remote files to local ones.
1389 QQmlAbstractUrlInterceptor *interceptor = typeLoader.engine()->urlInterceptor();
1390 QStringList localImportPaths = database->importPathList(
1391 interceptor ? QQmlImportDatabase::LocalOrRemote : QQmlImportDatabase::Local);
1392
1393 // Search local import paths for a matching version
1394 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(
1395 uri, localImportPaths, vmaj, vmin);
1396 bool pathTurnedRemote = false;
1397 for (QString qmldirPath : qmlDirPaths) {
1398 if (interceptor) {
1399 const QUrl intercepted = interceptor->intercept(
1400 QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
1401 QQmlAbstractUrlInterceptor::QmldirFile);
1402 qmldirPath = QQmlFile::urlToLocalFileOrQrc(intercepted);
1403 if (!pathTurnedRemote && qmldirPath.isEmpty() && !QQmlFile::isLocalFile(intercepted))
1404 pathTurnedRemote = true;
1405 }
1406
1407 QString absoluteFilePath = typeLoader.absoluteFilePath(qmldirPath);
1408 if (!absoluteFilePath.isEmpty()) {
1409 QString url;
1410 const QStringRef absolutePath = absoluteFilePath.leftRef(absoluteFilePath.lastIndexOf(Slash) + 1);
1411 if (absolutePath.at(0) == Colon)
1412 url = QLatin1String("qrc") + absolutePath;
1413 else
1414 url = QUrl::fromLocalFile(absolutePath.toString()).toString();
1415
1416 QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
1417 cache->versionMajor = vmaj;
1418 cache->versionMinor = vmin;
1419 cache->qmldirFilePath = absoluteFilePath;
1420 cache->qmldirPathUrl = url;
1421 cache->next = cacheHead;
1422 database->qmldirCache.insert(uri, cache);
1423
1424 *outQmldirFilePath = absoluteFilePath;
1425 *outQmldirPathUrl = url;
1426
1427 return QQmlImports::QmldirFound;
1428 }
1429 }
1430
1431 QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
1432 cache->versionMajor = vmaj;
1433 cache->versionMinor = vmin;
1434 cache->next = cacheHead;
1435 database->qmldirCache.insert(uri, cache);
1436
1437 return pathTurnedRemote ? QQmlImports::QmldirInterceptedToRemote : QQmlImports::QmldirNotFound;
1438 }
1439
validateQmldirVersion(const QQmlTypeLoaderQmldirContent & qmldir,const QString & uri,int vmaj,int vmin,QList<QQmlError> * errors)1440 bool QQmlImportsPrivate::validateQmldirVersion(const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, int vmaj, int vmin,
1441 QList<QQmlError> *errors)
1442 {
1443 int lowest_min = INT_MAX;
1444 int highest_min = INT_MIN;
1445
1446 typedef QQmlDirComponents::const_iterator ConstIterator;
1447 const QQmlDirComponents &components = qmldir.components();
1448
1449 ConstIterator cend = components.constEnd();
1450 for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
1451 for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) {
1452 if ((cit2->typeName == cit->typeName) &&
1453 (cit2->majorVersion == cit->majorVersion) &&
1454 (cit2->minorVersion == cit->minorVersion)) {
1455 // This entry clashes with a predecessor
1456 QQmlError error;
1457 error.setDescription(QQmlImportDatabase::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1458 .arg(cit->typeName).arg(cit->majorVersion).arg(cit->minorVersion).arg(uri));
1459 errors->prepend(error);
1460 return false;
1461 }
1462 }
1463
1464 if (cit->majorVersion == vmaj) {
1465 lowest_min = qMin(lowest_min, cit->minorVersion);
1466 highest_min = qMax(highest_min, cit->minorVersion);
1467 }
1468 }
1469
1470 typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
1471 const QQmlDirScripts &scripts = qmldir.scripts();
1472
1473 SConstIterator send = scripts.constEnd();
1474 for (SConstIterator sit = scripts.constBegin(); sit != send; ++sit) {
1475 for (SConstIterator sit2 = scripts.constBegin(); sit2 != sit; ++sit2) {
1476 if ((sit2->nameSpace == sit->nameSpace) &&
1477 (sit2->majorVersion == sit->majorVersion) &&
1478 (sit2->minorVersion == sit->minorVersion)) {
1479 // This entry clashes with a predecessor
1480 QQmlError error;
1481 error.setDescription(QQmlImportDatabase::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1482 .arg(sit->nameSpace).arg(sit->majorVersion).arg(sit->minorVersion).arg(uri));
1483 errors->prepend(error);
1484 return false;
1485 }
1486 }
1487
1488 if (sit->majorVersion == vmaj) {
1489 lowest_min = qMin(lowest_min, sit->minorVersion);
1490 highest_min = qMax(highest_min, sit->minorVersion);
1491 }
1492 }
1493
1494 if (lowest_min > vmin || highest_min < vmin) {
1495 QQmlError error;
1496 error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin));
1497 errors->prepend(error);
1498 return false;
1499 }
1500
1501 return true;
1502 }
1503
importNamespace(const QString & prefix) const1504 QQmlImportNamespace *QQmlImportsPrivate::importNamespace(const QString &prefix) const
1505 {
1506 QQmlImportNamespace *nameSpace = nullptr;
1507
1508 if (prefix.isEmpty()) {
1509 nameSpace = &unqualifiedset;
1510 } else {
1511 nameSpace = findQualifiedNamespace(prefix);
1512
1513 if (!nameSpace) {
1514 nameSpace = new QQmlImportNamespace;
1515 nameSpace->prefix = prefix;
1516 qualifiedSets.append(nameSpace);
1517 }
1518 }
1519
1520 return nameSpace;
1521 }
1522
addImportToNamespace(QQmlImportNamespace * nameSpace,const QString & uri,const QString & url,int vmaj,int vmin,QV4::CompiledData::Import::ImportType type,QList<QQmlError> * errors,bool lowPrecedence)1523 QQmlImportInstance *QQmlImportsPrivate::addImportToNamespace(QQmlImportNamespace *nameSpace,
1524 const QString &uri, const QString &url, int vmaj, int vmin,
1525 QV4::CompiledData::Import::ImportType type,
1526 QList<QQmlError> *errors, bool lowPrecedence)
1527 {
1528 Q_ASSERT(nameSpace);
1529 Q_ASSERT(errors);
1530 Q_UNUSED(errors);
1531 Q_ASSERT(url.isEmpty() || url.endsWith(Slash));
1532
1533 QQmlImportInstance *import = new QQmlImportInstance;
1534 import->uri = uri;
1535 import->url = url;
1536 import->localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url);
1537 import->majversion = vmaj;
1538 import->minversion = vmin;
1539 import->isLibrary = (type == QV4::CompiledData::Import::ImportLibrary);
1540
1541 if (lowPrecedence)
1542 nameSpace->imports.append(import);
1543 else
1544 nameSpace->imports.prepend(import);
1545
1546 return import;
1547 }
1548
addLibraryImport(const QString & uri,const QString & prefix,int vmaj,int vmin,const QString & qmldirIdentifier,const QString & qmldirUrl,bool incomplete,QQmlImportDatabase * database,QList<QQmlError> * errors)1549 bool QQmlImportsPrivate::addLibraryImport(const QString& uri, const QString &prefix,
1550 int vmaj, int vmin, const QString &qmldirIdentifier, const QString &qmldirUrl, bool incomplete,
1551 QQmlImportDatabase *database,
1552 QList<QQmlError> *errors)
1553 {
1554 Q_ASSERT(database);
1555 Q_ASSERT(errors);
1556
1557 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1558 Q_ASSERT(nameSpace);
1559
1560 QQmlImportInstance *inserted = addImportToNamespace(nameSpace, uri, qmldirUrl, vmaj, vmin, QV4::CompiledData::Import::ImportLibrary, errors);
1561 Q_ASSERT(inserted);
1562
1563 if (!incomplete) {
1564 QQmlTypeLoaderQmldirContent qmldir;
1565
1566 if (!qmldirIdentifier.isEmpty()) {
1567 if (!getQmldirContent(qmldirIdentifier, uri, &qmldir, errors))
1568 return false;
1569
1570 if (qmldir.hasContent()) {
1571 if (!importExtension(qmldir.pluginLocation(), uri, vmaj, vmin, database, qmldir, errors))
1572 return false;
1573
1574 if (!inserted->setQmldirContent(qmldirUrl, qmldir, nameSpace, errors))
1575 return false;
1576 }
1577 }
1578
1579 // Ensure that we are actually providing something
1580 if ((vmaj < 0) || (vmin < 0) || !QQmlMetaType::isModule(uri, vmaj, vmin)) {
1581 if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) {
1582 QQmlError error;
1583 if (QQmlMetaType::isAnyModule(uri))
1584 error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin));
1585 else
1586 error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri));
1587 errors->prepend(error);
1588 return false;
1589 } else if ((vmaj >= 0) && (vmin >= 0) && qmldir.hasContent()) {
1590 // Verify that the qmldir content is valid for this version
1591 if (!validateQmldirVersion(qmldir, uri, vmaj, vmin, errors))
1592 return false;
1593 }
1594 }
1595 }
1596
1597 return true;
1598 }
1599
addFileImport(const QString & uri,const QString & prefix,int vmaj,int vmin,bool isImplicitImport,bool incomplete,QQmlImportDatabase * database,QList<QQmlError> * errors)1600 bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix,
1601 int vmaj, int vmin,
1602 bool isImplicitImport, bool incomplete, QQmlImportDatabase *database,
1603 QList<QQmlError> *errors)
1604 {
1605 Q_ASSERT(errors);
1606
1607 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1608 Q_ASSERT(nameSpace);
1609
1610 // The uri for this import. For library imports this is the same as uri
1611 // specified by the user, but it may be different in the case of file imports.
1612 QString importUri = uri;
1613 QString qmldirUrl = resolveLocalUrl(base, importUri + (importUri.endsWith(Slash)
1614 ? String_qmldir
1615 : Slash_qmldir));
1616 if (QQmlAbstractUrlInterceptor *interceptor = typeLoader->engine()->urlInterceptor()) {
1617 qmldirUrl = interceptor->intercept(QUrl(qmldirUrl),
1618 QQmlAbstractUrlInterceptor::QmldirFile).toString();
1619 }
1620 QString qmldirIdentifier;
1621
1622 if (QQmlFile::isLocalFile(qmldirUrl)) {
1623
1624 QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
1625 Q_ASSERT(!localFileOrQrc.isEmpty());
1626
1627 const QString dir = localFileOrQrc.left(localFileOrQrc.lastIndexOf(Slash) + 1);
1628 if (!typeLoader->directoryExists(dir)) {
1629 if (!isImplicitImport) {
1630 QQmlError error;
1631 error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri));
1632 error.setUrl(QUrl(qmldirUrl));
1633 errors->prepend(error);
1634 }
1635 return false;
1636 }
1637
1638 // Transforms the (possible relative) uri into our best guess relative to the
1639 // import paths.
1640 importUri = resolvedUri(dir, database);
1641 if (importUri.endsWith(Slash))
1642 importUri.chop(1);
1643
1644 if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty())
1645 qmldirIdentifier = localFileOrQrc;
1646
1647 } else if (nameSpace->prefix.isEmpty() && !incomplete) {
1648
1649 if (!isImplicitImport) {
1650 QQmlError error;
1651 error.setDescription(QQmlImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(importUri));
1652 error.setUrl(QUrl(qmldirUrl));
1653 errors->prepend(error);
1654 }
1655
1656 return false;
1657
1658 }
1659
1660 // The url for the path containing files for this import
1661 QString url = resolveLocalUrl(base, uri);
1662 if (!url.endsWith(Slash) && !url.endsWith(Backslash))
1663 url += Slash;
1664
1665 // ### For enum support, we are now adding the implicit import always (and earlier). Bail early
1666 // if the implicit import has already been explicitly added, otherwise we can run into issues
1667 // with duplicate imports. However remember that we attempted to add this as implicit import, to
1668 // allow for the loading of internal types.
1669 if (isImplicitImport) {
1670 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
1671 it != nameSpace->imports.constEnd(); ++it) {
1672 if ((*it)->url == url) {
1673 (*it)->implicitlyImported = true;
1674 return true;
1675 }
1676 }
1677 }
1678
1679 QQmlImportInstance *inserted = addImportToNamespace(nameSpace, importUri, url, vmaj, vmin, QV4::CompiledData::Import::ImportFile, errors, isImplicitImport);
1680 Q_ASSERT(inserted);
1681
1682 if (!incomplete && !qmldirIdentifier.isEmpty()) {
1683 QQmlTypeLoaderQmldirContent qmldir;
1684 if (!getQmldirContent(qmldirIdentifier, importUri, &qmldir, errors))
1685 return false;
1686
1687 if (qmldir.hasContent()) {
1688 if (!importExtension(qmldir.pluginLocation(), importUri, vmaj, vmin, database, qmldir, errors))
1689 return false;
1690
1691 if (!inserted->setQmldirContent(url, qmldir, nameSpace, errors))
1692 return false;
1693 }
1694 }
1695
1696 return true;
1697 }
1698
updateQmldirContent(const QString & uri,const QString & prefix,const QString & qmldirIdentifier,const QString & qmldirUrl,QQmlImportDatabase * database,QList<QQmlError> * errors)1699 bool QQmlImportsPrivate::updateQmldirContent(const QString &uri, const QString &prefix,
1700 const QString &qmldirIdentifier, const QString& qmldirUrl,
1701 QQmlImportDatabase *database, QList<QQmlError> *errors)
1702 {
1703 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1704 Q_ASSERT(nameSpace);
1705
1706 if (QQmlImportInstance *import = nameSpace->findImport(uri)) {
1707 QQmlTypeLoaderQmldirContent qmldir;
1708 if (!getQmldirContent(qmldirIdentifier, uri, &qmldir, errors))
1709 return false;
1710
1711 if (qmldir.hasContent()) {
1712 int vmaj = import->majversion;
1713 int vmin = import->minversion;
1714 if (!importExtension(qmldir.pluginLocation(), uri, vmaj, vmin, database, qmldir, errors))
1715 return false;
1716
1717 if (import->setQmldirContent(qmldirUrl, qmldir, nameSpace, errors)) {
1718 if (import->qmlDirComponents.isEmpty() && import->qmlDirScripts.isEmpty()) {
1719 // The implicit import qmldir can be empty, and plugins have no extra versions
1720 if (uri != QLatin1String(".") && !QQmlMetaType::isModule(uri, vmaj, vmin)) {
1721 QQmlError error;
1722 if (QQmlMetaType::isAnyModule(uri))
1723 error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin));
1724 else
1725 error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri));
1726 errors->prepend(error);
1727 return false;
1728 }
1729 } else if ((vmaj >= 0) && (vmin >= 0)) {
1730 // Verify that the qmldir content is valid for this version
1731 if (!validateQmldirVersion(qmldir, uri, vmaj, vmin, errors))
1732 return false;
1733 }
1734 return true;
1735 }
1736 }
1737 }
1738
1739 if (errors->isEmpty()) {
1740 QQmlError error;
1741 error.setDescription(QQmlTypeLoader::tr("Cannot update qmldir content for '%1'").arg(uri));
1742 errors->prepend(error);
1743 }
1744
1745 return false;
1746 }
1747
1748 /*!
1749 \internal
1750
1751 Adds an implicit "." file import. This is equivalent to calling addFileImport(), but error
1752 messages related to the path or qmldir file not existing are suppressed.
1753
1754 Additionally, this will add the import with lowest instead of highest precedence.
1755 */
addImplicitImport(QQmlImportDatabase * importDb,QList<QQmlError> * errors)1756 bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb, QList<QQmlError> *errors)
1757 {
1758 Q_ASSERT(errors);
1759
1760 if (qmlImportTrace())
1761 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString())
1762 << ")::addImplicitImport";
1763
1764 bool incomplete = !isLocal(baseUrl());
1765 return d->addFileImport(QLatin1String("."), QString(), -1, -1, true, incomplete, importDb, errors);
1766 }
1767
1768 /*!
1769 \internal
1770 */
addInlineComponentImport(QQmlImportInstance * const importInstance,const QString & name,const QUrl importUrl,QQmlType containingType)1771 bool QQmlImports::addInlineComponentImport(QQmlImportInstance *const importInstance, const QString &name, const QUrl importUrl, QQmlType containingType)
1772 {
1773 importInstance->url = importUrl.toString();
1774 importInstance->uri = name;
1775 importInstance->isInlineComponent = true;
1776 importInstance->majversion = 0;
1777 importInstance->minversion = 0;
1778 importInstance->containingType = containingType;
1779 d->unqualifiedset.imports.push_back(importInstance);
1780 d->unqualifiedset.setNeedsSorting(true);
1781 return true;
1782 }
1783
1784 /*!
1785 \internal
1786
1787 Adds information to \a imports such that subsequent calls to resolveType()
1788 will resolve types qualified by \a prefix by considering types found at the given \a uri.
1789
1790 The uri is either a directory (if importType is FileImport), or a URI resolved using paths
1791 added via addImportPath() (if importType is LibraryImport).
1792
1793 The \a prefix may be empty, in which case the import location is considered for
1794 unqualified types.
1795
1796 The base URL must already have been set with Import::setBaseUrl().
1797
1798 Optionally, the url the import resolved to can be returned by providing the url parameter.
1799 Not all imports will result in an output url being generated, in which case the url will
1800 be set to an empty string.
1801
1802 Returns true on success, and false on failure. In case of failure, the errors array will
1803 filled appropriately.
1804 */
addFileImport(QQmlImportDatabase * importDb,const QString & uri,const QString & prefix,int vmaj,int vmin,bool incomplete,QList<QQmlError> * errors)1805 bool QQmlImports::addFileImport(QQmlImportDatabase *importDb,
1806 const QString& uri, const QString& prefix, int vmaj, int vmin,
1807 bool incomplete, QList<QQmlError> *errors)
1808 {
1809 Q_ASSERT(importDb);
1810 Q_ASSERT(errors);
1811
1812 if (qmlImportTrace())
1813 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addFileImport: "
1814 << uri << ' ' << vmaj << '.' << vmin << " as " << prefix;
1815
1816 return d->addFileImport(uri, prefix, vmaj, vmin, false, incomplete, importDb, errors);
1817 }
1818
addLibraryImport(QQmlImportDatabase * importDb,const QString & uri,const QString & prefix,int vmaj,int vmin,const QString & qmldirIdentifier,const QString & qmldirUrl,bool incomplete,QList<QQmlError> * errors)1819 bool QQmlImports::addLibraryImport(QQmlImportDatabase *importDb,
1820 const QString &uri, const QString &prefix, int vmaj, int vmin,
1821 const QString &qmldirIdentifier, const QString& qmldirUrl, bool incomplete, QList<QQmlError> *errors)
1822 {
1823 Q_ASSERT(importDb);
1824 Q_ASSERT(errors);
1825
1826 if (qmlImportTrace())
1827 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addLibraryImport: "
1828 << uri << ' ' << vmaj << '.' << vmin << " as " << prefix;
1829
1830 return d->addLibraryImport(uri, prefix, vmaj, vmin, qmldirIdentifier, qmldirUrl, incomplete, importDb, errors);
1831 }
1832
updateQmldirContent(QQmlImportDatabase * importDb,const QString & uri,const QString & prefix,const QString & qmldirIdentifier,const QString & qmldirUrl,QList<QQmlError> * errors)1833 bool QQmlImports::updateQmldirContent(QQmlImportDatabase *importDb,
1834 const QString &uri, const QString &prefix,
1835 const QString &qmldirIdentifier, const QString& qmldirUrl, QList<QQmlError> *errors)
1836 {
1837 Q_ASSERT(importDb);
1838 Q_ASSERT(errors);
1839
1840 if (qmlImportTrace())
1841 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::updateQmldirContent: "
1842 << uri << " to " << qmldirUrl << " as " << prefix;
1843
1844 return d->updateQmldirContent(uri, prefix, qmldirIdentifier, qmldirUrl, importDb, errors);
1845 }
1846
locateLocalQmldir(QQmlImportDatabase * importDb,const QString & uri,int vmaj,int vmin,QString * qmldirFilePath,QString * url)1847 QQmlImports::LocalQmldirResult QQmlImports::locateLocalQmldir(
1848 QQmlImportDatabase *importDb, const QString &uri, int vmaj, int vmin,
1849 QString *qmldirFilePath, QString *url)
1850 {
1851 return d->locateLocalQmldir(uri, vmaj, vmin, importDb, qmldirFilePath, url);
1852 }
1853
isLocal(const QString & url)1854 bool QQmlImports::isLocal(const QString &url)
1855 {
1856 return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty();
1857 }
1858
isLocal(const QUrl & url)1859 bool QQmlImports::isLocal(const QUrl &url)
1860 {
1861 return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty();
1862 }
1863
urlFromLocalFileOrQrcOrUrl(const QString & file)1864 QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file)
1865 {
1866 QUrl url(QLatin1String(file.at(0) == Colon ? "qrc" : "") + file);
1867
1868 // We don't support single character schemes as those conflict with windows drive letters.
1869 if (url.scheme().length() < 2)
1870 return QUrl::fromLocalFile(file);
1871 return url;
1872 }
1873
setDesignerSupportRequired(bool b)1874 void QQmlImports::setDesignerSupportRequired(bool b)
1875 {
1876 designerSupportRequired = b;
1877 }
1878
1879
1880 /*!
1881 \class QQmlImportDatabase
1882 \brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine.
1883 \internal
1884 */
QQmlImportDatabase(QQmlEngine * e)1885 QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
1886 : engine(e)
1887 {
1888 filePluginPath << QLatin1String(".");
1889 // Search order is applicationDirPath(), qrc:/qt-project.org/imports, $QML2_IMPORT_PATH, QLibraryInfo::Qml2ImportsPath
1890
1891 QString installImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
1892 addImportPath(installImportsPath);
1893
1894 // env import paths
1895 if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("QML2_IMPORT_PATH"))) {
1896 const QString envImportPath = qEnvironmentVariable("QML2_IMPORT_PATH");
1897 #if defined(Q_OS_WIN)
1898 QLatin1Char pathSep(';');
1899 #else
1900 QLatin1Char pathSep(':');
1901 #endif
1902 QStringList paths = envImportPath.split(pathSep, Qt::SkipEmptyParts);
1903 for (int ii = paths.count() - 1; ii >= 0; --ii)
1904 addImportPath(paths.at(ii));
1905 }
1906
1907 addImportPath(QStringLiteral("qrc:/qt-project.org/imports"));
1908 addImportPath(QCoreApplication::applicationDirPath());
1909 #if defined(Q_OS_ANDROID)
1910 addImportPath(QStringLiteral("qrc:/android_rcc_bundle/qml"));
1911 if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("QT_BUNDLED_LIBS_PATH"))) {
1912 const QString envImportPath = qEnvironmentVariable("QT_BUNDLED_LIBS_PATH");
1913 QLatin1Char pathSep(':');
1914 QStringList paths = envImportPath.split(pathSep, Qt::SkipEmptyParts);
1915 for (int ii = paths.count() - 1; ii >= 0; --ii)
1916 addPluginPath(paths.at(ii));
1917 }
1918 #endif
1919 }
1920
~QQmlImportDatabase()1921 QQmlImportDatabase::~QQmlImportDatabase()
1922 {
1923 clearDirCache();
1924 }
1925
1926 /*!
1927 \internal
1928
1929 Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
1930 The \a prefix must contain the dot.
1931
1932 \a qmldirPath is the location of the qmldir file.
1933 */
resolvePlugin(QQmlTypeLoader * typeLoader,const QString & qmldirPath,const QString & qmldirPluginPath,const QString & baseName,const QStringList & suffixes,const QString & prefix)1934 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1935 const QString &qmldirPath,
1936 const QString &qmldirPluginPath,
1937 const QString &baseName, const QStringList &suffixes,
1938 const QString &prefix)
1939 {
1940 QStringList searchPaths = filePluginPath;
1941 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
1942 if (!qmldirPluginPathIsRelative)
1943 searchPaths.prepend(qmldirPluginPath);
1944
1945 for (const QString &pluginPath : qAsConst(searchPaths)) {
1946 QString resolvedPath;
1947 if (pluginPath == QLatin1String(".")) {
1948 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String("."))
1949 resolvedPath = QDir::cleanPath(qmldirPath + Slash + qmldirPluginPath);
1950 else
1951 resolvedPath = qmldirPath;
1952 } else {
1953 if (QDir::isRelativePath(pluginPath))
1954 resolvedPath = QDir::cleanPath(qmldirPath + Slash + pluginPath);
1955 else
1956 resolvedPath = pluginPath;
1957 }
1958
1959 // hack for resources, should probably go away
1960 if (resolvedPath.startsWith(Colon))
1961 resolvedPath = QCoreApplication::applicationDirPath();
1962
1963 if (!resolvedPath.endsWith(Slash))
1964 resolvedPath += Slash;
1965
1966 #if defined(Q_OS_ANDROID)
1967 if (qmldirPath.size() > 25 && qmldirPath.at(0) == QLatin1Char(':') && qmldirPath.at(1) == QLatin1Char('/') &&
1968 qmldirPath.startsWith(QStringLiteral(":/android_rcc_bundle/qml/"), Qt::CaseInsensitive)) {
1969 QString pluginName = qmldirPath.mid(21) + Slash + baseName;
1970 pluginName.replace(QLatin1Char('/'), QLatin1Char('_'));
1971 QString bundledPath = resolvedPath + QLatin1String("lib") + pluginName;
1972 for (const QString &suffix : suffixes) {
1973 const QString absolutePath = typeLoader->absoluteFilePath(bundledPath + suffix);
1974 if (!absolutePath.isEmpty())
1975 return absolutePath;
1976 }
1977 }
1978 #endif
1979 resolvedPath += prefix + baseName;
1980 for (const QString &suffix : suffixes) {
1981 const QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
1982 if (!absolutePath.isEmpty())
1983 return absolutePath;
1984 }
1985 }
1986
1987 if (qmlImportTrace())
1988 qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName
1989 << "in" << qmldirPath;
1990
1991 return QString();
1992 }
1993
1994 /*!
1995 \internal
1996
1997 Returns the result of the merge of \a baseName with \a dir and the platform suffix.
1998
1999 \table
2000 \header \li Platform \li Valid suffixes
2001 \row \li Windows \li \c .dll
2002 \row \li Unix/Linux \li \c .so
2003 \row \li \macos \li \c .dylib, \c .bundle, \c .so
2004 \endtable
2005
2006 Version number on unix are ignored.
2007 */
resolvePlugin(QQmlTypeLoader * typeLoader,const QString & qmldirPath,const QString & qmldirPluginPath,const QString & baseName)2008 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
2009 const QString &qmldirPath, const QString &qmldirPluginPath,
2010 const QString &baseName)
2011 {
2012 #if defined(Q_OS_WIN)
2013 static const QString prefix;
2014 static const QStringList suffixes = {
2015 # ifdef QT_DEBUG
2016 QLatin1String("d.dll"), // try a qmake-style debug build first
2017 QLatin1String(".dll")
2018 #else
2019 QLatin1String(".dll"),
2020 QLatin1String("d.dll") // try a qmake-style debug build after
2021 # endif
2022 };
2023 #elif defined(Q_OS_DARWIN)
2024 static const QString prefix = QLatin1String("lib");
2025 static const QStringList suffixes = {
2026 # ifdef QT_DEBUG
2027 QLatin1String("_debug.dylib"), // try a qmake-style debug build first
2028 QLatin1String(".dylib"),
2029 # else
2030 QLatin1String(".dylib"),
2031 QLatin1String("_debug.dylib"), // try a qmake-style debug build after
2032 # endif
2033 QLatin1String(".so"),
2034 QLatin1String(".bundle")
2035 };
2036 #else // Unix
2037 static const QString prefix = QLatin1String("lib");
2038 static const QStringList suffixes = {
2039 # if defined(Q_OS_ANDROID)
2040 QStringLiteral(LIBS_SUFFIX),
2041 # endif
2042 QLatin1String(".so")
2043
2044 };
2045 #endif
2046
2047 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, suffixes, prefix);
2048 }
2049
2050 /*!
2051 \internal
2052 */
pluginPathList() const2053 QStringList QQmlImportDatabase::pluginPathList() const
2054 {
2055 return filePluginPath;
2056 }
2057
2058 /*!
2059 \internal
2060 */
setPluginPathList(const QStringList & paths)2061 void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
2062 {
2063 if (qmlImportTrace())
2064 qDebug().nospace() << "QQmlImportDatabase::setPluginPathList: " << paths;
2065
2066 filePluginPath = paths;
2067 }
2068
2069 /*!
2070 \internal
2071 */
addPluginPath(const QString & path)2072 void QQmlImportDatabase::addPluginPath(const QString& path)
2073 {
2074 if (qmlImportTrace())
2075 qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path;
2076
2077 QUrl url = QUrl(path);
2078 if (url.isRelative() || url.scheme() == QLatin1String("file")
2079 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
2080 QDir dir = QDir(path);
2081 filePluginPath.prepend(dir.canonicalPath());
2082 } else {
2083 filePluginPath.prepend(path);
2084 }
2085 }
2086
2087 /*!
2088 \internal
2089 */
addImportPath(const QString & path)2090 void QQmlImportDatabase::addImportPath(const QString& path)
2091 {
2092 if (qmlImportTrace())
2093 qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path;
2094
2095 if (path.isEmpty())
2096 return;
2097
2098 QUrl url = QUrl(path);
2099 QString cPath;
2100
2101 if (url.scheme() == QLatin1String("file")) {
2102 cPath = QQmlFile::urlToLocalFileOrQrc(url);
2103 } else if (path.startsWith(QLatin1Char(':'))) {
2104 // qrc directory, e.g. :/foo
2105 // need to convert to a qrc url, e.g. qrc:/foo
2106 cPath = QLatin1String("qrc") + path;
2107 cPath.replace(Backslash, Slash);
2108 } else if (url.isRelative() ||
2109 (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
2110 QDir dir = QDir(path);
2111 cPath = dir.canonicalPath();
2112 } else {
2113 cPath = path;
2114 cPath.replace(Backslash, Slash);
2115 }
2116
2117 if (!cPath.isEmpty()
2118 && !fileImportPath.contains(cPath))
2119 fileImportPath.prepend(cPath);
2120 }
2121
2122 /*!
2123 \internal
2124 */
importPathList(PathType type) const2125 QStringList QQmlImportDatabase::importPathList(PathType type) const
2126 {
2127 if (type == LocalOrRemote)
2128 return fileImportPath;
2129
2130 QStringList list;
2131 for (const QString &path : fileImportPath) {
2132 bool localPath = isPathAbsolute(path) || QQmlFile::isLocalFile(path);
2133 if (localPath == (type == Local))
2134 list.append(path);
2135 }
2136
2137 return list;
2138 }
2139
2140 /*!
2141 \internal
2142 */
setImportPathList(const QStringList & paths)2143 void QQmlImportDatabase::setImportPathList(const QStringList &paths)
2144 {
2145 if (qmlImportTrace())
2146 qDebug().nospace() << "QQmlImportDatabase::setImportPathList: " << paths;
2147
2148 fileImportPath.clear();
2149 for (auto it = paths.crbegin(); it != paths.crend(); ++it)
2150 addImportPath(*it);
2151
2152 // Our existing cached paths may have been invalidated
2153 clearDirCache();
2154 }
2155
2156 /*!
2157 \internal
2158 */
registerPluginTypes(QObject * instance,const QString & basePath,const QString & uri,const QString & typeNamespace,int vmaj,QList<QQmlError> * errors)2159 static bool registerPluginTypes(QObject *instance, const QString &basePath, const QString &uri,
2160 const QString &typeNamespace, int vmaj, QList<QQmlError> *errors)
2161 {
2162 if (qmlImportTrace())
2163 qDebug().nospace() << "QQmlImportDatabase::registerPluginTypes: " << uri << " from " << basePath;
2164
2165 if (!QQmlMetaType::registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors))
2166 return false;
2167
2168 if (vmaj >= 0 && !typeNamespace.isEmpty() && !QQmlMetaType::protectModule(uri, vmaj)) {
2169 QQmlError error;
2170 error.setDescription(
2171 QString::fromLatin1("Cannot protect module %1 %2 as it was never registered")
2172 .arg(uri).arg(vmaj));
2173 errors->append(error);
2174 return false;
2175 }
2176
2177 return true;
2178 }
2179
2180 /*!
2181 \internal
2182 */
importStaticPlugin(QObject * instance,const QString & basePath,const QString & uri,const QString & typeNamespace,int vmaj,QList<QQmlError> * errors)2183 bool QQmlImportDatabase::importStaticPlugin(QObject *instance, const QString &basePath,
2184 const QString &uri, const QString &typeNamespace, int vmaj, QList<QQmlError> *errors)
2185 {
2186 // Dynamic plugins are differentiated by their filepath. For static plugins we
2187 // don't have that information so we use their address as key instead.
2188 const QString uniquePluginID = QString::asprintf("%p", instance);
2189 {
2190 StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
2191 QMutexLocker lock(&plugins->mutex);
2192
2193 // Plugin types are global across all engines and should only be
2194 // registered once. But each engine still needs to be initialized.
2195 bool typesRegistered = plugins->contains(uniquePluginID);
2196
2197 if (typesRegistered) {
2198 Q_ASSERT_X(plugins->value(uniquePluginID).uri == uri,
2199 "QQmlImportDatabase::importStaticPlugin",
2200 "Internal error: Static plugin imported previously with different uri");
2201 } else {
2202 RegisteredPlugin plugin;
2203 plugin.uri = uri;
2204 plugin.loader = nullptr;
2205 plugins->insert(uniquePluginID, plugin);
2206
2207 if (!registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors))
2208 return false;
2209 }
2210
2211 // Release the lock on plugins early as we're done with the global part. Releasing the lock
2212 // also allows other QML loader threads to acquire the lock while this thread is blocking
2213 // in the initializeEngine call to the gui thread (which in turn may be busy waiting for
2214 // other QML loader threads and thus not process the initializeEngine call).
2215 }
2216
2217 if (!initializedPlugins.contains(uniquePluginID))
2218 finalizePlugin(instance, uniquePluginID, uri);
2219
2220 return true;
2221 }
2222
2223 #if QT_CONFIG(library)
2224 /*!
2225 \internal
2226 */
importDynamicPlugin(const QString & filePath,const QString & uri,const QString & typeNamespace,int vmaj,QList<QQmlError> * errors)2227 bool QQmlImportDatabase::importDynamicPlugin(const QString &filePath, const QString &uri,
2228 const QString &typeNamespace, int vmaj, QList<QQmlError> *errors)
2229 {
2230 QFileInfo fileInfo(filePath);
2231 const QString absoluteFilePath = fileInfo.absoluteFilePath();
2232
2233 QObject *instance = nullptr;
2234 bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
2235 {
2236 StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
2237 QMutexLocker lock(&plugins->mutex);
2238 bool typesRegistered = plugins->contains(absoluteFilePath);
2239
2240 if (typesRegistered) {
2241 Q_ASSERT_X(plugins->value(absoluteFilePath).uri == uri,
2242 "QQmlImportDatabase::importDynamicPlugin",
2243 "Internal error: Plugin imported previously with different uri");
2244 }
2245
2246 if (!engineInitialized || !typesRegistered) {
2247 if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
2248 if (errors) {
2249 QQmlError error;
2250 error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
2251 errors->prepend(error);
2252 }
2253 return false;
2254 }
2255
2256 QPluginLoader* loader = nullptr;
2257 if (!typesRegistered) {
2258 loader = new QPluginLoader(absoluteFilePath);
2259
2260 if (!loader->load()) {
2261 if (errors) {
2262 QQmlError error;
2263 error.setDescription(loader->errorString());
2264 errors->prepend(error);
2265 }
2266 delete loader;
2267 return false;
2268 }
2269 } else {
2270 loader = plugins->value(absoluteFilePath).loader;
2271 }
2272
2273 instance = loader->instance();
2274
2275 if (!typesRegistered) {
2276 RegisteredPlugin plugin;
2277 plugin.uri = uri;
2278 plugin.loader = loader;
2279 plugins->insert(absoluteFilePath, plugin);
2280
2281 // Continue with shared code path for dynamic and static plugins:
2282 if (!registerPluginTypes(instance, fileInfo.absolutePath(), uri, typeNamespace, vmaj, errors))
2283 return false;
2284 }
2285 }
2286
2287 // Release the lock on plugins early as we're done with the global part. Releasing the lock
2288 // also allows other QML loader threads to acquire the lock while this thread is blocking
2289 // in the initializeEngine call to the gui thread (which in turn may be busy waiting for
2290 // other QML loader threads and thus not process the initializeEngine call).
2291 }
2292
2293 if (!engineInitialized)
2294 finalizePlugin(instance, absoluteFilePath, uri);
2295
2296 return true;
2297 }
2298
removeDynamicPlugin(const QString & filePath)2299 bool QQmlImportDatabase::removeDynamicPlugin(const QString &filePath)
2300 {
2301 StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
2302 QMutexLocker lock(&plugins->mutex);
2303
2304 auto it = plugins->find(QFileInfo(filePath).absoluteFilePath());
2305 if (it == plugins->end())
2306 return false;
2307
2308 QPluginLoader *loader = it->loader;
2309 if (!loader)
2310 return false;
2311
2312 if (!loader->unload()) {
2313 qWarning("Unloading %s failed: %s", qPrintable(it->uri),
2314 qPrintable(loader->errorString()));
2315 }
2316
2317 delete loader;
2318 plugins->erase(it);
2319 return true;
2320 }
2321
dynamicPlugins() const2322 QStringList QQmlImportDatabase::dynamicPlugins() const
2323 {
2324 StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
2325 QMutexLocker lock(&plugins->mutex);
2326 QStringList results;
2327 for (auto it = plugins->constBegin(), end = plugins->constEnd(); it != end; ++it) {
2328 if (it->loader != nullptr)
2329 results.append(it.key());
2330 }
2331 return results;
2332 }
2333 #endif // QT_CONFIG(library)
2334
clearDirCache()2335 void QQmlImportDatabase::clearDirCache()
2336 {
2337 QStringHash<QmldirCache *>::ConstIterator itr = qmldirCache.constBegin();
2338 while (itr != qmldirCache.constEnd()) {
2339 QmldirCache *cache = *itr;
2340 do {
2341 QmldirCache *nextCache = cache->next;
2342 delete cache;
2343 cache = nextCache;
2344 } while (cache);
2345
2346 ++itr;
2347 }
2348 qmldirCache.clear();
2349 }
2350
finalizePlugin(QObject * instance,const QString & path,const QString & uri)2351 void QQmlImportDatabase::finalizePlugin(QObject *instance, const QString &path, const QString &uri)
2352 {
2353 // The plugin's per-engine initialization does not need lock protection, as this function is
2354 // only called from the engine specific loader thread and importDynamicPlugin as well as
2355 // importStaticPlugin are the only places of access.
2356
2357 initializedPlugins.insert(path);
2358 if (auto *extensionIface = qobject_cast<QQmlExtensionInterface *>(instance)) {
2359 QQmlEnginePrivate::get(engine)->typeLoader.initializeEngine(
2360 extensionIface, uri.toUtf8().constData());
2361 } else if (auto *engineIface = qobject_cast<QQmlEngineExtensionInterface *>(instance)) {
2362 QQmlEnginePrivate::get(engine)->typeLoader.initializeEngine(
2363 engineIface, uri.toUtf8().constData());
2364 }
2365 }
2366
2367 QT_END_NAMESPACE
2368