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 const QJsonArray pluginURIs = tryExtractQmlPluginURIs(plugin);
165 for (
const QJsonValueConstRef &pluginURI : pluginURIs)
166 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
230 QTypeRevision version, QList<QQmlError> *errors)
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) {
306 QTypeRevision importVersion = version;
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);
341QTypeRevision QQmlPluginImporter::importDynamicPlugin(
342 const QString &filePath,
const QString &pluginId,
bool optional)
344 QObject *instance =
nullptr;
345 QTypeRevision importVersion = version;
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();
366 qCDebug(lcQmlImport) <<
"module" << uri
367 <<
"provided by linked library" << qmldir->linkTarget()
368 <<
"; optional plugin not loaded";
370 plugins->insert(std::make_pair(pluginId, QmlPlugin()));
372 typeLoader->setPluginInitialized(pluginId);
373 return importVersion;
374 case QQmlMetaType::RegistrationResult::Failure:
375 return QTypeRevision();
379#if QT_CONFIG(library)
380 if (!typesRegistered) {
386 if (filePath.isEmpty())
387 return QTypeRevision();
390 plugin.data = std::make_unique<QPluginLoader>(fileInfo.absoluteFilePath());
391 if (!plugin.loader()->load()) {
394 error.setDescription(plugin.loader()->errorString());
395 errors->prepend(error);
397 return QTypeRevision();
400 qCDebug(lcQmlImport) <<
"module" << uri
401 <<
"provided by plugin" << fileInfo.absoluteFilePath();
403 instance = plugin.loader()->instance();
404 plugins->insert(std::make_pair(pluginId, std::move(plugin)));
407 if (QQmlMetaType::registerPluginTypes(
408 instance, fileInfo.absolutePath(), uri, qmldir->typeNamespace(),
409 importVersion, errors)
410 == QQmlMetaType::RegistrationResult::Failure) {
411 return QTypeRevision();
414 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
415 if (!importVersion.isValid())
416 return QTypeRevision();
418 Q_ASSERT(plugin != plugins->end());
419 if (
const auto &loader = plugin->second.loader()) {
420 instance = loader->instance();
421 }
else if (!optional) {
425 return QTypeRevision();
433 return QTypeRevision();
443 if (!engineInitialized)
444 finalizePlugin(instance, pluginId);
446 return QQmlImports::validVersion(importVersion);
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479QString QQmlPluginImporter::resolvePlugin(
const QString &qmldirPluginPath,
const QString &baseName)
482 static const QString prefix;
483 static const QStringList suffixes = {
485 QLatin1String(
"d.dll"),
486 QLatin1String(
".dll")
488 QLatin1String(
".dll"),
489 QLatin1String(
"d.dll")
492#elif defined(Q_OS_DARWIN)
493 static const QString prefix = QLatin1String(
"lib");
494 static const QStringList suffixes = {
496 QLatin1String(
"_debug.dylib"),
497 QLatin1String(
".dylib"),
499 QLatin1String(
".dylib"),
500 QLatin1String(
"_debug.dylib"),
502 QLatin1String(
".so"),
503 QLatin1String(
".bundle")
506 static const QString prefix = QLatin1String(
"lib");
507 static const QStringList suffixes = {
508 # if defined(Q_OS_ANDROID)
509 QStringLiteral(LIBS_SUFFIX),
515 QStringList searchPaths = typeLoader->pluginPathList();
516 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
517 if (!qmldirPluginPathIsRelative)
518 searchPaths.prepend(qmldirPluginPath);
520 for (
const QString &pluginPath : std::as_const(searchPaths)) {
521 QString resolvedBasePath;
522 if (pluginPath == QLatin1String(
".")) {
523 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty()
524 && qmldirPluginPath != QLatin1String(
".")) {
525 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + qmldirPluginPath);
527 resolvedBasePath = qmldirPath;
530 if (QDir::isRelativePath(pluginPath))
531 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + pluginPath);
533 resolvedBasePath = pluginPath;
537 if (resolvedBasePath.startsWith(u':'))
538 resolvedBasePath = QCoreApplication::applicationDirPath();
540 if (!resolvedBasePath.endsWith(u'/'))
541 resolvedBasePath += u'/';
543 QString resolvedPath = resolvedBasePath + prefix + baseName;
544 for (
const QString &suffix : suffixes) {
545 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
546 if (!absolutePath.isEmpty())
550#if defined(Q_OS_ANDROID)
551 if (qmldirPath.size() > 25 && qmldirPath.at(0) == QLatin1Char(
':')
552 && qmldirPath.at(1) == QLatin1Char(
'/')
553 && qmldirPath.startsWith(QStringLiteral(
":/android_rcc_bundle/qml/"),
554 Qt::CaseInsensitive)) {
555 QString pluginName = qmldirPath.mid(21) + u'/' + baseName;
556 pluginName.replace(QLatin1Char(
'/'), QLatin1Char(
'_'));
557 QString bundledPath = resolvedBasePath + QLatin1String(
"lib") + pluginName;
558 for (
const QString &suffix : suffixes) {
559 const QString absolutePath = typeLoader->absoluteFilePath(bundledPath + suffix);
560 if (!absolutePath.isEmpty()) {
561 qWarning(
"The implicit resolving of Qml plugin locations using the URI "
562 "embedded in the filename has been deprecated. Please use the "
563 "modern CMake API to create QML modules or set the name of "
564 "QML plugin in qmldir file, that matches the name of plugin "
565 "on file system. The correct plugin name is '%s'.",
566 qPrintable(pluginName));
574 qCDebug(lcQmlImport) <<
"resolvePlugin" <<
"Could not resolve dynamic plugin with base name"
575 << baseName <<
"in" << qmldirPath
576 <<
" file does not exist";
581QTypeRevision QQmlPluginImporter::importPlugins() {
582 const auto qmldirPlugins = qmldir->plugins();
583 const int qmldirPluginCount = qmldirPlugins.size();
584 QTypeRevision importVersion = version;
590 const bool canUseUris = qmldirPluginCount == 1
591 && qmldirPath.endsWith(u'/' + QString(uri).replace(u'.', u'/'));
592 const QString moduleId = canUseUris ? uri : qmldir->qmldirLocation();
594 if (typeLoader->isModulePluginProcessingDone(moduleId)) {
595 return QQmlImports::validVersion(importVersion);
604 int dynamicPluginsFound = 0;
605 int staticPluginsLoaded = 0;
607 for (
const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
608 const QString resolvedFilePath = resolvePlugin(plugin.path, plugin.name);
610 if (!canUseUris && resolvedFilePath.isEmpty())
613 importVersion = importDynamicPlugin(
614 resolvedFilePath, canUseUris ? uri : QFileInfo(resolvedFilePath).absoluteFilePath(),
616 if (importVersion.isValid())
617 ++dynamicPluginsFound;
618 else if (!resolvedFilePath.isEmpty())
619 return QTypeRevision();
622 if (dynamicPluginsFound < qmldirPluginCount) {
625 const auto pluginPairs = staticQmlPluginsMatchingURI({ uri, importVersion });
627 for (
const auto &pluginWithURI : std::as_const(pluginPairs)) {
628 QObject *instance = pluginWithURI.plugin.instance();
629 importVersion = importStaticPlugin(
630 instance, canUseUris ? uri : QString::asprintf(
"%p", instance));
631 if (!importVersion.isValid()) {
634 staticPluginsLoaded++;
635 qCDebug(lcQmlImport) <<
"importExtension" <<
"loaded static plugin "
636 << pluginWithURI.metadataURI;
638 if (!pluginPairs.empty() && staticPluginsLoaded == 0) {
640 return QTypeRevision();
644 if ((dynamicPluginsFound + staticPluginsLoaded) < qmldirPluginCount) {
647 if (qmldirPluginCount > 1 && staticPluginsLoaded > 0) {
648 error.setDescription(
649 QQmlImports::tr(
"could not resolve all plugins for module \"%1\"")
652 error.setDescription(QQmlImports::tr(
"module \"%1\" plugin \"%2\" not found")
653 .arg(uri, qmldirPlugins[dynamicPluginsFound].name));
655 error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation()));
656 errors->prepend(error);
658 return QTypeRevision();
661 typeLoader->setModulePluginProcessingDone(moduleId);
663 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)