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