1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qqmlpluginimporter_p.h" |
5 | #include "qqmlimport_p.h" |
6 | |
7 | #include <private/qqmlextensionplugin_p.h> |
8 | #include <private/qqmltypeloader_p.h> |
9 | #include <private/qqmlglobal_p.h> |
10 | |
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> |
16 | |
17 | #include <unordered_map> |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | Q_DECLARE_LOGGING_CATEGORY(lcQmlImport) |
22 | |
23 | struct QmlPlugin { |
24 | std::unique_ptr<QPluginLoader> loader; |
25 | }; |
26 | |
27 | class PluginMap |
28 | { |
29 | Q_DISABLE_COPY_MOVE(PluginMap) |
30 | public: |
31 | PluginMap() = default; |
32 | ~PluginMap() = default; |
33 | |
34 | // This is a std::unordered_map because QHash cannot handle move-only types. |
35 | using Container = std::unordered_map<QString, QmlPlugin>; |
36 | |
37 | private: |
38 | QBasicMutex mutex; |
39 | Container plugins; |
40 | friend class PluginMapPtr; |
41 | }; |
42 | |
43 | class PluginMapPtr |
44 | { |
45 | Q_DISABLE_COPY_MOVE(PluginMapPtr) |
46 | public: |
47 | PluginMapPtr(PluginMap *map) : map(map), locker(&map->mutex) {} |
48 | ~PluginMapPtr() = default; |
49 | |
50 | PluginMap::Container &operator*() { return map->plugins; } |
51 | const PluginMap::Container &operator*() const { return map->plugins; } |
52 | |
53 | PluginMap::Container *operator->() { return &map->plugins; } |
54 | const PluginMap::Container *operator->() const { return &map->plugins; } |
55 | |
56 | private: |
57 | PluginMap *map; |
58 | QMutexLocker<QBasicMutex> locker; |
59 | }; |
60 | |
61 | Q_GLOBAL_STATIC(PluginMap, qmlPluginsById); // stores the uri and the PluginLoaders |
62 | |
63 | static QVector<QStaticPlugin> makePlugins() |
64 | { |
65 | QVector<QStaticPlugin> plugins; |
66 | // To avoid traversing all static plugins for all imports, we cut down |
67 | // the list the first time called to only contain QML plugins: |
68 | const auto staticPlugins = QPluginLoader::staticPlugins(); |
69 | for (const QStaticPlugin &plugin : staticPlugins) { |
70 | const QString iid = plugin.metaData().value(key: QLatin1String("IID" )).toString(); |
71 | if (iid == QLatin1String(QQmlEngineExtensionInterface_iid) |
72 | || iid == QLatin1String(QQmlExtensionInterface_iid) |
73 | || iid == QLatin1String(QQmlExtensionInterface_iid_old)) { |
74 | if (Q_UNLIKELY(iid == QLatin1String(QQmlExtensionInterface_iid_old))) { |
75 | qWarning() |
76 | << "Found plugin with old IID, this will be unsupported in upcoming Qt releases:" |
77 | << plugin.metaData(); |
78 | } |
79 | plugins.append(t: plugin); |
80 | } |
81 | } |
82 | return plugins; |
83 | } |
84 | |
85 | /* |
86 | Returns the list of possible versioned URI combinations. For example, if \a uri is |
87 | QtQml.Models, \a vmaj is 2, and \a vmin is 0, this method returns the following: |
88 | [QtQml.Models.2.0, QtQml.2.0.Models, QtQml.Models.2, QtQml.2.Models, QtQml.Models] |
89 | */ |
90 | static QStringList versionUriList(const QString &uri, QTypeRevision version) |
91 | { |
92 | QStringList result; |
93 | for (int mode = QQmlImports::FullyVersioned; mode <= QQmlImports::Unversioned; ++mode) { |
94 | int index = uri.size(); |
95 | do { |
96 | QString versionUri = uri; |
97 | versionUri.insert(i: index, s: QQmlImports::versionString( |
98 | version, importVersion: QQmlImports::ImportVersion(mode))); |
99 | result += versionUri; |
100 | |
101 | index = uri.lastIndexOf(c: u'.', from: index - 1); |
102 | } while (index > 0 && mode != QQmlImports::Unversioned); |
103 | } |
104 | return result; |
105 | } |
106 | |
107 | static bool unloadPlugin(const std::pair<const QString, QmlPlugin> &plugin) |
108 | { |
109 | const auto &loader = plugin.second.loader; |
110 | if (!loader) |
111 | return false; |
112 | |
113 | #if QT_CONFIG(library) |
114 | if (auto extensionPlugin = qobject_cast<QQmlExtensionPlugin *>(object: loader->instance())) |
115 | extensionPlugin->unregisterTypes(); |
116 | |
117 | # ifndef Q_OS_MACOS |
118 | if (!loader->unload()) { |
119 | qWarning(msg: "Unloading %s failed: %s" , qPrintable(plugin.first), |
120 | qPrintable(loader->errorString())); |
121 | return false; |
122 | } |
123 | # endif |
124 | #endif |
125 | |
126 | return true; |
127 | } |
128 | |
129 | void qmlClearEnginePlugins() |
130 | { |
131 | PluginMapPtr plugins(qmlPluginsById()); |
132 | for (const auto &plugin : std::as_const(t&: *plugins)) |
133 | unloadPlugin(plugin); |
134 | plugins->clear(); |
135 | } |
136 | |
137 | bool QQmlPluginImporter::removePlugin(const QString &pluginId) |
138 | { |
139 | PluginMapPtr plugins(qmlPluginsById()); |
140 | |
141 | auto it = plugins->find(x: pluginId); |
142 | if (it == plugins->end()) |
143 | return false; |
144 | |
145 | const bool success = unloadPlugin(plugin: *it); |
146 | |
147 | plugins->erase(position: it); |
148 | return success; |
149 | } |
150 | |
151 | QStringList QQmlPluginImporter::plugins() |
152 | { |
153 | PluginMapPtr plugins(qmlPluginsById()); |
154 | QStringList results; |
155 | for (auto it = plugins->cbegin(), end = plugins->cend(); it != end; ++it) { |
156 | if (it->second.loader != nullptr) |
157 | results.append(t: it->first); |
158 | } |
159 | return results; |
160 | } |
161 | |
162 | QString QQmlPluginImporter::truncateToDirectory(const QString &qmldirFilePath) |
163 | { |
164 | const int slash = qmldirFilePath.lastIndexOf(c: u'/'); |
165 | return slash > 0 ? qmldirFilePath.left(n: slash) : qmldirFilePath; |
166 | } |
167 | |
168 | void QQmlPluginImporter::finalizePlugin(QObject *instance, const QString &pluginId) { |
169 | // The plugin's per-engine initialization does not need lock protection, as this function is |
170 | // only called from the engine specific loader thread and importDynamicPlugin as well as |
171 | // importStaticPlugin are the only places of access. |
172 | |
173 | database->initializedPlugins.insert(value: pluginId); |
174 | if (auto *extensionIface = qobject_cast<QQmlExtensionInterface *>(object: instance)) |
175 | typeLoader->initializeEngine(extensionIface, uri.toUtf8().constData()); |
176 | else if (auto *engineIface = qobject_cast<QQmlEngineExtensionInterface *>(object: instance)) |
177 | typeLoader->initializeEngine(engineIface, uri.toUtf8().constData()); |
178 | } |
179 | |
180 | QTypeRevision QQmlPluginImporter::importStaticPlugin(QObject *instance, const QString &pluginId) { |
181 | // Dynamic plugins are differentiated by their filepath. For static plugins we |
182 | // don't have that information so we use their address as key instead. |
183 | QTypeRevision importVersion = version; |
184 | { |
185 | PluginMapPtr plugins(qmlPluginsById()); |
186 | |
187 | // Plugin types are global across all engines and should only be |
188 | // registered once. But each engine still needs to be initialized. |
189 | bool typesRegistered = plugins->find(x: pluginId) != plugins->end(); |
190 | |
191 | if (!typesRegistered) { |
192 | plugins->insert(x: std::make_pair(x: pluginId, y: QmlPlugin())); |
193 | if (QQmlMetaType::registerPluginTypes( |
194 | instance, basePath: QFileInfo(qmldirPath).absoluteFilePath(), uri, |
195 | typeNamespace: qmldir->typeNamespace(), version: importVersion, errors) |
196 | == QQmlMetaType::RegistrationResult::Failure) { |
197 | return QTypeRevision(); |
198 | } |
199 | |
200 | importVersion = QQmlImportDatabase::lockModule( |
201 | uri, typeNamespace: qmldir->typeNamespace(), version: importVersion, errors); |
202 | if (!importVersion.isValid()) |
203 | return QTypeRevision(); |
204 | } |
205 | |
206 | // Release the lock on plugins early as we're done with the global part. Releasing the lock |
207 | // also allows other QML loader threads to acquire the lock while this thread is blocking |
208 | // in the initializeEngine call to the gui thread (which in turn may be busy waiting for |
209 | // other QML loader threads and thus not process the initializeEngine call). |
210 | } |
211 | |
212 | if (!database->initializedPlugins.contains(value: pluginId)) |
213 | finalizePlugin(instance, pluginId); |
214 | |
215 | return QQmlImports::validVersion(version: importVersion); |
216 | } |
217 | |
218 | QTypeRevision QQmlPluginImporter::importDynamicPlugin( |
219 | const QString &filePath, const QString &pluginId, bool optional) |
220 | { |
221 | QObject *instance = nullptr; |
222 | QTypeRevision importVersion = version; |
223 | |
224 | const bool engineInitialized = database->initializedPlugins.contains(value: pluginId); |
225 | { |
226 | PluginMapPtr plugins(qmlPluginsById()); |
227 | bool typesRegistered = plugins->find(x: pluginId) != plugins->end(); |
228 | |
229 | if (!engineInitialized || !typesRegistered) { |
230 | const QFileInfo fileInfo(filePath); |
231 | if (!typesRegistered && optional) { |
232 | switch (QQmlMetaType::registerPluginTypes( |
233 | instance: nullptr, basePath: fileInfo.absolutePath(), uri, typeNamespace: qmldir->typeNamespace(), |
234 | version: importVersion, errors)) { |
235 | case QQmlMetaType::RegistrationResult::NoRegistrationFunction: |
236 | // try again with plugin |
237 | break; |
238 | case QQmlMetaType::RegistrationResult::Success: |
239 | importVersion = QQmlImportDatabase::lockModule( |
240 | uri, typeNamespace: qmldir->typeNamespace(), version: importVersion, errors); |
241 | if (!importVersion.isValid()) |
242 | return QTypeRevision(); |
243 | // instance and loader intentionally left at nullptr |
244 | plugins->insert(x: std::make_pair(x: pluginId, y: QmlPlugin())); |
245 | // Not calling initializeEngine with null instance |
246 | database->initializedPlugins.insert(value: pluginId); |
247 | return importVersion; |
248 | case QQmlMetaType::RegistrationResult::Failure: |
249 | return QTypeRevision(); |
250 | } |
251 | } |
252 | |
253 | #if QT_CONFIG(library) |
254 | if (!typesRegistered) { |
255 | |
256 | // Check original filePath. If that one is empty, not being able |
257 | // to load the plugin is not an error. We were just checking if |
258 | // the types are already available. absoluteFilePath can still be |
259 | // empty if filePath is not. |
260 | if (filePath.isEmpty()) |
261 | return QTypeRevision(); |
262 | |
263 | const QString absoluteFilePath = fileInfo.absoluteFilePath(); |
264 | if (!QQml_isFileCaseCorrect(fileName: absoluteFilePath)) { |
265 | if (errors) { |
266 | QQmlError error; |
267 | error.setDescription( |
268 | QQmlImportDatabase::tr(sourceText: "File name case mismatch for \"%1\"" ) |
269 | .arg(a: absoluteFilePath)); |
270 | errors->prepend(t: error); |
271 | } |
272 | return QTypeRevision(); |
273 | } |
274 | |
275 | QmlPlugin plugin; |
276 | plugin.loader = std::make_unique<QPluginLoader>(args: absoluteFilePath); |
277 | if (!plugin.loader->load()) { |
278 | if (errors) { |
279 | QQmlError error; |
280 | error.setDescription(plugin.loader->errorString()); |
281 | errors->prepend(t: error); |
282 | } |
283 | return QTypeRevision(); |
284 | } |
285 | |
286 | instance = plugin.loader->instance(); |
287 | plugins->insert(x: std::make_pair(x: pluginId, y: std::move(plugin))); |
288 | |
289 | // Continue with shared code path for dynamic and static plugins: |
290 | if (QQmlMetaType::registerPluginTypes( |
291 | instance, basePath: fileInfo.absolutePath(), uri, typeNamespace: qmldir->typeNamespace(), |
292 | version: importVersion, errors) |
293 | == QQmlMetaType::RegistrationResult::Failure) { |
294 | return QTypeRevision(); |
295 | } |
296 | |
297 | importVersion = QQmlImportDatabase::lockModule( |
298 | uri, typeNamespace: qmldir->typeNamespace(), version: importVersion, errors); |
299 | if (!importVersion.isValid()) |
300 | return QTypeRevision(); |
301 | } else { |
302 | auto it = plugins->find(x: pluginId); |
303 | if (it != plugins->end() && it->second.loader) |
304 | instance = it->second.loader->instance(); |
305 | } |
306 | #else |
307 | // Here plugin is not optional and NOT QT_CONFIG(library) |
308 | // Cannot finalize such plugin and return valid, because no types are registered. |
309 | // Just return invalid. |
310 | if (!optional) |
311 | return QTypeRevision(); |
312 | #endif // QT_CONFIG(library) |
313 | } |
314 | |
315 | // Release the lock on plugins early as we're done with the global part. Releasing the lock |
316 | // also allows other QML loader threads to acquire the lock while this thread is blocking |
317 | // in the initializeEngine call to the gui thread (which in turn may be busy waiting for |
318 | // other QML loader threads and thus not process the initializeEngine call). |
319 | } |
320 | |
321 | if (!engineInitialized) |
322 | finalizePlugin(instance, pluginId); |
323 | |
324 | return QQmlImports::validVersion(version: importVersion); |
325 | } |
326 | |
327 | /*! |
328 | \internal |
329 | |
330 | Searches for a plugin called \a baseName in \a qmldirPluginPath, taking the |
331 | path of the qmldir file itself, and the plugin paths of the QQmlImportDatabase |
332 | into account. |
333 | |
334 | The baseName is amended with a platform-dependent prefix and suffix to |
335 | construct the final plugin file name: |
336 | |
337 | \table |
338 | \header \li Platform \li Prefix \li Valid suffixes |
339 | \row \li Windows \li \li \c .dll, \c .d.dll |
340 | \row \li Unix/Linux \li lib \li \c .so |
341 | \row \li \macos \li lib \li \c .dylib, \c _debug.dylib \c .bundle, \c .so |
342 | \row \li Android \li lib \li \c .so, \c _<ABI>.so |
343 | \endtable |
344 | |
345 | If the \a qmldirPluginPath is absolute, it is searched first. Then each of the |
346 | filePluginPath entries in the QQmlImportDatabase is checked in turn. If the |
347 | entry is relative, it is resolved on top of the path of the qmldir file, |
348 | otherwise it is taken verbatim. If a "." is found in the filePluginPath, and |
349 | \a qmldirPluginPath is relative, then \a qmldirPluginPath is used in its |
350 | place. |
351 | |
352 | TODO: Document the android special casing. |
353 | |
354 | TODO: The above paragraph, as well as the code implementing it makes very |
355 | little sense and is mostly here for backwards compatibility. |
356 | */ |
357 | QString QQmlPluginImporter::resolvePlugin(const QString &qmldirPluginPath, const QString &baseName) |
358 | { |
359 | #if defined(Q_OS_WIN) |
360 | static const QString prefix; |
361 | static const QStringList suffixes = { |
362 | # ifdef QT_DEBUG |
363 | QLatin1String("d.dll" ), // try a qmake-style debug build first |
364 | QLatin1String(".dll" ) |
365 | #else |
366 | QLatin1String(".dll" ), |
367 | QLatin1String("d.dll" ) // try a qmake-style debug build after |
368 | # endif |
369 | }; |
370 | #elif defined(Q_OS_DARWIN) |
371 | static const QString prefix = QLatin1String("lib" ); |
372 | static const QStringList suffixes = { |
373 | # ifdef QT_DEBUG |
374 | QLatin1String("_debug.dylib" ), // try a qmake-style debug build first |
375 | QLatin1String(".dylib" ), |
376 | # else |
377 | QLatin1String(".dylib" ), |
378 | QLatin1String("_debug.dylib" ), // try a qmake-style debug build after |
379 | # endif |
380 | QLatin1String(".so" ), |
381 | QLatin1String(".bundle" ) |
382 | }; |
383 | #else // Unix |
384 | static const QString prefix = QLatin1String("lib" ); |
385 | static const QStringList suffixes = { |
386 | # if defined(Q_OS_ANDROID) |
387 | QStringLiteral(LIBS_SUFFIX), |
388 | # endif |
389 | QLatin1String(".so" ) |
390 | }; |
391 | #endif |
392 | |
393 | QStringList searchPaths = database->filePluginPath; |
394 | bool qmldirPluginPathIsRelative = QDir::isRelativePath(path: qmldirPluginPath); |
395 | if (!qmldirPluginPathIsRelative) |
396 | searchPaths.prepend(t: qmldirPluginPath); |
397 | |
398 | for (const QString &pluginPath : std::as_const(t&: searchPaths)) { |
399 | QString resolvedBasePath; |
400 | if (pluginPath == QLatin1String("." )) { |
401 | if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() |
402 | && qmldirPluginPath != QLatin1String("." )) { |
403 | resolvedBasePath = QDir::cleanPath(path: qmldirPath + u'/' + qmldirPluginPath); |
404 | } else { |
405 | resolvedBasePath = qmldirPath; |
406 | } |
407 | } else { |
408 | if (QDir::isRelativePath(path: pluginPath)) |
409 | resolvedBasePath = QDir::cleanPath(path: qmldirPath + u'/' + pluginPath); |
410 | else |
411 | resolvedBasePath = pluginPath; |
412 | } |
413 | |
414 | // hack for resources, should probably go away |
415 | if (resolvedBasePath.startsWith(c: u':')) |
416 | resolvedBasePath = QCoreApplication::applicationDirPath(); |
417 | |
418 | if (!resolvedBasePath.endsWith(c: u'/')) |
419 | resolvedBasePath += u'/'; |
420 | |
421 | QString resolvedPath = resolvedBasePath + prefix + baseName; |
422 | for (const QString &suffix : suffixes) { |
423 | QString absolutePath = typeLoader->absoluteFilePath(path: resolvedPath + suffix); |
424 | if (!absolutePath.isEmpty()) |
425 | return absolutePath; |
426 | } |
427 | |
428 | #if defined(Q_OS_ANDROID) |
429 | if (qmldirPath.size() > 25 && qmldirPath.at(0) == QLatin1Char(':') |
430 | && qmldirPath.at(1) == QLatin1Char('/') |
431 | && qmldirPath.startsWith(QStringLiteral(":/android_rcc_bundle/qml/" ), |
432 | Qt::CaseInsensitive)) { |
433 | QString pluginName = qmldirPath.mid(21) + u'/' + baseName; |
434 | pluginName.replace(QLatin1Char('/'), QLatin1Char('_')); |
435 | QString bundledPath = resolvedBasePath + QLatin1String("lib" ) + pluginName; |
436 | for (const QString &suffix : suffixes) { |
437 | const QString absolutePath = typeLoader->absoluteFilePath(bundledPath + suffix); |
438 | if (!absolutePath.isEmpty()) { |
439 | qWarning("The implicit resolving of Qml plugin locations using the URI " |
440 | "embedded in the filename has been deprecated. Please use the " |
441 | "modern CMake API to create QML modules or set the name of " |
442 | "QML plugin in qmldir file, that matches the name of plugin " |
443 | "on file system. The correct plugin name is '%s'." , |
444 | qPrintable(pluginName)); |
445 | return absolutePath; |
446 | } |
447 | } |
448 | } |
449 | #endif |
450 | } |
451 | |
452 | qCDebug(lcQmlImport) << "resolvePlugin" << "Could not resolve dynamic plugin with base name" |
453 | << baseName << "in" << qmldirPath |
454 | << " file does not exist" ; |
455 | |
456 | return QString(); |
457 | } |
458 | |
459 | /* |
460 | Get all static plugins that are QML plugins and has a meta data URI that matches with one of |
461 | \a versionUris, which is a list of all possible versioned URI combinations - see versionUriList() |
462 | above. |
463 | */ |
464 | bool QQmlPluginImporter::populatePluginDataVector(QVector<StaticPluginData> &result, const QStringList &versionUris) |
465 | { |
466 | static const QVector<QStaticPlugin> plugins = makePlugins(); |
467 | for (const QStaticPlugin &plugin : plugins) { |
468 | // Since a module can list more than one plugin, |
469 | // we keep iterating even after we found a match. |
470 | QObject *instance = plugin.instance(); |
471 | if (qobject_cast<QQmlEngineExtensionPlugin *>(object: instance) |
472 | || qobject_cast<QQmlExtensionPlugin *>(object: instance)) { |
473 | const QJsonArray metaTagsUriList = plugin.metaData().value( |
474 | QStringLiteral("uri" )).toArray(); |
475 | if (metaTagsUriList.isEmpty()) { |
476 | if (errors) { |
477 | QQmlError error; |
478 | error.setDescription(QQmlImportDatabase::tr( |
479 | sourceText: "static plugin for module \"%1\" with name \"%2\" " |
480 | "has no metadata URI" ) |
481 | .arg(args: uri, args: QString::fromUtf8( |
482 | utf8: instance->metaObject()->className()))); |
483 | error.setUrl(QUrl::fromLocalFile(localfile: qmldir->qmldirLocation())); |
484 | errors->prepend(t: error); |
485 | } |
486 | return false; |
487 | } |
488 | // A plugin can be set up to handle multiple URIs, so go through the list: |
489 | for (const QJsonValueConstRef metaTagUri : metaTagsUriList) { |
490 | if (versionUris.contains(str: metaTagUri.toString())) { |
491 | result.append(t: { .plugin: plugin, .uriList: metaTagsUriList }); |
492 | break; |
493 | } |
494 | } |
495 | } |
496 | } |
497 | return true; |
498 | } |
499 | |
500 | QTypeRevision QQmlPluginImporter::importPlugins() { |
501 | const auto qmldirPlugins = qmldir->plugins(); |
502 | const int qmldirPluginCount = qmldirPlugins.size(); |
503 | QTypeRevision importVersion = version; |
504 | |
505 | // If the path contains a version marker or if we have more than one plugin, |
506 | // we need to use paths. In that case we cannot fall back to other instances |
507 | // of the same module if a qmldir is rejected. However, as we don't generate |
508 | // such modules, it shouldn't be a problem. |
509 | const bool canUseUris = qmldirPluginCount == 1 |
510 | && qmldirPath.endsWith(s: u'/' + QString(uri).replace(before: u'.', after: u'/')); |
511 | const QString moduleId = canUseUris ? uri : qmldir->qmldirLocation(); |
512 | |
513 | if (!database->modulesForWhichPluginsHaveBeenLoaded.contains(value: moduleId)) { |
514 | // First search for listed qmldir plugins dynamically. If we cannot resolve them all, we |
515 | // continue searching static plugins that has correct metadata uri. Note that since we |
516 | // only know the uri for a static plugin, and not the filename, we cannot know which |
517 | // static plugin belongs to which listed plugin inside qmldir. And for this reason, |
518 | // mixing dynamic and static plugins inside a single module is not recommended. |
519 | |
520 | int dynamicPluginsFound = 0; |
521 | int staticPluginsFound = 0; |
522 | |
523 | for (const QQmlDirParser::Plugin &plugin : qmldirPlugins) { |
524 | const QString resolvedFilePath = resolvePlugin(qmldirPluginPath: plugin.path, baseName: plugin.name); |
525 | |
526 | if (!canUseUris && resolvedFilePath.isEmpty()) |
527 | continue; |
528 | |
529 | importVersion = importDynamicPlugin( |
530 | filePath: resolvedFilePath, |
531 | pluginId: canUseUris ? uri : QFileInfo(resolvedFilePath).absoluteFilePath(), |
532 | optional: plugin.optional); |
533 | if (importVersion.isValid()) |
534 | ++dynamicPluginsFound; |
535 | else if (!resolvedFilePath.isEmpty()) |
536 | return QTypeRevision(); |
537 | } |
538 | |
539 | if (dynamicPluginsFound < qmldirPluginCount) { |
540 | // Check if the missing plugins can be resolved statically. We do this by looking at |
541 | // the URIs embedded in a plugins meta data. Since those URIs can be anything from |
542 | // fully versioned to unversioned, we need to compare with differnt version strings. |
543 | // If a module has several plugins, they must all have the same version. Start by |
544 | // populating pluginPairs with relevant plugins to cut the list short early on: |
545 | const QStringList versionUris = versionUriList(uri, version: importVersion); |
546 | QVector<StaticPluginData> pluginPairs; |
547 | if (!populatePluginDataVector(result&: pluginPairs, versionUris)) |
548 | return QTypeRevision(); |
549 | |
550 | for (const QString &versionUri : versionUris) { |
551 | for (const StaticPluginData &pair : std::as_const(t&: pluginPairs)) { |
552 | for (const QJsonValueConstRef metaTagUri : pair.uriList) { |
553 | if (versionUri == metaTagUri.toString()) { |
554 | staticPluginsFound++; |
555 | QObject *instance = pair.plugin.instance(); |
556 | importVersion = importStaticPlugin( |
557 | instance, |
558 | pluginId: canUseUris ? uri : QString::asprintf(format: "%p" , instance)); |
559 | if (!importVersion.isValid()){ |
560 | if (errors) { |
561 | Q_ASSERT(!errors->isEmpty()); |
562 | const QQmlError poppedError = errors->takeFirst(); |
563 | QQmlError error; |
564 | error.setDescription( |
565 | QQmlImportDatabase::tr( |
566 | sourceText: "static plugin for module \"%1\" with " |
567 | "name \"%2\" cannot be loaded: %3" ) |
568 | .arg(args: uri, args: QString::fromUtf8( |
569 | utf8: instance->metaObject()->className()), |
570 | args: poppedError.description())); |
571 | error.setUrl(QUrl::fromLocalFile(localfile: qmldir->qmldirLocation())); |
572 | errors->prepend(t: error); |
573 | } |
574 | return QTypeRevision(); |
575 | } |
576 | |
577 | qCDebug(lcQmlImport) |
578 | << "importExtension" << "loaded static plugin " << versionUri; |
579 | |
580 | break; |
581 | } |
582 | } |
583 | } |
584 | if (staticPluginsFound > 0) |
585 | break; |
586 | } |
587 | } |
588 | |
589 | if ((dynamicPluginsFound + staticPluginsFound) < qmldirPluginCount) { |
590 | if (errors) { |
591 | QQmlError error; |
592 | if (qmldirPluginCount > 1 && staticPluginsFound > 0) { |
593 | error.setDescription(QQmlImportDatabase::tr( |
594 | sourceText: "could not resolve all plugins for module \"%1\"" ) |
595 | .arg(a: uri)); |
596 | } else { |
597 | error.setDescription(QQmlImportDatabase::tr( |
598 | sourceText: "module \"%1\" plugin \"%2\" not found" ) |
599 | .arg(args: uri, args: qmldirPlugins[dynamicPluginsFound].name)); |
600 | } |
601 | error.setUrl(QUrl::fromLocalFile(localfile: qmldir->qmldirLocation())); |
602 | errors->prepend(t: error); |
603 | } |
604 | return QTypeRevision(); |
605 | } |
606 | |
607 | database->modulesForWhichPluginsHaveBeenLoaded.insert(value: moduleId); |
608 | } |
609 | return QQmlImports::validVersion(version: importVersion); |
610 | } |
611 | |
612 | QT_END_NAMESPACE |
613 | |