1 | // Copyright (C) 2017 Crimson AS <info@crimson.no> |
2 | // Copyright (C) 2016 The Qt Company Ltd. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include "qqmlimport_p.h" |
6 | |
7 | #include <QtCore/qdebug.h> |
8 | #include <QtCore/qdir.h> |
9 | #include <QtQml/qqmlfile.h> |
10 | #include <QtCore/qfileinfo.h> |
11 | #include <QtCore/qpluginloader.h> |
12 | #include <QtCore/qlibraryinfo.h> |
13 | #include <QtCore/qloggingcategory.h> |
14 | #include <QtQml/qqmlextensioninterface.h> |
15 | #include <QtQml/qqmlextensionplugin.h> |
16 | #include <private/qqmlextensionplugin_p.h> |
17 | #include <private/qqmlglobal_p.h> |
18 | #include <private/qqmltypenamecache_p.h> |
19 | #include <private/qqmlengine_p.h> |
20 | #include <private/qqmltypemodule_p.h> |
21 | #include <private/qqmltypeloaderqmldircontent_p.h> |
22 | #include <private/qqmlpluginimporter_p.h> |
23 | #include <QtCore/qjsonobject.h> |
24 | #include <QtCore/qjsonarray.h> |
25 | #include <QtQml/private/qqmltype_p_p.h> |
26 | #include <QtQml/private/qqmlimportresolver_p.h> |
27 | |
28 | #ifdef Q_OS_MACOS |
29 | #include "private/qcore_mac_p.h" |
30 | #endif |
31 | |
32 | #include <algorithm> |
33 | #include <functional> |
34 | |
35 | using namespace Qt::Literals::StringLiterals; |
36 | |
37 | QT_BEGIN_NAMESPACE |
38 | |
39 | DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE) |
40 | |
41 | class QmlImportCategoryHolder |
42 | { |
43 | Q_DISABLE_COPY_MOVE(QmlImportCategoryHolder); |
44 | public: |
45 | |
46 | QmlImportCategoryHolder() : m_category("qt.qml.import" ) |
47 | { |
48 | // We have to explicitly setEnabled() here because for categories that start with |
49 | // "qt." it won't accept QtDebugMsg as argument. Debug messages are off by default |
50 | // for all Qt logging categories. |
51 | if (qmlImportTrace()) |
52 | m_category.setEnabled(type: QtDebugMsg, enable: true); |
53 | } |
54 | |
55 | ~QmlImportCategoryHolder() = default; |
56 | |
57 | const QLoggingCategory &category() const { return m_category; } |
58 | |
59 | private: |
60 | QLoggingCategory m_category; |
61 | }; |
62 | |
63 | const QLoggingCategory &lcQmlImport() |
64 | { |
65 | static const QmlImportCategoryHolder holder; |
66 | return holder.category(); |
67 | } |
68 | |
69 | DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES) |
70 | |
71 | static const QLatin1Char Dot('.'); |
72 | static const QLatin1Char Slash('/'); |
73 | static const QLatin1Char Backslash('\\'); |
74 | static const QLatin1Char Colon(':'); |
75 | static const QLatin1String Slash_qmldir("/qmldir" ); |
76 | static const QLatin1String String_qmldir("qmldir" ); |
77 | static const QString dotqml_string(QStringLiteral(".qml" )); |
78 | static const QString dotuidotqml_string(QStringLiteral(".ui.qml" )); |
79 | static bool designerSupportRequired = false; |
80 | |
81 | namespace { |
82 | |
83 | QTypeRevision relevantVersion(const QString &uri, QTypeRevision version) |
84 | { |
85 | return QQmlMetaType::latestModuleVersion(uri).isValid() ? version : QTypeRevision(); |
86 | } |
87 | |
88 | QQmlError moduleNotFoundError(const QString &uri, QTypeRevision version) |
89 | { |
90 | QQmlError error; |
91 | if (version.hasMajorVersion()) { |
92 | error.setDescription(QQmlImportDatabase::tr( |
93 | sourceText: "module \"%1\" version %2.%3 is not installed" ) |
94 | .arg(a: uri).arg(a: version.majorVersion()) |
95 | .arg(a: version.hasMinorVersion() |
96 | ? QString::number(version.minorVersion()) |
97 | : QLatin1String("x" ))); |
98 | } else { |
99 | error.setDescription(QQmlImportDatabase::tr(sourceText: "module \"%1\" is not installed" ) |
100 | .arg(a: uri)); |
101 | } |
102 | return error; |
103 | } |
104 | |
105 | QString resolveLocalUrl(const QString &url, const QString &relative) |
106 | { |
107 | if (relative.contains(c: Colon)) { |
108 | // contains a host name |
109 | return QUrl(url).resolved(relative: QUrl(relative)).toString(); |
110 | } else if (relative.isEmpty()) { |
111 | return url; |
112 | } else if (relative.at(i: 0) == Slash || !url.contains(c: Slash)) { |
113 | return relative; |
114 | } else { |
115 | const QStringView baseRef = QStringView{url}.left(n: url.lastIndexOf(c: Slash) + 1); |
116 | if (relative == QLatin1String("." )) |
117 | return baseRef.toString(); |
118 | |
119 | QString base = baseRef + relative; |
120 | |
121 | // Remove any relative directory elements in the path |
122 | int length = base.size(); |
123 | int index = 0; |
124 | while ((index = base.indexOf(s: QLatin1String("/." ), from: index)) != -1) { |
125 | if ((length > (index + 2)) && (base.at(i: index + 2) == Dot) && |
126 | (length == (index + 3) || (base.at(i: index + 3) == Slash))) { |
127 | // Either "/../" or "/..<END>" |
128 | int previous = base.lastIndexOf(c: Slash, from: index - 1); |
129 | if (previous == -1) |
130 | break; |
131 | |
132 | int removeLength = (index - previous) + 3; |
133 | base.remove(i: previous + 1, len: removeLength); |
134 | length -= removeLength; |
135 | index = previous; |
136 | } else if ((length == (index + 2)) || (base.at(i: index + 2) == Slash)) { |
137 | // Either "/./" or "/.<END>" |
138 | base.remove(i: index, len: 2); |
139 | length -= 2; |
140 | } else { |
141 | ++index; |
142 | } |
143 | } |
144 | |
145 | return base; |
146 | } |
147 | } |
148 | |
149 | bool isPathAbsolute(const QString &path) |
150 | { |
151 | #if defined(Q_OS_UNIX) |
152 | return (path.at(i: 0) == Slash); |
153 | #else |
154 | QFileInfo fi(path); |
155 | return fi.isAbsolute(); |
156 | #endif |
157 | } |
158 | |
159 | } // namespace |
160 | |
161 | /*! |
162 | \internal |
163 | \class QQmlImportInstance |
164 | |
165 | A QQmlImportType represents a single import of a document, held within a |
166 | namespace. |
167 | |
168 | \note The uri here may not necessarily be unique (e.g. for file imports). |
169 | |
170 | \note Version numbers may be -1 for file imports: this means that no |
171 | version was specified as part of the import. Type resolution will be |
172 | responsible for attempting to find the "best" possible version. |
173 | */ |
174 | |
175 | /*! |
176 | \internal |
177 | \class QQmlImportNamespace |
178 | |
179 | A QQmlImportNamespace is a way of seperating imports into a local namespace. |
180 | |
181 | Within a QML document, there is at least one namespace (the |
182 | "unqualified set") where imports without a qualifier are placed, i.e: |
183 | |
184 | import QtQuick 2.6 |
185 | |
186 | will have a single namespace (the unqualified set) containing a single import |
187 | for QtQuick 2.6. However, there may be others if an import statement gives |
188 | a qualifier, i.e the following will result in an additional new |
189 | QQmlImportNamespace in the qualified set: |
190 | |
191 | import MyFoo 1.0 as Foo |
192 | */ |
193 | |
194 | /*! |
195 | \class QQmlImports |
196 | \brief The QQmlImports class encapsulates one QML document's import statements. |
197 | \internal |
198 | */ |
199 | |
200 | QTypeRevision QQmlImports::validVersion(QTypeRevision version) |
201 | { |
202 | // If the given version is invalid, return a valid but useless version to signal "It's OK". |
203 | return version.isValid() ? version : QTypeRevision::fromMinorVersion(minorVersion: 0); |
204 | } |
205 | |
206 | /*! |
207 | Sets the base URL to be used for all relative file imports added. |
208 | */ |
209 | void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString) |
210 | { |
211 | m_baseUrl = url; |
212 | |
213 | if (urlString.isEmpty()) |
214 | m_base = url.toString(); |
215 | else |
216 | m_base = urlString; |
217 | } |
218 | |
219 | /*! |
220 | \fn QQmlImports::baseUrl() |
221 | \internal |
222 | |
223 | Returns the base URL to be used for all relative file imports added. |
224 | */ |
225 | |
226 | /* |
227 | \internal |
228 | |
229 | This method is responsible for populating data of all types visible in this |
230 | document's imports into the \a cache for resolution elsewhere (e.g. in JS, |
231 | or when loading additional types). |
232 | |
233 | \note This is for C++ types only. Composite types are handled separately, |
234 | as they do not have a QQmlTypeModule. |
235 | */ |
236 | void QQmlImports::populateCache(QQmlTypeNameCache *cache) const |
237 | { |
238 | const QQmlImportNamespace &set = m_unqualifiedset; |
239 | |
240 | for (int ii = set.imports.size() - 1; ii >= 0; --ii) { |
241 | const QQmlImportInstance *import = set.imports.at(i: ii); |
242 | QQmlTypeModule *module = QQmlMetaType::typeModule(uri: import->uri, version: import->version); |
243 | if (module) { |
244 | cache->m_anonymousImports.append(t: QQmlTypeModuleVersion(module, import->version)); |
245 | } |
246 | } |
247 | |
248 | for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(v: ns)) { |
249 | |
250 | const QQmlImportNamespace &set = *ns; |
251 | |
252 | // positioning is important; we must create the namespace even if there is no module. |
253 | QQmlImportRef &typeimport = cache->m_namedImports[set.prefix]; |
254 | typeimport.m_qualifier = set.prefix; |
255 | |
256 | for (int ii = set.imports.size() - 1; ii >= 0; --ii) { |
257 | const QQmlImportInstance *import = set.imports.at(i: ii); |
258 | QQmlTypeModule *module = QQmlMetaType::typeModule(uri: import->uri, version: import->version); |
259 | if (module) { |
260 | QQmlImportRef &typeimport = cache->m_namedImports[set.prefix]; |
261 | typeimport.modules.append(t: QQmlTypeModuleVersion(module, import->version)); |
262 | } |
263 | } |
264 | } |
265 | } |
266 | |
267 | // We need to exclude the entry for the current baseUrl. This can happen for example |
268 | // when handling qmldir files on the remote dir case and the current type is marked as |
269 | // singleton. |
270 | bool excludeBaseUrl(const QString &importUrl, const QString &fileName, const QString &baseUrl) |
271 | { |
272 | if (importUrl.isEmpty()) |
273 | return false; |
274 | |
275 | if (baseUrl.startsWith(s: importUrl)) |
276 | { |
277 | if (fileName == QStringView{baseUrl}.mid(pos: importUrl.size())) |
278 | return false; |
279 | } |
280 | |
281 | return true; |
282 | } |
283 | |
284 | void findCompositeSingletons(const QQmlImportNamespace &set, QList<QQmlImports::CompositeSingletonReference> &resultList, const QUrl &baseUrl) |
285 | { |
286 | typedef QQmlDirComponents::const_iterator ConstIterator; |
287 | |
288 | for (int ii = set.imports.size() - 1; ii >= 0; --ii) { |
289 | const QQmlImportInstance *import = set.imports.at(i: ii); |
290 | |
291 | const QQmlDirComponents &components = import->qmlDirComponents; |
292 | |
293 | const QTypeRevision importVersion = import->version; |
294 | auto shouldSkipSingleton = [importVersion](QTypeRevision singletonVersion) -> bool { |
295 | return importVersion.hasMajorVersion() && |
296 | (singletonVersion.majorVersion() > importVersion.majorVersion() |
297 | || (singletonVersion.majorVersion() == importVersion.majorVersion() |
298 | && singletonVersion.minorVersion() > importVersion.minorVersion())); |
299 | }; |
300 | |
301 | ConstIterator cend = components.constEnd(); |
302 | for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) { |
303 | if (cit->singleton && excludeBaseUrl(importUrl: import->url, fileName: cit->fileName, baseUrl: baseUrl.toString())) { |
304 | if (shouldSkipSingleton(cit->version)) |
305 | continue; |
306 | QQmlImports::CompositeSingletonReference ref; |
307 | ref.typeName = cit->typeName; |
308 | ref.prefix = set.prefix; |
309 | ref.version = cit->version; |
310 | resultList.append(t: ref); |
311 | } |
312 | } |
313 | |
314 | if (QQmlTypeModule *module = QQmlMetaType::typeModule(uri: import->uri, version: import->version)) { |
315 | module->walkCompositeSingletons(callback: [&resultList, &set, &shouldSkipSingleton](const QQmlType &singleton) { |
316 | if (shouldSkipSingleton(singleton.version())) |
317 | return; |
318 | QQmlImports::CompositeSingletonReference ref; |
319 | ref.typeName = singleton.elementName(); |
320 | ref.prefix = set.prefix; |
321 | ref.version = singleton.version(); |
322 | resultList.append(t: ref); |
323 | }); |
324 | } |
325 | } |
326 | } |
327 | |
328 | /* |
329 | \internal |
330 | |
331 | Returns a list of all composite singletons present in this document's |
332 | imports. |
333 | |
334 | This information is used by QQmlTypeLoader to ensure that composite singletons |
335 | are marked as dependencies during type loading. |
336 | */ |
337 | QList<QQmlImports::CompositeSingletonReference> QQmlImports::resolvedCompositeSingletons() const |
338 | { |
339 | QList<QQmlImports::CompositeSingletonReference> compositeSingletons; |
340 | |
341 | const QQmlImportNamespace &set = m_unqualifiedset; |
342 | findCompositeSingletons(set, resultList&: compositeSingletons, baseUrl: baseUrl()); |
343 | |
344 | for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(v: ns)) { |
345 | const QQmlImportNamespace &set = *ns; |
346 | findCompositeSingletons(set, resultList&: compositeSingletons, baseUrl: baseUrl()); |
347 | } |
348 | |
349 | std::stable_sort(first: compositeSingletons.begin(), last: compositeSingletons.end(), |
350 | comp: [](const QQmlImports::CompositeSingletonReference &lhs, |
351 | const QQmlImports::CompositeSingletonReference &rhs) { |
352 | if (lhs.prefix != rhs.prefix) |
353 | return lhs.prefix < rhs.prefix; |
354 | |
355 | if (lhs.typeName != rhs.typeName) |
356 | return lhs.typeName < rhs.typeName; |
357 | |
358 | return lhs.version.majorVersion() != rhs.version.majorVersion() |
359 | ? lhs.version.majorVersion() < rhs.version.majorVersion() |
360 | : lhs.version.minorVersion() < rhs.version.minorVersion(); |
361 | }); |
362 | |
363 | return compositeSingletons; |
364 | } |
365 | |
366 | /* |
367 | \internal |
368 | |
369 | Returns a list of scripts imported by this document. This is used by |
370 | QQmlTypeLoader to properly handle dependencies on imported scripts. |
371 | */ |
372 | QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const |
373 | { |
374 | QList<QQmlImports::ScriptReference> scripts; |
375 | |
376 | const QQmlImportNamespace &set = m_unqualifiedset; |
377 | |
378 | for (int ii = set.imports.size() - 1; ii >= 0; --ii) { |
379 | const QQmlImportInstance *import = set.imports.at(i: ii); |
380 | |
381 | for (const QQmlDirParser::Script &script : import->qmlDirScripts) { |
382 | ScriptReference ref; |
383 | ref.nameSpace = script.nameSpace; |
384 | ref.location = QUrl(import->url).resolved(relative: QUrl(script.fileName)); |
385 | scripts.append(t: ref); |
386 | } |
387 | } |
388 | |
389 | for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(v: ns)) { |
390 | const QQmlImportNamespace &set = *ns; |
391 | |
392 | for (int ii = set.imports.size() - 1; ii >= 0; --ii) { |
393 | const QQmlImportInstance *import = set.imports.at(i: ii); |
394 | |
395 | for (const QQmlDirParser::Script &script : import->qmlDirScripts) { |
396 | ScriptReference ref; |
397 | ref.nameSpace = script.nameSpace; |
398 | ref.qualifier = set.prefix; |
399 | ref.location = QUrl(import->url).resolved(relative: QUrl(script.fileName)); |
400 | scripts.append(t: ref); |
401 | } |
402 | } |
403 | } |
404 | |
405 | return scripts; |
406 | } |
407 | |
408 | /*! |
409 | Forms complete paths to a qmldir file, from a base URL, a module URI and version specification. |
410 | |
411 | For example, QtQml.Models 2.0: |
412 | - base/QtQml/Models.2.0/qmldir |
413 | - base/QtQml.2.0/Models/qmldir |
414 | - base/QtQml/Models.2/qmldir |
415 | - base/QtQml.2/Models/qmldir |
416 | - base/QtQml/Models/qmldir |
417 | */ |
418 | QStringList QQmlImports::completeQmldirPaths(const QString &uri, const QStringList &basePaths, |
419 | QTypeRevision version) |
420 | { |
421 | QStringList paths = qQmlResolveImportPaths(uri, basePaths, version); |
422 | for (QString &path : paths) |
423 | path += Slash_qmldir; |
424 | return paths; |
425 | } |
426 | |
427 | QString QQmlImports::versionString(QTypeRevision version, ImportVersion versionMode) |
428 | { |
429 | if (versionMode == QQmlImports::FullyVersioned) { |
430 | // extension with fully encoded version number (eg. MyModule.3.2) |
431 | return QString::asprintf(format: ".%d.%d" , version.majorVersion(), version.minorVersion()); |
432 | } else if (versionMode == QQmlImports::PartiallyVersioned) { |
433 | // extension with encoded version major (eg. MyModule.3) |
434 | return QString::asprintf(format: ".%d" , version.majorVersion()); |
435 | } // else extension without version number (eg. MyModule) |
436 | return QString(); |
437 | } |
438 | |
439 | /*! |
440 | \internal |
441 | |
442 | The given (namespace qualified) \a type is resolved to either |
443 | \list |
444 | \li a QQmlImportNamespace stored at \a ns_return, or |
445 | \li a QQmlType stored at \a type_return, |
446 | \endlist |
447 | |
448 | If any return pointer is 0, the corresponding search is not done. |
449 | |
450 | \sa addFileImport(), addLibraryImport |
451 | */ |
452 | bool QQmlImports::resolveType( |
453 | const QHashedStringRef &type, QQmlType *type_return, QTypeRevision *version_return, |
454 | QQmlImportNamespace **ns_return, QList<QQmlError> *errors, |
455 | QQmlType::RegistrationType registrationType, bool *typeRecursionDetected) const |
456 | { |
457 | QQmlImportNamespace *ns = findQualifiedNamespace(type); |
458 | if (ns) { |
459 | if (ns_return) |
460 | *ns_return = ns; |
461 | return true; |
462 | } |
463 | if (type_return) { |
464 | if (resolveType(type, version_return, type_return, errors, registrationType, |
465 | typeRecursionDetected)) { |
466 | if (lcQmlImport().isDebugEnabled()) { |
467 | #define RESOLVE_TYPE_DEBUG qCDebug(lcQmlImport) \ |
468 | << "resolveType:" << qPrintable(baseUrl().toString()) << type.toString() << " => " |
469 | |
470 | if (type_return && type_return->isValid()) { |
471 | if (type_return->isCompositeSingleton()) |
472 | RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL-SINGLETON" ; |
473 | else if (type_return->isComposite()) |
474 | RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE/URL" ; |
475 | else if (type_return->isInlineComponentType()) |
476 | RESOLVE_TYPE_DEBUG << type_return->typeName() << ' ' << type_return->sourceUrl() << " TYPE(INLINECOMPONENT)" ; |
477 | else |
478 | RESOLVE_TYPE_DEBUG << type_return->typeName() << " TYPE" ; |
479 | } |
480 | #undef RESOLVE_TYPE_DEBUG |
481 | } |
482 | return true; |
483 | } |
484 | } |
485 | return false; |
486 | } |
487 | |
488 | bool QQmlImportInstance::setQmldirContent(const QString &resolvedUrl, |
489 | const QQmlTypeLoaderQmldirContent &qmldir, |
490 | QQmlImportNamespace *nameSpace, QList<QQmlError> *errors) |
491 | { |
492 | |
493 | const QString preferredPath = qmldir.preferredPath(); |
494 | if (preferredPath.isEmpty()) { |
495 | Q_ASSERT(resolvedUrl.endsWith(Slash)); |
496 | url = resolvedUrl; |
497 | } else { |
498 | Q_ASSERT(preferredPath.endsWith(Slash)); |
499 | if (preferredPath.startsWith(c: u':')) |
500 | url = QStringLiteral("qrc" ) + preferredPath; |
501 | else |
502 | url = QUrl::fromLocalFile(localfile: preferredPath).toString(); |
503 | } |
504 | |
505 | qmlDirComponents = qmldir.components(); |
506 | |
507 | const QQmlDirScripts &scripts = qmldir.scripts(); |
508 | if (!scripts.isEmpty()) { |
509 | // Verify that we haven't imported these scripts already |
510 | for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin(); |
511 | it != nameSpace->imports.constEnd(); ++it) { |
512 | if ((*it != this) && ((*it)->uri == uri)) { |
513 | QQmlError error; |
514 | error.setDescription( |
515 | QQmlImportDatabase::tr(sourceText: "\"%1\" is ambiguous. Found in %2 and in %3" ) |
516 | .arg(args&: uri, args&: url, args&: (*it)->url)); |
517 | errors->prepend(t: error); |
518 | return false; |
519 | } |
520 | } |
521 | |
522 | qmlDirScripts = getVersionedScripts(qmldirscripts: scripts, version); |
523 | } |
524 | |
525 | return true; |
526 | } |
527 | |
528 | QQmlDirScripts QQmlImportInstance::getVersionedScripts(const QQmlDirScripts &qmldirscripts, |
529 | QTypeRevision version) |
530 | { |
531 | QMap<QString, QQmlDirParser::Script> versioned; |
532 | |
533 | for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin(); |
534 | sit != qmldirscripts.constEnd(); ++sit) { |
535 | // Only include scripts that match our requested version |
536 | if ((!version.hasMajorVersion() || (sit->version.majorVersion() == version.majorVersion())) |
537 | && (!version.hasMinorVersion() |
538 | || (sit->version.minorVersion() <= version.minorVersion()))) { |
539 | // Load the highest version that matches |
540 | QMap<QString, QQmlDirParser::Script>::iterator vit = versioned.find(key: sit->nameSpace); |
541 | if (vit == versioned.end() |
542 | || (vit->version.minorVersion() < sit->version.minorVersion())) { |
543 | versioned.insert(key: sit->nameSpace, value: *sit); |
544 | } |
545 | } |
546 | } |
547 | |
548 | return versioned.values(); |
549 | } |
550 | |
551 | /*! |
552 | \fn QQmlImports::resolveType(QQmlImportNamespace *ns, const QHashedStringRef &type, QQmlType *type_return, QTypeRevision *version_return, QQmlType::RegistrationType registrationType = QQmlType::AnyRegistrationType) const |
553 | \internal |
554 | |
555 | Searching \e only in the namespace \a ns (previously returned in a call to |
556 | resolveType(), \a type is found and returned to |
557 | a QQmlType stored at \a type_return. If the type is from a QML file, the returned |
558 | type will be a CompositeType. |
559 | |
560 | If the return pointer is 0, the corresponding search is not done. |
561 | */ |
562 | |
563 | bool QQmlImportInstance::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type, |
564 | QTypeRevision *version_return, QQmlType *type_return, |
565 | const QString *base, bool *typeRecursionDetected, |
566 | QQmlType::RegistrationType registrationType, |
567 | QQmlImport::RecursionRestriction recursionRestriction, |
568 | QList<QQmlError> *errors) const |
569 | { |
570 | QQmlType t = QQmlMetaType::qmlType(name: type, module: uri, version); |
571 | if (t.isValid()) { |
572 | if (version_return) |
573 | *version_return = version; |
574 | if (type_return) |
575 | *type_return = t; |
576 | return true; |
577 | } |
578 | |
579 | const QString typeStr = type.toString(); |
580 | if (isInlineComponent) { |
581 | Q_ASSERT(type_return); |
582 | bool ret = uri == typeStr; |
583 | if (ret) { |
584 | Q_ASSERT(!type_return->isValid()); |
585 | auto createICType = [&]() { |
586 | auto typePriv = new QQmlTypePrivate {QQmlType::RegistrationType::InlineComponentType}; |
587 | const QUrl ownUrl = QUrl(url); |
588 | typePriv->elementName = ownUrl.fragment(); |
589 | Q_ASSERT(!typePriv->elementName.isEmpty()); |
590 | typePriv->extraData.id->url = ownUrl; |
591 | auto icType = QQmlType(typePriv); |
592 | typePriv->release(); |
593 | return icType; |
594 | }; |
595 | if (containingType.isValid()) { |
596 | // we currently cannot reference a Singleton inside itself |
597 | // in that case, containingType is still invalid |
598 | if (QQmlType ic = QQmlMetaType::inlineComponentType(containingType, name: typeStr); |
599 | ic.isValid()) { |
600 | *type_return = ic; |
601 | } else { |
602 | auto icType = createICType(); |
603 | QQmlMetaType::associateInlineComponent( |
604 | containingType, name: typeStr, metaTypeIds: CompositeMetaTypeIds {}, existingType: icType); |
605 | *type_return = QQmlType(icType); |
606 | } |
607 | } else { |
608 | *type_return = createICType(); |
609 | } |
610 | } |
611 | return ret; |
612 | } |
613 | QQmlDirComponents::ConstIterator it = qmlDirComponents.find(key: typeStr), end = qmlDirComponents.end(); |
614 | if (it != end) { |
615 | QString componentUrl; |
616 | bool isCompositeSingleton = false; |
617 | QQmlDirComponents::ConstIterator candidate = end; |
618 | for ( ; it != end && it.key() == typeStr; ++it) { |
619 | const QQmlDirParser::Component &c = *it; |
620 | switch (registrationType) { |
621 | case QQmlType::AnyRegistrationType: |
622 | break; |
623 | case QQmlType::CompositeSingletonType: |
624 | if (!c.singleton) |
625 | continue; |
626 | break; |
627 | default: |
628 | if (c.singleton) |
629 | continue; |
630 | break; |
631 | } |
632 | |
633 | // importing invalid version means import ALL versions |
634 | if (!version.hasMajorVersion() || (implicitlyImported && c.internal) |
635 | // allow the implicit import of internal types |
636 | || (c.version.majorVersion() == version.majorVersion() |
637 | && c.version.minorVersion() <= version.minorVersion())) { |
638 | // Is this better than the previous candidate? |
639 | if ((candidate == end) |
640 | || (c.version.majorVersion() > candidate->version.majorVersion()) |
641 | || ((c.version.majorVersion() == candidate->version.majorVersion()) |
642 | && (c.version.minorVersion() > candidate->version.minorVersion()))) { |
643 | if (base) { |
644 | componentUrl = resolveLocalUrl(url: QString(url + c.typeName + dotqml_string), relative: c.fileName); |
645 | if (c.internal) { |
646 | if (resolveLocalUrl(url: *base, relative: c.fileName) != componentUrl) |
647 | continue; // failed attempt to access an internal type |
648 | } |
649 | |
650 | const bool recursion = *base == componentUrl; |
651 | if (typeRecursionDetected) |
652 | *typeRecursionDetected = recursion; |
653 | |
654 | if (recursionRestriction == QQmlImport::PreventRecursion && recursion) { |
655 | continue; // no recursion |
656 | } |
657 | } |
658 | |
659 | // This is our best candidate so far |
660 | candidate = it; |
661 | isCompositeSingleton = c.singleton; |
662 | } |
663 | } |
664 | } |
665 | |
666 | if (candidate != end) { |
667 | if (!base) // ensure we have a componentUrl |
668 | componentUrl = resolveLocalUrl(url: QString(url + candidate->typeName + dotqml_string), relative: candidate->fileName); |
669 | QQmlType returnType = QQmlMetaType::typeForUrl(urlString: componentUrl, typeName: type, isCompositeSingleton, |
670 | errors: nullptr, version: candidate->version); |
671 | if (version_return) |
672 | *version_return = candidate->version; |
673 | if (type_return) |
674 | *type_return = returnType; |
675 | return returnType.isValid(); |
676 | } |
677 | } else if (!isLibrary) { |
678 | // the base path of the import if it's a local file |
679 | const QString localDirectoryPath = QQmlFile::urlToLocalFileOrQrc(url); |
680 | if (localDirectoryPath.isEmpty()) |
681 | return false; |
682 | |
683 | QString qmlUrl; |
684 | bool exists = false; |
685 | |
686 | const QString urlsToTry[2] = { |
687 | typeStr + dotqml_string, // Type -> Type.qml |
688 | typeStr + dotuidotqml_string // Type -> Type.ui.qml |
689 | }; |
690 | for (const QString &urlToTry : urlsToTry) { |
691 | exists = typeLoader->fileExists(path: localDirectoryPath, file: urlToTry); |
692 | if (exists) { |
693 | #if defined(Q_OS_MACOS) || defined(Q_OS_WIN) |
694 | // don't let function.qml confuse the use of "new Function(...)" for example. |
695 | if (!QQml_isFileCaseCorrect(localDirectoryPath + urlToTry)) { |
696 | exists = false; |
697 | if (errors) { |
698 | QQmlError caseError; |
699 | caseError.setDescription(QLatin1String("File name case mismatch" )); |
700 | errors->append(caseError); |
701 | } |
702 | break; |
703 | } |
704 | #else |
705 | Q_UNUSED(errors); |
706 | #endif |
707 | qmlUrl = url + urlToTry; |
708 | break; |
709 | } |
710 | } |
711 | |
712 | if (exists) { |
713 | const bool recursion = base && *base == qmlUrl; |
714 | if (typeRecursionDetected) |
715 | *typeRecursionDetected = recursion; |
716 | if (recursionRestriction == QQmlImport::AllowRecursion || !recursion) { |
717 | QQmlType returnType = QQmlMetaType::typeForUrl( |
718 | urlString: qmlUrl, typeName: type, isCompositeSingleton: registrationType == QQmlType::CompositeSingletonType, errors); |
719 | if (type_return) |
720 | *type_return = returnType; |
721 | return returnType.isValid(); |
722 | } |
723 | } |
724 | } |
725 | |
726 | return false; |
727 | } |
728 | |
729 | bool QQmlImports::resolveType( |
730 | const QHashedStringRef &type, QTypeRevision *version_return, QQmlType *type_return, |
731 | QList<QQmlError> *errors, QQmlType::RegistrationType registrationType, |
732 | bool *typeRecursionDetected) const |
733 | { |
734 | const QVector<QHashedStringRef> splitName = type.split(sep: Dot); |
735 | auto resolveTypeInNamespace = [&]( |
736 | QHashedStringRef unqualifiedtype, QQmlImportNamespace *nameSpace, |
737 | QList<QQmlError> *errors) -> bool { |
738 | if (nameSpace->resolveType( |
739 | typeLoader: m_typeLoader, type: unqualifiedtype, version_return, type_return, base: &m_base, errors, |
740 | registrationType, typeRecursionDeteced: typeRecursionDetected)) |
741 | return true; |
742 | if (nameSpace->imports.size() == 1 |
743 | && !nameSpace->imports.at(i: 0)->isLibrary |
744 | && type_return |
745 | && nameSpace != &m_unqualifiedset) { |
746 | // qualified, and only 1 url |
747 | *type_return = QQmlMetaType::typeForUrl( |
748 | urlString: resolveLocalUrl(url: nameSpace->imports.at(i: 0)->url, |
749 | relative: unqualifiedtype.toString() + QLatin1String(".qml" )), |
750 | typeName: type, isCompositeSingleton: false, errors); |
751 | return type_return->isValid(); |
752 | } |
753 | return false; |
754 | }; |
755 | switch (splitName.size()) { |
756 | case 1: { |
757 | // must be a simple type |
758 | return resolveTypeInNamespace(type, &m_unqualifiedset, errors); |
759 | } |
760 | case 2: { |
761 | // either namespace + simple type OR simple type + inline component |
762 | QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(i: 0)); |
763 | if (s) { |
764 | // namespace + simple type |
765 | return resolveTypeInNamespace(splitName.at(i: 1), s, errors); |
766 | } else { |
767 | if (resolveTypeInNamespace(splitName.at(i: 0), &m_unqualifiedset, nullptr)) { |
768 | // either simple type + inline component |
769 | const QString icName = splitName.at(i: 1).toString(); |
770 | const QQmlType ic = QQmlMetaType::inlineComponentType(containingType: *type_return, name: icName); |
771 | if (ic.isValid()) { |
772 | *type_return = ic; |
773 | } else { |
774 | auto icTypePriv = new QQmlTypePrivate(QQmlType::RegistrationType::InlineComponentType); |
775 | icTypePriv->setContainingType(type_return); |
776 | icTypePriv->extraData.id->url = type_return->sourceUrl(); |
777 | icTypePriv->extraData.id->url.setFragment(fragment: icName); |
778 | auto icType = QQmlType(icTypePriv); |
779 | icTypePriv->release(); |
780 | QQmlMetaType::associateInlineComponent( |
781 | containingType: *type_return, name: icName, metaTypeIds: CompositeMetaTypeIds {}, existingType: icType); |
782 | *type_return = icType; |
783 | } |
784 | Q_ASSERT(type_return->containingType().isValid()); |
785 | return true; |
786 | } else { |
787 | // or a failure |
788 | if (errors) { |
789 | QQmlError error; |
790 | error.setDescription(QQmlImportDatabase::tr(sourceText: "- %1 is neither a type nor a namespace" ).arg(a: splitName.at(i: 0).toString())); |
791 | errors->prepend(t: error); |
792 | } |
793 | return false; |
794 | } |
795 | } |
796 | } |
797 | case 3: { |
798 | // must be namespace + simple type + inline component |
799 | QQmlImportNamespace *s = findQualifiedNamespace(splitName.at(i: 0)); |
800 | QQmlError error; |
801 | if (!s) { |
802 | error.setDescription(QQmlImportDatabase::tr(sourceText: "- %1 is not a namespace" ).arg(a: splitName.at(i: 0).toString())); |
803 | } else { |
804 | if (resolveTypeInNamespace(splitName.at(i: 1), s, nullptr)) { |
805 | auto const icName = splitName.at(i: 2).toString(); |
806 | QQmlType ic = QQmlMetaType::inlineComponentType(containingType: *type_return, name: icName); |
807 | if (ic.isValid()) |
808 | *type_return = ic; |
809 | else { |
810 | auto icTypePriv = new QQmlTypePrivate(QQmlType::RegistrationType::InlineComponentType); |
811 | icTypePriv->setContainingType(type_return); |
812 | icTypePriv->extraData.id->url = type_return->sourceUrl(); |
813 | icTypePriv->extraData.id->url.setFragment(fragment: icName); |
814 | auto icType = QQmlType(icTypePriv); |
815 | icTypePriv->release(); |
816 | QQmlMetaType::associateInlineComponent( |
817 | containingType: *type_return, name: icName, metaTypeIds: CompositeMetaTypeIds {}, existingType: icType); |
818 | *type_return = icType; |
819 | } |
820 | return true; |
821 | } else { |
822 | error.setDescription(QQmlImportDatabase::tr(sourceText: "- %1 is not a type" ).arg(a: splitName.at(i: 1).toString())); |
823 | } |
824 | } |
825 | if (errors) { |
826 | errors->prepend(t: error); |
827 | } |
828 | return false; |
829 | } |
830 | default: { |
831 | // all other numbers suggest a user error |
832 | if (errors) { |
833 | QQmlError error; |
834 | error.setDescription(QQmlImportDatabase::tr(sourceText: "- nested namespaces not allowed" )); |
835 | errors->prepend(t: error); |
836 | } |
837 | return false; |
838 | } |
839 | } |
840 | Q_UNREACHABLE(); |
841 | } |
842 | |
843 | QQmlImportInstance *QQmlImportNamespace::findImport(const QString &uri) const |
844 | { |
845 | for (QQmlImportInstance *import : imports) { |
846 | if (import->uri == uri) |
847 | return import; |
848 | } |
849 | return nullptr; |
850 | } |
851 | |
852 | bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type, |
853 | QTypeRevision *version_return, QQmlType *type_return, |
854 | const QString *base, QList<QQmlError> *errors, |
855 | QQmlType::RegistrationType registrationType, |
856 | bool *typeRecursionDetected) |
857 | { |
858 | QQmlImport::RecursionRestriction recursionRestriction = |
859 | typeRecursionDetected ? QQmlImport::AllowRecursion : QQmlImport::PreventRecursion; |
860 | |
861 | bool localTypeRecursionDetected = false; |
862 | if (!typeRecursionDetected) |
863 | typeRecursionDetected = &localTypeRecursionDetected; |
864 | |
865 | // TODO: move the sorting somewhere else and make resolveType() const. |
866 | if (needsSorting()) { |
867 | std::stable_partition(first: imports.begin(), last: imports.end(), pred: [](QQmlImportInstance *import) { |
868 | return import->isInlineComponent; |
869 | }); |
870 | setNeedsSorting(false); |
871 | } |
872 | for (int i=0; i<imports.size(); ++i) { |
873 | const QQmlImportInstance *import = imports.at(i); |
874 | if (import->resolveType(typeLoader, type, version_return, type_return, base, |
875 | typeRecursionDetected, registrationType, recursionRestriction, errors)) { |
876 | if (qmlCheckTypes()) { |
877 | // check for type clashes |
878 | for (int j = i+1; j<imports.size(); ++j) { |
879 | const QQmlImportInstance *import2 = imports.at(i: j); |
880 | if (import2->resolveType(typeLoader, type, version_return, type_return: nullptr, base, |
881 | typeRecursionDetected: nullptr, registrationType)) { |
882 | if (errors) { |
883 | QString u1 = import->url; |
884 | QString u2 = import2->url; |
885 | if (base) { |
886 | QStringView b(*base); |
887 | int dot = b.lastIndexOf(c: Dot); |
888 | if (dot >= 0) { |
889 | b = b.left(n: dot+1); |
890 | QStringView l = b.left(n: dot); |
891 | if (u1.startsWith(s: b)) |
892 | u1 = u1.mid(position: b.size()); |
893 | else if (u1 == l) |
894 | u1 = QQmlImportDatabase::tr(sourceText: "local directory" ); |
895 | if (u2.startsWith(s: b)) |
896 | u2 = u2.mid(position: b.size()); |
897 | else if (u2 == l) |
898 | u2 = QQmlImportDatabase::tr(sourceText: "local directory" ); |
899 | } |
900 | } |
901 | |
902 | QQmlError error; |
903 | if (u1 != u2) { |
904 | error.setDescription( |
905 | QQmlImportDatabase::tr( |
906 | sourceText: "is ambiguous. Found in %1 and in %2" ) |
907 | .arg(args&: u1, args&: u2)); |
908 | } else { |
909 | error.setDescription( |
910 | QQmlImportDatabase::tr( |
911 | sourceText: "is ambiguous. Found in %1 in version " |
912 | "%2.%3 and %4.%5" ) |
913 | .arg(a: u1) |
914 | .arg(a: import->version.majorVersion()) |
915 | .arg(a: import->version.minorVersion()) |
916 | .arg(a: import2->version.majorVersion()) |
917 | .arg(a: import2->version.minorVersion())); |
918 | } |
919 | errors->prepend(t: error); |
920 | } |
921 | return false; |
922 | } |
923 | } |
924 | } |
925 | return true; |
926 | } |
927 | } |
928 | if (errors) { |
929 | QQmlError error; |
930 | if (*typeRecursionDetected) |
931 | error.setDescription(QQmlImportDatabase::tr(sourceText: "is instantiated recursively" )); |
932 | else |
933 | error.setDescription(QQmlImportDatabase::tr(sourceText: "is not a type" )); |
934 | errors->prepend(t: error); |
935 | } |
936 | return false; |
937 | } |
938 | |
939 | QQmlImportNamespace *QQmlImports::findQualifiedNamespace(const QHashedStringRef &prefix) const |
940 | { |
941 | for (QQmlImportNamespace *ns = m_qualifiedSets.first(); ns; ns = m_qualifiedSets.next(v: ns)) { |
942 | if (prefix == ns->prefix) |
943 | return ns; |
944 | } |
945 | return nullptr; |
946 | } |
947 | |
948 | /* |
949 | Import an extension defined by a qmldir file. |
950 | */ |
951 | QTypeRevision QQmlImports::importExtension( |
952 | const QString &uri, QTypeRevision version, QQmlImportDatabase *database, |
953 | const QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors) |
954 | { |
955 | Q_ASSERT(qmldir->hasContent()); |
956 | |
957 | qCDebug(lcQmlImport) |
958 | << "importExtension:" << qPrintable(m_base) << "loaded" << qmldir->qmldirLocation(); |
959 | |
960 | if (designerSupportRequired && !qmldir->designerSupported()) { |
961 | if (errors) { |
962 | QQmlError error; |
963 | error.setDescription( |
964 | QQmlImportDatabase::tr(sourceText: "module does not support the designer \"%1\"" ) |
965 | .arg(a: qmldir->typeNamespace())); |
966 | error.setUrl(QUrl::fromLocalFile(localfile: qmldir->qmldirLocation())); |
967 | errors->prepend(t: error); |
968 | } |
969 | return QTypeRevision(); |
970 | } |
971 | |
972 | if (qmldir->plugins().isEmpty()) |
973 | return validVersion(version); |
974 | |
975 | QQmlPluginImporter importer(uri, version, database, qmldir, m_typeLoader, errors); |
976 | return importer.importPlugins(); |
977 | } |
978 | |
979 | bool QQmlImports::getQmldirContent(const QString &qmldirIdentifier, const QString &uri, |
980 | QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors) |
981 | { |
982 | Q_ASSERT(errors); |
983 | Q_ASSERT(qmldir); |
984 | |
985 | *qmldir = m_typeLoader->qmldirContent(filePath: qmldirIdentifier); |
986 | if ((*qmldir).hasContent()) { |
987 | // Ensure that parsing was successful |
988 | if ((*qmldir).hasError()) { |
989 | QUrl url = QUrl::fromLocalFile(localfile: qmldirIdentifier); |
990 | const QList<QQmlError> qmldirErrors = (*qmldir).errors(uri); |
991 | for (int i = 0; i < qmldirErrors.size(); ++i) { |
992 | QQmlError error = qmldirErrors.at(i); |
993 | error.setUrl(url); |
994 | errors->append(t: error); |
995 | } |
996 | return false; |
997 | } |
998 | } |
999 | |
1000 | return true; |
1001 | } |
1002 | |
1003 | QString QQmlImports::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database) |
1004 | { |
1005 | QString dir = dir_arg; |
1006 | if (dir.endsWith(c: Slash) || dir.endsWith(c: Backslash)) |
1007 | dir.chop(n: 1); |
1008 | |
1009 | QStringList paths = database->fileImportPath; |
1010 | if (!paths.isEmpty()) |
1011 | std::sort(first: paths.begin(), last: paths.end(), comp: std::greater<QString>()); // Ensure subdirs preceed their parents. |
1012 | |
1013 | QString stableRelativePath = dir; |
1014 | for (const QString &path : std::as_const(t&: paths)) { |
1015 | if (dir.startsWith(s: path)) { |
1016 | stableRelativePath = dir.mid(position: path.size()+1); |
1017 | break; |
1018 | } |
1019 | } |
1020 | |
1021 | stableRelativePath.replace(before: Backslash, after: Slash); |
1022 | |
1023 | // remove optional versioning in dot notation from uri |
1024 | int versionDot = stableRelativePath.lastIndexOf(c: Dot); |
1025 | if (versionDot >= 0) { |
1026 | int nextSlash = stableRelativePath.indexOf(c: Slash, from: versionDot); |
1027 | if (nextSlash >= 0) |
1028 | stableRelativePath.remove(i: versionDot, len: nextSlash - versionDot); |
1029 | else |
1030 | stableRelativePath = stableRelativePath.left(n: versionDot); |
1031 | } |
1032 | |
1033 | stableRelativePath.replace(before: Slash, after: Dot); |
1034 | |
1035 | return stableRelativePath; |
1036 | } |
1037 | |
1038 | /*! |
1039 | \internal |
1040 | |
1041 | \fn template<typename Callback> |
1042 | QQmlImportDatabase::LocalQmldirResult QQmlImportDatabase::locateLocalQmldir( |
1043 | const QString &uri, QTypeRevision version, const Callback &callback) |
1044 | |
1045 | Locates the qmldir files for \a uri version \a version. For each one, calls |
1046 | the \a callback. If the \a callback returns \c true, returns QmldirFound. |
1047 | |
1048 | If at least one callback invocation returned \c false and there are no qmldir |
1049 | files left to check, returns QmldirRejected. |
1050 | |
1051 | Otherwise, if interception redirects a previously local qmldir URL to a remote |
1052 | one, returns QmldirInterceptedToRemote. Otherwise, returns QmldirNotFound. |
1053 | */ |
1054 | |
1055 | QTypeRevision QQmlImports::matchingQmldirVersion( |
1056 | const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, QTypeRevision version, |
1057 | QList<QQmlError> *errors) |
1058 | { |
1059 | int bestMajorVersion = -1; |
1060 | quint8 lowestMinorVersion = std::numeric_limits<quint8>::max(); |
1061 | quint8 highestMinorVersion = 0; |
1062 | |
1063 | auto addVersion = [&](QTypeRevision newVersion) { |
1064 | if (!newVersion.hasMajorVersion()) |
1065 | return; |
1066 | if (!version.hasMajorVersion() || version.majorVersion() == newVersion.majorVersion()) { |
1067 | if (newVersion.majorVersion() > bestMajorVersion) { |
1068 | bestMajorVersion = newVersion.majorVersion(); |
1069 | if (newVersion.hasMinorVersion()) { |
1070 | lowestMinorVersion = newVersion.minorVersion(); |
1071 | highestMinorVersion = newVersion.minorVersion(); |
1072 | } |
1073 | } else if (newVersion.majorVersion() == bestMajorVersion |
1074 | && newVersion.hasMinorVersion()) { |
1075 | lowestMinorVersion = qMin(a: lowestMinorVersion, b: newVersion.minorVersion()); |
1076 | highestMinorVersion = qMax(a: highestMinorVersion, b: newVersion.minorVersion()); |
1077 | } |
1078 | } |
1079 | }; |
1080 | |
1081 | typedef QQmlDirComponents::const_iterator ConstIterator; |
1082 | const QQmlDirComponents &components = qmldir.components(); |
1083 | |
1084 | ConstIterator cend = components.constEnd(); |
1085 | for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) { |
1086 | for (ConstIterator cit2 = components.constBegin(); cit2 != cit; ++cit2) { |
1087 | if (cit2->typeName == cit->typeName && cit2->version == cit->version) { |
1088 | // This entry clashes with a predecessor |
1089 | QQmlError error; |
1090 | error.setDescription( |
1091 | QQmlImportDatabase::tr( |
1092 | sourceText: "\"%1\" version %2.%3 is defined more than once in module \"%4\"" ) |
1093 | .arg(a: cit->typeName).arg(a: cit->version.majorVersion()) |
1094 | .arg(a: cit->version.minorVersion()).arg(a: uri)); |
1095 | errors->prepend(t: error); |
1096 | return QTypeRevision(); |
1097 | } |
1098 | } |
1099 | |
1100 | addVersion(cit->version); |
1101 | } |
1102 | |
1103 | typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator; |
1104 | const QQmlDirScripts &scripts = qmldir.scripts(); |
1105 | |
1106 | SConstIterator send = scripts.constEnd(); |
1107 | for (SConstIterator sit = scripts.constBegin(); sit != send; ++sit) { |
1108 | for (SConstIterator sit2 = scripts.constBegin(); sit2 != sit; ++sit2) { |
1109 | if (sit2->nameSpace == sit->nameSpace && sit2->version == sit->version) { |
1110 | // This entry clashes with a predecessor |
1111 | QQmlError error; |
1112 | error.setDescription(QQmlImportDatabase::tr(sourceText: "\"%1\" version %2.%3 is defined more than once in module \"%4\"" ) |
1113 | .arg(a: sit->nameSpace).arg(a: sit->version.majorVersion()) |
1114 | .arg(a: sit->version.minorVersion()).arg(a: uri)); |
1115 | errors->prepend(t: error); |
1116 | return QTypeRevision(); |
1117 | } |
1118 | } |
1119 | |
1120 | addVersion(sit->version); |
1121 | } |
1122 | |
1123 | // Failure to find a match is only an error if we were asking for a specific version ... |
1124 | if (version.hasMajorVersion() |
1125 | && (bestMajorVersion < 0 |
1126 | || (version.hasMinorVersion() |
1127 | && (lowestMinorVersion > version.minorVersion() |
1128 | || highestMinorVersion < version.minorVersion())))) { |
1129 | errors->prepend(t: moduleNotFoundError(uri, version)); |
1130 | return QTypeRevision(); |
1131 | } |
1132 | |
1133 | // ... otherwise, anything is valid. |
1134 | if (bestMajorVersion < 0) |
1135 | return validVersion(); |
1136 | |
1137 | return QTypeRevision::fromVersion( |
1138 | majorVersion: bestMajorVersion, |
1139 | minorVersion: (version.hasMajorVersion() && version.hasMinorVersion()) |
1140 | ? version.minorVersion() |
1141 | : highestMinorVersion); |
1142 | } |
1143 | |
1144 | QQmlImportNamespace *QQmlImports::importNamespace(const QString &prefix) |
1145 | { |
1146 | QQmlImportNamespace *nameSpace = nullptr; |
1147 | |
1148 | if (prefix.isEmpty()) { |
1149 | nameSpace = &m_unqualifiedset; |
1150 | } else { |
1151 | nameSpace = findQualifiedNamespace(prefix); |
1152 | |
1153 | if (!nameSpace) { |
1154 | nameSpace = new QQmlImportNamespace; |
1155 | nameSpace->prefix = prefix; |
1156 | m_qualifiedSets.append(v: nameSpace); |
1157 | } |
1158 | } |
1159 | |
1160 | return nameSpace; |
1161 | } |
1162 | |
1163 | QQmlImportInstance *QQmlImports::addImportToNamespace( |
1164 | QQmlImportNamespace *nameSpace, const QString &uri, const QString &url, QTypeRevision version, |
1165 | QV4::CompiledData::Import::ImportType type, QList<QQmlError> *errors, quint16 precedence) |
1166 | { |
1167 | Q_ASSERT(nameSpace); |
1168 | Q_ASSERT(errors); |
1169 | Q_UNUSED(errors); |
1170 | Q_ASSERT(url.isEmpty() || url.endsWith(Slash)); |
1171 | |
1172 | QQmlImportInstance *import = new QQmlImportInstance; |
1173 | import->uri = uri; |
1174 | import->url = url; |
1175 | import->version = version; |
1176 | import->isLibrary = (type == QV4::CompiledData::Import::ImportLibrary); |
1177 | import->precedence = precedence; |
1178 | import->implicitlyImported = precedence >= QQmlImportInstance::Implicit; |
1179 | |
1180 | for (auto it = nameSpace->imports.cbegin(), end = nameSpace->imports.cend(); |
1181 | it != end; ++it) { |
1182 | if ((*it)->precedence < precedence) |
1183 | continue; |
1184 | |
1185 | nameSpace->imports.insert(before: it, t: import); |
1186 | return import; |
1187 | } |
1188 | nameSpace->imports.append(t: import); |
1189 | return import; |
1190 | } |
1191 | |
1192 | QTypeRevision QQmlImports::addLibraryImport( |
1193 | QQmlImportDatabase *database, const QString &uri, const QString &prefix, |
1194 | QTypeRevision version, const QString &qmldirIdentifier, const QString &qmldirUrl, |
1195 | ImportFlags flags, quint16 precedence, QList<QQmlError> *errors) |
1196 | { |
1197 | Q_ASSERT(database); |
1198 | Q_ASSERT(errors); |
1199 | |
1200 | qCDebug(lcQmlImport) |
1201 | << "addLibraryImport:" << qPrintable(baseUrl().toString()) |
1202 | << uri << "version '" << version << "'" << "as" << prefix; |
1203 | |
1204 | QQmlImportNamespace *nameSpace = importNamespace(prefix); |
1205 | Q_ASSERT(nameSpace); |
1206 | |
1207 | QQmlImportInstance *inserted = addImportToNamespace( |
1208 | nameSpace, uri, url: qmldirUrl, version, |
1209 | type: QV4::CompiledData::Import::ImportLibrary, errors, |
1210 | precedence); |
1211 | Q_ASSERT(inserted); |
1212 | |
1213 | if (!(flags & QQmlImports::ImportIncomplete)) { |
1214 | QQmlTypeLoaderQmldirContent qmldir; |
1215 | |
1216 | if (!qmldirIdentifier.isEmpty()) { |
1217 | if (!getQmldirContent(qmldirIdentifier, uri, qmldir: &qmldir, errors)) |
1218 | return QTypeRevision(); |
1219 | |
1220 | if (qmldir.hasContent()) { |
1221 | version = importExtension(uri, version, database, qmldir: &qmldir, errors); |
1222 | if (!version.isValid()) |
1223 | return QTypeRevision(); |
1224 | |
1225 | if (!inserted->setQmldirContent(resolvedUrl: qmldirUrl, qmldir, nameSpace, errors)) |
1226 | return QTypeRevision(); |
1227 | } |
1228 | } |
1229 | |
1230 | // Ensure that we are actually providing something |
1231 | const QTypeRevision matchingVersion = QQmlMetaType::matchingModuleVersion(module: uri, version); |
1232 | if (matchingVersion.isValid()) |
1233 | return matchingVersion; |
1234 | |
1235 | if (inserted->qmlDirComponents.isEmpty() && inserted->qmlDirScripts.isEmpty()) { |
1236 | if (qmldir.plugins().isEmpty()) { |
1237 | if (!qmldir.imports().isEmpty()) |
1238 | return validVersion(); // This is a pure redirection |
1239 | if (qmldir.hasTypeInfo()) |
1240 | return validVersion(); // A pure C++ module without plugin |
1241 | } |
1242 | errors->prepend(t: moduleNotFoundError(uri, version: relevantVersion(uri, version))); |
1243 | return QTypeRevision(); |
1244 | } else if (qmldir.hasContent()) { |
1245 | // Verify that the qmldir content is valid for this version |
1246 | version = matchingQmldirVersion(qmldir, uri, version, errors); |
1247 | if (!version.isValid()) |
1248 | return QTypeRevision(); |
1249 | } |
1250 | } |
1251 | |
1252 | return validVersion(version); |
1253 | } |
1254 | |
1255 | /*! |
1256 | \internal |
1257 | |
1258 | Adds information to \a database such that subsequent calls to resolveType() |
1259 | will resolve types qualified by \a prefix by considering types found at the given \a uri. |
1260 | |
1261 | The \a uri is either a directory (if importType is FileImport), or a URI resolved using paths |
1262 | added via addImportPath() (if importType is LibraryImport). |
1263 | |
1264 | The \a prefix may be empty, in which case the import location is considered for |
1265 | unqualified types. |
1266 | |
1267 | The base URL must already have been set with Import::setBaseUrl(). |
1268 | |
1269 | Optionally, the qmldir the import resolved to can be returned by providing the \a localQmldir |
1270 | parameter. Not all imports will have a local qmldir. If there is none, the \a localQmldir |
1271 | parameter won't be set. |
1272 | |
1273 | Returns a valid QTypeRevision on success, and an invalid one on failure. |
1274 | In case of failure, the \a errors array will filled appropriately. |
1275 | */ |
1276 | QTypeRevision QQmlImports::addFileImport( |
1277 | QQmlImportDatabase *database, const QString &uri, const QString &prefix, |
1278 | QTypeRevision version, ImportFlags flags, quint16 precedence, |
1279 | QString *localQmldir, QList<QQmlError> *errors) |
1280 | { |
1281 | Q_ASSERT(database); |
1282 | Q_ASSERT(errors); |
1283 | |
1284 | qCDebug(lcQmlImport) |
1285 | << "addFileImport:" << qPrintable(baseUrl().toString()) |
1286 | << uri << version << "as" << prefix; |
1287 | |
1288 | if (uri.startsWith(c: Slash) || uri.startsWith(c: Colon)) { |
1289 | QQmlError error; |
1290 | const QString fix = uri.startsWith(c: Slash) ? QLatin1String("file:" ) + uri |
1291 | : QLatin1String("qrc" ) + uri; |
1292 | error.setDescription(QQmlImportDatabase::tr( |
1293 | sourceText: "\"%1\" is not a valid import URL. " |
1294 | "You can pass relative paths or URLs with schema, but not " |
1295 | "absolute paths or resource paths. Try \"%2\"." ).arg(args: uri, args: fix)); |
1296 | errors->prepend(t: error); |
1297 | return QTypeRevision(); |
1298 | } |
1299 | |
1300 | Q_ASSERT(errors); |
1301 | |
1302 | QQmlImportNamespace *nameSpace = importNamespace(prefix); |
1303 | Q_ASSERT(nameSpace); |
1304 | |
1305 | // The uri for this import. For library imports this is the same as uri |
1306 | // specified by the user, but it may be different in the case of file imports. |
1307 | QString importUri = uri; |
1308 | QString qmldirUrl = resolveLocalUrl(url: m_base, relative: importUri + (importUri.endsWith(c: Slash) |
1309 | ? String_qmldir |
1310 | : Slash_qmldir)); |
1311 | qmldirUrl = m_typeLoader->engine()->interceptUrl( |
1312 | url: QUrl(qmldirUrl), type: QQmlAbstractUrlInterceptor::QmldirFile).toString(); |
1313 | QString qmldirIdentifier; |
1314 | |
1315 | if (QQmlFile::isLocalFile(url: qmldirUrl)) { |
1316 | |
1317 | QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl); |
1318 | Q_ASSERT(!localFileOrQrc.isEmpty()); |
1319 | |
1320 | const QString dir = localFileOrQrc.left(n: localFileOrQrc.lastIndexOf(c: Slash) + 1); |
1321 | if (!m_typeLoader->directoryExists(path: dir)) { |
1322 | if (precedence < QQmlImportInstance::Implicit) { |
1323 | QQmlError error; |
1324 | error.setDescription(QQmlImportDatabase::tr(sourceText: "\"%1\": no such directory" ).arg(a: uri)); |
1325 | error.setUrl(QUrl(qmldirUrl)); |
1326 | errors->prepend(t: error); |
1327 | } |
1328 | return QTypeRevision(); |
1329 | } |
1330 | |
1331 | // Transforms the (possible relative) uri into our best guess relative to the |
1332 | // import paths. |
1333 | importUri = resolvedUri(dir_arg: dir, database); |
1334 | if (importUri.endsWith(c: Slash)) |
1335 | importUri.chop(n: 1); |
1336 | |
1337 | if (!m_typeLoader->absoluteFilePath(path: localFileOrQrc).isEmpty()) { |
1338 | qmldirIdentifier = localFileOrQrc; |
1339 | if (localQmldir) |
1340 | *localQmldir = qmldirIdentifier; |
1341 | } |
1342 | |
1343 | } else if (nameSpace->prefix.isEmpty() && !(flags & QQmlImports::ImportIncomplete)) { |
1344 | |
1345 | if (precedence < QQmlImportInstance::Implicit) { |
1346 | QQmlError error; |
1347 | error.setDescription(QQmlImportDatabase::tr(sourceText: "import \"%1\" has no qmldir and no namespace" ).arg(a: importUri)); |
1348 | error.setUrl(QUrl(qmldirUrl)); |
1349 | errors->prepend(t: error); |
1350 | } |
1351 | |
1352 | return QTypeRevision(); |
1353 | |
1354 | } |
1355 | |
1356 | // The url for the path containing files for this import |
1357 | QString url = resolveLocalUrl(url: m_base, relative: uri); |
1358 | if (url.isEmpty()) { |
1359 | QQmlError error; |
1360 | error.setDescription( |
1361 | QQmlImportDatabase::tr(sourceText: "Cannot resolve URL for import \"%1\"" ).arg(a: uri)); |
1362 | error.setUrl(m_baseUrl); |
1363 | errors->prepend(t: error); |
1364 | return QTypeRevision(); |
1365 | } |
1366 | |
1367 | if (!url.endsWith(c: Slash) && !url.endsWith(c: Backslash)) |
1368 | url += Slash; |
1369 | |
1370 | // ### For enum support, we are now adding the implicit import always (and earlier). Bail early |
1371 | // if the implicit import has already been explicitly added, otherwise we can run into issues |
1372 | // with duplicate imports. However remember that we attempted to add this as implicit import, to |
1373 | // allow for the loading of internal types. |
1374 | if (precedence >= QQmlImportInstance::Implicit) { |
1375 | for (QList<QQmlImportInstance *>::const_iterator it = nameSpace->imports.constBegin(); |
1376 | it != nameSpace->imports.constEnd(); ++it) { |
1377 | if ((*it)->url == url) { |
1378 | (*it)->implicitlyImported = true; |
1379 | return validVersion(version); |
1380 | } |
1381 | } |
1382 | } |
1383 | |
1384 | if (!(flags & QQmlImports::ImportIncomplete) && !qmldirIdentifier.isEmpty()) { |
1385 | QQmlTypeLoaderQmldirContent qmldir; |
1386 | if (!getQmldirContent(qmldirIdentifier, uri: importUri, qmldir: &qmldir, errors)) |
1387 | return QTypeRevision(); |
1388 | |
1389 | if (qmldir.hasContent()) { |
1390 | // Prefer the qmldir URI. Unless it doesn't exist. |
1391 | const QString qmldirUri = qmldir.typeNamespace(); |
1392 | if (!qmldirUri.isEmpty()) |
1393 | importUri = qmldirUri; |
1394 | |
1395 | QQmlImportInstance *inserted = addImportToNamespace( |
1396 | nameSpace, uri: importUri, url, version, type: QV4::CompiledData::Import::ImportFile, |
1397 | errors, precedence); |
1398 | Q_ASSERT(inserted); |
1399 | |
1400 | version = importExtension(uri: importUri, version, database, qmldir: &qmldir, errors); |
1401 | if (!version.isValid()) |
1402 | return QTypeRevision(); |
1403 | |
1404 | if (!inserted->setQmldirContent(resolvedUrl: url, qmldir, nameSpace, errors)) |
1405 | return QTypeRevision(); |
1406 | |
1407 | return validVersion(version); |
1408 | } |
1409 | } |
1410 | |
1411 | QQmlImportInstance *inserted = addImportToNamespace( |
1412 | nameSpace, uri: importUri, url, version, type: QV4::CompiledData::Import::ImportFile, |
1413 | errors, precedence); |
1414 | Q_ASSERT(inserted); |
1415 | return validVersion(version); |
1416 | } |
1417 | |
1418 | QTypeRevision QQmlImports::updateQmldirContent( |
1419 | QQmlImportDatabase *database, const QString &uri, const QString &prefix, |
1420 | const QString &qmldirIdentifier, const QString &qmldirUrl, QList<QQmlError> *errors) |
1421 | { |
1422 | Q_ASSERT(database); |
1423 | Q_ASSERT(errors); |
1424 | |
1425 | qDebug(catFunc: lcQmlImport) |
1426 | << "updateQmldirContent:" << qPrintable(baseUrl().toString()) |
1427 | << uri << "to" << qmldirUrl << "as" << prefix; |
1428 | |
1429 | QQmlImportNamespace *nameSpace = importNamespace(prefix); |
1430 | Q_ASSERT(nameSpace); |
1431 | |
1432 | if (QQmlImportInstance *import = nameSpace->findImport(uri)) { |
1433 | QQmlTypeLoaderQmldirContent qmldir; |
1434 | if (!getQmldirContent(qmldirIdentifier, uri, qmldir: &qmldir, errors)) |
1435 | return QTypeRevision(); |
1436 | |
1437 | if (qmldir.hasContent()) { |
1438 | QTypeRevision version = importExtension( |
1439 | uri, version: import->version, database, qmldir: &qmldir, errors); |
1440 | if (!version.isValid()) |
1441 | return QTypeRevision(); |
1442 | |
1443 | if (import->setQmldirContent(resolvedUrl: qmldirUrl, qmldir, nameSpace, errors)) { |
1444 | if (import->qmlDirComponents.isEmpty() && import->qmlDirScripts.isEmpty()) { |
1445 | // The implicit import qmldir can be empty, and plugins have no extra versions |
1446 | if (uri != QLatin1String("." ) && !QQmlMetaType::matchingModuleVersion(module: uri, version).isValid()) { |
1447 | errors->prepend(t: moduleNotFoundError(uri, version: relevantVersion(uri, version))); |
1448 | return QTypeRevision(); |
1449 | } |
1450 | } else { |
1451 | // Verify that the qmldir content is valid for this version |
1452 | version = matchingQmldirVersion(qmldir, uri, version, errors); |
1453 | if (!version.isValid()) |
1454 | return QTypeRevision(); |
1455 | } |
1456 | return validVersion(version); |
1457 | } |
1458 | } |
1459 | } |
1460 | |
1461 | if (errors->isEmpty()) { |
1462 | QQmlError error; |
1463 | error.setDescription(QQmlTypeLoader::tr(sourceText: "Cannot update qmldir content for '%1'" ).arg(a: uri)); |
1464 | errors->prepend(t: error); |
1465 | } |
1466 | |
1467 | return QTypeRevision(); |
1468 | } |
1469 | |
1470 | /*! |
1471 | \fn QQmlImports::addImplicitImport(QQmlImportDatabase *importDb, QString *localQmldir, QList<QQmlError> *errors) |
1472 | \internal |
1473 | |
1474 | Adds an implicit "." file import. This is equivalent to calling addFileImport(), but error |
1475 | messages related to the path or qmldir file not existing are suppressed. |
1476 | |
1477 | Additionally, this will add the import with lowest instead of highest precedence. |
1478 | */ |
1479 | |
1480 | |
1481 | /*! |
1482 | \internal |
1483 | */ |
1484 | bool QQmlImports::addInlineComponentImport(QQmlImportInstance *const importInstance, const QString &name, const QUrl importUrl, QQmlType containingType) |
1485 | { |
1486 | importInstance->url = importUrl.toString(); |
1487 | importInstance->uri = name; |
1488 | importInstance->isInlineComponent = true; |
1489 | importInstance->version = QTypeRevision::zero(); |
1490 | importInstance->containingType = containingType; |
1491 | m_unqualifiedset.imports.push_back(t: importInstance); |
1492 | m_unqualifiedset.setNeedsSorting(true); |
1493 | return true; |
1494 | } |
1495 | |
1496 | QUrl QQmlImports::urlFromLocalFileOrQrcOrUrl(const QString &file) |
1497 | { |
1498 | QUrl url(QLatin1String(file.at(i: 0) == Colon ? "qrc" : "" ) + file); |
1499 | |
1500 | // We don't support single character schemes as those conflict with windows drive letters. |
1501 | if (url.scheme().size() < 2) |
1502 | return QUrl::fromLocalFile(localfile: file); |
1503 | return url; |
1504 | } |
1505 | |
1506 | void QQmlImports::setDesignerSupportRequired(bool b) |
1507 | { |
1508 | designerSupportRequired = b; |
1509 | } |
1510 | |
1511 | static QStringList parseEnvPath(const QString &envImportPath) |
1512 | { |
1513 | if (QDir::listSeparator() == u':') { |
1514 | // Double colons are interpreted as separator + resource path. |
1515 | QStringList paths = envImportPath.split(sep: u':'); |
1516 | bool wasEmpty = false; |
1517 | for (auto it = paths.begin(); it != paths.end();) { |
1518 | if (it->isEmpty()) { |
1519 | wasEmpty = true; |
1520 | it = paths.erase(pos: it); |
1521 | } else { |
1522 | if (wasEmpty) { |
1523 | it->prepend(c: u':'); |
1524 | wasEmpty = false; |
1525 | } |
1526 | ++it; |
1527 | } |
1528 | } |
1529 | return paths; |
1530 | } else { |
1531 | return envImportPath.split(sep: QDir::listSeparator(), behavior: Qt::SkipEmptyParts); |
1532 | } |
1533 | } |
1534 | |
1535 | /*! |
1536 | \class QQmlImportDatabase |
1537 | \brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine. |
1538 | \internal |
1539 | */ |
1540 | QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e) |
1541 | : engine(e) |
1542 | { |
1543 | filePluginPath << QLatin1String("." ); |
1544 | // Search order is: |
1545 | // 1. android or macos specific bundle paths. |
1546 | // 2. applicationDirPath() |
1547 | // 3. qrc:/qt-project.org/imports |
1548 | // 4. qrc:/qt/qml |
1549 | // 5. $QML2_IMPORT_PATH |
1550 | // 6. $QML_IMPORT_PATH |
1551 | // 7. QLibraryInfo::QmlImportsPath |
1552 | |
1553 | QString installImportsPath = QLibraryInfo::path(p: QLibraryInfo::QmlImportsPath); |
1554 | addImportPath(dir: installImportsPath); |
1555 | |
1556 | auto addEnvImportPath = [this](const char *var) { |
1557 | if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty(var))) { |
1558 | const QStringList paths = parseEnvPath(envImportPath: qEnvironmentVariable(varName: var)); |
1559 | for (int ii = paths.size() - 1; ii >= 0; --ii) |
1560 | addImportPath(dir: paths.at(i: ii)); |
1561 | } |
1562 | }; |
1563 | |
1564 | // env import paths |
1565 | addEnvImportPath("QML_IMPORT_PATH" ); |
1566 | addEnvImportPath("QML2_IMPORT_PATH" ); |
1567 | |
1568 | addImportPath(QStringLiteral("qrc:/qt/qml" )); |
1569 | addImportPath(QStringLiteral("qrc:/qt-project.org/imports" )); |
1570 | addImportPath(dir: QCoreApplication::applicationDirPath()); |
1571 | |
1572 | auto addEnvPluginPath = [this](const char *var) { |
1573 | if (Q_UNLIKELY(!qEnvironmentVariableIsEmpty(var))) { |
1574 | const QStringList paths = parseEnvPath(envImportPath: qEnvironmentVariable(varName: var)); |
1575 | for (int ii = paths.size() - 1; ii >= 0; --ii) |
1576 | addPluginPath(path: paths.at(i: ii)); |
1577 | } |
1578 | }; |
1579 | |
1580 | addEnvPluginPath("QML_PLUGIN_PATH" ); |
1581 | #if defined(Q_OS_ANDROID) |
1582 | addImportPath(QStringLiteral("qrc:/android_rcc_bundle/qml" )); |
1583 | addEnvPluginPath("QT_BUNDLED_LIBS_PATH" ); |
1584 | #elif defined(Q_OS_MACOS) |
1585 | // Add the main bundle's Resources/qml directory as an import path, so that QML modules are |
1586 | // found successfully when running the app from its build dir. |
1587 | // This is where macdeployqt and our CMake deployment logic puts Qt and user qmldir files. |
1588 | if (CFBundleRef bundleRef = CFBundleGetMainBundle()) { |
1589 | if (QCFType<CFURLRef> urlRef = CFBundleCopyResourceURL( |
1590 | bundleRef, |
1591 | QCFString(QLatin1String("qml" )), 0, 0)) { |
1592 | if (QCFType<CFURLRef> absoluteUrlRef = CFURLCopyAbsoluteURL(urlRef)) { |
1593 | if (QCFString path = CFURLCopyFileSystemPath(absoluteUrlRef, kCFURLPOSIXPathStyle)) { |
1594 | if (QFile::exists(path)) { |
1595 | addImportPath(QDir(path).canonicalPath()); |
1596 | } |
1597 | } |
1598 | } |
1599 | } |
1600 | } |
1601 | #endif // Q_OS_DARWIN |
1602 | } |
1603 | |
1604 | /*! |
1605 | \internal |
1606 | */ |
1607 | void QQmlImportDatabase::setPluginPathList(const QStringList &paths) |
1608 | { |
1609 | qCDebug(lcQmlImport) << "setPluginPathList:" << paths; |
1610 | filePluginPath = paths; |
1611 | } |
1612 | |
1613 | /*! |
1614 | \internal |
1615 | */ |
1616 | void QQmlImportDatabase::addPluginPath(const QString& path) |
1617 | { |
1618 | qCDebug(lcQmlImport) << "addPluginPath:" << path; |
1619 | |
1620 | QUrl url = QUrl(path); |
1621 | if (url.isRelative() || url.scheme() == QLatin1String("file" ) |
1622 | || (url.scheme().size() == 1 && QFile::exists(fileName: path)) ) { // windows path |
1623 | QDir dir = QDir(path); |
1624 | filePluginPath.prepend(t: dir.canonicalPath()); |
1625 | } else { |
1626 | filePluginPath.prepend(t: path); |
1627 | } |
1628 | } |
1629 | |
1630 | QString QQmlImportDatabase::absoluteFilePath(const QString &path) const |
1631 | { |
1632 | return QQmlEnginePrivate::get(e: engine)->typeLoader.absoluteFilePath(path); |
1633 | } |
1634 | |
1635 | /*! |
1636 | \internal |
1637 | */ |
1638 | void QQmlImportDatabase::addImportPath(const QString& path) |
1639 | { |
1640 | qCDebug(lcQmlImport) << "addImportPath:" << path; |
1641 | |
1642 | if (path.isEmpty()) |
1643 | return; |
1644 | |
1645 | QUrl url = QUrl(path); |
1646 | QString cPath; |
1647 | |
1648 | if (url.scheme() == QLatin1String("file" )) { |
1649 | cPath = QQmlFile::urlToLocalFileOrQrc(url); |
1650 | } else if (path.startsWith(c: QLatin1Char(':'))) { |
1651 | // qrc directory, e.g. :/foo |
1652 | // need to convert to a qrc url, e.g. qrc:/foo |
1653 | cPath = QLatin1String("qrc" ) + path; |
1654 | cPath.replace(before: Backslash, after: Slash); |
1655 | } else if (url.isRelative() || |
1656 | (url.scheme().size() == 1 && QFile::exists(fileName: path)) ) { // windows path |
1657 | QDir dir = QDir(path); |
1658 | cPath = dir.canonicalPath(); |
1659 | } else { |
1660 | cPath = path; |
1661 | cPath.replace(before: Backslash, after: Slash); |
1662 | } |
1663 | |
1664 | if (!cPath.isEmpty()) { |
1665 | if (fileImportPath.contains(str: cPath)) |
1666 | fileImportPath.move(from: fileImportPath.indexOf(str: cPath), to: 0); |
1667 | else |
1668 | fileImportPath.prepend(t: cPath); |
1669 | } |
1670 | } |
1671 | |
1672 | /*! |
1673 | \internal |
1674 | */ |
1675 | QStringList QQmlImportDatabase::importPathList(PathType type) const |
1676 | { |
1677 | if (type == LocalOrRemote) |
1678 | return fileImportPath; |
1679 | |
1680 | QStringList list; |
1681 | for (const QString &path : fileImportPath) { |
1682 | bool localPath = isPathAbsolute(path) || QQmlFile::isLocalFile(url: path); |
1683 | if (localPath == (type == Local)) |
1684 | list.append(t: path); |
1685 | } |
1686 | |
1687 | return list; |
1688 | } |
1689 | |
1690 | /*! |
1691 | \internal |
1692 | */ |
1693 | void QQmlImportDatabase::setImportPathList(const QStringList &paths) |
1694 | { |
1695 | qCDebug(lcQmlImport) << "setImportPathList:" << paths; |
1696 | |
1697 | fileImportPath.clear(); |
1698 | for (auto it = paths.crbegin(); it != paths.crend(); ++it) |
1699 | addImportPath(path: *it); |
1700 | |
1701 | // Our existing cached paths may have been invalidated |
1702 | clearDirCache(); |
1703 | } |
1704 | |
1705 | /*! |
1706 | \internal |
1707 | */ |
1708 | QTypeRevision QQmlImportDatabase::lockModule(const QString &uri, const QString &typeNamespace, |
1709 | QTypeRevision version, QList<QQmlError> *errors) |
1710 | { |
1711 | if (!version.hasMajorVersion()) { |
1712 | version = QQmlMetaType::latestModuleVersion(uri); |
1713 | if (!version.isValid()) |
1714 | errors->prepend(t: moduleNotFoundError(uri, version)); |
1715 | } |
1716 | if (version.hasMajorVersion() && !typeNamespace.isEmpty() |
1717 | && !QQmlMetaType::protectModule(uri, version, weakProtectAllVersions: true)) { |
1718 | // Not being able to protect the module means there are not types registered for it, |
1719 | // means the plugin we loaded didn't provide any, means we didn't find the module. |
1720 | // We output the generic error message as depending on the load order of imports we may |
1721 | // hit this path or another one that only figures "plugin is already loaded but module |
1722 | // unavailable" and doesn't try to protect it anymore. |
1723 | errors->prepend(t: moduleNotFoundError(uri, version)); |
1724 | return QTypeRevision(); |
1725 | } |
1726 | |
1727 | return version; |
1728 | } |
1729 | |
1730 | bool QQmlImportDatabase::removeDynamicPlugin(const QString &pluginId) |
1731 | { |
1732 | return QQmlPluginImporter::removePlugin(pluginId); |
1733 | } |
1734 | |
1735 | QStringList QQmlImportDatabase::dynamicPlugins() const |
1736 | { |
1737 | return QQmlPluginImporter::plugins(); |
1738 | } |
1739 | |
1740 | void QQmlImportDatabase::clearDirCache() |
1741 | { |
1742 | QStringHash<QmldirCache *>::ConstIterator itr = qmldirCache.constBegin(); |
1743 | while (itr != qmldirCache.constEnd()) { |
1744 | QmldirCache *cache = *itr; |
1745 | do { |
1746 | QmldirCache *nextCache = cache->next; |
1747 | delete cache; |
1748 | cache = nextCache; |
1749 | } while (cache); |
1750 | |
1751 | ++itr; |
1752 | } |
1753 | qmldirCache.clear(); |
1754 | } |
1755 | |
1756 | QT_END_NAMESPACE |
1757 | |