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>
22 std::unique_ptr<QPluginLoader> loader;
56 QMutexLocker<QBasicMutex> locker;
74
75
76
77
78
79
82 const auto isQmlPlugin = [](
const QStaticPlugin &plugin,
auto &&pluginMetadata) ->
bool {
83 const QString iid = pluginMetadata.value(QLatin1String(
"IID")).toString();
84 const bool isQmlExtensionIID = iid == QLatin1String(QQmlEngineExtensionInterface_iid)
85 || iid == QLatin1String(QQmlExtensionInterface_iid)
86 || iid == QLatin1String(QQmlExtensionInterface_iid_old);
87 if (Q_UNLIKELY(iid == QLatin1String(QQmlExtensionInterface_iid_old))) {
88 qWarning() << QQmlImports::tr(
89 "Found plugin with old IID, this will be unsupported in upcoming Qt "
93 if (!isQmlExtensionIID) {
100 const auto *pluginInstance = plugin.instance();
101 return qobject_cast<
const QQmlEngineExtensionPlugin *>(pluginInstance)
102 || qobject_cast<
const QQmlExtensionPlugin *>(pluginInstance);
105 const auto &pluginMetadata = plugin.metaData();
106 if (!isQmlPlugin(plugin, pluginMetadata)) {
110 const QJsonArray metadataUriList = pluginMetadata.value(QStringLiteral(
"uri")).toArray();
111 if (metadataUriList.isEmpty()) {
112 qWarning() << QQmlImports::tr(
"qml static plugin with name \"%2\" has no metadata URI")
113 .arg(plugin.instance()->metaObject()->className())
117 return metadataUriList;
122 QVector<StaticPluginMapping> qmlPlugins;
123 const auto staticPlugins = QPluginLoader::staticPlugins();
124 qmlPlugins.reserve(staticPlugins.size());
126 for (
const auto &plugin : staticPlugins) {
128 for (
const QJsonValueConstRef &pluginURI : tryExtractQmlPluginURIs(plugin)) {
129 qmlPlugins.append({ plugin, pluginURI.toString() });
136
137
138
139
143 for (
int mode = QQmlImports::FullyVersioned; mode <= QQmlImports::Unversioned; ++mode) {
144 int index = uri.id.size();
146 QString versionUri = uri.id;
149 QQmlImports::versionString(uri.version, QQmlImports::ImportVersion(mode)));
150 result += versionUri;
152 index = uri.id.lastIndexOf(u'.', index - 1);
153 }
while (index > 0 && mode != QQmlImports::Unversioned);
159
160
161
162
165 static const auto qmlPlugins = staticQmlPlugins();
171 const QStringList versionedURIs = versionUriList(uri);
172 QVector<StaticPluginMapping> matches;
173 std::copy_if(qmlPlugins.begin(), qmlPlugins.end(), std::back_inserter(matches),
174 [&](
const auto &pluginMapping) {
175 return versionedURIs.contains(pluginMapping.metadataURI);
180static bool unloadPlugin(
const std::pair<
const QString, QmlPlugin> &plugin)
182 const auto &loader = plugin.second.loader;
186#if QT_CONFIG(library)
187 if (
auto extensionPlugin = qobject_cast<QQmlExtensionPlugin *>(loader->instance()))
188 extensionPlugin->unregisterTypes();
191 if (!loader->unload()) {
192 qWarning(
"Unloading %s failed: %s", qPrintable(plugin.first),
193 qPrintable(loader->errorString()));
203
204
208 if (!version.hasMajorVersion()) {
209 version = QQmlMetaType::latestModuleVersion(uri);
210 if (!version.isValid())
211 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
213 if (version.hasMajorVersion() && !typeNamespace.isEmpty()
214 && !QQmlMetaType::protectModule(uri, version,
true)) {
220 errors->prepend(QQmlImports::moduleNotFoundError(uri, version));
221 return QTypeRevision();
231 for (
const auto &plugin : std::as_const(*plugins))
232 unloadPlugin(plugin);
236bool QQmlPluginImporter::removePlugin(
const QString &pluginId)
240 auto it = plugins->find(pluginId);
241 if (it == plugins->end())
244 const bool success = unloadPlugin(*it);
250QStringList QQmlPluginImporter::plugins()
254 for (
auto it = plugins->cbegin(), end = plugins->cend(); it != end; ++it) {
255 if (it->second.loader !=
nullptr)
256 results.append(it->first);
261QString QQmlPluginImporter::truncateToDirectory(
const QString &qmldirFilePath)
263 const int slash = qmldirFilePath.lastIndexOf(u'/');
264 return slash > 0 ? qmldirFilePath.left(slash) : qmldirFilePath;
267void QQmlPluginImporter::finalizePlugin(QObject *instance,
const QString &pluginId) {
272 typeLoader->setPluginInitialized(pluginId);
273 if (
auto *extensionIface = qobject_cast<QQmlExtensionInterface *>(instance))
274 typeLoader->initializeEngine(extensionIface, uri.toUtf8().constData());
275 else if (
auto *engineIface = qobject_cast<QQmlEngineExtensionInterface *>(instance))
276 typeLoader->initializeEngine(engineIface, uri.toUtf8().constData());
279QTypeRevision QQmlPluginImporter::importStaticPlugin(QObject *instance,
const QString &pluginId) {
288 bool typesRegistered = plugins->find(pluginId) != plugins->end();
290 if (!typesRegistered) {
291 plugins->insert(std::make_pair(pluginId, QmlPlugin()));
292 if (QQmlMetaType::registerPluginTypes(
293 instance, QFileInfo(qmldirPath).absoluteFilePath(), uri,
294 qmldir->typeNamespace(), importVersion, errors)
295 == QQmlMetaType::RegistrationResult::Failure) {
296 return QTypeRevision();
299 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
300 if (!importVersion.isValid())
301 return QTypeRevision();
310 if (!typeLoader->isPluginInitialized(pluginId))
311 finalizePlugin(instance, pluginId);
313 return QQmlImports::validVersion(importVersion);
317 const QString &filePath,
const QString &pluginId,
bool optional)
319 QObject *instance =
nullptr;
322 const bool engineInitialized = typeLoader->isPluginInitialized(pluginId);
325 const auto plugin = plugins->find(pluginId);
326 bool typesRegistered = plugin != plugins->end();
328 if (!engineInitialized || !typesRegistered) {
329 const QFileInfo fileInfo(filePath);
330 if (!typesRegistered && optional) {
331 switch (QQmlMetaType::registerPluginTypes(
332 nullptr, fileInfo.absolutePath(), uri, qmldir->typeNamespace(),
333 importVersion, errors)) {
334 case QQmlMetaType::RegistrationResult::NoRegistrationFunction:
337 case QQmlMetaType::RegistrationResult::Success:
338 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
339 if (!importVersion.isValid())
340 return QTypeRevision();
342 plugins->insert(std::make_pair(pluginId, QmlPlugin()));
344 typeLoader->setPluginInitialized(pluginId);
345 return importVersion;
346 case QQmlMetaType::RegistrationResult::Failure:
347 return QTypeRevision();
351#if QT_CONFIG(library)
352 if (!typesRegistered) {
358 if (filePath.isEmpty())
359 return QTypeRevision();
362 plugin.loader = std::make_unique<QPluginLoader>(fileInfo.absoluteFilePath());
363 if (!plugin.loader->load()) {
366 error.setDescription(plugin.loader->errorString());
367 errors->prepend(error);
369 return QTypeRevision();
372 instance = plugin.loader->instance();
373 plugins->insert(std::make_pair(pluginId, std::move(plugin)));
376 if (QQmlMetaType::registerPluginTypes(
377 instance, fileInfo.absolutePath(), uri, qmldir->typeNamespace(),
378 importVersion, errors)
379 == QQmlMetaType::RegistrationResult::Failure) {
380 return QTypeRevision();
383 importVersion = lockModule(uri, qmldir->typeNamespace(), importVersion, errors);
384 if (!importVersion.isValid())
385 return QTypeRevision();
387 Q_ASSERT(plugin != plugins->end());
388 if (
const auto &loader = plugin->second.loader) {
389 instance = loader->instance();
390 }
else if (!optional) {
394 return QTypeRevision();
402 return QTypeRevision();
412 if (!engineInitialized)
413 finalizePlugin(instance, pluginId);
415 return QQmlImports::validVersion(importVersion);
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448QString QQmlPluginImporter::resolvePlugin(
const QString &qmldirPluginPath,
const QString &baseName)
451 static const QString prefix;
452 static const QStringList suffixes = {
454 QLatin1String(
"d.dll"),
455 QLatin1String(
".dll")
457 QLatin1String(
".dll"),
458 QLatin1String(
"d.dll")
461#elif defined(Q_OS_DARWIN)
462 static const QString prefix = QLatin1String(
"lib");
463 static const QStringList suffixes = {
465 QLatin1String(
"_debug.dylib"),
466 QLatin1String(
".dylib"),
468 QLatin1String(
".dylib"),
469 QLatin1String(
"_debug.dylib"),
471 QLatin1String(
".so"),
472 QLatin1String(
".bundle")
475 static const QString prefix = QLatin1String(
"lib");
476 static const QStringList suffixes = {
477 # if defined(Q_OS_ANDROID)
478 QStringLiteral(LIBS_SUFFIX),
484 QStringList searchPaths = typeLoader->pluginPathList();
485 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
486 if (!qmldirPluginPathIsRelative)
487 searchPaths.prepend(qmldirPluginPath);
489 for (
const QString &pluginPath : std::as_const(searchPaths)) {
490 QString resolvedBasePath;
491 if (pluginPath == QLatin1String(
".")) {
492 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty()
493 && qmldirPluginPath != QLatin1String(
".")) {
494 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + qmldirPluginPath);
496 resolvedBasePath = qmldirPath;
499 if (QDir::isRelativePath(pluginPath))
500 resolvedBasePath = QDir::cleanPath(qmldirPath + u'/' + pluginPath);
502 resolvedBasePath = pluginPath;
506 if (resolvedBasePath.startsWith(u':'))
507 resolvedBasePath = QCoreApplication::applicationDirPath();
509 if (!resolvedBasePath.endsWith(u'/'))
510 resolvedBasePath += u'/';
512 QString resolvedPath = resolvedBasePath + prefix + baseName;
513 for (
const QString &suffix : suffixes) {
514 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + suffix);
515 if (!absolutePath.isEmpty())
519#if defined(Q_OS_ANDROID)
520 if (qmldirPath.size() > 25 && qmldirPath.at(0) == QLatin1Char(
':')
521 && qmldirPath.at(1) == QLatin1Char(
'/')
522 && qmldirPath.startsWith(QStringLiteral(
":/android_rcc_bundle/qml/"),
523 Qt::CaseInsensitive)) {
524 QString pluginName = qmldirPath.mid(21) + u'/' + baseName;
525 pluginName.replace(QLatin1Char(
'/'), QLatin1Char(
'_'));
526 QString bundledPath = resolvedBasePath + QLatin1String(
"lib") + pluginName;
527 for (
const QString &suffix : suffixes) {
528 const QString absolutePath = typeLoader->absoluteFilePath(bundledPath + suffix);
529 if (!absolutePath.isEmpty()) {
530 qWarning(
"The implicit resolving of Qml plugin locations using the URI "
531 "embedded in the filename has been deprecated. Please use the "
532 "modern CMake API to create QML modules or set the name of "
533 "QML plugin in qmldir file, that matches the name of plugin "
534 "on file system. The correct plugin name is '%s'.",
535 qPrintable(pluginName));
543 qCDebug(lcQmlImport) <<
"resolvePlugin" <<
"Could not resolve dynamic plugin with base name"
544 << baseName <<
"in" << qmldirPath
545 <<
" file does not exist";
551 const auto qmldirPlugins = qmldir->plugins();
552 const int qmldirPluginCount = qmldirPlugins.size();
559 const bool canUseUris = qmldirPluginCount == 1
560 && qmldirPath.endsWith(u'/' + QString(uri).replace(u'.', u'/'));
561 const QString moduleId = canUseUris ? uri : qmldir->qmldirLocation();
563 if (typeLoader->isModulePluginProcessingDone(moduleId)) {
564 return QQmlImports::validVersion(importVersion);
573 int dynamicPluginsFound = 0;
574 int staticPluginsLoaded = 0;
576 for (
const QQmlDirParser::Plugin &plugin : qmldirPlugins) {
577 const QString resolvedFilePath = resolvePlugin(plugin.path, plugin.name);
579 if (!canUseUris && resolvedFilePath.isEmpty())
582 importVersion = importDynamicPlugin(
583 resolvedFilePath, canUseUris ? uri : QFileInfo(resolvedFilePath).absoluteFilePath(),
585 if (importVersion.isValid())
586 ++dynamicPluginsFound;
587 else if (!resolvedFilePath.isEmpty())
588 return QTypeRevision();
591 if (dynamicPluginsFound < qmldirPluginCount) {
594 const auto pluginPairs = staticQmlPluginsMatchingURI({ uri, importVersion });
596 for (
const auto &pluginWithURI : std::as_const(pluginPairs)) {
597 QObject *instance = pluginWithURI.plugin.instance();
598 importVersion = importStaticPlugin(
599 instance, canUseUris ? uri : QString::asprintf(
"%p", instance));
600 if (!importVersion.isValid()) {
603 staticPluginsLoaded++;
604 qCDebug(lcQmlImport) <<
"importExtension" <<
"loaded static plugin "
605 << pluginWithURI.metadataURI;
607 if (!pluginPairs.empty() && staticPluginsLoaded == 0) {
609 return QTypeRevision();
613 if ((dynamicPluginsFound + staticPluginsLoaded) < qmldirPluginCount) {
616 if (qmldirPluginCount > 1 && staticPluginsLoaded > 0) {
617 error.setDescription(
618 QQmlImports::tr(
"could not resolve all plugins for module \"%1\"")
621 error.setDescription(QQmlImports::tr(
"module \"%1\" plugin \"%2\" not found")
622 .arg(uri, qmldirPlugins[dynamicPluginsFound].name));
624 error.setUrl(QUrl::fromLocalFile(qmldir->qmldirLocation()));
625 errors->prepend(error);
627 return QTypeRevision();
630 typeLoader->setModulePluginProcessingDone(moduleId);
632 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)