/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/ ** ** This file is part of the QtDeclarative module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qdeclarativeimport_p.h" #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE) DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES) static bool greaterThan(const QString &s1, const QString &s2) { return s1 > s2; } QString resolveLocalUrl(const QString &url, const QString &relative) { if (relative.contains(QLatin1Char(':'))) { // contains a host name return QUrl(url).resolved(QUrl(relative)).toString(); } else if (relative.isEmpty()) { return url; } else if (relative.at(0) == QLatin1Char('/') || !url.contains(QLatin1Char('/'))) { return relative; } else { if (relative == QLatin1String(".")) return url.left(url.lastIndexOf(QLatin1Char('/')) + 1); else if (relative.startsWith(QLatin1String("./"))) return url.left(url.lastIndexOf(QLatin1Char('/')) + 1) + relative.mid(2); return url.left(url.lastIndexOf(QLatin1Char('/')) + 1) + relative; } } typedef QMap StringStringMap; Q_GLOBAL_STATIC(StringStringMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri class QDeclarativeImportedNamespace { public: struct Data { QString uri; QString url; int majversion; int minversion; bool isLibrary; QDeclarativeDirComponents qmlDirComponents; QDeclarativeDirScripts qmlDirScripts; }; QList imports; bool find_helper(QDeclarativeTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor, QDeclarativeType** type_return, QString* url_return, QString *base = 0, bool *typeRecursionDetected = 0); bool find(QDeclarativeTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QDeclarativeType** type_return, QString* url_return, QString *base = 0, QList *errors = 0); }; class QDeclarativeImportsPrivate { public: QDeclarativeImportsPrivate(QDeclarativeTypeLoader *loader); ~QDeclarativeImportsPrivate(); bool importExtension(const QString &absoluteFilePath, const QString &uri, QDeclarativeImportDatabase *database, QDeclarativeDirComponents* components, QDeclarativeDirScripts *scripts, QList *errors); QString resolvedUri(const QString &dir_arg, QDeclarativeImportDatabase *database); bool add(const QDeclarativeDirComponents &qmldircomponentsnetwork, const QString& uri_arg, const QString& prefix, int vmaj, int vmin, QDeclarativeScript::Import::Type importType, QDeclarativeImportDatabase *database, QList *errors); bool find(const QString& type, int *vmajor, int *vminor, QDeclarativeType** type_return, QString* url_return, QList *errors); QDeclarativeImportedNamespace *findNamespace(const QString& type); QUrl baseUrl; QString base; int ref; QSet qmlDirFilesForWhichPluginsHaveBeenLoaded; QDeclarativeImportedNamespace unqualifiedset; QHash set; QDeclarativeTypeLoader *typeLoader; }; /*! \class QDeclarativeImports \brief The QDeclarativeImports class encapsulates one QML document's import statements. \internal */ QDeclarativeImports::QDeclarativeImports(const QDeclarativeImports ©) : d(copy.d) { ++d->ref; } QDeclarativeImports & QDeclarativeImports::operator =(const QDeclarativeImports ©) { ++copy.d->ref; if (--d->ref == 0) delete d; d = copy.d; return *this; } QDeclarativeImports::QDeclarativeImports(QDeclarativeTypeLoader *typeLoader) : d(new QDeclarativeImportsPrivate(typeLoader)) { } QDeclarativeImports::~QDeclarativeImports() { if (--d->ref == 0) delete d; } /*! Sets the base URL to be used for all relative file imports added. */ void QDeclarativeImports::setBaseUrl(const QUrl& url, const QString &urlString) { d->baseUrl = url; if (urlString.isEmpty()) { d->base = url.toString(); } else { //Q_ASSERT(url.toString() == urlString); d->base = urlString; } } /*! Returns the base URL to be used for all relative file imports added. */ QUrl QDeclarativeImports::baseUrl() const { return d->baseUrl; } void QDeclarativeImports::populateCache(QDeclarativeTypeNameCache *cache, QDeclarativeEngine *engine) const { const QDeclarativeImportedNamespace &set = d->unqualifiedset; for (int ii = set.imports.count() - 1; ii >= 0; --ii) { const QDeclarativeImportedNamespace::Data &data = set.imports.at(ii); QDeclarativeTypeModule *module = QDeclarativeMetaType::typeModule(data.uri, data.majversion); if (module) cache->m_anonymousImports.append(QDeclarativeTypeModuleVersion(module, data.minversion)); } for (QHash::ConstIterator iter = d->set.begin(); iter != d->set.end(); ++iter) { const QDeclarativeImportedNamespace &set = *iter.value(); for (int ii = set.imports.count() - 1; ii >= 0; --ii) { const QDeclarativeImportedNamespace::Data &data = set.imports.at(ii); QDeclarativeTypeModule *module = QDeclarativeMetaType::typeModule(data.uri, data.majversion); if (module) { QDeclarativeTypeNameCache::Import &import = cache->m_namedImports[iter.key()]; import.modules.append(QDeclarativeTypeModuleVersion(module, data.minversion)); } QDeclarativeMetaType::ModuleApi moduleApi = QDeclarativeMetaType::moduleApi(data.uri, data.majversion, data.minversion); if (moduleApi.script || moduleApi.qobject) { QDeclarativeTypeNameCache::Import &import = cache->m_namedImports[iter.key()]; QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine); import.moduleApi = ep->moduleApiInstance(moduleApi); } } } } QList QDeclarativeImports::resolvedScripts() const { QList scripts; const QDeclarativeImportedNamespace &set = d->unqualifiedset; for (int ii = set.imports.count() - 1; ii >= 0; --ii) { const QDeclarativeImportedNamespace::Data &data = set.imports.at(ii); foreach (const QDeclarativeDirParser::Script &script, data.qmlDirScripts) { ScriptReference ref; ref.nameSpace = script.nameSpace; ref.location = QUrl(data.url).resolved(QUrl(script.fileName)); scripts.append(ref); } } for (QHash::ConstIterator iter = d->set.constBegin(); iter != d->set.constEnd(); ++iter) { const QDeclarativeImportedNamespace &set = *iter.value(); for (int ii = set.imports.count() - 1; ii >= 0; --ii) { const QDeclarativeImportedNamespace::Data &data = set.imports.at(ii); foreach (const QDeclarativeDirParser::Script &script, data.qmlDirScripts) { ScriptReference ref; ref.nameSpace = script.nameSpace; ref.qualifier = iter.key(); ref.location = QUrl(data.url).resolved(QUrl(script.fileName)); scripts.append(ref); } } } return scripts; } /*! \internal The given (namespace qualified) \a type is resolved to either \list \o a QDeclarativeImportedNamespace stored at \a ns_return, \o a QDeclarativeType stored at \a type_return, or \o a component located at \a url_return. \endlist If any return pointer is 0, the corresponding search is not done. \sa addImport() */ bool QDeclarativeImports::resolveType(const QString& type, QDeclarativeType** type_return, QString* url_return, int *vmaj, int *vmin, QDeclarativeImportedNamespace** ns_return, QList *errors) const { QDeclarativeImportedNamespace* ns = d->findNamespace(type); if (ns) { if (ns_return) *ns_return = ns; return true; } if (type_return || url_return) { if (d->find(type,vmaj,vmin,type_return,url_return, errors)) { if (qmlImportTrace()) { if (type_return && *type_return && url_return && !url_return->isEmpty()) qDebug().nospace() << "QDeclarativeImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " << type << " => " << (*type_return)->typeName() << " " << *url_return; if (type_return && *type_return) qDebug().nospace() << "QDeclarativeImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " << type << " => " << (*type_return)->typeName(); if (url_return && !url_return->isEmpty()) qDebug().nospace() << "QDeclarativeImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: " << type << " => " << *url_return; } return true; } } return false; } /*! \internal Searching \e only in the namespace \a ns (previously returned in a call to resolveType(), \a type is found and returned to either a QDeclarativeType stored at \a type_return, or a component located at \a url_return. If either return pointer is 0, the corresponding search is not done. */ bool QDeclarativeImports::resolveType(QDeclarativeImportedNamespace* ns, const QString& type, QDeclarativeType** type_return, QString* url_return, int *vmaj, int *vmin) const { return ns->find(d->typeLoader,type,vmaj,vmin,type_return,url_return); } bool QDeclarativeImportedNamespace::find_helper(QDeclarativeTypeLoader *typeLoader, const Data &data, const QString& type, int *vmajor, int *vminor, QDeclarativeType** type_return, QString* url_return, QString *base, bool *typeRecursionDetected) { int vmaj = data.majversion; int vmin = data.minversion; if (vmaj >= 0 && vmin >= 0) { QString qt = data.uri + QLatin1Char('/') + type; QDeclarativeType *t = QDeclarativeMetaType::qmlType(qt,vmaj,vmin); if (t) { if (vmajor) *vmajor = vmaj; if (vminor) *vminor = vmin; if (type_return) *type_return = t; return true; } } const QDeclarativeDirComponents &qmldircomponents = data.qmlDirComponents; bool typeWasDeclaredInQmldir = false; if (!qmldircomponents.isEmpty()) { foreach (const QDeclarativeDirParser::Component &c, qmldircomponents) { if (c.typeName == type) { typeWasDeclaredInQmldir = true; // importing version -1 means import ALL versions if ((vmaj == -1) || (c.majorVersion == vmaj && vmin >= c.minorVersion)) { QString url(data.url + type + QLatin1String(".qml")); QString candidate = resolveLocalUrl(url, c.fileName); if (c.internal && base) { if (resolveLocalUrl(*base, c.fileName) != candidate) continue; // failed attempt to access an internal type } if (base && *base == candidate) { if (typeRecursionDetected) *typeRecursionDetected = true; continue; // no recursion } if (url_return) *url_return = candidate; return true; } } } } if (!typeWasDeclaredInQmldir && !data.isLibrary) { // XXX search non-files too! (eg. zip files, see QT-524) QString url(data.url + type + QLatin1String(".qml")); QString file = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url); if (!typeLoader->absoluteFilePath(file).isEmpty()) { if (base && *base == url) { // no recursion if (typeRecursionDetected) *typeRecursionDetected = true; } else { if (url_return) *url_return = url; return true; } } } return false; } QDeclarativeImportsPrivate::QDeclarativeImportsPrivate(QDeclarativeTypeLoader *loader) : ref(1), typeLoader(loader) { } QDeclarativeImportsPrivate::~QDeclarativeImportsPrivate() { foreach (QDeclarativeImportedNamespace* s, set.values()) delete s; } bool QDeclarativeImportsPrivate::importExtension(const QString &absoluteFilePath, const QString &uri, QDeclarativeImportDatabase *database, QDeclarativeDirComponents* components, QDeclarativeDirScripts* scripts, QList *errors) { const QDeclarativeDirParser *qmldirParser = typeLoader->qmlDirParser(absoluteFilePath); if (qmldirParser->hasError()) { if (errors) { const QList qmldirErrors = qmldirParser->errors(uri); for (int i = 0; i < qmldirErrors.size(); ++i) errors->prepend(qmldirErrors.at(i)); } return false; } if (qmlImportTrace()) qDebug().nospace() << "QDeclarativeImports(" << qPrintable(base) << "::importExtension: " << "loaded " << absoluteFilePath; if (! qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(absoluteFilePath)) { qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(absoluteFilePath); QString qmldirPath = absoluteFilePath; int slash = absoluteFilePath.lastIndexOf(QLatin1Char('/')); if (slash > 0) qmldirPath.truncate(slash); foreach (const QDeclarativeDirParser::Plugin &plugin, qmldirParser->plugins()) { QString resolvedFilePath = database->resolvePlugin(typeLoader, qmldirPath, plugin.path, plugin.name); if (!resolvedFilePath.isEmpty()) { if (!database->importPlugin(resolvedFilePath, uri, errors)) { if (errors) { // XXX TODO: should we leave the import plugin error alone? // Here, we pop it off the top and coalesce it into this error's message. // The reason is that the lower level may add url and line/column numbering information. QDeclarativeError poppedError = errors->takeFirst(); QDeclarativeError error; error.setDescription(QDeclarativeImportDatabase::tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(poppedError.description())); error.setUrl(QUrl::fromLocalFile(absoluteFilePath)); errors->prepend(error); } return false; } } else { if (errors) { QDeclarativeError error; error.setDescription(QDeclarativeImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name)); error.setUrl(QUrl::fromLocalFile(absoluteFilePath)); errors->prepend(error); } return false; } } } if (components) *components = qmldirParser->components(); if (scripts) *scripts = qmldirParser->scripts(); return true; } QString QDeclarativeImportsPrivate::resolvedUri(const QString &dir_arg, QDeclarativeImportDatabase *database) { QString dir = dir_arg; if (dir.endsWith(QLatin1Char('/')) || dir.endsWith(QLatin1Char('\\'))) dir.chop(1); QStringList paths = database->fileImportPath; qSort(paths.begin(), paths.end(), greaterThan); // Ensure subdirs preceed their parents. QString stableRelativePath = dir; foreach(const QString &path, paths) { if (dir.startsWith(path)) { stableRelativePath = dir.mid(path.length()+1); break; } } stableRelativePath.replace(QLatin1Char('\\'), QLatin1Char('/')); // remove optional versioning in dot notation from uri int lastSlash = stableRelativePath.lastIndexOf(QLatin1Char('/')); if (lastSlash >= 0) { int versionDot = stableRelativePath.indexOf(QLatin1Char('.'), lastSlash); if (versionDot >= 0) stableRelativePath = stableRelativePath.left(versionDot); } stableRelativePath.replace(QLatin1Char('/'), QLatin1Char('.')); return stableRelativePath; } bool QDeclarativeImportsPrivate::add(const QDeclarativeDirComponents &qmldircomponentsnetwork, const QString& uri_arg, const QString& prefix, int vmaj, int vmin, QDeclarativeScript::Import::Type importType, QDeclarativeImportDatabase *database, QList *errors) { static QLatin1String Slash_qmldir("/qmldir"); static QLatin1Char Slash('/'); QDeclarativeDirComponents qmldircomponents = qmldircomponentsnetwork; QDeclarativeDirScripts qmldirscripts; QString uri = uri_arg; QDeclarativeImportedNamespace *s; if (prefix.isEmpty()) { s = &unqualifiedset; } else { s = set.value(prefix); if (!s) set.insert(prefix,(s=new QDeclarativeImportedNamespace)); } QString url = uri; bool versionFound = false; if (importType == QDeclarativeScript::Import::Library) { Q_ASSERT(vmaj >= 0 && vmin >= 0); // Versions are always specified for libraries url.replace(QLatin1Char('.'), Slash); bool found = false; QString dir; QString qmldir; // step 1: search for extension with fully encoded version number foreach (const QString &p, database->fileImportPath) { dir = p+Slash+url; QFileInfo fi(dir+QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin)+QLatin1String("/qmldir")); const QString absoluteFilePath = fi.absoluteFilePath(); if (fi.isFile()) { found = true; const QString absolutePath = fi.absolutePath(); if (absolutePath.at(0) == QLatin1Char(':')) url = QLatin1String("qrc://") + absolutePath.mid(1); else url = QUrl::fromLocalFile(fi.absolutePath()).toString(); uri = resolvedUri(dir, database); if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors)) return false; break; } } // TODO: Should this search be omitted if found == true? // step 2: search for extension with encoded version major foreach (const QString &p, database->fileImportPath) { dir = p+Slash+url; QFileInfo fi(dir+QString(QLatin1String(".%1")).arg(vmaj)+QLatin1String("/qmldir")); const QString absoluteFilePath = fi.absoluteFilePath(); if (fi.isFile()) { found = true; const QString absolutePath = fi.absolutePath(); if (absolutePath.at(0) == QLatin1Char(':')) url = QLatin1String("qrc://") + absolutePath.mid(1); else url = QUrl::fromLocalFile(fi.absolutePath()).toString(); uri = resolvedUri(dir, database); if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors)) return false; break; } } if (!found) { // step 3: search for extension without version number foreach (const QString &p, database->fileImportPath) { dir = p+Slash+url; qmldir = dir+Slash_qmldir; QString absoluteFilePath = typeLoader->absoluteFilePath(qmldir); if (!absoluteFilePath.isEmpty()) { found = true; QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(Slash)+1); if (absolutePath.at(0) == QLatin1Char(':')) url = QLatin1String("qrc://") + absolutePath.mid(1); else url = QUrl::fromLocalFile(absolutePath).toString(); uri = resolvedUri(dir, database); if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, &qmldirscripts, errors)) return false; break; } } } if (QDeclarativeMetaType::isModule(uri, vmaj, vmin)) versionFound = true; if (!versionFound && qmldircomponents.isEmpty() && qmldirscripts.isEmpty()) { if (errors) { QDeclarativeError error; // we don't set the url or line or column as these will be set by the loader. if (QDeclarativeMetaType::isAnyModule(uri)) error.setDescription(QDeclarativeImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin)); else error.setDescription(QDeclarativeImportDatabase::tr("module \"%1\" is not installed").arg(uri_arg)); errors->prepend(error); } return false; } } else { if (importType == QDeclarativeScript::Import::File && qmldircomponents.isEmpty()) { QString importUrl = resolveLocalUrl(base, uri + Slash_qmldir); QString localFileOrQrc = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(importUrl); if (!localFileOrQrc.isEmpty()) { QString dir = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri)); if (!typeLoader->directoryExists(dir)) { if (errors) { QDeclarativeError error; // we don't set the line or column as these will be set by the loader. error.setDescription(QDeclarativeImportDatabase::tr("\"%1\": no such directory").arg(uri_arg)); error.setUrl(QUrl(importUrl)); errors->prepend(error); } return false; // local import dirs must exist } uri = resolvedUri(dir, database); if (uri.endsWith(Slash)) uri.chop(1); if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) { if (!importExtension(localFileOrQrc,uri,database,&qmldircomponents,&qmldirscripts,errors)) return false; } } else { if (prefix.isEmpty()) { // directory must at least exist for valid import QString localFileOrQrc = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(resolveLocalUrl(base, uri)); if (!typeLoader->directoryExists(localFileOrQrc)) { if (errors) { QDeclarativeError error; // we don't set the line or column as these will be set by the loader. if (localFileOrQrc.isEmpty()) error.setDescription(QDeclarativeImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(uri)); else error.setDescription(QDeclarativeImportDatabase::tr("\"%1\": no such directory").arg(uri)); error.setUrl(QUrl(importUrl)); errors->prepend(error); } return false; } } } } url = resolveLocalUrl(base, url); } if (!versionFound && (vmaj > -1) && (vmin > -1) && !qmldircomponents.isEmpty()) { int lowest_min = INT_MAX; int highest_min = INT_MIN; QList::const_iterator cend = qmldircomponents.constEnd(); for (QList::const_iterator cit = qmldircomponents.constBegin(); cit != cend; ++cit) { if (cit->majorVersion == vmaj) { lowest_min = qMin(lowest_min, cit->minorVersion); highest_min = qMax(highest_min, cit->minorVersion); } } if (lowest_min > vmin || highest_min < vmin) { if (errors) { QDeclarativeError error; // we don't set the url or line or column information, as these will be set by the loader. error.setDescription(QDeclarativeImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin)); errors->prepend(error); } return false; } } if (!url.endsWith(Slash)) url += Slash; QMap scripts; if (!qmldirscripts.isEmpty()) { // Verify that we haven't imported these scripts already QList::const_iterator end = s->imports.constEnd(); for (QList::const_iterator it = s->imports.constBegin(); it != end; ++it) { if (it->uri == uri) { QDeclarativeError error; error.setDescription(QDeclarativeImportDatabase::tr("\"%1\" is ambiguous. Found in %2 and in %3").arg(uri).arg(url).arg(it->url)); errors->prepend(error); return false; } } QList::const_iterator send = qmldirscripts.constEnd(); for (QList::const_iterator sit = qmldirscripts.constBegin(); sit != send; ++sit) { // Only include scripts that match our requested version if (((vmaj == -1) || (sit->majorVersion == vmaj)) && ((vmin == -1) || (sit->minorVersion <= vmin))) { // Load the highest version that matches QMap::iterator it = scripts.find(sit->nameSpace); if (it == scripts.end() || (it->minorVersion < sit->minorVersion)) { scripts.insert(sit->nameSpace, *sit); } } } } QDeclarativeImportedNamespace::Data data; data.uri = uri; data.url = url; data.majversion = vmaj; data.minversion = vmin; data.isLibrary = importType == QDeclarativeScript::Import::Library; data.qmlDirComponents = qmldircomponents; data.qmlDirScripts = scripts.values(); s->imports.prepend(data); return true; } bool QDeclarativeImportsPrivate::find(const QString& type, int *vmajor, int *vminor, QDeclarativeType** type_return, QString* url_return, QList *errors) { QDeclarativeImportedNamespace *s = 0; int slash = type.indexOf(QLatin1Char('/')); if (slash >= 0) { QString namespaceName = type.left(slash); s = set.value(namespaceName); if (!s) { if (errors) { QDeclarativeError error; error.setDescription(QDeclarativeImportDatabase::tr("- %1 is not a namespace").arg(namespaceName)); errors->prepend(error); } return false; } int nslash = type.indexOf(QLatin1Char('/'),slash+1); if (nslash > 0) { if (errors) { QDeclarativeError error; error.setDescription(QDeclarativeImportDatabase::tr("- nested namespaces not allowed")); errors->prepend(error); } return false; } } else { s = &unqualifiedset; } QString unqualifiedtype = slash < 0 ? type : type.mid(slash+1); // common-case opt (QString::mid works fine, but slower) if (s) { if (s->find(typeLoader,unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errors)) return true; if (s->imports.count() == 1 && !s->imports.at(0).isLibrary && url_return && s != &unqualifiedset) { // qualified, and only 1 url *url_return = resolveLocalUrl(s->imports.at(0).url, unqualifiedtype + QLatin1String(".qml")); return true; } } return false; } QDeclarativeImportedNamespace *QDeclarativeImportsPrivate::findNamespace(const QString& type) { return set.value(type); } bool QDeclarativeImportedNamespace::find(QDeclarativeTypeLoader *typeLoader, const QString& type, int *vmajor, int *vminor, QDeclarativeType** type_return, QString* url_return, QString *base, QList *errors) { bool typeRecursionDetected = false; for (int i=0; i= 0) { b = b.left(slash+1); QString l = b.left(slash); if (u1.startsWith(b)) u1 = u1.mid(b.count()); else if (u1 == l) u1 = QDeclarativeImportDatabase::tr("local directory"); if (u2.startsWith(b)) u2 = u2.mid(b.count()); else if (u2 == l) u2 = QDeclarativeImportDatabase::tr("local directory"); } } QDeclarativeError error; if (u1 != u2) { error.setDescription(QDeclarativeImportDatabase::tr("is ambiguous. Found in %1 and in %2").arg(u1).arg(u2)); } else { error.setDescription(QDeclarativeImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5") .arg(u1) .arg(imports.at(i).majversion).arg(imports.at(i).minversion) .arg(imports.at(j).majversion).arg(imports.at(j).minversion)); } errors->prepend(error); } return false; } } } return true; } } if (errors) { QDeclarativeError error; if (typeRecursionDetected) error.setDescription(QDeclarativeImportDatabase::tr("is instantiated recursively")); else error.setDescription(QDeclarativeImportDatabase::tr("is not a type")); errors->prepend(error); } return false; } /*! \class QDeclarativeImportDatabase \brief The QDeclarativeImportDatabase class manages the QML imports for a QDeclarativeEngine. \internal */ QDeclarativeImportDatabase::QDeclarativeImportDatabase(QDeclarativeEngine *e) : engine(e) { filePluginPath << QLatin1String("."); // Search order is applicationDirPath(), $QML_IMPORT_PATH, QLibraryInfo::ImportsPath #ifndef QT_NO_SETTINGS QString installImportsPath = QLibraryInfo::location(QLibraryInfo::ImportsPath); addImportPath(installImportsPath); #endif // QT_NO_SETTINGS // env import paths QByteArray envImportPath = qgetenv("QML_IMPORT_PATH"); if (!envImportPath.isEmpty()) { #if defined(Q_OS_WIN) QLatin1Char pathSep(';'); #else QLatin1Char pathSep(':'); #endif QStringList paths = QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts); for (int ii = paths.count() - 1; ii >= 0; --ii) addImportPath(paths.at(ii)); } addImportPath(QCoreApplication::applicationDirPath()); } QDeclarativeImportDatabase::~QDeclarativeImportDatabase() { } /*! \internal Adds information to \a imports such that subsequent calls to resolveType() will resolve types qualified by \a prefix by considering types found at the given \a uri. The uri is either a directory (if importType is FileImport), or a URI resolved using paths added via addImportPath() (if importType is LibraryImport). The \a prefix may be empty, in which case the import location is considered for unqualified types. The base URL must already have been set with Import::setBaseUrl(). */ bool QDeclarativeImports::addImport(QDeclarativeImportDatabase *importDb, const QString& uri, const QString& prefix, int vmaj, int vmin, QDeclarativeScript::Import::Type importType, const QDeclarativeDirComponents &qmldircomponentsnetwork, QList *errors) { if (qmlImportTrace()) qDebug().nospace() << "QDeclarativeImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: " << uri << " " << vmaj << '.' << vmin << " " << (importType==QDeclarativeScript::Import::Library? "Library" : "File") << " as " << prefix; return d->add(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, importDb, errors); } /*! \internal Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix. The \a prefix must contain the dot. \a qmldirPath is the location of the qmldir file. */ QString QDeclarativeImportDatabase::resolvePlugin(QDeclarativeTypeLoader *typeLoader, const QString &qmldirPath, const QString &qmldirPluginPath, const QString &baseName, const QStringList &suffixes, const QString &prefix) { QStringList searchPaths = filePluginPath; bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath); if (!qmldirPluginPathIsRelative) searchPaths.prepend(qmldirPluginPath); foreach (const QString &pluginPath, searchPaths) { QString resolvedPath; if (pluginPath == QLatin1String(".")) { if (qmldirPluginPathIsRelative && !qmldirPluginPath.isEmpty() && qmldirPluginPath != QLatin1String(".")) resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + qmldirPluginPath); else resolvedPath = qmldirPath; } else { if (QDir::isRelativePath(pluginPath)) resolvedPath = QDir::cleanPath(qmldirPath + QLatin1Char('/') + pluginPath); else resolvedPath = pluginPath; } // hack for resources, should probably go away if (resolvedPath.startsWith(QLatin1Char(':'))) resolvedPath = QCoreApplication::applicationDirPath(); if (!resolvedPath.endsWith(QLatin1Char('/'))) resolvedPath += QLatin1Char('/'); foreach (const QString &suffix, suffixes) { QString pluginFileName = prefix; pluginFileName += baseName; pluginFileName += suffix; QString absolutePath = typeLoader->absoluteFilePath(resolvedPath + pluginFileName); if (!absolutePath.isEmpty()) return absolutePath; } } if (qmlImportTrace()) qDebug() << "QDeclarativeImportDatabase::resolvePlugin: Could not resolve plugin" << baseName << "in" << qmldirPath; return QString(); } /*! \internal Returns the result of the merge of \a baseName with \a dir and the platform suffix. \table \header \i Platform \i Valid suffixes \row \i Windows \i \c .dll \row \i Unix/Linux \i \c .so \row \i AIX \i \c .a \row \i HP-UX \i \c .sl, \c .so (HP-UXi) \row \i Mac OS X \i \c .dylib, \c .bundle, \c .so \endtable Version number on unix are ignored. */ QString QDeclarativeImportDatabase::resolvePlugin(QDeclarativeTypeLoader *typeLoader, const QString &qmldirPath, const QString &qmldirPluginPath, const QString &baseName) { #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, QStringList() # ifdef QT_DEBUG << QLatin1String("d.dll") // try a qmake-style debug build first # endif << QLatin1String(".dll")); #else # if defined(Q_OS_DARWIN) return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, QStringList() # ifdef QT_DEBUG << QLatin1String("_debug.dylib") // try a qmake-style debug build first << QLatin1String(".dylib") # else << QLatin1String(".dylib") << QLatin1String("_debug.dylib") // try a qmake-style debug build after # endif << QLatin1String(".so") << QLatin1String(".bundle"), QLatin1String("lib")); # else // Generic Unix QStringList validSuffixList; # if defined(Q_OS_HPUX) /* See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF": "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit), the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix." */ validSuffixList << QLatin1String(".sl"); # if defined __ia64 validSuffixList << QLatin1String(".so"); # endif # elif defined(Q_OS_AIX) validSuffixList << QLatin1String(".a") << QLatin1String(".so"); # elif defined(Q_OS_UNIX) validSuffixList << QLatin1String(".so"); # endif // Examples of valid library names: // libfoo.so return resolvePlugin(typeLoader, qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib")); # endif #endif } /*! \internal */ QStringList QDeclarativeImportDatabase::pluginPathList() const { return filePluginPath; } /*! \internal */ void QDeclarativeImportDatabase::setPluginPathList(const QStringList &paths) { filePluginPath = paths; } /*! \internal */ void QDeclarativeImportDatabase::addPluginPath(const QString& path) { if (qmlImportTrace()) qDebug().nospace() << "QDeclarativeImportDatabase::addPluginPath: " << path; QUrl url = QUrl(path); if (url.isRelative() || url.scheme() == QLatin1String("file") || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path QDir dir = QDir(path); filePluginPath.prepend(dir.canonicalPath()); } else { filePluginPath.prepend(path); } } /*! \internal */ void QDeclarativeImportDatabase::addImportPath(const QString& path) { if (qmlImportTrace()) qDebug().nospace() << "QDeclarativeImportDatabase::addImportPath: " << path; if (path.isEmpty()) return; QUrl url = QUrl(path); QString cPath; if (url.isRelative() || url.scheme() == QLatin1String("file") || (url.scheme().length() == 1 && QFile::exists(path)) ) { // windows path QDir dir = QDir(path); cPath = dir.canonicalPath(); } else { cPath = path; cPath.replace(QLatin1Char('\\'), QLatin1Char('/')); } if (!cPath.isEmpty() && !fileImportPath.contains(cPath)) fileImportPath.prepend(cPath); } /*! \internal */ QStringList QDeclarativeImportDatabase::importPathList() const { return fileImportPath; } /*! \internal */ void QDeclarativeImportDatabase::setImportPathList(const QStringList &paths) { fileImportPath = paths; } /*! \internal */ bool QDeclarativeImportDatabase::importPlugin(const QString &filePath, const QString &uri, QList *errors) { if (qmlImportTrace()) qDebug().nospace() << "QDeclarativeImportDatabase::importPlugin: " << uri << " from " << filePath; #ifndef QT_NO_LIBRARY QFileInfo fileInfo(filePath); const QString absoluteFilePath = fileInfo.absoluteFilePath(); bool engineInitialized = initializedPlugins.contains(absoluteFilePath); bool typesRegistered = qmlEnginePluginsWithRegisteredTypes()->contains(absoluteFilePath); if (typesRegistered) { Q_ASSERT_X(qmlEnginePluginsWithRegisteredTypes()->value(absoluteFilePath) == uri, "QDeclarativeImportDatabase::importExtension", "Internal error: Plugin imported previously with different uri"); } if (!engineInitialized || !typesRegistered) { if (!QDeclarative_isFileCaseCorrect(absoluteFilePath)) { if (errors) { QDeclarativeError error; error.setDescription(tr("File name case mismatch for \"%1\"").arg(absoluteFilePath)); errors->prepend(error); } return false; } QPluginLoader loader(absoluteFilePath); if (!loader.load()) { if (errors) { QDeclarativeError error; error.setDescription(loader.errorString()); errors->prepend(error); } return false; } QObject *instance = loader.instance(); if (QDeclarativeTypesExtensionInterface *iface = qobject_cast(instance)) { const QByteArray bytes = uri.toUtf8(); const char *moduleId = bytes.constData(); if (!typesRegistered) { // XXX thread this code should probably be protected with a mutex. qmlEnginePluginsWithRegisteredTypes()->insert(absoluteFilePath, uri); iface->registerTypes(moduleId); } if (!engineInitialized) { // things on the engine (eg. adding new global objects) have to be done for every // engine. // XXX protect against double initialization initializedPlugins.insert(absoluteFilePath); QDeclarativeExtensionInterface *eiface = qobject_cast(instance); if (eiface) { QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine); ep->typeLoader.initializeEngine(eiface, moduleId); } } } else { if (errors) { QDeclarativeError error; error.setDescription(loader.errorString()); errors->prepend(error); } return false; } } return true; #else return false; #endif } QT_END_NAMESPACE