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.end();
856 for (ConstIterator cit = qmldircomponents.begin(); cit != cend; ++cit) {
857 if (cit->majorVersion == vmaj) {
858 lowest_min = qMin(lowest_min, cit->minorVersion);
859 highest_min = qMax(highest_min, cit->minorVersion);
863 for (SConstIterator cit = qmldirscripts.constBegin();
864 cit != qmldirscripts.constEnd(); ++cit) {
865 if (cit->majorVersion == vmaj) {
866 lowest_min = qMin(lowest_min, cit->minorVersion);
867 highest_min = qMax(highest_min, cit->minorVersion);
871 if (lowest_min > vmin || highest_min < vmin) {
873 error.setDescription(tr("module \"%1\" version %2.%3 is not installed").arg(importedUri).arg(vmaj).arg(vmin));
874 errors->prepend(error);
882 Q_ASSERT(importType == QQmlScript::Import::File);
884 if (qmldircomponents.isEmpty()) {
886 QString qmldirPath = uri;
887 if (uri.endsWith(Slash)) qmldirPath += String_qmldir;
888 else qmldirPath += Slash_qmldir;
889 QString qmldirUrl = resolveLocalUrl(base, qmldirPath);
891 if (QQmlFile::isBundle(qmldirUrl)) {
893 QString dir = resolveLocalUrl(base, uri);
894 Q_ASSERT(QQmlFile::isBundle(dir));
895 if (!QQmlFile::bundleDirectoryExists(dir, typeLoader->engine())) {
896 if (!isImplicitImport) {
898 error.setDescription(tr("\"%1\": no such directory").arg(importedUri));
899 error.setUrl(QUrl(qmldirUrl));
900 errors->prepend(error);
905 // Transforms the (possible relative) uri into our best guess relative to the
907 uri = resolvedUri(dir, database);
909 if (uri.endsWith(Slash))
911 if (QQmlFile::bundleFileExists(qmldirUrl, typeLoader->engine())) {
912 if (!importExtension(qmldirUrl, uri, database, &qmldircomponents,
913 &qmldirscripts, &url, errors))
917 } else if (QQmlFile::isLocalFile(qmldirUrl)) {
919 QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
920 Q_ASSERT(!localFileOrQrc.isEmpty());
922 QString dir = QQmlFile::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
923 if (!typeLoader->directoryExists(dir)) {
924 if (!isImplicitImport) {
926 error.setDescription(tr("\"%1\": no such directory").arg(importedUri));
927 error.setUrl(QUrl(qmldirUrl));
928 errors->prepend(error);
933 // Transforms the (possible relative) uri into our best guess relative to the
935 uri = resolvedUri(dir, database);
937 if (uri.endsWith(Slash))
939 if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) {
940 if (!importExtension(localFileOrQrc, uri, database, &qmldircomponents,
941 &qmldirscripts, &url, errors))
945 } else if (prefix.isEmpty()) {
947 if (!isImplicitImport) {
949 error.setDescription(tr("import \"%1\" has no qmldir and no namespace").arg(uri));
950 error.setUrl(QUrl(qmldirUrl));
951 errors->prepend(error);
959 url = resolveLocalUrl(base, importedUri);
960 if (!url.endsWith(Slash))
964 Q_ASSERT(url.isEmpty() || url.endsWith(Slash));
966 QMap<QString, QQmlDirParser::Script> scripts;
967 if (!qmldirscripts.isEmpty()) {
968 // Verify that we haven't imported these scripts already
969 for (QList<QQmlImportNamespace::Import>::const_iterator it = importSet->imports.constBegin();
970 it != importSet->imports.constEnd(); ++it) {
971 if (it->uri == uri) {
973 error.setDescription(tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url));
974 errors->prepend(error);
979 for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
980 sit != qmldirscripts.constEnd(); ++sit) {
981 // Only include scripts that match our requested version
982 if (((vmaj == -1) || (sit->majorVersion == vmaj)) &&
983 ((vmin == -1) || (sit->minorVersion <= vmin))) {
985 // Load the highest version that matches
986 QMap<QString, QQmlDirParser::Script>::iterator it = scripts.find(sit->nameSpace);
987 if (it == scripts.end() || (it->minorVersion < sit->minorVersion)) {
988 scripts.insert(sit->nameSpace, *sit);
994 QQmlImportNamespace::Import import;
997 import.majversion = vmaj;
998 import.minversion = vmin;
999 import.isLibrary = importType == QQmlScript::Import::Library;
1000 import.qmlDirComponents = qmldircomponents;
1001 import.qmlDirScripts = scripts.values();
1003 importSet->imports.prepend(import);
1005 if (outUrl) *outUrl = url;
1013 Adds an implicit "." file import. This is equivalent to calling addImport(), but error
1014 messages related to the path or qmldir file not existing are suppressed.
1016 bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb,
1017 const QQmlDirComponents &qmldircomponentsnetwork,
1018 QList<QQmlError> *errors)
1022 if (qmlImportTrace())
1023 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString())
1024 << ")::addImplicitImport";
1027 return d->addImport(qmldircomponentsnetwork, QLatin1String("."), QString(), -1, -1,
1028 QQmlScript::Import::File, true, importDb, 0, errors);
1034 Adds information to \a imports such that subsequent calls to resolveType()
1035 will resolve types qualified by \a prefix by considering types found at the given \a uri.
1037 The uri is either a directory (if importType is FileImport), or a URI resolved using paths
1038 added via addImportPath() (if importType is LibraryImport).
1040 The \a prefix may be empty, in which case the import location is considered for
1043 The base URL must already have been set with Import::setBaseUrl().
1045 Optionally, the url the import resolved to can be returned by providing the url parameter.
1046 Not all imports will result in an output url being generated, in which case the url will
1047 be set to an empty string.
1049 Returns true on success, and false on failure. In case of failure, the errors array will
1050 filled appropriately.
1052 bool QQmlImports::addImport(QQmlImportDatabase *importDb,
1053 const QString& uri, const QString& prefix, int vmaj, int vmin,
1054 QQmlScript::Import::Type importType,
1055 const QQmlDirComponents &qmldircomponentsnetwork,
1056 QString *url, QList<QQmlError> *errors)
1060 if (qmlImportTrace())
1061 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ')' << "::addImport: "
1062 << uri << ' ' << vmaj << '.' << vmin << ' '
1063 << (importType==QQmlScript::Import::Library? "Library" : "File")
1064 << " as " << prefix;
1066 return d->addImport(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, false,
1067 importDb, url, errors);
1071 \class QQmlImportDatabase
1072 \brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine.
1075 QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
1078 filePluginPath << QLatin1String(".");
1080 // Search order is applicationDirPath(), $QML_IMPORT_PATH, QLibraryInfo::ImportsPath
1082 #ifndef QT_NO_SETTINGS
1083 QString installImportsPath = QLibraryInfo::location(QLibraryInfo::ImportsPath);
1084 addImportPath(installImportsPath);
1085 #endif // QT_NO_SETTINGS
1088 QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");
1089 if (!envImportPath.isEmpty()) {
1090 #if defined(Q_OS_WIN)
1091 QLatin1Char pathSep(';');
1093 QLatin1Char pathSep(':');
1095 QStringList paths = QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts);
1096 for (int ii = paths.count() - 1; ii >= 0; --ii)
1097 addImportPath(paths.at(ii));
1100 addImportPath(QCoreApplication::applicationDirPath());
1103 QQmlImportDatabase::~QQmlImportDatabase()
1105 for (QStringHash<QmldirCache *>::ConstIterator iter = qmldirCache.begin();
1106 iter != qmldirCache.end(); ++iter) {
1108 QmldirCache *c = *iter;
1110 QmldirCache *n = c->next;
1120 Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
1121 The \a prefix must contain the dot.
1123 \a qmldirPath is the location of the qmldir file.
1125 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1126 const QString &qmldirPath,
1127 const QString &qmldirPluginPath,
1128 const QString &baseName, const QStringList &suffixes,
1129 const QString &prefix)
1131 QStringList searchPaths = filePluginPath;
1132 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
1133 if (!qmldirPluginPathIsRelative)
1134 searchPaths.prepend(qmldirPluginPath);
1136 foreach (const QString &pluginPath, searchPaths) {
1138 QString resolvedPath;
1139 if (pluginPath == QLatin1String(".")) {
1140 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String("."))
1141 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + qmldirPluginPath);
1143 resolvedPath = qmldirPath;
1145 if (QDir::isRelativePath(pluginPath))
1146 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + pluginPath);
1148 resolvedPath = pluginPath;
1151 // hack for resources, should probably go away
1152 if (resolvedPath.startsWith(QLatin1Char(':')))
1153 resolvedPath = QCoreApplication::applicationDirPath();
1155 if (!resolvedPath.endsWith(QLatin1Char('/')))
1156 resolvedPath += QLatin1Char('/');
1158 foreach (const QString &suffix, suffixes) {
1159 QString pluginFileName = prefix;
1161 pluginFileName += baseName;
1162 pluginFileName += suffix;
1164 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + pluginFileName);
1165 if (!absolutePath.isEmpty())
1166 return absolutePath;
1170 if (qmlImportTrace())
1171 qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName
1172 << "in" << qmldirPath;
1180 Returns the result of the merge of \a baseName with \a dir and the platform suffix.
1183 \header \li Platform \li Valid suffixes
1184 \row \li Windows \li \c .dll
1185 \row \li Unix/Linux \li \c .so
1186 \row \li AIX \li \c .a
1187 \row \li HP-UX \li \c .sl, \c .so (HP-UXi)
1188 \row \li Mac OS X \li \c .dylib, \c .bundle, \c .so
1191 Version number on unix are ignored.
1193 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1194 const QString &qmldirPath, const QString &qmldirPluginPath,
1195 const QString &baseName)
1197 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
1198 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
1201 << QLatin1String("d.dll") // try a qmake-style debug build first
1203 << QLatin1String(".dll"));
1206 # if defined(Q_OS_DARWIN)
1208 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
1211 << QLatin1String("_debug.dylib") // try a qmake-style debug build first
1212 << QLatin1String(".dylib")
1214 << QLatin1String(".dylib")
1215 << QLatin1String("_debug.dylib") // try a qmake-style debug build after
1217 << QLatin1String(".so")
1218 << QLatin1String(".bundle"),
1219 QLatin1String("lib"));
1220 # else // Generic Unix
1221 QStringList validSuffixList;
1223 # if defined(Q_OS_HPUX)
1225 See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF":
1226 "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit),
1227 the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix."
1229 validSuffixList << QLatin1String(".sl");
1231 validSuffixList << QLatin1String(".so");
1233 # elif defined(Q_OS_AIX)
1234 validSuffixList << QLatin1String(".a") << QLatin1String(".so");
1235 # elif defined(Q_OS_UNIX)
1236 validSuffixList << QLatin1String(".so");
1239 // Examples of valid library names:
1242 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib"));
1251 QStringList QQmlImportDatabase::pluginPathList() const
1253 return filePluginPath;
1259 void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
1261 if (qmlImportTrace())
1262 qDebug().nospace() << "QQmlImportDatabase::setPluginPathList: " << paths;
1264 filePluginPath = paths;
1270 void QQmlImportDatabase::addPluginPath(const QString& path)
1272 if (qmlImportTrace())
1273 qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path;
1275 QUrl url = QUrl(path);
1276 if (url.isRelative() || url.scheme() == QLatin1String("file")
1277 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1278 QDir dir = QDir(path);
1279 filePluginPath.prepend(dir.canonicalPath());
1281 filePluginPath.prepend(path);
1288 void QQmlImportDatabase::addImportPath(const QString& path)
1290 if (qmlImportTrace())
1291 qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path;
1296 QUrl url = QUrl(path);
1299 if (url.isRelative() || url.scheme() == QLatin1String("file")
1300 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1301 QDir dir = QDir(path);
1302 cPath = dir.canonicalPath();
1305 cPath.replace(QLatin1Char('\\'), QLatin1Char('/'));
1308 if (!cPath.isEmpty()
1309 && !fileImportPath.contains(cPath))
1310 fileImportPath.prepend(cPath);
1316 QStringList QQmlImportDatabase::importPathList() const
1318 return fileImportPath;
1324 void QQmlImportDatabase::setImportPathList(const QStringList &paths)
1326 if (qmlImportTrace())
1327 qDebug().nospace() << "QQmlImportDatabase::setImportPathList: " << paths;
1329 fileImportPath = paths;
1335 bool QQmlImportDatabase::importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors)
1337 if (qmlImportTrace())
1338 qDebug().nospace() << "QQmlImportDatabase::importPlugin: " << uri << " from " << filePath;
1340 #ifndef QT_NO_LIBRARY
1341 QFileInfo fileInfo(filePath);
1342 const QString absoluteFilePath = fileInfo.absoluteFilePath();
1344 bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
1345 bool typesRegistered = qmlEnginePluginsWithRegisteredTypes()->contains(absoluteFilePath);
1347 if (typesRegistered) {
1348 Q_ASSERT_X(qmlEnginePluginsWithRegisteredTypes()->value(absoluteFilePath) == uri,
1349 "QQmlImportDatabase::importExtension",
1350 "Internal error: Plugin imported previously with different uri");
1353 if (!engineInitialized || !typesRegistered) {
1354 if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
1357 error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
1358 errors->prepend(error);
1362 QPluginLoader loader(absoluteFilePath);
1364 if (!loader.load()) {
1367 error.setDescription(loader.errorString());
1368 errors->prepend(error);
1373 QObject *instance = loader.instance();
1374 if (QQmlTypesExtensionInterface *iface = qobject_cast<QQmlExtensionInterface *>(instance)) {
1376 const QByteArray bytes = uri.toUtf8();
1377 const char *moduleId = bytes.constData();
1378 if (!typesRegistered) {
1380 // XXX thread this code should probably be protected with a mutex.
1381 qmlEnginePluginsWithRegisteredTypes()->insert(absoluteFilePath, uri);
1382 iface->registerTypes(moduleId);
1384 if (!engineInitialized) {
1385 // things on the engine (eg. adding new global objects) have to be done for every
1387 // XXX protect against double initialization
1388 initializedPlugins.insert(absoluteFilePath);
1390 QQmlExtensionInterface *eiface =
1391 qobject_cast<QQmlExtensionInterface *>(instance);
1393 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
1394 ep->typeLoader.initializeEngine(eiface, moduleId);
1400 error.setDescription(loader.errorString());
1401 errors->prepend(error);