7#include <private/qqmlextensionplugin_p.h>
8#include <private/qqmltypeloader_p.h>
9#include <private/qqmlglobal_p.h>
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>
17#include <unordered_map>
23 using Loader = std::unique_ptr<QPluginLoader>;
24 QPluginLoader *loader()
const
26 if (
auto loader = std::get_if<Loader>(&data))
31 bool hasInstanceOrLoader()
const
33 if (
auto instance = std::get_if<QQmlExtensionPlugin *>(&data))
35 return bool(std::get<Loader>(data));
38 QString unloadOrErrorMessage()
const
40 if (
auto instance = std::get_if<QQmlExtensionPlugin *>(&data)) {
42 (*instance)->unregisterTypes();
45 const Loader &loader = std::get<Loader>(data);
50 if (
auto extensionPlugin = qobject_cast<QQmlExtensionPlugin *>(loader->instance()))
51 extensionPlugin->unregisterTypes();
52 if (!loader->unload())
53 return loader->errorString();
57 std::variant<Loader, QQmlExtensionPlugin *> data;
91 QMutexLocker<QBasicMutex> locker;
109
110
111
112
113
114
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 "
128 if (!isQmlExtensionIID) {
135 const auto *pluginInstance = plugin.instance();
136 return qobject_cast<
const QQmlEngineExtensionPlugin *>(pluginInstance)
137 || qobject_cast<
const QQmlExtensionPlugin *>(pluginInstance);
140 const auto &pluginMetadata = plugin.metaData();
141 if (!isQmlPlugin(plugin, pluginMetadata)) {
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())
152 return metadataUriList;
157 QList<StaticPluginMapping> qmlPlugins;
158 const auto staticPlugins = QPluginLoader::staticPlugins();
159 qmlPlugins.reserve(staticPlugins.size());
161 for (
const auto &plugin : staticPlugins) {
163 for (
const QJsonValueConstRef &pluginURI : tryExtractQmlPluginURIs(plugin)) {
164 qmlPlugins.append({ plugin, pluginURI.toString() });
171
172
173
174
178 for (
int mode = QQmlImports::FullyVersioned; mode <= QQmlImports::Unversioned; ++mode) {
179 int index = uri.id.size();
181 QString versionUri = uri.id;
184 QQmlImports::versionString(uri.version, QQmlImports::ImportVersion(mode)));
185 result += versionUri;
187 index = uri.id.lastIndexOf(u'.', index - 1);
188 }
while (index > 0 && mode != QQmlImports::Unversioned);
194
195
196
197
200 static const auto qmlPlugins = staticQmlPlugins();
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);
215static bool unloadPlugin(
const std::pair<
const QString, QmlPlugin> &plugin)
217 const QString errorMessage = plugin.second.unloadOrErrorMessage();
218 if (errorMessage.isEmpty())
221 qWarning(
"Unloading %s failed: %s", qPrintable(plugin.first), qPrintable(errorMessage));
226
227
231 if (!version.hasMajorVersion()) {
232 version = QQmlMetaType::latestModuleVersion(uri);
233 if (!version.isValid())
234 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
236 if (version.hasMajorVersion() && !typeNamespace.isEmpty()
237 && !QQmlMetaType::protectModule(uri, version,
true)) {
243 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
244 return QTypeRevision();
254 for (
const auto &plugin : std::as_const(*plugins))
255 unloadPlugin(plugin);
259bool QQmlPluginImporter::removePlugin(
const QString &pluginId)
263 auto it = plugins->find(pluginId);
264 if (it == plugins->end())
267 const bool success = unloadPlugin(*it);
273QStringList QQmlPluginImporter::plugins()
277 for (
auto it = plugins->cbegin(), end = plugins->cend(); it != end; ++it) {
278 if (it->second.hasInstanceOrLoader())
279 results.append(it->first);
284QString QQmlPluginImporter::truncateToDirectory(
const QString &qmldirFilePath)
286 const int slash = qmldirFilePath.lastIndexOf(u'/');
287 return slash > 0 ? qmldirFilePath.left(slash) : qmldirFilePath;
290void QQmlPluginImporter::finalizePlugin(QObject *instance,
const QString &pluginId) {
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());
302QTypeRevision QQmlPluginImporter::importStaticPlugin(QObject *instance,
const QString &pluginId) {
311 bool typesRegistered = plugins->find(pluginId) != plugins->end();
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();
323 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
324 if (!importVersion.isValid())
325 return QTypeRevision();
334 if (!typeLoader->isPluginInitialized(pluginId))
335 finalizePlugin(instance, pluginId);
337 return QQmlImports::validVersion(importVersion);
341 const QString &filePath,
const QString &pluginId,
bool optional)
343 QObject *instance =
nullptr;
346 const bool engineInitialized = typeLoader->isPluginInitialized(pluginId);
349 const auto plugin = plugins->find(pluginId);
350 bool typesRegistered = plugin != plugins->end();
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:
361 case QQmlMetaType::RegistrationResult::Success:
362 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
363 if (!importVersion.isValid())
364 return QTypeRevision();
366 plugins->insert(std::make_pair(pluginId, QmlPlugin()));
368 typeLoader->setPluginInitialized(pluginId);
369 return importVersion;
370 case QQmlMetaType::RegistrationResult::Failure:
371 return QTypeRevision();
375#if QT_CONFIG(library)
376 if (!typesRegistered) {
382 if (filePath.isEmpty())
383 return QTypeRevision();
386 plugin.data = std::make_unique<QPluginLoader>(fileInfo.absoluteFilePath());
387 if (!plugin.loader()->load()) {
390 error.setDescription(plugin.loader()->errorString());
391 errors->prepend(error);
393 return QTypeRevision();
396 instance = plugin.loader()->instance();
397 plugins->insert(std::make_pair(pluginId, std::move(plugin)));
400 if (QQmlMetaType::registerPluginTypes(
401 instance, fileInfo.absolutePath(), uri, qmldir->typeNamespace(),
402 importVersion, errors)
403 == QQmlMetaType::RegistrationResult::Failure) {
404 return QTypeRevision();
407 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
408 if (!importVersion.isValid())
409 return QTypeRevision();
411 Q_ASSERT(plugin != plugins->end());
412 if (
const auto &loader = plugin->second.loader()) {
413 instance = loader->instance();
414 }
else if (!optional) {
418 return QTypeRevision();
426 return QTypeRevision();
436 if (!engineInitialized)
437 finalizePlugin(instance, pluginId);
439 return QQmlImports::validVersion(importVersion);
443
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
472QString QQmlPluginImporter::resolvePlugin(
const QString &qmldirPluginPath,
const QString &baseName)
475 static const QString prefix;
476 static const QStringList suffixes = {
478 QLatin1String(
"d.dll"),
479 QLatin1String(
".dll")
481 QLatin1String(
".dll"),
482 QLatin1String(
"d.dll")
485#elif defined(Q_OS_DARWIN)
486 static const QString prefix = QLatin1String(
"lib");
487 static const QStringList suffixes = {
489 QLatin1String(
"_debug.dylib"),
490 QLatin1String(
".dylib"),
492 QLatin1String(
".dylib"),
493 QLatin1String(
"_debug.dylib"),
495 QLatin1String(
".so"),
496 QLatin1String(
".bundle")
499 static const QString prefix = QLatin1String(
"lib");
500 static const QStringList suffixes = {
501 # if defined(Q_OS_ANDROID)
502 QStringLiteral(LIBS_SUFFIX),
508 QStringList searchPaths = typeLoader->pluginPathList();
509 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
510 if (!qmldirPluginPathIsRelative)
511 searchPaths.prepend(qmldirPluginPath);
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);
520 resolvedBasePath = qmldirPath;
523 if (QDir::isRelativePath(pluginPath))
524 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + pluginPath);
526 resolvedBasePath = pluginPath;
530 if (resolvedBasePath.startsWith(u':'))
531 resolvedBasePath = QCoreApplication::applicationDirPath();
533 if (!resolvedBasePath.endsWith(u'/'))
534 resolvedBasePath += u'/';
536 QString resolvedPath = resolvedBasePath + prefix + baseName;
537 for (
const QString &suffix : suffixes) {
538 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
539 if (!absolutePath.isEmpty())
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));
567 qCDebug(lcQmlImport) <<
"resolvePlugin" <<
"Could not resolve dynamic plugin with base name"
568 << baseName <<
"in" << qmldirPath
569 <<
" file does not exist";
575 const auto qmldirPlugins = qmldir->plugins();
576 const int qmldirPluginCount = qmldirPlugins.size();
583 const bool canUseUris = qmldirPluginCount == 1
584 && qmldirPath.endsWith(u'/' + QString(uri).replace(u'.', u'/'));
585 const QString moduleId = canUseUris ? uri : qmldir->qmldirLocation();
587 if (typeLoader->isModulePluginProcessingDone(moduleId)) {
588 return QQmlImports::validVersion(importVersion);
597 int dynamicPluginsFound = 0;
598 int staticPluginsLoaded = 0;
600 for (
const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
601 const QString resolvedFilePath = resolvePlugin(plugin.path, plugin.name);
603 if (!canUseUris && resolvedFilePath.isEmpty())
606 importVersion = importDynamicPlugin(
607 resolvedFilePath, canUseUris ? uri : QFileInfo(resolvedFilePath).absoluteFilePath(),
609 if (importVersion.isValid())
610 ++dynamicPluginsFound;
611 else if (!resolvedFilePath.isEmpty())
612 return QTypeRevision();
615 if (dynamicPluginsFound < qmldirPluginCount) {
618 const auto pluginPairs = staticQmlPluginsMatchingURI({ uri, importVersion });
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()) {
627 staticPluginsLoaded++;
628 qCDebug(lcQmlImport) <<
"importExtension" <<
"loaded static plugin "
629 << pluginWithURI.metadataURI;
631 if (!pluginPairs.empty() && staticPluginsLoaded == 0) {
633 return QTypeRevision();
637 if ((dynamicPluginsFound + staticPluginsLoaded) < qmldirPluginCount) {
640 if (qmldirPluginCount > 1 && staticPluginsLoaded > 0) {
641 error.setDescription(
642 QQmlImports::tr(
"could not resolve all plugins for module \"%1\"")
645 error.setDescription(QQmlImports::tr(
"module \"%1\" plugin \"%2\" not found")
646 .arg(uri, qmldirPlugins[dynamicPluginsFound].name));
648 error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation()));
649 errors->prepend(error);
651 return QTypeRevision();
654 typeLoader->setModulePluginProcessingDone(moduleId);
656 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)