diff options
author | Fawzi Mohamed <fawzi.mohamed@digia.com> | 2014-01-23 14:28:31 +0100 |
---|---|---|
committer | Fawzi Mohamed <fawzi.mohamed@digia.com> | 2014-02-19 21:18:58 +0100 |
commit | d24cb60d487e2241c8364b83658f31178305176a (patch) | |
tree | 2702c6f07ecca6a11c194b5352ab622dd55dc783 /src/libs | |
parent | bf989b75a212b6a39f5a910ca78ad84f50e71f00 (diff) | |
download | qt-creator-d24cb60d487e2241c8364b83658f31178305176a.tar.gz |
qml: moving most of ModelManager logic to ModelManageInterface
Currently ModelManager contains lot logic, but as it sits in QmlJSTools
it is not possible to use it in standalone tests.
Moving most of the logic to ModelManagerInterface (and cleanup)
to allow better testing, and refactoring.
This introduces a dependency of the qmljs lib on the cplusplus lib
Also a (small) part of the CppTool::ModelManagerInterface has been
moved to CPlusPlus::CppModelManagerBase to remove the dependency on
CppTools to gather the Qml types exposed from C++.
Change-Id: Icad7fe96dfd0f1a2b1058d82bd98c77c40aa5e9d
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@digia.com>
Diffstat (limited to 'src/libs')
-rw-r--r-- | src/libs/cplusplus/cplusplus-lib.pri | 6 | ||||
-rw-r--r-- | src/libs/cplusplus/cplusplus.qbs | 1 | ||||
-rw-r--r-- | src/libs/cplusplus/cppmodelmanagerbase.cpp | 76 | ||||
-rw-r--r-- | src/libs/cplusplus/cppmodelmanagerbase.h | 62 | ||||
-rw-r--r-- | src/libs/qmljs/qmljs-lib.pri | 4 | ||||
-rw-r--r-- | src/libs/qmljs/qmljs.qbs | 3 | ||||
-rw-r--r-- | src/libs/qmljs/qmljs_dependencies.pri | 3 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsconstants.h | 6 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsfindexportedcpptypes.cpp | 777 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsfindexportedcpptypes.h | 64 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsmodelmanagerinterface.cpp | 1140 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsmodelmanagerinterface.h | 167 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsplugindumper.cpp | 589 | ||||
-rw-r--r-- | src/libs/qmljs/qmljsplugindumper.h | 104 |
14 files changed, 2953 insertions, 49 deletions
diff --git a/src/libs/cplusplus/cplusplus-lib.pri b/src/libs/cplusplus/cplusplus-lib.pri index bd77b940bf..7d2a3c9aca 100644 --- a/src/libs/cplusplus/cplusplus-lib.pri +++ b/src/libs/cplusplus/cplusplus-lib.pri @@ -57,7 +57,8 @@ HEADERS += \ $$PWD/pp-scanner.h \ $$PWD/findcdbbreakpoint.h \ $$PWD/PPToken.h \ - $$PWD/Dumpers.h + $$PWD/Dumpers.h \ + $$PWD/cppmodelmanagerbase.h SOURCES += \ $$PWD/SimpleLexer.cpp \ @@ -85,6 +86,7 @@ SOURCES += \ $$PWD/pp-scanner.cpp \ $$PWD/findcdbbreakpoint.cpp \ $$PWD/PPToken.cpp \ - $$PWD/Dumpers.cpp + $$PWD/Dumpers.cpp \ + $$PWD/cppmodelmanagerbase.cpp RESOURCES += $$PWD/cplusplus.qrc diff --git a/src/libs/cplusplus/cplusplus.qbs b/src/libs/cplusplus/cplusplus.qbs index 2331804a55..09dfe63cec 100644 --- a/src/libs/cplusplus/cplusplus.qbs +++ b/src/libs/cplusplus/cplusplus.qbs @@ -94,6 +94,7 @@ QtcLibrary { "BackwardsScanner.cpp", "BackwardsScanner.h", "CppDocument.cpp", "CppDocument.h", "CppRewriter.cpp", "CppRewriter.h", + "cppmodelmanagerbase.cpp", "cppmodelmanagerbase.h", "DependencyTable.cpp", "DependencyTable.h", "DeprecatedGenTemplateInstance.cpp", "DeprecatedGenTemplateInstance.h", "ExpressionUnderCursor.cpp", "ExpressionUnderCursor.h", diff --git a/src/libs/cplusplus/cppmodelmanagerbase.cpp b/src/libs/cplusplus/cppmodelmanagerbase.cpp new file mode 100644 index 0000000000..6ad44b5bc4 --- /dev/null +++ b/src/libs/cplusplus/cppmodelmanagerbase.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "cppmodelmanagerbase.h" + +namespace CPlusPlus { + +static CppModelManagerBase *g_instance = 0; + +CppModelManagerBase::CppModelManagerBase(QObject *parent) + : QObject(parent) +{ + Q_ASSERT(!g_instance); + g_instance = this; +} + +CppModelManagerBase::~CppModelManagerBase() +{ + Q_ASSERT(g_instance == this); + g_instance = 0; +} + +CppModelManagerBase *CppModelManagerBase::instance() +{ + return g_instance; +} + +bool CppModelManagerBase::trySetExtraDiagnostics(const QString &fileName, const QString &kind, + const QList<CPlusPlus::Document::DiagnosticMessage> &diagnostics) +{ + if (CppModelManagerBase *mm = instance()) + return mm->setExtraDiagnostics(fileName, kind, diagnostics); + return false; +} + +bool CppModelManagerBase::setExtraDiagnostics(const QString &fileName, const QString &kind, + const QList<CPlusPlus::Document::DiagnosticMessage> &diagnostics) +{ + Q_UNUSED(fileName); + Q_UNUSED(kind); + Q_UNUSED(diagnostics); + return false; +} + +CPlusPlus::Snapshot CppModelManagerBase::snapshot() const +{ + return CPlusPlus::Snapshot(); +} + +} // namespace CPlusPlus diff --git a/src/libs/cplusplus/cppmodelmanagerbase.h b/src/libs/cplusplus/cppmodelmanagerbase.h new file mode 100644 index 0000000000..f76c80a3ed --- /dev/null +++ b/src/libs/cplusplus/cppmodelmanagerbase.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef CPPMODELMANAGERBASE_H +#define CPPMODELMANAGERBASE_H + +#include <cplusplus/CppDocument.h> + +#include <QObject> +#include <QList> + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace CPlusPlus { + +class CPLUSPLUS_EXPORT CppModelManagerBase : public QObject +{ + Q_OBJECT +public: + CppModelManagerBase(QObject *parent = 0); + ~CppModelManagerBase(); + + static CppModelManagerBase *instance(); + static bool trySetExtraDiagnostics(const QString &fileName, const QString &kind, + const QList<Document::DiagnosticMessage> &diagnostics); + + virtual bool setExtraDiagnostics(const QString &fileName, const QString &kind, + const QList<Document::DiagnosticMessage> &diagnostics); + virtual CPlusPlus::Snapshot snapshot() const; +}; + +} // namespace CPlusPlus + +#endif // CPPMODELMANAGERBASE_H diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri index 6324d83e32..47c4a9ecf6 100644 --- a/src/libs/qmljs/qmljs-lib.pri +++ b/src/libs/qmljs/qmljs-lib.pri @@ -11,6 +11,7 @@ HEADERS += \ $$PWD/qmljsbind.h \ $$PWD/qmljsbundle.h \ $$PWD/qmljsevaluate.h \ + $$PWD/qmljsfindexportedcpptypes.h \ $$PWD/qmljsdocument.h \ $$PWD/qmljsscanner.h \ $$PWD/qmljsinterpreter.h \ @@ -25,6 +26,7 @@ HEADERS += \ $$PWD/qmljsrewriter.h \ $$PWD/qmljsicons.h \ $$PWD/qmljsdelta.h \ + $$PWD/qmljsplugindumper.h \ $$PWD/qmljstypedescriptionreader.h \ $$PWD/qmljsscopeastpath.h \ $$PWD/qmljsvalueowner.h \ @@ -47,6 +49,7 @@ SOURCES += \ $$PWD/qmljsbind.cpp \ $$PWD/qmljsbundle.cpp \ $$PWD/qmljsevaluate.cpp \ + $$PWD/qmljsfindexportedcpptypes.cpp \ $$PWD/qmljsdocument.cpp \ $$PWD/qmljsscanner.cpp \ $$PWD/qmljsinterpreter.cpp \ @@ -60,6 +63,7 @@ SOURCES += \ $$PWD/qmljsrewriter.cpp \ $$PWD/qmljsicons.cpp \ $$PWD/qmljsdelta.cpp \ + $$PWD/qmljsplugindumper.cpp \ $$PWD/qmljstypedescriptionreader.cpp \ $$PWD/qmljsscopeastpath.cpp \ $$PWD/qmljsvalueowner.cpp \ diff --git a/src/libs/qmljs/qmljs.qbs b/src/libs/qmljs/qmljs.qbs index 94968cf313..0016128545 100644 --- a/src/libs/qmljs/qmljs.qbs +++ b/src/libs/qmljs/qmljs.qbs @@ -11,6 +11,7 @@ QtcLibrary { Depends { name: "Utils" } Depends { name: "LanguageUtils" } + Depends { name: "CPlusPlus" } Depends { name: "Qt"; submodules: ["widgets", "script", "xml"] } Group { @@ -33,6 +34,7 @@ QtcLibrary { "qmljsdelta.cpp", "qmljsdelta.h", "qmljsdocument.cpp", "qmljsdocument.h", "qmljsevaluate.cpp", "qmljsevaluate.h", + "qmljsfindexportedcpptypes.cpp", "qmljsfindexportedcpptypes.h", "qmljsicons.cpp", "qmljsicons.h", "qmljsicontextpane.h", "qmljsimportdependencies.cpp", "qmljsimportdependencies.h", @@ -41,6 +43,7 @@ QtcLibrary { "qmljslineinfo.cpp", "qmljslineinfo.h", "qmljslink.cpp", "qmljslink.h", "qmljsmodelmanagerinterface.cpp", "qmljsmodelmanagerinterface.h", + "qmljsplugindumper.cpp", "qmljsplugindumper.h", "qmljspropertyreader.cpp", "qmljspropertyreader.h", "qmljsqrcparser.cpp", "qmljsqrcparser.h", "qmljsreformatter.cpp", "qmljsreformatter.h", diff --git a/src/libs/qmljs/qmljs_dependencies.pri b/src/libs/qmljs/qmljs_dependencies.pri index e1d1204148..3686f2e628 100644 --- a/src/libs/qmljs/qmljs_dependencies.pri +++ b/src/libs/qmljs/qmljs_dependencies.pri @@ -1,4 +1,5 @@ QTC_LIB_NAME = QmlJS QTC_LIB_DEPENDS += \ utils \ - languageutils + languageutils \ + cplusplus diff --git a/src/libs/qmljs/qmljsconstants.h b/src/libs/qmljs/qmljsconstants.h index 3ddb014754..377f282ea4 100644 --- a/src/libs/qmljs/qmljsconstants.h +++ b/src/libs/qmljs/qmljsconstants.h @@ -80,5 +80,11 @@ enum Enum }; } +namespace Constants { + +const char TASK_INDEX[] = "QmlJSEditor.TaskIndex"; +const char TASK_IMPORT_SCAN[] = "QmlJSEditor.TaskImportScan"; + +} // namespace Constants } // namespace QmlJS #endif // QMLJSCONSTANTS_H diff --git a/src/libs/qmljs/qmljsfindexportedcpptypes.cpp b/src/libs/qmljs/qmljsfindexportedcpptypes.cpp new file mode 100644 index 0000000000..7034ea64b9 --- /dev/null +++ b/src/libs/qmljs/qmljsfindexportedcpptypes.cpp @@ -0,0 +1,777 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmljsfindexportedcpptypes.h" + +#include <qmljs/qmljsinterpreter.h> +#include <qmljs/qmljsdocument.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> +#include <cplusplus/cppmodelmanagerbase.h> +#include <cplusplus/CppDocument.h> + +#include <QDebug> +#include <QList> + +//using namespace QmlJS; + +namespace { +using namespace CPlusPlus; + +class ExportedQmlType { +public: + QString packageName; + QString typeName; + LanguageUtils::ComponentVersion version; + Scope *scope; + QString typeExpression; +}; + +class ContextProperty { +public: + QString name; + QString expression; + unsigned line, column; +}; + +class FindExportsVisitor : protected ASTVisitor +{ + CPlusPlus::Document::Ptr _doc; + QList<ExportedQmlType> _exportedTypes; + QList<ContextProperty> _contextProperties; + CompoundStatementAST *_compound; + ASTMatcher _matcher; + ASTPatternBuilder _builder; + Overview _overview; + QList<CPlusPlus::Document::DiagnosticMessage> _messages; + +public: + FindExportsVisitor(CPlusPlus::Document::Ptr doc) + : ASTVisitor(doc->translationUnit()) + , _doc(doc) + , _compound(0) + {} + + void operator()() + { + _exportedTypes.clear(); + _contextProperties.clear(); + accept(translationUnit()->ast()); + } + + QList<CPlusPlus::Document::DiagnosticMessage> messages() const + { + return _messages; + } + + QList<ExportedQmlType> exportedTypes() const + { + return _exportedTypes; + } + + QList<ContextProperty> contextProperties() const + { + return _contextProperties; + } + +protected: + virtual bool visit(CompoundStatementAST *ast) + { + CompoundStatementAST *old = _compound; + _compound = ast; + accept(ast->statement_list); + _compound = old; + return false; + } + + virtual bool visit(CallAST *ast) + { + if (checkForQmlRegisterType(ast)) + return false; + checkForSetContextProperty(ast); + return false; + } + + bool checkForQmlRegisterType(CallAST *ast) + { + IdExpressionAST *idExp = ast->base_expression->asIdExpression(); + if (!idExp || !idExp->name) + return false; + TemplateIdAST *templateId = idExp->name->asTemplateId(); + if (!templateId || !templateId->identifier_token) + return false; + + // check the name + const Identifier *templateIdentifier = translationUnit()->identifier(templateId->identifier_token); + if (!templateIdentifier) + return false; + const QString callName = QString::fromUtf8(templateIdentifier->chars()); + int argCount = 0; + if (callName == QLatin1String("qmlRegisterType")) + argCount = 4; + else if (callName == QLatin1String("qmlRegisterUncreatableType")) + argCount = 5; + else + return false; + + // check that there is a typeId + if (!templateId->template_argument_list || !templateId->template_argument_list->value) + return false; + // sometimes there can be a second argument, the metaRevisionNumber + if (templateId->template_argument_list->next) { + if (!templateId->template_argument_list->next->value || + templateId->template_argument_list->next->next) + return false; + // should just check for a generic ExpressionAST? + NumericLiteralAST *metaRevision = + templateId->template_argument_list->next->value->asNumericLiteral(); + if (!metaRevision) + return false; + } + + TypeIdAST *typeId = templateId->template_argument_list->value->asTypeId(); + if (!typeId) + return false; + + // must have four arguments for qmlRegisterType and five for qmlRegisterUncreatableType + if (!ast->expression_list + || !ast->expression_list->value || !ast->expression_list->next + || !ast->expression_list->next->value || !ast->expression_list->next->next + || !ast->expression_list->next->next->value || !ast->expression_list->next->next->next + || !ast->expression_list->next->next->next->value) + return false; + if (argCount == 4 && ast->expression_list->next->next->next->next) + return false; + if (argCount == 5 && (!ast->expression_list->next->next->next->next + || !ast->expression_list->next->next->next->next->value + || ast->expression_list->next->next->next->next->next)) + return false; + + // 4th argument must be a string literal + const StringLiteral *nameLit = 0; + if (StringLiteralAST *nameAst = skipStringCall(ast->expression_list->next->next->next->value)->asStringLiteral()) + nameLit = translationUnit()->stringLiteral(nameAst->literal_token); + if (!nameLit) { + unsigned line, column; + translationUnit()->getTokenStartPosition(ast->expression_list->next->next->next->value->firstToken(), &line, &column); + _messages += Document::DiagnosticMessage( + Document::DiagnosticMessage::Warning, + _doc->fileName(), + line, column, + QmlJS::FindExportedCppTypes::tr( + "The type will only be available in Qt Creator's QML editors when the type name is a string literal")); + return false; + } + + // if the first argument is a string literal, things are easy + QString packageName; + if (StringLiteralAST *packageAst = skipStringCall(ast->expression_list->value)->asStringLiteral()) { + const StringLiteral *packageLit = translationUnit()->stringLiteral(packageAst->literal_token); + packageName = QString::fromUtf8(packageLit->chars(), packageLit->size()); + } + // as a special case, allow an identifier package argument if there's a + // Q_ASSERT(QLatin1String(uri) == QLatin1String("actual uri")); + // in the enclosing compound statement + QString uriNameString; + if (IdExpressionAST *uriName = ast->expression_list->value->asIdExpression()) + uriNameString = stringOf(uriName); + if (packageName.isEmpty() && !uriNameString.isEmpty() && _compound) { + for (StatementListAST *it = _compound->statement_list; it; it = it->next) { + StatementAST *stmt = it->value; + + packageName = nameOfUriAssert(stmt, uriNameString); + if (!packageName.isEmpty()) + break; + } + } + if (packageName.isEmpty() && _compound) { + // check the comments in _compound for annotations + QRegExp uriAnnotation(QLatin1String("@uri\\s*([\\w\\.]*)")); + + // scan every comment between the pipes in + // {| + // // comment + // othercode; + // |qmlRegisterType<Foo>(...); + const Token begin = _doc->translationUnit()->tokenAt(_compound->firstToken()); + const Token end = _doc->translationUnit()->tokenAt(ast->firstToken()); + + // go through comments backwards to find the annotation closest to the call + for (unsigned i = _doc->translationUnit()->commentCount(); i-- > 0; ) { + const Token commentToken = _doc->translationUnit()->commentAt(i); + if (commentToken.begin() >= end.begin() || commentToken.end() <= begin.begin()) + continue; + const QString comment = stringOf(commentToken); + if (uriAnnotation.indexIn(comment) != -1) { + packageName = uriAnnotation.cap(1); + break; + } + } + } + if (packageName.isEmpty()) { + packageName = QmlJS::CppQmlTypes::defaultPackage; + unsigned line, column; + translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column); + _messages += Document::DiagnosticMessage( + Document::DiagnosticMessage::Warning, + _doc->fileName(), + line, column, + QmlJS::FindExportedCppTypes::tr( + "The module URI cannot be determined by static analysis. The type will be available\n" + "globally in the QML editor. You can add a \"// @uri My.Module.Uri\" annotation to let\n" + "Qt Creator know about a likely URI.")); + } + + // second and third argument must be integer literals + const NumericLiteral *majorLit = 0; + const NumericLiteral *minorLit = 0; + if (NumericLiteralAST *majorAst = ast->expression_list->next->value->asNumericLiteral()) + majorLit = translationUnit()->numericLiteral(majorAst->literal_token); + if (NumericLiteralAST *minorAst = ast->expression_list->next->next->value->asNumericLiteral()) + minorLit = translationUnit()->numericLiteral(minorAst->literal_token); + + // build the descriptor + ExportedQmlType exportedType; + exportedType.typeName = QString::fromUtf8(nameLit->chars(), nameLit->size()); + exportedType.packageName = packageName; + if (majorLit && minorLit && majorLit->isInt() && minorLit->isInt()) { + exportedType.version = LanguageUtils::ComponentVersion( + QString::fromUtf8(majorLit->chars(), majorLit->size()).toInt(), + QString::fromUtf8(minorLit->chars(), minorLit->size()).toInt()); + } + + // we want to do lookup later, so also store the surrounding scope + unsigned line, column; + translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column); + exportedType.scope = _doc->scopeAt(line, column); + + // and the expression + const Token begin = translationUnit()->tokenAt(typeId->firstToken()); + const Token last = translationUnit()->tokenAt(typeId->lastToken() - 1); + exportedType.typeExpression = QString::fromUtf8(_doc->utf8Source().mid(begin.begin(), last.end() - begin.begin())); + + _exportedTypes += exportedType; + + return true; + } + + static NameAST *callName(ExpressionAST *exp) + { + if (IdExpressionAST *idExp = exp->asIdExpression()) + return idExp->name; + if (MemberAccessAST *memberExp = exp->asMemberAccess()) + return memberExp->member_name; + return 0; + } + + static ExpressionAST *skipQVariant(ExpressionAST *ast, TranslationUnit *translationUnit) + { + CallAST *call = ast->asCall(); + if (!call) + return ast; + if (!call->expression_list + || !call->expression_list->value + || call->expression_list->next) + return ast; + + IdExpressionAST *idExp = call->base_expression->asIdExpression(); + if (!idExp || !idExp->name) + return ast; + + // QVariant(foo) -> foo + if (SimpleNameAST *simpleName = idExp->name->asSimpleName()) { + const Identifier *id = translationUnit->identifier(simpleName->identifier_token); + if (!id) + return ast; + if (QString::fromUtf8(id->chars(), id->size()) != QLatin1String("QVariant")) + return ast; + return call->expression_list->value; + // QVariant::fromValue(foo) -> foo + } else if (QualifiedNameAST *q = idExp->name->asQualifiedName()) { + SimpleNameAST *simpleRhsName = q->unqualified_name->asSimpleName(); + if (!simpleRhsName + || !q->nested_name_specifier_list + || !q->nested_name_specifier_list->value + || q->nested_name_specifier_list->next) + return ast; + const Identifier *rhsId = translationUnit->identifier(simpleRhsName->identifier_token); + if (!rhsId) + return ast; + if (QString::fromUtf8(rhsId->chars(), rhsId->size()) != QLatin1String("fromValue")) + return ast; + NestedNameSpecifierAST *nested = q->nested_name_specifier_list->value; + SimpleNameAST *simpleLhsName = nested->class_or_namespace_name->asSimpleName(); + if (!simpleLhsName) + return ast; + const Identifier *lhsId = translationUnit->identifier(simpleLhsName->identifier_token); + if (!lhsId) + return ast; + if (QString::fromUtf8(lhsId->chars(), lhsId->size()) != QLatin1String("QVariant")) + return ast; + return call->expression_list->value; + } + + return ast; + } + + bool checkForSetContextProperty(CallAST *ast) + { + // check whether ast->base_expression has the 'setContextProperty' name + NameAST *callNameAst = callName(ast->base_expression); + if (!callNameAst) + return false; + SimpleNameAST *simpleNameAst = callNameAst->asSimpleName(); + if (!simpleNameAst || !simpleNameAst->identifier_token) + return false; + const Identifier *nameIdentifier = translationUnit()->identifier(simpleNameAst->identifier_token); + if (!nameIdentifier) + return false; + const QString callName = QString::fromUtf8(nameIdentifier->chars(), nameIdentifier->size()); + if (callName != QLatin1String("setContextProperty")) + return false; + + // must have two arguments + if (!ast->expression_list + || !ast->expression_list->value || !ast->expression_list->next + || !ast->expression_list->next->value || ast->expression_list->next->next) + return false; + + // first argument must be a string literal + const StringLiteral *nameLit = 0; + if (StringLiteralAST *nameAst = skipStringCall(ast->expression_list->value)->asStringLiteral()) + nameLit = translationUnit()->stringLiteral(nameAst->literal_token); + if (!nameLit) { + unsigned line, column; + translationUnit()->getTokenStartPosition(ast->expression_list->value->firstToken(), &line, &column); + _messages += Document::DiagnosticMessage( + Document::DiagnosticMessage::Warning, + _doc->fileName(), + line, column, + QmlJS::FindExportedCppTypes::tr( + "must be a string literal to be available in the QML editor")); + return false; + } + + ContextProperty contextProperty; + contextProperty.name = QString::fromUtf8(nameLit->chars(), nameLit->size()); + contextProperty.expression = stringOf(skipQVariant(ast->expression_list->next->value, translationUnit())); + // we want to do lookup later, so also store the line and column of the target scope + translationUnit()->getTokenStartPosition(ast->firstToken(), + &contextProperty.line, + &contextProperty.column); + + _contextProperties += contextProperty; + + return true; + } + +private: + QString stringOf(CPlusPlus::AST *ast) + { + return stringOf(ast->firstToken(), ast->lastToken() - 1); + } + + QString stringOf(int first, int last) + { + const Token firstToken = translationUnit()->tokenAt(first); + const Token lastToken = translationUnit()->tokenAt(last); + return QString::fromUtf8(_doc->utf8Source().mid(firstToken.begin(), lastToken.end() - firstToken.begin())); + } + + QString stringOf(const Token &token) + { + return QString::fromUtf8(_doc->utf8Source().mid(token.begin(), token.length())); + } + + ExpressionAST *skipStringCall(ExpressionAST *exp) + { + if (!exp || !exp->asCall()) + return exp; + + IdExpressionAST *callName = _builder.IdExpression(); + CallAST *call = _builder.Call(callName); + if (!exp->match(call, &_matcher)) + return exp; + + const QString name = stringOf(callName); + if (name != QLatin1String("QLatin1String") + && name != QLatin1String("QString")) + return exp; + + if (!call->expression_list || call->expression_list->next) + return exp; + + return call->expression_list->value; + } + + QString nameOfUriAssert(StatementAST *stmt, const QString &uriName) + { + QString null; + + IdExpressionAST *outerCallName = _builder.IdExpression(); + BinaryExpressionAST *binary = _builder.BinaryExpression(); + // assert(... == ...); + ExpressionStatementAST *pattern = _builder.ExpressionStatement( + _builder.Call(outerCallName, _builder.ExpressionList( + binary))); + + if (!stmt->match(pattern, &_matcher)) { + outerCallName = _builder.IdExpression(); + binary = _builder.BinaryExpression(); + // the expansion of Q_ASSERT(...), + // ((!(... == ...)) ? qt_assert(...) : ...); + pattern = _builder.ExpressionStatement( + _builder.NestedExpression( + _builder.ConditionalExpression( + _builder.NestedExpression( + _builder.UnaryExpression( + _builder.NestedExpression( + binary))), + _builder.Call(outerCallName)))); + + if (!stmt->match(pattern, &_matcher)) + return null; + } + + const QString outerCall = stringOf(outerCallName); + if (outerCall != QLatin1String("qt_assert") + && outerCall != QLatin1String("assert") + && outerCall != QLatin1String("Q_ASSERT")) + return null; + + if (translationUnit()->tokenAt(binary->binary_op_token).kind() != T_EQUAL_EQUAL) + return null; + + ExpressionAST *lhsExp = skipStringCall(binary->left_expression); + ExpressionAST *rhsExp = skipStringCall(binary->right_expression); + if (!lhsExp || !rhsExp) + return null; + + StringLiteralAST *uriString = lhsExp->asStringLiteral(); + IdExpressionAST *uriArgName = lhsExp->asIdExpression(); + if (!uriString) + uriString = rhsExp->asStringLiteral(); + if (!uriArgName) + uriArgName = rhsExp->asIdExpression(); + if (!uriString || !uriArgName) + return null; + + if (stringOf(uriArgName) != uriName) + return null; + + const StringLiteral *packageLit = translationUnit()->stringLiteral(uriString->literal_token); + return QString::fromUtf8(packageLit->chars(), packageLit->size()); + } +}; + +static FullySpecifiedType stripPointerAndReference(const FullySpecifiedType &type) +{ + Type *t = type.type(); + while (t) { + if (PointerType *ptr = t->asPointerType()) + t = ptr->elementType().type(); + else if (ReferenceType *ref = t->asReferenceType()) + t = ref->elementType().type(); + else + break; + } + return FullySpecifiedType(t); +} + +static QString toQmlType(const FullySpecifiedType &type) +{ + Overview overview; + QString result = overview.prettyType(stripPointerAndReference(type)); + if (result == QLatin1String("QString")) + result = QLatin1String("string"); + return result; +} + +static Class *lookupClass(const QString &expression, Scope *scope, TypeOfExpression &typeOf) +{ + QList<LookupItem> results = typeOf(expression.toUtf8(), scope); + Class *klass = 0; + foreach (const LookupItem &item, results) { + if (item.declaration()) { + klass = item.declaration()->asClass(); + if (klass) + return klass; + } + } + return 0; +} + +static LanguageUtils::FakeMetaObject::Ptr buildFakeMetaObject( + Class *klass, + QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *fakeMetaObjects, + TypeOfExpression &typeOf) +{ + using namespace LanguageUtils; + + if (FakeMetaObject::Ptr fmo = fakeMetaObjects->value(klass)) + return fmo; + + FakeMetaObject::Ptr fmo(new FakeMetaObject); + if (!klass) + return fmo; + fakeMetaObjects->insert(klass, fmo); + + Overview namePrinter; + + fmo->setClassName(namePrinter.prettyName(klass->name())); + // add the no-package export, so the cpp name can be used in properties + fmo->addExport(fmo->className(), QmlJS::CppQmlTypes::cppPackage, ComponentVersion()); + + for (unsigned i = 0; i < klass->memberCount(); ++i) { + Symbol *member = klass->memberAt(i); + if (!member->name()) + continue; + if (Function *func = member->type()->asFunctionType()) { + if (!func->isSlot() && !func->isInvokable() && !func->isSignal()) + continue; + FakeMetaMethod method(namePrinter.prettyName(func->name()), toQmlType(func->returnType())); + if (func->isSignal()) + method.setMethodType(FakeMetaMethod::Signal); + else + method.setMethodType(FakeMetaMethod::Slot); + for (unsigned a = 0, argc = func->argumentCount(); a < argc; ++a) { + Symbol *arg = func->argumentAt(a); + QString name; + if (arg->name()) + name = namePrinter.prettyName(arg->name()); + method.addParameter(name, toQmlType(arg->type())); + } + fmo->addMethod(method); + } + if (QtPropertyDeclaration *propDecl = member->asQtPropertyDeclaration()) { + const FullySpecifiedType &type = propDecl->type(); + const bool isList = false; // ### fixme + const bool isWritable = propDecl->flags() & QtPropertyDeclaration::WriteFunction; + const bool isPointer = type.type() && type.type()->isPointerType(); + const int revision = 0; // ### fixme + FakeMetaProperty property( + namePrinter.prettyName(propDecl->name()), + toQmlType(type), + isList, isWritable, isPointer, + revision); + fmo->addProperty(property); + } + if (QtEnum *qtEnum = member->asQtEnum()) { + // find the matching enum + Enum *e = 0; + QList<LookupItem> result = typeOf(namePrinter.prettyName(qtEnum->name()).toUtf8(), klass); + foreach (const LookupItem &item, result) { + if (item.declaration()) { + e = item.declaration()->asEnum(); + if (e) + break; + } + } + if (!e) + continue; + + FakeMetaEnum metaEnum(namePrinter.prettyName(e->name())); + for (unsigned j = 0; j < e->memberCount(); ++j) { + Symbol *enumMember = e->memberAt(j); + if (!enumMember->name()) + continue; + metaEnum.addKey(namePrinter.prettyName(enumMember->name()), 0); + } + fmo->addEnum(metaEnum); + } + } + + // only single inheritance is supported + if (klass->baseClassCount() > 0) { + BaseClass *base = klass->baseClassAt(0); + if (!base->name()) + return fmo; + const QString baseClassName = namePrinter.prettyName(base->name()); + fmo->setSuperclassName(baseClassName); + + Class *baseClass = lookupClass(baseClassName, klass, typeOf); + if (!baseClass) + return fmo; + buildFakeMetaObject(baseClass, fakeMetaObjects, typeOf); + } + + return fmo; +} + +static void buildExportedQmlObjects( + TypeOfExpression &typeOf, + const QList<ExportedQmlType> &cppExports, + QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *fakeMetaObjects) +{ + using namespace LanguageUtils; + + if (cppExports.isEmpty()) + return; + + foreach (const ExportedQmlType &exportedType, cppExports) { + Class *klass = lookupClass(exportedType.typeExpression, exportedType.scope, typeOf); + // accepts a null klass + FakeMetaObject::Ptr fmo = buildFakeMetaObject(klass, fakeMetaObjects, typeOf); + fmo->addExport(exportedType.typeName, + exportedType.packageName, + exportedType.version); + } +} + +static void buildContextProperties( + const Document::Ptr &doc, + TypeOfExpression &typeOf, + const QList<ContextProperty> &contextPropertyDescriptions, + QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> *fakeMetaObjects, + QHash<QString, QString> *contextProperties) +{ + using namespace LanguageUtils; + + foreach (const ContextProperty &property, contextPropertyDescriptions) { + Scope *scope = doc->scopeAt(property.line, property.column); + QList<LookupItem> results = typeOf(property.expression.toUtf8(), scope); + QString typeName; + if (!results.isEmpty()) { + LookupItem result = results.first(); + FullySpecifiedType simpleType = stripPointerAndReference(result.type()); + if (NamedType *namedType = simpleType.type()->asNamedType()) { + Scope *typeScope = result.scope(); + if (!typeScope) + typeScope = scope; // incorrect but may be an ok fallback + ClassOrNamespace *binding = typeOf.context().lookupType(namedType->name(), typeScope); + if (binding && !binding->symbols().isEmpty()) { + // find the best 'Class' symbol + for (int i = binding->symbols().size() - 1; i >= 0; --i) { + if (Class *klass = binding->symbols().at(i)->asClass()) { + FakeMetaObject::Ptr fmo = buildFakeMetaObject(klass, fakeMetaObjects, typeOf); + typeName = fmo->className(); + break; + } + } + } + } + } + + contextProperties->insert(property.name, typeName); + } +} + +} // anonymous namespace + +namespace QmlJS { + +FindExportedCppTypes::FindExportedCppTypes(const CPlusPlus::Snapshot &snapshot) + : m_snapshot(snapshot) +{ +} + +void FindExportedCppTypes::operator()(const CPlusPlus::Document::Ptr &document) +{ + m_contextProperties.clear(); + m_exportedTypes.clear(); + + // this check only guards against some input errors, if document's source and AST has not + // been guarded properly the source and AST may still become empty/null while this function is running + if (document->utf8Source().isEmpty() + || !document->translationUnit()->ast()) + return; + + FindExportsVisitor finder(document); + finder(); + static const QString kindKey = QLatin1String("QmlJSTools.ExportedQmlTypesDiagnostic"); + CppModelManagerBase::trySetExtraDiagnostics(document->fileName(), kindKey, + finder.messages()); + + // if nothing was found, done + const QList<ContextProperty> contextPropertyDescriptions = finder.contextProperties(); + const QList<ExportedQmlType> exports = finder.exportedTypes(); + if (exports.isEmpty() && contextPropertyDescriptions.isEmpty()) + return; + + // context properties need lookup inside function scope, and thus require a full check + CPlusPlus::Document::Ptr localDoc = document; + if (document->checkMode() != CPlusPlus::Document::FullCheck && !contextPropertyDescriptions.isEmpty()) { + localDoc = m_snapshot.documentFromSource(document->utf8Source(), document->fileName()); + localDoc->check(); + } + + // create a single type of expression (and bindings) for the document + TypeOfExpression typeOf; + typeOf.init(localDoc, m_snapshot); + QHash<Class *, LanguageUtils::FakeMetaObject::Ptr> fakeMetaObjects; + + // generate the exports from qmlRegisterType + buildExportedQmlObjects(typeOf, exports, &fakeMetaObjects); + + // add the types from the context properties and create a name->cppname map + // also expose types where necessary + buildContextProperties(localDoc, typeOf, contextPropertyDescriptions, + &fakeMetaObjects, &m_contextProperties); + + // convert to list of FakeMetaObject::ConstPtr + m_exportedTypes.reserve(fakeMetaObjects.size()); + foreach (const LanguageUtils::FakeMetaObject::Ptr &fmo, fakeMetaObjects) { + fmo->updateFingerprint(); + m_exportedTypes += fmo; + } +} + +QList<LanguageUtils::FakeMetaObject::ConstPtr> FindExportedCppTypes::exportedTypes() const +{ + return m_exportedTypes; +} + +QHash<QString, QString> FindExportedCppTypes::contextProperties() const +{ + return m_contextProperties; +} + +bool FindExportedCppTypes::maybeExportsTypes(const CPlusPlus::Document::Ptr &document) +{ + if (!document->control()) + return false; + const QByteArray qmlRegisterTypeToken("qmlRegisterType"); + const QByteArray qmlRegisterUncreatableTypeToken("qmlRegisterUncreatableType"); + const QByteArray setContextPropertyToken("setContextProperty"); + if (document->control()->findIdentifier( + qmlRegisterTypeToken.constData(), qmlRegisterTypeToken.size())) { + return true; + } + if (document->control()->findIdentifier( + qmlRegisterUncreatableTypeToken.constData(), qmlRegisterUncreatableTypeToken.size())) { + return true; + } + if (document->control()->findIdentifier( + setContextPropertyToken.constData(), setContextPropertyToken.size())) { + return true; + } + return false; +} + +} // namespace QmlJS diff --git a/src/libs/qmljs/qmljsfindexportedcpptypes.h b/src/libs/qmljs/qmljsfindexportedcpptypes.h new file mode 100644 index 0000000000..ca6508c39a --- /dev/null +++ b/src/libs/qmljs/qmljsfindexportedcpptypes.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLJS_QMLJSFINDEXPORTEDCPPTYPES_H +#define QMLJS_QMLJSFINDEXPORTEDCPPTYPES_H + +#include "qmljs_global.h" +#include <cplusplus/CppDocument.h> +#include <languageutils/fakemetaobject.h> + +#include <QCoreApplication> +#include <QHash> + +namespace QmlJS { + +class QMLJS_EXPORT FindExportedCppTypes +{ + Q_DECLARE_TR_FUNCTIONS(QmlJSTools::FindExportedCppTypes) +public: + FindExportedCppTypes(const CPlusPlus::Snapshot &snapshot); + + // document must have a valid source and ast for the duration of the call + void operator()(const CPlusPlus::Document::Ptr &document); + + QList<LanguageUtils::FakeMetaObject::ConstPtr> exportedTypes() const; + QHash<QString, QString> contextProperties() const; + + static bool maybeExportsTypes(const CPlusPlus::Document::Ptr &document); + +private: + CPlusPlus::Snapshot m_snapshot; + QList<LanguageUtils::FakeMetaObject::ConstPtr> m_exportedTypes; + QHash<QString, QString> m_contextProperties; +}; + +} // namespace QmlJS + +#endif // QMLJS_QMLJSFINDEXPORTEDCPPTYPES_H diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index ec70d6bf65..d9aa6aa118 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -27,11 +27,33 @@ ** ****************************************************************************/ +#include "qmljsbind.h" +#include "qmljsconstants.h" +#include "qmljsfindexportedcpptypes.h" +#include "qmljsinterpreter.h" #include "qmljsmodelmanagerinterface.h" +#include "qmljsplugindumper.h" +#include "qmljstypedescriptionreader.h" +#include <cplusplus/cppmodelmanagerbase.h> +#include <utils/function.h> +#include <utils/hostosinfo.h> + +#include <QDebug> +#include <QDir> +#include <QFile> #include <QFileInfo> +#include <QMetaObject> +#include <QRegExp> +#include <QTextDocument> +#include <QTextStream> +#include <QTimer> +#include <QtAlgorithms> +#include <utils/runextensions.h> + +#include <stdio.h> -using namespace QmlJS; +namespace QmlJS { /*! \class QmlJS::ModelManagerInterface @@ -54,9 +76,45 @@ using namespace QmlJS; static ModelManagerInterface *g_instance = 0; +static QStringList environmentImportPaths() +{ + QStringList paths; + + QByteArray envImportPath = qgetenv("QML_IMPORT_PATH"); + + foreach (const QString &path, QString::fromLatin1(envImportPath) + .split(Utils::HostOsInfo::pathListSeparator(), QString::SkipEmptyParts)) { + QString canonicalPath = QDir(path).canonicalPath(); + if (!canonicalPath.isEmpty() && !paths.contains(canonicalPath)) + paths.append(canonicalPath); + } + + return paths; +} + ModelManagerInterface::ModelManagerInterface(QObject *parent) - : QObject(parent) + : QObject(parent), + m_shouldScanImports(false), + m_pluginDumper(new PluginDumper(this)) { + m_synchronizer.setCancelOnWait(true); + + m_updateCppQmlTypesTimer = new QTimer(this); + m_updateCppQmlTypesTimer->setInterval(1000); + m_updateCppQmlTypesTimer->setSingleShot(true); + connect(m_updateCppQmlTypesTimer, SIGNAL(timeout()), SLOT(startCppQmlTypeUpdate())); + + m_asyncResetTimer = new QTimer(this); + m_asyncResetTimer->setInterval(15000); + m_asyncResetTimer->setSingleShot(true); + connect(m_asyncResetTimer, SIGNAL(timeout()), SLOT(resetCodeModel())); + + qRegisterMetaType<QmlJS::Document::Ptr>("QmlJS::Document::Ptr"); + qRegisterMetaType<QmlJS::LibraryInfo>("QmlJS::LibraryInfo"); + + m_defaultImportPaths << environmentImportPaths(); + updateImportPaths(); + Q_ASSERT(! g_instance); g_instance = this; } @@ -113,8 +171,1086 @@ ModelManagerInterface *ModelManagerInterface::instance() return g_instance; } +void ModelManagerInterface::writeWarning(const QString &msg) +{ + if (ModelManagerInterface *i = instance()) + i->writeMessageInternal(msg); + else + qDebug() << msg; +} + +ModelManagerInterface::WorkingCopy ModelManagerInterface::workingCopy() +{ + if (ModelManagerInterface *i = instance()) + return i->workingCopyInternal(); + return WorkingCopy(); +} + QHash<QString, Language::Enum> ModelManagerInterface::languageForSuffix() const { return defaultLanguageMapping(); } +void ModelManagerInterface::writeMessageInternal(const QString &msg) const +{ + qDebug() << msg; +} + +ModelManagerInterface::WorkingCopy ModelManagerInterface::workingCopyInternal() const +{ + ModelManagerInterface::WorkingCopy res; + return res; +} + +void ModelManagerInterface::addTaskInternal(QFuture<void> result, const QString &msg, + const char *taskId) const +{ + Q_UNUSED(result); + qDebug() << "started " << taskId << " " << msg; +} + +void ModelManagerInterface::loadQmlTypeDescriptionsInternal(const QString &resourcePath) +{ + const QDir typeFileDir(resourcePath + QLatin1String("/qml-type-descriptions")); + const QStringList qmlTypesExtensions = QStringList() << QLatin1String("*.qmltypes"); + QFileInfoList qmlTypesFiles = typeFileDir.entryInfoList( + qmlTypesExtensions, + QDir::Files, + QDir::Name); + + QStringList errors; + QStringList warnings; + + // filter out the actual Qt builtins + for (int i = 0; i < qmlTypesFiles.size(); ++i) { + if (qmlTypesFiles.at(i).baseName() == QLatin1String("builtins")) { + QFileInfoList list; + list.append(qmlTypesFiles.at(i)); + CppQmlTypesLoader::defaultQtObjects = + CppQmlTypesLoader::loadQmlTypes(list, &errors, &warnings); + qmlTypesFiles.removeAt(i); + break; + } + } + + // load the fallbacks for libraries + CppQmlTypesLoader::defaultLibraryObjects.unite( + CppQmlTypesLoader::loadQmlTypes(qmlTypesFiles, &errors, &warnings)); + + foreach (const QString &error, errors) + writeMessageInternal(error); + foreach (const QString &warning, warnings) + writeMessageInternal(warning); +} + + + +Snapshot ModelManagerInterface::snapshot() const +{ + QMutexLocker locker(&m_mutex); + return _validSnapshot; +} + +Snapshot ModelManagerInterface::newestSnapshot() const +{ + QMutexLocker locker(&m_mutex); + return _newestSnapshot; +} + +void ModelManagerInterface::updateSourceFiles(const QStringList &files, + bool emitDocumentOnDiskChanged) +{ + refreshSourceFiles(files, emitDocumentOnDiskChanged); +} + +QFuture<void> ModelManagerInterface::refreshSourceFiles(const QStringList &sourceFiles, + bool emitDocumentOnDiskChanged) +{ + if (sourceFiles.isEmpty()) + return QFuture<void>(); + + QFuture<void> result = QtConcurrent::run(&ModelManagerInterface::parse, + workingCopyInternal(), sourceFiles, + this, Language::Qml, + emitDocumentOnDiskChanged); + + if (m_synchronizer.futures().size() > 10) { + QList<QFuture<void> > futures = m_synchronizer.futures(); + + m_synchronizer.clearFutures(); + + foreach (const QFuture<void> &future, futures) { + if (! (future.isFinished() || future.isCanceled())) + m_synchronizer.addFuture(future); + } + } + + m_synchronizer.addFuture(result); + + if (sourceFiles.count() > 1) + addTaskInternal(result, tr("Indexing"), Constants::TASK_INDEX); + + if (sourceFiles.count() > 1 && !m_shouldScanImports) { + bool scan = false; + { + QMutexLocker l(&m_mutex); + if (!m_shouldScanImports) { + m_shouldScanImports = true; + scan = true; + } + } + if (scan) + updateImportPaths(); + } + + return result; +} + + +void ModelManagerInterface::fileChangedOnDisk(const QString &path) +{ + QtConcurrent::run(&ModelManagerInterface::parse, + workingCopyInternal(), QStringList() << path, + this, Language::Unknown, true); +} + +void ModelManagerInterface::removeFiles(const QStringList &files) +{ + emit aboutToRemoveFiles(files); + + QMutexLocker locker(&m_mutex); + + foreach (const QString &file, files) { + _validSnapshot.remove(file); + _newestSnapshot.remove(file); + } +} + +namespace { +bool pInfoLessThanActive(const ModelManagerInterface::ProjectInfo &p1, const ModelManagerInterface::ProjectInfo &p2) +{ + QStringList s1 = p1.activeResourceFiles; + QStringList s2 = p2.activeResourceFiles; + if (s1.size() < s2.size()) + return true; + if (s1.size() > s2.size()) + return false; + for (int i = 0; i < s1.size(); ++i) { + if (s1.at(i) < s2.at(i)) + return true; + else if (s1.at(i) > s2.at(i)) + return false; + } + return false; +} + +bool pInfoLessThanAll(const ModelManagerInterface::ProjectInfo &p1, const ModelManagerInterface::ProjectInfo &p2) +{ + QStringList s1 = p1.allResourceFiles; + QStringList s2 = p2.allResourceFiles; + if (s1.size() < s2.size()) + return true; + if (s1.size() > s2.size()) + return false; + for (int i = 0; i < s1.size(); ++i) { + if (s1.at(i) < s2.at(i)) + return true; + else if (s1.at(i) > s2.at(i)) + return false; + } + return false; +} +} + +QStringList ModelManagerInterface::filesAtQrcPath(const QString &path, const QLocale *locale, + ProjectExplorer::Project *project, + QrcResourceSelector resources) +{ + QString normPath = QrcParser::normalizedQrcFilePath(path); + QList<ProjectInfo> pInfos; + if (project) + pInfos.append(projectInfo(project)); + else + pInfos = projectInfos(); + + QStringList res; + QSet<QString> pathsChecked; + foreach (const ModelManagerInterface::ProjectInfo &pInfo, pInfos) { + QStringList qrcFilePaths; + if (resources == ActiveQrcResources) + qrcFilePaths = pInfo.activeResourceFiles; + else + qrcFilePaths = pInfo.allResourceFiles; + foreach (const QString &qrcFilePath, qrcFilePaths) { + if (pathsChecked.contains(qrcFilePath)) + continue; + pathsChecked.insert(qrcFilePath); + QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath); + if (qrcFile.isNull()) + continue; + qrcFile->collectFilesAtPath(normPath, &res, locale); + } + } + res.sort(); // make the result predictable + return res; +} + +QMap<QString, QStringList> ModelManagerInterface::filesInQrcPath(const QString &path, + const QLocale *locale, + ProjectExplorer::Project *project, + bool addDirs, + QrcResourceSelector resources) +{ + QString normPath = QrcParser::normalizedQrcDirectoryPath(path); + QList<ProjectInfo> pInfos; + if (project) { + pInfos.append(projectInfo(project)); + } else { + pInfos = projectInfos(); + if (resources == ActiveQrcResources) // make the result predictable + qSort(pInfos.begin(), pInfos.end(), &pInfoLessThanActive); + else + qSort(pInfos.begin(), pInfos.end(), &pInfoLessThanAll); + } + QMap<QString, QStringList> res; + QSet<QString> pathsChecked; + foreach (const ModelManagerInterface::ProjectInfo &pInfo, pInfos) { + QStringList qrcFilePaths; + if (resources == ActiveQrcResources) + qrcFilePaths = pInfo.activeResourceFiles; + else + qrcFilePaths = pInfo.allResourceFiles; + foreach (const QString &qrcFilePath, qrcFilePaths) { + if (pathsChecked.contains(qrcFilePath)) + continue; + pathsChecked.insert(qrcFilePath); + QrcParser::ConstPtr qrcFile = m_qrcCache.parsedPath(qrcFilePath); + + if (qrcFile.isNull()) + continue; + qrcFile->collectFilesInPath(normPath, &res, addDirs, locale); + } + } + return res; +} + +QList<ModelManagerInterface::ProjectInfo> ModelManagerInterface::projectInfos() const +{ + QMutexLocker locker(&m_mutex); + + return m_projects.values(); +} + +ModelManagerInterface::ProjectInfo ModelManagerInterface::projectInfo(ProjectExplorer::Project *project) const +{ + QMutexLocker locker(&m_mutex); + + return m_projects.value(project, ProjectInfo()); +} + +void ModelManagerInterface::updateProjectInfo(const ProjectInfo &pinfo, ProjectExplorer::Project *p) +{ + if (! pinfo.isValid() || !p) + return; + + Snapshot snapshot; + ProjectInfo oldInfo; + { + QMutexLocker locker(&m_mutex); + oldInfo = m_projects.value(p); + m_projects.insert(p, pinfo); + snapshot = _validSnapshot; + } + + if (oldInfo.qmlDumpPath != pinfo.qmlDumpPath + || oldInfo.qmlDumpEnvironment != pinfo.qmlDumpEnvironment) { + m_pluginDumper->scheduleRedumpPlugins(); + m_pluginDumper->scheduleMaybeRedumpBuiltins(pinfo); + } + + + updateImportPaths(); + + // remove files that are no longer in the project and have been deleted + QStringList deletedFiles; + foreach (const QString &oldFile, oldInfo.sourceFiles) { + if (snapshot.document(oldFile) + && !pinfo.sourceFiles.contains(oldFile) + && !QFile::exists(oldFile)) { + deletedFiles += oldFile; + } + } + removeFiles(deletedFiles); + foreach (const QString &oldFile, deletedFiles) + m_fileToProject.remove(oldFile, p); + + // parse any files not yet in the snapshot + QStringList newFiles; + foreach (const QString &file, pinfo.sourceFiles) { + if (!snapshot.document(file)) + newFiles += file; + } + updateSourceFiles(newFiles, false); + foreach (const QString &newFile, deletedFiles) + m_fileToProject.insert(newFile, p); + + // update qrc cache + foreach (const QString &newQrc, pinfo.allResourceFiles) + m_qrcCache.addPath(newQrc); + foreach (const QString &oldQrc, oldInfo.allResourceFiles) + m_qrcCache.removePath(oldQrc); + + int majorVersion, minorVersion, patchVersion; + // dump builtin types if the shipped definitions are probably outdated and the + // Qt version ships qmlplugindump + if (::sscanf(pinfo.qtVersionString.toLatin1().constData(), "%d.%d.%d", + &majorVersion, &minorVersion, &patchVersion) != 3) + majorVersion = minorVersion = patchVersion = -1; + + if (majorVersion > 4 || (majorVersion == 4 && (minorVersion > 8 || (majorVersion == 8 + && patchVersion >= 5)))) { + m_pluginDumper->loadBuiltinTypes(pinfo); + } + + emit projectInfoUpdated(pinfo); +} + + +void ModelManagerInterface::removeProjectInfo(ProjectExplorer::Project *project) +{ + ProjectInfo info; + info.sourceFiles.clear(); + // update with an empty project info to clear data + updateProjectInfo(info, project); + + { + QMutexLocker locker(&m_mutex); + m_projects.remove(project); + } +} + +ModelManagerInterface::ProjectInfo ModelManagerInterface::projectInfoForPath(QString path) +{ + QMutexLocker locker(&m_mutex); + + foreach (const ProjectInfo &p, m_projects) + if (p.sourceFiles.contains(path)) + return p; + return ProjectInfo(); +} + +void ModelManagerInterface::emitDocumentChangedOnDisk(Document::Ptr doc) +{ emit documentChangedOnDisk(doc); } + +void ModelManagerInterface::updateQrcFile(const QString &path) +{ + m_qrcCache.updatePath(path); +} + +void ModelManagerInterface::updateDocument(Document::Ptr doc) +{ + { + QMutexLocker locker(&m_mutex); + _validSnapshot.insert(doc); + _newestSnapshot.insert(doc, true); + } + emit documentUpdated(doc); +} + +void ModelManagerInterface::updateLibraryInfo(const QString &path, const LibraryInfo &info) +{ + if (!info.pluginTypeInfoError().isEmpty()) + qDebug() << "Dumping errors for " << path << ":" << info.pluginTypeInfoError(); + + { + QMutexLocker locker(&m_mutex); + _validSnapshot.insertLibraryInfo(path, info); + _newestSnapshot.insertLibraryInfo(path, info); + } + // only emit if we got new useful information + if (info.isValid()) + emit libraryInfoUpdated(path, info); +} + +static QStringList filesInDirectoryForLanguages(const QString &path, QList<Language::Enum> languages) +{ + const QStringList pattern = ModelManagerInterface::globPatternsForLanguages(languages); + QStringList files; + + const QDir dir(path); + foreach (const QFileInfo &fi, dir.entryInfoList(pattern, QDir::Files)) + files += fi.absoluteFilePath(); + + return files; +} + +static void findNewImplicitImports(const Document::Ptr &doc, const Snapshot &snapshot, + QStringList *importedFiles, QSet<QString> *scannedPaths) +{ + // scan files that could be implicitly imported + // it's important we also do this for JS files, otherwise the isEmpty check will fail + if (snapshot.documentsInDirectory(doc->path()).isEmpty()) { + if (! scannedPaths->contains(doc->path())) { + *importedFiles += filesInDirectoryForLanguages(doc->path(), + Document::companionLanguages(doc->language())); + scannedPaths->insert(doc->path()); + } + } +} + +static void findNewFileImports(const Document::Ptr &doc, const Snapshot &snapshot, + QStringList *importedFiles, QSet<QString> *scannedPaths) +{ + // scan files and directories that are explicitly imported + foreach (const ImportInfo &import, doc->bind()->imports()) { + const QString &importName = import.path(); + if (import.type() == ImportType::File) { + if (! snapshot.document(importName)) + *importedFiles += importName; + } else if (import.type() == ImportType::Directory) { + if (snapshot.documentsInDirectory(importName).isEmpty()) { + if (! scannedPaths->contains(importName)) { + *importedFiles += filesInDirectoryForLanguages(importName, + Document::companionLanguages(doc->language())); + scannedPaths->insert(importName); + } + } + } else if (import.type() == ImportType::QrcFile) { + QStringList importPaths = ModelManagerInterface::instance()->filesAtQrcPath(importName); + foreach (const QString &importPath, importPaths) { + if (! snapshot.document(importPath)) + *importedFiles += importPath; + } + } else if (import.type() == ImportType::QrcDirectory) { + QMapIterator<QString,QStringList> dirContents(ModelManagerInterface::instance()->filesInQrcPath(importName)); + while (dirContents.hasNext()) { + dirContents.next(); + if (Document::isQmlLikeOrJsLanguage(ModelManagerInterface::guessLanguageOfFile(dirContents.key()))) { + foreach (const QString &filePath, dirContents.value()) { + if (! snapshot.document(filePath)) + *importedFiles += filePath; + } + } + } + } + } +} + +static bool findNewQmlLibraryInPath(const QString &path, + const Snapshot &snapshot, + ModelManagerInterface *modelManager, + QStringList *importedFiles, + QSet<QString> *scannedPaths, + QSet<QString> *newLibraries, + bool ignoreMissing) +{ + // if we know there is a library, done + const LibraryInfo &existingInfo = snapshot.libraryInfo(path); + if (existingInfo.isValid()) + return true; + if (newLibraries->contains(path)) + return true; + // if we looked at the path before, done + if (existingInfo.wasScanned()) + return false; + + const QDir dir(path); + QFile qmldirFile(dir.filePath(QLatin1String("qmldir"))); + if (!qmldirFile.exists()) { + if (!ignoreMissing) { + LibraryInfo libraryInfo(LibraryInfo::NotFound); + modelManager->updateLibraryInfo(path, libraryInfo); + } + return false; + } + + if (Utils::HostOsInfo::isWindowsHost()) { + // QTCREATORBUG-3402 - be case sensitive even here? + } + + // found a new library! + qmldirFile.open(QFile::ReadOnly); + QString qmldirData = QString::fromUtf8(qmldirFile.readAll()); + + QmlDirParser qmldirParser; + qmldirParser.parse(qmldirData); + + const QString libraryPath = QFileInfo(qmldirFile).absolutePath(); + newLibraries->insert(libraryPath); + modelManager->updateLibraryInfo(libraryPath, LibraryInfo(qmldirParser)); + + // scan the qml files in the library + foreach (const QmlDirParser::Component &component, qmldirParser.components()) { + if (! component.fileName.isEmpty()) { + const QFileInfo componentFileInfo(dir.filePath(component.fileName)); + const QString path = QDir::cleanPath(componentFileInfo.absolutePath()); + if (! scannedPaths->contains(path)) { + *importedFiles += filesInDirectoryForLanguages(path, + Document::companionLanguages(Language::Unknown)); + scannedPaths->insert(path); + } + } + } + + return true; +} + +static void findNewQmlLibrary( + const QString &path, + const LanguageUtils::ComponentVersion &version, + const Snapshot &snapshot, + ModelManagerInterface *modelManager, + QStringList *importedFiles, + QSet<QString> *scannedPaths, + QSet<QString> *newLibraries) +{ + QString libraryPath = QString::fromLatin1("%1.%2.%3").arg( + path, + QString::number(version.majorVersion()), + QString::number(version.minorVersion())); + findNewQmlLibraryInPath( + libraryPath, snapshot, modelManager, + importedFiles, scannedPaths, newLibraries, false); + + libraryPath = QString::fromLatin1("%1.%2").arg( + path, + QString::number(version.majorVersion())); + findNewQmlLibraryInPath( + libraryPath, snapshot, modelManager, + importedFiles, scannedPaths, newLibraries, false); + + findNewQmlLibraryInPath( + path, snapshot, modelManager, + importedFiles, scannedPaths, newLibraries, false); +} + +static void findNewLibraryImports(const Document::Ptr &doc, const Snapshot &snapshot, + ModelManagerInterface *modelManager, + QStringList *importedFiles, QSet<QString> *scannedPaths, QSet<QString> *newLibraries) +{ + // scan current dir + findNewQmlLibraryInPath(doc->path(), snapshot, modelManager, + importedFiles, scannedPaths, newLibraries, false); + + // scan dir and lib imports + const QStringList importPaths = modelManager->importPaths(); + foreach (const ImportInfo &import, doc->bind()->imports()) { + if (import.type() == ImportType::Directory) { + const QString targetPath = import.path(); + findNewQmlLibraryInPath(targetPath, snapshot, modelManager, + importedFiles, scannedPaths, newLibraries, false); + } + + if (import.type() == ImportType::Library) { + if (!import.version().isValid()) + continue; + foreach (const QString &importPath, importPaths) { + const QString targetPath = QDir(importPath).filePath(import.path()); + findNewQmlLibrary(targetPath, import.version(), snapshot, modelManager, + importedFiles, scannedPaths, newLibraries); + } + } + } +} + +void ModelManagerInterface::parseLoop(QSet<QString> &scannedPaths, + QSet<QString> &newLibraries, + WorkingCopy workingCopy, + QStringList files, + ModelManagerInterface *modelManager, + Language::Enum mainLanguage, + bool emitDocChangedOnDisk, + Utils::function<bool(qreal)> reportProgress) +{ + for (int i = 0; i < files.size(); ++i) { + if (!reportProgress(qreal(i) / files.size())) + return; + + const QString fileName = files.at(i); + + Language::Enum language = guessLanguageOfFile(fileName); + if (language == Language::Unknown) { + if (fileName.endsWith(QLatin1String(".qrc"))) + modelManager->updateQrcFile(fileName); + continue; + } + if (language == Language::Qml + && (mainLanguage == Language::QmlQtQuick1 || Language::QmlQtQuick2)) + language = mainLanguage; + QString contents; + int documentRevision = 0; + + if (workingCopy.contains(fileName)) { + QPair<QString, int> entry = workingCopy.get(fileName); + contents = entry.first; + documentRevision = entry.second; + } else { + QFile inFile(fileName); + + if (inFile.open(QIODevice::ReadOnly)) { + QTextStream ins(&inFile); + contents = ins.readAll(); + inFile.close(); + } + } + + Document::MutablePtr doc = Document::create(fileName, language); + doc->setEditorRevision(documentRevision); + doc->setSource(contents); + doc->parse(); + + // update snapshot. requires synchronization, but significantly reduces amount of file + // system queries for library imports because queries are cached in libraryInfo + const Snapshot snapshot = modelManager->snapshot(); + + // get list of referenced files not yet in snapshot or in directories already scanned + QStringList importedFiles; + findNewImplicitImports(doc, snapshot, &importedFiles, &scannedPaths); + findNewFileImports(doc, snapshot, &importedFiles, &scannedPaths); + findNewLibraryImports(doc, snapshot, modelManager, &importedFiles, &scannedPaths, &newLibraries); + + // add new files to parse list + foreach (const QString &file, importedFiles) { + if (! files.contains(file)) + files.append(file); + } + + modelManager->updateDocument(doc); + if (emitDocChangedOnDisk) + modelManager->emitDocumentChangedOnDisk(doc); + } +} + +class FutureReporter +{ +public: + FutureReporter(QFutureInterface<void> &future, int multiplier = 100, int base = 0) + :future(future), multiplier(multiplier), base(base) + { } + bool operator()(qreal val) + { + if (future.isCanceled()) + return false; + future.setProgressValue(int(base + multiplier * val)); + return true; + } +private: + QFutureInterface<void> &future; + int multiplier; + int base; +}; + +void ModelManagerInterface::parse(QFutureInterface<void> &future, + WorkingCopy workingCopy, + QStringList files, + ModelManagerInterface *modelManager, + Language::Enum mainLanguage, + bool emitDocChangedOnDisk) +{ + FutureReporter reporter(future); + future.setProgressRange(0, 100); + + // paths we have scanned for files and added to the files list + QSet<QString> scannedPaths; + // libraries we've found while scanning imports + QSet<QString> newLibraries; + parseLoop(scannedPaths, newLibraries, workingCopy, files, modelManager, mainLanguage, + emitDocChangedOnDisk, reporter); + future.setProgressValue(100); +} + +struct ScanItem { + QString path; + int depth; + ScanItem(QString path = QString(), int depth = 0) + : path(path), depth(depth) + { } +}; + +void ModelManagerInterface::importScan(QFutureInterface<void> &future, + ModelManagerInterface::WorkingCopy workingCopy, + QStringList paths, ModelManagerInterface *modelManager, + Language::Enum language, + bool emitDocChangedOnDisk) +{ + // paths we have scanned for files and added to the files list + QSet<QString> scannedPaths = modelManager->m_scannedPaths; + // libraries we've found while scanning imports + QSet<QString> newLibraries; + + QVector<ScanItem> pathsToScan; + pathsToScan.reserve(paths.size()); + { + QMutexLocker l(&modelManager->m_mutex); + foreach (const QString &path, paths) { + QString cPath = QDir::cleanPath(path); + if (modelManager->m_scannedPaths.contains(cPath)) + continue; + pathsToScan.append(ScanItem(cPath)); + modelManager->m_scannedPaths.insert(cPath); + } + } + const int maxScanDepth = 5; + int progressRange = pathsToScan.size() * (1 << (2 + maxScanDepth)); + int totalWork(progressRange), workDone(0); + future.setProgressRange(0, progressRange); // update max length while iterating? + const bool libOnly = true; // FIXME remove when tested more + const Snapshot snapshot = modelManager->snapshot(); + while (!pathsToScan.isEmpty() && !future.isCanceled()) { + ScanItem toScan = pathsToScan.last(); + pathsToScan.pop_back(); + int pathBudget = (maxScanDepth + 2 - toScan.depth); + if (!scannedPaths.contains(toScan.path)) { + QStringList importedFiles; + if (!findNewQmlLibraryInPath(toScan.path, snapshot, modelManager, &importedFiles, + &scannedPaths, &newLibraries, true) + && !libOnly && snapshot.documentsInDirectory(toScan.path).isEmpty()) + importedFiles += filesInDirectoryForLanguages(toScan.path, + Document::companionLanguages(language)); + workDone += 1; + future.setProgressValue(progressRange * workDone / totalWork); + if (!importedFiles.isEmpty()) { + FutureReporter reporter(future, progressRange * pathBudget / (4 * totalWork), + progressRange * workDone / totalWork); + parseLoop(scannedPaths, newLibraries, workingCopy, importedFiles, modelManager, + language, emitDocChangedOnDisk, reporter); // run in parallel?? + importedFiles.clear(); + } + workDone += pathBudget / 4 - 1; + future.setProgressValue(progressRange * workDone / totalWork); + } else { + workDone += pathBudget / 4; + } + // always descend tree, as we might have just scanned with a smaller depth + if (toScan.depth < maxScanDepth) { + QDir dir(toScan.path); + QStringList subDirs(dir.entryList(QDir::Dirs)); + workDone += 1; + totalWork += pathBudget / 2 * subDirs.size() - pathBudget * 3 / 4 + 1; + foreach (const QString path, subDirs) + pathsToScan.append(ScanItem(dir.absoluteFilePath(path), toScan.depth + 1)); + } else { + workDone += pathBudget *3 / 4; + } + future.setProgressValue(progressRange * workDone / totalWork); + } + future.setProgressValue(progressRange); + if (future.isCanceled()) { + // assume no work has been done + QMutexLocker l(&modelManager->m_mutex); + foreach (const QString &path, paths) + modelManager->m_scannedPaths.remove(path); + } +} + +QStringList ModelManagerInterface::importPaths() const +{ + QMutexLocker l(&m_mutex); + return m_allImportPaths; +} + +QmlLanguageBundles ModelManagerInterface::activeBundles() const +{ + QMutexLocker l(&m_mutex); + return m_activeBundles; +} + +QmlLanguageBundles ModelManagerInterface::extendedBundles() const +{ + QMutexLocker l(&m_mutex); + return m_extendedBundles; +} + +void ModelManagerInterface::updateImportPaths() +{ + QStringList allImportPaths; + QmlLanguageBundles activeBundles; + QmlLanguageBundles extendedBundles; + QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(m_projects); + while (it.hasNext()) { + it.next(); + foreach (const QString &path, it.value().importPaths) { + const QString canonicalPath = QFileInfo(path).canonicalFilePath(); + if (!canonicalPath.isEmpty()) + allImportPaths += canonicalPath; + } + } + it.toFront(); + while (it.hasNext()) { + it.next(); + activeBundles.mergeLanguageBundles(it.value().activeBundle); + foreach (Language::Enum l, it.value().activeBundle.languages()) { + foreach (const QString &path, it.value().activeBundle.bundleForLanguage(l) + .searchPaths().stringList()) { + const QString canonicalPath = QFileInfo(path).canonicalFilePath(); + if (!canonicalPath.isEmpty()) + allImportPaths += canonicalPath; + } + } + } + it.toFront(); + while (it.hasNext()) { + it.next(); + extendedBundles.mergeLanguageBundles(it.value().extendedBundle); + foreach (Language::Enum l, it.value().extendedBundle.languages()) { + foreach (const QString &path, it.value().extendedBundle.bundleForLanguage(l) + .searchPaths().stringList()) { + const QString canonicalPath = QFileInfo(path).canonicalFilePath(); + if (!canonicalPath.isEmpty()) + allImportPaths += canonicalPath; + } + } + } + allImportPaths += m_defaultImportPaths; + allImportPaths.removeDuplicates(); + + { + QMutexLocker l(&m_mutex); + m_allImportPaths = allImportPaths; + m_activeBundles = activeBundles; + m_extendedBundles = extendedBundles; + } + + + // check if any file in the snapshot imports something new in the new paths + Snapshot snapshot = _validSnapshot; + QStringList importedFiles; + QSet<QString> scannedPaths; + QSet<QString> newLibraries; + foreach (const Document::Ptr &doc, snapshot) + findNewLibraryImports(doc, snapshot, this, &importedFiles, &scannedPaths, &newLibraries); + + updateSourceFiles(importedFiles, true); + + if (!m_shouldScanImports) + return; + QStringList pathToScan; + { + QMutexLocker l(&m_mutex); + foreach (QString importPath, allImportPaths) + if (!m_scannedPaths.contains(importPath)) { + pathToScan.append(importPath); + } + } + + if (pathToScan.count() > 1) { + QFuture<void> result = QtConcurrent::run(&ModelManagerInterface::importScan, + workingCopyInternal(), pathToScan, + this, Language::Qml, + true); + + if (m_synchronizer.futures().size() > 10) { + QList<QFuture<void> > futures = m_synchronizer.futures(); + + m_synchronizer.clearFutures(); + + foreach (const QFuture<void> &future, futures) { + if (! (future.isFinished() || future.isCanceled())) + m_synchronizer.addFuture(future); + } + } + + m_synchronizer.addFuture(result); + + addTaskInternal(result, tr("Qml import scan"), Constants::TASK_IMPORT_SCAN); + } +} + +ModelManagerInterface::ProjectInfo ModelManagerInterface::defaultProjectInfo() const +{ + if (m_projects.isEmpty()) + return ProjectInfo(); + return m_projects.begin().value(); +} + +void ModelManagerInterface::loadPluginTypes(const QString &libraryPath, const QString &importPath, + const QString &importUri, const QString &importVersion) +{ + m_pluginDumper->loadPluginTypes(libraryPath, importPath, importUri, importVersion); +} + +// is called *inside a c++ parsing thread*, to allow hanging on to source and ast +void ModelManagerInterface::maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc) +{ + // avoid scanning documents without source code available + doc->keepSourceAndAST(); + if (doc->utf8Source().isEmpty()) { + doc->releaseSourceAndAST(); + return; + } + + // keep source and AST alive if we want to scan for register calls + const bool scan = FindExportedCppTypes::maybeExportsTypes(doc); + if (!scan) + doc->releaseSourceAndAST(); + + // delegate actual queuing to the gui thread + QMetaObject::invokeMethod(this, "queueCppQmlTypeUpdate", + Q_ARG(CPlusPlus::Document::Ptr, doc), Q_ARG(bool, scan)); +} + +void ModelManagerInterface::queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc, bool scan) +{ + QPair<CPlusPlus::Document::Ptr, bool> prev = m_queuedCppDocuments.value(doc->fileName()); + if (prev.first && prev.second) + prev.first->releaseSourceAndAST(); + m_queuedCppDocuments.insert(doc->fileName(), qMakePair(doc, scan)); + m_updateCppQmlTypesTimer->start(); +} + +void ModelManagerInterface::startCppQmlTypeUpdate() +{ + // if a future is still running, delay + if (m_cppQmlTypesUpdater.isRunning()) { + m_updateCppQmlTypesTimer->start(); + return; + } + + CPlusPlus::CppModelManagerBase *cppModelManager = + CPlusPlus::CppModelManagerBase::instance(); + if (!cppModelManager) + return; + + m_cppQmlTypesUpdater = QtConcurrent::run( + &ModelManagerInterface::updateCppQmlTypes, + this, cppModelManager->snapshot(), m_queuedCppDocuments); + m_queuedCppDocuments.clear(); +} + +void ModelManagerInterface::asyncReset() +{ + m_asyncResetTimer->start(); +} + +void ModelManagerInterface::updateCppQmlTypes(QFutureInterface<void> &interface, + ModelManagerInterface *qmlModelManager, + CPlusPlus::Snapshot snapshot, + QHash<QString, QPair<CPlusPlus::Document::Ptr, bool> > documents) +{ + CppDataHash newData = qmlModelManager->cppData(); + + FindExportedCppTypes finder(snapshot); + + bool hasNewInfo = false; + typedef QPair<CPlusPlus::Document::Ptr, bool> DocScanPair; + foreach (const DocScanPair &pair, documents) { + if (interface.isCanceled()) + return; + + CPlusPlus::Document::Ptr doc = pair.first; + const bool scan = pair.second; + const QString fileName = doc->fileName(); + if (!scan) { + hasNewInfo = hasNewInfo || newData.remove(fileName) > 0; + continue; + } + + finder(doc); + + QList<LanguageUtils::FakeMetaObject::ConstPtr> exported = finder.exportedTypes(); + QHash<QString, QString> contextProperties = finder.contextProperties(); + if (exported.isEmpty() && contextProperties.isEmpty()) { + hasNewInfo = hasNewInfo || newData.remove(fileName) > 0; + } else { + CppData &data = newData[fileName]; + // currently we have no simple way to compare, so we assume the worse + hasNewInfo = true; + data.exportedTypes = exported; + data.contextProperties = contextProperties; + } + + doc->releaseSourceAndAST(); + } + + QMutexLocker locker(&qmlModelManager->m_cppDataMutex); + qmlModelManager->m_cppDataHash = newData; + if (hasNewInfo) + // one could get away with re-linking the cpp types... + QMetaObject::invokeMethod(qmlModelManager, "asyncReset"); +} + +ModelManagerInterface::CppDataHash ModelManagerInterface::cppData() const +{ + QMutexLocker locker(&m_cppDataMutex); + return m_cppDataHash; +} + +LibraryInfo ModelManagerInterface::builtins(const Document::Ptr &doc) const +{ + QList<ProjectExplorer::Project *> projects = m_fileToProject.values(doc->fileName()); + + ProjectExplorer::Project *project = 0; + foreach (ProjectExplorer::Project *p, projects) { + if (p) { + project = p; + break; + } + } + + if (!project) + return LibraryInfo(); + + QMutexLocker locker(&m_mutex); + ProjectInfo info = m_projects.value(project); + if (!info.isValid()) + return LibraryInfo(); + + return _validSnapshot.libraryInfo(info.qtImportsPath); +} + +ViewerContext ModelManagerInterface::completeVContext(const ViewerContext &vCtx, + const Document::Ptr &doc) const +{ + Q_UNUSED(doc); + ViewerContext res = vCtx; + switch (res.flags) { + case ViewerContext::Complete: + break; + case ViewerContext::AddQtPath: + case ViewerContext::AddAllPaths: + res.paths << importPaths(); + } + res.flags = ViewerContext::Complete; + return res; +} + +ViewerContext ModelManagerInterface::defaultVContext(bool autoComplete, const Document::Ptr &doc) const +{ + if (autoComplete) + return completeVContext(m_vContext, doc); + else + return m_vContext; +} + +void ModelManagerInterface::setDefaultVContext(const ViewerContext &vContext) +{ + m_vContext = vContext; +} + +void ModelManagerInterface::joinAllThreads() +{ + foreach (QFuture<void> future, m_synchronizer.futures()) + future.waitForFinished(); +} + +void ModelManagerInterface::resetCodeModel() +{ + QStringList documents; + + { + QMutexLocker locker(&m_mutex); + + // find all documents currently in the code model + foreach (Document::Ptr doc, _validSnapshot) + documents.append(doc->fileName()); + + // reset the snapshot + _validSnapshot = Snapshot(); + _newestSnapshot = Snapshot(); + } + + // start a reparse thread + updateSourceFiles(documents, false); +} + +} // namespace QmlJS diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h index 7d225f33a6..88463d36ca 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.h +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h @@ -31,21 +31,31 @@ #define QMLJSMODELMANAGERINTERFACE_H #include "qmljs_global.h" -#include "qmljsdocument.h" #include "qmljsbundle.h" #include "qmljsconstants.h" +#include "qmljsdocument.h" +#include "qmljsqrcparser.h" #include "qmljsviewercontext.h" + +#include <cplusplus/CppDocument.h> #include <utils/environment.h> +#include <QFuture> +#include <QFutureSynchronizer> +#include <QHash> +#include <QMultiHash> #include <QObject> -#include <QStringList> #include <QPointer> +#include <QStringList> +#include <QStringList> +#include <QTimer> namespace ProjectExplorer { class Project; } namespace QmlJS { class Snapshot; +class PluginDumper; class QMLJS_EXPORT ModelManagerInterface: public QObject { @@ -140,61 +150,130 @@ public: static Language::Enum guessLanguageOfFile(const QString &fileName); static QStringList globPatternsForLanguages(const QList<Language::Enum> languages); static ModelManagerInterface *instance(); - - virtual WorkingCopy workingCopy() const = 0; - - virtual QmlJS::Snapshot snapshot() const = 0; - virtual QmlJS::Snapshot newestSnapshot() const = 0; - - virtual void updateSourceFiles(const QStringList &files, - bool emitDocumentOnDiskChanged) = 0; - virtual void fileChangedOnDisk(const QString &path) = 0; - virtual void removeFiles(const QStringList &files) = 0; - virtual QStringList filesAtQrcPath(const QString &path, const QLocale *locale = 0, - ProjectExplorer::Project *project = 0, - QrcResourceSelector resources = AllQrcResources) = 0; - virtual QMap<QString,QStringList> filesInQrcPath(const QString &path, - const QLocale *locale = 0, - ProjectExplorer::Project *project = 0, - bool addDirs = false, - QrcResourceSelector resources = AllQrcResources) = 0; - - virtual QList<ProjectInfo> projectInfos() const = 0; - virtual ProjectInfo projectInfo(ProjectExplorer::Project *project) const = 0; - virtual void updateProjectInfo(const ProjectInfo &pinfo) = 0; - Q_SLOT virtual void removeProjectInfo(ProjectExplorer::Project *project) = 0; - virtual ProjectInfo projectInfoForPath(QString path) = 0; - - virtual QStringList importPaths() const = 0; - virtual QmlJS::QmlLanguageBundles activeBundles() const = 0; - virtual QmlJS::QmlLanguageBundles extendedBundles() const = 0; - - virtual void loadPluginTypes(const QString &libraryPath, const QString &importPath, - const QString &importUri, const QString &importVersion) = 0; - - virtual CppDataHash cppData() const = 0; - - virtual LibraryInfo builtins(const Document::Ptr &doc) const = 0; - + static void writeWarning(const QString &msg); + static WorkingCopy workingCopy(); + + QmlJS::Snapshot snapshot() const; + QmlJS::Snapshot newestSnapshot() const; + + void updateSourceFiles(const QStringList &files, + bool emitDocumentOnDiskChanged); + void fileChangedOnDisk(const QString &path); + void removeFiles(const QStringList &files); + QStringList filesAtQrcPath(const QString &path, const QLocale *locale = 0, + ProjectExplorer::Project *project = 0, + QrcResourceSelector resources = AllQrcResources); + QMap<QString,QStringList> filesInQrcPath(const QString &path, + const QLocale *locale = 0, + ProjectExplorer::Project *project = 0, + bool addDirs = false, + QrcResourceSelector resources = AllQrcResources); + + QList<ProjectInfo> projectInfos() const; + ProjectInfo projectInfo(ProjectExplorer::Project *project) const; + void updateProjectInfo(const ProjectInfo &pinfo, ProjectExplorer::Project *p); + + void updateDocument(QmlJS::Document::Ptr doc); + void updateLibraryInfo(const QString &path, const QmlJS::LibraryInfo &info); + void emitDocumentChangedOnDisk(QmlJS::Document::Ptr doc); + void updateQrcFile(const QString &path); + ProjectInfo projectInfoForPath(QString path); + + QStringList importPaths() const; + QmlJS::QmlLanguageBundles activeBundles() const; + QmlJS::QmlLanguageBundles extendedBundles() const; + + void loadPluginTypes(const QString &libraryPath, const QString &importPath, + const QString &importUri, const QString &importVersion); + + CppDataHash cppData() const; + LibraryInfo builtins(const Document::Ptr &doc) const; virtual ViewerContext completeVContext(const ViewerContext &vCtx, - const Document::Ptr &doc = Document::Ptr(0)) const = 0; + const Document::Ptr &doc = Document::Ptr(0)) const; virtual ViewerContext defaultVContext(bool autoComplete = true, - const Document::Ptr &doc = Document::Ptr(0)) const = 0; - virtual void setDefaultVContext(const ViewerContext &vContext) = 0; + const Document::Ptr &doc = Document::Ptr(0)) const; + virtual void setDefaultVContext(const ViewerContext &vContext); // Blocks until all parsing threads are done. Used for testing. - virtual void joinAllThreads() = 0; -public slots: - virtual void resetCodeModel() = 0; + void joinAllThreads(); + virtual ModelManagerInterface::ProjectInfo defaultProjectInfo() const; + +public slots: + virtual void resetCodeModel(); + void removeProjectInfo(ProjectExplorer::Project *project); signals: void documentUpdated(QmlJS::Document::Ptr doc); void documentChangedOnDisk(QmlJS::Document::Ptr doc); void aboutToRemoveFiles(const QStringList &files); void libraryInfoUpdated(const QString &path, const QmlJS::LibraryInfo &info); void projectInfoUpdated(const ProjectInfo &pinfo); + void projectPathChanged(const QString &projectPath); +protected slots: + void maybeQueueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc); + void queueCppQmlTypeUpdate(const CPlusPlus::Document::Ptr &doc, bool scan); + void asyncReset(); + virtual void startCppQmlTypeUpdate(); protected: virtual QHash<QString,Language::Enum> languageForSuffix() const; + virtual void writeMessageInternal(const QString &msg) const; + virtual WorkingCopy workingCopyInternal() const; + virtual void addTaskInternal(QFuture<void> result, const QString &msg, const char *taskId) const; + + QFuture<void> refreshSourceFiles(const QStringList &sourceFiles, + bool emitDocumentOnDiskChanged); + + static void parseLoop(QSet<QString> &scannedPaths, QSet<QString> &newLibraries, + WorkingCopy workingCopyInternal, QStringList files, ModelManagerInterface *modelManager, + QmlJS::Language::Enum mainLanguage, bool emitDocChangedOnDisk, + Utils::function<bool (qreal)> reportProgress); + static void parse(QFutureInterface<void> &future, + WorkingCopy workingCopyInternal, + QStringList files, + ModelManagerInterface *modelManager, + QmlJS::Language::Enum mainLanguage, + bool emitDocChangedOnDisk); + static void importScan(QFutureInterface<void> &future, + WorkingCopy workingCopyInternal, + QStringList paths, + ModelManagerInterface *modelManager, + QmlJS::Language::Enum mainLanguage, + bool emitDocChangedOnDisk); + static void updateCppQmlTypes(QFutureInterface<void> &interface, + ModelManagerInterface *qmlModelManager, + CPlusPlus::Snapshot snapshot, + QHash<QString, QPair<CPlusPlus::Document::Ptr, bool> > documents); + + void updateImportPaths(); + void loadQmlTypeDescriptionsInternal(const QString &path); + + mutable QMutex m_mutex; + QmlJS::Snapshot _validSnapshot; + QmlJS::Snapshot _newestSnapshot; + QStringList m_allImportPaths; + QStringList m_defaultImportPaths; + QmlJS::QmlLanguageBundles m_activeBundles; + QmlJS::QmlLanguageBundles m_extendedBundles; + QmlJS::ViewerContext m_vContext; + bool m_shouldScanImports; + QSet<QString> m_scannedPaths; + + QTimer *m_updateCppQmlTypesTimer; + QTimer *m_asyncResetTimer; + QHash<QString, QPair<CPlusPlus::Document::Ptr, bool> > m_queuedCppDocuments; + QFuture<void> m_cppQmlTypesUpdater; + QrcCache m_qrcCache; + + CppDataHash m_cppDataHash; + mutable QMutex m_cppDataMutex; + + // project integration + QMap<ProjectExplorer::Project *, ProjectInfo> m_projects; + QMultiHash<QString, ProjectExplorer::Project *> m_fileToProject; + + PluginDumper *m_pluginDumper; + + QFutureSynchronizer<void> m_synchronizer; }; } // namespace QmlJS diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp new file mode 100644 index 0000000000..12df8f5d00 --- /dev/null +++ b/src/libs/qmljs/qmljsplugindumper.cpp @@ -0,0 +1,589 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "qmljsplugindumper.h" +#include "qmljsmodelmanagerinterface.h" + +#include <qmljs/qmljsinterpreter.h> +//#include <projectexplorer/session.h> +//#include <coreplugin/messagemanager.h> +#include <utils/filesystemwatcher.h> +#include <utils/fileutils.h> + +#include <QDir> + +using namespace LanguageUtils; +using namespace QmlJS; + +PluginDumper::PluginDumper(ModelManagerInterface *modelManager) + : QObject(modelManager) + , m_modelManager(modelManager) + , m_pluginWatcher(0) +{ + qRegisterMetaType<QmlJS::ModelManagerInterface::ProjectInfo>("QmlJS::ModelManagerInterface::ProjectInfo"); +} + +Utils::FileSystemWatcher *PluginDumper::pluginWatcher() +{ + if (!m_pluginWatcher) { + m_pluginWatcher = new Utils::FileSystemWatcher(this); + m_pluginWatcher->setObjectName(QLatin1String("PluginDumperWatcher")); + connect(m_pluginWatcher, SIGNAL(fileChanged(QString)), + this, SLOT(pluginChanged(QString))); + } + return m_pluginWatcher; +} + +void PluginDumper::loadBuiltinTypes(const QmlJS::ModelManagerInterface::ProjectInfo &info) +{ + // move to the owning thread + metaObject()->invokeMethod(this, "onLoadBuiltinTypes", + Q_ARG(QmlJS::ModelManagerInterface::ProjectInfo, info)); +} + +void PluginDumper::loadPluginTypes(const QString &libraryPath, const QString &importPath, const QString &importUri, const QString &importVersion) +{ + // move to the owning thread + metaObject()->invokeMethod(this, "onLoadPluginTypes", + Q_ARG(QString, libraryPath), + Q_ARG(QString, importPath), + Q_ARG(QString, importUri), + Q_ARG(QString, importVersion)); +} + +void PluginDumper::scheduleRedumpPlugins() +{ + // move to the owning thread + metaObject()->invokeMethod(this, "dumpAllPlugins", Qt::QueuedConnection); +} + +void PluginDumper::scheduleMaybeRedumpBuiltins(const QmlJS::ModelManagerInterface::ProjectInfo &info) +{ + // move to the owning thread + metaObject()->invokeMethod(this, "dumpBuiltins", Qt::QueuedConnection, + Q_ARG(QmlJS::ModelManagerInterface::ProjectInfo, info)); +} + +void PluginDumper::onLoadBuiltinTypes(const QmlJS::ModelManagerInterface::ProjectInfo &info, bool force) +{ + if (info.qmlDumpPath.isEmpty() || info.qtImportsPath.isEmpty()) + return; + + const QString importsPath = QDir::cleanPath(info.qtImportsPath); + if (m_runningQmldumps.values().contains(importsPath)) + return; + + LibraryInfo builtinInfo; + if (!force) { + const Snapshot snapshot = m_modelManager->snapshot(); + builtinInfo = snapshot.libraryInfo(info.qtImportsPath); + if (builtinInfo.isValid()) + return; + } + builtinInfo = LibraryInfo(LibraryInfo::Found); + m_modelManager->updateLibraryInfo(info.qtImportsPath, builtinInfo); + + // prefer QTDIR/imports/builtins.qmltypes if available + const QString builtinQmltypesPath = info.qtImportsPath + QLatin1String("/builtins.qmltypes"); + if (QFile::exists(builtinQmltypesPath)) { + loadQmltypesFile(QStringList(builtinQmltypesPath), info.qtImportsPath, builtinInfo); + return; + } + // QTDIR/imports/QtQuick1/builtins.qmltypes was used in developer builds of 5.0.0, 5.0.1 + const QString builtinQmltypesPath2 = info.qtImportsPath + + QLatin1String("/QtQuick1/builtins.qmltypes"); + if (QFile::exists(builtinQmltypesPath2)) { + loadQmltypesFile(QStringList(builtinQmltypesPath2), info.qtImportsPath, builtinInfo); + return; + } + + // run qmldump + QProcess *process = new QProcess(this); + process->setEnvironment(info.qmlDumpEnvironment.toStringList()); + connect(process, SIGNAL(finished(int)), SLOT(qmlPluginTypeDumpDone(int))); + connect(process, SIGNAL(error(QProcess::ProcessError)), SLOT(qmlPluginTypeDumpError(QProcess::ProcessError))); + QStringList args(QLatin1String("--builtins")); + process->start(info.qmlDumpPath, args); + m_runningQmldumps.insert(process, info.qtImportsPath); + m_qtToInfo.insert(info.qtImportsPath, info); +} + +static QString makeAbsolute(const QString &path, const QString &base) +{ + if (QFileInfo(path).isAbsolute()) + return path; + return QString::fromLatin1("%1%2%3").arg(base, QDir::separator(), path); +} + +void PluginDumper::onLoadPluginTypes(const QString &libraryPath, const QString &importPath, const QString &importUri, const QString &importVersion) +{ + const QString canonicalLibraryPath = QDir::cleanPath(libraryPath); + if (m_runningQmldumps.values().contains(canonicalLibraryPath)) + return; + const Snapshot snapshot = m_modelManager->snapshot(); + const LibraryInfo libraryInfo = snapshot.libraryInfo(canonicalLibraryPath); + if (libraryInfo.pluginTypeInfoStatus() != LibraryInfo::NoTypeInfo) + return; + + // avoid inserting the same plugin twice + int index; + for (index = 0; index < m_plugins.size(); ++index) { + if (m_plugins.at(index).qmldirPath == libraryPath) + break; + } + if (index == m_plugins.size()) + m_plugins.append(Plugin()); + + Plugin &plugin = m_plugins[index]; + plugin.qmldirPath = canonicalLibraryPath; + plugin.importPath = importPath; + plugin.importUri = importUri; + plugin.importVersion = importVersion; + + // add default qmltypes file if it exists + const QLatin1String defaultQmltypesFileName("plugins.qmltypes"); + const QString defaultQmltypesPath = makeAbsolute(defaultQmltypesFileName, canonicalLibraryPath); + if (!plugin.typeInfoPaths.contains(defaultQmltypesPath) && QFile::exists(defaultQmltypesPath)) + plugin.typeInfoPaths += defaultQmltypesPath; + + // add typeinfo files listed in qmldir + foreach (const QmlDirParser::TypeInfo &typeInfo, libraryInfo.typeInfos()) { + QString pathNow = makeAbsolute(typeInfo.fileName, canonicalLibraryPath); + if (!plugin.typeInfoPaths.contains(pathNow) && QFile::exists(pathNow)) + plugin.typeInfoPaths += pathNow; + } + + // watch plugin libraries + foreach (const QmlDirParser::Plugin &plugin, snapshot.libraryInfo(canonicalLibraryPath).plugins()) { + const QString pluginLibrary = resolvePlugin(canonicalLibraryPath, plugin.path, plugin.name); + if (!pluginLibrary.isEmpty()) { + if (!pluginWatcher()->watchesFile(pluginLibrary)) + pluginWatcher()->addFile(pluginLibrary, Utils::FileSystemWatcher::WatchModifiedDate); + m_libraryToPluginIndex.insert(pluginLibrary, index); + } + } + + // watch library qmltypes file + if (!plugin.typeInfoPaths.isEmpty()) { + foreach (const QString &path, plugin.typeInfoPaths) { + if (!QFile::exists(path)) + continue; + if (!pluginWatcher()->watchesFile(path)) + pluginWatcher()->addFile(path, Utils::FileSystemWatcher::WatchModifiedDate); + m_libraryToPluginIndex.insert(path, index); + } + } + + dump(plugin); +} + +void PluginDumper::dumpBuiltins(const QmlJS::ModelManagerInterface::ProjectInfo &info) +{ + // if the builtin types were generated with a different qmldump, regenerate! + if (m_qtToInfo.contains(info.qtImportsPath)) { + QmlJS::ModelManagerInterface::ProjectInfo oldInfo = m_qtToInfo.value(info.qtImportsPath); + if (oldInfo.qmlDumpPath != info.qmlDumpPath + || oldInfo.qmlDumpEnvironment != info.qmlDumpEnvironment) { + m_qtToInfo.remove(info.qtImportsPath); + onLoadBuiltinTypes(info, true); + } + } +} + +void PluginDumper::dumpAllPlugins() +{ + foreach (const Plugin &plugin, m_plugins) { + dump(plugin); + } +} + +static QString noTypeinfoError(const QString &libraryPath) +{ + return PluginDumper::tr("QML module does not contain information about components contained in plugins\n\n" + "Module path: %1\n" + "See \"Using QML Modules with Plugins\" in the documentation.").arg( + libraryPath); +} + +static QString qmldumpErrorMessage(const QString &libraryPath, const QString &error) +{ + return noTypeinfoError(libraryPath) + QLatin1String("\n\n") + + PluginDumper::tr("Automatic type dump of QML module failed.\nErrors:\n%1"). + arg(error) + QLatin1Char('\n'); +} + +static QString qmldumpFailedMessage(const QString &libraryPath, const QString &error) +{ + QString firstLines = + QStringList(error.split(QLatin1Char('\n')).mid(0, 10)).join(QLatin1String("\n")); + return noTypeinfoError(libraryPath) + QLatin1String("\n\n") + + PluginDumper::tr("Automatic type dump of QML module failed.\n" + "First 10 lines or errors:\n" + "\n" + "%1" + "\n" + "Check 'General Messages' output pane for details." + ).arg(firstLines); +} + +static void printParseWarnings(const QString &libraryPath, const QString &warning) +{ + ModelManagerInterface::writeWarning( + PluginDumper::tr("Warnings while parsing qmltypes information of %1:\n" + "%2").arg(libraryPath, warning)); +} + +static QString qmlPluginDumpErrorMessage(QProcess *process) +{ + QString errorMessage; +#if QT_VERSION >= 0x050000 + const QString binary = QDir::toNativeSeparators(process->program()); +#else + const QString binary = QLatin1String("qmlplugindump"); +#endif + switch (process->error()) { + case QProcess::FailedToStart: + errorMessage = PluginDumper::tr("\"%1\" failed to start: %2").arg(binary, process->errorString()); + break; + case QProcess::Crashed: + errorMessage = PluginDumper::tr("\"%1\" crashed.").arg(binary); + break; + case QProcess::Timedout: + errorMessage = PluginDumper::tr("\"%1\" timed out.").arg(binary); + break; + case QProcess::ReadError: + case QProcess::WriteError: + errorMessage = PluginDumper::tr("I/O error running \"%1\".").arg(binary); + break; + case QProcess::UnknownError: + if (process->exitCode()) + errorMessage = PluginDumper::tr("\"%1\" returned exit code %2.").arg(binary).arg(process->exitCode()); + break; + } +#if QT_VERSION >= 0x050000 + errorMessage += QLatin1Char('\n') + PluginDumper::tr("Arguments: %1").arg(process->arguments().join(QLatin1Char(' '))); +#endif + if (process->error() != QProcess::FailedToStart) { + const QString stdErr = QString::fromLocal8Bit(process->readAllStandardError()); + if (!stdErr.isEmpty()) { + errorMessage += QLatin1Char('\n'); + errorMessage += stdErr; + } + } + return errorMessage; +} + +void PluginDumper::qmlPluginTypeDumpDone(int exitCode) +{ + QProcess *process = qobject_cast<QProcess *>(sender()); + if (!process) + return; + process->deleteLater(); + + const QString libraryPath = m_runningQmldumps.take(process); + if (libraryPath.isEmpty()) + return; + const Snapshot snapshot = m_modelManager->snapshot(); + LibraryInfo libraryInfo = snapshot.libraryInfo(libraryPath); + + if (exitCode != 0) { + const QString errorMessages = qmlPluginDumpErrorMessage(process); + ModelManagerInterface::writeWarning(qmldumpErrorMessage(libraryPath, errorMessages)); + libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, qmldumpFailedMessage(libraryPath, errorMessages)); + } + + const QByteArray output = process->readAllStandardOutput(); + QString error; + QString warning; + CppQmlTypesLoader::BuiltinObjects objectsList; + QList<ModuleApiInfo> moduleApis; + CppQmlTypesLoader::parseQmlTypeDescriptions(output, &objectsList, &moduleApis, &error, &warning, + QLatin1String("<dump of ") + libraryPath + QLatin1String(">")); + if (exitCode == 0) { + if (!error.isEmpty()) { + libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, + qmldumpErrorMessage(libraryPath, error)); + printParseWarnings(libraryPath, libraryInfo.pluginTypeInfoError()); + } else { + libraryInfo.setMetaObjects(objectsList.values()); + libraryInfo.setModuleApis(moduleApis); + libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpDone); + } + + if (!warning.isEmpty()) + printParseWarnings(libraryPath, warning); + } + libraryInfo.updateFingerprint(); + + m_modelManager->updateLibraryInfo(libraryPath, libraryInfo); +} + +void PluginDumper::qmlPluginTypeDumpError(QProcess::ProcessError) +{ + QProcess *process = qobject_cast<QProcess *>(sender()); + if (!process) + return; + process->deleteLater(); + + const QString libraryPath = m_runningQmldumps.take(process); + if (libraryPath.isEmpty()) + return; + + const QString errorMessages = qmlPluginDumpErrorMessage(process); + ModelManagerInterface::writeWarning(qmldumpErrorMessage(libraryPath, errorMessages)); + if (!libraryPath.isEmpty()) { + const Snapshot snapshot = m_modelManager->snapshot(); + LibraryInfo libraryInfo = snapshot.libraryInfo(libraryPath); + libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, qmldumpFailedMessage(libraryPath, errorMessages)); + libraryInfo.updateFingerprint(); + m_modelManager->updateLibraryInfo(libraryPath, libraryInfo); + } +} + +void PluginDumper::pluginChanged(const QString &pluginLibrary) +{ + const int pluginIndex = m_libraryToPluginIndex.value(pluginLibrary, -1); + if (pluginIndex == -1) + return; + + const Plugin &plugin = m_plugins.at(pluginIndex); + dump(plugin); +} + +void PluginDumper::loadQmltypesFile(const QStringList &qmltypesFilePaths, + const QString &libraryPath, + QmlJS::LibraryInfo libraryInfo) +{ + QStringList errors; + QStringList warnings; + QList<FakeMetaObject::ConstPtr> objects; + QList<ModuleApiInfo> moduleApis; + + foreach (const QString &qmltypesFilePath, qmltypesFilePaths) { + Utils::FileReader reader; + if (!reader.fetch(qmltypesFilePath, QFile::Text)) { + errors += reader.errorString(); + continue; + } + + QString error; + QString warning; + CppQmlTypesLoader::BuiltinObjects newObjects; + QList<ModuleApiInfo> newModuleApis; + CppQmlTypesLoader::parseQmlTypeDescriptions(reader.data(), &newObjects, &newModuleApis, &error, &warning, qmltypesFilePath); + if (!error.isEmpty()) { + errors += tr("Failed to parse '%1'.\nError: %2").arg(qmltypesFilePath, error); + } else { + objects += newObjects.values(); + moduleApis += newModuleApis; + } + if (!warning.isEmpty()) + warnings += warning; + } + + libraryInfo.setMetaObjects(objects); + libraryInfo.setModuleApis(moduleApis); + if (errors.isEmpty()) { + libraryInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileDone); + } else { + printParseWarnings(libraryPath, errors.join(QLatin1String("\n"))); + errors.prepend(tr("Errors while reading typeinfo files:")); + libraryInfo.setPluginTypeInfoStatus(LibraryInfo::TypeInfoFileError, errors.join(QLatin1String("\n"))); + } + + if (!warnings.isEmpty()) + printParseWarnings(libraryPath, warnings.join(QLatin1String("\n"))); + + libraryInfo.updateFingerprint(); + m_modelManager->updateLibraryInfo(libraryPath, libraryInfo); +} + +void PluginDumper::dump(const Plugin &plugin) +{ + // if there are type infos, don't dump! + if (!plugin.typeInfoPaths.isEmpty()) { + const Snapshot snapshot = m_modelManager->snapshot(); + LibraryInfo libraryInfo = snapshot.libraryInfo(plugin.qmldirPath); + if (!libraryInfo.isValid()) + return; + + loadQmltypesFile(plugin.typeInfoPaths, plugin.qmldirPath, libraryInfo); + return; + } + + ModelManagerInterface::ProjectInfo info = m_modelManager->defaultProjectInfo(); + + if (!info.tryQmlDump || info.qmlDumpPath.isEmpty()) { + const Snapshot snapshot = m_modelManager->snapshot(); + LibraryInfo libraryInfo = snapshot.libraryInfo(plugin.qmldirPath); + if (!libraryInfo.isValid()) + return; + + QString errorMessage; + if (!info.tryQmlDump) { + errorMessage = noTypeinfoError(plugin.qmldirPath); + } else { + errorMessage = qmldumpErrorMessage(plugin.qmldirPath, + tr("Could not locate the helper application for dumping type information from C++ plugins.\n" + "Please build the qmldump application on the Qt version options page.")); + } + + libraryInfo.setPluginTypeInfoStatus(LibraryInfo::DumpError, errorMessage); + libraryInfo.updateFingerprint(); + m_modelManager->updateLibraryInfo(plugin.qmldirPath, libraryInfo); + return; + } + + QProcess *process = new QProcess(this); + process->setEnvironment(info.qmlDumpEnvironment.toStringList()); + connect(process, SIGNAL(finished(int)), SLOT(qmlPluginTypeDumpDone(int))); + connect(process, SIGNAL(error(QProcess::ProcessError)), SLOT(qmlPluginTypeDumpError(QProcess::ProcessError))); + QStringList args; + if (plugin.importUri.isEmpty()) { + args << QLatin1String("--path"); + args << plugin.importPath; + if (ComponentVersion(plugin.importVersion).isValid()) + args << plugin.importVersion; + } else { + if (info.qmlDumpHasRelocatableFlag) + args << QLatin1String("-relocatable"); + args << plugin.importUri; + args << plugin.importVersion; + args << plugin.importPath; + } + process->start(info.qmlDumpPath, args); + m_runningQmldumps.insert(process, plugin.qmldirPath); +} + +/*! + 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. + + Adapted from QDeclarativeImportDatabase::resolvePlugin. +*/ +QString PluginDumper::resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath, + const QString &baseName, const QStringList &suffixes, + const QString &prefix) +{ + QStringList searchPaths; + searchPaths.append(QLatin1String(".")); + + bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath); + if (!qmldirPluginPathIsRelative) + searchPaths.prepend(qmldirPluginPath); + + foreach (const QString &pluginPath, searchPaths) { + + QString resolvedPath; + + if (pluginPath == QLatin1String(".")) { + if (qmldirPluginPathIsRelative) + resolvedPath = qmldirPath.absoluteFilePath(qmldirPluginPath); + else + resolvedPath = qmldirPath.absolutePath(); + } else { + resolvedPath = pluginPath; + } + + QDir dir(resolvedPath); + foreach (const QString &suffix, suffixes) { + QString pluginFileName = prefix; + + pluginFileName += baseName; + pluginFileName += suffix; + + QFileInfo fileInfo(dir, pluginFileName); + + if (fileInfo.exists()) + return fileInfo.absoluteFilePath(); + } + } + + return QString(); +} + +/*! + Returns the result of the merge of \a baseName with \a dir and the platform suffix. + + Adapted from QDeclarativeImportDatabase::resolvePlugin. + + \table + \header \li Platform \li Valid suffixes + \row \li Windows \li \c .dll + \row \li Unix/Linux \li \c .so + \row \li AIX \li \c .a + \row \li HP-UX \li \c .sl, \c .so (HP-UXi) + \row \li Mac OS X \li \c .dylib, \c .bundle, \c .so + \endtable + + Version number on unix are ignored. +*/ +QString PluginDumper::resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath, + const QString &baseName) +{ +#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) + return resolvePlugin(qmldirPath, qmldirPluginPath, baseName, + QStringList() + << QLatin1String("d.dll") // try a qmake-style debug build first + << QLatin1String(".dll")); +#elif defined(Q_OS_DARWIN) + return resolvePlugin(qmldirPath, qmldirPluginPath, baseName, + QStringList() + << QLatin1String("_debug.dylib") // try a qmake-style debug build first + << QLatin1String(".dylib") + << 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(qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib")); +#endif +} diff --git a/src/libs/qmljs/qmljsplugindumper.h b/src/libs/qmljs/qmljsplugindumper.h new file mode 100644 index 0000000000..8c4a95c42e --- /dev/null +++ b/src/libs/qmljs/qmljsplugindumper.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLJSPLUGINDUMPER_H +#define QMLJSPLUGINDUMPER_H + +#include <qmljs/qmljsmodelmanagerinterface.h> + +#include <QObject> +#include <QHash> +#include <QProcess> + +QT_BEGIN_NAMESPACE +class QDir; +QT_END_NAMESPACE + +namespace Utils { class FileSystemWatcher; } + +namespace QmlJS { + +class PluginDumper : public QObject +{ + Q_OBJECT +public: + explicit PluginDumper(ModelManagerInterface *modelManager); + +public: + void loadBuiltinTypes(const QmlJS::ModelManagerInterface::ProjectInfo &info); + void loadPluginTypes(const QString &libraryPath, const QString &importPath, + const QString &importUri, const QString &importVersion); + void scheduleRedumpPlugins(); + void scheduleMaybeRedumpBuiltins(const QmlJS::ModelManagerInterface::ProjectInfo &info); + +private slots: + void onLoadBuiltinTypes(const QmlJS::ModelManagerInterface::ProjectInfo &info, + bool force = false); + void onLoadPluginTypes(const QString &libraryPath, const QString &importPath, + const QString &importUri, const QString &importVersion); + void dumpBuiltins(const QmlJS::ModelManagerInterface::ProjectInfo &info); + void dumpAllPlugins(); + void qmlPluginTypeDumpDone(int exitCode); + void qmlPluginTypeDumpError(QProcess::ProcessError error); + void pluginChanged(const QString &pluginLibrary); + +private: + class Plugin { + public: + QString qmldirPath; + QString importPath; + QString importUri; + QString importVersion; + QStringList typeInfoPaths; + }; + + void dump(const Plugin &plugin); + void loadQmltypesFile(const QStringList &qmltypesFilePaths, + const QString &libraryPath, + QmlJS::LibraryInfo libraryInfo); + QString resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath, + const QString &baseName); + QString resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath, + const QString &baseName, const QStringList &suffixes, + const QString &prefix = QString()); + +private: + Utils::FileSystemWatcher *pluginWatcher(); + + ModelManagerInterface *m_modelManager; + Utils::FileSystemWatcher *m_pluginWatcher; + QHash<QProcess *, QString> m_runningQmldumps; + QList<Plugin> m_plugins; + QHash<QString, int> m_libraryToPluginIndex; + QHash<QString, QmlJS::ModelManagerInterface::ProjectInfo> m_qtToInfo; +}; + +} // namespace QmlJS + +#endif // QMLJSPLUGINDUMPER_H |