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