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)) {
41 (*instance)->unregisterTypes();
44 const Loader &loader = std::get<Loader>(data);
48 if (!loader->unload())
49 return loader->errorString();
53 std::variant<Loader, QQmlExtensionPlugin *> data;
87 QMutexLocker<QBasicMutex> locker;
105
106
107
108
109
110
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 "
124 if (!isQmlExtensionIID) {
131 const auto *pluginInstance = plugin.instance();
132 return qobject_cast<
const QQmlEngineExtensionPlugin *>(pluginInstance)
133 || qobject_cast<
const QQmlExtensionPlugin *>(pluginInstance);
136 const auto &pluginMetadata = plugin.metaData();
137 if (!isQmlPlugin(plugin, pluginMetadata)) {
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())
148 return metadataUriList;
153 QVector<StaticPluginMapping> qmlPlugins;
154 const auto staticPlugins = QPluginLoader::staticPlugins();
155 qmlPlugins.reserve(staticPlugins.size());
157 for (
const auto &plugin : staticPlugins) {
159 for (
const QJsonValueConstRef &pluginURI : tryExtractQmlPluginURIs(plugin)) {
160 qmlPlugins.append({ plugin, pluginURI.toString() });
167
168
169
170
174 for (
int mode = QQmlImports::FullyVersioned; mode <= QQmlImports::Unversioned; ++mode) {
175 int index = uri.id.size();
177 QString versionUri = uri.id;
180 QQmlImports::versionString(uri.version, QQmlImports::ImportVersion(mode)));
181 result += versionUri;
183 index = uri.id.lastIndexOf(u'.', index - 1);
184 }
while (index > 0 && mode != QQmlImports::Unversioned);
190
191
192
193
196 static const auto qmlPlugins = staticQmlPlugins();
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);
211static bool unloadPlugin(
const std::pair<
const QString, QmlPlugin> &plugin)
213 const QString errorMessage = plugin.second.unloadOrErrorMessage();
214 if (errorMessage.isEmpty())
217 qWarning(
"Unloading %s failed: %s", qPrintable(plugin.first), qPrintable(errorMessage));
222
223
227 if (!version.hasMajorVersion()) {
228 version = QQmlMetaType::latestModuleVersion(uri);
229 if (!version.isValid())
230 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
232 if (version.hasMajorVersion() && !typeNamespace.isEmpty()
233 && !QQmlMetaType::protectModule(uri, version,
true)) {
239 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
240 return QTypeRevision();
250 for (
const auto &plugin : std::as_const(*plugins))
251 unloadPlugin(plugin);
255bool QQmlPluginImporter::removePlugin(
const QString &pluginId)
259 auto it = plugins->find(pluginId);
260 if (it == plugins->end())
263 const bool success = unloadPlugin(*it);
269QStringList QQmlPluginImporter::plugins()
273 for (
auto it = plugins->cbegin(), end = plugins->cend(); it != end; ++it) {
274 if (it->second.hasInstanceOrLoader())
275 results.append(it->first);
280QString QQmlPluginImporter::truncateToDirectory(
const QString &qmldirFilePath)
282 const int slash = qmldirFilePath.lastIndexOf(u'/');
283 return slash > 0 ? qmldirFilePath.left(slash) : qmldirFilePath;
286void QQmlPluginImporter::finalizePlugin(QObject *instance,
const QString &pluginId) {
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());
298QTypeRevision QQmlPluginImporter::importStaticPlugin(QObject *instance,
const QString &pluginId) {
307 bool typesRegistered = plugins->find(pluginId) != plugins->end();
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();
319 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
320 if (!importVersion.isValid())
321 return QTypeRevision();
330 if (!typeLoader->isPluginInitialized(pluginId))
331 finalizePlugin(instance, pluginId);
333 return QQmlImports::validVersion(importVersion);
337 const QString &filePath,
const QString &pluginId,
bool optional)
339 QObject *instance =
nullptr;
342 const bool engineInitialized = typeLoader->isPluginInitialized(pluginId);
345 const auto plugin = plugins->find(pluginId);
346 bool typesRegistered = plugin != plugins->end();
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:
357 case QQmlMetaType::RegistrationResult::Success:
358 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
359 if (!importVersion.isValid())
360 return QTypeRevision();
362 plugins->insert(std::make_pair(pluginId, QmlPlugin()));
364 typeLoader->setPluginInitialized(pluginId);
365 return importVersion;
366 case QQmlMetaType::RegistrationResult::Failure:
367 return QTypeRevision();
371#if QT_CONFIG(library)
372 if (!typesRegistered) {
378 if (filePath.isEmpty())
379 return QTypeRevision();
382 plugin.data = std::make_unique<QPluginLoader>(fileInfo.absoluteFilePath());
383 if (!plugin.loader()->load()) {
386 error.setDescription(plugin.loader()->errorString());
387 errors->prepend(error);
389 return QTypeRevision();
392 instance = plugin.loader()->instance();
393 plugins->insert(std::make_pair(pluginId, std::move(plugin)));
396 if (QQmlMetaType::registerPluginTypes(
397 instance, fileInfo.absolutePath(), uri, qmldir->typeNamespace(),
398 importVersion, errors)
399 == QQmlMetaType::RegistrationResult::Failure) {
400 return QTypeRevision();
403 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
404 if (!importVersion.isValid())
405 return QTypeRevision();
407 Q_ASSERT(plugin != plugins->end());
408 if (
const auto &loader = plugin->second.loader()) {
409 instance = loader->instance();
410 }
else if (!optional) {
414 return QTypeRevision();
422 return QTypeRevision();
432 if (!engineInitialized)
433 finalizePlugin(instance, pluginId);
435 return QQmlImports::validVersion(importVersion);
439
440
441
442
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
468QString QQmlPluginImporter::resolvePlugin(
const QString &qmldirPluginPath,
const QString &baseName)
471 static const QString prefix;
472 static const QStringList suffixes = {
474 QLatin1String(
"d.dll"),
475 QLatin1String(
".dll")
477 QLatin1String(
".dll"),
478 QLatin1String(
"d.dll")
481#elif defined(Q_OS_DARWIN)
482 static const QString prefix = QLatin1String(
"lib");
483 static const QStringList suffixes = {
485 QLatin1String(
"_debug.dylib"),
486 QLatin1String(
".dylib"),
488 QLatin1String(
".dylib"),
489 QLatin1String(
"_debug.dylib"),
491 QLatin1String(
".so"),
492 QLatin1String(
".bundle")
495 static const QString prefix = QLatin1String(
"lib");
496 static const QStringList suffixes = {
497 # if defined(Q_OS_ANDROID)
498 QStringLiteral(LIBS_SUFFIX),
504 QStringList searchPaths = typeLoader->pluginPathList();
505 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
506 if (!qmldirPluginPathIsRelative)
507 searchPaths.prepend(qmldirPluginPath);
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);
516 resolvedBasePath = qmldirPath;
519 if (QDir::isRelativePath(pluginPath))
520 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + pluginPath);
522 resolvedBasePath = pluginPath;
526 if (resolvedBasePath.startsWith(u':'))
527 resolvedBasePath = QCoreApplication::applicationDirPath();
529 if (!resolvedBasePath.endsWith(u'/'))
530 resolvedBasePath += u'/';
532 QString resolvedPath = resolvedBasePath + prefix + baseName;
533 for (
const QString &suffix : suffixes) {
534 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
535 if (!absolutePath.isEmpty())
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));
563 qCDebug(lcQmlImport) <<
"resolvePlugin" <<
"Could not resolve dynamic plugin with base name"
564 << baseName <<
"in" << qmldirPath
565 <<
" file does not exist";
571 const auto qmldirPlugins = qmldir->plugins();
572 const int qmldirPluginCount = qmldirPlugins.size();
579 const bool canUseUris = qmldirPluginCount == 1
580 && qmldirPath.endsWith(u'/' + QString(uri).replace(u'.', u'/'));
581 const QString moduleId = canUseUris ? uri : qmldir->qmldirLocation();
583 if (typeLoader->isModulePluginProcessingDone(moduleId)) {
584 return QQmlImports::validVersion(importVersion);
593 int dynamicPluginsFound = 0;
594 int staticPluginsLoaded = 0;
596 for (
const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
597 const QString resolvedFilePath = resolvePlugin(plugin.path, plugin.name);
599 if (!canUseUris && resolvedFilePath.isEmpty())
602 importVersion = importDynamicPlugin(
603 resolvedFilePath, canUseUris ? uri : QFileInfo(resolvedFilePath).absoluteFilePath(),
605 if (importVersion.isValid())
606 ++dynamicPluginsFound;
607 else if (!resolvedFilePath.isEmpty())
608 return QTypeRevision();
611 if (dynamicPluginsFound < qmldirPluginCount) {
614 const auto pluginPairs = staticQmlPluginsMatchingURI({ uri, importVersion });
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()) {
623 staticPluginsLoaded++;
624 qCDebug(lcQmlImport) <<
"importExtension" <<
"loaded static plugin "
625 << pluginWithURI.metadataURI;
627 if (!pluginPairs.empty() && staticPluginsLoaded == 0) {
629 return QTypeRevision();
633 if ((dynamicPluginsFound + staticPluginsLoaded) < qmldirPluginCount) {
636 if (qmldirPluginCount > 1 && staticPluginsLoaded > 0) {
637 error.setDescription(
638 QQmlImports::tr(
"could not resolve all plugins for module \"%1\"")
641 error.setDescription(QQmlImports::tr(
"module \"%1\" plugin \"%2\" not found")
642 .arg(uri, qmldirPlugins[dynamicPluginsFound].name));
644 error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation()));
645 errors->prepend(error);
647 return QTypeRevision();
650 typeLoader->setModulePluginProcessingDone(moduleId);
652 return QQmlImports::validVersion(importVersion);
const PluginMap::Container & operator*() const
PluginMap::Container * operator->()
const PluginMap::Container * operator->() const
PluginMap::Container & operator*()
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)