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>
57 DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
58 DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
60 QString resolveLocalUrl(const QString &url, const QString &relative)
62 if (relative.contains(QLatin1Char(':'))) {
63 // contains a host name
64 return QUrl(url).resolved(QUrl(relative)).toString();
65 } else if (relative.isEmpty()) {
67 } else if (relative.at(0) == QLatin1Char('/') || !url.contains(QLatin1Char('/'))) {
70 QString base(url.left(url.lastIndexOf(QLatin1Char('/')) + 1));
72 if (relative == QLatin1String("."))
75 base.append(relative);
77 // Remove any relative directory elements in the path
78 const QLatin1Char dot('.');
79 const QLatin1Char slash('/');
81 int length = base.length();
83 while ((index = base.indexOf(QLatin1String("/."), index)) != -1) {
84 if ((length > (index + 2)) && (base.at(index + 2) == dot) &&
85 (length == (index + 3) || (base.at(index + 3) == slash))) {
86 // Either "/../" or "/..<END>"
87 int previous = base.lastIndexOf(slash, index - 1);
91 int removeLength = (index - previous) + 3;
92 base.remove(previous + 1, removeLength);
93 length -= removeLength;
95 } else if ((length == (index + 2)) || (base.at(index + 2) == slash)) {
96 // Either "/./" or "/.<END>"
97 base.remove(index, 2);
108 typedef QMap<QString, QString> StringStringMap;
109 Q_GLOBAL_STATIC(StringStringMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri
111 class QQmlImportNamespace
120 QQmlDirComponents qmlDirComponents;
121 QQmlDirScripts qmlDirScripts;
123 bool resolveType(QQmlTypeLoader *typeLoader, const QString& type,
124 int *vmajor, int *vminor,
125 QQmlType** type_return, QString* url_return,
126 QString *base = 0, bool *typeRecursionDetected = 0) const;
128 QList<Import> imports;
130 bool resolveType(QQmlTypeLoader *typeLoader, const QString& type,
131 int *vmajor, int *vminor,
132 QQmlType** type_return, QString* url_return,
133 QString *base = 0, QList<QQmlError> *errors = 0);
136 class QQmlImportsPrivate
139 QQmlImportsPrivate(QQmlTypeLoader *loader);
140 ~QQmlImportsPrivate();
142 bool addImport(const QQmlDirComponents &qmldircomponentsnetwork,
143 const QString &importedUri, const QString& prefix,
144 int vmaj, int vmin, QQmlScript::Import::Type importType,
145 bool isImplicitImport, QQmlImportDatabase *database,
146 QString *, QList<QQmlError> *errors);
148 bool resolveType(const QString& type, int *vmajor, int *vminor,
149 QQmlType** type_return, QString* url_return,
150 QList<QQmlError> *errors);
156 QSet<QString> qmlDirFilesForWhichPluginsHaveBeenLoaded;
157 QQmlImportNamespace unqualifiedset;
158 QHash<QString, QQmlImportNamespace *> set;
159 QQmlTypeLoader *typeLoader;
161 static inline QString tr(const char *str) {
162 return QQmlImportDatabase::tr(str);
166 static bool locateQmldir(const QString &uri, int vmaj, int vmin,
167 QQmlImportDatabase *database,
168 QString *outQmldirFilePath, QString *outUrl);
169 bool importExtension(const QString &absoluteFilePath, const QString &uri,
170 QQmlImportDatabase *database, QQmlDirComponents* components,
171 QQmlDirScripts *scripts,
172 QString *url, QList<QQmlError> *errors);
173 QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database);
178 \brief The QQmlImports class encapsulates one QML document's import statements.
181 QQmlImports::QQmlImports(const QQmlImports ©)
188 QQmlImports::operator =(const QQmlImports ©)
197 QQmlImports::QQmlImports(QQmlTypeLoader *typeLoader)
198 : d(new QQmlImportsPrivate(typeLoader))
202 QQmlImports::~QQmlImports()
209 Sets the base URL to be used for all relative file imports added.
211 void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString)
215 if (urlString.isEmpty()) {
216 d->base = url.toString();
218 //Q_ASSERT(url.toString() == urlString);
224 Returns the base URL to be used for all relative file imports added.
226 QUrl QQmlImports::baseUrl() const
231 void QQmlImports::populateCache(QQmlTypeNameCache *cache, QQmlEngine *engine) const
233 const QQmlImportNamespace &set = d->unqualifiedset;
235 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
236 const QQmlImportNamespace::Import &import = set.imports.at(ii);
237 QQmlTypeModule *module = QQmlMetaType::typeModule(import.uri, import.majversion);
239 cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, import.minversion));
242 for (QHash<QString, QQmlImportNamespace* >::ConstIterator iter = d->set.begin();
243 iter != d->set.end();
246 const QQmlImportNamespace &set = *iter.value();
247 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
248 const QQmlImportNamespace::Import &import = set.imports.at(ii);
249 QQmlTypeModule *module = QQmlMetaType::typeModule(import.uri, import.majversion);
251 QQmlTypeNameCache::Import &typeimport = cache->m_namedImports[iter.key()];
252 typeimport.modules.append(QQmlTypeModuleVersion(module, import.minversion));
255 QQmlMetaType::ModuleApi moduleApi = QQmlMetaType::moduleApi(import.uri, import.majversion,
257 if (moduleApi.script || moduleApi.qobject) {
258 QQmlTypeNameCache::Import &import = cache->m_namedImports[iter.key()];
259 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
260 import.moduleApi = ep->moduleApiInstance(moduleApi);
266 QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
268 QList<QQmlImports::ScriptReference> scripts;
270 const QQmlImportNamespace &set = d->unqualifiedset;
272 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
273 const QQmlImportNamespace::Import &import = set.imports.at(ii);
275 foreach (const QQmlDirParser::Script &script, import.qmlDirScripts) {
277 ref.nameSpace = script.nameSpace;
278 ref.location = QUrl(import.url).resolved(QUrl(script.fileName));
283 for (QHash<QString, QQmlImportNamespace* >::ConstIterator iter = d->set.constBegin();
284 iter != d->set.constEnd();
286 const QQmlImportNamespace &set = *iter.value();
288 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
289 const QQmlImportNamespace::Import &import = set.imports.at(ii);
291 foreach (const QQmlDirParser::Script &script, import.qmlDirScripts) {
293 ref.nameSpace = script.nameSpace;
294 ref.qualifier = iter.key();
295 ref.location = QUrl(import.url).resolved(QUrl(script.fileName));
307 The given (namespace qualified) \a type is resolved to either
309 \li a QQmlImportNamespace stored at \a ns_return,
310 \li a QQmlType stored at \a type_return, or
311 \li a component located at \a url_return.
314 If any return pointer is 0, the corresponding search is not done.
318 bool QQmlImports::resolveType(const QString& type,
319 QQmlType** type_return, QString* url_return, int *vmaj, int *vmin,
320 QQmlImportNamespace** ns_return, QList<QQmlError> *errors) const
322 QQmlImportNamespace* ns = d->set.value(type);
328 if (type_return || url_return) {
329 if (d->resolveType(type,vmaj,vmin,type_return,url_return, errors)) {
330 if (qmlImportTrace()) {
331 #define RESOLVE_TYPE_DEBUG qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) \
332 << ")" << "::resolveType: " << type << " => "
334 if (type_return && *type_return && url_return && !url_return->isEmpty())
335 RESOLVE_TYPE_DEBUG << (*type_return)->typeName() << " " << *url_return;
336 if (type_return && *type_return)
337 RESOLVE_TYPE_DEBUG << (*type_return)->typeName();
338 if (url_return && !url_return->isEmpty())
339 RESOLVE_TYPE_DEBUG << *url_return;
341 #undef RESOLVE_TYPE_DEBUG
352 Searching \e only in the namespace \a ns (previously returned in a call to
353 resolveType(), \a type is found and returned to either
354 a QQmlType stored at \a type_return, or
355 a component located at \a url_return.
357 If either return pointer is 0, the corresponding search is not done.
359 bool QQmlImports::resolveType(QQmlImportNamespace* ns, const QString& type,
360 QQmlType** type_return, QString* url_return,
361 int *vmaj, int *vmin) const
363 return ns->resolveType(d->typeLoader,type,vmaj,vmin,type_return,url_return);
366 bool QQmlImportNamespace::Import::resolveType(QQmlTypeLoader *typeLoader,
367 const QString& type, int *vmajor, int *vminor,
368 QQmlType** type_return, QString* url_return,
369 QString *base, bool *typeRecursionDetected) const
371 if (majversion >= 0 && minversion >= 0) {
372 QString qt = uri + QLatin1Char('/') + type;
373 QQmlType *t = QQmlMetaType::qmlType(qt, majversion, minversion);
375 if (vmajor) *vmajor = majversion;
376 if (vminor) *vminor = minversion;
383 bool typeWasDeclaredInQmldir = false;
384 if (!qmlDirComponents.isEmpty()) {
385 foreach (const QQmlDirParser::Component &c, qmlDirComponents) {
386 if (c.typeName == type) {
387 typeWasDeclaredInQmldir = true;
388 // importing version -1 means import ALL versions
389 if ((majversion == -1) || (c.majorVersion == majversion &&
390 minversion >= c.minorVersion)) {
391 QString candidate = resolveLocalUrl(QString(url + type + QLatin1String(".qml")),
393 if (c.internal && base) {
394 if (resolveLocalUrl(*base, c.fileName) != candidate)
395 continue; // failed attempt to access an internal type
397 if (base && *base == candidate) {
398 if (typeRecursionDetected)
399 *typeRecursionDetected = true;
400 continue; // no recursion
403 *url_return = candidate;
410 if (!typeWasDeclaredInQmldir && !isLibrary) {
411 QString qmlUrl(url + type + QLatin1String(".qml"));
415 if (QQmlFile::isBundle(qmlUrl)) {
416 exists = QQmlFile::bundleFileExists(qmlUrl, typeLoader->engine());
418 QString file = QQmlFile::urlToLocalFileOrQrc(qmlUrl);
419 exists = !typeLoader->absoluteFilePath(QQmlFile::urlToLocalFileOrQrc(qmlUrl)).isEmpty();
423 if (base && *base == qmlUrl) { // no recursion
424 if (typeRecursionDetected)
425 *typeRecursionDetected = true;
428 *url_return = qmlUrl;
437 bool QQmlImportsPrivate::resolveType(const QString& type, int *vmajor, int *vminor,
438 QQmlType** type_return, QString* url_return,
439 QList<QQmlError> *errors)
441 QQmlImportNamespace *s = 0;
442 int slash = type.indexOf(QLatin1Char('/'));
444 QString namespaceName = type.left(slash);
445 s = set.value(namespaceName);
449 error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName));
450 errors->prepend(error);
454 int nslash = type.indexOf(QLatin1Char('/'),slash+1);
458 error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed"));
459 errors->prepend(error);
466 QString unqualifiedtype = slash < 0 ? type : type.mid(slash+1); // common-case opt (QString::mid works fine, but slower)
468 if (s->resolveType(typeLoader,unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errors))
470 if (s->imports.count() == 1 && !s->imports.at(0).isLibrary && url_return && s != &unqualifiedset) {
471 // qualified, and only 1 url
472 *url_return = resolveLocalUrl(s->imports.at(0).url, unqualifiedtype + QLatin1String(".qml"));
480 bool QQmlImportNamespace::resolveType(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor,
481 int *vminor, QQmlType** type_return,
482 QString* url_return, QString *base, QList<QQmlError> *errors)
484 bool typeRecursionDetected = false;
485 for (int i=0; i<imports.count(); ++i) {
486 const Import &import = imports.at(i);
487 if (import.resolveType(typeLoader, type, vmajor, vminor, type_return, url_return,
488 base, &typeRecursionDetected)) {
489 if (qmlCheckTypes()) {
490 // check for type clashes
491 for (int j = i+1; j<imports.count(); ++j) {
492 const Import &import2 = imports.at(j);
493 if (import2.resolveType(typeLoader, type, vmajor, vminor, 0, 0, base)) {
495 QString u1 = imports.at(i).url;
496 QString u2 = imports.at(j).url;
499 int slash = b.lastIndexOf(QLatin1Char('/'));
502 QString l = b.left(slash);
503 if (u1.startsWith(b))
504 u1 = u1.mid(b.count());
506 u1 = QQmlImportDatabase::tr("local directory");
507 if (u2.startsWith(b))
508 u2 = u2.mid(b.count());
510 u2 = QQmlImportDatabase::tr("local directory");
516 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2));
518 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
520 .arg(imports.at(i).majversion).arg(imports.at(i).minversion)
521 .arg(imports.at(j).majversion).arg(imports.at(j).minversion));
523 errors->prepend(error);
534 if (typeRecursionDetected)
535 error.setDescription(QQmlImportDatabase::tr("is instantiated recursively"));
537 error.setDescription(QQmlImportDatabase::tr("is not a type"));
538 errors->prepend(error);
543 QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader)
544 : ref(1), typeLoader(loader) {
547 QQmlImportsPrivate::~QQmlImportsPrivate()
549 foreach (QQmlImportNamespace* s, set.values())
554 Import an extension defined by a qmldir file.
556 \a qmldirFilePath is either a raw file path, or a bundle url.
558 This call will modify the \a url parameter if importing the extension redirects to
561 bool QQmlImportsPrivate::importExtension(const QString &qmldirFilePath,
563 QQmlImportDatabase *database,
564 QQmlDirComponents* components,
565 QQmlDirScripts* scripts,
567 QList<QQmlError> *errors)
569 // As qmldirFilePath is always local, this method can always return synchronously
570 const QQmlDirParser *qmldirParser = typeLoader->qmlDirParser(qmldirFilePath, uri, url);
571 if (qmldirParser->hasError()) {
575 if (QQmlFile::isBundle(qmldirFilePath))
576 url = QUrl(qmldirFilePath);
578 url = QUrl::fromLocalFile(qmldirFilePath);
580 const QList<QQmlError> qmldirErrors = qmldirParser->errors(uri);
581 for (int i = 0; i < qmldirErrors.size(); ++i) {
582 QQmlError error = qmldirErrors.at(i);
584 errors->append(error);
590 if (qmlImportTrace())
591 qDebug().nospace() << "QQmlImports(" << qPrintable(base) << "::importExtension: "
592 << "loaded " << qmldirFilePath;
594 if (!qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(qmldirFilePath)) {
595 qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(qmldirFilePath);
597 QString qmldirPath = qmldirFilePath;
599 if (QQmlFile::isBundle(*url))
600 qmldirPath = QQmlFile::bundleFileName(*url, typeLoader->engine());
602 int slash = qmldirFilePath.lastIndexOf(QLatin1Char('/'));
604 qmldirPath.truncate(slash);
606 foreach (const QQmlDirParser::Plugin &plugin, qmldirParser->plugins()) {
607 QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath,
608 plugin.path, plugin.name);
609 if (!resolvedFilePath.isEmpty()) {
610 if (!database->importPlugin(resolvedFilePath, uri, errors)) {
612 // XXX TODO: should we leave the import plugin error alone?
613 // Here, we pop it off the top and coalesce it into this error's message.
614 // The reason is that the lower level may add url and line/column numbering information.
615 QQmlError poppedError = errors->takeFirst();
617 error.setDescription(tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(poppedError.description()));
618 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
619 errors->prepend(error);
626 error.setDescription(tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name));
627 error.setUrl(QUrl::fromLocalFile(qmldirFilePath));
628 errors->prepend(error);
636 *components = qmldirParser->components();
638 *scripts = qmldirParser->scripts();
643 QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database)
645 struct I { static bool greaterThan(const QString &s1, const QString &s2) {
649 QString dir = dir_arg;
650 if (dir.endsWith(QLatin1Char('/')) || dir.endsWith(QLatin1Char('\\')))
653 QStringList paths = database->fileImportPath;
654 qSort(paths.begin(), paths.end(), I::greaterThan); // Ensure subdirs preceed their parents.
656 QString stableRelativePath = dir;
657 foreach(const QString &path, paths) {
658 if (dir.startsWith(path)) {
659 stableRelativePath = dir.mid(path.length()+1);
664 stableRelativePath.replace(QLatin1Char('\\'), QLatin1Char('/'));
666 // remove optional versioning in dot notation from uri
667 int lastSlash = stableRelativePath.lastIndexOf(QLatin1Char('/'));
668 if (lastSlash >= 0) {
669 int versionDot = stableRelativePath.indexOf(QLatin1Char('.'), lastSlash);
671 stableRelativePath = stableRelativePath.left(versionDot);
674 stableRelativePath.replace(QLatin1Char('/'), QLatin1Char('.'));
676 return stableRelativePath;
680 Locates the qmldir file for \a uri version \a vmaj.vmin. Returns true if found,
681 and fills in outQmldirFilePath and outQmldirUrl appropriately. Otherwise returns
684 bool QQmlImportsPrivate::locateQmldir(const QString &uri, int vmaj, int vmin,
685 QQmlImportDatabase *database,
686 QString *outQmldirFilePath,
687 QString *outQmldirPathUrl)
689 Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries
693 QQmlImportDatabase::QmldirCache *cacheHead = 0;
695 QQmlImportDatabase::QmldirCache **cachePtr = database->qmldirCache.value(uri);
697 cacheHead = *cachePtr;
698 QQmlImportDatabase::QmldirCache *cache = cacheHead;
700 if (cache->versionMajor == vmaj && cache->versionMinor == vmin) {
701 *outQmldirFilePath = cache->qmldirFilePath;
702 *outQmldirPathUrl = cache->qmldirPathUrl;
703 return !cache->qmldirFilePath.isEmpty();
710 static QLatin1Char Slash('/');
711 static QLatin1String Slash_qmldir("/qmldir");
714 url.replace(QLatin1Char('.'), Slash);
716 // step 0: search for extension with fully encoded version number (eg. MyModule.3.2)
717 // step 1: search for extension with encoded version major (eg. MyModule.3)
718 // step 2: search for extension without version number (eg. MyModule)
719 for (int step = 0; step <= 2; ++step) {
720 foreach (const QString &p, database->fileImportPath) {
721 QString dir = p + Slash + url;
723 QString qmldirFile = dir;
724 if (step == 0) qmldirFile += QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin);
725 else if (step == 1) qmldirFile += QString(QLatin1String(".%1")).arg(vmaj);
726 qmldirFile += Slash_qmldir;
728 QQmlTypeLoader &typeLoader = QQmlEnginePrivate::get(database->engine)->typeLoader;
729 QString absoluteFilePath = typeLoader.absoluteFilePath(qmldirFile);
730 if (!absoluteFilePath.isEmpty()) {
731 QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(Slash)+1);
732 if (absolutePath.at(0) == QLatin1Char(':'))
733 url = QLatin1String("qrc://") + absolutePath.mid(1);
735 url = QUrl::fromLocalFile(absolutePath).toString();
737 QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
738 cache->versionMajor = vmaj;
739 cache->versionMinor = vmin;
740 cache->qmldirFilePath = absoluteFilePath;
741 cache->qmldirPathUrl = url;
742 cache->next = cacheHead;
743 database->qmldirCache.insert(uri, cache);
745 *outQmldirFilePath = absoluteFilePath;
746 *outQmldirPathUrl = url;
753 QQmlImportDatabase::QmldirCache *cache = new QQmlImportDatabase::QmldirCache;
754 cache->versionMajor = vmaj;
755 cache->versionMinor = vmin;
756 cache->next = cacheHead;
757 database->qmldirCache.insert(uri, cache);
762 bool QQmlImportsPrivate::addImport(const QQmlDirComponents &qmldircomponentsnetwork,
763 const QString& importedUri, const QString& prefix,
764 int vmaj, int vmin, QQmlScript::Import::Type importType,
765 bool isImplicitImport, QQmlImportDatabase *database,
766 QString *outUrl, QList<QQmlError> *errors)
769 Q_ASSERT(importType == QQmlScript::Import::File || importType == QQmlScript::Import::Library);
771 static QLatin1String String_qmldir("qmldir");
772 static QLatin1String Slash_qmldir("/qmldir");
773 static QLatin1Char Slash('/');
775 // The list of components defined by a qmldir file for this import.
776 QQmlDirComponents qmldircomponents;
777 // The list of scripts defined by a qmldir file for this import.
778 QQmlDirScripts qmldirscripts;
779 // The namespace that this import affects.
780 QQmlImportNamespace *importSet = 0;
781 // The uri for this import. For library imports this is the same as importedUri
782 // specified by the user, but it may be different in the case of file imports.
784 // The url for the path containing files for this import.
787 qmldircomponents = qmldircomponentsnetwork;
789 if (prefix.isEmpty()) {
790 importSet = &unqualifiedset;
792 importSet = set.value(prefix);
794 importSet = new QQmlImportNamespace;
795 set.insert(prefix, importSet);
799 if (importType == QQmlScript::Import::Library) {
800 Q_ASSERT(vmaj >= 0 && vmin >= 0);
802 QString qmldirFilePath;
804 if (locateQmldir(uri, vmaj, vmin, database, &qmldirFilePath, &url)) {
806 if (!importExtension(qmldirFilePath, uri, database, &qmldircomponents,
807 &qmldirscripts, &url, errors))
812 if (!QQmlMetaType::isModule(uri, vmaj, vmin)) {
814 if (qmldircomponents.isEmpty() && qmldirscripts.isEmpty()) {
816 if (QQmlMetaType::isAnyModule(uri))
817 error.setDescription(tr("module \"%1\" version %2.%3 is not installed").arg(importedUri).arg(vmaj).arg(vmin));
819 error.setDescription(tr("module \"%1\" is not installed").arg(importedUri));
820 errors->prepend(error);
823 int lowest_min = INT_MAX;
824 int highest_min = INT_MIN;
825 typedef QList<QQmlDirParser::Component>::const_iterator ConstIterator;
826 typedef QList<QQmlDirParser::Script>::const_iterator SConstIterator;
828 for (ConstIterator cit = qmldircomponents.constBegin();
829 cit != qmldircomponents.constEnd(); ++cit) {
830 if (cit->majorVersion == vmaj) {
831 lowest_min = qMin(lowest_min, cit->minorVersion);
832 highest_min = qMax(highest_min, cit->minorVersion);
836 for (SConstIterator cit = qmldirscripts.constBegin();
837 cit != qmldirscripts.constEnd(); ++cit) {
838 if (cit->majorVersion == vmaj) {
839 lowest_min = qMin(lowest_min, cit->minorVersion);
840 highest_min = qMax(highest_min, cit->minorVersion);
844 if (lowest_min > vmin || highest_min < vmin) {
846 error.setDescription(tr("module \"%1\" version %2.%3 is not installed").arg(importedUri).arg(vmaj).arg(vmin));
847 errors->prepend(error);
855 Q_ASSERT(importType == QQmlScript::Import::File);
857 if (qmldircomponents.isEmpty()) {
859 QString qmldirPath = uri;
860 if (uri.endsWith(Slash)) qmldirPath += String_qmldir;
861 else qmldirPath += Slash_qmldir;
862 QString qmldirUrl = resolveLocalUrl(base, qmldirPath);
864 if (QQmlFile::isBundle(qmldirUrl)) {
866 QString dir = resolveLocalUrl(base, uri);
867 Q_ASSERT(QQmlFile::isBundle(dir));
868 if (!QQmlFile::bundleDirectoryExists(dir, typeLoader->engine())) {
869 if (!isImplicitImport) {
871 error.setDescription(tr("\"%1\": no such directory").arg(importedUri));
872 error.setUrl(QUrl(qmldirUrl));
873 errors->prepend(error);
878 // Transforms the (possible relative) uri into our best guess relative to the
880 uri = resolvedUri(dir, database);
882 if (uri.endsWith(Slash))
884 if (QQmlFile::bundleFileExists(qmldirUrl, typeLoader->engine())) {
885 if (!importExtension(qmldirUrl, uri, database, &qmldircomponents,
886 &qmldirscripts, &url, errors))
890 } else if (QQmlFile::isLocalFile(qmldirUrl)) {
892 QString localFileOrQrc = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
893 Q_ASSERT(!localFileOrQrc.isEmpty());
895 QString dir = QQmlFile::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
896 if (!typeLoader->directoryExists(dir)) {
897 if (!isImplicitImport) {
899 error.setDescription(tr("\"%1\": no such directory").arg(importedUri));
900 error.setUrl(QUrl(qmldirUrl));
901 errors->prepend(error);
906 // Transforms the (possible relative) uri into our best guess relative to the
908 uri = resolvedUri(dir, database);
910 if (uri.endsWith(Slash))
912 if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) {
913 if (!importExtension(localFileOrQrc, uri, database, &qmldircomponents,
914 &qmldirscripts, &url, errors))
918 } else if (prefix.isEmpty()) {
920 if (!isImplicitImport) {
922 error.setDescription(tr("import \"%1\" has no qmldir and no namespace").arg(uri));
923 error.setUrl(QUrl(qmldirUrl));
924 errors->prepend(error);
932 url = resolveLocalUrl(base, importedUri);
933 if (!url.endsWith(Slash))
937 Q_ASSERT(url.isEmpty() || url.endsWith(Slash));
939 QMap<QString, QQmlDirParser::Script> scripts;
940 if (!qmldirscripts.isEmpty()) {
941 // Verify that we haven't imported these scripts already
942 for (QList<QQmlImportNamespace::Import>::const_iterator it = importSet->imports.constBegin();
943 it != importSet->imports.constEnd(); ++it) {
944 if (it->uri == uri) {
946 error.setDescription(tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url));
947 errors->prepend(error);
952 for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin();
953 sit != qmldirscripts.constEnd(); ++sit) {
954 // Only include scripts that match our requested version
955 if (((vmaj == -1) || (sit->majorVersion == vmaj)) &&
956 ((vmin == -1) || (sit->minorVersion <= vmin))) {
958 // Load the highest version that matches
959 QMap<QString, QQmlDirParser::Script>::iterator it = scripts.find(sit->nameSpace);
960 if (it == scripts.end() || (it->minorVersion < sit->minorVersion)) {
961 scripts.insert(sit->nameSpace, *sit);
967 QQmlImportNamespace::Import import;
970 import.majversion = vmaj;
971 import.minversion = vmin;
972 import.isLibrary = importType == QQmlScript::Import::Library;
973 import.qmlDirComponents = qmldircomponents;
974 import.qmlDirScripts = scripts.values();
976 importSet->imports.prepend(import);
978 if (outUrl) *outUrl = url;
986 Adds an implicit "." file import. This is equivalent to calling addImport(), but error
987 messages related to the path or qmldir file not existing are suppressed.
989 bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb,
990 const QQmlDirComponents &qmldircomponentsnetwork,
991 QList<QQmlError> *errors)
995 if (qmlImportTrace())
996 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString())
997 << ")::addImplicitImport";
1000 return d->addImport(qmldircomponentsnetwork, QLatin1String("."), QString(), -1, -1,
1001 QQmlScript::Import::File, true, importDb, 0, errors);
1007 Adds information to \a imports such that subsequent calls to resolveType()
1008 will resolve types qualified by \a prefix by considering types found at the given \a uri.
1010 The uri is either a directory (if importType is FileImport), or a URI resolved using paths
1011 added via addImportPath() (if importType is LibraryImport).
1013 The \a prefix may be empty, in which case the import location is considered for
1016 The base URL must already have been set with Import::setBaseUrl().
1018 Optionally, the url the import resolved to can be returned by providing the url parameter.
1019 Not all imports will result in an output url being generated, in which case the url will
1020 be set to an empty string.
1022 Returns true on success, and false on failure. In case of failure, the errors array will
1023 filled appropriately.
1025 bool QQmlImports::addImport(QQmlImportDatabase *importDb,
1026 const QString& uri, const QString& prefix, int vmaj, int vmin,
1027 QQmlScript::Import::Type importType,
1028 const QQmlDirComponents &qmldircomponentsnetwork,
1029 QString *url, QList<QQmlError> *errors)
1033 if (qmlImportTrace())
1034 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: "
1035 << uri << " " << vmaj << '.' << vmin << " "
1036 << (importType==QQmlScript::Import::Library? "Library" : "File")
1037 << " as " << prefix;
1039 return d->addImport(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, false,
1040 importDb, url, errors);
1044 \class QQmlImportDatabase
1045 \brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine.
1048 QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
1051 filePluginPath << QLatin1String(".");
1053 // Search order is applicationDirPath(), $QML_IMPORT_PATH, QLibraryInfo::ImportsPath
1055 #ifndef QT_NO_SETTINGS
1056 QString installImportsPath = QLibraryInfo::location(QLibraryInfo::ImportsPath);
1057 addImportPath(installImportsPath);
1058 #endif // QT_NO_SETTINGS
1061 QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");
1062 if (!envImportPath.isEmpty()) {
1063 #if defined(Q_OS_WIN)
1064 QLatin1Char pathSep(';');
1066 QLatin1Char pathSep(':');
1068 QStringList paths = QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts);
1069 for (int ii = paths.count() - 1; ii >= 0; --ii)
1070 addImportPath(paths.at(ii));
1073 addImportPath(QCoreApplication::applicationDirPath());
1076 QQmlImportDatabase::~QQmlImportDatabase()
1078 for (QStringHash<QmldirCache *>::ConstIterator iter = qmldirCache.begin();
1079 iter != qmldirCache.end(); ++iter) {
1081 QmldirCache *c = *iter;
1083 QmldirCache *n = c->next;
1093 Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
1094 The \a prefix must contain the dot.
1096 \a qmldirPath is the location of the qmldir file.
1098 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1099 const QString &qmldirPath,
1100 const QString &qmldirPluginPath,
1101 const QString &baseName, const QStringList &suffixes,
1102 const QString &prefix)
1104 QStringList searchPaths = filePluginPath;
1105 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
1106 if (!qmldirPluginPathIsRelative)
1107 searchPaths.prepend(qmldirPluginPath);
1109 foreach (const QString &pluginPath, searchPaths) {
1111 QString resolvedPath;
1112 if (pluginPath == QLatin1String(".")) {
1113 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String("."))
1114 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + qmldirPluginPath);
1116 resolvedPath = qmldirPath;
1118 if (QDir::isRelativePath(pluginPath))
1119 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + pluginPath);
1121 resolvedPath = pluginPath;
1124 // hack for resources, should probably go away
1125 if (resolvedPath.startsWith(QLatin1Char(':')))
1126 resolvedPath = QCoreApplication::applicationDirPath();
1128 if (!resolvedPath.endsWith(QLatin1Char('/')))
1129 resolvedPath += QLatin1Char('/');
1131 foreach (const QString &suffix, suffixes) {
1132 QString pluginFileName = prefix;
1134 pluginFileName += baseName;
1135 pluginFileName += suffix;
1137 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + pluginFileName);
1138 if (!absolutePath.isEmpty())
1139 return absolutePath;
1143 if (qmlImportTrace())
1144 qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName
1145 << "in" << qmldirPath;
1153 Returns the result of the merge of \a baseName with \a dir and the platform suffix.
1156 \header \li Platform \li Valid suffixes
1157 \row \li Windows \li \c .dll
1158 \row \li Unix/Linux \li \c .so
1159 \row \li AIX \li \c .a
1160 \row \li HP-UX \li \c .sl, \c .so (HP-UXi)
1161 \row \li Mac OS X \li \c .dylib, \c .bundle, \c .so
1164 Version number on unix are ignored.
1166 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
1167 const QString &qmldirPath, const QString &qmldirPluginPath,
1168 const QString &baseName)
1170 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
1171 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
1174 << QLatin1String("d.dll") // try a qmake-style debug build first
1176 << QLatin1String(".dll"));
1179 # if defined(Q_OS_DARWIN)
1181 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
1184 << QLatin1String("_debug.dylib") // try a qmake-style debug build first
1185 << QLatin1String(".dylib")
1187 << QLatin1String(".dylib")
1188 << QLatin1String("_debug.dylib") // try a qmake-style debug build after
1190 << QLatin1String(".so")
1191 << QLatin1String(".bundle"),
1192 QLatin1String("lib"));
1193 # else // Generic Unix
1194 QStringList validSuffixList;
1196 # if defined(Q_OS_HPUX)
1198 See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF":
1199 "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit),
1200 the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix."
1202 validSuffixList << QLatin1String(".sl");
1204 validSuffixList << QLatin1String(".so");
1206 # elif defined(Q_OS_AIX)
1207 validSuffixList << QLatin1String(".a") << QLatin1String(".so");
1208 # elif defined(Q_OS_UNIX)
1209 validSuffixList << QLatin1String(".so");
1212 // Examples of valid library names:
1215 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib"));
1224 QStringList QQmlImportDatabase::pluginPathList() const
1226 return filePluginPath;
1232 void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
1234 filePluginPath = paths;
1240 void QQmlImportDatabase::addPluginPath(const QString& path)
1242 if (qmlImportTrace())
1243 qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path;
1245 QUrl url = QUrl(path);
1246 if (url.isRelative() || url.scheme() == QLatin1String("file")
1247 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1248 QDir dir = QDir(path);
1249 filePluginPath.prepend(dir.canonicalPath());
1251 filePluginPath.prepend(path);
1258 void QQmlImportDatabase::addImportPath(const QString& path)
1260 if (qmlImportTrace())
1261 qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path;
1266 QUrl url = QUrl(path);
1269 if (url.isRelative() || url.scheme() == QLatin1String("file")
1270 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1271 QDir dir = QDir(path);
1272 cPath = dir.canonicalPath();
1275 cPath.replace(QLatin1Char('\\'), QLatin1Char('/'));
1278 if (!cPath.isEmpty()
1279 && !fileImportPath.contains(cPath))
1280 fileImportPath.prepend(cPath);
1286 QStringList QQmlImportDatabase::importPathList() const
1288 return fileImportPath;
1294 void QQmlImportDatabase::setImportPathList(const QStringList &paths)
1296 fileImportPath = paths;
1302 bool QQmlImportDatabase::importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors)
1304 if (qmlImportTrace())
1305 qDebug().nospace() << "QQmlImportDatabase::importPlugin: " << uri << " from " << filePath;
1307 #ifndef QT_NO_LIBRARY
1308 QFileInfo fileInfo(filePath);
1309 const QString absoluteFilePath = fileInfo.absoluteFilePath();
1311 bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
1312 bool typesRegistered = qmlEnginePluginsWithRegisteredTypes()->contains(absoluteFilePath);
1314 if (typesRegistered) {
1315 Q_ASSERT_X(qmlEnginePluginsWithRegisteredTypes()->value(absoluteFilePath) == uri,
1316 "QQmlImportDatabase::importExtension",
1317 "Internal error: Plugin imported previously with different uri");
1320 if (!engineInitialized || !typesRegistered) {
1321 if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
1324 error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
1325 errors->prepend(error);
1329 QPluginLoader loader(absoluteFilePath);
1331 if (!loader.load()) {
1334 error.setDescription(loader.errorString());
1335 errors->prepend(error);
1340 QObject *instance = loader.instance();
1341 if (QQmlTypesExtensionInterface *iface = qobject_cast<QQmlExtensionInterface *>(instance)) {
1343 const QByteArray bytes = uri.toUtf8();
1344 const char *moduleId = bytes.constData();
1345 if (!typesRegistered) {
1347 // XXX thread this code should probably be protected with a mutex.
1348 qmlEnginePluginsWithRegisteredTypes()->insert(absoluteFilePath, uri);
1349 iface->registerTypes(moduleId);
1351 if (!engineInitialized) {
1352 // things on the engine (eg. adding new global objects) have to be done for every
1354 // XXX protect against double initialization
1355 initializedPlugins.insert(absoluteFilePath);
1357 QQmlExtensionInterface *eiface =
1358 qobject_cast<QQmlExtensionInterface *>(instance);
1360 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
1361 ep->typeLoader.initializeEngine(eiface, moduleId);
1367 error.setDescription(loader.errorString());
1368 errors->prepend(error);