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 for (const QJsonValueConstRef &pluginURI : tryExtractQmlPluginURIs(plugin)) {
165 qmlPlugins.append({ plugin, pluginURI.toString() });
166 }
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 // instance and loader intentionally left at nullptr
367 plugins->insert(std::make_pair(pluginId, QmlPlugin()));
368 // Not calling initializeEngine with null instance
369 typeLoader->setPluginInitialized(pluginId);
370 return importVersion;
371 case QQmlMetaType::RegistrationResult::Failure:
372 return QTypeRevision();
373 }
374 }
375
376#if QT_CONFIG(library)
377 if (!typesRegistered) {
378
379 // Check original filePath. If that one is empty, not being able
380 // to load the plugin is not an error. We were just checking if
381 // the types are already available. absoluteFilePath can still be
382 // empty if filePath is not.
383 if (filePath.isEmpty())
384 return QTypeRevision();
385
386 QmlPlugin plugin;
387 plugin.data = std::make_unique<QPluginLoader>(fileInfo.absoluteFilePath());
388 if (!plugin.loader()->load()) {
389 if (errors) {
390 QQmlError error;
391 error.setDescription(plugin.loader()->errorString());
392 errors->prepend(error);
393 }
394 return QTypeRevision();
395 }
396
397 instance = plugin.loader()->instance();
398 plugins->insert(std::make_pair(pluginId, std::move(plugin)));
399
400 // Continue with shared code path for dynamic and static plugins:
401 if (QQmlMetaType::registerPluginTypes(
402 instance, fileInfo.absolutePath(), uri, qmldir->typeNamespace(),
403 importVersion, errors)
404 == QQmlMetaType::RegistrationResult::Failure) {
405 return QTypeRevision();
406 }
407
408 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
409 if (!importVersion.isValid())
410 return QTypeRevision();
411 } else {
412 Q_ASSERT(plugin != plugins->end());
413 if (const auto &loader = plugin->second.loader()) {
414 instance = loader->instance();
415 } else if (!optional) {
416 // If the plugin is not optional, we absolutely need to have a loader.
417 // Not having a loader here can mean that the plugin was loaded statically
418 // before. Return an invalid result to have the caller try that option.
419 return QTypeRevision();
420 }
421 }
422#else
423 // Here plugin is not optional and NOT QT_CONFIG(library)
424 // Cannot finalize such plugin and return valid, because no types are registered.
425 // Just return invalid.
426 if (!optional)
427 return QTypeRevision();
428#endif // QT_CONFIG(library)
429 }
430
431 // Release the lock on plugins early as we're done with the global part. Releasing the lock
432 // also allows other QML loader threads to acquire the lock while this thread is blocking
433 // in the initializeEngine call to the gui thread (which in turn may be busy waiting for
434 // other QML loader threads and thus not process the initializeEngine call).
435 }
436
437 if (!engineInitialized)
438 finalizePlugin(instance, pluginId);
439
440 return QQmlImports::validVersion(importVersion);
441}
442
443/*!
444 \internal
445
446 Searches for a plugin called \a baseName in \a qmldirPluginPath, taking the
447 path of the qmldir file itself, and the plugin paths of the QQmlTypeLoader
448 into account.
449
450 The baseName is amended with a platform-dependent prefix and suffix to
451 construct the final plugin file name:
452
453 \table
454 \header \li Platform \li Prefix \li Valid suffixes
455 \row \li Windows \li \li \c .dll, \c .d.dll
456 \row \li Unix/Linux \li lib \li \c .so
457 \row \li \macos \li lib \li \c .dylib, \c _debug.dylib \c .bundle, \c .so
458 \row \li Android \li lib \li \c .so, \c _<ABI>.so
459 \endtable
460
461 If the \a qmldirPluginPath is absolute, it is searched first. Then each of the
462 filePluginPath entries in the QQmlTypeLoader is checked in turn. If the
463 entry is relative, it is resolved on top of the path of the qmldir file,
464 otherwise it is taken verbatim. If a "." is found in the filePluginPath, and
465 \a qmldirPluginPath is relative, then \a qmldirPluginPath is used in its
466 place.
467
468 TODO: Document the android special casing.
469
470 TODO: The above paragraph, as well as the code implementing it makes very
471 little sense and is mostly here for backwards compatibility.
472 */
473QString QQmlPluginImporter::resolvePlugin(const QString &qmldirPluginPath, const QString &baseName)
474{
475#if defined(Q_OS_WIN)
476 static const QString prefix;
477 static const QStringList suffixes = {
478 # ifdef QT_DEBUG
479 QLatin1String("d.dll"), // try a qmake-style debug build first
480 QLatin1String(".dll")
481 #else
482 QLatin1String(".dll"),
483 QLatin1String("d.dll") // try a qmake-style debug build after
484 # endif
485 };
486#elif defined(Q_OS_DARWIN)
487 static const QString prefix = QLatin1String("lib");
488 static const QStringList suffixes = {
489 # ifdef QT_DEBUG
490 QLatin1String("_debug.dylib"), // try a qmake-style debug build first
491 QLatin1String(".dylib"),
492 # else
493 QLatin1String(".dylib"),
494 QLatin1String("_debug.dylib"), // try a qmake-style debug build after
495 # endif
496 QLatin1String(".so"),
497 QLatin1String(".bundle")
498 };
499#else // Unix
500 static const QString prefix = QLatin1String("lib");
501 static const QStringList suffixes = {
502 # if defined(Q_OS_ANDROID)
503 QStringLiteral(LIBS_SUFFIX),
504 # endif
505 QLatin1String(".so")
506 };
507#endif
508
509 QStringList searchPaths = typeLoader->pluginPathList();
510 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
511 if (!qmldirPluginPathIsRelative)
512 searchPaths.prepend(qmldirPluginPath);
513
514 for (const QString &pluginPath : std::as_const(searchPaths)) {
515 QString resolvedBasePath;
516 if (pluginPath == QLatin1String(".")) {
517 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty()
518 && qmldirPluginPath != QLatin1String(".")) {
519 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + qmldirPluginPath);
520 } else {
521 resolvedBasePath = qmldirPath;
522 }
523 } else {
524 if (QDir::isRelativePath(pluginPath))
525 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + pluginPath);
526 else
527 resolvedBasePath = pluginPath;
528 }
529
530 // hack for resources, should probably go away
531 if (resolvedBasePath.startsWith(u':'))
532 resolvedBasePath = QCoreApplication::applicationDirPath();
533
534 if (!resolvedBasePath.endsWith(u'/'))
535 resolvedBasePath += u'/';
536
537 QString resolvedPath = resolvedBasePath + prefix + baseName;
538 for (const QString &suffix : suffixes) {
539 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
540 if (!absolutePath.isEmpty())
541 return absolutePath;
542 }
543
544#if defined(Q_OS_ANDROID)
545 if (qmldirPath.size() > 25 && qmldirPath.at(0) == QLatin1Char(':')
546 && qmldirPath.at(1) == QLatin1Char('/')
547 && qmldirPath.startsWith(QStringLiteral(":/android_rcc_bundle/qml/"),
548 Qt::CaseInsensitive)) {
549 QString pluginName = qmldirPath.mid(21) + u'/' + baseName;
550 pluginName.replace(QLatin1Char('/'), QLatin1Char('_'));
551 QString bundledPath = resolvedBasePath + QLatin1String("lib") + pluginName;
552 for (const QString &suffix : suffixes) {
553 const QString absolutePath = typeLoader->absoluteFilePath(bundledPath + suffix);
554 if (!absolutePath.isEmpty()) {
555 qWarning("The implicit resolving of Qml plugin locations using the URI "
556 "embedded in the filename has been deprecated. Please use the "
557 "modern CMake API to create QML modules or set the name of "
558 "QML plugin in qmldir file, that matches the name of plugin "
559 "on file system. The correct plugin name is '%s'.",
560 qPrintable(pluginName));
561 return absolutePath;
562 }
563 }
564 }
565#endif
566 }
567
568 qCDebug(lcQmlImport) << "resolvePlugin" << "Could not resolve dynamic plugin with base name"
569 << baseName << "in" << qmldirPath
570 << " file does not exist";
571
572 return QString();
573}
574
575QTypeRevision QQmlPluginImporter::importPlugins() {
576 const auto qmldirPlugins = qmldir->plugins();
577 const int qmldirPluginCount = qmldirPlugins.size();
578 QTypeRevision importVersion = version;
579
580 // If the path contains a version marker or if we have more than one plugin,
581 // we need to use paths. In that case we cannot fall back to other instances
582 // of the same module if a qmldir is rejected. However, as we don't generate
583 // such modules, it shouldn't be a problem.
584 const bool canUseUris = qmldirPluginCount == 1
585 && qmldirPath.endsWith(u'/' + QString(uri).replace(u'.', u'/'));
586 const QString moduleId = canUseUris ? uri : qmldir->qmldirLocation();
587
588 if (typeLoader->isModulePluginProcessingDone(moduleId)) {
589 return QQmlImports::validVersion(importVersion);
590 }
591
592 // First search for listed qmldir plugins dynamically. If we cannot resolve them all, we
593 // continue searching static plugins that has correct metadata uri. Note that since we
594 // only know the uri for a static plugin, and not the filename, we cannot know which
595 // static plugin belongs to which listed plugin inside qmldir. And for this reason,
596 // mixing dynamic and static plugins inside a single module is not recommended.
597
598 int dynamicPluginsFound = 0;
599 int staticPluginsLoaded = 0;
600
601 for (const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
602 const QString resolvedFilePath = resolvePlugin(plugin.path, plugin.name);
603
604 if (!canUseUris && resolvedFilePath.isEmpty())
605 continue;
606
607 importVersion = importDynamicPlugin(
608 resolvedFilePath, canUseUris ? uri : QFileInfo(resolvedFilePath).absoluteFilePath(),
609 plugin.optional);
610 if (importVersion.isValid())
611 ++dynamicPluginsFound;
612 else if (!resolvedFilePath.isEmpty())
613 return QTypeRevision();
614 }
615
616 if (dynamicPluginsFound < qmldirPluginCount) {
617 // Check if the missing plugins can be resolved statically. We do this by looking at
618 // the URIs embedded in a plugins meta data.
619 const auto pluginPairs = staticQmlPluginsMatchingURI({ uri, importVersion });
620
621 for (const auto &pluginWithURI : std::as_const(pluginPairs)) {
622 QObject *instance = pluginWithURI.plugin.instance();
623 importVersion = importStaticPlugin(
624 instance, canUseUris ? uri : QString::asprintf("%p", instance));
625 if (!importVersion.isValid()) {
626 continue;
627 }
628 staticPluginsLoaded++;
629 qCDebug(lcQmlImport) << "importExtension" << "loaded static plugin "
630 << pluginWithURI.metadataURI;
631 }
632 if (!pluginPairs.empty() && staticPluginsLoaded == 0) {
633 // found matched plugins, but failed to load, early exit to preserve the error
634 return QTypeRevision();
635 }
636 }
637
638 if ((dynamicPluginsFound + staticPluginsLoaded) < qmldirPluginCount) {
639 if (errors) {
640 QQmlError error;
641 if (qmldirPluginCount > 1 && staticPluginsLoaded > 0) {
642 error.setDescription(
643 QQmlImports::tr("could not resolve all plugins for module \"%1\"")
644 .arg(uri));
645 } else {
646 error.setDescription(QQmlImports::tr("module \"%1\" plugin \"%2\" not found")
647 .arg(uri, qmldirPlugins[dynamicPluginsFound].name));
648 }
649 error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation()));
650 errors->prepend(error);
651 }
652 return QTypeRevision();
653 }
654
655 typeLoader->setModulePluginProcessingDone(moduleId);
656
657 return QQmlImports::validVersion(importVersion);
658}
659
660QT_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