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
62#include <algorithm>
63#include <functional>
64
65QT_BEGIN_NAMESPACE
66
67DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
68DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
69
70static const QLatin1Char Dot('.');
71static const QLatin1Char Slash('/');
72static const QLatin1Char Backslash('\\');
73static const QLatin1Char Colon(':');
74static const QLatin1String Slash_qmldir("/qmldir");
75static const QLatin1String String_qmldir("qmldir");
76static const QString dotqml_string(QStringLiteral(".qml"));
77static const QString dotuidotqml_string(QStringLiteral(".ui.qml"));
78static bool designerSupportRequired = false;
79
80namespace {
81
82QString resolveLocalUrl(const QString &url, const QString &relative)
83{
84 if (relative.contains(Colon)) {
85 // contains a host name
86 return QUrl(url).resolved(QUrl(relative)).toString();
87 } else if (relative.isEmpty()) {
88 return url;
89 } else if (relative.at(0) == Slash || !url.contains(Slash)) {
90 return relative;
91 } else {
92 const QStringRef baseRef = url.leftRef(url.lastIndexOf(Slash) + 1);
93 if (relative == QLatin1String("."))
94 return baseRef.toString();
95
96 QString base = baseRef + relative;
97
98 // Remove any relative directory elements in the path
99 int length = base.length();
100 int index = 0;
101 while ((index = base.indexOf(QLatin1String("/."), index)) != -1) {
102 if ((length > (index + 2)) && (base.at(index + 2) == Dot) &&
103 (length == (index + 3) || (base.at(index + 3) == Slash))) {
104 // Either "/../" or "/..<END>"
105 int previous = base.lastIndexOf(Slash, index - 1);
106 if (previous == -1)
107 break;
108
109 int removeLength = (index - previous) + 3;
110 base.remove(previous + 1, removeLength);
111 length -= removeLength;
112 index = previous;
113 } else if ((length == (index + 2)) || (base.at(index + 2) == Slash)) {
114 // Either "/./" or "/.<END>"
115 base.remove(index, 2);
116 length -= 2;
117 } else {
118 ++index;
119 }
120 }
121
122 return base;
123 }
124}
125
126bool isPathAbsolute(const QString &path)
127{
128#if defined(Q_OS_UNIX)
129 return (path.at(0) == Slash);
130#else
131 QFileInfo fi(path);
132 return fi.isAbsolute();
133#endif
134}
135
136} // namespace
137
138struct RegisteredPlugin {
139 QString uri;
140 QPluginLoader* loader;
141};
142
143struct StringRegisteredPluginMap : public QMap<QString, RegisteredPlugin> {
144 QMutex mutex;
145
146 ~StringRegisteredPluginMap()
147 {
148 QMutexLocker lock(&mutex);
149 for (const RegisteredPlugin &plugin : qAsConst(*this))
150 delete plugin.loader;
151 }
152};
153
154Q_GLOBAL_STATIC(StringRegisteredPluginMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri and the PluginLoaders
155
156void qmlClearEnginePlugins()
157{
158 StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
159 QMutexLocker lock(&plugins->mutex);
160#if QT_CONFIG(library)
161 for (auto &plugin : qAsConst(*plugins)) {
162 QPluginLoader* loader = plugin.loader;
163 if (loader && !loader->unload())
164 qWarning("Unloading %s failed: %s", qPrintable(plugin.uri), qPrintable(loader->errorString()));
165 delete loader;
166 }
167#endif
168 plugins->clear();
169}
170
171typedef QPair<QStaticPlugin, QJsonArray> StaticPluginPair;
172
173/*!
174 \internal
175 \class QQmlImportInstance
176
177 A QQmlImportType represents a single import of a document, held within a
178 namespace.
179
180 \note The uri here may not necessarily be unique (e.g. for file imports).
181
182 \note Version numbers may be -1 for file imports: this means that no
183 version was specified as part of the import. Type resolution will be
184 responsible for attempting to find the "best" possible version.
185*/
186
187/*!
188 \internal
189 \class QQmlImportNamespace
190
191 A QQmlImportNamespace is a way of seperating imports into a local namespace.
192
193 Within a QML document, there is at least one namespace (the
194 "unqualified set") where imports without a qualifier are placed, i.e:
195
196 import QtQuick 2.6
197
198 will have a single namespace (the unqualified set) containing a single import
199 for QtQuick 2.6. However, there may be others if an import statement gives
200 a qualifier, i.e the following will result in an additional new
201 QQmlImportNamespace in the qualified set:
202
203 import MyFoo 1.0 as Foo
204*/
205
206class QQmlImportsPrivate
207{
208public:
209 QQmlImportsPrivate(QQmlTypeLoader *loader);
210 ~QQmlImportsPrivate();
211
212 QQmlImportNamespace *importNamespace(const QString &prefix) const;
213
214 bool addLibraryImport(const QString& uri, const QString &prefix,
215 int vmaj, int vmin, const QString &qmldirIdentifier, const QString &qmldirUrl, bool incomplete,
216 QQmlImportDatabase *database,
217 QList<QQmlError> *errors);
218
219 bool addFileImport(const QString &uri, const QString &prefix,
220 int vmaj, int vmin,
221 bool isImplicitImport, bool incomplete, QQmlImportDatabase *database,
222 QList<QQmlError> *errors);
223
224 bool updateQmldirContent(const QString &uri, const QString &prefix,
225 const QString &qmldirIdentifier, const QString& qmldirUrl,
226 QQmlImportDatabase *database,
227 QList<QQmlError> *errors);
228
229 bool resolveType(const QHashedStringRef &type, int *vmajor, int *vminor,
230 QQmlType *type_return, QList<QQmlError> *errors,
231 QQmlType::RegistrationType registrationType,
232 QQmlImport::RecursionRestriction recursionRestriction
233 = QQmlImport::PreventRecursion);
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 bool locateQmldir(const QString &uri, int vmaj, int vmin,
250 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*/
281QQmlImports::QQmlImports(const QQmlImports &copy)
282: d(copy.d)
283{
284 ++d->ref;
285}
286
287QQmlImports &
288QQmlImports::operator =(const QQmlImports &copy)
289{
290 ++copy.d->ref;
291 if (--d->ref == 0)
292 delete d;
293 d = copy.d;
294 return *this;
295}
296
297QQmlImports::QQmlImports(QQmlTypeLoader *typeLoader)
298 : d(new QQmlImportsPrivate(typeLoader))
299{
300}
301
302QQmlImports::~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*/
311void 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*/
326QUrl 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*/
341void 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.
375bool 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
389void 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*/
443QList<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*/
478QList<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
514static 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*/
535QStringList QQmlImports::completeQmldirPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin)
536{
537 const QVector<QStringRef> parts = uri.splitRef(Dot, QString::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
568QString 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*/
593bool 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 QQmlImport::RecursionRestriction recursionRestriction) 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 recursionRestriction)) {
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
618 RESOLVE_TYPE_DEBUG << type_return->typeName() << " TYPE";
619 }
620#undef RESOLVE_TYPE_DEBUG
621 }
622 return true;
623 }
624 }
625 return false;
626}
627
628bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl, const QQmlTypeLoaderQmldirContent &qmldir, QQmlImportNamespace *nameSpace, QList<QQmlError> *errors)
629{
630 Q_ASSERT(resolvedUrl.endsWith(Slash));
631 url = resolvedUrl;
632 localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url);
633
634 qmlDirComponents = qmldir.components();
635
636 const QQmlDirScripts &scripts = qmldir.scripts();
637 if (!scripts.isEmpty()) {
638 // Verify that we haven't imported these scripts already
639 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
640 it != nameSpace->imports.constEnd(); ++it) {
641 if ((*it != this) && ((*it)->uri == uri)) {
642 QQmlError error;
643 error.setDescription(QQmlImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg((*it)->url));
644 errors->prepend(error);
645 return false;
646 }
647 }
648
649 qmlDirScripts = getVersionedScripts(scripts, majversion, minversion);
650 }
651
652 return true;
653}
654
655QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qmldirscripts, int vmaj, int vmin)
656{
657 QMap<QString, QQmlDirParser::Script> versioned;
658
659 for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
660 sit != qmldirscripts.constEnd(); ++sit) {
661 // Only include scripts that match our requested version
662 if (((vmaj == -1) || (sit->majorVersion == vmaj)) &&
663 ((vmin == -1) || (sit->minorVersion <= vmin))) {
664 // Load the highest version that matches
665 QMap<QString, QQmlDirParser::Script>::iterator vit = versioned.find(sit->nameSpace);
666 if (vit == versioned.end() || (vit->minorVersion < sit->minorVersion)) {
667 versioned.insert(sit->nameSpace, *sit);
668 }
669 }
670 }
671
672 return versioned.values();
673}
674
675/*!
676 \internal
677
678 Searching \e only in the namespace \a ns (previously returned in a call to
679 resolveType(), \a type is found and returned to
680 a QQmlType stored at \a type_return. If the type is from a QML file, the returned
681 type will be a CompositeType.
682
683 If the return pointer is 0, the corresponding search is not done.
684*/
685bool QQmlImports::resolveType(QQmlImportNamespace *ns, const QHashedStringRef &type,
686 QQmlType *type_return, int *vmaj, int *vmin,
687 QQmlType::RegistrationType registrationType) const
688{
689 return ns->resolveType(d->typeLoader, type, vmaj, vmin, type_return, nullptr, nullptr, registrationType);
690}
691
692bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type,
693 int *vmajor, int *vminor, QQmlType *type_return, QString *base,
694 bool *typeRecursionDetected,
695 QQmlType::RegistrationType registrationType,
696 QQmlImport::RecursionRestriction recursionRestriction,
697 QList<QQmlError> *errors) const
698{
699 if (majversion >= 0 && minversion >= 0) {
700 QQmlType t = QQmlMetaType::qmlType(type, uri, majversion, minversion);
701 if (t.isValid()) {
702 if (vmajor)
703 *vmajor = majversion;
704 if (vminor)
705 *vminor = minversion;
706 if (type_return)
707 *type_return = t;
708 return true;
709 }
710 }
711
712 const QString typeStr = type.toString();
713 QQmlDirComponents::ConstIterator it = qmlDirComponents.find(typeStr), end = qmlDirComponents.end();
714 if (it != end) {
715 QString componentUrl;
716 bool isCompositeSingleton = false;
717 QQmlDirComponents::ConstIterator candidate = end;
718 for ( ; it != end && it.key() == typeStr; ++it) {
719 const QQmlDirParser::Component &c = *it;
720 switch (registrationType) {
721 case QQmlType::AnyRegistrationType:
722 break;
723 case QQmlType::CompositeSingletonType:
724 if (!c.singleton)
725 continue;
726 break;
727 default:
728 if (c.singleton)
729 continue;
730 break;
731 }
732
733 // importing version -1 means import ALL versions
734 if ((majversion == -1) ||
735 (implicitlyImported && c.internal) || // allow the implicit import of internal types
736 (c.majorVersion == majversion && c.minorVersion <= minversion)) {
737 // Is this better than the previous candidate?
738 if ((candidate == end) ||
739 (c.majorVersion > candidate->majorVersion) ||
740 ((c.majorVersion == candidate->majorVersion) && (c.minorVersion > candidate->minorVersion))) {
741 if (base) {
742 componentUrl = resolveLocalUrl(QString(url + c.typeName + dotqml_string), c.fileName);
743 if (c.internal) {
744 if (resolveLocalUrl(*base, c.fileName) != componentUrl)
745 continue; // failed attempt to access an internal type
746 }
747 if (recursionRestriction == QQmlImport::PreventRecursion && *base == componentUrl) {
748 if (typeRecursionDetected)
749 *typeRecursionDetected = true;
750 continue; // no recursion
751 }
752 }
753
754 // This is our best candidate so far
755 candidate = it;
756 isCompositeSingleton = c.singleton;
757 }
758 }
759 }
760
761 if (candidate != end) {
762 if (!base) // ensure we have a componentUrl
763 componentUrl = resolveLocalUrl(QString(url + candidate->typeName + dotqml_string), candidate->fileName);
764 QQmlType returnType = QQmlMetaType::typeForUrl(componentUrl, type, isCompositeSingleton,
765 nullptr, candidate->majorVersion,
766 candidate->minorVersion);
767 if (vmajor)
768 *vmajor = candidate->majorVersion;
769 if (vminor)
770 *vminor = candidate->minorVersion;
771 if (type_return)
772 *type_return = returnType;
773 return returnType.isValid();
774 }
775 } else if (!isLibrary && !localDirectoryPath.isEmpty()) {
776 QString qmlUrl;
777 bool exists = false;
778
779 const QString urlsToTry[2] = {
780 typeStr + dotqml_string, // Type -> Type.qml
781 typeStr + dotuidotqml_string // Type -> Type.ui.qml
782 };
783 for (uint i = 0; i < sizeof(urlsToTry) / sizeof(urlsToTry[0]); ++i) {
784 exists = typeLoader->fileExists(localDirectoryPath, urlsToTry[i]);
785 if (exists) {
786#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
787 // don't let function.qml confuse the use of "new Function(...)" for example.
788 if (!QQml_isFileCaseCorrect(localDirectoryPath + urlsToTry[i])) {
789 exists = false;
790 if (errors) {
791 QQmlError caseError;
792 caseError.setDescription(QLatin1String("File name case mismatch"));
793 errors->append(caseError);
794 }
795 break;
796 }
797#else
798 Q_UNUSED(errors);
799#endif
800 qmlUrl = url + urlsToTry[i];
801 break;
802 }
803 }
804
805 if (exists) {
806 if (recursionRestriction == QQmlImport::PreventRecursion && base && (*base == qmlUrl)) { // no recursion
807 if (typeRecursionDetected)
808 *typeRecursionDetected = true;
809 } else {
810 QQmlType returnType = QQmlMetaType::typeForUrl(
811 qmlUrl, type, registrationType == QQmlType::CompositeSingletonType, errors);
812 if (type_return)
813 *type_return = returnType;
814 return returnType.isValid();
815 }
816 }
817 }
818
819 return false;
820}
821
822bool QQmlImportsPrivate::resolveType(const QHashedStringRef& type, int *vmajor, int *vminor,
823 QQmlType *type_return, QList<QQmlError> *errors,
824 QQmlType::RegistrationType registrationType,
825 QQmlImport::RecursionRestriction recursionRestriction)
826{
827 QQmlImportNamespace *s = nullptr;
828 int dot = type.indexOf(Dot);
829 if (dot >= 0) {
830 QHashedStringRef namespaceName(type.constData(), dot);
831 s = findQualifiedNamespace(namespaceName);
832 if (!s) {
833 if (errors) {
834 QQmlError error;
835 error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName.toString()));
836 errors->prepend(error);
837 }
838 return false;
839 }
840 int ndot = type.indexOf(Dot,dot+1);
841 if (ndot > 0) {
842 if (errors) {
843 QQmlError error;
844 error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed"));
845 errors->prepend(error);
846 }
847 return false;
848 }
849 } else {
850 s = &unqualifiedset;
851 }
852 QHashedStringRef unqualifiedtype = dot < 0 ? type : QHashedStringRef(type.constData()+dot+1, type.length()-dot-1);
853 if (s) {
854 if (s->resolveType(typeLoader, unqualifiedtype, vmajor, vminor, type_return, &base, errors,
855 registrationType, recursionRestriction))
856 return true;
857 if (s->imports.count() == 1 && !s->imports.at(0)->isLibrary && type_return && s != &unqualifiedset) {
858 // qualified, and only 1 url
859 *type_return = QQmlMetaType::typeForUrl(
860 resolveLocalUrl(s->imports.at(0)->url,
861 unqualifiedtype.toString() + QLatin1String(".qml")),
862 type, false, errors);
863 return type_return->isValid();
864 }
865 }
866
867 return false;
868}
869
870QQmlImportInstance *QQmlImportNamespace::findImport(const QString &uri) const
871{
872 for (QQmlImportInstance *import : imports) {
873 if (import->uri == uri)
874 return import;
875 }
876 return nullptr;
877}
878
879bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type,
880 int *vmajor, int *vminor, QQmlType *type_return,
881 QString *base, QList<QQmlError> *errors,
882 QQmlType::RegistrationType registrationType,
883 QQmlImport::RecursionRestriction recursionRestriction)
884{
885 bool typeRecursionDetected = false;
886 for (int i=0; i<imports.count(); ++i) {
887 const QQmlImportInstance *import = imports.at(i);
888 if (import->resolveType(typeLoader, type, vmajor, vminor, type_return, base,
889 &typeRecursionDetected, registrationType, recursionRestriction, errors)) {
890 if (qmlCheckTypes()) {
891 // check for type clashes
892 for (int j = i+1; j<imports.count(); ++j) {
893 const QQmlImportInstance *import2 = imports.at(j);
894 if (import2->resolveType(typeLoader, type, vmajor, vminor, nullptr, base,
895 nullptr, registrationType)) {
896 if (errors) {
897 QString u1 = import->url;
898 QString u2 = import2->url;
899 if (base) {
900 QStringRef b(base);
901 int dot = b.lastIndexOf(Dot);
902 if (dot >= 0) {
903 b = b.left(dot+1);
904 QStringRef l = b.left(dot);
905 if (u1.startsWith(b))
906 u1 = u1.mid(b.count());
907 else if (u1 == l)
908 u1 = QQmlImportDatabase::tr("local directory");
909 if (u2.startsWith(b))
910 u2 = u2.mid(b.count());
911 else if (u2 == l)
912 u2 = QQmlImportDatabase::tr("local directory");
913 }
914 }
915
916 QQmlError error;
917 if (u1 != u2) {
918 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2));
919 } else {
920 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
921 .arg(u1)
922 .arg(import->majversion).arg(import->minversion)
923 .arg(import2->majversion).arg(import2->minversion));
924 }
925 errors->prepend(error);
926 }
927 return false;
928 }
929 }
930 }
931 return true;
932 }
933 }
934 if (errors) {
935 QQmlError error;
936 if (typeRecursionDetected)
937 error.setDescription(QQmlImportDatabase::tr("is instantiated recursively"));
938 else
939 error.setDescription(QQmlImportDatabase::tr("is not a type"));
940 errors->prepend(error);
941 }
942 return false;
943}
944
945QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader)
946: ref(1), typeLoader(loader) {
947}
948
949QQmlImportsPrivate::~QQmlImportsPrivate()
950{
951 while (QQmlImportNamespace *ns = qualifiedSets.takeFirst())
952 delete ns;
953}
954
955QQmlImportNamespace *QQmlImportsPrivate::findQualifiedNamespace(const QHashedStringRef &prefix) const
956{
957 for (QQmlImportNamespace *ns = qualifiedSets.first(); ns; ns = qualifiedSets.next(ns)) {
958 if (prefix == ns->prefix)
959 return ns;
960 }
961 return nullptr;
962}
963
964/*
965 Returns the list of possible versioned URI combinations. For example, if \a uri is
966 QtQml.Models, \a vmaj is 2, and \a vmin is 0, this method returns the following:
967 [QtQml.Models.2.0, QtQml.2.0.Models, QtQml.Models.2, QtQml.2.Models, QtQml.Models]
968 */
969static QStringList versionUriList(const QString &uri, int vmaj, int vmin)
970{
971 QStringList result;
972 for (int version = QQmlImports::FullyVersioned; version <= QQmlImports::Unversioned; ++version) {
973 int index = uri.length();
974 do {
975 QString versionUri = uri;
976 versionUri.insert(index, QQmlImports::versionString(vmaj, vmin, static_cast<QQmlImports::ImportVersion>(version)));
977 result += versionUri;
978
979 index = uri.lastIndexOf(Dot, index - 1);
980 } while (index > 0 && version != QQmlImports::Unversioned);
981 }
982 return result;
983}
984
985static QVector<QStaticPlugin> makePlugins()
986{
987 QVector<QStaticPlugin> plugins;
988 // To avoid traversing all static plugins for all imports, we cut down
989 // the list the first time called to only contain QML plugins:
990 const auto staticPlugins = QPluginLoader::staticPlugins();
991 for (const QStaticPlugin &plugin : staticPlugins) {
992 const QString iid = plugin.metaData().value(QLatin1String("IID")).toString();
993 if (iid == QLatin1String(QQmlExtensionInterface_iid) || iid == QLatin1String(QQmlExtensionInterface_iid_old)) {
994 plugins.append(plugin);
995 }
996 }
997 return plugins;
998}
999
1000/*
1001 Get all static plugins that are QML plugins and has a meta data URI that matches with one of
1002 \a versionUris, which is a list of all possible versioned URI combinations - see versionUriList()
1003 above.
1004 */
1005bool QQmlImportsPrivate::populatePluginPairVector(QVector<StaticPluginPair> &result, const QString &uri, const QStringList &versionUris,
1006 const QString &qmldirPath, QList<QQmlError> *errors)
1007{
1008 static const QVector<QStaticPlugin> plugins = makePlugins();
1009 for (const QStaticPlugin &plugin : plugins) {
1010 // Since a module can list more than one plugin, we keep iterating even after we found a match.
1011 if (QQmlExtensionPlugin *instance = qobject_cast<QQmlExtensionPlugin *>(plugin.instance())) {
1012 const QJsonArray metaTagsUriList = plugin.metaData().value(QLatin1String("uri")).toArray();
1013 if (metaTagsUriList.isEmpty()) {
1014 if (errors) {
1015 QQmlError error;
1016 error.setDescription(QQmlImportDatabase::tr("static plugin for module \"%1\" with name \"%2\" has no metadata URI")
1017 .arg(uri).arg(QString::fromUtf8(instance->metaObject()->className())));
1018 error.setUrl(QUrl::fromLocalFile(qmldirPath));
1019 errors->prepend(error);
1020 }
1021 return false;
1022 }
1023 // A plugin can be set up to handle multiple URIs, so go through the list:
1024 for (const QJsonValue &metaTagUri : metaTagsUriList) {
1025 if (versionUris.contains(metaTagUri.toString())) {
1026 result.append(qMakePair(plugin, metaTagsUriList));
1027 break;
1028 }
1029 }
1030 }
1031 }
1032 return true;
1033}
1034
1035/*
1036Import an extension defined by a qmldir file.
1037
1038\a qmldirFilePath is a raw file path.
1039*/
1040bool QQmlImportsPrivate::importExtension(const QString &qmldirFilePath,
1041 const QString &uri,
1042 int vmaj, int vmin,
1043 QQmlImportDatabase *database,
1044 const QQmlTypeLoaderQmldirContent &qmldir,
1045 QList<QQmlError> *errors)
1046{
1047 Q_ASSERT(qmldir.hasContent());
1048
1049 if (qmlImportTrace())
1050 qDebug().nospace() << "QQmlImports(" << qPrintable(base) << ")::importExtension: "
1051 << "loaded " << qmldirFilePath;
1052
1053 if (designerSupportRequired && !qmldir.designerSupported()) {
1054 if (errors) {
1055 QQmlError error;
1056 error.setDescription(QQmlImportDatabase::tr("module does not support the designer \"%1\"").arg(qmldir.typeNamespace()));
1057 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1058 errors->prepend(error);
1059 }
1060 return false;
1061 }
1062
1063 int qmldirPluginCount = qmldir.plugins().count();
1064 if (qmldirPluginCount == 0)
1065 return true;
1066
1067 if (!database->qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(qmldirFilePath)) {
1068 // First search for listed qmldir plugins dynamically. If we cannot resolve them all, we continue
1069 // searching static plugins that has correct metadata uri. Note that since we only know the uri
1070 // for a static plugin, and not the filename, we cannot know which static plugin belongs to which
1071 // listed plugin inside qmldir. And for this reason, mixing dynamic and static plugins inside a
1072 // single module is not recommended.
1073
1074 QString typeNamespace = qmldir.typeNamespace();
1075 QString qmldirPath = qmldirFilePath;
1076 int slash = qmldirPath.lastIndexOf(Slash);
1077 if (slash > 0)
1078 qmldirPath.truncate(slash);
1079
1080 int dynamicPluginsFound = 0;
1081 int staticPluginsFound = 0;
1082
1083#if QT_CONFIG(library)
1084 const auto qmldirPlugins = qmldir.plugins();
1085 for (const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
1086 QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name);
1087 if (!resolvedFilePath.isEmpty()) {
1088 dynamicPluginsFound++;
1089 if (!database->importDynamicPlugin(resolvedFilePath, uri, typeNamespace, vmaj, errors)) {
1090 if (errors) {
1091 // XXX TODO: should we leave the import plugin error alone?
1092 // Here, we pop it off the top and coalesce it into this error's message.
1093 // The reason is that the lower level may add url and line/column numbering information.
1094 QQmlError error;
1095 error.setDescription(
1096 QQmlImportDatabase::tr(
1097 "plugin cannot be loaded for module \"%1\": %2")
1098 .arg(uri, errors->takeFirst().description()));
1099 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1100 errors->prepend(error);
1101 }
1102 return false;
1103 }
1104 }
1105 }
1106#endif // QT_CONFIG(library)
1107
1108 if (dynamicPluginsFound < qmldirPluginCount) {
1109 // Check if the missing plugins can be resolved statically. We do this by looking at
1110 // the URIs embedded in a plugins meta data. Since those URIs can be anything from fully
1111 // versioned to unversioned, we need to compare with differnt version strings. If a module
1112 // has several plugins, they must all have the same version. Start by populating pluginPairs
1113 // with relevant plugins to cut the list short early on:
1114 const QStringList versionUris = versionUriList(uri, vmaj, vmin);
1115 QVector<StaticPluginPair> pluginPairs;
1116 if (!populatePluginPairVector(pluginPairs, uri, versionUris, qmldirFilePath, errors))
1117 return false;
1118
1119 const QString basePath = QFileInfo(qmldirPath).absoluteFilePath();
1120 for (const QString &versionUri : versionUris) {
1121 for (const StaticPluginPair &pair : qAsConst(pluginPairs)) {
1122 for (const QJsonValue &metaTagUri : pair.second) {
1123 if (versionUri == metaTagUri.toString()) {
1124 staticPluginsFound++;
1125 QObject *instance = pair.first.instance();
1126 if (!database->importStaticPlugin(instance, basePath, uri, typeNamespace, vmaj, errors)) {
1127 if (errors) {
1128 QQmlError poppedError = errors->takeFirst();
1129 QQmlError error;
1130 error.setDescription(QQmlImportDatabase::tr("static plugin for module \"%1\" with name \"%2\" cannot be loaded: %3")
1131 .arg(uri).arg(QString::fromUtf8(instance->metaObject()->className())).arg(poppedError.description()));
1132 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1133 errors->prepend(error);
1134 }
1135 return false;
1136 }
1137 break;
1138 }
1139 }
1140 }
1141 if (staticPluginsFound > 0)
1142 break;
1143 }
1144 }
1145
1146 if ((dynamicPluginsFound + staticPluginsFound) < qmldirPluginCount) {
1147 if (errors) {
1148 QQmlError error;
1149 if (qmldirPluginCount > 1 && staticPluginsFound > 0)
1150 error.setDescription(QQmlImportDatabase::tr("could not resolve all plugins for module \"%1\"").arg(uri));
1151 else
1152 error.setDescription(QQmlImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(qmldir.plugins()[dynamicPluginsFound].name));
1153 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
1154 errors->prepend(error);
1155 }
1156 return false;
1157 }
1158
1159 database->qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(qmldirFilePath);
1160 }
1161 return true;
1162}
1163
1164bool QQmlImportsPrivate::getQmldirContent(const QString &qmldirIdentifier, const QString &uri,
1165 QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors)
1166{
1167 Q_ASSERT(errors);
1168 Q_ASSERT(qmldir);
1169
1170 *qmldir = typeLoader->qmldirContent(qmldirIdentifier);
1171 if ((*qmldir).hasContent()) {
1172 // Ensure that parsing was successful
1173 if ((*qmldir).hasError()) {
1174 QUrl url = QUrl::fromLocalFile(qmldirIdentifier);
1175 const QList<QQmlError> qmldirErrors = (*qmldir).errors(uri);
1176 for (int i = 0; i < qmldirErrors.size(); ++i) {
1177 QQmlError error = qmldirErrors.at(i);
1178 error.setUrl(url);
1179 errors->append(error);
1180 }
1181 return false;
1182 }
1183 }
1184
1185 return true;
1186}
1187
1188QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database)
1189{
1190 QString dir = dir_arg;
1191 if (dir.endsWith(Slash) || dir.endsWith(Backslash))
1192 dir.chop(1);
1193
1194 QStringList paths = database->fileImportPath;
1195 if (!paths.isEmpty())
1196 std::sort(paths.begin(), paths.end(), std::greater<QString>()); // Ensure subdirs preceed their parents.
1197
1198 QString stableRelativePath = dir;
1199 for (const QString &path : qAsConst(paths)) {
1200 if (dir.startsWith(path)) {
1201 stableRelativePath = dir.mid(path.length()+1);
1202 break;
1203 }
1204 }
1205
1206 stableRelativePath.replace(Backslash, Slash);
1207
1208 // remove optional versioning in dot notation from uri
1209 int versionDot = stableRelativePath.lastIndexOf(Dot);
1210 if (versionDot >= 0) {
1211 int nextSlash = stableRelativePath.indexOf(Slash, versionDot);
1212 if (nextSlash >= 0)
1213 stableRelativePath.remove(versionDot, nextSlash - versionDot);
1214 else
1215 stableRelativePath = stableRelativePath.left(versionDot);
1216 }
1217
1218 stableRelativePath.replace(Slash, Dot);
1219
1220 return stableRelativePath;
1221}
1222
1223/*
1224Locates the qmldir file for \a uri version \a vmaj.vmin. Returns true if found,
1225and fills in outQmldirFilePath and outQmldirUrl appropriately. Otherwise returns
1226false.
1227*/
1228bool QQmlImportsPrivate::locateQmldir(const QString &uri, int vmaj, int vmin, QQmlImportDatabase *database,
1229 QString *outQmldirFilePath, QString *outQmldirPathUrl)
1230{
1231 Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries
1232
1233 // Check cache first
1234
1235 QQmlImportDatabase::QmldirCache *cacheHead = nullptr;
1236 {
1237 QQmlImportDatabase::QmldirCache **cachePtr = database->qmldirCache.value(uri);
1238 if (cachePtr) {
1239 cacheHead = *cachePtr;
1240 QQmlImportDatabase::QmldirCache *cache = cacheHead;
1241 while (cache) {
1242 if (cache->versionMajor == vmaj && cache->versionMinor == vmin) {
1243 *outQmldirFilePath = cache->qmldirFilePath;
1244 *outQmldirPathUrl = cache->qmldirPathUrl;
1245 return !cache->qmldirFilePath.isEmpty();
1246 }
1247 cache = cache->next;
1248 }
1249 }
1250 }
1251
1252 QQmlTypeLoader &typeLoader = QQmlEnginePrivate::get(database->engine)->typeLoader;
1253
1254 // Interceptor might redirect remote files to local ones.
1255 QQmlAbstractUrlInterceptor *interceptor = typeLoader.engine()->urlInterceptor();
1256 QStringList localImportPaths = database->importPathList(
1257 interceptor ? QQmlImportDatabase::LocalOrRemote : QQmlImportDatabase::Local);
1258
1259 // Search local import paths for a matching version
1260 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(uri, localImportPaths, vmaj, vmin);
1261 for (QString qmldirPath : qmlDirPaths) {
1262 if (interceptor) {
1263 qmldirPath = QQmlFile::urlToLocalFileOrQrc(
1264 interceptor->intercept(QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
1265 QQmlAbstractUrlInterceptor::QmldirFile));
1266 }
1267
1268 QString absoluteFilePath = typeLoader.absoluteFilePath(qmldirPath);
1269 if (!absoluteFilePath.isEmpty()) {
1270 QString url;
1271 const QStringRef absolutePath = absoluteFilePath.leftRef(absoluteFilePath.lastIndexOf(Slash) + 1);
1272 if (absolutePath.at(0) == Colon)
1273 url = QLatin1String("qrc") + absolutePath;
1274 else
1275 url = QUrl::fromLocalFile(absolutePath.toString()).toString();
1276
1277 QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
1278 cache->versionMajor = vmaj;
1279 cache->versionMinor = vmin;
1280 cache->qmldirFilePath = absoluteFilePath;
1281 cache->qmldirPathUrl = url;
1282 cache->next = cacheHead;
1283 database->qmldirCache.insert(uri, cache);
1284
1285 *outQmldirFilePath = absoluteFilePath;
1286 *outQmldirPathUrl = url;
1287
1288 return true;
1289 }
1290 }
1291
1292 QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
1293 cache->versionMajor = vmaj;
1294 cache->versionMinor = vmin;
1295 cache->next = cacheHead;
1296 database->qmldirCache.insert(uri, cache);
1297
1298 return false;
1299}
1300
1301bool QQmlImportsPrivate::validateQmldirVersion(const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, int vmaj, int vmin,
1302 QList<QQmlError> *errors)
1303{
1304 int lowest_min = INT_MAX;
1305 int highest_min = INT_MIN;
1306
1307 typedef QQmlDirComponents::const_iterator ConstIterator;
1308 const QQmlDirComponents &components = qmldir.components();
1309
1310 ConstIterator cend = components.constEnd();
1311 for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) {
1312 for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) {
1313 if ((cit2->typeName == cit->typeName) &&
1314 (cit2->majorVersion == cit->majorVersion) &&
1315 (cit2->minorVersion == cit->minorVersion)) {
1316 // This entry clashes with a predecessor
1317 QQmlError error;
1318 error.setDescription(QQmlImportDatabase::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1319 .arg(cit->typeName).arg(cit->majorVersion).arg(cit->minorVersion).arg(uri));
1320 errors->prepend(error);
1321 return false;
1322 }
1323 }
1324
1325 if (cit->majorVersion == vmaj) {
1326 lowest_min = qMin(lowest_min, cit->minorVersion);
1327 highest_min = qMax(highest_min, cit->minorVersion);
1328 }
1329 }
1330
1331 typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
1332 const QQmlDirScripts &scripts = qmldir.scripts();
1333
1334 SConstIterator send = scripts.constEnd();
1335 for (SConstIterator sit = scripts.constBegin(); sit != send; ++sit) {
1336 for (SConstIterator sit2 = scripts.constBegin(); sit2 != sit; ++sit2) {
1337 if ((sit2->nameSpace == sit->nameSpace) &&
1338 (sit2->majorVersion == sit->majorVersion) &&
1339 (sit2->minorVersion == sit->minorVersion)) {
1340 // This entry clashes with a predecessor
1341 QQmlError error;
1342 error.setDescription(QQmlImportDatabase::tr("\"%1\" version %2.%3 is defined more than once in module \"%4\"")
1343 .arg(sit->nameSpace).arg(sit->majorVersion).arg(sit->minorVersion).arg(uri));
1344 errors->prepend(error);
1345 return false;
1346 }
1347 }
1348
1349 if (sit->majorVersion == vmaj) {
1350 lowest_min = qMin(lowest_min, sit->minorVersion);
1351 highest_min = qMax(highest_min, sit->minorVersion);
1352 }
1353 }
1354
1355 if (lowest_min > vmin || highest_min < vmin) {
1356 QQmlError error;
1357 error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin));
1358 errors->prepend(error);
1359 return false;
1360 }
1361
1362 return true;
1363}
1364
1365QQmlImportNamespace *QQmlImportsPrivate::importNamespace(const QString &prefix) const
1366{
1367 QQmlImportNamespace *nameSpace = nullptr;
1368
1369 if (prefix.isEmpty()) {
1370 nameSpace = &unqualifiedset;
1371 } else {
1372 nameSpace = findQualifiedNamespace(prefix);
1373
1374 if (!nameSpace) {
1375 nameSpace = new QQmlImportNamespace;
1376 nameSpace->prefix = prefix;
1377 qualifiedSets.append(nameSpace);
1378 }
1379 }
1380
1381 return nameSpace;
1382}
1383
1384QQmlImportInstance *QQmlImportsPrivate::addImportToNamespace(QQmlImportNamespace *nameSpace,
1385 const QString &uri, const QString &url, int vmaj, int vmin,
1386 QV4::CompiledData::Import::ImportType type,
1387 QList<QQmlError> *errors, bool lowPrecedence)
1388{
1389 Q_ASSERT(nameSpace);
1390 Q_ASSERT(errors);
1391 Q_UNUSED(errors);
1392 Q_ASSERT(url.isEmpty() || url.endsWith(Slash));
1393
1394 QQmlImportInstance *import = new QQmlImportInstance;
1395 import->uri = uri;
1396 import->url = url;
1397 import->localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url);
1398 import->majversion = vmaj;
1399 import->minversion = vmin;
1400 import->isLibrary = (type == QV4::CompiledData::Import::ImportLibrary);
1401
1402 if (lowPrecedence)
1403 nameSpace->imports.append(import);
1404 else
1405 nameSpace->imports.prepend(import);
1406
1407 return import;
1408}
1409
1410bool QQmlImportsPrivate::addLibraryImport(const QString& uri, const QString &prefix,
1411 int vmaj, int vmin, const QString &qmldirIdentifier, const QString &qmldirUrl, bool incomplete,
1412 QQmlImportDatabase *database,
1413 QList<QQmlError> *errors)
1414{
1415 Q_ASSERT(database);
1416 Q_ASSERT(errors);
1417
1418 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1419 Q_ASSERT(nameSpace);
1420
1421 QQmlImportInstance *inserted = addImportToNamespace(nameSpace, uri, qmldirUrl, vmaj, vmin, QV4::CompiledData::Import::ImportLibrary, errors);
1422 Q_ASSERT(inserted);
1423
1424 if (!incomplete) {
1425 QQmlTypeLoaderQmldirContent qmldir;
1426
1427 if (!qmldirIdentifier.isEmpty()) {
1428 if (!getQmldirContent(qmldirIdentifier, uri, &qmldir, errors))
1429 return false;
1430
1431 if (qmldir.hasContent()) {
1432 if (!importExtension(qmldir.pluginLocation(), uri, vmaj, vmin, database, qmldir, errors))
1433 return false;
1434
1435 if (!inserted->setQmldirContent(qmldirUrl, qmldir, nameSpace, errors))
1436 return false;
1437 }
1438 }
1439
1440 // Ensure that we are actually providing something
1441 if ((vmaj < 0) || (vmin < 0) || !QQmlMetaType::isModule(uri, vmaj, vmin)) {
1442 if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) {
1443 QQmlError error;
1444 if (QQmlMetaType::isAnyModule(uri))
1445 error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri).arg(vmaj).arg(vmin));
1446 else
1447 error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri));
1448 errors->prepend(error);
1449 return false;
1450 } else if ((vmaj >= 0) && (vmin >= 0) && qmldir.hasContent()) {
1451 // Verify that the qmldir content is valid for this version
1452 if (!validateQmldirVersion(qmldir, uri, vmaj, vmin, errors))
1453 return false;
1454 }
1455 }
1456 }
1457
1458 return true;
1459}
1460
1461bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix,
1462 int vmaj, int vmin,
1463 bool isImplicitImport, bool incomplete, QQmlImportDatabase *database,
1464 QList<QQmlError> *errors)
1465{
1466 Q_ASSERT(errors);
1467
1468 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1469 Q_ASSERT(nameSpace);
1470
1471 // The uri for this import. For library imports this is the same as uri
1472 // specified by the user, but it may be different in the case of file imports.
1473 QString importUri = uri;
1474 QString qmldirUrl = resolveLocalUrl(base, importUri + (importUri.endsWith(Slash)
1475 ? String_qmldir
1476 : Slash_qmldir));
1477 if (QQmlAbstractUrlInterceptor *interceptor = typeLoader->engine()->urlInterceptor()) {
1478 qmldirUrl = interceptor->intercept(QUrl(qmldirUrl),
1479 QQmlAbstractUrlInterceptor::QmldirFile).toString();
1480 }
1481 QString qmldirIdentifier;
1482
1483 if (QQmlFile::isLocalFile(qmldirUrl)) {
1484
1485 QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
1486 Q_ASSERT(!localFileOrQrc.isEmpty());
1487
1488 const QString dir = localFileOrQrc.left(localFileOrQrc.lastIndexOf(Slash) + 1);
1489 if (!typeLoader->directoryExists(dir)) {
1490 if (!isImplicitImport) {
1491 QQmlError error;
1492 error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri));
1493 error.setUrl(QUrl(qmldirUrl));
1494 errors->prepend(error);
1495 }
1496 return false;
1497 }
1498
1499 // Transforms the (possible relative) uri into our best guess relative to the
1500 // import paths.
1501 importUri = resolvedUri(dir, database);
1502 if (importUri.endsWith(Slash))
1503 importUri.chop(1);
1504
1505 if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty())
1506 qmldirIdentifier = localFileOrQrc;
1507
1508 } else if (nameSpace->prefix.isEmpty() && !incomplete) {
1509
1510 if (!isImplicitImport) {
1511 QQmlError error;
1512 error.setDescription(QQmlImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(importUri));
1513 error.setUrl(QUrl(qmldirUrl));
1514 errors->prepend(error);
1515 }
1516
1517 return false;
1518
1519 }
1520
1521 // The url for the path containing files for this import
1522 QString url = resolveLocalUrl(base, uri);
1523 if (!url.endsWith(Slash) && !url.endsWith(Backslash))
1524 url += Slash;
1525
1526 // ### For enum support, we are now adding the implicit import always (and earlier). Bail early
1527 // if the implicit import has already been explicitly added, otherwise we can run into issues
1528 // with duplicate imports. However remember that we attempted to add this as implicit import, to
1529 // allow for the loading of internal types.
1530 if (isImplicitImport) {
1531 for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin();
1532 it != nameSpace->imports.constEnd(); ++it) {
1533 if ((*it)->url == url) {
1534 (*it)->implicitlyImported = true;
1535 return true;
1536 }
1537 }
1538 }
1539
1540 QQmlImportInstance *inserted = addImportToNamespace(nameSpace, importUri, url, vmaj, vmin, QV4::CompiledData::Import::ImportFile, errors, isImplicitImport);
1541 Q_ASSERT(inserted);
1542
1543 if (!incomplete && !qmldirIdentifier.isEmpty()) {
1544 QQmlTypeLoaderQmldirContent qmldir;
1545 if (!getQmldirContent(qmldirIdentifier, importUri, &qmldir, errors))
1546 return false;
1547
1548 if (qmldir.hasContent()) {
1549 if (!importExtension(qmldir.pluginLocation(), importUri, vmaj, vmin, database, qmldir, errors))
1550 return false;
1551
1552 if (!inserted->setQmldirContent(url, qmldir, nameSpace, errors))
1553 return false;
1554 }
1555 }
1556
1557 return true;
1558}
1559
1560bool QQmlImportsPrivate::updateQmldirContent(const QString &uri, const QString &prefix,
1561 const QString &qmldirIdentifier, const QString& qmldirUrl,
1562 QQmlImportDatabase *database, QList<QQmlError> *errors)
1563{
1564 QQmlImportNamespace *nameSpace = importNamespace(prefix);
1565 Q_ASSERT(nameSpace);
1566
1567 if (QQmlImportInstance *import = nameSpace->findImport(uri)) {
1568 QQmlTypeLoaderQmldirContent qmldir;
1569 if (!getQmldirContent(qmldirIdentifier, uri, &qmldir, errors))
1570 return false;
1571
1572 if (qmldir.hasContent()) {
1573 int vmaj = import->majversion;
1574 int vmin = import->minversion;
1575 if (!importExtension(qmldir.pluginLocation(), uri, vmaj, vmin, database, qmldir, errors))
1576 return false;
1577
1578 if (import->setQmldirContent(qmldirUrl, qmldir, nameSpace, errors)) {
1579 if (import->qmlDirComponents.isEmpty() && import->qmlDirScripts.isEmpty()) {
1580 // The implicit import qmldir can be empty, and plugins have no extra versions
1581 if (uri != QLatin1String(".") && !QQmlMetaType::isModule(uri, vmaj, vmin)) {
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 }
1590 } else if ((vmaj >= 0) && (vmin >= 0)) {
1591 // Verify that the qmldir content is valid for this version
1592 if (!validateQmldirVersion(qmldir, uri, vmaj, vmin, errors))
1593 return false;
1594 }
1595 return true;
1596 }
1597 }
1598 }
1599
1600 if (errors->isEmpty()) {
1601 QQmlError error;
1602 error.setDescription(QQmlTypeLoader::tr("Cannot update qmldir content for '%1'").arg(uri));
1603 errors->prepend(error);
1604 }
1605
1606 return false;
1607}
1608
1609/*!
1610 \internal
1611
1612 Adds an implicit "." file import. This is equivalent to calling addFileImport(), but error
1613 messages related to the path or qmldir file not existing are suppressed.
1614
1615 Additionally, this will add the import with lowest instead of highest precedence.
1616*/
1617bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb, QList<QQmlError> *errors)
1618{
1619 Q_ASSERT(errors);
1620
1621 if (qmlImportTrace())
1622 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString())
1623 << ")::addImplicitImport";
1624
1625 bool incomplete = !isLocal(baseUrl());
1626 return d->addFileImport(QLatin1String("."), QString(), -1, -1, true, incomplete, importDb, errors);
1627}
1628
1629/*!
1630 \internal
1631
1632 Adds information to \a imports such that subsequent calls to resolveType()
1633 will resolve types qualified by \a prefix by considering types found at the given \a uri.
1634
1635 The uri is either a directory (if importType is FileImport), or a URI resolved using paths
1636 added via addImportPath() (if importType is LibraryImport).
1637
1638 The \a prefix may be empty, in which case the import location is considered for
1639 unqualified types.
1640
1641 The base URL must already have been set with Import::setBaseUrl().
1642
1643 Optionally, the url the import resolved to can be returned by providing the url parameter.
1644 Not all imports will result in an output url being generated, in which case the url will
1645 be set to an empty string.
1646
1647 Returns true on success, and false on failure. In case of failure, the errors array will
1648 filled appropriately.
1649*/
1650bool QQmlImports::addFileImport(QQmlImportDatabase *importDb,
1651 const QString& uri, const QString& prefix, int vmaj, int vmin,
1652 bool incomplete, QList<QQmlError> *errors)
1653{
1654 Q_ASSERT(importDb);
1655 Q_ASSERT(errors);
1656
1657 if (qmlImportTrace())
1658 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addFileImport: "
1659 << uri << ' ' << vmaj << '.' << vmin << " as " << prefix;
1660
1661 return d->addFileImport(uri, prefix, vmaj, vmin, false, incomplete, importDb, errors);
1662}
1663
1664bool QQmlImports::addLibraryImport(QQmlImportDatabase *importDb,
1665 const QString &uri, const QString &prefix, int vmaj, int vmin,
1666 const QString &qmldirIdentifier, const QString& qmldirUrl, bool incomplete, QList<QQmlError> *errors)
1667{
1668 Q_ASSERT(importDb);
1669 Q_ASSERT(errors);
1670
1671 if (qmlImportTrace())
1672 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addLibraryImport: "
1673 << uri << ' ' << vmaj << '.' << vmin << " as " << prefix;
1674
1675 return d->addLibraryImport(uri, prefix, vmaj, vmin, qmldirIdentifier, qmldirUrl, incomplete, importDb, errors);
1676}
1677
1678bool QQmlImports::updateQmldirContent(QQmlImportDatabase *importDb,
1679 const QString &uri, const QString &prefix,
1680 const QString &qmldirIdentifier, const QString& qmldirUrl, QList<QQmlError> *errors)
1681{
1682 Q_ASSERT(importDb);
1683 Q_ASSERT(errors);
1684
1685 if (qmlImportTrace())
1686 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::updateQmldirContent: "
1687 << uri << " to " << qmldirUrl << " as " << prefix;
1688
1689 return d->updateQmldirContent(uri, prefix, qmldirIdentifier, qmldirUrl, importDb, errors);
1690}
1691
1692bool QQmlImports::locateQmldir(QQmlImportDatabase *importDb,
1693 const QString& uri, int vmaj, int vmin,
1694 QString *qmldirFilePath, QString *url)
1695{
1696 return d->locateQmldir(uri, vmaj, vmin, importDb, qmldirFilePath, url);
1697}
1698
1699bool QQmlImports::isLocal(const QString &url)
1700{
1701 return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty();
1702}
1703
1704bool QQmlImports::isLocal(const QUrl &url)
1705{
1706 return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty();
1707}
1708
1709QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file)
1710{
1711 QUrl url(QLatin1String(file.at(0) == Colon ? "qrc" : "") + file);
1712
1713 // We don't support single character schemes as those conflict with windows drive letters.
1714 if (url.scheme().length() < 2)
1715 return QUrl::fromLocalFile(file);
1716 return url;
1717}
1718
1719void QQmlImports::setDesignerSupportRequired(bool b)
1720{
1721 designerSupportRequired = b;
1722}
1723
1724
1725/*!
1726\class QQmlImportDatabase
1727\brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine.
1728\internal
1729*/
1730QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
1731: engine(e)
1732{
1733 filePluginPath << QLatin1String(".");
1734 // Search order is applicationDirPath(), qrc:/qt-project.org/imports, $QML2_IMPORT_PATH, QLibraryInfo::Qml2ImportsPath
1735
1736 QString installImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
1737 addImportPath(installImportsPath);
1738
1739 // env import paths
1740 if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty("QML2_IMPORT_PATH"))) {
1741 const QString envImportPath = qEnvironmentVariable("QML2_IMPORT_PATH");
1742#if defined(Q_OS_WIN)
1743 QLatin1Char pathSep(';');
1744#else
1745 QLatin1Char pathSep(':');
1746#endif
1747 QStringList paths = envImportPath.split(pathSep, QString::SkipEmptyParts);
1748 for (int ii = paths.count() - 1; ii >= 0; --ii)
1749 addImportPath(paths.at(ii));
1750 }
1751
1752 addImportPath(QStringLiteral("qrc:/qt-project.org/imports"));
1753 addImportPath(QCoreApplication::applicationDirPath());
1754}
1755
1756QQmlImportDatabase::~QQmlImportDatabase()
1757{
1758 clearDirCache();
1759}
1760
1761/*!
1762 \internal
1763
1764 Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
1765 The \a prefix must contain the dot.
1766
1767 \a qmldirPath is the location of the qmldir file.
1768 */
1769QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1770 const QString &qmldirPath,
1771 const QString &qmldirPluginPath,
1772 const QString &baseName, const QStringList &suffixes,
1773 const QString &prefix)
1774{
1775 QStringList searchPaths = filePluginPath;
1776 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
1777 if (!qmldirPluginPathIsRelative)
1778 searchPaths.prepend(qmldirPluginPath);
1779
1780 for (const QString &pluginPath : qAsConst(searchPaths)) {
1781 QString resolvedPath;
1782 if (pluginPath == QLatin1String(".")) {
1783 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String("."))
1784 resolvedPath = QDir::cleanPath(qmldirPath + Slash + qmldirPluginPath);
1785 else
1786 resolvedPath = qmldirPath;
1787 } else {
1788 if (QDir::isRelativePath(pluginPath))
1789 resolvedPath = QDir::cleanPath(qmldirPath + Slash + pluginPath);
1790 else
1791 resolvedPath = pluginPath;
1792 }
1793
1794 // hack for resources, should probably go away
1795 if (resolvedPath.startsWith(Colon))
1796 resolvedPath = QCoreApplication::applicationDirPath();
1797
1798 if (!resolvedPath.endsWith(Slash))
1799 resolvedPath += Slash;
1800
1801 resolvedPath += prefix + baseName;
1802 for (const QString &suffix : suffixes) {
1803 const QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
1804 if (!absolutePath.isEmpty())
1805 return absolutePath;
1806 }
1807 }
1808
1809 if (qmlImportTrace())
1810 qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName
1811 << "in" << qmldirPath;
1812
1813 return QString();
1814}
1815
1816/*!
1817 \internal
1818
1819 Returns the result of the merge of \a baseName with \a dir and the platform suffix.
1820
1821 \table
1822 \header \li Platform \li Valid suffixes
1823 \row \li Windows \li \c .dll
1824 \row \li Unix/Linux \li \c .so
1825 \row \li \macos \li \c .dylib, \c .bundle, \c .so
1826 \endtable
1827
1828 Version number on unix are ignored.
1829*/
1830QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1831 const QString &qmldirPath, const QString &qmldirPluginPath,
1832 const QString &baseName)
1833{
1834#if defined(Q_OS_WIN)
1835 static const QString prefix;
1836 static const QStringList suffixes = {
1837# ifdef QT_DEBUG
1838 QLatin1String("d.dll"), // try a qmake-style debug build first
1839# endif
1840 QLatin1String(".dll")
1841 };
1842#elif defined(Q_OS_DARWIN)
1843 static const QString prefix = QLatin1String("lib");
1844 static const QStringList suffixes = {
1845# ifdef QT_DEBUG
1846 QLatin1String("_debug.dylib"), // try a qmake-style debug build first
1847 QLatin1String(".dylib"),
1848# else
1849 QLatin1String(".dylib"),
1850 QLatin1String("_debug.dylib"), // try a qmake-style debug build after
1851# endif
1852 QLatin1String(".so"),
1853 QLatin1String(".bundle")
1854 };
1855# else // Unix
1856 static const QString prefix = QLatin1String("lib");
1857 static const QStringList suffixes = { QLatin1String(".so") };
1858#endif
1859
1860 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, suffixes, prefix);
1861}
1862
1863/*!
1864 \internal
1865*/
1866QStringList QQmlImportDatabase::pluginPathList() const
1867{
1868 return filePluginPath;
1869}
1870
1871/*!
1872 \internal
1873*/
1874void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
1875{
1876 if (qmlImportTrace())
1877 qDebug().nospace() << "QQmlImportDatabase::setPluginPathList: " << paths;
1878
1879 filePluginPath = paths;
1880}
1881
1882/*!
1883 \internal
1884*/
1885void QQmlImportDatabase::addPluginPath(const QString& path)
1886{
1887 if (qmlImportTrace())
1888 qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path;
1889
1890 QUrl url = QUrl(path);
1891 if (url.isRelative() || url.scheme() == QLatin1String("file")
1892 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1893 QDir dir = QDir(path);
1894 filePluginPath.prepend(dir.canonicalPath());
1895 } else {
1896 filePluginPath.prepend(path);
1897 }
1898}
1899
1900/*!
1901 \internal
1902*/
1903void QQmlImportDatabase::addImportPath(const QString& path)
1904{
1905 if (qmlImportTrace())
1906 qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path;
1907
1908 if (path.isEmpty())
1909 return;
1910
1911 QUrl url = QUrl(path);
1912 QString cPath;
1913
1914 if (url.scheme() == QLatin1String("file")) {
1915 cPath = QQmlFile::urlToLocalFileOrQrc(url);
1916 } else if (path.startsWith(QLatin1Char(':'))) {
1917 // qrc directory, e.g. :/foo
1918 // need to convert to a qrc url, e.g. qrc:/foo
1919 cPath = QLatin1String("qrc") + path;
1920 cPath.replace(Backslash, Slash);
1921 } else if (url.isRelative() ||
1922 (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1923 QDir dir = QDir(path);
1924 cPath = dir.canonicalPath();
1925 } else {
1926 cPath = path;
1927 cPath.replace(Backslash, Slash);
1928 }
1929
1930 if (!cPath.isEmpty()
1931 && !fileImportPath.contains(cPath))
1932 fileImportPath.prepend(cPath);
1933}
1934
1935/*!
1936 \internal
1937*/
1938QStringList QQmlImportDatabase::importPathList(PathType type) const
1939{
1940 if (type == LocalOrRemote)
1941 return fileImportPath;
1942
1943 QStringList list;
1944 for (const QString &path : fileImportPath) {
1945 bool localPath = isPathAbsolute(path) || QQmlFile::isLocalFile(path);
1946 if (localPath == (type == Local))
1947 list.append(path);
1948 }
1949
1950 return list;
1951}
1952
1953/*!
1954 \internal
1955*/
1956void QQmlImportDatabase::setImportPathList(const QStringList &paths)
1957{
1958 if (qmlImportTrace())
1959 qDebug().nospace() << "QQmlImportDatabase::setImportPathList: " << paths;
1960
1961 fileImportPath.clear();
1962 for (auto it = paths.crbegin(); it != paths.crend(); ++it)
1963 addImportPath(*it);
1964
1965 // Our existing cached paths may have been invalidated
1966 clearDirCache();
1967}
1968
1969/*!
1970 \internal
1971*/
1972bool QQmlImportDatabase::registerPluginTypes(QObject *instance, const QString &basePath,
1973 const QString &uri, const QString &typeNamespace, int vmaj, QList<QQmlError> *errors)
1974{
1975 if (qmlImportTrace())
1976 qDebug().nospace() << "QQmlImportDatabase::registerPluginTypes: " << uri << " from " << basePath;
1977 return QQmlMetaType::registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors);
1978}
1979
1980/*!
1981 \internal
1982*/
1983bool QQmlImportDatabase::importStaticPlugin(QObject *instance, const QString &basePath,
1984 const QString &uri, const QString &typeNamespace, int vmaj, QList<QQmlError> *errors)
1985{
1986 // Dynamic plugins are differentiated by their filepath. For static plugins we
1987 // don't have that information so we use their address as key instead.
1988 const QString uniquePluginID = QString::asprintf("%p", instance);
1989 {
1990 StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
1991 QMutexLocker lock(&plugins->mutex);
1992
1993 // Plugin types are global across all engines and should only be
1994 // registered once. But each engine still needs to be initialized.
1995 bool typesRegistered = plugins->contains(uniquePluginID);
1996
1997 if (typesRegistered) {
1998 Q_ASSERT_X(plugins->value(uniquePluginID).uri == uri,
1999 "QQmlImportDatabase::importStaticPlugin",
2000 "Internal error: Static plugin imported previously with different uri");
2001 } else {
2002 RegisteredPlugin plugin;
2003 plugin.uri = uri;
2004 plugin.loader = nullptr;
2005 plugins->insert(uniquePluginID, plugin);
2006
2007 if (!registerPluginTypes(instance, basePath, uri, typeNamespace, vmaj, errors))
2008 return false;
2009 }
2010
2011 // Release the lock on plugins early as we're done with the global part. Releasing the lock
2012 // also allows other QML loader threads to acquire the lock while this thread is blocking
2013 // in the initializeEngine call to the gui thread (which in turn may be busy waiting for
2014 // other QML loader threads and thus not process the initializeEngine call).
2015 }
2016
2017 // The plugin's per-engine initialization does not need lock protection, as this function is
2018 // only called from the engine specific loader thread and importDynamicPlugin as well as
2019 // importStaticPlugin are the only places of access.
2020 if (!initializedPlugins.contains(uniquePluginID)) {
2021 initializedPlugins.insert(uniquePluginID);
2022
2023 if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) {
2024 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
2025 ep->typeLoader.initializeEngine(eiface, uri.toUtf8().constData());
2026 }
2027 }
2028
2029 return true;
2030}
2031
2032#if QT_CONFIG(library)
2033/*!
2034 \internal
2035*/
2036bool QQmlImportDatabase::importDynamicPlugin(const QString &filePath, const QString &uri,
2037 const QString &typeNamespace, int vmaj, QList<QQmlError> *errors)
2038{
2039 QFileInfo fileInfo(filePath);
2040 const QString absoluteFilePath = fileInfo.absoluteFilePath();
2041
2042 QObject *instance = nullptr;
2043 bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
2044 {
2045 StringRegisteredPluginMap *plugins = qmlEnginePluginsWithRegisteredTypes();
2046 QMutexLocker lock(&plugins->mutex);
2047 bool typesRegistered = plugins->contains(absoluteFilePath);
2048
2049 if (typesRegistered) {
2050 Q_ASSERT_X(plugins->value(absoluteFilePath).uri == uri,
2051 "QQmlImportDatabase::importDynamicPlugin",
2052 "Internal error: Plugin imported previously with different uri");
2053 }
2054
2055 if (!engineInitialized || !typesRegistered) {
2056 if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
2057 if (errors) {
2058 QQmlError error;
2059 error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
2060 errors->prepend(error);
2061 }
2062 return false;
2063 }
2064
2065 QPluginLoader* loader = nullptr;
2066 if (!typesRegistered) {
2067 loader = new QPluginLoader(absoluteFilePath);
2068
2069 if (!loader->load()) {
2070 if (errors) {
2071 QQmlError error;
2072 error.setDescription(loader->errorString());
2073 errors->prepend(error);
2074 }
2075 delete loader;
2076 return false;
2077 }
2078 } else {
2079 loader = plugins->value(absoluteFilePath).loader;
2080 }
2081
2082 instance = loader->instance();
2083
2084 if (!typesRegistered) {
2085 RegisteredPlugin plugin;
2086 plugin.uri = uri;
2087 plugin.loader = loader;
2088 plugins->insert(absoluteFilePath, plugin);
2089
2090 // Continue with shared code path for dynamic and static plugins:
2091 if (!registerPluginTypes(instance, fileInfo.absolutePath(), uri, typeNamespace, vmaj, errors))
2092 return false;
2093 }
2094 }
2095
2096 // Release the lock on plugins early as we're done with the global part. Releasing the lock
2097 // also allows other QML loader threads to acquire the lock while this thread is blocking
2098 // in the initializeEngine call to the gui thread (which in turn may be busy waiting for
2099 // other QML loader threads and thus not process the initializeEngine call).
2100 }
2101
2102
2103 if (!engineInitialized) {
2104 // The plugin's per-engine initialization does not need lock protection, as this function is
2105 // only called from the engine specific loader thread and importDynamicPlugin as well as
2106 // importStaticPlugin are the only places of access.
2107 initializedPlugins.insert(absoluteFilePath);
2108
2109 if (QQmlExtensionInterface *eiface = qobject_cast<QQmlExtensionInterface *>(instance)) {
2110 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
2111 ep->typeLoader.initializeEngine(eiface, uri.toUtf8().constData());
2112 }
2113 }
2114
2115 return true;
2116}
2117
2118#endif // QT_CONFIG(library)
2119
2120void QQmlImportDatabase::clearDirCache()
2121{
2122 QStringHash<QmldirCache *>::ConstIterator itr = qmldirCache.constBegin();
2123 while (itr != qmldirCache.constEnd()) {
2124 QmldirCache *cache = *itr;
2125 do {
2126 QmldirCache *nextCache = cache->next;
2127 delete cache;
2128 cache = nextCache;
2129 } while (cache);
2130
2131 ++itr;
2132 }
2133 qmldirCache.clear();
2134}
2135
2136QT_END_NAMESPACE
2137