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 <QtCore/qfileinfo.h>
47 #include <QtCore/qpluginloader.h>
48 #include <QtCore/qlibraryinfo.h>
49 #include <QtQml/qqmlextensioninterface.h>
50 #include <private/qqmlglobal_p.h>
51 #include <private/qqmltypenamecache_p.h>
52 #include <private/qqmlengine_p.h>
56 DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
57 DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
59 static bool greaterThan(const QString &s1, const QString &s2)
64 QString resolveLocalUrl(const QString &url, const QString &relative)
66 if (relative.contains(QLatin1Char(':'))) {
67 // contains a host name
68 return QUrl(url).resolved(QUrl(relative)).toString();
69 } else if (relative.isEmpty()) {
71 } else if (relative.at(0) == QLatin1Char('/') || !url.contains(QLatin1Char('/'))) {
74 if (relative == QLatin1String("."))
75 return url.left(url.lastIndexOf(QLatin1Char('/')) + 1);
76 else if (relative.startsWith(QLatin1String("./")))
77 return url.left(url.lastIndexOf(QLatin1Char('/')) + 1) + relative.mid(2);
78 return url.left(url.lastIndexOf(QLatin1Char('/')) + 1) + relative;
84 typedef QMap<QString, QString> StringStringMap;
85 Q_GLOBAL_STATIC(StringStringMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri
87 class QQmlImportedNamespace
96 QQmlDirComponents qmlDirComponents;
97 QQmlDirScripts qmlDirScripts;
102 bool find_helper(QQmlTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor,
103 QQmlType** type_return, QString* url_return,
104 QString *base = 0, bool *typeRecursionDetected = 0);
105 bool find(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QQmlType** type_return,
106 QString* url_return, QString *base = 0, QList<QQmlError> *errors = 0);
109 class QQmlImportsPrivate {
111 QQmlImportsPrivate(QQmlTypeLoader *loader);
112 ~QQmlImportsPrivate();
114 bool importExtension(const QString &absoluteFilePath, const QString &uri,
115 QQmlImportDatabase *database, QQmlDirComponents* components,
116 QQmlDirScripts *scripts,
117 QList<QQmlError> *errors);
119 QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database);
120 bool add(const QQmlDirComponents &qmldircomponentsnetwork,
121 const QString& uri_arg, const QString& prefix,
122 int vmaj, int vmin, QQmlScript::Import::Type importType,
123 QQmlImportDatabase *database, QList<QQmlError> *errors);
124 bool find(const QString& type, int *vmajor, int *vminor,
125 QQmlType** type_return, QString* url_return, QList<QQmlError> *errors);
127 QQmlImportedNamespace *findNamespace(const QString& type);
133 QSet<QString> qmlDirFilesForWhichPluginsHaveBeenLoaded;
134 QQmlImportedNamespace unqualifiedset;
135 QHash<QString,QQmlImportedNamespace* > set;
136 QQmlTypeLoader *typeLoader;
141 \brief The QQmlImports class encapsulates one QML document's import statements.
144 QQmlImports::QQmlImports(const QQmlImports ©)
151 QQmlImports::operator =(const QQmlImports ©)
160 QQmlImports::QQmlImports(QQmlTypeLoader *typeLoader)
161 : d(new QQmlImportsPrivate(typeLoader))
165 QQmlImports::~QQmlImports()
172 Sets the base URL to be used for all relative file imports added.
174 void QQmlImports::setBaseUrl(const QUrl& url, const QString &urlString)
178 if (urlString.isEmpty()) {
179 d->base = url.toString();
181 //Q_ASSERT(url.toString() == urlString);
187 Returns the base URL to be used for all relative file imports added.
189 QUrl QQmlImports::baseUrl() const
194 void QQmlImports::populateCache(QQmlTypeNameCache *cache, QQmlEngine *engine) const
196 const QQmlImportedNamespace &set = d->unqualifiedset;
198 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
199 const QQmlImportedNamespace::Data &data = set.imports.at(ii);
200 QQmlTypeModule *module = QQmlMetaType::typeModule(data.uri, data.majversion);
202 cache->m_anonymousImports.append(QQmlTypeModuleVersion(module, data.minversion));
205 for (QHash<QString,QQmlImportedNamespace* >::ConstIterator iter = d->set.begin();
206 iter != d->set.end();
209 const QQmlImportedNamespace &set = *iter.value();
210 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
211 const QQmlImportedNamespace::Data &data = set.imports.at(ii);
212 QQmlTypeModule *module = QQmlMetaType::typeModule(data.uri, data.majversion);
214 QQmlTypeNameCache::Import &import = cache->m_namedImports[iter.key()];
215 import.modules.append(QQmlTypeModuleVersion(module, data.minversion));
218 QQmlMetaType::ModuleApi moduleApi = QQmlMetaType::moduleApi(data.uri, data.majversion, data.minversion);
219 if (moduleApi.script || moduleApi.qobject) {
220 QQmlTypeNameCache::Import &import = cache->m_namedImports[iter.key()];
221 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
222 import.moduleApi = ep->moduleApiInstance(moduleApi);
228 QList<QQmlImports::ScriptReference> QQmlImports::resolvedScripts() const
230 QList<QQmlImports::ScriptReference> scripts;
232 const QQmlImportedNamespace &set = d->unqualifiedset;
234 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
235 const QQmlImportedNamespace::Data &data = set.imports.at(ii);
237 foreach (const QQmlDirParser::Script &script, data.qmlDirScripts) {
239 ref.nameSpace = script.nameSpace;
240 ref.location = QUrl(data.url).resolved(QUrl(script.fileName));
245 for (QHash<QString,QQmlImportedNamespace* >::ConstIterator iter = d->set.constBegin();
246 iter != d->set.constEnd();
248 const QQmlImportedNamespace &set = *iter.value();
250 for (int ii = set.imports.count() - 1; ii >= 0; --ii) {
251 const QQmlImportedNamespace::Data &data = set.imports.at(ii);
253 foreach (const QQmlDirParser::Script &script, data.qmlDirScripts) {
255 ref.nameSpace = script.nameSpace;
256 ref.qualifier = iter.key();
257 ref.location = QUrl(data.url).resolved(QUrl(script.fileName));
269 The given (namespace qualified) \a type is resolved to either
271 \li a QQmlImportedNamespace stored at \a ns_return,
272 \li a QQmlType stored at \a type_return, or
273 \li a component located at \a url_return.
276 If any return pointer is 0, the corresponding search is not done.
280 bool QQmlImports::resolveType(const QString& type,
281 QQmlType** type_return, QString* url_return, int *vmaj, int *vmin,
282 QQmlImportedNamespace** ns_return, QList<QQmlError> *errors) const
284 QQmlImportedNamespace* ns = d->findNamespace(type);
290 if (type_return || url_return) {
291 if (d->find(type,vmaj,vmin,type_return,url_return, errors)) {
292 if (qmlImportTrace()) {
293 if (type_return && *type_return && url_return && !url_return->isEmpty())
294 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: "
295 << type << " => " << (*type_return)->typeName() << " " << *url_return;
296 if (type_return && *type_return)
297 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: "
298 << type << " => " << (*type_return)->typeName();
299 if (url_return && !url_return->isEmpty())
300 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: "
301 << type << " => " << *url_return;
312 Searching \e only in the namespace \a ns (previously returned in a call to
313 resolveType(), \a type is found and returned to either
314 a QQmlType stored at \a type_return, or
315 a component located at \a url_return.
317 If either return pointer is 0, the corresponding search is not done.
319 bool QQmlImports::resolveType(QQmlImportedNamespace* ns, const QString& type,
320 QQmlType** type_return, QString* url_return,
321 int *vmaj, int *vmin) const
323 return ns->find(d->typeLoader,type,vmaj,vmin,type_return,url_return);
326 bool QQmlImportedNamespace::find_helper(QQmlTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor,
327 QQmlType** type_return, QString* url_return,
328 QString *base, bool *typeRecursionDetected)
330 int vmaj = data.majversion;
331 int vmin = data.minversion;
333 if (vmaj >= 0 && vmin >= 0) {
334 QString qt = data.uri + QLatin1Char('/') + type;
335 QQmlType *t = QQmlMetaType::qmlType(qt,vmaj,vmin);
337 if (vmajor) *vmajor = vmaj;
338 if (vminor) *vminor = vmin;
345 const QQmlDirComponents &qmldircomponents = data.qmlDirComponents;
346 bool typeWasDeclaredInQmldir = false;
347 if (!qmldircomponents.isEmpty()) {
348 foreach (const QQmlDirParser::Component &c, qmldircomponents) {
349 if (c.typeName == type) {
350 typeWasDeclaredInQmldir = true;
351 // importing version -1 means import ALL versions
352 if ((vmaj == -1) || (c.majorVersion == vmaj && vmin >= c.minorVersion)) {
353 QString url(data.url + type + QLatin1String(".qml"));
354 QString candidate = resolveLocalUrl(url, c.fileName);
355 if (c.internal && base) {
356 if (resolveLocalUrl(*base, c.fileName) != candidate)
357 continue; // failed attempt to access an internal type
359 if (base && *base == candidate) {
360 if (typeRecursionDetected)
361 *typeRecursionDetected = true;
362 continue; // no recursion
365 *url_return = candidate;
372 if (!typeWasDeclaredInQmldir && !data.isLibrary) {
373 // XXX search non-files too! (eg. zip files, see QT-524)
374 QString url(data.url + type + QLatin1String(".qml"));
375 QString file = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
376 if (!typeLoader->absoluteFilePath(file).isEmpty()) {
377 if (base && *base == url) { // no recursion
378 if (typeRecursionDetected)
379 *typeRecursionDetected = true;
390 QQmlImportsPrivate::QQmlImportsPrivate(QQmlTypeLoader *loader)
391 : ref(1), typeLoader(loader)
395 QQmlImportsPrivate::~QQmlImportsPrivate()
397 foreach (QQmlImportedNamespace* s, set.values())
401 bool QQmlImportsPrivate::importExtension(const QString &absoluteFilePath, const QString &uri,
402 QQmlImportDatabase *database,
403 QQmlDirComponents* components,
404 QQmlDirScripts* scripts,
405 QList<QQmlError> *errors)
407 const QQmlDirParser *qmldirParser = typeLoader->qmlDirParser(absoluteFilePath);
408 if (qmldirParser->hasError()) {
410 const QList<QQmlError> qmldirErrors = qmldirParser->errors(uri);
411 for (int i = 0; i < qmldirErrors.size(); ++i)
412 errors->prepend(qmldirErrors.at(i));
417 if (qmlImportTrace())
418 qDebug().nospace() << "QQmlImports(" << qPrintable(base) << "::importExtension: "
419 << "loaded " << absoluteFilePath;
421 if (! qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(absoluteFilePath)) {
422 qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(absoluteFilePath);
424 QString qmldirPath = absoluteFilePath;
425 int slash = absoluteFilePath.lastIndexOf(QLatin1Char('/'));
427 qmldirPath.truncate(slash);
428 foreach (const QQmlDirParser::Plugin &plugin, qmldirParser->plugins()) {
430 QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name);
431 if (!resolvedFilePath.isEmpty()) {
432 if (!database->importPlugin(resolvedFilePath, uri, errors)) {
434 // XXX TODO: should we leave the import plugin error alone?
435 // Here, we pop it off the top and coalesce it into this error's message.
436 // The reason is that the lower level may add url and line/column numbering information.
437 QQmlError poppedError = errors->takeFirst();
439 error.setDescription(QQmlImportDatabase::tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(poppedError.description()));
440 error.setUrl(QUrl::fromLocalFile(absoluteFilePath));
441 errors->prepend(error);
448 error.setDescription(QQmlImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name));
449 error.setUrl(QUrl::fromLocalFile(absoluteFilePath));
450 errors->prepend(error);
458 *components = qmldirParser->components();
460 *scripts = qmldirParser->scripts();
465 QString QQmlImportsPrivate::resolvedUri(const QString &dir_arg, QQmlImportDatabase *database)
467 QString dir = dir_arg;
468 if (dir.endsWith(QLatin1Char('/')) || dir.endsWith(QLatin1Char('\\')))
471 QStringList paths = database->fileImportPath;
472 qSort(paths.begin(), paths.end(), greaterThan); // Ensure subdirs preceed their parents.
474 QString stableRelativePath = dir;
475 foreach(const QString &path, paths) {
476 if (dir.startsWith(path)) {
477 stableRelativePath = dir.mid(path.length()+1);
482 stableRelativePath.replace(QLatin1Char('\\'), QLatin1Char('/'));
484 // remove optional versioning in dot notation from uri
485 int lastSlash = stableRelativePath.lastIndexOf(QLatin1Char('/'));
486 if (lastSlash >= 0) {
487 int versionDot = stableRelativePath.indexOf(QLatin1Char('.'), lastSlash);
489 stableRelativePath = stableRelativePath.left(versionDot);
492 stableRelativePath.replace(QLatin1Char('/'), QLatin1Char('.'));
493 return stableRelativePath;
496 bool QQmlImportsPrivate::add(const QQmlDirComponents &qmldircomponentsnetwork,
497 const QString& uri_arg, const QString& prefix, int vmaj, int vmin,
498 QQmlScript::Import::Type importType,
499 QQmlImportDatabase *database, QList<QQmlError> *errors)
501 static QLatin1String Slash_qmldir("/qmldir");
502 static QLatin1Char Slash('/');
504 QQmlDirComponents qmldircomponents = qmldircomponentsnetwork;
505 QQmlDirScripts qmldirscripts;
506 QString uri = uri_arg;
507 QQmlImportedNamespace *s;
508 if (prefix.isEmpty()) {
511 s = set.value(prefix);
513 set.insert(prefix,(s=new QQmlImportedNamespace));
516 bool versionFound = false;
517 if (importType == QQmlScript::Import::Library) {
519 Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries
521 url.replace(QLatin1Char('.'), Slash);
526 // step 1: search for extension with fully encoded version number
527 foreach (const QString &p, database->fileImportPath) {
530 QFileInfo fi(dir+QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin)+QLatin1String("/qmldir"));
531 const QString absoluteFilePath = fi.absoluteFilePath();
536 const QString absolutePath = fi.absolutePath();
537 if (absolutePath.at(0) == QLatin1Char(':'))
538 url = QLatin1String("qrc://") + absolutePath.mid(1);
540 url = QUrl::fromLocalFile(fi.absolutePath()).toString();
541 uri = resolvedUri(dir, database);
542 if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
548 // TODO: Should this search be omitted if found == true?
550 // step 2: search for extension with encoded version major
551 foreach (const QString &p, database->fileImportPath) {
554 QFileInfo fi(dir+QString(QLatin1String(".%1")).arg(vmaj)+QLatin1String("/qmldir"));
555 const QString absoluteFilePath = fi.absoluteFilePath();
560 const QString absolutePath = fi.absolutePath();
561 if (absolutePath.at(0) == QLatin1Char(':'))
562 url = QLatin1String("qrc://") + absolutePath.mid(1);
564 url = QUrl::fromLocalFile(fi.absolutePath()).toString();
565 uri = resolvedUri(dir, database);
566 if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
573 // step 3: search for extension without version number
575 foreach (const QString &p, database->fileImportPath) {
577 qmldir = dir+Slash_qmldir;
579 QString absoluteFilePath = typeLoader->absoluteFilePath(qmldir);
580 if (!absoluteFilePath.isEmpty()) {
582 QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(Slash)+1);
583 if (absolutePath.at(0) == QLatin1Char(':'))
584 url = QLatin1String("qrc://") + absolutePath.mid(1);
586 url = QUrl::fromLocalFile(absolutePath).toString();
587 uri = resolvedUri(dir, database);
588 if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors))
595 if (QQmlMetaType::isModule(uri, vmaj, vmin))
598 if (!versionFound && qmldircomponents.isEmpty() && qmldirscripts.isEmpty()) {
600 QQmlError error; // we don't set the url or line or column as these will be set by the loader.
601 if (QQmlMetaType::isAnyModule(uri))
602 error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin));
604 error.setDescription(QQmlImportDatabase::tr("module \"%1\" is not installed").arg(uri_arg));
605 errors->prepend(error);
610 if (importType == QQmlScript::Import::File && qmldircomponents.isEmpty()) {
611 QString importUrl = resolveLocalUrl(base, uri + Slash_qmldir);
612 QString localFileOrQrc = QQmlEnginePrivate::urlToLocalFileOrQrc(importUrl);
613 if (!localFileOrQrc.isEmpty()) {
614 QString dir = QQmlEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
615 if (!typeLoader->directoryExists(dir)) {
617 QQmlError error; // we don't set the line or column as these will be set by the loader.
618 error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri_arg));
619 error.setUrl(QUrl(importUrl));
620 errors->prepend(error);
622 return false; // local import dirs must exist
624 uri = resolvedUri(dir, database);
625 if (uri.endsWith(Slash))
627 if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) {
628 if (!importExtension(localFileOrQrc,uri,database,&qmldircomponents,&qmldirscripts,errors))
632 if (prefix.isEmpty()) {
633 // directory must at least exist for valid import
634 QString localFileOrQrc = QQmlEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri));
635 if (!typeLoader->directoryExists(localFileOrQrc)) {
637 QQmlError error; // we don't set the line or column as these will be set by the loader.
638 if (localFileOrQrc.isEmpty())
639 error.setDescription(QQmlImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(uri));
641 error.setDescription(QQmlImportDatabase::tr("\"%1\": no such directory").arg(uri));
642 error.setUrl(QUrl(importUrl));
643 errors->prepend(error);
651 url = resolveLocalUrl(base, url);
654 if (!versionFound && (vmaj > -1) && (vmin > -1) && !qmldircomponents.isEmpty()) {
655 int lowest_min = INT_MAX;
656 int highest_min = INT_MIN;
658 QList<QQmlDirParser::Component>::const_iterator cend = qmldircomponents.constEnd();
659 for (QList<QQmlDirParser::Component>::const_iterator cit = qmldircomponents.constBegin(); cit != cend; ++cit) {
660 if (cit->majorVersion == vmaj) {
661 lowest_min = qMin(lowest_min, cit->minorVersion);
662 highest_min = qMax(highest_min, cit->minorVersion);
666 if (lowest_min > vmin || highest_min < vmin) {
668 QQmlError error; // we don't set the url or line or column information, as these will be set by the loader.
669 error.setDescription(QQmlImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin));
670 errors->prepend(error);
676 if (!url.endsWith(Slash))
679 QMap<QString, QQmlDirParser::Script> scripts;
681 if (!qmldirscripts.isEmpty()) {
682 // Verify that we haven't imported these scripts already
683 QList<QQmlImportedNamespace::Data>::const_iterator end = s->imports.constEnd();
684 for (QList<QQmlImportedNamespace::Data>::const_iterator it = s->imports.constBegin(); it != end; ++it) {
685 if (it->uri == uri) {
687 error.setDescription(QQmlImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url));
688 errors->prepend(error);
693 QList<QQmlDirParser::Script>::const_iterator send = qmldirscripts.constEnd();
694 for (QList<QQmlDirParser::Script>::const_iterator sit = qmldirscripts.constBegin(); sit != send; ++sit) {
695 // Only include scripts that match our requested version
696 if (((vmaj == -1) || (sit->majorVersion == vmaj)) &&
697 ((vmin == -1) || (sit->minorVersion <= vmin))) {
699 // Load the highest version that matches
700 QMap<QString, QQmlDirParser::Script>::iterator it = scripts.find(sit->nameSpace);
701 if (it == scripts.end() || (it->minorVersion < sit->minorVersion)) {
702 scripts.insert(sit->nameSpace, *sit);
708 QQmlImportedNamespace::Data data;
711 data.majversion = vmaj;
712 data.minversion = vmin;
713 data.isLibrary = importType == QQmlScript::Import::Library;
714 data.qmlDirComponents = qmldircomponents;
715 data.qmlDirScripts = scripts.values();
717 s->imports.prepend(data);
722 bool QQmlImportsPrivate::find(const QString& type, int *vmajor, int *vminor, QQmlType** type_return,
723 QString* url_return, QList<QQmlError> *errors)
725 QQmlImportedNamespace *s = 0;
726 int slash = type.indexOf(QLatin1Char('/'));
728 QString namespaceName = type.left(slash);
729 s = set.value(namespaceName);
733 error.setDescription(QQmlImportDatabase::tr("- %1 is not a namespace").arg(namespaceName));
734 errors->prepend(error);
738 int nslash = type.indexOf(QLatin1Char('/'),slash+1);
742 error.setDescription(QQmlImportDatabase::tr("- nested namespaces not allowed"));
743 errors->prepend(error);
750 QString unqualifiedtype = slash < 0 ? type : type.mid(slash+1); // common-case opt (QString::mid works fine, but slower)
752 if (s->find(typeLoader,unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errors))
754 if (s->imports.count() == 1 && !s->imports.at(0).isLibrary && url_return && s != &unqualifiedset) {
755 // qualified, and only 1 url
756 *url_return = resolveLocalUrl(s->imports.at(0).url, unqualifiedtype + QLatin1String(".qml"));
764 QQmlImportedNamespace *QQmlImportsPrivate::findNamespace(const QString& type)
766 return set.value(type);
769 bool QQmlImportedNamespace::find(QQmlTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QQmlType** type_return,
770 QString* url_return, QString *base, QList<QQmlError> *errors)
772 bool typeRecursionDetected = false;
773 for (int i=0; i<imports.count(); ++i) {
774 if (find_helper(typeLoader, imports.at(i), type, vmajor, vminor, type_return, url_return, base, &typeRecursionDetected)) {
775 if (qmlCheckTypes()) {
776 // check for type clashes
777 for (int j = i+1; j<imports.count(); ++j) {
778 if (find_helper(typeLoader, imports.at(j), type, vmajor, vminor, 0, 0, base)) {
780 QString u1 = imports.at(i).url;
781 QString u2 = imports.at(j).url;
784 int slash = b.lastIndexOf(QLatin1Char('/'));
787 QString l = b.left(slash);
788 if (u1.startsWith(b))
789 u1 = u1.mid(b.count());
791 u1 = QQmlImportDatabase::tr("local directory");
792 if (u2.startsWith(b))
793 u2 = u2.mid(b.count());
795 u2 = QQmlImportDatabase::tr("local directory");
801 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2));
803 error.setDescription(QQmlImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
805 .arg(imports.at(i).majversion).arg(imports.at(i).minversion)
806 .arg(imports.at(j).majversion).arg(imports.at(j).minversion));
808 errors->prepend(error);
819 if (typeRecursionDetected)
820 error.setDescription(QQmlImportDatabase::tr("is instantiated recursively"));
822 error.setDescription(QQmlImportDatabase::tr("is not a type"));
823 errors->prepend(error);
829 \class QQmlImportDatabase
830 \brief The QQmlImportDatabase class manages the QML imports for a QQmlEngine.
833 QQmlImportDatabase::QQmlImportDatabase(QQmlEngine *e)
836 filePluginPath << QLatin1String(".");
838 // Search order is applicationDirPath(), $QML_IMPORT_PATH, QLibraryInfo::ImportsPath
840 #ifndef QT_NO_SETTINGS
841 QString installImportsPath = QLibraryInfo::location(QLibraryInfo::ImportsPath);
842 addImportPath(installImportsPath);
843 #endif // QT_NO_SETTINGS
846 QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");
847 if (!envImportPath.isEmpty()) {
848 #if defined(Q_OS_WIN)
849 QLatin1Char pathSep(';');
851 QLatin1Char pathSep(':');
853 QStringList paths = QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts);
854 for (int ii = paths.count() - 1; ii >= 0; --ii)
855 addImportPath(paths.at(ii));
858 addImportPath(QCoreApplication::applicationDirPath());
861 QQmlImportDatabase::~QQmlImportDatabase()
868 Adds information to \a imports such that subsequent calls to resolveType()
869 will resolve types qualified by \a prefix by considering types found at the given \a uri.
871 The uri is either a directory (if importType is FileImport), or a URI resolved using paths
872 added via addImportPath() (if importType is LibraryImport).
874 The \a prefix may be empty, in which case the import location is considered for
877 The base URL must already have been set with Import::setBaseUrl().
879 bool QQmlImports::addImport(QQmlImportDatabase *importDb,
880 const QString& uri, const QString& prefix, int vmaj, int vmin,
881 QQmlScript::Import::Type importType,
882 const QQmlDirComponents &qmldircomponentsnetwork,
883 QList<QQmlError> *errors)
885 if (qmlImportTrace())
886 qDebug().nospace() << "QQmlImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: "
887 << uri << " " << vmaj << '.' << vmin << " "
888 << (importType==QQmlScript::Import::Library? "Library" : "File")
891 return d->add(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, importDb, errors);
897 Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
898 The \a prefix must contain the dot.
900 \a qmldirPath is the location of the qmldir file.
902 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
903 const QString &qmldirPath, const QString &qmldirPluginPath,
904 const QString &baseName, const QStringList &suffixes,
905 const QString &prefix)
907 QStringList searchPaths = filePluginPath;
908 bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
909 if (!qmldirPluginPathIsRelative)
910 searchPaths.prepend(qmldirPluginPath);
912 foreach (const QString &pluginPath, searchPaths) {
914 QString resolvedPath;
915 if (pluginPath == QLatin1String(".")) {
916 if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String("."))
917 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + qmldirPluginPath);
919 resolvedPath = qmldirPath;
921 if (QDir::isRelativePath(pluginPath))
922 resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + pluginPath);
924 resolvedPath = pluginPath;
927 // hack for resources, should probably go away
928 if (resolvedPath.startsWith(QLatin1Char(':')))
929 resolvedPath = QCoreApplication::applicationDirPath();
931 if (!resolvedPath.endsWith(QLatin1Char('/')))
932 resolvedPath += QLatin1Char('/');
934 foreach (const QString &suffix, suffixes) {
935 QString pluginFileName = prefix;
937 pluginFileName += baseName;
938 pluginFileName += suffix;
940 QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + pluginFileName);
941 if (!absolutePath.isEmpty())
946 if (qmlImportTrace())
947 qDebug() << "QQmlImportDatabase::resolvePlugin: Could not resolve plugin" << baseName
948 << "in" << qmldirPath;
956 Returns the result of the merge of \a baseName with \a dir and the platform suffix.
959 \header \li Platform \li Valid suffixes
960 \row \li Windows \li \c .dll
961 \row \li Unix/Linux \li \c .so
962 \row \li AIX \li \c .a
963 \row \li HP-UX \li \c .sl, \c .so (HP-UXi)
964 \row \li Mac OS X \li \c .dylib, \c .bundle, \c .so
967 Version number on unix are ignored.
969 QString QQmlImportDatabase::resolvePlugin(QQmlTypeLoader *typeLoader,
970 const QString &qmldirPath, const QString &qmldirPluginPath,
971 const QString &baseName)
973 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
974 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
977 << QLatin1String("d.dll") // try a qmake-style debug build first
979 << QLatin1String(".dll"));
982 # if defined(Q_OS_DARWIN)
984 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName,
987 << QLatin1String("_debug.dylib") // try a qmake-style debug build first
988 << QLatin1String(".dylib")
990 << QLatin1String(".dylib")
991 << QLatin1String("_debug.dylib") // try a qmake-style debug build after
993 << QLatin1String(".so")
994 << QLatin1String(".bundle"),
995 QLatin1String("lib"));
996 # else // Generic Unix
997 QStringList validSuffixList;
999 # if defined(Q_OS_HPUX)
1001 See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF":
1002 "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit),
1003 the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix."
1005 validSuffixList << QLatin1String(".sl");
1007 validSuffixList << QLatin1String(".so");
1009 # elif defined(Q_OS_AIX)
1010 validSuffixList << QLatin1String(".a") << QLatin1String(".so");
1011 # elif defined(Q_OS_UNIX)
1012 validSuffixList << QLatin1String(".so");
1015 // Examples of valid library names:
1018 return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib"));
1027 QStringList QQmlImportDatabase::pluginPathList() const
1029 return filePluginPath;
1035 void QQmlImportDatabase::setPluginPathList(const QStringList &paths)
1037 filePluginPath = paths;
1043 void QQmlImportDatabase::addPluginPath(const QString& path)
1045 if (qmlImportTrace())
1046 qDebug().nospace() << "QQmlImportDatabase::addPluginPath: " << path;
1048 QUrl url = QUrl(path);
1049 if (url.isRelative() || url.scheme() == QLatin1String("file")
1050 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1051 QDir dir = QDir(path);
1052 filePluginPath.prepend(dir.canonicalPath());
1054 filePluginPath.prepend(path);
1061 void QQmlImportDatabase::addImportPath(const QString& path)
1063 if (qmlImportTrace())
1064 qDebug().nospace() << "QQmlImportDatabase::addImportPath: " << path;
1069 QUrl url = QUrl(path);
1072 if (url.isRelative() || url.scheme() == QLatin1String("file")
1073 || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path
1074 QDir dir = QDir(path);
1075 cPath = dir.canonicalPath();
1078 cPath.replace(QLatin1Char('\\'), QLatin1Char('/'));
1081 if (!cPath.isEmpty()
1082 && !fileImportPath.contains(cPath))
1083 fileImportPath.prepend(cPath);
1089 QStringList QQmlImportDatabase::importPathList() const
1091 return fileImportPath;
1097 void QQmlImportDatabase::setImportPathList(const QStringList &paths)
1099 fileImportPath = paths;
1105 bool QQmlImportDatabase::importPlugin(const QString &filePath, const QString &uri, QList<QQmlError> *errors)
1107 if (qmlImportTrace())
1108 qDebug().nospace() << "QQmlImportDatabase::importPlugin: " << uri << " from " << filePath;
1110 #ifndef QT_NO_LIBRARY
1111 QFileInfo fileInfo(filePath);
1112 const QString absoluteFilePath = fileInfo.absoluteFilePath();
1114 bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
1115 bool typesRegistered = qmlEnginePluginsWithRegisteredTypes()->contains(absoluteFilePath);
1117 if (typesRegistered) {
1118 Q_ASSERT_X(qmlEnginePluginsWithRegisteredTypes()->value(absoluteFilePath) == uri,
1119 "QQmlImportDatabase::importExtension",
1120 "Internal error: Plugin imported previously with different uri");
1123 if (!engineInitialized || !typesRegistered) {
1124 if (!QQml_isFileCaseCorrect(absoluteFilePath)) {
1127 error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath));
1128 errors->prepend(error);
1132 QPluginLoader loader(absoluteFilePath);
1134 if (!loader.load()) {
1137 error.setDescription(loader.errorString());
1138 errors->prepend(error);
1143 QObject *instance = loader.instance();
1144 if (QQmlTypesExtensionInterface *iface = qobject_cast<QQmlExtensionInterface *>(instance)) {
1146 const QByteArray bytes = uri.toUtf8();
1147 const char *moduleId = bytes.constData();
1148 if (!typesRegistered) {
1150 // XXX thread this code should probably be protected with a mutex.
1151 qmlEnginePluginsWithRegisteredTypes()->insert(absoluteFilePath, uri);
1152 iface->registerTypes(moduleId);
1154 if (!engineInitialized) {
1155 // things on the engine (eg. adding new global objects) have to be done for every
1157 // XXX protect against double initialization
1158 initializedPlugins.insert(absoluteFilePath);
1160 QQmlExtensionInterface *eiface =
1161 qobject_cast<QQmlExtensionInterface *>(instance);
1163 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
1164 ep->typeLoader.initializeEngine(eiface, moduleId);
1170 error.setDescription(loader.errorString());
1171 errors->prepend(error);