1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qqmlimport_p.h"
44 #include <QtCore/qdebug.h>
45 #include <QtCore/qdir.h>
46 #include <QtQml/qqmlfile.h>
47 #include <QtCore/qfileinfo.h>
48 #include <QtCore/qpluginloader.h>
49 #include <QtCore/qlibraryinfo.h>
50 #include <QtQml/qqmlextensioninterface.h>
51 #include <private/qqmlglobal_p.h>
52 #include <private/qqmltypenamecache_p.h>
53 #include <private/qqmlengine_p.h>
54 #include <private/qfieldlist_p.h>
58 DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
59 DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
61 static QString dotqml_string(QLatin1String(".qml"));
63 QString resolveLocalUrl(const QString &url, const QString &relative)
65 if (relative.contains(QLatin1Char(':'))) {
66 // contains a host name
67 return QUrl(url).resolved(QUrl(relative)).toString();
68 } else if (relative.isEmpty()) {
70 } else if (relative.at(0) == QLatin1Char('/') || !url.contains(QLatin1Char('/'))) {
73 QString base(url.left(url.lastIndexOf(QLatin1Char('/')) + 1));
75 if (relative == QLatin1String("."))
78 base.append(relative);
80 // Remove any relative directory elements in the path
81 const QLatin1Char dot('.');
82 const QLatin1Char slash('/');
84 int length = base.length();
86 while ((index = base.indexOf(QLatin1String("/."), index)) != -1) {
87 if ((length > (index + 2)) && (base.at(index + 2) == dot) &&
88 (length == (index + 3) || (base.at(index + 3) == slash))) {
89 // Either "/../" or "/..<END>"
90 int previous = base.lastIndexOf(slash, index - 1);
94 int removeLength = (index - previous) + 3;
95 base.remove(previous + 1, removeLength);
96 length -= removeLength;
98 } else if ((length == (index + 2)) || (base.at(index + 2) == slash)) {
99 // Either "/./" or "/.<END>"
100 base.remove(index, 2);
111 typedef QMap<QString, QString> StringStringMap;
112 Q_GLOBAL_STATIC(StringStringMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri
114 class QQmlImportNamespace
117 QQmlImportNamespace() : nextNamespace(0) {}
125 QQmlDirComponents qmlDirComponents;
126 QQmlDirScripts qmlDirScripts;
128 bool resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type,
129 int *vmajor, int *vminor,
130 QQmlType** type_return, QString* url_return,
131 QString *base = 0, bool *typeRecursionDetected = 0) const;
133 QList<Import> imports;
135 bool resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type,
136 int *vmajor, int *vminor,
137 QQmlType** type_return, QString* url_return,
138 QString *base = 0, QList<QQmlError> *errors = 0);
140 // Prefix when used as a qualified import. Otherwise empty.
141 QHashedString prefix;
143 // Used by QQmlImportsPrivate::qualifiedSets
144 QQmlImportNamespace *nextNamespace;
147 class QQmlImportsPrivate
150 QQmlImportsPrivate(QQmlTypeLoader *loader);
151 ~QQmlImportsPrivate();
153 bool addImport(const QQmlDirComponents &qmldircomponentsnetwork,
154 const QString &importedUri, const QString& prefix,
155 int vmaj, int vmin, QQmlScript::Import::Type importType,
156 bool isImplicitImport, QQmlImportDatabase *database,
157 QString *, QList<QQmlError> *errors);
159 bool resolveType(const QHashedStringRef &type, int *vmajor, int *vminor,
160 QQmlType** type_return, QString* url_return,
161 QList<QQmlError> *errors);
167 QQmlImportNamespace unqualifiedset;
169 QQmlImportNamespace *findQualifiedNamespace(const QHashedStringRef &);
170 QFieldList<QQmlImportNamespace, &QQmlImportNamespace::nextNamespace> qualifiedSets;
172 QQmlTypeLoader *typeLoader;
174 static inline QString tr(const char *str) {
175 return QQmlImportDatabase::tr(str);
179 static bool locateQmldir(const QString &uri, int vmaj, int vmin,
180 QQmlImportDatabase *database,
181 QString *outQmldirFilePath, QString *outUrl);
182 bool importExtension(const QString &absoluteFilePath, const QString &uri,
183 QQmlImportDatabase *database, QQmlDirComponents* components,
184 QQmlDirScripts *scripts,
185 QString *url, QList<QQmlError> *errors);
186 QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database);
191 \brief The QQmlImports class encapsulates one QML document's import statements.
194 QQmlImports::QQmlImports(const QQmlImports ©)
201 QQmlImports::operator =(const QQmlImports ©)
210 QQmlImports::QQmlImports(QQmlTypeLoader *typeLoader)
211 : d(new QQmlImportsPrivate(typeLoader))
215 QQmlImports::~QQmlImports()
222 Sets the base URL to be used for all relative file imports added.
224 void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString)
228 if (urlString.isEmpty()) {
229 d->base = url.toString();
231 //Q_ASSERT(url.toString() == urlString);
237 Returns the base URL to be used for all relative file imports added.
239 QUrl QQmlImports::baseUrl() const
244 void QQmlImports::populateCache(QQmlTypeNameCache *cache, QQmlEngine *engine) const
246 const QQmlImportNamespace &set = d->unqualifiedset;
248 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
249 const QQmlImportNamespace::Import &import = set.imports.at(ii);
250 QQmlTypeModule *module = QQmlMetaType::typeModule(import.uri, import.majversion);
252 cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, import.minversion));
255 for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) {
257 const QQmlImportNamespace &set = *ns;
259 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
260 const QQmlImportNamespace::Import &import = set.imports.at(ii);
261 QQmlTypeModule *module = QQmlMetaType::typeModule(import.uri, import.majversion);
263 QQmlTypeNameCache::Import &typeimport = cache->m_namedImports[set.prefix];
264 typeimport.modules.append(QQmlTypeModuleVersion(module, import.minversion));
267 QQmlMetaType::ModuleApi moduleApi = QQmlMetaType::moduleApi(import.uri, import.majversion,
269 if (moduleApi.script || moduleApi.qobject) {
270 QQmlTypeNameCache::Import &import = cache->m_namedImports[set.prefix];
271 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
272 import.moduleApi = ep->moduleApiInstance(moduleApi);
278 QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
280 QList<QQmlImports::ScriptReference> scripts;
282 const QQmlImportNamespace &set = d->unqualifiedset;
284 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
285 const QQmlImportNamespace::Import &import = set.imports.at(ii);
287 foreach (const QQmlDirParser::Script &script, import.qmlDirScripts) {
289 ref.nameSpace = script.nameSpace;
290 ref.location = QUrl(import.url).resolved(QUrl(script.fileName));
295 for (QQmlImportNamespace *ns = d->qualifiedSets.first(); ns; ns = d->qualifiedSets.next(ns)) {
296 const QQmlImportNamespace &set = *ns;
298 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
299 const QQmlImportNamespace::Import &import = set.imports.at(ii);
301 foreach (const QQmlDirParser::Script &script, import.qmlDirScripts) {
303 ref.nameSpace = script.nameSpace;
304 ref.qualifier = set.prefix;
305 ref.location = QUrl(import.url).resolved(QUrl(script.fileName));
317 The given (namespace qualified) \a type is resolved to either
319 \li a QQmlImportNamespace stored at \a ns_return,
320 \li a QQmlType stored at \a type_return, or
321 \li a component located at \a url_return.
324 If any return pointer is 0, the corresponding search is not done.
328 bool QQmlImports::resolveType(const QHashedStringRef &type,
329 QQmlType** type_return, QString* url_return, int *vmaj, int *vmin,
330 QQmlImportNamespace** ns_return, QList<QQmlError> *errors) const
332 QQmlImportNamespace* ns = d->findQualifiedNamespace(type);
338 if (type_return || url_return) {
339 if (d->resolveType(type,vmaj,vmin,type_return,url_return, errors)) {
340 if (qmlImportTrace()) {
341 #define RESOLVE_TYPE_DEBUG qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) \
342 << ')' << "::resolveType: " << type.toString() << " => "
344 if (type_return && *type_return && url_return && !url_return->isEmpty())
345 RESOLVE_TYPE_DEBUG << (*type_return)->typeName() << ' ' << *url_return;
346 if (type_return && *type_return)
347 RESOLVE_TYPE_DEBUG << (*type_return)->typeName();
348 if (url_return && !url_return->isEmpty())
349 RESOLVE_TYPE_DEBUG << *url_return;
351 #undef RESOLVE_TYPE_DEBUG
362 Searching \e only in the namespace \a ns (previously returned in a call to
363 resolveType(), \a type is found and returned to either
364 a QQmlType stored at \a type_return, or
365 a component located at \a url_return.
367 If either return pointer is 0, the corresponding search is not done.
369 bool QQmlImports::resolveType(QQmlImportNamespace* ns, const QHashedStringRef &type,
370 QQmlType** type_return, QString* url_return,
371 int *vmaj, int *vmin) const
373 return ns->resolveType(d->typeLoader,type,vmaj,vmin,type_return,url_return);
376 bool QQmlImportNamespace::Import::resolveType(QQmlTypeLoader *typeLoader,
377 const QHashedStringRef& type, int *vmajor, int *vminor,
378 QQmlType** type_return, QString* url_return,
379 QString *base, bool *typeRecursionDetected) const
381 if (majversion >= 0 && minversion >= 0) {
382 QQmlType *t = QQmlMetaType::qmlType(type, uri, majversion, minversion);
384 if (vmajor) *vmajor = majversion;
385 if (vminor) *vminor = minversion;
392 bool typeWasDeclaredInQmldir = false;
393 if (!qmlDirComponents.isEmpty()) {
394 QQmlDirComponents::ConstIterator it = qmlDirComponents.find(type);
395 if (it != qmlDirComponents.end()) {
396 typeWasDeclaredInQmldir = true;
397 // first found is last inserted - process in reverse
398 QQmlDirComponents::ConstIterator begin = it;
399 while (++it != qmlDirComponents.end() && it.key() == type) {}
402 const QQmlDirParser::Component &c = *it;
404 // importing version -1 means import ALL versions
405 if ((majversion == -1) || (c.majorVersion == majversion &&
406 minversion >= c.minorVersion)) {
408 QString candidate = resolveLocalUrl(QString(url + c.typeName + dotqml_string), c.fileName);
409 if (c.internal && base) {
410 if (resolveLocalUrl(*base, c.fileName) != candidate)
411 continue; // failed attempt to access an internal type
413 if (base && *base == candidate) {
414 if (typeRecursionDetected)
415 *typeRecursionDetected = true;
416 continue; // no recursion
419 *url_return = candidate;
422 } while (it != begin);
426 if (!typeWasDeclaredInQmldir && !isLibrary) {
427 QString qmlUrl = url + QString::fromRawData(type.constData(), type.length()) + dotqml_string;
431 if (QQmlFile::isBundle(qmlUrl)) {
432 exists = QQmlFile::bundleFileExists(qmlUrl, typeLoader->engine());
434 exists = !typeLoader->absoluteFilePath(QQmlFile::urlToLocalFileOrQrc(qmlUrl)).isEmpty();
438 if (base && *base == qmlUrl) { // no recursion
439 if (typeRecursionDetected)
440 *typeRecursionDetected = true;
443 *url_return = qmlUrl;
452 bool QQmlImportsPrivate::resolveType(const QHashedStringRef& type, int *vmajor, int *vminor,
453 QQmlType** type_return, QString* url_return,
454 QList<QQmlError> *errors)
456 QQmlImportNamespace *s = 0;
457 int dot = type.indexOf(QLatin1Char('.'));
459 QHashedStringRef namespaceName(type.constData(), dot);
460 s = findQualifiedNamespace(namespaceName);
464 error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName.toString()));
465 errors->prepend(error);
469 int ndot = type.indexOf(QLatin1Char('.'),dot+1);
473 error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed"));
474 errors->prepend(error);
481 QHashedStringRef unqualifiedtype = dot < 0 ? type : QHashedStringRef(type.constData()+dot+1, type.length()-dot-1);
483 if (s->resolveType(typeLoader,unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errors))
485 if (s->imports.count() == 1 && !s->imports.at(0).isLibrary && url_return && s != &unqualifiedset) {
486 // qualified, and only 1 url
487 *url_return = resolveLocalUrl(s->imports.at(0).url, unqualifiedtype.toString() + QLatin1String(".qml"));
495 bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type,
496 int *vmajor, int *vminor, QQmlType** type_return,
497 QString* url_return, QString *base, QList<QQmlError> *errors)
499 bool typeRecursionDetected = false;
500 for (int i=0; i<imports.count(); ++i) {
501 const Import &import = imports.at(i);
502 if (import.resolveType(typeLoader, type, vmajor, vminor, type_return, url_return,
503 base, &typeRecursionDetected)) {
504 if (qmlCheckTypes()) {
505 // check for type clashes
506 for (int j = i+1; j<imports.count(); ++j) {
507 const Import &import2 = imports.at(j);
508 if (import2.resolveType(typeLoader, type, vmajor, vminor, 0, 0, base)) {
510 QString u1 = imports.at(i).url;
511 QString u2 = imports.at(j).url;
514 int dot = b.lastIndexOf(QLatin1Char('.'));
517 QString l = b.left(dot);
518 if (u1.startsWith(b))
519 u1 = u1.mid(b.count());
521 u1 = QQmlImportDatabase::tr("local directory");
522 if (u2.startsWith(b))
523 u2 = u2.mid(b.count());
525 u2 = QQmlImportDatabase::tr("local directory");
531 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2));
533 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
535 .arg(imports.at(i).majversion).arg(imports.at(i).minversion)
536 .arg(imports.at(j).majversion).arg(imports.at(j).minversion));
538 errors->prepend(error);
549 if (typeRecursionDetected)
550 error.setDescription(QQmlImportDatabase::tr("is instantiated recursively"));
552 error.setDescription(QQmlImportDatabase::tr("is not a type"));
553 errors->prepend(error);
558 QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader)
559 : ref(1), typeLoader(loader) {
562 QQmlImportsPrivate::~QQmlImportsPrivate()
564 while (QQmlImportNamespace *ns = qualifiedSets.takeFirst())
568 QQmlImportNamespace *QQmlImportsPrivate::findQualifiedNamespace(const QHashedStringRef &prefix)
570 for (QQmlImportNamespace *ns = qualifiedSets.first(); ns; ns = qualifiedSets.next(ns)) {
571 if (prefix == ns->prefix)
578 Import an extension defined by a qmldir file.
580 \a qmldirFilePath is either a raw file path, or a bundle url.
582 This call will modify the \a url parameter if importing the extension redirects to
585 bool QQmlImportsPrivate::importExtension(const QString &qmldirFilePath,
587 QQmlImportDatabase *database,
588 QQmlDirComponents* components,
589 QQmlDirScripts* scripts,
591 QList<QQmlError> *errors)
593 // As qmldirFilePath is always local, this method can always return synchronously
594 const QQmlDirParser *qmldirParser = typeLoader->qmlDirParser(qmldirFilePath, uri, url);
595 if (qmldirParser->hasError()) {
599 if (QQmlFile::isBundle(qmldirFilePath))
600 url = QUrl(qmldirFilePath);
602 url = QUrl::fromLocalFile(qmldirFilePath);
604 const QList<QQmlError> qmldirErrors = qmldirParser->errors(uri);
605 for (int i = 0; i < qmldirErrors.size(); ++i) {
606 QQmlError error = qmldirErrors.at(i);
608 errors->append(error);
614 if (qmlImportTrace())
615 qDebug().nospace() << "QQmlImports(" << qPrintable(base) << "::importExtension: "
616 << "loaded " << qmldirFilePath;
618 if (!database->qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(qmldirFilePath)) {
620 QString qmldirPath = qmldirFilePath;
622 if (QQmlFile::isBundle(*url))
623 qmldirPath = QQmlFile::bundleFileName(*url, typeLoader->engine());
625 int slash = qmldirFilePath.lastIndexOf(QLatin1Char('/'));
627 qmldirPath.truncate(slash);
629 foreach (const QQmlDirParser::Plugin &plugin, qmldirParser->plugins()) {
630 QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath,
631 plugin.path, plugin.name);
632 if (!resolvedFilePath.isEmpty()) {
633 if (!database->importPlugin(resolvedFilePath, uri, errors)) {
635 // XXX TODO: should we leave the import plugin error alone?
636 // Here, we pop it off the top and coalesce it into this error's message.
637 // The reason is that the lower level may add url and line/column numbering information.
638 QQmlError poppedError = errors->takeFirst();
640 error.setDescription(tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(poppedError.description()));
641 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
642 errors->prepend(error);
649 error.setDescription(tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name));
650 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
651 errors->prepend(error);
657 database->qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(qmldirFilePath);
661 *components = qmldirParser->components();
663 *scripts = qmldirParser->scripts();
668 QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database)
670 struct I { static bool greaterThan(const QString &s1, const QString &s2) {
674 QString dir = dir_arg;
675 if (dir.endsWith(QLatin1Char('/')) || dir.endsWith(QLatin1Char('\\')))
678 QStringList paths = database->fileImportPath;
679 qSort(paths.begin(), paths.end(), I::greaterThan); // Ensure subdirs preceed their parents.
681 QString stableRelativePath = dir;
682 foreach(const QString &path, paths) {
683 if (dir.startsWith(path)) {
684 stableRelativePath = dir.mid(path.length()+1);
689 stableRelativePath.replace(QLatin1Char('\\'), QLatin1Char('/'));
691 // remove optional versioning in dot notation from uri
692 int lastSlash = stableRelativePath.lastIndexOf(QLatin1Char('/'));
693 if (lastSlash >= 0) {
694 int versionDot = stableRelativePath.indexOf(QLatin1Char('.'), lastSlash);
696 stableRelativePath = stableRelativePath.left(versionDot);
699 stableRelativePath.replace(QLatin1Char('/'), QLatin1Char('.'));
701 return stableRelativePath;
705 Locates the qmldir file for \a uri version \a vmaj.vmin. Returns true if found,
706 and fills in outQmldirFilePath and outQmldirUrl appropriately. Otherwise returns
709 bool QQmlImportsPrivate::locateQmldir(const QString &uri, int vmaj, int vmin,
710 QQmlImportDatabase *database,
711 QString *outQmldirFilePath,
712 QString *outQmldirPathUrl)
714 Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries
718 QQmlImportDatabase::QmldirCache *cacheHead = 0;
720 QQmlImportDatabase::QmldirCache **cachePtr = database->qmldirCache.value(uri);
722 cacheHead = *cachePtr;
723 QQmlImportDatabase::QmldirCache *cache = cacheHead;
725 if (cache->versionMajor == vmaj && cache->versionMinor == vmin) {
726 *outQmldirFilePath = cache->qmldirFilePath;
727 *outQmldirPathUrl = cache->qmldirPathUrl;
728 return !cache->qmldirFilePath.isEmpty();
735 static QLatin1Char Slash('/');
736 static QLatin1String Slash_qmldir("/qmldir");
739 url.replace(QLatin1Char('.'), Slash);
741 // step 0: search for extension with fully encoded version number (eg. MyModule.3.2)
742 // step 1: search for extension with encoded version major (eg. MyModule.3)
743 // step 2: search for extension without version number (eg. MyModule)
744 for (int step = 0; step <= 2; ++step) {
745 foreach (const QString &p, database->fileImportPath) {
746 QString dir = p + Slash + url;
748 QString qmldirFile = dir;
749 if (step == 0) qmldirFile += QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin);
750 else if (step == 1) qmldirFile += QString(QLatin1String(".%1")).arg(vmaj);
751 qmldirFile += Slash_qmldir;
753 QQmlTypeLoader &typeLoader = QQmlEnginePrivate::get(database->engine)->typeLoader;
754 QString absoluteFilePath = typeLoader.absoluteFilePath(qmldirFile);
755 if (!absoluteFilePath.isEmpty()) {
756 QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(Slash)+1);
757 if (absolutePath.at(0) == QLatin1Char(':'))
758 url = QLatin1String("qrc://") + absolutePath.mid(1);
760 url = QUrl::fromLocalFile(absolutePath).toString();
762 QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
763 cache->versionMajor = vmaj;
764 cache->versionMinor = vmin;
765 cache->qmldirFilePath = absoluteFilePath;
766 cache->qmldirPathUrl = url;
767 cache->next = cacheHead;
768 database->qmldirCache.insert(uri, cache);
770 *outQmldirFilePath = absoluteFilePath;
771 *outQmldirPathUrl = url;
778 QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
779 cache->versionMajor = vmaj;
780 cache->versionMinor = vmin;
781 cache->next = cacheHead;
782 database->qmldirCache.insert(uri, cache);
787 bool QQmlImportsPrivate::addImport(const QQmlDirComponents &qmldircomponentsnetwork,
788 const QString& importedUri, const QString& prefix,
789 int vmaj, int vmin, QQmlScript::Import::Type importType,
790 bool isImplicitImport, QQmlImportDatabase *database,
791 QString *outUrl, QList<QQmlError> *errors)
794 Q_ASSERT(importType == QQmlScript::Import::File || importType == QQmlScript::Import::Library);
796 static QLatin1String String_qmldir("qmldir");
797 static QLatin1String Slash_qmldir("/qmldir");
798 static QLatin1Char Slash('/');
800 // The list of components defined by a qmldir file for this import.
801 QQmlDirComponents qmldircomponents;
802 // The list of scripts defined by a qmldir file for this import.
803 QQmlDirScripts qmldirscripts;
804 // The namespace that this import affects.
805 QQmlImportNamespace *importSet = 0;
806 // The uri for this import. For library imports this is the same as importedUri
807 // specified by the user, but it may be different in the case of file imports.
809 // The url for the path containing files for this import.
812 qmldircomponents = qmldircomponentsnetwork;
814 if (prefix.isEmpty()) {
815 importSet = &unqualifiedset;
817 importSet = findQualifiedNamespace(prefix);
820 importSet = new QQmlImportNamespace;
821 importSet->prefix = prefix;
822 qualifiedSets.append(importSet);
826 if (importType == QQmlScript::Import::Library) {
827 Q_ASSERT(vmaj >= 0 && vmin >= 0);
829 QString qmldirFilePath;
831 if (locateQmldir(uri, vmaj, vmin, database, &qmldirFilePath, &url)) {
833 if (!importExtension(qmldirFilePath, uri, database, &qmldircomponents,
834 &qmldirscripts, &url, errors))
839 if (!QQmlMetaType::isModule(uri, vmaj, vmin)) {
841 if (qmldircomponents.isEmpty() && qmldirscripts.isEmpty()) {
843 if (QQmlMetaType::isAnyModule(uri))
844 error.setDescription(tr("module \"%1\" version %2.%3 is not installed").arg(importedUri).arg(vmaj).arg(vmin));
846 error.setDescription(tr("module \"%1\" is not installed").arg(importedUri));
847 errors->prepend(error);
850 int lowest_min = INT_MAX;
851 int highest_min = INT_MIN;
852 typedef QQmlDirComponents::const_iterator ConstIterator;
853 typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
855 ConstIterator cend = qmldircomponents.constEnd();
856 for (ConstIterator cit = qmldircomponents.constBegin(); cit != cend; ++cit) {
857 for (ConstIterator cit2 = qmldircomponents.constBegin(); cit2 != cit; ++cit2) {
858 if ((cit2->typeName == cit->typeName) &&
859 (cit2->majorVersion == cit->majorVersion) &&
860 (cit2->minorVersion == cit->minorVersion)) {
861 // This is entry clashes with a predecessor
863 error.setDescription(tr("\"%1\" version %2.%3 is already defined in module \"%4\"")
864 .arg(cit->typeName).arg(cit->majorVersion).arg(cit->minorVersion).arg(importedUri));
865 errors->prepend(error);
870 if (cit->majorVersion == vmaj) {
871 lowest_min = qMin(lowest_min, cit->minorVersion);
872 highest_min = qMax(highest_min, cit->minorVersion);
876 SConstIterator send = qmldirscripts.constEnd();
877 for (SConstIterator sit = qmldirscripts.constBegin(); sit != send; ++sit) {
878 for (SConstIterator sit2 = qmldirscripts.constBegin(); sit2 != sit; ++sit2) {
879 if ((sit2->nameSpace == sit->nameSpace) &&
880 (sit2->majorVersion == sit->majorVersion) &&
881 (sit2->minorVersion == sit->minorVersion)) {
882 // This is entry clashes with a predecessor
884 error.setDescription(tr("\"%1\" version %2.%3 is already defined in module \"%4\"")
885 .arg(sit->nameSpace).arg(sit->majorVersion).arg(sit->minorVersion).arg(importedUri));
886 errors->prepend(error);
891 if (sit->majorVersion == vmaj) {
892 lowest_min = qMin(lowest_min, sit->minorVersion);
893 highest_min = qMax(highest_min, sit->minorVersion);
897 if (lowest_min > vmin || highest_min < vmin) {
899 error.setDescription(tr("module \"%1\" version %2.%3 is not installed").arg(importedUri).arg(vmaj).arg(vmin));
900 errors->prepend(error);
908 Q_ASSERT(importType == QQmlScript::Import::File);
910 if (qmldircomponents.isEmpty()) {
912 QString qmldirPath = uri;
913 if (uri.endsWith(Slash)) qmldirPath += String_qmldir;
914 else qmldirPath += Slash_qmldir;
915 QString qmldirUrl = resolveLocalUrl(base, qmldirPath);
917 if (QQmlFile::isBundle(qmldirUrl)) {
919 QString dir = resolveLocalUrl(base, uri);
920 Q_ASSERT(QQmlFile::isBundle(dir));
921 if (!QQmlFile::bundleDirectoryExists(dir, typeLoader->engine())) {
922 if (!isImplicitImport) {
924 error.setDescription(tr("\"%1\": no such directory").arg(importedUri));
925 error.setUrl(QUrl(qmldirUrl));
926 errors->prepend(error);
931 // Transforms the (possible relative) uri into our best guess relative to the
933 uri = resolvedUri(dir, database);
935 if (uri.endsWith(Slash))
937 if (QQmlFile::bundleFileExists(qmldirUrl, typeLoader->engine())) {
938 if (!importExtension(qmldirUrl, uri, database, &qmldircomponents,
939 &qmldirscripts, &url, errors))
943 } else if (QQmlFile::isLocalFile(qmldirUrl)) {
945 QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
946 Q_ASSERT(!localFileOrQrc.isEmpty());
948 QString dir = QQmlFile::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
949 if (!typeLoader->directoryExists(dir)) {
950 if (!isImplicitImport) {
952 error.setDescription(tr("\"%1\": no such directory").arg(importedUri));
953 error.setUrl(QUrl(qmldirUrl));
954 errors->prepend(error);
959 // Transforms the (possible relative) uri into our best guess relative to the
961 uri = resolvedUri(dir, database);
963 if (uri.endsWith(Slash))
965 if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) {
966 if (!importExtension(localFileOrQrc, uri, database, &qmldircomponents,
967 &qmldirscripts, &url, errors))
971 } else if (prefix.isEmpty()) {
973 if (!isImplicitImport) {
975 error.setDescription(tr("import \"%1\" has no qmldir and no namespace").arg(uri));
976 error.setUrl(QUrl(qmldirUrl));
977 errors->prepend(error);
985 url = resolveLocalUrl(base, importedUri);
986 if (!url.endsWith(Slash))
990 Q_ASSERT(url.isEmpty() || url.endsWith(Slash));
992 QMap<QString, QQmlDirParser::Script> scripts;
993 if (!qmldirscripts.isEmpty()) {
994 // Verify that we haven't imported these scripts already
995 for (QList<QQmlImportNamespace::Import>::const_iterator it = importSet->imports.constBegin();
996 it != importSet->imports.constEnd(); ++it) {
997 if (it->uri == uri) {
999 error.setDescription(tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url));
1000 errors->prepend(error);
1005 for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
1006 sit != qmldirscripts.constEnd(); ++sit) {
1007 // Only include scripts that match our requested version
1008 if (((vmaj == -1) || (sit->majorVersion == vmaj)) &&
1009 ((vmin == -1) || (sit->minorVersion <= vmin))) {
1011 // Load the highest version that matches
1012 QMap<QString, QQmlDirParser::Script>::iterator it = scripts.find(sit->nameSpace);
1013 if (it == scripts.end() || (it->minorVersion < sit->minorVersion)) {
1014 scripts.insert(sit->nameSpace, *sit);
1020 QQmlImportNamespace::Import import;
1023 import.majversion = vmaj;
1024 import.minversion = vmin;
1025 import.isLibrary = importType == QQmlScript::Import::Library;
1026 import.qmlDirComponents = qmldircomponents;
1027 import.qmlDirScripts = scripts.values();
1029 importSet->imports.prepend(import);
1031 if (outUrl) *outUrl = url;
1039 Adds an implicit "." file import. This is equivalent to calling addImport(), but error
1040 messages related to the path or qmldir file not existing are suppressed.
1042 bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb,
1043 const QQmlDirComponents &qmldircomponentsnetwork,
1044 QList<QQmlError> *errors)
1048 if (qmlImportTrace())
1049 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString())
1050 << ")::addImplicitImport";
1053 return d->addImport(qmldircomponentsnetwork, QLatin1String("."), QString(), -1, -1,
1054 QQmlScript::Import::File, true, importDb, 0, errors);
1060 Adds information to \a imports such that subsequent calls to resolveType()
1061 will resolve types qualified by \a prefix by considering types found at the given \a uri.
1063 The uri is either a directory (if importType is FileImport), or a URI resolved using paths
1064 added via addImportPath() (if importType is LibraryImport).
1066 The \a prefix may be empty, in which case the import location is considered for
1069 The base URL must already have been set with Import::setBaseUrl().
1071 Optionally, the url the import resolved to can be returned by providing the url parameter.
1072 Not all imports will result in an output url being generated, in which case the url will
1073 be set to an empty string.
1075 Returns true on success, and false on failure. In case of failure, the errors array will
1076 filled appropriately.
1078 bool QQmlImports::addImport(QQmlImportDatabase *importDb,
1079 const QString& uri, const QString& prefix, int vmaj, int vmin,
1080 QQmlScript::Import::Type importType,
1081 const QQmlDirComponents &qmldircomponentsnetwork,
1082 QString *url, QList<QQmlError> *errors)
1086 if (qmlImportTrace())
1087 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addImport: "
1088 << uri << ' ' << vmaj << '.' << vmin << ' '
1089 << (importType==QQmlScript::Import::Library? "Library" : "File")
1090 << " as " << prefix;
1092 return d->addImport(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, false,
1093 importDb, url, errors);
1097 \class QQmlImportDatabase
1098 \brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine.
1101 QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
1104 filePluginPath << QLatin1String(".");
1106 // Search order is applicationDirPath(), $QML_IMPORT_PATH, QLibraryInfo::ImportsPath
1108 #ifndef QT_NO_SETTINGS
1109 QString installImportsPath = QLibraryInfo::location(QLibraryInfo::ImportsPath);
1110 addImportPath(installImportsPath);
1111 #endif // QT_NO_SETTINGS
1114 QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");
1115 if (!envImportPath.isEmpty()) {
1116 #if defined(Q_OS_WIN)
1117 QLatin1Char pathSep(';');
1119 QLatin1Char pathSep(':');
1121 QStringList paths = QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts);
1122 for (int ii = paths.count() - 1; ii >= 0; --ii)
1123 addImportPath(paths.at(ii));
1126 addImportPath(QCoreApplication::applicationDirPath());
1129 QQmlImportDatabase::~QQmlImportDatabase()
1131 for (QStringHash<QmldirCache *>::ConstIterator iter = qmldirCache.begin();
1132 iter != qmldirCache.end(); ++iter) {
1134 QmldirCache *c = *iter;
1136 QmldirCache *n = c->next;
1146 Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
1147 The \a prefix must contain the dot.
1149 \a qmldirPath is the location of the qmldir file.
1151 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1152 const QString &qmldirPath,
1153 const QString &qmldirPluginPath,
1154 const QString &baseName, const QStringList &suffixes,
1155 const QString &prefix)
1157 QStringList searchPaths = filePluginPath;
1158 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
1159 if (!qmldirPluginPathIsRelative)
1160 searchPaths.prepend(qmldirPluginPath);
1162 foreach (const QString &pluginPath, searchPaths) {
1164 QString resolvedPath;
1165 if (pluginPath == QLatin1String(".")) {
1166 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String("."))
1167 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + qmldirPluginPath);
1169 resolvedPath = qmldirPath;
1171 if (QDir::isRelativePath(pluginPath))
1172 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + pluginPath);
1174 resolvedPath = pluginPath;
1177 // hack for resources, should probably go away
1178 if (resolvedPath.startsWith(QLatin1Char(':')))
1179 resolvedPath = QCoreApplication::applicationDirPath();
1181 if (!resolvedPath.endsWith(QLatin1Char('/')))
1182 resolvedPath += QLatin1Char('/');
1184 foreach (const QString &suffix, suffixes) {
1185 QString pluginFileName = prefix;
1187 pluginFileName += baseName;
1188 pluginFileName += suffix;
1190 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + pluginFileName);
1191 if (!absolutePath.isEmpty())
1192 return absolutePath;
1196 if (qmlImportTrace())
1197 qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName
1198 << "in" << qmldirPath;
1206 Returns the result of the merge of \a baseName with \a dir and the platform suffix.
1209 \header \li Platform \li Valid suffixes
1210 \row \li Windows \li \c .dll
1211 \row \li Unix/Linux \li \c .so
1212 \row \li AIX \li \c .a
1213 \row \li HP-UX \li \c .sl, \c .so (HP-UXi)
1214 \row \li Mac OS X \li \c .dylib, \c .bundle, \c .so
1217 Version number on unix are ignored.
1219 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1220 const QString &qmldirPath, const QString &qmldirPluginPath,
1221 const QString &baseName)
1223 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
1224 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
1227 << QLatin1String("d.dll") // try a qmake-style debug build first
1229 << QLatin1String(".dll"));
1232 # if defined(Q_OS_DARWIN)
1234 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
1237 << QLatin1String("_debug.dylib") // try a qmake-style debug build first
1238 << QLatin1String(".dylib")
1240 << QLatin1String(".dylib")
1241 << QLatin1String("_debug.dylib") // try a qmake-style debug build after
1243 << QLatin1String(".so")
1244 << QLatin1String(".bundle"),
1245 QLatin1String("lib"));
1246 # else // Generic Unix
1247 QStringList validSuffixList;
1249 # if defined(Q_OS_HPUX)
1251 See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF":
1252 "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit),
1253 the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix."
1255 validSuffixList << QLatin1String(".sl");
1257 validSuffixList << QLatin1String(".so");
1259 # elif defined(Q_OS_AIX)
1260 validSuffixList << QLatin1String(".a") << QLatin1String(".so");
1261 # elif defined(Q_OS_UNIX)
1262 validSuffixList << QLatin1String(".so");
1265 // Examples of valid library names:
1268 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib"));
1277 QStringList QQmlImportDatabase::pluginPathList() const
1279 return filePluginPath;
1285 void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
1287 if (qmlImportTrace())
1288 qDebug().nospace() << "QQmlImportDatabase::setPluginPathList: " << paths;
1290 filePluginPath = paths;
1296 void QQmlImportDatabase::addPluginPath(const QString& path)
1298 if (qmlImportTrace())
1299 qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path;
1301 QUrl url = QUrl(path);
1302 if (url.isRelative() || url.scheme() == QLatin1String("file")
1303 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1304 QDir dir = QDir(path);
1305 filePluginPath.prepend(dir.canonicalPath());
1307 filePluginPath.prepend(path);
1314 void QQmlImportDatabase::addImportPath(const QString& path)
1316 if (qmlImportTrace())
1317 qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path;
1322 QUrl url = QUrl(path);
1325 if (url.isRelative() || url.scheme() == QLatin1String("file")
1326 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1327 QDir dir = QDir(path);
1328 cPath = dir.canonicalPath();
1331 cPath.replace(QLatin1Char('\\'), QLatin1Char('/'));
1334 if (!cPath.isEmpty()
1335 && !fileImportPath.contains(cPath))
1336 fileImportPath.prepend(cPath);
1342 QStringList QQmlImportDatabase::importPathList() const
1344 return fileImportPath;
1350 void QQmlImportDatabase::setImportPathList(const QStringList &paths)
1352 if (qmlImportTrace())
1353 qDebug().nospace() << "QQmlImportDatabase::setImportPathList: " << paths;
1355 fileImportPath = paths;
1361 bool QQmlImportDatabase::importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors)
1363 if (qmlImportTrace())
1364 qDebug().nospace() << "QQmlImportDatabase::importPlugin: " << uri << " from " << filePath;
1366 #ifndef QT_NO_LIBRARY
1367 QFileInfo fileInfo(filePath);
1368 const QString absoluteFilePath = fileInfo.absoluteFilePath();
1370 bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
1371 bool typesRegistered = qmlEnginePluginsWithRegisteredTypes()->contains(absoluteFilePath);
1373 if (typesRegistered) {
1374 Q_ASSERT_X(qmlEnginePluginsWithRegisteredTypes()->value(absoluteFilePath) == uri,
1375 "QQmlImportDatabase::importExtension",
1376 "Internal error: Plugin imported previously with different uri");
1379 if (!engineInitialized || !typesRegistered) {
1380 if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
1383 error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
1384 errors->prepend(error);
1388 QPluginLoader loader(absoluteFilePath);
1390 if (!loader.load()) {
1393 error.setDescription(loader.errorString());
1394 errors->prepend(error);
1399 QObject *instance = loader.instance();
1400 if (QQmlTypesExtensionInterface *iface = qobject_cast<QQmlExtensionInterface *>(instance)) {
1402 const QByteArray bytes = uri.toUtf8();
1403 const char *moduleId = bytes.constData();
1404 if (!typesRegistered) {
1406 // XXX thread this code should probably be protected with a mutex.
1407 qmlEnginePluginsWithRegisteredTypes()->insert(absoluteFilePath, uri);
1408 iface->registerTypes(moduleId);
1410 if (!engineInitialized) {
1411 // things on the engine (eg. adding new global objects) have to be done for every
1413 // XXX protect against double initialization
1414 initializedPlugins.insert(absoluteFilePath);
1416 QQmlExtensionInterface *eiface =
1417 qobject_cast<QQmlExtensionInterface *>(instance);
1419 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
1420 ep->typeLoader.initializeEngine(eiface, moduleId);
1426 error.setDescription(loader.errorString());
1427 errors->prepend(error);