8#include <private/qqmlextensionplugin_p.h>
9#include <private/qqmltypeloader_p.h>
10#include <private/qqmlglobal_p.h>
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>
18#include <unordered_map>
24 using Loader = std::unique_ptr<QPluginLoader>;
25 QPluginLoader *loader()
const
27 if (
auto loader = std::get_if<Loader>(&data))
32 bool hasInstanceOrLoader()
const
34 if (
auto instance = std::get_if<QQmlExtensionPlugin *>(&data))
36 return bool(std::get<Loader>(data));
39 QString unloadOrErrorMessage()
const
41 if (
auto instance = std::get_if<QQmlExtensionPlugin *>(&data)) {
43 (*instance)->unregisterTypes();
46 const Loader &loader = std::get<Loader>(data);
51 if (
auto extensionPlugin = qobject_cast<QQmlExtensionPlugin *>(loader->instance()))
52 extensionPlugin->unregisterTypes();
53 if (!loader->unload())
54 return loader->errorString();
58 std::variant<Loader, QQmlExtensionPlugin *> data;
92 QMutexLocker<QBasicMutex> locker;
110
111
112
113
114
115
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 "
129 if (!isQmlExtensionIID) {
136 const auto *pluginInstance = plugin.instance();
137 return qobject_cast<
const QQmlEngineExtensionPlugin *>(pluginInstance)
138 || qobject_cast<
const QQmlExtensionPlugin *>(pluginInstance);
141 const auto &pluginMetadata = plugin.metaData();
142 if (!isQmlPlugin(plugin, pluginMetadata)) {
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())
153 return metadataUriList;
158 QList<StaticPluginMapping> qmlPlugins;
159 const auto staticPlugins = QPluginLoader::staticPlugins();
160 qmlPlugins.reserve(staticPlugins.size());
162 for (
const auto &plugin : staticPlugins) {
164 for (
const QJsonValueConstRef &pluginURI : tryExtractQmlPluginURIs(plugin)) {
165 qmlPlugins.append({ plugin, pluginURI.toString() });
172
173
174
175
179 for (
int mode = QQmlImports::FullyVersioned; mode <= QQmlImports::Unversioned; ++mode) {
180 int index = uri.id.size();
182 QString versionUri = uri.id;
185 QQmlImports::versionString(uri.version, QQmlImports::ImportVersion(mode)));
186 result += versionUri;
188 index = uri.id.lastIndexOf(u'.', index - 1);
189 }
while (index > 0 && mode != QQmlImports::Unversioned);
195
196
197
198
201 static const auto qmlPlugins = staticQmlPlugins();
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);
216static bool unloadPlugin(
const std::pair<
const QString, QmlPlugin> &plugin)
218 const QString errorMessage = plugin.second.unloadOrErrorMessage();
219 if (errorMessage.isEmpty())
222 qWarning(
"Unloading %s failed: %s", qPrintable(plugin.first), qPrintable(errorMessage));
227
228
232 if (!version.hasMajorVersion()) {
233 version = QQmlMetaType::latestModuleVersion(uri);
234 if (!version.isValid())
235 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
237 if (version.hasMajorVersion() && !typeNamespace.isEmpty()
238 && !QQmlMetaType::protectModule(uri, version,
true)) {
244 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
245 return QTypeRevision();
255 for (
const auto &plugin : std::as_const(*plugins))
256 unloadPlugin(plugin);
260bool QQmlPluginImporter::removePlugin(
const QString &pluginId)
264 auto it = plugins->find(pluginId);
265 if (it == plugins->end())
268 const bool success = unloadPlugin(*it);
274QStringList QQmlPluginImporter::plugins()
278 for (
auto it = plugins->cbegin(), end = plugins->cend(); it != end; ++it) {
279 if (it->second.hasInstanceOrLoader())
280 results.append(it->first);
285QString QQmlPluginImporter::truncateToDirectory(
const QString &qmldirFilePath)
287 const int slash = qmldirFilePath.lastIndexOf(u'/');
288 return slash > 0 ? qmldirFilePath.left(slash) : qmldirFilePath;
291void QQmlPluginImporter::finalizePlugin(QObject *instance,
const QString &pluginId) {
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());
303QTypeRevision QQmlPluginImporter::importStaticPlugin(QObject *instance,
const QString &pluginId) {
312 bool typesRegistered = plugins->find(pluginId) != plugins->end();
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();
324 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
325 if (!importVersion.isValid())
326 return QTypeRevision();
335 if (!typeLoader->isPluginInitialized(pluginId))
336 finalizePlugin(instance, pluginId);
338 return QQmlImports::validVersion(importVersion);
342 const QString &filePath,
const QString &pluginId,
bool optional)
344 QObject *instance =
nullptr;
347 const bool engineInitialized = typeLoader->isPluginInitialized(pluginId);
350 const auto plugin = plugins->find(pluginId);
351 bool typesRegistered = plugin != plugins->end();
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:
362 case QQmlMetaType::RegistrationResult::Success:
363 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
364 if (!importVersion.isValid())
365 return QTypeRevision();
367 plugins->insert(std::make_pair(pluginId, QmlPlugin()));
369 typeLoader->setPluginInitialized(pluginId);
370 return importVersion;
371 case QQmlMetaType::RegistrationResult::Failure:
372 return QTypeRevision();
376#if QT_CONFIG(library)
377 if (!typesRegistered) {
383 if (filePath.isEmpty())
384 return QTypeRevision();
387 plugin.data = std::make_unique<QPluginLoader>(fileInfo.absoluteFilePath());
388 if (!plugin.loader()->load()) {
391 error.setDescription(plugin.loader()->errorString());
392 errors->prepend(error);
394 return QTypeRevision();
397 instance = plugin.loader()->instance();
398 plugins->insert(std::make_pair(pluginId, std::move(plugin)));
401 if (QQmlMetaType::registerPluginTypes(
402 instance, fileInfo.absolutePath(), uri, qmldir->typeNamespace(),
403 importVersion, errors)
404 == QQmlMetaType::RegistrationResult::Failure) {
405 return QTypeRevision();
408 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
409 if (!importVersion.isValid())
410 return QTypeRevision();
412 Q_ASSERT(plugin != plugins->end());
413 if (
const auto &loader = plugin->second.loader()) {
414 instance = loader->instance();
415 }
else if (!optional) {
419 return QTypeRevision();
427 return QTypeRevision();
437 if (!engineInitialized)
438 finalizePlugin(instance, pluginId);
440 return QQmlImports::validVersion(importVersion);
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473QString QQmlPluginImporter::resolvePlugin(
const QString &qmldirPluginPath,
const QString &baseName)
476 static const QString prefix;
477 static const QStringList suffixes = {
479 QLatin1String(
"d.dll"),
480 QLatin1String(
".dll")
482 QLatin1String(
".dll"),
483 QLatin1String(
"d.dll")
486#elif defined(Q_OS_DARWIN)
487 static const QString prefix = QLatin1String(
"lib");
488 static const QStringList suffixes = {
490 QLatin1String(
"_debug.dylib"),
491 QLatin1String(
".dylib"),
493 QLatin1String(
".dylib"),
494 QLatin1String(
"_debug.dylib"),
496 QLatin1String(
".so"),
497 QLatin1String(
".bundle")
500 static const QString prefix = QLatin1String(
"lib");
501 static const QStringList suffixes = {
502 # if defined(Q_OS_ANDROID)
503 QStringLiteral(LIBS_SUFFIX),
509 QStringList searchPaths = typeLoader->pluginPathList();
510 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
511 if (!qmldirPluginPathIsRelative)
512 searchPaths.prepend(qmldirPluginPath);
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);
521 resolvedBasePath = qmldirPath;
524 if (QDir::isRelativePath(pluginPath))
525 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + pluginPath);
527 resolvedBasePath = pluginPath;
531 if (resolvedBasePath.startsWith(u':'))
532 resolvedBasePath = QCoreApplication::applicationDirPath();
534 if (!resolvedBasePath.endsWith(u'/'))
535 resolvedBasePath += u'/';
537 QString resolvedPath = resolvedBasePath + prefix + baseName;
538 for (
const QString &suffix : suffixes) {
539 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
540 if (!absolutePath.isEmpty())
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));
568 qCDebug(lcQmlImport) <<
"resolvePlugin" <<
"Could not resolve dynamic plugin with base name"
569 << baseName <<
"in" << qmldirPath
570 <<
" file does not exist";
576 const auto qmldirPlugins = qmldir->plugins();
577 const int qmldirPluginCount = qmldirPlugins.size();
584 const bool canUseUris = qmldirPluginCount == 1
585 && qmldirPath.endsWith(u'/' + QString(uri).replace(u'.', u'/'));
586 const QString moduleId = canUseUris ? uri : qmldir->qmldirLocation();
588 if (typeLoader->isModulePluginProcessingDone(moduleId)) {
589 return QQmlImports::validVersion(importVersion);
598 int dynamicPluginsFound = 0;
599 int staticPluginsLoaded = 0;
601 for (
const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
602 const QString resolvedFilePath = resolvePlugin(plugin.path, plugin.name);
604 if (!canUseUris && resolvedFilePath.isEmpty())
607 importVersion = importDynamicPlugin(
608 resolvedFilePath, canUseUris ? uri : QFileInfo(resolvedFilePath).absoluteFilePath(),
610 if (importVersion.isValid())
611 ++dynamicPluginsFound;
612 else if (!resolvedFilePath.isEmpty())
613 return QTypeRevision();
616 if (dynamicPluginsFound < qmldirPluginCount) {
619 const auto pluginPairs = staticQmlPluginsMatchingURI({ uri, importVersion });
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()) {
628 staticPluginsLoaded++;
629 qCDebug(lcQmlImport) <<
"importExtension" <<
"loaded static plugin "
630 << pluginWithURI.metadataURI;
632 if (!pluginPairs.empty() && staticPluginsLoaded == 0) {
634 return QTypeRevision();
638 if ((dynamicPluginsFound + staticPluginsLoaded) < qmldirPluginCount) {
641 if (qmldirPluginCount > 1 && staticPluginsLoaded > 0) {
642 error.setDescription(
643 QQmlImports::tr(
"could not resolve all plugins for module \"%1\"")
646 error.setDescription(QQmlImports::tr(
"module \"%1\" plugin \"%2\" not found")
647 .arg(uri, qmldirPlugins[dynamicPluginsFound].name));
649 error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation()));
650 errors->prepend(error);
652 return QTypeRevision();
655 typeLoader->setModulePluginProcessingDone(moduleId);
657 return QQmlImports::validVersion(importVersion);
const PluginMap::Container & operator*() const
PluginMap::Container * operator->()
const PluginMap::Container * operator->() const
PluginMap::Container & operator*()
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)