Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlpluginimporter.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:execute-external-code
4
6#include "qqmlimport_p.h"
7
8#include <private/qqmlextensionplugin_p.h>
9#include <private/qqmltypeloader_p.h>
10#include <private/qqmlglobal_p.h>
11
12#include <QtCore/qobject.h>
13#include <QtCore/qpluginloader.h>
14#include <QtCore/qdir.h>
15#include <QtCore/qloggingcategory.h>
16#include <QtCore/qjsonarray.h>
17
18#include <unordered_map>
19
20QT_BEGIN_NAMESPACE
21
22struct QmlPlugin
23{
24 using Loader = std::unique_ptr<QPluginLoader>;
25 QPluginLoader *loader() const
26 {
27 if (auto loader = std::get_if<Loader>(&data))
28 return loader->get();
29 return nullptr;
30 }
31
32 bool hasInstanceOrLoader() const
33 {
34 if (auto instance = std::get_if<QQmlExtensionPlugin *>(&data))
35 return *instance;
36 return bool(std::get<Loader>(data));
37 }
38
39 QString unloadOrErrorMessage() const
40 {
41 if (auto instance = std::get_if<QQmlExtensionPlugin *>(&data)) {
42 if (*instance)
43 (*instance)->unregisterTypes();
44 return {};
45 }
46 const Loader &loader = std::get<Loader>(data);
47 if (!loader)
48 return {};
49
50#if QT_CONFIG(library)
51 if (auto extensionPlugin = qobject_cast<QQmlExtensionPlugin *>(loader->instance()))
52 extensionPlugin->unregisterTypes();
53 if (!loader->unload())
54 return loader->errorString();
55#endif
56 return {};
57 }
58 std::variant<Loader, QQmlExtensionPlugin *> data;
59};
60
62{
64public:
65 PluginMap() = default;
66 ~PluginMap() = default;
67
68 // This is a std::unordered_map because QHash cannot handle move-only types.
70
71private:
72 QBasicMutex mutex;
73 Container plugins;
74 friend class PluginMapPtr;
75};
76
78{
80public:
82 ~PluginMapPtr() = default;
83
84 PluginMap::Container &operator*() { return map->plugins; }
85 const PluginMap::Container &operator*() const { return map->plugins; }
86
87 PluginMap::Container *operator->() { return &map->plugins; }
88 const PluginMap::Container *operator->() const { return &map->plugins; }
89
90private:
91 PluginMap *map;
92 QMutexLocker<QBasicMutex> locker;
93};
94
95Q_GLOBAL_STATIC(PluginMap, qmlPluginsById); // stores the uri and the PluginLoaders
96
102
108
109/*
110 * This function verifies if staticPlugin is valid QML plugin by
111 * verifying the IID member of Metadata and
112 * checks whether the plugin instance has the correct parent plugin extension class.
113 * Checks the presence of URI member of Metadata and returns QJsonArray,
114 * which in majority cases contains only 1 element.
115 */
116static QJsonArray tryExtractQmlPluginURIs(const QStaticPlugin &plugin)
117{
118 const auto isQmlPlugin = [](const QStaticPlugin &plugin, auto &&pluginMetadata) -> bool {
119 const QString iid = pluginMetadata.value(QLatin1String("IID")).toString();
120 const bool isQmlExtensionIID = iid == QLatin1String(QQmlEngineExtensionInterface_iid)
121 || iid == QLatin1String(QQmlExtensionInterface_iid)
122 || iid == QLatin1String(QQmlExtensionInterface_iid_old);
123 if (Q_UNLIKELY(iid == QLatin1String(QQmlExtensionInterface_iid_old))) {
124 qWarning() << QQmlImports::tr(
125 "Found plugin with old IID, this will be unsupported in upcoming Qt "
126 "releases:")
127 << pluginMetadata;
128 }
129 if (!isQmlExtensionIID) {
130 return false;
131 }
132
133 // plugin.instance must not be called earlier to avoid instantiating
134 // non-QML plugins, potentially from an unexpected thread(typeloader vs
135 // main thread)
136 const auto *pluginInstance = plugin.instance();
137 return qobject_cast<const QQmlEngineExtensionPlugin *>(pluginInstance)
138 || qobject_cast<const QQmlExtensionPlugin *>(pluginInstance);
139 };
140
141 const auto &pluginMetadata = plugin.metaData();
142 if (!isQmlPlugin(plugin, pluginMetadata)) {
143 return {};
144 }
145
146 const QJsonArray metadataUriList = pluginMetadata.value(QStringLiteral("uri")).toArray();
147 if (metadataUriList.isEmpty()) {
148 qWarning() << QQmlImports::tr("qml static plugin with name \"%2\" has no metadata URI")
149 .arg(plugin.instance()->metaObject()->className())
150 << pluginMetadata;
151 return {};
152 }
153 return metadataUriList;
154}
155
157{
158 QList<StaticPluginMapping> qmlPlugins;
159 const auto staticPlugins = QPluginLoader::staticPlugins();
160 qmlPlugins.reserve(staticPlugins.size());
161
162 for (const auto &plugin : staticPlugins) {
163 // Filter out static plugins which are not Qml plugins
164 const QJsonArray pluginURIs = tryExtractQmlPluginURIs(plugin);
165 for (const QJsonValueConstRef &pluginURI : pluginURIs)
166 qmlPlugins.append({ plugin, pluginURI.toString() });
167 }
168 return qmlPlugins;
169}
170
171/*
172 Returns the list of possible versioned URI combinations. For example, if \a uri is
173 (id = QtQml.Models, (vmaj = 2, vmin = 0)), this method returns the following:
174 [QtQml.Models.2.0, QtQml.2.0.Models, QtQml.Models.2, QtQml.2.Models, QtQml.Models]
175 */
177{
178 QStringList result;
179 for (int mode = QQmlImports::FullyVersioned; mode <= QQmlImports::Unversioned; ++mode) {
180 int index = uri.id.size();
181 do {
182 QString versionUri = uri.id;
183 versionUri.insert(
184 index,
185 QQmlImports::versionString(uri.version, QQmlImports::ImportVersion(mode)));
186 result += versionUri;
187
188 index = uri.id.lastIndexOf(u'.', index - 1);
189 } while (index > 0 && mode != QQmlImports::Unversioned);
190 }
191 return result;
192}
193
194/*
195 Get all static plugins that are QML plugins and has a meta data URI that matches with one of
196 \a versionUris, which is a list of all possible versioned URI combinations - see
197 versionUriList() above.
198 */
200{
201 static const auto qmlPlugins = staticQmlPlugins();
202
203 // Since plugin metadata URIs can be anything from
204 // fully versioned to unversioned, we need to compare with differnt version strings.
205 // If a module has several plugins, they must all have the same version. Start by
206 // populating pluginPairs with relevant plugins to cut the list short early on:
207 const QStringList versionedURIs = versionUriList(uri);
208 QList<StaticPluginMapping> matches;
209 std::copy_if(qmlPlugins.begin(), qmlPlugins.end(), std::back_inserter(matches),
210 [&](const auto &pluginMapping) {
211 return versionedURIs.contains(pluginMapping.metadataURI);
212 });
213 return matches;
214}
215
216static bool unloadPlugin(const std::pair<const QString, QmlPlugin> &plugin)
217{
218 const QString errorMessage = plugin.second.unloadOrErrorMessage();
219 if (errorMessage.isEmpty())
220 return true;
221
222 qWarning("Unloading %s failed: %s", qPrintable(plugin.first), qPrintable(errorMessage));
223 return false;
224}
225
226/*!
227 \internal
228*/
229static QTypeRevision lockModule(const QString &uri, const QString &typeNamespace,
230 QTypeRevision version, QList<QQmlError> *errors)
231{
232 if (!version.hasMajorVersion()) {
233 version = QQmlMetaType::latestModuleVersion(uri);
234 if (!version.isValid())
235 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
236 }
237 if (version.hasMajorVersion() && !typeNamespace.isEmpty()
238 && !QQmlMetaType::protectModule(uri, version, true)) {
239 // Not being able to protect the module means there are not types registered for it,
240 // means the plugin we loaded didn't provide any, means we didn't find the module.
241 // We output the generic error message as depending on the load order of imports we may
242 // hit this path or another one that only figures "plugin is already loaded but module
243 // unavailable" and doesn't try to protect it anymore.
244 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
245 return QTypeRevision();
246 }
247
248 return version;
249}
250
251
253{
254 PluginMapPtr plugins(qmlPluginsById());
255 for (const auto &plugin : std::as_const(*plugins))
256 unloadPlugin(plugin);
257 plugins->clear();
258}
259
260bool QQmlPluginImporter::removePlugin(const QString &pluginId)
261{
262 PluginMapPtr plugins(qmlPluginsById());
263
264 auto it = plugins->find(pluginId);
265 if (it == plugins->end())
266 return false;
267
268 const bool success = unloadPlugin(*it);
269
270 plugins->erase(it);
271 return success;
272}
273
274QStringList QQmlPluginImporter::plugins()
275{
276 PluginMapPtr plugins(qmlPluginsById());
277 QStringList results;
278 for (auto it = plugins->cbegin(), end = plugins->cend(); it != end; ++it) {
279 if (it->second.hasInstanceOrLoader())
280 results.append(it->first);
281 }
282 return results;
283}
284
285QString QQmlPluginImporter::truncateToDirectory(const QString &qmldirFilePath)
286{
287 const int slash = qmldirFilePath.lastIndexOf(u'/');
288 return slash > 0 ? qmldirFilePath.left(slash) : qmldirFilePath;
289}
290
291void QQmlPluginImporter::finalizePlugin(QObject *instance, const QString &pluginId) {
292 // The plugin's per-engine initialization does not need lock protection, as this function is
293 // only called from the engine specific loader thread and importDynamicPlugin as well as
294 // importStaticPlugin are the only places of access.
295
296 typeLoader->setPluginInitialized(pluginId);
297 if (auto *extensionIface = qobject_cast<QQmlExtensionInterface *>(instance))
298 typeLoader->initializeEngine(extensionIface, uri.toUtf8().constData());
299 else if (auto *engineIface = qobject_cast<QQmlEngineExtensionInterface *>(instance))
300 typeLoader->initializeEngine(engineIface, uri.toUtf8().constData());
301}
302
303QTypeRevision QQmlPluginImporter::importStaticPlugin(QObject *instance, const QString &pluginId) {
304 // Dynamic plugins are differentiated by their filepath. For static plugins we
305 // don't have that information so we use their address as key instead.
306 QTypeRevision importVersion = version;
307 {
308 PluginMapPtr plugins(qmlPluginsById());
309
310 // Plugin types are global across all engines and should only be
311 // registered once. But each engine still needs to be initialized.
312 bool typesRegistered = plugins->find(pluginId) != plugins->end();
313
314 if (!typesRegistered) {
315 plugins->insert(std::make_pair(
316 pluginId, QmlPlugin{ qobject_cast<QQmlExtensionPlugin *>(instance) }));
317 if (QQmlMetaType::registerPluginTypes(
318 instance, QFileInfo(qmldirPath).absoluteFilePath(), uri,
319 qmldir->typeNamespace(), importVersion, errors)
320 == QQmlMetaType::RegistrationResult::Failure) {
321 return QTypeRevision();
322 }
323
324 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
325 if (!importVersion.isValid())
326 return QTypeRevision();
327 }
328
329 // Release the lock on plugins early as we're done with the global part. Releasing the lock
330 // also allows other QML loader threads to acquire the lock while this thread is blocking
331 // in the initializeEngine call to the gui thread (which in turn may be busy waiting for
332 // other QML loader threads and thus not process the initializeEngine call).
333 }
334
335 if (!typeLoader->isPluginInitialized(pluginId))
336 finalizePlugin(instance, pluginId);
337
338 return QQmlImports::validVersion(importVersion);
339}
340
341QTypeRevision QQmlPluginImporter::importDynamicPlugin(
342 const QString &filePath, const QString &pluginId, bool optional)
343{
344 QObject *instance = nullptr;
345 QTypeRevision importVersion = version;
346
347 const bool engineInitialized = typeLoader->isPluginInitialized(pluginId);
348 {
349 PluginMapPtr plugins(qmlPluginsById());
350 const auto plugin = plugins->find(pluginId);
351 bool typesRegistered = plugin != plugins->end();
352
353 if (!engineInitialized || !typesRegistered) {
354 const QFileInfo fileInfo(filePath);
355 if (!typesRegistered && optional) {
356 switch (QQmlMetaType::registerPluginTypes(
357 nullptr, fileInfo.absolutePath(), uri, qmldir->typeNamespace(),
358 importVersion, errors)) {
359 case QQmlMetaType::RegistrationResult::NoRegistrationFunction:
360 // try again with plugin
361 break;
362 case QQmlMetaType::RegistrationResult::Success:
363 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
364 if (!importVersion.isValid())
365 return QTypeRevision();
366 qCDebug(lcQmlImport) << "module" << uri
367 << "provided by linked library" << qmldir->linkTarget()
368 << "; optional plugin not loaded";
369 // instance and loader intentionally left at nullptr
370 plugins->insert(std::make_pair(pluginId, QmlPlugin()));
371 // Not calling initializeEngine with null instance
372 typeLoader->setPluginInitialized(pluginId);
373 return importVersion;
374 case QQmlMetaType::RegistrationResult::Failure:
375 return QTypeRevision();
376 }
377 }
378
379#if QT_CONFIG(library)
380 if (!typesRegistered) {
381
382 // Check original filePath. If that one is empty, not being able
383 // to load the plugin is not an error. We were just checking if
384 // the types are already available. absoluteFilePath can still be
385 // empty if filePath is not.
386 if (filePath.isEmpty())
387 return QTypeRevision();
388
389 QmlPlugin plugin;
390 plugin.data = std::make_unique<QPluginLoader>(fileInfo.absoluteFilePath());
391 if (!plugin.loader()->load()) {
392 if (errors) {
393 QQmlError error;
394 error.setDescription(plugin.loader()->errorString());
395 errors->prepend(error);
396 }
397 return QTypeRevision();
398 }
399
400 qCDebug(lcQmlImport) << "module" << uri
401 << "provided by plugin" << fileInfo.absoluteFilePath();
402
403 instance = plugin.loader()->instance();
404 plugins->insert(std::make_pair(pluginId, std::move(plugin)));
405
406 // Continue with shared code path for dynamic and static plugins:
407 if (QQmlMetaType::registerPluginTypes(
408 instance, fileInfo.absolutePath(), uri, qmldir->typeNamespace(),
409 importVersion, errors)
410 == QQmlMetaType::RegistrationResult::Failure) {
411 return QTypeRevision();
412 }
413
414 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
415 if (!importVersion.isValid())
416 return QTypeRevision();
417 } else {
418 Q_ASSERT(plugin != plugins->end());
419 if (const auto &loader = plugin->second.loader()) {
420 instance = loader->instance();
421 } else if (!optional) {
422 // If the plugin is not optional, we absolutely need to have a loader.
423 // Not having a loader here can mean that the plugin was loaded statically
424 // before. Return an invalid result to have the caller try that option.
425 return QTypeRevision();
426 }
427 }
428#else
429 // Here plugin is not optional and NOT QT_CONFIG(library)
430 // Cannot finalize such plugin and return valid, because no types are registered.
431 // Just return invalid.
432 if (!optional)
433 return QTypeRevision();
434#endif // QT_CONFIG(library)
435 }
436
437 // Release the lock on plugins early as we're done with the global part. Releasing the lock
438 // also allows other QML loader threads to acquire the lock while this thread is blocking
439 // in the initializeEngine call to the gui thread (which in turn may be busy waiting for
440 // other QML loader threads and thus not process the initializeEngine call).
441 }
442
443 if (!engineInitialized)
444 finalizePlugin(instance, pluginId);
445
446 return QQmlImports::validVersion(importVersion);
447}
448
449/*!
450 \internal
451
452 Searches for a plugin called \a baseName in \a qmldirPluginPath, taking the
453 path of the qmldir file itself, and the plugin paths of the QQmlTypeLoader
454 into account.
455
456 The baseName is amended with a platform-dependent prefix and suffix to
457 construct the final plugin file name:
458
459 \table
460 \header \li Platform \li Prefix \li Valid suffixes
461 \row \li Windows \li \li \c .dll, \c .d.dll
462 \row \li Unix/Linux \li lib \li \c .so
463 \row \li \macos \li lib \li \c .dylib, \c _debug.dylib \c .bundle, \c .so
464 \row \li Android \li lib \li \c .so, \c _<ABI>.so
465 \endtable
466
467 If the \a qmldirPluginPath is absolute, it is searched first. Then each of the
468 filePluginPath entries in the QQmlTypeLoader is checked in turn. If the
469 entry is relative, it is resolved on top of the path of the qmldir file,
470 otherwise it is taken verbatim. If a "." is found in the filePluginPath, and
471 \a qmldirPluginPath is relative, then \a qmldirPluginPath is used in its
472 place.
473
474 TODO: Document the android special casing.
475
476 TODO: The above paragraph, as well as the code implementing it makes very
477 little sense and is mostly here for backwards compatibility.
478 */
479QString QQmlPluginImporter::resolvePlugin(const QString &qmldirPluginPath, const QString &baseName)
480{
481#if defined(Q_OS_WIN)
482 static const QString prefix;
483 static const QStringList suffixes = {
484 # ifdef QT_DEBUG
485 QLatin1String("d.dll"), // try a qmake-style debug build first
486 QLatin1String(".dll")
487 #else
488 QLatin1String(".dll"),
489 QLatin1String("d.dll") // try a qmake-style debug build after
490 # endif
491 };
492#elif defined(Q_OS_DARWIN)
493 static const QString prefix = QLatin1String("lib");
494 static const QStringList suffixes = {
495 # ifdef QT_DEBUG
496 QLatin1String("_debug.dylib"), // try a qmake-style debug build first
497 QLatin1String(".dylib"),
498 # else
499 QLatin1String(".dylib"),
500 QLatin1String("_debug.dylib"), // try a qmake-style debug build after
501 # endif
502 QLatin1String(".so"),
503 QLatin1String(".bundle")
504 };
505#else // Unix
506 static const QString prefix = QLatin1String("lib");
507 static const QStringList suffixes = {
508 # if defined(Q_OS_ANDROID)
509 QStringLiteral(LIBS_SUFFIX),
510 # endif
511 QLatin1String(".so")
512 };
513#endif
514
515 QStringList searchPaths = typeLoader->pluginPathList();
516 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
517 if (!qmldirPluginPathIsRelative)
518 searchPaths.prepend(qmldirPluginPath);
519
520 for (const QString &pluginPath : std::as_const(searchPaths)) {
521 QString resolvedBasePath;
522 if (pluginPath == QLatin1String(".")) {
523 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty()
524 && qmldirPluginPath != QLatin1String(".")) {
525 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + qmldirPluginPath);
526 } else {
527 resolvedBasePath = qmldirPath;
528 }
529 } else {
530 if (QDir::isRelativePath(pluginPath))
531 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + pluginPath);
532 else
533 resolvedBasePath = pluginPath;
534 }
535
536 // hack for resources, should probably go away
537 if (resolvedBasePath.startsWith(u':'))
538 resolvedBasePath = QCoreApplication::applicationDirPath();
539
540 if (!resolvedBasePath.endsWith(u'/'))
541 resolvedBasePath += u'/';
542
543 QString resolvedPath = resolvedBasePath + prefix + baseName;
544 for (const QString &suffix : suffixes) {
545 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
546 if (!absolutePath.isEmpty())
547 return absolutePath;
548 }
549
550#if defined(Q_OS_ANDROID)
551 if (qmldirPath.size() > 25 && qmldirPath.at(0) == QLatin1Char(':')
552 && qmldirPath.at(1) == QLatin1Char('/')
553 && qmldirPath.startsWith(QStringLiteral(":/android_rcc_bundle/qml/"),
554 Qt::CaseInsensitive)) {
555 QString pluginName = qmldirPath.mid(21) + u'/' + baseName;
556 pluginName.replace(QLatin1Char('/'), QLatin1Char('_'));
557 QString bundledPath = resolvedBasePath + QLatin1String("lib") + pluginName;
558 for (const QString &suffix : suffixes) {
559 const QString absolutePath = typeLoader->absoluteFilePath(bundledPath + suffix);
560 if (!absolutePath.isEmpty()) {
561 qWarning("The implicit resolving of Qml plugin locations using the URI "
562 "embedded in the filename has been deprecated. Please use the "
563 "modern CMake API to create QML modules or set the name of "
564 "QML plugin in qmldir file, that matches the name of plugin "
565 "on file system. The correct plugin name is '%s'.",
566 qPrintable(pluginName));
567 return absolutePath;
568 }
569 }
570 }
571#endif
572 }
573
574 qCDebug(lcQmlImport) << "resolvePlugin" << "Could not resolve dynamic plugin with base name"
575 << baseName << "in" << qmldirPath
576 << " file does not exist";
577
578 return QString();
579}
580
581QTypeRevision QQmlPluginImporter::importPlugins() {
582 const auto qmldirPlugins = qmldir->plugins();
583 const int qmldirPluginCount = qmldirPlugins.size();
584 QTypeRevision importVersion = version;
585
586 // If the path contains a version marker or if we have more than one plugin,
587 // we need to use paths. In that case we cannot fall back to other instances
588 // of the same module if a qmldir is rejected. However, as we don't generate
589 // such modules, it shouldn't be a problem.
590 const bool canUseUris = qmldirPluginCount == 1
591 && qmldirPath.endsWith(u'/' + QString(uri).replace(u'.', u'/'));
592 const QString moduleId = canUseUris ? uri : qmldir->qmldirLocation();
593
594 if (typeLoader->isModulePluginProcessingDone(moduleId)) {
595 return QQmlImports::validVersion(importVersion);
596 }
597
598 // First search for listed qmldir plugins dynamically. If we cannot resolve them all, we
599 // continue searching static plugins that has correct metadata uri. Note that since we
600 // only know the uri for a static plugin, and not the filename, we cannot know which
601 // static plugin belongs to which listed plugin inside qmldir. And for this reason,
602 // mixing dynamic and static plugins inside a single module is not recommended.
603
604 int dynamicPluginsFound = 0;
605 int staticPluginsLoaded = 0;
606
607 for (const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
608 const QString resolvedFilePath = resolvePlugin(plugin.path, plugin.name);
609
610 if (!canUseUris && resolvedFilePath.isEmpty())
611 continue;
612
613 importVersion = importDynamicPlugin(
614 resolvedFilePath, canUseUris ? uri : QFileInfo(resolvedFilePath).absoluteFilePath(),
615 plugin.optional);
616 if (importVersion.isValid())
617 ++dynamicPluginsFound;
618 else if (!resolvedFilePath.isEmpty())
619 return QTypeRevision();
620 }
621
622 if (dynamicPluginsFound < qmldirPluginCount) {
623 // Check if the missing plugins can be resolved statically. We do this by looking at
624 // the URIs embedded in a plugins meta data.
625 const auto pluginPairs = staticQmlPluginsMatchingURI({ uri, importVersion });
626
627 for (const auto &pluginWithURI : std::as_const(pluginPairs)) {
628 QObject *instance = pluginWithURI.plugin.instance();
629 importVersion = importStaticPlugin(
630 instance, canUseUris ? uri : QString::asprintf("%p", instance));
631 if (!importVersion.isValid()) {
632 continue;
633 }
634 staticPluginsLoaded++;
635 qCDebug(lcQmlImport) << "importExtension" << "loaded static plugin "
636 << pluginWithURI.metadataURI;
637 }
638 if (!pluginPairs.empty() && staticPluginsLoaded == 0) {
639 // found matched plugins, but failed to load, early exit to preserve the error
640 return QTypeRevision();
641 }
642 }
643
644 if ((dynamicPluginsFound + staticPluginsLoaded) < qmldirPluginCount) {
645 if (errors) {
646 QQmlError error;
647 if (qmldirPluginCount > 1 && staticPluginsLoaded > 0) {
648 error.setDescription(
649 QQmlImports::tr("could not resolve all plugins for module \"%1\"")
650 .arg(uri));
651 } else {
652 error.setDescription(QQmlImports::tr("module \"%1\" plugin \"%2\" not found")
653 .arg(uri, qmldirPlugins[dynamicPluginsFound].name));
654 }
655 error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation()));
656 errors->prepend(error);
657 }
658 return QTypeRevision();
659 }
660
661 typeLoader->setModulePluginProcessingDone(moduleId);
662
663 return QQmlImports::validVersion(importVersion);
664}
665
666QT_END_NAMESPACE
const PluginMap::Container & operator*() const
PluginMap::Container * operator->()
const PluginMap::Container * operator->() const
PluginMap::Container & operator*()
~PluginMap()=default
void qmlClearEnginePlugins()
static QList< StaticPluginMapping > staticQmlPlugins()
static QStringList versionUriList(const VersionedURI &uri)
static QTypeRevision lockModule(const QString &uri, const QString &typeNamespace, QTypeRevision version, QList< QQmlError > *errors)
static QJsonArray tryExtractQmlPluginURIs(const QStaticPlugin &plugin)
static QList< StaticPluginMapping > staticQmlPluginsMatchingURI(const VersionedURI &uri)
Q_GLOBAL_STATIC(PluginMap, qmlPluginsById)
static bool unloadPlugin(const std::pair< const QString, QmlPlugin > &plugin)
QTypeRevision version