diff --git a/plugins/clang/duchain/builder.cpp b/plugins/clang/duchain/builder.cpp index 91c3de2d56..8f8ade95fc 100644 --- a/plugins/clang/duchain/builder.cpp +++ b/plugins/clang/duchain/builder.cpp @@ -1,1575 +1,1576 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * Copyright 2015 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "builder.h" #include "util/clangdebug.h" #include "templatehelpers.h" #include "cursorkindtraits.h" #include "clangducontext.h" #include "macrodefinition.h" #include "types/classspecializationtype.h" #include "util/clangutils.h" #include "util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// Turn on for debugging the declaration building #define IF_DEBUG(x) namespace { // TODO: this is ugly, can we find a better alternative? bool jsonTestRun() { static bool runningTest = qEnvironmentVariableIsSet("KDEV_CLANG_JSON_TEST_RUN"); return runningTest; } //BEGIN helpers // HACK: current alias type template machinery is badly broken wrt spelling // location, work around this by adjusting all references to point to child // type alias node with proper location // TODO: investigate upstream implementation of CXCursor_TypeAliasTemplateDecl CXCursor findEmbeddedTypeAlias(CXCursor aliasTemplate) { auto result = clang_getNullCursor(); clang_visitChildren(aliasTemplate, [] (CXCursor cursor, CXCursor, CXClientData data) { if (clang_getCursorKind(cursor) == CXCursor_TypeAliasDecl) { auto res = reinterpret_cast(data); *res = cursor; return CXChildVisit_Break; } return CXChildVisit_Continue; }, &result); return result; } /** * Find the cursor that cursor @p cursor references * * First tries to get the referenced cursor via clang_getCursorReferenced, * and if that fails, tries to get them via clang_getOverloadedDecl * (which returns the referenced cursor for CXCursor_OverloadedDeclRef, for example) * * @return Valid cursor on success, else null cursor */ CXCursor referencedCursor(CXCursor cursor) { auto referenced = clang_getCursorReferenced(cursor); // HACK: see notes at getEmbeddedTypeAlias() if (clang_getCursorKind(referenced) == CXCursor_TypeAliasTemplateDecl) { return findEmbeddedTypeAlias(referenced); } if (!clang_equalCursors(cursor, referenced)) { return referenced; } // get the first result for now referenced = clang_getOverloadedDecl(cursor, 0); if (!clang_Cursor_isNull(referenced)) { return referenced; } return clang_getNullCursor(); } Identifier makeId(CXCursor cursor) { if (CursorKindTraits::isClassTemplate(cursor.kind)) { // TODO: how to handle functions here? We don't want to add the "real" function arguments here // and there does not seem to be an API to get the template arguments for non-specializations easily // NOTE: using the QString overload of the Identifier ctor here, so that the template name gets parsed return Identifier(ClangString(clang_getCursorDisplayName(cursor)).toString()); } return Identifier(ClangString(clang_getCursorSpelling(cursor)).toIndexed()); } QByteArray makeComment(CXComment comment) { if (Q_UNLIKELY(jsonTestRun())) { auto kind = clang_Comment_getKind(comment); if (kind == CXComment_Text) return ClangString(clang_TextComment_getText(comment)).toByteArray(); QByteArray text; int numChildren = clang_Comment_getNumChildren(comment); for (int i = 0; i < numChildren; ++i) text += makeComment(clang_Comment_getChild(comment, i)); return text; } return ClangString(clang_FullComment_getAsHTML(comment)).toByteArray(); } AbstractType* createDelayedType(CXType type) { auto t = new DelayedType; QString typeName = ClangString(clang_getTypeSpelling(type)).toString(); typeName.remove(QStringLiteral("const ")); typeName.remove(QStringLiteral("volatile ")); t->setIdentifier(IndexedTypeIdentifier(typeName)); return t; } void contextImportDecl(DUContext* context, const DeclarationPointer& decl) { auto top = context->topContext(); if (auto import = decl->logicalInternalContext(top)) { context->addImportedParentContext(import); context->topContext()->updateImportsCache(); } } //END helpers CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data); //BEGIN IdType template struct IdType; template struct IdType::type> { typedef StructureType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef EnumerationType Type; }; template struct IdType::type> { typedef EnumeratorType Type; }; //END IdType //BEGIN DeclType template struct DeclType; template struct DeclType::type> { typedef Declaration Type; }; template struct DeclType::type> { typedef MacroDefinition Type; }; template struct DeclType::type> { typedef ForwardDeclaration Type; }; template struct DeclType::type> { typedef ClassDeclaration Type; }; template struct DeclType::type> { typedef ClassFunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDefinition Type; }; template struct DeclType::type> { typedef NamespaceAliasDeclaration Type; }; template struct DeclType::type> { typedef ClassMemberDeclaration Type; }; //END DeclType //BEGIN CurrentContext struct CurrentContext { CurrentContext(DUContext* context, QSet keepAliveContexts) : context(context) , keepAliveContexts(keepAliveContexts) { DUChainReadLocker lock; previousChildContexts = context->childContexts(); previousChildDeclarations = context->localDeclarations(); } ~CurrentContext() { DUChainWriteLocker lock; foreach (auto childContext, previousChildContexts) { if (!keepAliveContexts.contains(childContext)) { delete childContext; } } qDeleteAll(previousChildDeclarations); if (resortChildContexts) { context->resortChildContexts(); } if (resortLocalDeclarations) { context->resortLocalDeclarations(); } } DUContext* context; // when updating, this contains child contexts of the current parent context QVector previousChildContexts; // when updating, this contains contexts that must not be deleted QSet keepAliveContexts; // when updating, this contains child declarations of the current parent context QVector previousChildDeclarations; bool resortChildContexts = false; bool resortLocalDeclarations = false; }; //END CurrentContext //BEGIN Visitor struct Visitor { explicit Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update); AbstractType *makeType(CXType type, CXCursor parent); AbstractType::Ptr makeAbsType(CXType type, CXCursor parent) { return AbstractType::Ptr(makeType(type, parent)); } //BEGIN dispatch* template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); CXChildVisitResult dispatchTypeAliasTemplate(CXCursor cursor, CXCursor parent) { return CursorKindTraits::isClass(clang_getCursorKind(parent)) ? buildTypeAliasTemplateDecl(cursor) : buildTypeAliasTemplateDecl(cursor); } template AbstractType *dispatchType(CXType type, CXCursor cursor) { IF_DEBUG(clangDebug() << "TK:" << type.kind;) auto kdevType = createType(type, cursor); if (kdevType) { setTypeModifiers(type, kdevType); } return kdevType; } //BEGIN dispatch* //BEGIN build* template CXChildVisitResult buildDeclaration(CXCursor cursor); template CXChildVisitResult buildTypeAliasTemplateDecl(CXCursor cursor); CXChildVisitResult buildUse(CXCursor cursor); CXChildVisitResult buildMacroExpansion(CXCursor cursor); CXChildVisitResult buildCompoundStatement(CXCursor cursor); CXChildVisitResult buildCXXBaseSpecifier(CXCursor cursor); CXChildVisitResult buildParmDecl(CXCursor cursor); //END build* //BEGIN create* template DeclType* createDeclarationCommon(CXCursor cursor, const Identifier& id) { auto range = ClangHelpers::cursorSpellingNameRange(cursor, id); if (id.isEmpty()) { // This is either an anonymous function parameter e.g.: void f(int); // Or anonymous struct/class/union e.g.: struct {} anonymous; // Set empty range for it range.end = range.start; } // check if cursor is inside a macro expansion auto clangRange = clang_Cursor_getSpellingNameRange(cursor, 0, 0); unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(clangRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (m_macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); // Set empty ranges for declarations inside macro expansion if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } if (m_update) { const IndexedIdentifier indexedId(id); DUChainWriteLocker lock; auto it = m_parentContext->previousChildDeclarations.begin(); while (it != m_parentContext->previousChildDeclarations.end()) { auto decl = dynamic_cast(*it); if (decl && decl->indexedIdentifier() == indexedId) { decl->setRange(range); m_parentContext->resortLocalDeclarations = true; setDeclData(cursor, decl); m_cursorToDeclarationCache[cursor] = decl; m_parentContext->previousChildDeclarations.erase(it); return decl; } ++it; } } auto decl = new DeclType(range, nullptr); decl->setIdentifier(id); m_cursorToDeclarationCache[cursor] = decl; setDeclData(cursor, decl); { DUChainWriteLocker lock; decl->setContext(m_parentContext->context); } return decl; } template Declaration* createDeclaration(CXCursor cursor, const Identifier& id, DUContext *context) { auto decl = createDeclarationCommon(cursor, id); auto type = createType(cursor); DUChainWriteLocker lock; if (context) decl->setInternalContext(context); setDeclType(decl, type); setDeclInCtxtData(cursor, decl); return decl; } template DUContext* createContext(CXCursor cursor, const QualifiedIdentifier& scopeId = {}) { // wtf: why is the DUContext API requesting a QID when it needs a plain Id?! // see: testNamespace auto range = ClangRange(clang_getCursorExtent(cursor)).toRangeInRevision(); DUChainWriteLocker lock; if (m_update) { const IndexedQualifiedIdentifier indexedScopeId(scopeId); auto it = m_parentContext->previousChildContexts.begin(); while (it != m_parentContext->previousChildContexts.end()) { auto ctx = *it; if (ctx->type() == Type && ctx->indexedLocalScopeIdentifier() == indexedScopeId) { ctx->setRange(range); m_parentContext->resortChildContexts = true; m_parentContext->previousChildContexts.erase(it); return ctx; } ++it; } } //TODO: (..type, id..) constructor for DUContext? auto context = new ClangNormalDUContext(range, m_parentContext->context); context->setType(Type); context->setLocalScopeIdentifier(scopeId); if (Type == DUContext::Other || Type == DUContext::Function) context->setInSymbolTable(false); if (CK == CXCursor_CXXMethod) { CXCursor semParent = clang_getCursorSemanticParent(cursor); // only import the semantic parent if it differs from the lexical parent if (!clang_Cursor_isNull(semParent) && !clang_equalCursors(semParent, clang_getCursorLexicalParent(cursor))) { auto semParentDecl = findDeclaration(semParent); if (semParentDecl) { contextImportDecl(context, semParentDecl); } } } return context; } template = dummy> AbstractType *createType(CXType, CXCursor) { // TODO: would be nice to instantiate a ConstantIntegralType here and set a value if possible // but unfortunately libclang doesn't offer API to that // also see http://marc.info/?l=cfe-commits&m=131609142917881&w=2 return new IntegralType(CursorKindTraits::integralType(TK)); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ptr = new PointerType; ptr->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ptr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto arr = new ArrayType; arr->setDimension((TK == CXType_IncompleteArray || TK == CXType_VariableArray || TK == CXType_DependentSizedArray) ? 0 : clang_getArraySize(type)); arr->setElementType(makeAbsType(clang_getArrayElementType(type), parent)); return arr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ref = new ReferenceType; ref->setIsRValue(type.kind == CXType_RValueReference); ref->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ref; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto func = new FunctionType; func->setReturnType(makeAbsType(clang_getResultType(type), parent)); const int numArgs = clang_getNumArgTypes(type); for (int i = 0; i < numArgs; ++i) { func->addArgument(makeAbsType(clang_getArgType(type, i), parent)); } if (clang_isFunctionTypeVariadic(type)) { auto type = new DelayedType; static const auto id = IndexedTypeIdentifier(QStringLiteral("...")); type->setIdentifier(id); type->setKind(DelayedType::Unresolved); func->addArgument(AbstractType::Ptr(type)); } return func; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { DeclarationPointer decl = findDeclaration(clang_getTypeDeclaration(type)); DUChainReadLocker lock; if (!decl) { // probably a forward-declared type decl = ClangHelpers::findForwardDeclaration(type, m_parentContext->context, parent); } if (clang_Type_getNumTemplateArguments(type) != -1) { return createClassTemplateSpecializationType(type, decl); } auto t = new StructureType; if (decl) { t->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user t->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(ClangString(clang_getTypeSpelling(type)).toString())))); } return t; } template = dummy> AbstractType *createType(CXType type, CXCursor) { auto t = new EnumerationType; setIdTypeDecl(clang_getTypeDeclaration(type), t); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto t = new TypeAliasType; CXCursor location = clang_getTypeDeclaration(type); t->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(location), parent)); setIdTypeDecl(location, t); return t; } template = dummy> AbstractType *createType(CXType, CXCursor /*parent*/) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QLatin1String(CursorKindTraits::delayedTypeName(TK))); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor /*parent*/) { return createDelayedType(type); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto numTA = clang_Type_getNumTemplateArguments(type); // TODO: We should really expose more types to libclang! if (numTA != -1 && ClangString(clang_getTypeSpelling(type)).toString().contains(QLatin1Char('<'))) { return createClassTemplateSpecializationType(type); } // Maybe it's the ElaboratedType. E.g.: "struct Type foo();" or "NS::Type foo();" or "void foo(enum Enum e);" e.t.c. auto oldType = type; type = clang_getCanonicalType(type); bool isElaboratedType = type.kind != CXType_FunctionProto && type.kind != CXType_FunctionNoProto && type.kind != CXType_Unexposed && type.kind != CXType_Invalid && type.kind != CXType_Record; return !isElaboratedType ? createDelayedType(oldType) : makeType(type, parent); } template = dummy> typename IdType::Type *createType(CXCursor) { return new typename IdType::Type; } template = dummy> EnumeratorType *createType(CXCursor cursor) { auto type = new EnumeratorType; type->setValue(clang_getEnumConstantDeclUnsignedValue(cursor)); return type; } template = dummy> TypeAliasType *createType(CXCursor cursor) { auto type = new TypeAliasType; type->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(cursor), cursor)); return type; } template = dummy> AbstractType* createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); #if CINDEX_VERSION_MINOR < 31 if (clangType.kind == CXType_Unexposed) { // Clang sometimes can return CXType_Unexposed for CXType_FunctionProto kind. E.g. if it's AttributedType. return dispatchType(clangType, cursor); } #endif return makeType(clangType, cursor); } template = dummy> AbstractType *createType(CXCursor) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QStringLiteral("Label")); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); return makeType(clangType, cursor); } #if CINDEX_VERSION_MINOR >= 32 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto deducedType = clang_getCanonicalType(type); bool isDeduced = deducedType.kind != CXType_Invalid && deducedType.kind != CXType_Auto; return !isDeduced ? createDelayedType(type) : makeType(deducedType, parent); } #endif #if CINDEX_VERSION_MINOR >= 34 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto underyingType = clang_Type_getNamedType(type); return makeType(underyingType, parent); } #endif /// @param declaration an optional declaration that will be associated with created type AbstractType* createClassTemplateSpecializationType(CXType type, const DeclarationPointer& declaration = {}) { auto numTA = clang_Type_getNumTemplateArguments(type); Q_ASSERT(numTA != -1); auto typeDecl = clang_getTypeDeclaration(type); if (!declaration && typeDecl.kind == CXCursor_NoDeclFound) { // clang_getTypeDeclaration doesn't handle all types, fall back to delayed type... return createDelayedType(type); } QStringList typesStr; QString tStr = ClangString(clang_getTypeSpelling(type)).toString(); ParamIterator iter(QStringLiteral("<>"), tStr); while (iter) { typesStr.append(*iter); ++iter; } auto cst = new ClassSpecializationType; for (int i = 0; i < numTA; i++) { auto argumentType = clang_Type_getTemplateArgumentAsType(type, i); AbstractType::Ptr currentType; if (argumentType.kind == CXType_Invalid) { if(i >= typesStr.size()){ currentType = createDelayedType(argumentType); } else { auto t = new DelayedType; t->setIdentifier(IndexedTypeIdentifier(typesStr[i])); currentType = t; } } else { currentType = makeType(argumentType, typeDecl); } if (currentType) { cst->addParameter(currentType->indexed()); } } auto decl = declaration ? declaration : findDeclaration(typeDecl); DUChainReadLocker lock; if (decl) { cst->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user Identifier id(tStr); id.clearTemplateIdentifiers(); cst->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(id)))); } return cst; } //END create* //BEGIN setDeclData template void setDeclData(CXCursor cursor, Declaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, MacroDefinition* decl) const; template void setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template void setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, FunctionDefinition *decl) const; template void setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const; //END setDeclData //BEGIN setDeclInCtxtData template void setDeclInCtxtData(CXCursor, Declaration*) { //No-op } template void setDeclInCtxtData(CXCursor cursor, ClassFunctionDeclaration *decl) { // HACK to retrieve function-constness // This looks like a bug in Clang -- In theory setTypeModifiers should take care of setting the const modifier // however, clang_isConstQualifiedType() for TK == CXType_FunctionProto always returns false // TODO: Debug further auto type = decl->abstractType(); if (type) { if (clang_CXXMethod_isConst(cursor)) { type->setModifiers(type->modifiers() | AbstractType::ConstModifier); decl->setAbstractType(type); } } } template void setDeclInCtxtData(CXCursor cursor, FunctionDefinition *def) { setDeclInCtxtData(cursor, static_cast(def)); const CXCursor canon = clang_getCanonicalCursor(cursor); if (auto decl = findDeclaration(canon)) { def->setDeclaration(decl.data()); } } //END setDeclInCtxtData //BEGIN setDeclType template void setDeclType(Declaration *decl, typename IdType::Type *type) { setDeclType(decl, static_cast(type)); setDeclType(decl, static_cast(type)); } template void setDeclType(Declaration *decl, IdentifiedType *type) { type->setDeclaration(decl); } template void setDeclType(Declaration *decl, AbstractType *type) { decl->setAbstractType(AbstractType::Ptr(type)); } //END setDeclType template void setTypeModifiers(CXType type, AbstractType* kdevType) const; const CXFile m_file; const IncludeFileContexts &m_includes; DeclarationPointer findDeclaration(CXCursor cursor) const; void setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const; std::unordered_map> m_uses; /// At these location offsets (cf. @ref clang_getExpansionLocation) we encountered macro expansions QSet m_macroExpansionLocations; mutable QHash m_cursorToDeclarationCache; CurrentContext *m_parentContext; const bool m_update; }; //BEGIN setTypeModifiers template void Visitor::setTypeModifiers(CXType type, AbstractType* kdevType) const { quint64 modifiers = 0; if (clang_isConstQualifiedType(type)) { modifiers |= AbstractType::ConstModifier; } if (clang_isVolatileQualifiedType(type)) { modifiers |= AbstractType::VolatileModifier; } if (TK == CXType_Short || TK == CXType_UShort) { modifiers |= AbstractType::ShortModifier; } if (TK == CXType_Long || TK == CXType_LongDouble || TK == CXType_ULong) { modifiers |= AbstractType::LongModifier; } if (TK == CXType_LongLong || TK == CXType_ULongLong) { modifiers |= AbstractType::LongLongModifier; } if (TK == CXType_SChar) { modifiers |= AbstractType::SignedModifier; } if (TK == CXType_UChar || TK == CXType_UInt || TK == CXType_UShort || TK == CXType_UInt128 || TK == CXType_ULong || TK == CXType_ULongLong) { modifiers |= AbstractType::UnsignedModifier; } kdevType->setModifiers(modifiers); } //END setTypeModifiers //BEGIN dispatchCursor template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { const bool decision = CursorKindTraits::isClass(clang_getCursorKind(parent)); return decision ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) const bool isDefinition = clang_isCursorDefinition(cursor); return isDefinition ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) // We may end up visiting the same cursor twice in some cases // see discussion on https://git.reviewboard.kde.org/r/119526/ // TODO: Investigate why this is happening in libclang if ((CursorKindTraits::isClass(CK) || CK == CXCursor_EnumDecl) && clang_getCursorKind(parent) == CXCursor_VarDecl) { return CXChildVisit_Continue; } constexpr bool isClassMember = IsInClass == Decision::True; constexpr bool isDefinition = IsDefinition == Decision::True; // always build a context for class templates and functions, otherwise we "leak" // the function/template parameter declarations into the surrounding context, // which can lead to interesting bugs, like https://bugs.kde.org/show_bug.cgi?id=368067 constexpr bool hasContext = isDefinition || CursorKindTraits::isFunction(CK) || CursorKindTraits::isClassTemplate(CK); return buildDeclaration::Type, hasContext>(cursor); } //END dispatchCursor //BEGIN setDeclData template void Visitor::setDeclData(CXCursor cursor, Declaration *decl, bool setComment) const { if (setComment) decl->setComment(makeComment(clang_Cursor_getParsedComment(cursor))); if (CursorKindTraits::isAliasType(CK)) { decl->setIsTypeAlias(true); } if (CK == CXCursor_Namespace) decl->setKind(Declaration::Namespace); if (CK == CXCursor_EnumDecl || CK == CXCursor_EnumConstantDecl || CursorKindTraits::isClass(CK) || CursorKindTraits::isAliasType(CK)) decl->setKind(Declaration::Type); int isAlwaysDeprecated; clang_getCursorPlatformAvailability(cursor, &isAlwaysDeprecated, nullptr, nullptr, nullptr, nullptr, 0); decl->setDeprecated(isAlwaysDeprecated); } template void Visitor::setDeclData(CXCursor cursor, MacroDefinition* decl) const { setDeclData(cursor, static_cast(decl)); if (m_update) { decl->clearParameters(); } auto unit = clang_Cursor_getTranslationUnit(cursor); auto range = clang_getCursorExtent(cursor); // TODO: Quite lacking API in libclang here. // No way to find out if this macro is function-like or not // cf. http://clang.llvm.org/doxygen/classclang_1_1MacroInfo.html // And no way to get the actual definition text range // Should be quite easy to expose that in libclang, though // Let' still get some basic support for this and parse on our own, it's not that difficult const QString contents = QString::fromUtf8(ClangUtils::getRawContents(unit, range)); const int firstOpeningParen = contents.indexOf(QLatin1Char('(')); const int firstWhitespace = contents.indexOf(QLatin1Char(' ')); const bool isFunctionLike = (firstOpeningParen != -1) && (firstOpeningParen < firstWhitespace); decl->setFunctionLike(isFunctionLike); // now extract the actual definition text int start = -1; if (isFunctionLike) { const int closingParen = findClose(contents, firstOpeningParen); if (closingParen != -1) { start = closingParen + 2; // + ')' + ' ' // extract macro function parameters const QString parameters = contents.mid(firstOpeningParen, closingParen - firstOpeningParen + 1); ParamIterator paramIt(QStringLiteral("():"), parameters, 0); while (paramIt) { decl->addParameter(IndexedString(*paramIt)); ++paramIt; } } } else { start = firstWhitespace + 1; // + ' ' } if (start == -1) { // unlikely: invalid macro definition, insert the complete #define statement decl->setDefinition(IndexedString(QLatin1String("#define ") + contents)); } else if (start < contents.size()) { decl->setDefinition(IndexedString(contents.mid(start))); } // else: macro has no body => leave the definition text empty } template void Visitor::setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); //A CXCursor_VarDecl in a class is static (otherwise it'd be a CXCursor_FieldDecl) if (CK == CXCursor_VarDecl) decl->setStatic(true); decl->setAccessPolicy(CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor))); #if CINDEX_VERSION_MINOR >= 32 decl->setMutable(clang_CXXField_isMutable(cursor)); #endif #if CINDEX_VERSION_MINOR >= 30 if (!jsonTestRun()) { auto offset = clang_Cursor_getOffsetOfField(cursor); if (offset >= 0) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignedTo = clang_Type_getAlignOf(type); const auto byteOffset = offset / 8; const auto bitOffset = offset % 8; const QString byteOffsetStr = i18np("1 Byte", "%1 Bytes", byteOffset); const QString bitOffsetStr = bitOffset ? i18np("1 Bit", "%1 Bits", bitOffset) : QString(); const QString offsetStr = bitOffset ? i18nc("%1: bytes, %2: bits", "%1, %2", byteOffsetStr, bitOffsetStr) : byteOffsetStr; decl->setComment(decl->comment() + i18n("

offset in parent: %1; " "size: %2 Bytes; " "aligned to: %3 Bytes

", offsetStr, sizeOf, alignedTo).toUtf8()); } } #endif } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { CXCursorKind kind = clang_getTemplateCursorKind(cursor); switch (kind) { case CXCursor_UnionDecl: setDeclData(cursor, decl); break; case CXCursor_StructDecl: setDeclData(cursor, decl); break; case CXCursor_ClassDecl: setDeclData(cursor, decl); break; default: Q_ASSERT(false); break; } } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { if (m_update) { decl->clearBaseClasses(); } setDeclData(cursor, static_cast(decl)); if (CK == CXCursor_UnionDecl) decl->setClassType(ClassDeclarationData::Union); if (CK == CXCursor_StructDecl) decl->setClassType(ClassDeclarationData::Struct); if (clang_isCursorDefinition(cursor)) { decl->setDeclarationIsDefinition(true); } if (!jsonTestRun()) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0 && alignOf >= 0) { decl->setComment(decl->comment() + i18n("

size: %1 Bytes; " "aligned to: %2 Bytes

", sizeOf, alignOf).toUtf8()); } } } template void Visitor::setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const { if (m_update) { decl->clearDefaultParameters(); } // No setDeclData(...) here: AbstractFunctionDeclaration is an interface // TODO: Can we get the default arguments directly from Clang? // also see http://clang-developers.42468.n3.nabble.com/Finding-default-value-for-function-argument-with-clang-c-API-td4036919.html const QVector defaultArgs = ClangUtils::getDefaultArguments(cursor, ClangUtils::MinimumSize); foreach (const QString& defaultArg, defaultArgs) { decl->addDefaultParameter(IndexedString(defaultArg)); } } template void Visitor::setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl)); decl->setIsAbstract(clang_CXXMethod_isPureVirtual(cursor)); decl->setStatic(clang_CXXMethod_isStatic(cursor)); decl->setVirtual(clang_CXXMethod_isVirtual(cursor)); // TODO: Set flags in one go? (needs new API in kdevplatform) const auto attributes = ClangUtils::specialAttributes(cursor); decl->setIsSignal(attributes & FunctionSignalFlag); decl->setIsSlot(attributes & FunctionSlotFlag); decl->setIsFinal(attributes & FinalFunctionFlag); } template void Visitor::setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, FunctionDefinition *decl) const { bool setComment = clang_equalCursors(clang_getCanonicalCursor(cursor), cursor); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor parent, CXClientData data) -> CXChildVisitResult { if (clang_getCursorKind(cursor) == CXCursor_NamespaceRef) { const auto id = QualifiedIdentifier(ClangString(clang_getCursorSpelling(cursor)).toString()); reinterpret_cast(data)->setImportIdentifier(id); return CXChildVisit_Break; } else { return visitCursor(cursor, parent, data); } }, decl); } //END setDeclData //BEGIN build* template CXChildVisitResult Visitor::buildDeclaration(CXCursor cursor) { auto id = makeId(cursor); if (CK == CXCursor_UnexposedDecl && id.isEmpty()) { // skip unexposed declarations that have no identifier set // this is useful to skip e.g. friend declarations return CXChildVisit_Recurse; } IF_DEBUG(clangDebug() << "id:" << id << "- CK:" << CK << "- DeclType:" << typeid(DeclType).name() << "- hasContext:" << hasContext;) // Code path for class declarations that may be defined "out-of-line", e.g. // "SomeNameSpace::SomeClass {};" QScopedPointer helperContext; if (CursorKindTraits::isClass(CK) || CursorKindTraits::isFunction(CK)) { const auto lexicalParent = clang_getCursorLexicalParent(cursor); const auto semanticParent = clang_getCursorSemanticParent(cursor); const bool isOutOfLine = !clang_equalCursors(lexicalParent, semanticParent); if (isOutOfLine) { const QString scope = ClangUtils::getScope(cursor); auto context = createContext(cursor, QualifiedIdentifier(scope)); helperContext.reset(new CurrentContext(context, m_parentContext->keepAliveContexts)); } } // if helperContext is null, this is a no-op PushValue pushCurrent(m_parentContext, helperContext.isNull() ? m_parentContext : helperContext.data()); if (hasContext) { auto context = createContext(cursor, QualifiedIdentifier(id)); createDeclaration(cursor, id, context); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } createDeclaration(cursor, id, nullptr); return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildParmDecl(CXCursor cursor) { return buildDeclaration::Type, false>(cursor); } CXChildVisitResult Visitor::buildUse(CXCursor cursor) { m_uses[m_parentContext->context].push_back(cursor); return cursor.kind == CXCursor_DeclRefExpr || cursor.kind == CXCursor_MemberRefExpr ? CXChildVisit_Recurse : CXChildVisit_Continue; } CXChildVisitResult Visitor::buildMacroExpansion(CXCursor cursor) { buildUse(cursor); // cache that we encountered a macro expansion at this location unsigned int offset; clang_getSpellingLocation(clang_getCursorLocation(cursor), nullptr, nullptr, nullptr, &offset); m_macroExpansionLocations << offset; return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCompoundStatement(CXCursor cursor) { if (m_parentContext->context->type() == DUContext::Function) { auto context = createContext(cursor); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCXXBaseSpecifier(CXCursor cursor) { auto currentContext = m_parentContext->context; bool virtualInherited = clang_isVirtualBase(cursor); Declaration::AccessPolicy access = CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor)); auto classDeclCursor = clang_getCursorReferenced(cursor); auto decl = findDeclaration(classDeclCursor); if (!decl) { // this happens for templates with template-dependent base classes e.g. - dunno whether we can/should do more here clangDebug() << "failed to find declaration for base specifier:" << clang_getCursorDisplayName(cursor); return CXChildVisit_Recurse; } DUChainWriteLocker lock; contextImportDecl(currentContext, decl); auto classDecl = dynamic_cast(currentContext->owner()); Q_ASSERT(classDecl); classDecl->addBaseClass({decl->indexedType(), access, virtualInherited}); return CXChildVisit_Recurse; } template CXChildVisitResult Visitor::buildTypeAliasTemplateDecl(CXCursor cursor) { auto aliasDecl = findEmbeddedTypeAlias(cursor); // NOTE: using aliasDecl here averts having to add a workaround to makeId() auto id = makeId(aliasDecl); // create template context to prevent leaking child template params auto context = createContext(cursor, QualifiedIdentifier(id)); using DeclType = typename DeclType::Type; createDeclaration(aliasDecl, id, context); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor parent, CXClientData data) { // NOTE: immediately recurse into embedded alias decl return clang_getCursorKind(cursor) == CXCursor_TypeAliasDecl ? CXChildVisit_Recurse : visitCursor(cursor, parent, data); }, this); return CXChildVisit_Continue; } //END build* DeclarationPointer Visitor::findDeclaration(CXCursor cursor) const { const auto it = m_cursorToDeclarationCache.constFind(cursor); if (it != m_cursorToDeclarationCache.constEnd()) { return *it; } // fallback, and cache result auto decl = ClangHelpers::findDeclaration(cursor, m_includes); m_cursorToDeclarationCache.insert(cursor, decl); return decl; } void Visitor::setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const { DeclarationPointer decl = findDeclaration(typeCursor); DUChainReadLocker lock; if (decl) { idType->setDeclaration(decl.data()); } } AbstractType *Visitor::makeType(CXType type, CXCursor parent) { #define UseKind(TypeKind) case TypeKind: return dispatchType(type, parent) switch (type.kind) { UseKind(CXType_Void); UseKind(CXType_Bool); UseKind(CXType_Short); UseKind(CXType_UShort); UseKind(CXType_Int); UseKind(CXType_UInt); UseKind(CXType_Long); UseKind(CXType_ULong); UseKind(CXType_LongLong); UseKind(CXType_ULongLong); UseKind(CXType_Float); UseKind(CXType_LongDouble); UseKind(CXType_Double); UseKind(CXType_Char_U); UseKind(CXType_Char_S); UseKind(CXType_UChar); UseKind(CXType_SChar); UseKind(CXType_Char16); UseKind(CXType_Char32); UseKind(CXType_Pointer); UseKind(CXType_BlockPointer); UseKind(CXType_MemberPointer); UseKind(CXType_ObjCObjectPointer); UseKind(CXType_ConstantArray); UseKind(CXType_VariableArray); UseKind(CXType_IncompleteArray); UseKind(CXType_DependentSizedArray); UseKind(CXType_LValueReference); UseKind(CXType_RValueReference); UseKind(CXType_FunctionNoProto); UseKind(CXType_FunctionProto); UseKind(CXType_Record); UseKind(CXType_Enum); UseKind(CXType_Typedef); UseKind(CXType_Int128); UseKind(CXType_UInt128); UseKind(CXType_Vector); UseKind(CXType_Unexposed); UseKind(CXType_WChar); UseKind(CXType_ObjCInterface); UseKind(CXType_ObjCId); UseKind(CXType_ObjCClass); UseKind(CXType_ObjCSel); UseKind(CXType_NullPtr); + UseKind(CXType_Float128); #if CINDEX_VERSION_MINOR >= 32 UseKind(CXType_Auto); #endif #if CINDEX_VERSION_MINOR >= 34 UseKind(CXType_Elaborated); #endif case CXType_Invalid: return nullptr; default: qCWarning(KDEV_CLANG) << "Unhandled type:" << type.kind << clang_getTypeSpelling(type); return nullptr; } #undef UseKind } RangeInRevision rangeInRevisionForUse(CXCursor cursor, CXCursorKind referencedCursorKind, CXSourceRange useRange, const QSet& macroExpansionLocations) { auto range = ClangRange(useRange).toRangeInRevision(); //TODO: Fix in clang, happens for operator<<, operator<, probably more if (clang_Range_isNull(useRange)) { useRange = clang_getCursorExtent(cursor); range = ClangRange(useRange).toRangeInRevision(); } if (referencedCursorKind == CXCursor_ConversionFunction) { range.end = range.start; range.start.column--; } // For uses inside macro expansions, create an empty use range at the spelling location // the empty range is required in order to not "overlap" the macro expansion range // and to allow proper navigation for the macro expansion // also see JSON test 'macros.cpp' if (clang_getCursorKind(cursor) != CXCursor_MacroExpansion) { unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(useRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } } else { // Workaround for wrong use range returned by clang for macro expansions const auto contents = ClangUtils::getRawContents(clang_Cursor_getTranslationUnit(cursor), useRange); const int firstOpeningParen = contents.indexOf('('); if (firstOpeningParen != -1) { range.end.column = range.start.column + firstOpeningParen; range.end.line = range.start.line; } } return range; } Visitor::Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) : m_file(file) , m_includes(includes) , m_parentContext(nullptr) , m_update(update) { CXCursor tuCursor = clang_getTranslationUnitCursor(tu); auto top = includes[file]; // when updating, this contains child contexts that should be kept alive // even when they are not part of the AST anymore // this is required for some assistants, such as the signature assistant QSet keepAliveContexts; { DUChainReadLocker lock; foreach (const auto& problem, top->problems()) { const auto& desc = problem->description(); if (desc.startsWith(QLatin1String("Return type of out-of-line definition of '")) && desc.endsWith(QLatin1String("' differs from that in the declaration"))) { auto ctx = top->findContextAt(problem->range().start); // keep the context and its parents alive // this also keeps declarations in this context alive while (ctx) { keepAliveContexts << ctx; ctx = ctx->parentContext(); } } } } CurrentContext parent(top, keepAliveContexts); m_parentContext = &parent; clang_visitChildren(tuCursor, &visitCursor, this); if (m_update) { DUChainWriteLocker lock; top->deleteUsesRecursively(); } for (const auto &contextUses : m_uses) { for (const auto &cursor : contextUses.second) { auto referenced = referencedCursor(cursor); if (clang_Cursor_isNull(referenced)) { continue; } // first, try the canonical referenced cursor // this is important to get the correct function declaration e.g. auto canonicalReferenced = clang_getCanonicalCursor(referenced); auto used = findDeclaration(canonicalReferenced); if (!used) { // if the above failed, try the non-canonicalized version as a fallback // this is required for friend declarations that occur before // the real declaration. there, the canonical cursor points to // the friend declaration which is not what we are looking for used = findDeclaration(referenced); } if (!used) { // as a last resort, try to resolve the forward declaration DUChainReadLocker lock; DeclarationPointer decl = ClangHelpers::findForwardDeclaration(clang_getCursorType(referenced), contextUses.first, referenced); used = decl; if (!used) { continue; } } #if CINDEX_VERSION_MINOR >= 29 if (clang_Cursor_getNumTemplateArguments(referenced) >= 0) { // Ideally, we don't need this, but for function templates clang_getCanonicalCursor returns a function definition // See also the testUsesCreatedForDeclarations test DUChainReadLocker lock; used = DUChainUtils::declarationForDefinition(used.data()); } #endif const auto useRange = clang_getCursorReferenceNameRange(cursor, 0, 0); const auto range = rangeInRevisionForUse(cursor, referenced.kind, useRange, m_macroExpansionLocations); DUChainWriteLocker lock; auto usedIndex = top->indexForUsedDeclaration(used.data()); contextUses.first->createUse(usedIndex, range); } } } //END Visitor CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data) { Visitor *visitor = static_cast(data); const auto kind = clang_getCursorKind(cursor); auto location = clang_getCursorLocation(cursor); CXFile file; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); // don't skip MemberRefExpr with invalid location, see also: // http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-May/043114.html if (!ClangUtils::isFileEqual(file, visitor->m_file) && (file || kind != CXCursor_MemberRefExpr)) { return CXChildVisit_Continue; } #define UseCursorKind(CursorKind, ...) case CursorKind: return visitor->dispatchCursor(__VA_ARGS__); switch (kind) { UseCursorKind(CXCursor_UnexposedDecl, cursor, parent); UseCursorKind(CXCursor_StructDecl, cursor, parent); UseCursorKind(CXCursor_UnionDecl, cursor, parent); UseCursorKind(CXCursor_ClassDecl, cursor, parent); UseCursorKind(CXCursor_EnumDecl, cursor, parent); UseCursorKind(CXCursor_FieldDecl, cursor, parent); UseCursorKind(CXCursor_EnumConstantDecl, cursor, parent); UseCursorKind(CXCursor_FunctionDecl, cursor, parent); UseCursorKind(CXCursor_VarDecl, cursor, parent); UseCursorKind(CXCursor_TypeAliasDecl, cursor, parent); UseCursorKind(CXCursor_TypedefDecl, cursor, parent); UseCursorKind(CXCursor_CXXMethod, cursor, parent); UseCursorKind(CXCursor_Namespace, cursor, parent); UseCursorKind(CXCursor_NamespaceAlias, cursor, parent); UseCursorKind(CXCursor_Constructor, cursor, parent); UseCursorKind(CXCursor_Destructor, cursor, parent); UseCursorKind(CXCursor_ConversionFunction, cursor, parent); UseCursorKind(CXCursor_TemplateTypeParameter, cursor, parent); UseCursorKind(CXCursor_NonTypeTemplateParameter, cursor, parent); UseCursorKind(CXCursor_TemplateTemplateParameter, cursor, parent); UseCursorKind(CXCursor_FunctionTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplatePartialSpecialization, cursor, parent); UseCursorKind(CXCursor_ObjCInterfaceDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryDecl, cursor, parent); UseCursorKind(CXCursor_ObjCProtocolDecl, cursor, parent); UseCursorKind(CXCursor_ObjCPropertyDecl, cursor, parent); UseCursorKind(CXCursor_ObjCIvarDecl, cursor, parent); UseCursorKind(CXCursor_ObjCInstanceMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCClassMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCImplementationDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryImplDecl, cursor, parent); UseCursorKind(CXCursor_MacroDefinition, cursor, parent); UseCursorKind(CXCursor_LabelStmt, cursor, parent); case CXCursor_TypeRef: case CXCursor_TemplateRef: case CXCursor_NamespaceRef: case CXCursor_MemberRef: case CXCursor_LabelRef: case CXCursor_OverloadedDeclRef: case CXCursor_VariableRef: case CXCursor_DeclRefExpr: case CXCursor_MemberRefExpr: case CXCursor_ObjCClassRef: return visitor->buildUse(cursor); case CXCursor_MacroExpansion: return visitor->buildMacroExpansion(cursor); case CXCursor_CompoundStmt: return visitor->buildCompoundStatement(cursor); case CXCursor_CXXBaseSpecifier: return visitor->buildCXXBaseSpecifier(cursor); case CXCursor_ParmDecl: return visitor->buildParmDecl(cursor); // TODO: fix upstream and then just adapt this to UseCursorKind() case CXCursor_TypeAliasTemplateDecl: return visitor->dispatchTypeAliasTemplate(cursor, parent); default: return CXChildVisit_Recurse; } } } namespace Builder { void visit(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) { Visitor visitor(tu, file, includes, update); } } diff --git a/plugins/clang/duchain/cursorkindtraits.h b/plugins/clang/duchain/cursorkindtraits.h index 21b2f45657..a456151aa1 100644 --- a/plugins/clang/duchain/cursorkindtraits.h +++ b/plugins/clang/duchain/cursorkindtraits.h @@ -1,268 +1,269 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CURSORKINDTRAITS_H #define CURSORKINDTRAITS_H #include #include #include #include #include #include #include #include #include #include "templatehelpers.h" namespace CursorKindTraits { using namespace KDevelop; constexpr bool isClassTemplate(CXCursorKind CK) { return CK == CXCursor_ClassTemplate || CK == CXCursor_ClassTemplatePartialSpecialization; } constexpr bool isClass(CXCursorKind CK) { return isClassTemplate(CK) || CK == CXCursor_StructDecl || CK == CXCursor_ClassDecl || CK == CXCursor_UnionDecl || CK == CXCursor_ObjCInterfaceDecl || CK == CXCursor_ObjCCategoryDecl || CK == CXCursor_ObjCImplementationDecl || CK == CXCursor_ObjCCategoryImplDecl; } constexpr bool isFunction(CXCursorKind CK) { return CK == CXCursor_FunctionDecl || CK == CXCursor_CXXMethod || CK == CXCursor_Constructor || CK == CXCursor_Destructor || CK == CXCursor_ConversionFunction || CK == CXCursor_FunctionTemplate || CK == CXCursor_ObjCInstanceMethodDecl || CK == CXCursor_ObjCClassMethodDecl; } constexpr bool isDeclaration(CXCursorKind CK) { return isClass(CK) || isFunction(CK) || CK == CXCursor_UnexposedDecl || CK == CXCursor_FieldDecl || CK == CXCursor_ObjCProtocolDecl || CK == CXCursor_ObjCPropertyDecl || CK == CXCursor_ObjCIvarDecl || CK == CXCursor_EnumConstantDecl || CK == CXCursor_VarDecl || CK == CXCursor_ParmDecl || CK == CXCursor_TypedefDecl || CK == CXCursor_TemplateTypeParameter || CK == CXCursor_NonTypeTemplateParameter || CK == CXCursor_TemplateTemplateParameter || CK == CXCursor_NamespaceAlias || CK == CXCursor_UsingDirective || CK == CXCursor_UsingDeclaration || CK == CXCursor_TypeAliasDecl || CK == CXCursor_LabelStmt; } constexpr Decision isDefinition(CXCursorKind CK) { return CK == CXCursor_Namespace || CK == CXCursor_MacroDefinition ? Decision::True : isClass(CK) || isFunction(CK) || CK == CXCursor_EnumDecl ? Decision::Maybe : Decision::False; } constexpr Decision isInClass(CXCursorKind CK) { return CK == CXCursor_FieldDecl || CK == CXCursor_ObjCPropertyDecl || CK == CXCursor_ObjCIvarDecl || CK == CXCursor_ObjCInstanceMethodDecl || CK == CXCursor_ObjCClassMethodDecl ? Decision::True : CK == CXCursor_Namespace || CK == CXCursor_TemplateTypeParameter || CK == CXCursor_FunctionDecl || CK == CXCursor_TemplateTemplateParameter || CK == CXCursor_NonTypeTemplateParameter || CK == CXCursor_MacroDefinition || CK == CXCursor_MacroExpansion ? Decision::False : Decision::Maybe; } constexpr DUContext::ContextType contextType(CXCursorKind CK) { return CK == CXCursor_StructDecl ? DUContext::Class : CK == CXCursor_UnionDecl ? DUContext::Class : CK == CXCursor_ClassDecl ? DUContext::Class : CK == CXCursor_EnumDecl ? DUContext::Enum : CK == CXCursor_FunctionDecl ? DUContext::Function : CK == CXCursor_CXXMethod ? DUContext::Function : CK == CXCursor_Namespace ? DUContext::Namespace : CK == CXCursor_Constructor ? DUContext::Function : CK == CXCursor_Destructor ? DUContext::Function : CK == CXCursor_ConversionFunction ? DUContext::Function : CK == CXCursor_FunctionTemplate ? DUContext::Function : CK == CXCursor_ClassTemplate ? DUContext::Class : CK == CXCursor_ClassTemplatePartialSpecialization ? DUContext::Class : CK == CXCursor_MacroDefinition ? DUContext::Other : static_cast(-1); } constexpr bool isKDevDeclaration(CXCursorKind CK, bool isClassMember) { return !isClassMember && (CK == CXCursor_UnexposedDecl || CK == CXCursor_FieldDecl || CK == CXCursor_ObjCProtocolDecl || CK == CXCursor_ObjCPropertyDecl || CK == CXCursor_ObjCIvarDecl || CK == CXCursor_EnumConstantDecl || CK == CXCursor_VarDecl || CK == CXCursor_ParmDecl || CK == CXCursor_TypedefDecl || CK == CXCursor_TemplateTypeParameter || CK == CXCursor_NonTypeTemplateParameter || CK == CXCursor_TemplateTemplateParameter || CK == CXCursor_UsingDirective || CK == CXCursor_UsingDeclaration || CK == CXCursor_TypeAliasDecl || CK == CXCursor_Namespace || CK == CXCursor_EnumDecl || CK == CXCursor_LabelStmt); } constexpr bool isKDevClassDeclaration(CXCursorKind CK, bool isDefinition) { return isDefinition && isClass(CK); } constexpr bool isKDevForwardDeclaration(CXCursorKind CK, bool isDefinition) { return !isDefinition && isClass(CK); } constexpr bool isKDevClassFunctionDeclaration(CXCursorKind CK, bool isInClass) { return isInClass && isFunction(CK); } constexpr bool isKDevFunctionDeclaration(CXCursorKind CK, bool isDefinition, bool isInClass) { return !isDefinition && !isInClass && isFunction(CK); } constexpr bool isKDevFunctionDefinition(CXCursorKind CK, bool isDefinition, bool isInClass) { return isDefinition && !isInClass && isFunction(CK); } constexpr bool isKDevNamespaceAliasDeclaration(CXCursorKind CK, bool isDefinition) { return !isDefinition && CK == CXCursor_NamespaceAlias; } constexpr bool isKDevClassMemberDeclaration(CXCursorKind CK, bool isInClass) { return isInClass && isKDevDeclaration(CK, false); } constexpr Declaration::AccessPolicy kdevAccessPolicy(CX_CXXAccessSpecifier access) { return access == CX_CXXPrivate ? Declaration::Private : access == CX_CXXProtected ? Declaration::Protected : access == CX_CXXPublic ? Declaration::Public : Declaration::DefaultAccess; } constexpr IntegralType::CommonIntegralTypes integralType(CXTypeKind TK) { return TK == CXType_Void ? IntegralType::TypeVoid : TK == CXType_Bool ? IntegralType::TypeBoolean : TK == CXType_Float ? IntegralType::TypeFloat : TK == CXType_Char16 ? IntegralType::TypeChar16_t : TK == CXType_Char32 ? IntegralType::TypeChar32_t : TK == CXType_WChar ? IntegralType::TypeWchar_t : ( TK == CXType_LongDouble - ||TK == CXType_Double) ? IntegralType::TypeDouble + ||TK == CXType_Double + ||TK == CXType_Float128) ? IntegralType::TypeDouble : ( TK == CXType_Short ||TK == CXType_UShort ||TK == CXType_Int ||TK == CXType_UInt ||TK == CXType_Long ||TK == CXType_ULong ||TK == CXType_LongLong ||TK == CXType_ULongLong) ? IntegralType::TypeInt : ( TK == CXType_Char_U ||TK == CXType_Char_S ||TK == CXType_UChar ||TK == CXType_SChar) ? IntegralType::TypeChar : static_cast(-1); } constexpr bool isArrayType(CXTypeKind TK) { return TK == CXType_ConstantArray || TK == CXType_IncompleteArray || TK == CXType_VariableArray || TK == CXType_DependentSizedArray; } constexpr bool isPointerType(CXTypeKind TK) { return TK == CXType_Pointer || TK == CXType_BlockPointer || TK == CXType_MemberPointer || TK == CXType_ObjCObjectPointer; } constexpr bool isAliasType(CXCursorKind CK) { return CK == CXCursor_TypedefDecl || CK == CXCursor_TypeAliasDecl; } constexpr bool isIdentifiedType(CXCursorKind CK) { return isClass(CK) || isAliasType(CK) || CK == CXCursor_EnumDecl || CK == CXCursor_EnumConstantDecl; } constexpr const char* delayedTypeName(CXTypeKind TK) { return TK == CXType_Int128 ? "__int128" : TK == CXType_UInt128 ? "unsigned __int128" : TK == CXType_ObjCId ? "id" : TK == CXType_ObjCSel ? "SEL" : TK == CXType_NullPtr ? "nullptr_t" : nullptr; } } #endif //CURSORKINDTRAITS_H diff --git a/plugins/cmake/cmakebuilddirchooser.cpp b/plugins/cmake/cmakebuilddirchooser.cpp index d5926fe3f5..760c4cae06 100644 --- a/plugins/cmake/cmakebuilddirchooser.cpp +++ b/plugins/cmake/cmakebuilddirchooser.cpp @@ -1,333 +1,344 @@ /* KDevelop CMake Support * * Copyright 2007 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakebuilddirchooser.h" #include "ui_cmakebuilddirchooser.h" #include "cmakeutils.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; CMakeBuildDirChooser::CMakeBuildDirChooser(QWidget* parent) : QDialog(parent) { setWindowTitle(i18n("Configure a build directory - %1", ICore::self()->runtimeController()->currentRuntime()->name())); m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); auto mainWidget = new QWidget(this); auto mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); m_chooserUi = new Ui::CMakeBuildDirChooser; m_chooserUi->setupUi(mainWidget); setShowAvailableBuildDirs(false); mainLayout->addWidget(m_buttonBox); m_chooserUi->buildFolder->setMode(KFile::Directory|KFile::ExistingOnly); m_chooserUi->installPrefix->setMode(KFile::Directory|KFile::ExistingOnly); m_extraArgumentsHistory = new CMakeExtraArgumentsHistory(m_chooserUi->extraArguments); connect(m_chooserUi->buildFolder, &KUrlRequester::textChanged, this, &CMakeBuildDirChooser::updated); connect(m_chooserUi->buildType, static_cast(&QComboBox::currentIndexChanged), this, &CMakeBuildDirChooser::updated); connect(m_chooserUi->extraArguments, &KComboBox::editTextChanged, this, &CMakeBuildDirChooser::updated); connect(m_chooserUi->availableBuildDirs, static_cast(&QComboBox::currentIndexChanged), this, &CMakeBuildDirChooser::adoptPreviousBuildDirectory); updated(); } CMakeBuildDirChooser::~CMakeBuildDirChooser() { delete m_extraArgumentsHistory; delete m_chooserUi; } void CMakeBuildDirChooser::setProject( IProject* project ) { m_project = project; KDevelop::Path folder = m_project->path(); QString relative=CMake::projectRootRelative(m_project); folder.cd(relative); m_srcFolder = folder; m_chooserUi->buildFolder->setUrl(KDevelop::proposedBuildFolder(m_srcFolder).toUrl()); setWindowTitle(i18n("Configure a build directory for %1", project->name())); update(); } void CMakeBuildDirChooser::buildDirSettings( const KDevelop::Path& buildDir, QString& srcDir, QString& installDir, QString& buildType) { static const QString srcLine = QStringLiteral("CMAKE_HOME_DIRECTORY:INTERNAL="); static const QString installLine = QStringLiteral("CMAKE_INSTALL_PREFIX:PATH="); static const QString buildLine = QStringLiteral("CMAKE_BUILD_TYPE:STRING="); const Path cachePath(buildDir, QStringLiteral("CMakeCache.txt")); QFile file(cachePath.toLocalFile()); srcDir.clear(); installDir.clear(); buildType.clear(); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qCWarning(CMAKE) << "Something really strange happened reading" << cachePath; return; } int cnt = 0; while (cnt != 3 && !file.atEnd()) { // note: CMakeCache.txt is UTF8-encoded, also see bug 329305 QString line = QString::fromUtf8(file.readLine().trimmed()); if (line.startsWith(srcLine)) { srcDir = line.mid(srcLine.count()); ++cnt; } if (line.startsWith(installLine)) { installDir = line.mid(installLine.count()); ++cnt; } if (line.startsWith(buildLine)) { buildType = line.mid(buildLine.count()); ++cnt; } } qCDebug(CMAKE) << "The source directory for " << file.fileName() << "is" << srcDir; qCDebug(CMAKE) << "The install directory for " << file.fileName() << "is" << installDir; qCDebug(CMAKE) << "The build type for " << file.fileName() << "is" << buildType; } void CMakeBuildDirChooser::updated() { StatusTypes st; Path chosenBuildFolder(m_chooserUi->buildFolder->url()); bool emptyUrl = chosenBuildFolder.isEmpty(); if( emptyUrl ) st |= BuildFolderEmpty; bool dirEmpty = false, dirExists= false, dirRelative = false; QString srcDir; QString installDir; QString buildType; if(!emptyUrl) { QDir d(chosenBuildFolder.toLocalFile()); dirExists = d.exists(); dirEmpty = dirExists && d.count()<=2; dirRelative = d.isRelative(); if(!dirEmpty && dirExists && !dirRelative) { bool hasCache=QFile::exists(Path(chosenBuildFolder, QStringLiteral("CMakeCache.txt")).toLocalFile()); if(hasCache) { QString proposed=m_srcFolder.toLocalFile(); buildDirSettings(chosenBuildFolder, srcDir, installDir, buildType); if(!srcDir.isEmpty()) { if(QDir(srcDir).canonicalPath()==QDir(proposed).canonicalPath()) { st |= CorrectBuildDir | BuildDirCreated; } } else { qCWarning(CMAKE) << "maybe you are trying a damaged CMakeCache.txt file. Proper: "; } if(!installDir.isEmpty() && QDir(installDir).exists()) { m_chooserUi->installPrefix->setUrl(QUrl::fromLocalFile(installDir)); } m_chooserUi->buildType->setCurrentText(buildType); } } if(m_alreadyUsed.contains(chosenBuildFolder.toLocalFile()) && !m_chooserUi->availableBuildDirs->isEnabled()) { st=DirAlreadyCreated; } } else { setStatus(i18n("You need to specify a build directory."), false); return; } if(st & (BuildDirCreated | CorrectBuildDir)) { setStatus(i18n("Using an already created build directory."), true); m_chooserUi->installPrefix->setEnabled(false); m_chooserUi->buildType->setEnabled(false); } else { bool correct = (dirEmpty || !dirExists) && !(st & DirAlreadyCreated) && !dirRelative; if(correct) { st |= CorrectBuildDir; setStatus(i18n("Creating a new build directory."), true); } else { //Useful to explain what's going wrong if(st & DirAlreadyCreated) setStatus(i18n("Build directory already configured."), false); else if (!srcDir.isEmpty()) setStatus(i18n("This build directory is for %1, " "but the project directory is %2.", srcDir, m_srcFolder.toLocalFile()), false); else if(dirRelative) setStatus(i18n("You may not select a relative build directory."), false); else if(!dirEmpty) setStatus(i18n("The selected build directory is not empty."), false); } m_chooserUi->installPrefix->setEnabled(correct); m_chooserUi->buildType->setEnabled(correct); } } +void CMakeBuildDirChooser::setCMakeExecutable(const Path& path) +{ + m_chooserUi->cmakeExecutable->setUrl(path.toUrl()); + updated(); +} + void CMakeBuildDirChooser::setInstallPrefix(const Path& path) { m_chooserUi->installPrefix->setUrl(path.toUrl()); updated(); } void CMakeBuildDirChooser::setBuildFolder(const Path& path) { m_chooserUi->buildFolder->setUrl(path.toUrl()); updated(); } void CMakeBuildDirChooser::setBuildType(const QString& s) { m_chooserUi->buildType->addItem(s); m_chooserUi->buildType->setCurrentIndex(m_chooserUi->buildType->findText(s)); updated(); } void CMakeBuildDirChooser::setAlreadyUsed (const QStringList & used) { m_chooserUi->availableBuildDirs->addItems(used); m_alreadyUsed = used; updated(); } void CMakeBuildDirChooser::setExtraArguments(const QString& args) { m_chooserUi->extraArguments->setEditText(args); updated(); } void CMakeBuildDirChooser::setStatus(const QString& message, bool canApply) { KColorScheme scheme(QPalette::Normal); KColorScheme::ForegroundRole role; if (canApply) { role = KColorScheme::PositiveText; } else { role = KColorScheme::NegativeText; } m_chooserUi->status->setText(QStringLiteral("%2").arg(scheme.foreground(role).color().name()).arg(message)); auto okButton = m_buttonBox->button(QDialogButtonBox::Ok); okButton->setEnabled(canApply); if (canApply) { auto cancelButton = m_buttonBox->button(QDialogButtonBox::Cancel); cancelButton->clearFocus(); } } void CMakeBuildDirChooser::adoptPreviousBuildDirectory(int index) { if (index > 0) { Q_ASSERT(m_project); + m_chooserUi->cmakeExecutable->setUrl(CMake::currentCMakeExecutable(m_project, index -1).toUrl()); m_chooserUi->buildFolder->setUrl(CMake::currentBuildDir(m_project, index -1).toUrl()); m_chooserUi->installPrefix->setUrl(CMake::currentInstallDir(m_project, index -1).toUrl()); m_chooserUi->buildType->setCurrentText(CMake::currentBuildType(m_project, index -1)); m_chooserUi->extraArguments->setCurrentText(CMake::currentExtraArguments(m_project, index -1)); } + m_chooserUi->label_5->setEnabled(index == 0); + m_chooserUi->cmakeExecutable->setEnabled(index == 0); m_chooserUi->label_3->setEnabled(index == 0); m_chooserUi->buildFolder->setEnabled(index == 0); m_chooserUi->label->setEnabled(index == 0); m_chooserUi->installPrefix->setEnabled(index == 0); m_chooserUi->label_2->setEnabled(index == 0); m_chooserUi->buildType->setEnabled(index == 0); m_chooserUi->status->setEnabled(index == 0); m_chooserUi->extraArguments->setEnabled(index == 0); m_chooserUi->label_4->setEnabled(index == 0); } bool CMakeBuildDirChooser::reuseBuilddir() { return m_chooserUi->availableBuildDirs->currentIndex() > 0; } int CMakeBuildDirChooser::alreadyUsedIndex() const { return m_chooserUi->availableBuildDirs->currentIndex() - 1; } void CMakeBuildDirChooser::setShowAvailableBuildDirs(bool show) { m_chooserUi->availableLabel->setVisible(show); m_chooserUi->availableBuildDirs->setVisible(show); } +Path CMakeBuildDirChooser::cmakeExecutable() const { return Path(m_chooserUi->cmakeExecutable->url()); } + Path CMakeBuildDirChooser::installPrefix() const { return Path(m_chooserUi->installPrefix->url()); } Path CMakeBuildDirChooser::buildFolder() const { return Path(m_chooserUi->buildFolder->url()); } QString CMakeBuildDirChooser::buildType() const { return m_chooserUi->buildType->currentText(); } QString CMakeBuildDirChooser::extraArguments() const { return m_chooserUi->extraArguments->currentText(); } diff --git a/plugins/cmake/cmakebuilddirchooser.h b/plugins/cmake/cmakebuilddirchooser.h index 9180957634..7b8fab657e 100644 --- a/plugins/cmake/cmakebuilddirchooser.h +++ b/plugins/cmake/cmakebuilddirchooser.h @@ -1,97 +1,100 @@ /* KDevelop CMake Support * * Copyright 2007 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEBUILDDIRCHOOSER_H #define CMAKEBUILDDIRCHOOSER_H #include #include #include #include "cmakeextraargumentshistory.h" #include "cmakecommonexport.h" class QDialogButtonBox; namespace Ui { class CMakeBuildDirChooser; } namespace KDevelop { class IProject; } class KDEVCMAKECOMMON_EXPORT CMakeBuildDirChooser : public QDialog { Q_OBJECT public: enum StatusType { BuildDirCreated = 1, CorrectProject = 2, BuildFolderEmpty = 4, + HaveCMake = 8, CorrectBuildDir = 16, DirAlreadyCreated = 32 //Error message in case it's already configured }; Q_DECLARE_FLAGS( StatusTypes, StatusType ) explicit CMakeBuildDirChooser(QWidget* parent = nullptr); ~CMakeBuildDirChooser() override; + KDevelop::Path cmakeExecutable() const; KDevelop::Path installPrefix() const; KDevelop::Path buildFolder() const; QString buildType() const; QString extraArguments() const; int alreadyUsedIndex() const; bool reuseBuilddir(); + void setCMakeExecutable(const KDevelop::Path& path); void setInstallPrefix(const KDevelop::Path& path); void setBuildFolder(const KDevelop::Path& path); void setBuildType(const QString& buildType); void setProject( KDevelop::IProject* project ); void setSourceFolder(const KDevelop::Path &path) { m_srcFolder = path; } void setAlreadyUsed(const QStringList& used); void setStatus(const QString& message, bool canApply); void setExtraArguments(const QString& args); void setShowAvailableBuildDirs(bool show); private Q_SLOTS: void updated(); private: void adoptPreviousBuildDirectory(int index); void buildDirSettings( const KDevelop::Path& buildDir, QString& srcDir, QString& installDir, QString& buildType); QStringList m_alreadyUsed; CMakeExtraArgumentsHistory* m_extraArgumentsHistory; Ui::CMakeBuildDirChooser* m_chooserUi; QDialogButtonBox* m_buttonBox; KDevelop::IProject* m_project; KDevelop::Path m_srcFolder; }; Q_DECLARE_OPERATORS_FOR_FLAGS( CMakeBuildDirChooser::StatusTypes ) #endif diff --git a/plugins/cmake/cmakebuilddirchooser.ui b/plugins/cmake/cmakebuilddirchooser.ui index 77302cac5c..d30da357e0 100644 --- a/plugins/cmake/cmakebuilddirchooser.ui +++ b/plugins/cmake/cmakebuilddirchooser.ui @@ -1,173 +1,196 @@ CMakeBuildDirChooser 0 0 611 244 QFormLayout::ExpandingFieldsGrow 0 0 0 0 Available build directories: New build directory Build &directory: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter buildFolder &Installation prefix: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter installPrefix 0 0 Build &type: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter buildType true 0 Debug Release RelWithDebInfo MinSizeRel Extra arguments: 0 0 true - + + + + CMake &executable: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + cmakeExecutable + + + + + + + + 0 + 0 + + + + + Status Message... true KComboBox QComboBox
kcombobox.h
KUrlRequester QWidget
kurlrequester.h
diff --git a/plugins/cmake/cmakeserver.cpp b/plugins/cmake/cmakeserver.cpp index eed40d251b..be682b8419 100644 --- a/plugins/cmake/cmakeserver.cpp +++ b/plugins/cmake/cmakeserver.cpp @@ -1,208 +1,211 @@ /* KDevelop CMake Support * * Copyright 2017 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakeserver.h" #include "cmakeprojectdata.h" #include "cmakeutils.h" #include #include #include +#include #include #include #include #include #include #include #include "debug.h" CMakeServer::CMakeServer(QObject* parent) : QObject(parent) , m_localSocket(new QLocalSocket(this)) { QString path; { const auto cacheLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); QDir::temp().mkpath(cacheLocation); QTemporaryFile file(cacheLocation + "/kdevelopcmake"); file.open(); file.close(); path = file.fileName(); Q_ASSERT(!path.isEmpty()); } m_process.setProcessChannelMode(QProcess::ForwardedChannels); #if QT_VERSION < 0x050600 connect(&m_process, static_cast(&QProcess::error), #else connect(&m_process, &QProcess::errorOccurred, #endif this, [this, path](QProcess::ProcessError error) { qCWarning(CMAKE) << "cmake server error:" << error << path << m_process.readAllStandardError() << m_process.readAllStandardOutput(); }); connect(&m_process, static_cast(&QProcess::finished), this, [](int code){ qCDebug(CMAKE) << "cmake server finished with code" << code; }); connect(&m_process, static_cast(&QProcess::finished), this, &CMakeServer::finished); connect(m_localSocket, &QIODevice::readyRead, this, &CMakeServer::processOutput); connect(m_localSocket, static_cast(&QLocalSocket::error), this, [this, path](QLocalSocket::LocalSocketError socketError) { qCWarning(CMAKE) << "cmake server socket error:" << socketError << path; setConnected(false); }); connect(m_localSocket, &QLocalSocket::connected, this, [this]() { setConnected(true); }); connect(&m_process, &QProcess::started, this, [this, path](){ //Once the process has started, wait for the file to be created, then connect to it QTimer::singleShot(1000, this, [this, path]() { m_localSocket->connectToServer(path, QIODevice::ReadWrite); }); }); - m_process.setProgram(CMake::findExecutable()); + // we're called with the importing project as our parent, so we can fetch configured + // cmake executable (project-specific or kdevelop-wide) rather than the system version. + m_process.setProgram(CMake::currentCMakeExecutable(dynamic_cast(parent)).toLocalFile()); m_process.setArguments({"-E", "server", "--experimental", "--pipe=" + path}); KDevelop::ICore::self()->runtimeController()->currentRuntime()->startProcess(&m_process); } CMakeServer::~CMakeServer() { m_process.disconnect(); m_process.kill(); m_process.waitForFinished(); } void CMakeServer::setConnected(bool conn) { if (conn == m_connected) return; m_connected = conn; if (m_connected) Q_EMIT connected(); else Q_EMIT disconnected(); } bool CMakeServer::isServerAvailable() { return m_localSocket->isOpen(); } static QByteArray openTag() { return QByteArrayLiteral("\n[== \"CMake Server\" ==[\n"); } static QByteArray closeTag() { return QByteArrayLiteral("\n]== \"CMake Server\" ==]\n"); } void CMakeServer::sendCommand(const QJsonObject& object) { Q_ASSERT(isServerAvailable()); const QByteArray data = openTag() + QJsonDocument(object).toJson(QJsonDocument::Compact) + closeTag(); auto len = m_localSocket->write(data); // qCDebug(CMAKE) << "writing...\n" << QJsonDocument(object).toJson(); Q_ASSERT(len > 0); } void CMakeServer::processOutput() { Q_ASSERT(m_localSocket); const auto openTag = ::openTag(); const auto closeTag = ::closeTag(); m_buffer += m_localSocket->readAll(); for(; m_buffer.size() > openTag.size(); ) { Q_ASSERT(m_buffer.startsWith(openTag)); const int idx = m_buffer.indexOf(closeTag, openTag.size()); if (idx >= 0) { emitResponse(m_buffer.mid(openTag.size(), idx - openTag.size())); m_buffer = m_buffer.mid(idx + closeTag.size()); } else { break; } } } void CMakeServer::emitResponse(const QByteArray& data) { QJsonParseError error; auto doc = QJsonDocument::fromJson(data, &error); if (error.error) { qCWarning(CMAKE) << "error processing" << error.errorString() << data; } Q_ASSERT(doc.isObject()); Q_EMIT response(doc.object()); } void CMakeServer::handshake(const KDevelop::Path& source, const KDevelop::Path& build) { Q_ASSERT(!source.isEmpty()); const QString generatorVariable = QStringLiteral("CMAKE_GENERATOR"); const QString homeDirectoryVariable = QStringLiteral("CMAKE_HOME_DIRECTORY"); const auto cacheValues = CMake::readCacheValues(KDevelop::Path(build, QStringLiteral("CMakeCache.txt")), {generatorVariable, homeDirectoryVariable}); QString generator = cacheValues.value(generatorVariable); if (generator.isEmpty()) { generator = CMake::defaultGenerator(); } // prefer pre-existing source directory, see also: https://gitlab.kitware.com/cmake/cmake/issues/16736 QString sourceDirectory = cacheValues.value(homeDirectoryVariable); if (sourceDirectory.isEmpty()) { sourceDirectory = source.toLocalFile(); } else if (QFileInfo(sourceDirectory).canonicalFilePath() != QFileInfo(source.toLocalFile()).canonicalFilePath()) { qCWarning(CMAKE) << "Build directory is configured for another source directory:" << homeDirectoryVariable << sourceDirectory << "wanted to open" << source << "in" << build; } qCDebug(CMAKE) << "Using generator" << generator << "for project" << source << "in" << build; sendCommand({ {"cookie", {}}, {"type", "handshake"}, {"major", 1}, {"protocolVersion", QJsonObject{{"major", 1}} }, {"sourceDirectory", sourceDirectory}, {"buildDirectory", build.toLocalFile()}, {"generator", generator} //TODO: make it possible to keep whatever they have ATM }); } void CMakeServer::configure(const QStringList& args) { sendCommand({ {"type", "configure"}, {"cacheArguments", QJsonArray::fromStringList(args)} }); } void CMakeServer::compute() { sendCommand({ {"type", "compute"} }); } void CMakeServer::codemodel() { sendCommand({ {"type", "codemodel"} }); } diff --git a/plugins/cmake/cmakeserverimportjob.cpp b/plugins/cmake/cmakeserverimportjob.cpp index 885b519ac3..f93ded0965 100644 --- a/plugins/cmake/cmakeserverimportjob.cpp +++ b/plugins/cmake/cmakeserverimportjob.cpp @@ -1,197 +1,205 @@ /* KDevelop CMake Support * * Copyright 2017 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakeserverimportjob.h" #include "cmakeutils.h" #include "cmakeserver.h" #include #include #include #include #include #include #include #include #include #include "debug.h" static QString unescape(const QStringRef& input) { QString output; output.reserve(input.length()); bool isEscaped = false; for (auto it = input.data(), end = it + input.length(); it != end; ++it) { QChar c = *it; if (!isEscaped && c == '\\') { isEscaped = true; } else { output.append(c); isEscaped = false; } } return output; } static QHash processDefines(const QString &compileFlags, const QJsonArray &defines) { QHash ret; const auto& defineRx = MakeFileResolver::defineRegularExpression(); auto it = defineRx.globalMatch(compileFlags); while (it.hasNext()) { const auto match = it.next(); QString value; if (match.lastCapturedIndex() > 1) { value = unescape(match.capturedRef(match.lastCapturedIndex())); } ret[match.captured(1)] = value; } for (const QJsonValue& defineValue: defines) { const QString define = defineValue.toString(); const int eqIdx = define.indexOf(QLatin1Char('=')); if (eqIdx<0) { ret[define] = QString(); } else { ret[define.left(eqIdx)] = define.mid(eqIdx+1); } } return ret; } CMakeTarget::Type typeToEnum(const QJsonObject& target) { static const QHash s_types = { {QStringLiteral("EXECUTABLE"), CMakeTarget::Executable}, {QStringLiteral("STATIC_LIBRARY"), CMakeTarget::Library}, {QStringLiteral("MODULE_LIBRARY"), CMakeTarget::Library}, {QStringLiteral("SHARED_LIBRARY"), CMakeTarget::Library}, {QStringLiteral("OBJECT_LIBRARY"), CMakeTarget::Library}, {QStringLiteral("INTERFACE_LIBRARY"), CMakeTarget::Library} }; const auto value = target.value(QLatin1String("type")).toString(); return s_types.value(value, CMakeTarget::Custom); } void CMakeServerImportJob::processCodeModel(const QJsonObject &response, CMakeProjectData &data) { const auto configs = response.value(QStringLiteral("configurations")).toArray(); qCDebug(CMAKE) << "process response" << response; data.targets.clear(); data.compilationData.files.clear(); const auto rt = KDevelop::ICore::self()->runtimeController()->currentRuntime(); for (const auto &config: configs) { const auto projects = config.toObject().value(QStringLiteral("projects")).toArray(); for (const auto &project: projects) { const auto targets = project.toObject().value(QStringLiteral("targets")).toArray(); for (const auto &targetObject: targets) { const auto target = targetObject.toObject(); const KDevelop::Path targetDir = rt->pathInHost(KDevelop::Path(target.value(QStringLiteral("sourceDirectory")).toString())); data.targets[targetDir] += CMakeTarget { typeToEnum(target), target.value(QStringLiteral("name")).toString(), kTransform(target[QLatin1String("artifacts")].toArray(), [](const QJsonValue& val) { return KDevelop::Path(val.toString()); }) }; const auto fileGroups = target.value(QStringLiteral("fileGroups")).toArray(); for (const auto &fileGroupValue: fileGroups) { const auto fileGroup = fileGroupValue.toObject(); CMakeFile file; file.includes = kTransform(fileGroup.value(QStringLiteral("includePath")).toArray(), [](const QJsonValue& val) { return KDevelop::Path(val.toObject().value(QStringLiteral("path")).toString()); }); file.compileFlags = fileGroup.value(QStringLiteral("compileFlags")).toString(); file.defines = processDefines(file.compileFlags, fileGroup.value(QStringLiteral("defines")).toArray()); const auto sourcesArray = fileGroup.value(QStringLiteral("sources")).toArray(); const KDevelop::Path::List sources = kTransform(sourcesArray, [targetDir](const QJsonValue& val) { return KDevelop::Path(targetDir, val.toString()); }); for (const auto& source: sources) { // NOTE: we use the canonical file path to prevent issues with symlinks in the path // leading to lookup failures const auto localFile = rt->pathInHost(source); const auto canonicalFile = QFileInfo(source.toLocalFile()).canonicalFilePath(); const auto sourcePath = localFile.toLocalFile() == canonicalFile ? localFile : KDevelop::Path(canonicalFile); data.compilationData.files[sourcePath] = file; } qCDebug(CMAKE) << "registering..." << sources << file; } } } } } CMakeServerImportJob::CMakeServerImportJob(KDevelop::IProject* project, CMakeServer* server, QObject* parent) : KJob(parent) , m_server(server) , m_project(project) { connect(m_server.data(), &CMakeServer::disconnected, this, [this]() { setError(UnexpectedDisconnect); emitResult(); }); } void CMakeServerImportJob::start() { if (m_server->isServerAvailable()) doStart(); else connect(m_server.data(), &CMakeServer::connected, this, &CMakeServerImportJob::doStart); } void CMakeServerImportJob::doStart() { connect(m_server.data(), &CMakeServer::response, this, &CMakeServerImportJob::processResponse); m_server->handshake(m_project->path(), CMake::currentBuildDir(m_project)); } void CMakeServerImportJob::processResponse(const QJsonObject& response) { const auto responseType = response.value(QStringLiteral("type")); if (responseType == QLatin1String("reply")) { const auto inReplyTo = response.value(QStringLiteral("inReplyTo")); qCDebug(CMAKE) << "replying..." << inReplyTo; if (inReplyTo == QLatin1String("handshake")) { m_server->configure({}); } else if (inReplyTo == QLatin1String("configure")) { m_server->compute(); } else if (inReplyTo == QLatin1String("compute")) { m_server->codemodel(); } else if(inReplyTo == QLatin1String("codemodel")) { processCodeModel(response, m_data); m_data.m_testSuites = CMake::importTestSuites(CMake::currentBuildDir(m_project)); m_data.m_server = m_server; emitResult(); } else { qCWarning(CMAKE) << "unhandled reply" << response; } } else if(responseType == QLatin1String("error")) { setError(ErrorResponse); setErrorText(response.value(QStringLiteral("errorMessage")).toString()); qCWarning(CMAKE) << "error!!" << response; emitResult(); + } else if (responseType == QLatin1String("progress")) { + int progress = response.value(QStringLiteral("progressCurrent")).toInt(); + int total = response.value(QStringLiteral("progressMaximum")).toInt(); + if (progress >= 0 && total > 0) { + setPercent(100.0 * progress / total); + } + } else if (responseType == QLatin1String("message") || responseType == QLatin1String("hello")) { + // Known, but not used for anything currently. } else { qCWarning(CMAKE) << "unhandled message" << response; } } diff --git a/plugins/cmake/cmakeutils.cpp b/plugins/cmake/cmakeutils.cpp index 800566cdf2..b8c07fada9 100644 --- a/plugins/cmake/cmakeutils.cpp +++ b/plugins/cmake/cmakeutils.cpp @@ -1,699 +1,737 @@ /* KDevelop CMake Support * * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakeutils.h" #include "cmakeprojectdata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "icmakedocumentation.h" #include "cmakebuilddirchooser.h" #include "settings/cmakecachemodel.h" #include "debug.h" #include "cmakebuilderconfig.h" #include #include "parser/cmakelistsparser.h" using namespace KDevelop; namespace Config { namespace Old { static const QString currentBuildDirKey = QStringLiteral("CurrentBuildDir"); static const QString oldcmakeExecutableKey = QStringLiteral("CMake Binary"); // Todo: Remove at some point static const QString currentBuildTypeKey = QStringLiteral("CurrentBuildType"); static const QString currentInstallDirKey = QStringLiteral("CurrentInstallDir"); static const QString currentEnvironmentKey = QStringLiteral("CurrentEnvironment"); static const QString currentExtraArgumentsKey = QStringLiteral("Extra Arguments"); +static const QString currentCMakeExecutableKey = QStringLiteral("Current CMake Binary"); static const QString projectRootRelativeKey = QStringLiteral("ProjectRootRelative"); static const QString projectBuildDirs = QStringLiteral("BuildDirs"); } static const QString buildDirIndexKey_ = QStringLiteral("Current Build Directory Index"); static const QString buildDirOverrideIndexKey = QStringLiteral("Temporary Build Directory Index"); static const QString buildDirCountKey = QStringLiteral("Build Directory Count"); //the used builddir will change for every runtime static QString buildDirIndexKey() { const QString currentRuntime = ICore::self()->runtimeController()->currentRuntime()->name(); return buildDirIndexKey_ + '-' + currentRuntime; } namespace Specific { static const QString buildDirPathKey = QStringLiteral("Build Directory Path"); +// TODO: migrate to more generic & consistent key term "CMake Executable" +// Support the old "CMake Binary" key too for backwards compatibility during +// a reasonable transition period. Both keys are saved at least until 5.2.0 +// is released. Import support for the old key will need to remain for a +// considably longer period, ideally. +static const QString cmakeBinaryKey = QStringLiteral("CMake Binary"); static const QString cmakeExecutableKey = QStringLiteral("CMake Executable"); static const QString cmakeBuildTypeKey = QStringLiteral("Build Type"); static const QString cmakeInstallDirKey = QStringLiteral("Install Directory"); static const QString cmakeEnvironmentKey = QStringLiteral("Environment Profile"); static const QString cmakeArgumentsKey = QStringLiteral("Extra Arguments"); static const QString buildDirRuntime = QStringLiteral("Runtime"); } static const QString groupNameBuildDir = QStringLiteral("CMake Build Directory %1"); static const QString groupName = QStringLiteral("CMake"); } // namespace Config namespace { KConfigGroup baseGroup( KDevelop::IProject* project ) { if (!project) return KConfigGroup(); return project->projectConfiguration()->group( Config::groupName ); } KConfigGroup buildDirGroup( KDevelop::IProject* project, int buildDirIndex ) { return baseGroup(project).group( Config::groupNameBuildDir.arg(buildDirIndex) ); } bool buildDirGroupExists( KDevelop::IProject* project, int buildDirIndex ) { return baseGroup(project).hasGroup( Config::groupNameBuildDir.arg(buildDirIndex) ); } QString readBuildDirParameter( KDevelop::IProject* project, const QString& key, const QString& aDefault, int buildDirectory ) { const int buildDirIndex = buildDirectory<0 ? CMake::currentBuildDirIndex(project) : buildDirectory; if (buildDirIndex >= 0) return buildDirGroup( project, buildDirIndex ).readEntry( key, aDefault ); else return aDefault; } void writeBuildDirParameter( KDevelop::IProject* project, const QString& key, const QString& value ) { int buildDirIndex = CMake::currentBuildDirIndex(project); if (buildDirIndex >= 0) { KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex ); buildDirGrp.writeEntry( key, value ); } else { qCWarning(CMAKE) << "cannot write key" << key << "(" << value << ")" << "when no builddir is set!"; } } void writeProjectBaseParameter( KDevelop::IProject* project, const QString& key, const QString& value ) { KConfigGroup baseGrp = baseGroup(project); baseGrp.writeEntry( key, value ); } void setBuildDirRuntime( KDevelop::IProject* project, const QString& name) { writeBuildDirParameter(project, Config::Specific::buildDirRuntime, name); } QString buildDirRuntime( KDevelop::IProject* project, int builddir) { return readBuildDirParameter(project, Config::Specific::buildDirRuntime, QString(), builddir); } } // namespace namespace CMake { KDevelop::Path::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs) { const KDevelop::Path buildDir(CMake::currentBuildDir(project)); const KDevelop::Path installDir(CMake::currentInstallDir(project)); KDevelop::Path::List newList; newList.reserve(dirs.size()); foreach(const QString& s, dirs) { KDevelop::Path dir; if(s.startsWith(QLatin1String("#[bin_dir]"))) { dir = KDevelop::Path(buildDir, s); } else if(s.startsWith(QLatin1String("#[install_dir]"))) { dir = KDevelop::Path(installDir, s); } else { dir = KDevelop::Path(s); } // qCDebug(CMAKE) << "resolved" << s << "to" << d; if (!newList.contains(dir)) { newList.append(dir); } } return newList; } ///NOTE: when you change this, update @c defaultConfigure in cmakemanagertest.cpp bool checkForNeedingConfigure( KDevelop::IProject* project ) { const QString currentRuntime = ICore::self()->runtimeController()->currentRuntime()->name(); const KDevelop::Path builddir = currentBuildDir(project); const bool isValid = (buildDirRuntime(project, -1) == currentRuntime || buildDirRuntime(project, -1).isEmpty()) && builddir.isValid(); if( !isValid ) { CMakeBuildDirChooser bd; bd.setProject( project ); const auto builddirs = CMake::allBuildDirs(project); bd.setAlreadyUsed( builddirs ); bd.setShowAvailableBuildDirs(!builddirs.isEmpty()); + bd.setCMakeExecutable(currentCMakeExecutable(project)); if( !bd.exec() ) { return false; } if (bd.reuseBuilddir()) { CMake::setCurrentBuildDirIndex( project, bd.alreadyUsedIndex() ); } else { QString newbuilddir = bd.buildFolder().toLocalFile(); int addedBuildDirIndex = buildDirCount( project ); // old count is the new index // Initialize the kconfig items with the values from the dialog, this ensures the settings // end up in the config file once the changes are saved qCDebug(CMAKE) << "adding to cmake config: new builddir index" << addedBuildDirIndex; qCDebug(CMAKE) << "adding to cmake config: builddir path " << bd.buildFolder(); qCDebug(CMAKE) << "adding to cmake config: installdir " << bd.installPrefix(); qCDebug(CMAKE) << "adding to cmake config: extra args" << bd.extraArguments(); qCDebug(CMAKE) << "adding to cmake config: build type " << bd.buildType(); + qCDebug(CMAKE) << "adding to cmake config: cmake executable " << bd.cmakeExecutable(); qCDebug(CMAKE) << "adding to cmake config: environment "; CMake::setBuildDirCount( project, addedBuildDirIndex + 1 ); CMake::setCurrentBuildDirIndex( project, addedBuildDirIndex ); CMake::setCurrentBuildDir( project, bd.buildFolder() ); CMake::setCurrentInstallDir( project, bd.installPrefix() ); CMake::setCurrentExtraArguments( project, bd.extraArguments() ); CMake::setCurrentBuildType( project, bd.buildType() ); + CMake::setCurrentCMakeExecutable(project, bd.cmakeExecutable()); CMake::setCurrentEnvironment( project, QString() ); } setBuildDirRuntime( project, currentRuntime ); return true; } else if( !QFile::exists( KDevelop::Path(builddir, QStringLiteral("CMakeCache.txt")).toLocalFile() ) || //TODO: maybe we could use the builder for that? !(QFile::exists( KDevelop::Path(builddir, QStringLiteral("Makefile")).toLocalFile() ) || QFile::exists( KDevelop::Path(builddir, QStringLiteral("build.ninja")).toLocalFile() ) ) ) { // User entered information already, but cmake hasn't actually been run yet. setBuildDirRuntime( project, currentRuntime ); return true; } setBuildDirRuntime( project, currentRuntime ); return false; } QHash enumerateTargets(const KDevelop::Path& targetsFilePath, const QString& sourceDir, const KDevelop::Path &buildDir) { const QString buildPath = buildDir.toLocalFile(); QHash targets; QFile targetsFile(targetsFilePath.toLocalFile()); if (!targetsFile.open(QIODevice::ReadOnly)) { qCDebug(CMAKE) << "Couldn't find the Targets file in" << targetsFile.fileName(); } QTextStream targetsFileStream(&targetsFile); const QRegularExpression rx(QStringLiteral("^(.*)/CMakeFiles/(.*).dir$")); while (!targetsFileStream.atEnd()) { const QString line = targetsFileStream.readLine(); auto match = rx.match(line); if (!match.isValid()) qCDebug(CMAKE) << "invalid match for" << line; const QString sourcePath = match.captured(1).replace(buildPath, sourceDir); targets[KDevelop::Path(sourcePath)].append(match.captured(2)); } return targets; } KDevelop::Path projectRoot(KDevelop::IProject* project) { if (!project) { return {}; } return project->path().cd(CMake::projectRootRelative(project)); } KDevelop::Path currentBuildDir( KDevelop::IProject* project, int builddir ) { return KDevelop::Path(readBuildDirParameter( project, Config::Specific::buildDirPathKey, QString(), builddir )); } KDevelop::Path commandsFile(KDevelop::IProject* project) { auto currentBuildDir = CMake::currentBuildDir(project); if (currentBuildDir.isEmpty()) { return {}; } return KDevelop::Path(currentBuildDir, QStringLiteral("compile_commands.json")); } KDevelop::Path targetDirectoriesFile(KDevelop::IProject* project) { auto currentBuildDir = CMake::currentBuildDir(project); if (currentBuildDir.isEmpty()) { return {}; } return KDevelop::Path(currentBuildDir, QStringLiteral("CMakeFiles/TargetDirectories.txt")); } QString currentBuildType( KDevelop::IProject* project, int builddir ) { return readBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, QStringLiteral("Release"), builddir ); } QString findExecutable() { auto cmake = QStandardPaths::findExecutable(QStringLiteral("cmake")); #ifdef Q_OS_WIN if (cmake.isEmpty()) cmake = QStandardPaths::findExecutable("cmake",{ "C:\\Program Files (x86)\\CMake\\bin", "C:\\Program Files\\CMake\\bin", "C:\\Program Files (x86)\\CMake 2.8\\bin", "C:\\Program Files\\CMake 2.8\\bin"}); #endif return cmake; } +KDevelop::Path currentCMakeExecutable(KDevelop::IProject* project, int builddir) +{ + const auto defaultCMakeExecutable = CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile(); + if (project) { + // check for "CMake Executable" but for now also "CMake Binary", falling back to the default. + auto projectCMakeExecutable = readBuildDirParameter( project, Config::Specific::cmakeExecutableKey, + readBuildDirParameter( project, Config::Specific::cmakeBinaryKey, defaultCMakeExecutable, builddir), + builddir ); + if (projectCMakeExecutable != defaultCMakeExecutable) { + QFileInfo info(projectCMakeExecutable); + if (!info.isExecutable()) { + projectCMakeExecutable = defaultCMakeExecutable; + } + } + return KDevelop::Path(projectCMakeExecutable); + } + return KDevelop::Path(defaultCMakeExecutable); +} + KDevelop::Path currentInstallDir( KDevelop::IProject* project, int builddir ) { const QString defaultInstallDir = #ifdef Q_OS_WIN QStringLiteral("C:\\Program Files"); #else QStringLiteral("/usr/local"); #endif return KDevelop::Path(readBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, defaultInstallDir, builddir )); } QString projectRootRelative( KDevelop::IProject* project ) { return baseGroup(project).readEntry( Config::Old::projectRootRelativeKey, "." ); } bool hasProjectRootRelative(KDevelop::IProject* project) { return baseGroup(project).hasKey( Config::Old::projectRootRelativeKey ); } QString currentExtraArguments( KDevelop::IProject* project, int builddir ) { return readBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, QString(), builddir ); } QString currentCmakeExecutable( KDevelop::IProject* project, int builddir ) { return readBuildDirParameter( project, Config::Specific::cmakeExecutableKey, QString(), builddir ); } void setCurrentInstallDir( KDevelop::IProject* project, const KDevelop::Path& path ) { writeBuildDirParameter( project, Config::Specific::cmakeInstallDirKey, path.toLocalFile() ); } void setCurrentBuildType( KDevelop::IProject* project, const QString& type ) { writeBuildDirParameter( project, Config::Specific::cmakeBuildTypeKey, type ); } +void setCurrentCMakeExecutable(KDevelop::IProject* project, const KDevelop::Path& path) +{ + // maintain compatibility with older versions for now + writeBuildDirParameter(project, Config::Specific::cmakeBinaryKey, path.toLocalFile()); + writeBuildDirParameter(project, Config::Specific::cmakeExecutableKey, path.toLocalFile()); +} + void setCurrentBuildDir( KDevelop::IProject* project, const KDevelop::Path& path ) { writeBuildDirParameter( project, Config::Specific::buildDirPathKey, path.toLocalFile() ); } void setProjectRootRelative( KDevelop::IProject* project, const QString& relative) { writeProjectBaseParameter( project, Config::Old::projectRootRelativeKey, relative ); } void setCurrentExtraArguments( KDevelop::IProject* project, const QString& string) { writeBuildDirParameter( project, Config::Specific::cmakeArgumentsKey, string ); } void setCurrentCmakeExecutable( KDevelop::IProject* project, const QString& string ) { writeBuildDirParameter( project, Config::Specific::cmakeExecutableKey, string ); } QString currentEnvironment(KDevelop::IProject* project, int builddir) { return readBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, QString(), builddir ); } int currentBuildDirIndex( KDevelop::IProject* project ) { KConfigGroup baseGrp = baseGroup(project); if ( baseGrp.hasKey( Config::buildDirOverrideIndexKey ) ) return baseGrp.readEntry( Config::buildDirOverrideIndexKey, 0 ); else if (baseGrp.hasKey(Config::buildDirIndexKey())) return baseGrp.readEntry( Config::buildDirIndexKey(), 0 ); else return baseGrp.readEntry( Config::buildDirIndexKey_, 0 ); // backwards compatibility } void setCurrentBuildDirIndex( KDevelop::IProject* project, int buildDirIndex ) { writeProjectBaseParameter( project, Config::buildDirIndexKey(), QString::number (buildDirIndex) ); } void setCurrentEnvironment( KDevelop::IProject* project, const QString& environment ) { writeBuildDirParameter( project, Config::Specific::cmakeEnvironmentKey, environment ); } void initBuildDirConfig( KDevelop::IProject* project ) { int buildDirIndex = currentBuildDirIndex( project ); if (buildDirCount(project) <= buildDirIndex ) setBuildDirCount( project, buildDirIndex + 1 ); } int buildDirCount( KDevelop::IProject* project ) { return baseGroup(project).readEntry( Config::buildDirCountKey, 0 ); } void setBuildDirCount( KDevelop::IProject* project, int count ) { writeProjectBaseParameter( project, Config::buildDirCountKey, QString::number(count) ); } void removeBuildDirConfig( KDevelop::IProject* project ) { int buildDirIndex = currentBuildDirIndex( project ); if ( !buildDirGroupExists( project, buildDirIndex ) ) { qCWarning(CMAKE) << "build directory config" << buildDirIndex << "to be removed but does not exist"; return; } int bdCount = buildDirCount(project); setBuildDirCount( project, bdCount - 1 ); removeOverrideBuildDirIndex( project ); setCurrentBuildDirIndex( project, -1 ); // move (rename) the upper config groups to keep the numbering // if there's nothing to move, just delete the group physically if (buildDirIndex + 1 == bdCount) buildDirGroup( project, buildDirIndex ).deleteGroup(); else for (int i = buildDirIndex + 1; i < bdCount; ++i) { KConfigGroup src = buildDirGroup( project, i ); KConfigGroup dest = buildDirGroup( project, i - 1 ); dest.deleteGroup(); src.copyTo(&dest); src.deleteGroup(); } } QHash readCacheValues(const KDevelop::Path& cmakeCachePath, QSet variables) { QHash ret; QFile file(cmakeCachePath.toLocalFile()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qCWarning(CMAKE) << "couldn't open CMakeCache.txt" << cmakeCachePath; return ret; } QTextStream in(&file); while (!in.atEnd() && !variables.isEmpty()) { QString line = in.readLine().trimmed(); if(!line.isEmpty() && line[0].isLetter()) { CacheLine c; c.readLine(line); if(!c.isCorrect()) continue; if (variables.remove(c.name())) { ret[c.name()] = c.value(); } } } return ret; } void updateConfig( KDevelop::IProject* project, int buildDirIndex) { if (buildDirIndex < 0) return; KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex ); const KDevelop::Path builddir(buildDirGrp.readEntry( Config::Specific::buildDirPathKey, QString() )); const KDevelop::Path cacheFilePath( builddir, QStringLiteral("CMakeCache.txt")); const QMap keys = { + { QStringLiteral("CMAKE_COMMAND"), Config::Specific::cmakeExecutableKey }, { QStringLiteral("CMAKE_INSTALL_PREFIX"), Config::Specific::cmakeInstallDirKey }, { QStringLiteral("CMAKE_BUILD_TYPE"), Config::Specific::cmakeBuildTypeKey } }; const QHash cacheValues = readCacheValues(cacheFilePath, keys.keys().toSet()); for(auto it = cacheValues.constBegin(), itEnd = cacheValues.constEnd(); it!=itEnd; ++it) { const QString key = keys.value(it.key()); Q_ASSERT(!key.isEmpty()); // Use cache only when the config value is not set. Without this check we will always // overwrite values provided by the user in config dialog. if (buildDirGrp.readEntry(key).isEmpty() && !it.value().isEmpty()) { buildDirGrp.writeEntry( key, it.value() ); } } } void attemptMigrate( KDevelop::IProject* project ) { if ( !baseGroup(project).hasKey( Config::Old::projectBuildDirs ) ) { qCDebug(CMAKE) << "CMake settings migration: already done, exiting"; return; } KConfigGroup baseGrp = baseGroup(project); KDevelop::Path buildDir( baseGrp.readEntry( Config::Old::currentBuildDirKey, QString() ) ); int buildDirIndex = -1; const QStringList existingBuildDirs = baseGrp.readEntry( Config::Old::projectBuildDirs, QStringList() ); { // also, find current build directory in this list (we need an index, not path) QString currentBuildDirCanonicalPath = QDir( buildDir.toLocalFile() ).canonicalPath(); for( int i = 0; i < existingBuildDirs.count(); ++i ) { const QString& nextBuildDir = existingBuildDirs.at(i); if( QDir(nextBuildDir).canonicalPath() == currentBuildDirCanonicalPath ) { buildDirIndex = i; } } } int buildDirsCount = existingBuildDirs.count(); qCDebug(CMAKE) << "CMake settings migration: existing build directories" << existingBuildDirs; qCDebug(CMAKE) << "CMake settings migration: build directory count" << buildDirsCount; qCDebug(CMAKE) << "CMake settings migration: current build directory" << buildDir << "(index" << buildDirIndex << ")"; baseGrp.writeEntry( Config::buildDirCountKey, buildDirsCount ); baseGrp.writeEntry( Config::buildDirIndexKey(), buildDirIndex ); for (int i = 0; i < buildDirsCount; ++i) { qCDebug(CMAKE) << "CMake settings migration: writing group" << i << ": path" << existingBuildDirs.at(i); KConfigGroup buildDirGrp = buildDirGroup( project, i ); buildDirGrp.writeEntry( Config::Specific::buildDirPathKey, existingBuildDirs.at(i) ); } baseGrp.deleteEntry( Config::Old::currentBuildDirKey ); + baseGrp.deleteEntry( Config::Old::currentCMakeExecutableKey ); baseGrp.deleteEntry( Config::Old::currentBuildTypeKey ); baseGrp.deleteEntry( Config::Old::currentInstallDirKey ); baseGrp.deleteEntry( Config::Old::currentEnvironmentKey ); baseGrp.deleteEntry( Config::Old::currentExtraArgumentsKey ); baseGrp.deleteEntry( Config::Old::projectBuildDirs ); } void setOverrideBuildDirIndex( KDevelop::IProject* project, int overrideBuildDirIndex ) { writeProjectBaseParameter( project, Config::buildDirOverrideIndexKey, QString::number(overrideBuildDirIndex) ); } void removeOverrideBuildDirIndex( KDevelop::IProject* project, bool writeToMainIndex ) { KConfigGroup baseGrp = baseGroup(project); if( !baseGrp.hasKey(Config::buildDirOverrideIndexKey) ) return; if( writeToMainIndex ) baseGrp.writeEntry( Config::buildDirIndexKey(), baseGrp.readEntry(Config::buildDirOverrideIndexKey) ); baseGrp.deleteEntry(Config::buildDirOverrideIndexKey); } ICMakeDocumentation* cmakeDocumentation() { return KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.ICMakeDocumentation")); } QStringList allBuildDirs(KDevelop::IProject* project) { QStringList result; int bdCount = buildDirCount(project); for (int i = 0; i < bdCount; ++i) result += buildDirGroup( project, i ).readEntry( Config::Specific::buildDirPathKey ); return result; } QString executeProcess(const QString& execName, const QStringList& args) { Q_ASSERT(!execName.isEmpty()); qCDebug(CMAKE) << "Executing:" << execName << "::" << args; QProcess p; QTemporaryDir tmp(QStringLiteral("kdevcmakemanager")); p.setWorkingDirectory( tmp.path() ); p.start(execName, args, QIODevice::ReadOnly); if(!p.waitForFinished()) { qCDebug(CMAKE) << "failed to execute:" << execName << args << p.exitStatus() << p.readAllStandardError(); } QByteArray b = p.readAllStandardOutput(); QString t; t.prepend(b.trimmed()); return t; } QStringList supportedGenerators() { QStringList generatorNames; bool hasNinja = ICore::self() && ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevNinjaBuilder")); if (hasNinja) generatorNames << QStringLiteral("Ninja"); #ifdef Q_OS_WIN // Visual Studio solution is the standard generator under windows, but we don't want to use // the VS IDE, so we need nmake makefiles generatorNames << QStringLiteral("NMake Makefiles") << QStringLiteral("MinGW Makefiles"); #endif generatorNames << QStringLiteral("Unix Makefiles"); return generatorNames; } QString defaultGenerator() { const QStringList generatorNames = supportedGenerators(); QString defGen = generatorNames.value(CMakeBuilderSettings::self()->generator()); if (defGen.isEmpty()) { qCWarning(CMAKE) << "Couldn't find builder with index " << CMakeBuilderSettings::self()->generator() << ", defaulting to 0"; CMakeBuilderSettings::self()->setGenerator(0); defGen = generatorNames.at(0); } return defGen; } QVector importTestSuites(const Path &buildDir) { const auto contents = CMakeListsParser::readCMakeFile(buildDir.toLocalFile() + "/CTestTestfile.cmake"); QVector tests; for (const auto& entry: contents) { if (entry.name == QLatin1String("add_test")) { auto args = entry.arguments; Test test; test.name = args.takeFirst().value; test.executable = args.takeFirst().value; test.arguments = kTransform(args, [](const CMakeFunctionArgument& arg) { return arg.value; }); tests += test; } else if (entry.name == QLatin1String("subdirs")) { tests += importTestSuites(Path(buildDir, entry.arguments.constFirst().value)); } else if (entry.name == QLatin1String("set_tests_properties")) { if(entry.arguments.count() < 4 || entry.arguments.count() % 2) { qCWarning(CMAKE) << "found set_tests_properties() with unexpected number of arguments:" << entry.arguments.count(); continue; } if (tests.isEmpty() || entry.arguments.constFirst().value != tests.constLast().name) { qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.constFirst().value << " ...), but expected test " << tests.constLast().name; continue; } if (entry.arguments[1].value != QLatin1String("PROPERTIES")) { qCWarning(CMAKE) << "found set_tests_properties(" << entry.arguments.constFirst().value << entry.arguments.at(1).value << "...), but expected PROPERTIES as second argument"; continue; } Test &test = tests.last(); for (int i = 2; i < entry.arguments.count(); i += 2) test.properties[entry.arguments[i].value] = entry.arguments[i + 1].value; } } return tests; } } diff --git a/plugins/cmake/cmakeutils.h b/plugins/cmake/cmakeutils.h index a70cb757d5..357478f3cc 100644 --- a/plugins/cmake/cmakeutils.h +++ b/plugins/cmake/cmakeutils.h @@ -1,267 +1,277 @@ /* KDevelop CMake Support * * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEUTILS_H #define CMAKEUTILS_H #include "cmakecommonexport.h" #include #include #include namespace KDevelop { class ProjectBaseItem; class IProject; class Path; } struct Test; class ICMakeDocumentation; class CMakeCacheModel; template static T kTransform(const Q& list, W func) { T ret; ret.reserve(list.size()); for (auto it = list.constBegin(), itEnd = list.constEnd(); it!=itEnd; ++it) ret += func(*it); return ret; } template static int kIndexOf(const Q& list, W func) { int i = 0; for (auto it = list.constBegin(), itEnd = list.constEnd(); it!=itEnd; ++it) { if (func(*it)) return i; ++i; } return -1; } template static T kFilter(const Q &input, _UnaryOperation op) { T ret; for(const auto& v : input) { if (op(v)) ret += v; } return ret; } namespace CMake { /** * Checks whether there's a need to run cmake for the given project item * This is the case if no builddir has been specified, in which case * it asks for one. * * @returns true if configure should be run, false otherwise */ KDEVCMAKECOMMON_EXPORT bool checkForNeedingConfigure( KDevelop::IProject* project ); /** * @returns the current builddir for the given project or an empty url if none * has been set by the user. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path currentBuildDir( KDevelop::IProject* project, int builddir = -1 ); /** * @returns the path to the 'compile_commands.json' file in the current builddir for the given project * or an empty url if none has been set by the user. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path commandsFile( KDevelop::IProject* project ); /** * @returns the path to the 'CMakeFiles/TargetDirectories.txt' file in the current builddir for the given project * or an empty url if none has been set by the user. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path targetDirectoriesFile( KDevelop::IProject* project ); /** * @returns the current build type for the given project or "Release" as default value. */ KDEVCMAKECOMMON_EXPORT QString currentBuildType( KDevelop::IProject* project, int builddir = -1 ); /** * @returns the current build type for the given project or "Release" as default value. */ KDEVCMAKECOMMON_EXPORT QString currentCmakeExecutable( KDevelop::IProject* project, int builddir = -1 ); /** * @returns the CMake executable, taking into account standard * installation dirs on Windows, or empty string in case of failure. */ KDEVCMAKECOMMON_EXPORT QString findExecutable(); + /** + * @returns the current CMake executable for the given project, falling back to + * CMakeBuilderSettings::self()->cmakeExecutable() as the user-specified KDevelop-wide default value. + */ + KDEVCMAKECOMMON_EXPORT KDevelop::Path currentCMakeExecutable(KDevelop::IProject* project, int builddir = -1); /** * @returns the current install dir for the given project or "/usr/local" as default value. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path currentInstallDir( KDevelop::IProject* project, int builddir = -1 ); /** * @returns the current extra arguments for the given project or "" as default value. */ KDEVCMAKECOMMON_EXPORT QString currentExtraArguments( KDevelop::IProject* project, int builddir = -1 ); /** * @returns the current build dir for the given project. */ KDEVCMAKECOMMON_EXPORT QString projectRootRelative( KDevelop::IProject* project ); /** * @returns whether there's projectRootRelative defined */ KDEVCMAKECOMMON_EXPORT bool hasProjectRootRelative( KDevelop::IProject* project ); /** * Extracts target names from builddir/CMakeFiles/TargetDirectories.txt and maps corresponding source locations to them. */ KDEVCMAKECOMMON_EXPORT QHash enumerateTargets(const KDevelop::Path& targetsFilePath, const QString& sourceDir, const KDevelop::Path &buildDir); /** * Convenience function to get the project root. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path projectRoot( KDevelop::IProject* project ); /** * @returns the environment configuration for a @p project */ KDEVCMAKECOMMON_EXPORT QString currentEnvironment( KDevelop::IProject* project, int builddir = -1 ); /** * Sets the current install dir for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentInstallDir( KDevelop::IProject* project, const KDevelop::Path &path ); /** * Sets the current build type for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentBuildType( KDevelop::IProject* project, const QString& type ); + /** + * Sets the current CMake executable for the given project. + */ + KDEVCMAKECOMMON_EXPORT void setCurrentCMakeExecutable(KDevelop::IProject* project, const KDevelop::Path& path); + /** * Sets the current build dir for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentBuildDir( KDevelop::IProject* project, const KDevelop::Path& path ); /** * Sets the current build dir for the given project. */ KDEVCMAKECOMMON_EXPORT void setProjectRootRelative( KDevelop::IProject* project, const QString& path); /** * Sets the current extra arguments for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentExtraArguments( KDevelop::IProject* project, const QString& args ); /** * Sets the current CMake binary the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentCmakeExecutable(KDevelop::IProject* project, const QString& string); /** * Obtains a cmake documentation instance if it exists */ KDEVCMAKECOMMON_EXPORT ICMakeDocumentation* cmakeDocumentation(); /** * Retrieves the configured build directories for @p project. */ KDEVCMAKECOMMON_EXPORT QStringList allBuildDirs(KDevelop::IProject* project); /** * Attempts to migrate the CMake configuration to per-builddir format. * Silently returns if the migration has already been performed. */ KDEVCMAKECOMMON_EXPORT void attemptMigrate( KDevelop::IProject* project ); /** * Attempts to update CMake configuration keys from the cache data (CMakeCache.txt) * * The model is created based on build directory path for the given index @p buildDirectory */ KDEVCMAKECOMMON_EXPORT void updateConfig( KDevelop::IProject* project, int buildDirectory); /** * Returns the current build directory count. */ KDEVCMAKECOMMON_EXPORT int buildDirCount( KDevelop::IProject* project ); /** * Sets the build directory count (equivalent to adding a new build directory). */ KDEVCMAKECOMMON_EXPORT void setBuildDirCount( KDevelop::IProject* project, int count ); /** * @returns the current builddir index for the given project or -1 if none * has been set by the user. */ KDEVCMAKECOMMON_EXPORT int currentBuildDirIndex( KDevelop::IProject *project ); /** * Sets the current build dir index for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentBuildDirIndex( KDevelop::IProject* project, int buildDirIndex ); /** * A hack to avoid adding an optional "build directory index" parameter to all functions here. * This function sets an alternate build directory index key that overrides regular build directory index. */ KDEVCMAKECOMMON_EXPORT void setOverrideBuildDirIndex( KDevelop::IProject* project, int overrideBuildDirIndex ); /** * This removes build directory override key (\ref setOverrideBuildDirIndex). * Silently returns if there is no override. * * @param writeToMainIndex Whether the overridden index should be saved to regular */ KDEVCMAKECOMMON_EXPORT void removeOverrideBuildDirIndex( KDevelop::IProject* project, bool writeToMainIndex = false ); /** * Sets the environment configuration for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentEnvironment( KDevelop::IProject* project, const QString& environment ); /** * Removes current build directory (overridden or not) from the project configuration. * Override is then cleared and index set to -1. */ KDEVCMAKECOMMON_EXPORT void removeBuildDirConfig( KDevelop::IProject* project ); KDEVCMAKECOMMON_EXPORT KDevelop::Path::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs); /** Runs the process specified by @p execName with @p args */ KDEVCMAKECOMMON_EXPORT QString executeProcess(const QString& execName, const QStringList& args=QStringList()); /** Opens @p cmakeCachePath and parses it, returns a hash with the specified keys in @p variables */ KDEVCMAKECOMMON_EXPORT QHash readCacheValues(const KDevelop::Path& cmakeCachePath, QSet variables); KDEVCMAKECOMMON_EXPORT QStringList supportedGenerators(); KDEVCMAKECOMMON_EXPORT QString defaultGenerator(); KDEVCMAKECOMMON_EXPORT QVector importTestSuites(const KDevelop::Path &buildDir); } #endif diff --git a/plugins/cmake/settings/cmakepreferences.cpp b/plugins/cmake/settings/cmakepreferences.cpp index 6800a0ed1c..a5ae6534df 100644 --- a/plugins/cmake/settings/cmakepreferences.cpp +++ b/plugins/cmake/settings/cmakepreferences.cpp @@ -1,406 +1,411 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2008 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakepreferences.h" #include #include #include #include #include #include #include #include #include #include "ui_cmakebuildsettings.h" #include "cmakecachedelegate.h" #include "cmakebuilddirchooser.h" +#include "cmakebuilderconfig.h" #include #include #include #include #include #include using namespace KDevelop; CMakePreferences::CMakePreferences(IPlugin* plugin, const ProjectConfigOptions& options, QWidget* parent) : ConfigPage(plugin, nullptr, parent), m_project(options.project), m_currentModel(nullptr) { m_prefsUi = new Ui::CMakeBuildSettings; m_prefsUi->setupUi(this); m_prefsUi->addBuildDir->setIcon(QIcon::fromTheme( QStringLiteral("list-add") )); m_prefsUi->removeBuildDir->setIcon(QIcon::fromTheme( QStringLiteral("list-remove") )); m_prefsUi->addBuildDir->setText(QString()); m_prefsUi->removeBuildDir->setText(QString()); m_prefsUi->cacheList->setItemDelegate(new CMakeCacheDelegate(m_prefsUi->cacheList)); m_prefsUi->cacheList->setSelectionMode(QAbstractItemView::SingleSelection); m_prefsUi->cacheList->horizontalHeader()->setStretchLastSection(true); m_prefsUi->cacheList->verticalHeader()->hide(); // configure the extraArguments widget to span the advanced box width but not // expand the dialog to the width of the longest element in the argument history. // static_cast needed because KComboBox::minimumSizeHint() override mistakingly made it protected m_prefsUi->extraArguments->setMinimumWidth(static_cast(m_prefsUi->extraArguments)->minimumSizeHint().width()); m_extraArgumentsHistory = new CMakeExtraArgumentsHistory(m_prefsUi->extraArguments); connect(m_prefsUi->buildDirs, static_cast(&KComboBox::currentIndexChanged), this, &CMakePreferences::buildDirChanged); connect(m_prefsUi->showInternal, &QCheckBox::stateChanged, this, &CMakePreferences::showInternal); connect(m_prefsUi->addBuildDir, &QPushButton::pressed, this, &CMakePreferences::createBuildDir); connect(m_prefsUi->removeBuildDir, &QPushButton::pressed, this, &CMakePreferences::removeBuildDir); connect(m_prefsUi->showAdvanced, &QPushButton::toggled, this, &CMakePreferences::showAdvanced); connect(m_prefsUi->environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &CMakePreferences::changed); connect(m_prefsUi->configureEnvironment, &EnvironmentConfigureButton::environmentConfigured, this, &CMakePreferences::changed); connect(m_prefsUi->installationPrefix, &KUrlRequester::textChanged, this, &CMakePreferences::changed); connect(m_prefsUi->buildType, static_cast(&QComboBox::currentIndexChanged), this, &CMakePreferences::changed); connect(m_prefsUi->buildType, &QComboBox::currentTextChanged, this, &CMakePreferences::changed); connect(m_prefsUi->extraArguments, &KComboBox::currentTextChanged, this, &CMakePreferences::changed); connect(m_prefsUi->extraArguments, &KComboBox::editTextChanged, this, &CMakePreferences::changed); connect(m_prefsUi->cMakeExecutable, &KUrlRequester::textChanged, this, &CMakePreferences::changed); showInternal(m_prefsUi->showInternal->checkState()); m_subprojFolder = Path(options.projectTempFile).parent(); qCDebug(CMAKE) << "Source folder: " << m_srcFolder << options.projectTempFile; // foreach(const QVariant &v, args) // { // qCDebug(CMAKE) << "arg: " << v.toString(); // } m_prefsUi->configureEnvironment->setSelectionWidget(m_prefsUi->environment); m_prefsUi->showAdvanced->setChecked(false); showAdvanced(false); reset(); // load the initial values } CMakePreferences::~CMakePreferences() { CMake::removeOverrideBuildDirIndex(m_project); delete m_extraArgumentsHistory; delete m_prefsUi; } void CMakePreferences::initAdvanced() { m_prefsUi->environment->setCurrentProfile( CMake::currentEnvironment(m_project) ); m_prefsUi->installationPrefix->setText(CMake::currentInstallDir(m_project).toLocalFile()); m_prefsUi->installationPrefix->setMode(KFile::Directory); const QString buildType = CMake::currentBuildType(m_project); if (m_prefsUi->buildType->findText(buildType) == -1) { m_prefsUi->buildType->addItem(buildType); } m_prefsUi->buildType->setCurrentIndex(m_prefsUi->buildType->findText(buildType)); m_prefsUi->extraArguments->setEditText(CMake::currentExtraArguments(m_project)); m_prefsUi->cMakeExecutable->setText(CMake::currentCmakeExecutable(m_project)); } void CMakePreferences::reset() { qCDebug(CMAKE) << "********loading"; m_prefsUi->buildDirs->clear(); m_prefsUi->buildDirs->addItems( CMake::allBuildDirs(m_project) ); CMake::removeOverrideBuildDirIndex(m_project); // addItems() triggers buildDirChanged(), compensate for it m_prefsUi->buildDirs->setCurrentIndex( CMake::currentBuildDirIndex(m_project) ); initAdvanced(); m_srcFolder = m_project->path(); m_prefsUi->removeBuildDir->setEnabled(m_prefsUi->buildDirs->count()!=0); // QString cmDir=group.readEntry("CMakeDirectory"); // m_prefsUi->kcfg_cmakeDir->setUrl(QUrl(cmDir)); // qCDebug(CMAKE) << "cmakedir" << cmDir; } void CMakePreferences::apply() { qCDebug(CMAKE) << "*******saving"; // the build directory list is incrementally maintained through createBuildDir() and removeBuildDir(). // We won't rewrite it here based on the data from m_prefsUi->buildDirs. CMake::removeOverrideBuildDirIndex( m_project, true ); // save current selection int savedBuildDir = CMake::currentBuildDirIndex(m_project); if (savedBuildDir < 0) { // no build directory exists: skip any writing to config file as well as configuring return; } CMake::setCurrentEnvironment( m_project, m_prefsUi->environment->currentProfile() ); CMake::setCurrentInstallDir( m_project, Path(m_prefsUi->installationPrefix->text()) ); const QString buildType = m_prefsUi->buildType->currentText(); if (m_prefsUi->buildType->findText(buildType) == -1) { m_prefsUi->buildType->addItem(buildType); } CMake::setCurrentBuildType( m_project, buildType ); CMake::setCurrentExtraArguments( m_project, m_prefsUi->extraArguments->currentText() ); CMake::setCurrentCmakeExecutable( m_project, m_prefsUi->cMakeExecutable->text() ); - qCDebug(CMAKE) << "writing to cmake config: using builddir " << CMake::currentBuildDirIndex(m_project); qCDebug(CMAKE) << "writing to cmake config: builddir path " << CMake::currentBuildDir(m_project); qCDebug(CMAKE) << "writing to cmake config: installdir " << CMake::currentInstallDir(m_project); qCDebug(CMAKE) << "writing to cmake config: build type " << CMake::currentBuildType(m_project); + qCDebug(CMAKE) << "writing to cmake config: cmake executable " << CMake::currentCMakeExecutable(m_project); qCDebug(CMAKE) << "writing to cmake config: environment " << CMake::currentEnvironment(m_project); qCDebug(CMAKE) << "writing to cmake config: cmake executable " << CMake::currentCmakeExecutable(m_project); //We run cmake on the builddir to generate it configure(); } void CMakePreferences::defaults() { // do nothing } void CMakePreferences::configureCacheView() { // Sets up the cache view after model re-creation/reset. // Emits changed(false) because model re-creation probably means // mass programmatical invocation of itemChanged(), which invokes changed(true) - which is not what we want. m_prefsUi->cacheList->setModel(m_currentModel); m_prefsUi->cacheList->hideColumn(1); m_prefsUi->cacheList->hideColumn(3); m_prefsUi->cacheList->hideColumn(4); m_prefsUi->cacheList->horizontalHeader()->resizeSection(0, 200); if( m_currentModel ) { m_prefsUi->cacheList->setEnabled( true ); foreach(const QModelIndex & idx, m_currentModel->persistentIndices()) { m_prefsUi->cacheList->openPersistentEditor(idx); } } else { m_prefsUi->cacheList->setEnabled( false ); } showInternal(m_prefsUi->showInternal->checkState()); } void CMakePreferences::updateCache(const Path &newBuildDir) { const Path file = newBuildDir.isValid() ? Path(newBuildDir, QStringLiteral("CMakeCache.txt")) : Path(); if(QFile::exists(file.toLocalFile())) { if (m_currentModel) { m_currentModel->deleteLater(); } m_currentModel = new CMakeCacheModel(this, file); configureCacheView(); connect(m_currentModel, &CMakeCacheModel::itemChanged, this, &CMakePreferences::cacheEdited); connect(m_currentModel, &CMakeCacheModel::modelReset, this, &CMakePreferences::configureCacheView); connect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, &CMakePreferences::listSelectionChanged); } else { disconnect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, nullptr); if (m_currentModel) { m_currentModel->deleteLater(); m_currentModel = nullptr; } configureCacheView(); } if( !m_currentModel ) emit changed(); } void CMakePreferences::listSelectionChanged(const QModelIndex & index, const QModelIndex& ) { qCDebug(CMAKE) << "item " << index << " selected"; QModelIndex idx = index.sibling(index.row(), 3); QModelIndex idxType = index.sibling(index.row(), 1); QString comment=QStringLiteral("%1. %2") .arg(m_currentModel->itemFromIndex(idxType)->text()) .arg(m_currentModel->itemFromIndex(idx)->text()); m_prefsUi->commentText->setText(comment); } void CMakePreferences::showInternal(int state) { if(!m_currentModel) return; bool showAdv=(state == Qt::Checked); for(int i=0; irowCount(); i++) { bool hidden=m_currentModel->isInternal(i) || (!showAdv && m_currentModel->isAdvanced(i)); m_prefsUi->cacheList->setRowHidden(i, hidden); } } void CMakePreferences::buildDirChanged(int index) { CMake::setOverrideBuildDirIndex( m_project, index ); const Path buildDir = CMake::currentBuildDir(m_project); initAdvanced(); updateCache(buildDir); qCDebug(CMAKE) << "builddir Changed" << buildDir; emit changed(); } void CMakePreferences::cacheUpdated() { const Path buildDir = CMake::currentBuildDir(m_project); updateCache(buildDir); qCDebug(CMAKE) << "cache updated for" << buildDir; } void CMakePreferences::createBuildDir() { CMakeBuildDirChooser bdCreator; bdCreator.setProject( m_project ); // NOTE: (on removing the trailing slashes) // Generally, we have no clue about how shall a trailing slash look in the current system. // Moreover, the slash may be a part of the filename. // It may be '/' or '\', so maybe should we rely on CMake::allBuildDirs() for returning well-formed paths? QStringList used = CMake::allBuildDirs( m_project ); bdCreator.setAlreadyUsed(used); + bdCreator.setCMakeExecutable(Path(CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile())); if(bdCreator.exec()) { int addedBuildDirIndex = m_prefsUi->buildDirs->count(); // Initialize the kconfig items with the values from the dialog, this ensures the settings // end up in the config file once the changes are saved qCDebug(CMAKE) << "adding to cmake config: new builddir index" << addedBuildDirIndex; qCDebug(CMAKE) << "adding to cmake config: builddir path " << bdCreator.buildFolder(); qCDebug(CMAKE) << "adding to cmake config: installdir " << bdCreator.installPrefix(); qCDebug(CMAKE) << "adding to cmake config: extra args" << bdCreator.extraArguments(); qCDebug(CMAKE) << "adding to cmake config: build type " << bdCreator.buildType(); + qCDebug(CMAKE) << "adding to cmake config: cmake executable " << bdCreator.cmakeExecutable(); qCDebug(CMAKE) << "adding to cmake config: environment empty"; CMake::setOverrideBuildDirIndex( m_project, addedBuildDirIndex ); CMake::setBuildDirCount( m_project, addedBuildDirIndex + 1 ); CMake::setCurrentBuildDir( m_project, bdCreator.buildFolder() ); CMake::setCurrentInstallDir( m_project, bdCreator.installPrefix() ); CMake::setCurrentExtraArguments( m_project, bdCreator.extraArguments() ); CMake::setCurrentBuildType( m_project, bdCreator.buildType() ); + CMake::setCurrentCMakeExecutable(m_project, bdCreator.cmakeExecutable()); CMake::setCurrentEnvironment( m_project, QString() ); QString newbuilddir = bdCreator.buildFolder().toLocalFile(); m_prefsUi->buildDirs->addItem( newbuilddir ); m_prefsUi->buildDirs->setCurrentIndex( addedBuildDirIndex ); m_prefsUi->removeBuildDir->setEnabled( true ); qCDebug(CMAKE) << "Emitting changed signal for cmake kcm"; emit changed(); } //TODO: Save it for next runs } void CMakePreferences::removeBuildDir() { int curr=m_prefsUi->buildDirs->currentIndex(); if(curr < 0) return; Path removedPath = CMake::currentBuildDir( m_project ); QString removed = removedPath.toLocalFile(); if(QDir(removed).exists()) { KMessageBox::ButtonCode ret = KMessageBox::warningYesNo(this, i18n("The %1 directory is about to be removed in KDevelop's list.\n" "Do you want KDevelop to remove it in the file system as well?", removed)); if(ret == KMessageBox::Yes) { auto deleteJob = KIO::del(removedPath.toUrl()); KJobWidgets::setWindow(deleteJob, this); if (!deleteJob->exec()) KMessageBox::error(this, i18n("Could not remove: %1", removed)); } } qCDebug(CMAKE) << "removing from cmake config: using builddir " << curr; qCDebug(CMAKE) << "removing from cmake config: builddir path " << removedPath; qCDebug(CMAKE) << "removing from cmake config: installdir " << CMake::currentInstallDir( m_project ); qCDebug(CMAKE) << "removing from cmake config: extra args" << CMake::currentExtraArguments( m_project ); qCDebug(CMAKE) << "removing from cmake config: buildtype " << CMake::currentBuildType( m_project ); + qCDebug(CMAKE) << "removing from cmake config: cmake executable " << CMake::currentCMakeExecutable(m_project); qCDebug(CMAKE) << "removing from cmake config: environment " << CMake::currentEnvironment( m_project ); CMake::removeBuildDirConfig(m_project); m_prefsUi->buildDirs->removeItem( curr ); // this triggers buildDirChanged() if(m_prefsUi->buildDirs->count()==0) m_prefsUi->removeBuildDir->setEnabled(false); emit changed(); } void CMakePreferences::configure() { IProjectBuilder *b=m_project->buildSystemManager()->builder(); KJob* job=b->configure(m_project); if( m_currentModel ) { QVariantMap map = m_currentModel->changedValues(); job->setProperty("extraCMakeCacheValues", map); connect(job, &KJob::finished, m_currentModel, &CMakeCacheModel::reset); } else { connect(job, &KJob::finished, this, &CMakePreferences::cacheUpdated); } connect(job, &KJob::finished, m_project, &IProject::reloadModel); ICore::self()->runController()->registerJob(job); } void CMakePreferences::showAdvanced(bool v) { qCDebug(CMAKE) << "toggle pressed: " << v; m_prefsUi->advancedBox->setHidden(!v); } QString CMakePreferences::name() const { return i18n("CMake"); } QString CMakePreferences::fullName() const { return i18n("Configure CMake settings"); } QIcon CMakePreferences::icon() const { return QIcon::fromTheme("cmake"); } diff --git a/plugins/cmake/tests/CMakeLists.txt b/plugins/cmake/tests/CMakeLists.txt index c636abd91f..bc1ceb4e59 100644 --- a/plugins/cmake/tests/CMakeLists.txt +++ b/plugins/cmake/tests/CMakeLists.txt @@ -1,18 +1,12 @@ -include_directories( - ${KDevelop_SOURCE_DIR}/projectmanagers/cmake - ${KDevelop_BINARY_DIR}/projectmanagers/cmake - ${KDevelop_SOURCE_DIR}/projectmanagers/cmake/parser -) - configure_file("paths.h.cmake" "cmake-test-paths.h" ESCAPE_QUOTES) set(commonlibs Qt5::Test Qt5::Core KDev::Interfaces kdevcmakecommon) ecm_add_test(cmakeparsertest.cpp ../parser/cmListFileLexer.c TEST_NAME test_cmakeparser LINK_LIBRARIES ${commonlibs}) ecm_add_test(test_cmakemanager.cpp LINK_LIBRARIES ${commonlibs} KDev::Language KDev::Tests KDev::Project kdevcmakemanagernosettings) ecm_add_test(test_ctestfindsuites.cpp LINK_LIBRARIES ${commonlibs} KDev::Language KDev::Tests) ecm_add_test(test_cmakeserver.cpp LINK_LIBRARIES ${commonlibs} KDev::Language KDev::Tests KDev::Project kdevcmakemanagernosettings) # this is not a unit test but a testing tool, kept here for convenience add_executable(kdevprojectopen kdevprojectopen.cpp) target_link_libraries(kdevprojectopen Qt5::Test KDev::Project KDev::Tests kdevcmakecommon) diff --git a/plugins/cmake/tests/testhelpers.h b/plugins/cmake/tests/testhelpers.h index c6529cb808..64b9e0b3c9 100644 --- a/plugins/cmake/tests/testhelpers.h +++ b/plugins/cmake/tests/testhelpers.h @@ -1,150 +1,151 @@ /* This file is part of KDevelop Copyright 2012 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTHELPERS_H #define TESTHELPERS_H #include "cmake-test-paths.h" #include #include #include #include #include #include #include #include #include #include #include #include static QString currentBuildDirKey = QStringLiteral("Build Directory Path"); static QString currentCMakeExecutableKey = QStringLiteral("CMake Binary"); static QString currentBuildTypeKey = QStringLiteral("Build Type"); static QString currentInstallDirKey = QStringLiteral("Install Directory"); static QString currentExtraArgumentsKey = QStringLiteral("Extra Arguments"); static QString currentBuildDirectoryIndexKey = QStringLiteral("Current Build Directory Index"); static QString projectBuildDirectoryCount = QStringLiteral("Build Directory Count"); static QString projectRootRelativeKey = QStringLiteral("ProjectRootRelative"); static QString projectBuildDirs = QStringLiteral("BuildDirs"); static const QString buildDirRuntime = QStringLiteral("Runtime"); struct TestProjectPaths { // foo/ KDevelop::Path sourceDir; // foo/foo.kdev4 KDevelop::Path projectFile; // foo/.kdev4/foo.kdev4 KDevelop::Path configFile; }; TestProjectPaths projectPaths(const QString& project, const QString& name = QString()) { TestProjectPaths paths; if(QDir::isRelativePath(project)) { QFileInfo info(QStringLiteral(CMAKE_TESTS_PROJECTS_DIR)+"/"+project); Q_ASSERT(info.exists()); paths.sourceDir = KDevelop::Path(info.canonicalFilePath()); } else { paths.sourceDir = KDevelop::Path(project); } Q_ASSERT(QFile::exists(paths.sourceDir.toLocalFile())); QString kdev4Name; if (name.isEmpty()) { QDir d(paths.sourceDir.toLocalFile()); kdev4Name = d.entryList(QStringList(QStringLiteral("*.kdev4")), QDir::Files).takeFirst(); } else kdev4Name = name+".kdev4"; paths.projectFile = KDevelop::Path(paths.sourceDir, kdev4Name); Q_ASSERT(QFile::exists(paths.projectFile.toLocalFile())); paths.configFile = KDevelop::Path(paths.sourceDir, QString(QStringLiteral(".kdev4/") + kdev4Name)); return paths; } /** * apply default configuration to project in @p sourceDir called @p projectName * * this prevents the dialog to popup asking for user interaction * which should never happen in an automated unit test */ void defaultConfigure(const TestProjectPaths& paths) { KConfig config(paths.configFile.toLocalFile()); // clear config config.deleteGroup("CMake"); // apply default configuration CMakeBuildDirChooser bd; bd.setSourceFolder(paths.sourceDir); bd.setBuildFolder(KDevelop::Path(CMAKE_TESTS_BINARY_DIR + QStringLiteral("/build-") + paths.sourceDir.lastPathSegment())); // we don't want to execute, just pick the defaults from the dialog KConfigGroup cmakeGrp = config.group("CMake"); { QDir buildFolder( bd.buildFolder().toLocalFile() ); if ( !buildFolder.exists() ) { if ( !buildFolder.mkpath( buildFolder.absolutePath() ) ) { Q_ASSERT(false && "The build directory did not exist and could not be created."); } } } cmakeGrp.writeEntry( projectBuildDirectoryCount, 1); cmakeGrp.writeEntry( currentBuildDirectoryIndexKey, 0); KConfigGroup buildDirGrp = cmakeGrp.group(QStringLiteral("CMake Build Directory 0")); buildDirGrp.writeEntry( currentBuildDirKey, bd.buildFolder().toLocalFile() ); + buildDirGrp.writeEntry( currentCMakeExecutableKey, bd.cmakeExecutable().toLocalFile() ); buildDirGrp.writeEntry( currentInstallDirKey, bd.installPrefix().toLocalFile() ); buildDirGrp.writeEntry( currentExtraArgumentsKey, bd.extraArguments() ); buildDirGrp.writeEntry( currentBuildTypeKey, bd.buildType() ); buildDirGrp.writeEntry( projectBuildDirs, QStringList() << bd.buildFolder().toLocalFile()); buildDirGrp.writeEntry( buildDirRuntime, KDevelop::ICore::self()->runtimeController()->currentRuntime()->name()); config.sync(); } KDevelop::IProject* loadProject(const QString& name, const QString& relative = QString()) { qRegisterMetaType(); const TestProjectPaths paths = projectPaths(name+relative, name); defaultConfigure(paths); QSignalSpy spy(KDevelop::ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*))); Q_ASSERT(spy.isValid()); KDevelop::ICore::self()->projectController()->openProject(paths.projectFile.toUrl()); if ( spy.isEmpty() && !spy.wait(30000) ) { qFatal( "Timeout while waiting for opened signal" ); } KDevelop::IProject* project = KDevelop::ICore::self()->projectController()->findProjectByName(name); Q_ASSERT(project); Q_ASSERT(project->buildSystemManager()); Q_ASSERT(paths.projectFile == project->projectFile()); return project; } #endif diff --git a/plugins/cmakebuilder/CMakeLists.txt b/plugins/cmakebuilder/CMakeLists.txt index 9f237be360..57d6e2ea20 100644 --- a/plugins/cmakebuilder/CMakeLists.txt +++ b/plugins/cmakebuilder/CMakeLists.txt @@ -1,35 +1,31 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevcmakebuilder\") -include_directories( - ${KDevelop_SOURCE_DIR}/projectmanagers/cmake - ) - ########### next target ############### set(cmakebuilder_SRCS cmakebuilder.cpp cmakejob.cpp prunejob.cpp cmakebuilderpreferences.cpp ) ecm_qt_declare_logging_category(cmakebuilder_SRCS HEADER debug.h IDENTIFIER KDEV_CMAKEBUILDER CATEGORY_NAME "kdevelop.projectbuilders.cmakebuilder" ) ki18n_wrap_ui(cmakebuilder_SRCS cmakebuilderpreferences.ui) kdevplatform_add_plugin(kdevcmakebuilder JSON kdevcmakebuilder.json SOURCES ${cmakebuilder_SRCS}) target_link_libraries( kdevcmakebuilder kdevcmakecommon KF5::KIOWidgets KDev::Interfaces KDev::OutputView KDev::Util KDev::Shell KDev::Project KDev::IMakeBuilder ) diff --git a/plugins/custom-definesandincludes/definesandincludesmanager.cpp b/plugins/custom-definesandincludes/definesandincludesmanager.cpp index de317ae977..27ca304f1b 100644 --- a/plugins/custom-definesandincludes/definesandincludesmanager.cpp +++ b/plugins/custom-definesandincludes/definesandincludesmanager.cpp @@ -1,400 +1,402 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "definesandincludesmanager.h" #include "kcm_widget/definesandincludesconfigpage.h" #include "compilerprovider/compilerprovider.h" #include "compilerprovider/widget/compilerswidget.h" #include "noprojectincludesanddefines/noprojectincludepathsmanager.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { ///@return: The ConfigEntry, with includes/defines from @p paths for all parent folders of @p item. static ConfigEntry findConfigForItem(QVector paths, const KDevelop::ProjectBaseItem* item) { ConfigEntry ret; const Path itemPath = item->path(); const Path rootDirectory = item->project()->path(); Path closestPath; std::sort(paths.begin(), paths.end(), [] (const ConfigEntry& lhs, const ConfigEntry& rhs) { // sort in reverse order to do a bottom-up search return lhs.path > rhs.path; }); for (const ConfigEntry & entry : paths) { Path targetDirectory = rootDirectory; // note: a dot represents the project root if (entry.path != QLatin1String(".")) { targetDirectory.addPath(entry.path); } if (targetDirectory == itemPath || targetDirectory.isParentOf(itemPath)) { ret.includes += entry.includes; for (auto it = entry.defines.constBegin(); it != entry.defines.constEnd(); it++) { if (!ret.defines.contains(it.key())) { ret.defines[it.key()] = it.value(); } } if (targetDirectory.segments().size() > closestPath.segments().size()) { ret.parserArguments = entry.parserArguments; closestPath = targetDirectory; } } } ret.includes.removeDuplicates(); Q_ASSERT(!ret.parserArguments.cppArguments.isEmpty()); Q_ASSERT(!ret.parserArguments.cArguments.isEmpty()); return ret; } void merge(Defines* target, const Defines& source) { if (target->isEmpty()) { *target = source; return; } for (auto it = source.constBegin(); it != source.constEnd(); ++it) { target->insert(it.key(), it.value()); } } QString argumentsForPath(const Path& path, const ParserArguments& arguments) { auto languageType = Utils::languageType(path, arguments.parseAmbiguousAsCPP); switch (languageType) { case Utils::C: return arguments.cArguments; case Utils::Cpp: return arguments.cppArguments; case Utils::OpenCl: return arguments.openClArguments; case Utils::Cuda: return arguments.cudaArguments; case Utils::ObjC: return QString(); case Utils::Other: return QString(); } Q_UNREACHABLE(); return QString(); } } K_PLUGIN_FACTORY_WITH_JSON(DefinesAndIncludesManagerFactory, "kdevdefinesandincludesmanager.json", registerPlugin(); ) DefinesAndIncludesManager::DefinesAndIncludesManager( QObject* parent, const QVariantList& ) : IPlugin(QStringLiteral("kdevdefinesandincludesmanager"), parent ) , m_settings(SettingsManager::globalInstance()) , m_noProjectIPM(new NoProjectIncludePathsManager()) { registerProvider(m_settings->provider()); #ifdef Q_OS_OSX m_defaultFrameworkDirectories += Path(QStringLiteral("/Library/Frameworks")); m_defaultFrameworkDirectories += Path(QStringLiteral("/System/Library/Frameworks")); #endif } DefinesAndIncludesManager::~DefinesAndIncludesManager() = default; Defines DefinesAndIncludesManager::defines( ProjectBaseItem* item, Type type ) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); if (!item) { return m_settings->provider()->defines(nullptr); } Defines defines; for (auto provider : m_providers) { if (provider->type() & type) { merge(&defines, provider->defines(item)); } } if ( type & ProjectSpecific ) { auto buildManager = item->project()->buildSystemManager(); if ( buildManager ) { merge(&defines, buildManager->defines(item)); } } // Manually set defines have the highest priority and overwrite values of all other types of defines. if (type & UserDefined) { auto cfg = item->project()->projectConfiguration().data(); merge(&defines, findConfigForItem(m_settings->readPaths(cfg), item).defines); } merge(&defines, m_noProjectIPM->includesAndDefines(item->path().path()).second); return defines; } Path::List DefinesAndIncludesManager::includes( ProjectBaseItem* item, Type type ) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); if (!item) { return m_settings->provider()->includes(nullptr); } Path::List includes; if (type & UserDefined) { auto cfg = item->project()->projectConfiguration().data(); includes += KDevelop::toPathList(findConfigForItem(m_settings->readPaths(cfg), item).includes); } if ( type & ProjectSpecific ) { auto buildManager = item->project()->buildSystemManager(); if ( buildManager ) { includes += buildManager->includeDirectories(item); } } for (auto provider : m_providers) { if ( !(provider->type() & type) ) { continue; } auto newItems = provider->includes(item); if ( provider->type() & DefinesAndIncludesManager::CompilerSpecific ) { // If an item occurs in the "compiler specific" list, but was previously supplied // in the user include path list already, remove it from there. // Re-ordering the system include paths causes confusion in some cases. Q_FOREACH (const auto& x, newItems ) { includes.removeAll(x); } } includes += newItems; } includes += m_noProjectIPM->includesAndDefines(item->path().path()).first; return includes; } Path::List DefinesAndIncludesManager::frameworkDirectories( ProjectBaseItem* item, Type type ) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); if (!item) { return m_settings->provider()->frameworkDirectories(nullptr); } Path::List frameworkDirectories = m_defaultFrameworkDirectories; if ( type & ProjectSpecific ) { auto buildManager = item->project()->buildSystemManager(); if ( buildManager ) { frameworkDirectories += buildManager->frameworkDirectories(item); } } for (auto provider : m_providers) { if (provider->type() & type) { frameworkDirectories += provider->frameworkDirectories(item); } } return frameworkDirectories; } bool DefinesAndIncludesManager::unregisterProvider(IDefinesAndIncludesManager::Provider* provider) { int idx = m_providers.indexOf(provider); if (idx != -1) { m_providers.remove(idx); return true; } return false; } void DefinesAndIncludesManager::registerProvider(IDefinesAndIncludesManager::Provider* provider) { Q_ASSERT(provider); if (m_providers.contains(provider)) { return; } m_providers.push_back(provider); } Defines DefinesAndIncludesManager::defines(const QString& path) const { Defines ret = m_settings->provider()->defines(nullptr); merge(&ret, m_noProjectIPM->includesAndDefines(path).second); return ret; } Path::List DefinesAndIncludesManager::includes(const QString& path) const { return m_settings->provider()->includes(nullptr) + m_noProjectIPM->includesAndDefines(path).first; } Path::List DefinesAndIncludesManager::frameworkDirectories(const QString& /* path */) const { return m_settings->provider()->frameworkDirectories(nullptr); } void DefinesAndIncludesManager::openConfigurationDialog(const QString& pathToFile) { if (auto project = KDevelop::ICore::self()->projectController()->findProjectForUrl(QUrl::fromLocalFile(pathToFile))) { KDevelop::ICore::self()->projectController()->configureProject(project); } else { m_noProjectIPM->openConfigurationDialog(pathToFile); } } Path::List DefinesAndIncludesManager::includesInBackground(const QString& path) const { Path::List includes; for (auto provider: m_backgroundProviders) { includes += provider->includesInBackground(path); } return includes; } Path::List DefinesAndIncludesManager::frameworkDirectoriesInBackground(const QString& path) const { Path::List fwDirs; for (auto provider: m_backgroundProviders) { fwDirs += provider->frameworkDirectoriesInBackground(path); } return fwDirs; } Defines DefinesAndIncludesManager::definesInBackground(const QString& path) const { QHash defines; for (auto provider: m_backgroundProviders) { auto result = provider->definesInBackground(path); for (auto it = result.constBegin(); it != result.constEnd(); it++) { defines[it.key()] = it.value(); } } merge(&defines, m_noProjectIPM->includesAndDefines(path).second); return defines; } bool DefinesAndIncludesManager::unregisterBackgroundProvider(IDefinesAndIncludesManager::BackgroundProvider* provider) { int idx = m_backgroundProviders.indexOf(provider); if (idx != -1) { m_backgroundProviders.remove(idx); return true; } return false; } void DefinesAndIncludesManager::registerBackgroundProvider(IDefinesAndIncludesManager::BackgroundProvider* provider) { Q_ASSERT(provider); if (m_backgroundProviders.contains(provider)) { return; } m_backgroundProviders.push_back(provider); } QString DefinesAndIncludesManager::parserArguments(KDevelop::ProjectBaseItem* item) const { Q_ASSERT(item); Q_ASSERT(QThread::currentThread() == qApp->thread()); + auto cfg = item->project()->projectConfiguration().data(); + const auto parserArguments = findConfigForItem(m_settings->readPaths(cfg), item).parserArguments; + auto arguments = argumentsForPath(item->path(), parserArguments); + auto buildManager = item->project()->buildSystemManager(); if ( buildManager ) { - const auto args = buildManager->extraArguments(item); - if (!args.isEmpty()) - return args; + const auto extraArguments = buildManager->extraArguments(item); + if (!extraArguments.isEmpty()) + arguments += ' ' + extraArguments; } - auto cfg = item->project()->projectConfiguration().data(); - const auto arguments = findConfigForItem(m_settings->readPaths(cfg), item).parserArguments; - return argumentsForPath(item->path(), arguments); + return arguments; } QString DefinesAndIncludesManager::parserArguments(const QString& path) const { const auto args = m_settings->defaultParserArguments(); return argumentsForPath(Path(path), args); } int DefinesAndIncludesManager::perProjectConfigPages() const { return 1; } ConfigPage* DefinesAndIncludesManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new DefinesAndIncludesConfigPage(this, options, parent); } return nullptr; } KDevelop::ConfigPage* DefinesAndIncludesManager::configPage(int number, QWidget* parent) { return number == 0 ? new CompilersWidget(parent) : nullptr; } int DefinesAndIncludesManager::configPages() const { return 1; } #include "definesandincludesmanager.moc" diff --git a/plugins/qmakemanager/qmakeprojectfile.cpp b/plugins/qmakemanager/qmakeprojectfile.cpp index 7b586b0f97..24bb5c1339 100644 --- a/plugins/qmakemanager/qmakeprojectfile.cpp +++ b/plugins/qmakemanager/qmakeprojectfile.cpp @@ -1,443 +1,444 @@ /* KDevelop QMake Support * * Copyright 2006 Andreas Pakulat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "qmakeprojectfile.h" #include #include #include #include "debug.h" #include "parser/ast.h" #include "qmakecache.h" #include "qmakemkspecs.h" #include "qmakeconfig.h" #include #include #define ifDebug(x) QHash> QMakeProjectFile::m_qmakeQueryCache; const QStringList QMakeProjectFile::FileVariables = QStringList() << QStringLiteral("IDLS") << QStringLiteral("RESOURCES") << QStringLiteral("IMAGES") << QStringLiteral("LEXSOURCES") << QStringLiteral("DISTFILES") << QStringLiteral("YACCSOURCES") << QStringLiteral("TRANSLATIONS") << QStringLiteral("HEADERS") << QStringLiteral("SOURCES") << QStringLiteral("INTERFACES") << QStringLiteral("FORMS"); QMakeProjectFile::QMakeProjectFile(const QString& projectfile) : QMakeFile(projectfile) , m_mkspecs(nullptr) , m_cache(nullptr) { } void QMakeProjectFile::setQMakeCache(QMakeCache* cache) { m_cache = cache; } void QMakeProjectFile::setMkSpecs(QMakeMkSpecs* mkspecs) { m_mkspecs = mkspecs; } bool QMakeProjectFile::read() { // default values // NOTE: if we already have such a var, e.g. in an include file, we must not overwrite it here! if (!m_variableValues.contains(QStringLiteral("QT"))) { m_variableValues[QStringLiteral("QT")] = QStringList() << QStringLiteral("core") << QStringLiteral("gui"); } if (!m_variableValues.contains(QStringLiteral("CONFIG"))) { m_variableValues[QStringLiteral("CONFIG")] = QStringList() << QStringLiteral("qt"); } Q_ASSERT(m_mkspecs); foreach (const QString& var, m_mkspecs->variables()) { if (!m_variableValues.contains(var)) { m_variableValues[var] = m_mkspecs->variableValues(var); } } if (m_cache) { foreach (const QString& var, m_cache->variables()) { if (!m_variableValues.contains(var)) { m_variableValues[var] = m_cache->variableValues(var); } } } /// TODO: more special variables m_variableValues[QStringLiteral("PWD")] = QStringList() << pwd(); m_variableValues[QStringLiteral("_PRO_FILE_")] = QStringList() << proFile(); m_variableValues[QStringLiteral("_PRO_FILE_PWD_")] = QStringList() << proFilePwd(); m_variableValues[QStringLiteral("OUT_PWD")] = QStringList() << outPwd(); const QString qtInstallHeaders = QStringLiteral("QT_INSTALL_HEADERS"); const QString qtVersion = QStringLiteral("QT_VERSION"); const QString qtInstallLibs = QStringLiteral("QT_INSTALL_LIBS"); const QString executable = QMakeConfig::qmakeExecutable(project()); if (!m_qmakeQueryCache.contains(executable)) { const auto queryResult = QMakeConfig::queryQMake(executable, {qtInstallHeaders, qtVersion, qtInstallLibs}); if (queryResult.isEmpty()) { qCWarning(KDEV_QMAKE) << "Failed to query qmake - bad qmake executable configured?" << executable; } m_qmakeQueryCache[executable] = queryResult; } const auto cachedQueryResult = m_qmakeQueryCache.value(executable); m_qtIncludeDir = cachedQueryResult.value(qtInstallHeaders); m_qtVersion = cachedQueryResult.value(qtVersion); m_qtLibDir = cachedQueryResult.value(qtInstallLibs); return QMakeFile::read(); } QStringList QMakeProjectFile::subProjects() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching subprojects";) QStringList list; foreach (QString subdir, variableValues("SUBDIRS")) { QString fileOrPath; ifDebug(qCDebug(KDEV_QMAKE) << "Found value:" << subdir;) if (containsVariable(subdir + ".file") && !variableValues(subdir + ".file").isEmpty()) { subdir = variableValues(subdir + ".file").first(); } else if (containsVariable(subdir + ".subdir") && !variableValues(subdir + ".subdir").isEmpty()) { subdir = variableValues(subdir + ".subdir").first(); } if (subdir.endsWith(QLatin1String(".pro"))) { fileOrPath = resolveToSingleFileName(subdir.trimmed()); } else { fileOrPath = resolveToSingleFileName(subdir.trimmed()); } if (fileOrPath.isEmpty()) { qCWarning(KDEV_QMAKE) << "could not resolve subdir" << subdir << "to file or path, skipping"; continue; } list << fileOrPath; } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "subprojects";) return list; } bool QMakeProjectFile::hasSubProject(const QString& file) const { foreach (const QString& sub, subProjects()) { if (sub == file) { return true; } else if (QFileInfo(file).absoluteDir() == sub) { return true; } } return false; } void QMakeProjectFile::addPathsForVariable(const QString& variable, QStringList* list, const QString& base) const { const QStringList values = variableValues(variable); ifDebug(qCDebug(KDEV_QMAKE) << variable << values;) foreach (const QString& val, values) { QString path = resolveToSingleFileName(val, base); if (!path.isEmpty() && !list->contains(val)) { list->append(path); } } } QStringList QMakeProjectFile::includeDirectories() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching include dirs" << m_qtIncludeDir;) ifDebug(qCDebug(KDEV_QMAKE) << "CONFIG" << variableValues("CONFIG");) QStringList list; addPathsForVariable(QStringLiteral("INCLUDEPATH"), &list); addPathsForVariable(QStringLiteral("QMAKE_INCDIR"), &list); if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("opengl"))) { addPathsForVariable(QStringLiteral("QMAKE_INCDIR_OPENGL"), &list); } if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("qt"))) { if (!list.contains(m_qtIncludeDir)) list << m_qtIncludeDir; QDir incDir(m_qtIncludeDir); auto modules = variableValues(QStringLiteral("QT")); if (!modules.isEmpty() && !modules.contains(QStringLiteral("core"))) { // TODO: proper dependency tracking of modules // for now, at least include core if we include any other module modules << QStringLiteral("core"); } // TODO: This is all very fragile, should rather read QMake module .pri files (e.g. qt_lib_core_private.pri) foreach (const QString& module, modules) { QString pattern = module; bool isPrivate = false; if (module.endsWith(QLatin1String("-private"))) { pattern.chop(qstrlen("-private")); isPrivate = true; } else if (module.endsWith(QLatin1String("_private"))) { // _private is less common, but still a valid suffix pattern.chop(qstrlen("_private")); isPrivate = true; } if (pattern == QLatin1String("qtestlib") || pattern == QLatin1String("testlib")) { pattern = QStringLiteral("QtTest"); } else if (pattern == QLatin1String("qaxcontainer")) { pattern = QStringLiteral("ActiveQt"); } else if (pattern == QLatin1String("qaxserver")) { pattern = QStringLiteral("ActiveQt"); } QFileInfoList match = incDir.entryInfoList({QString("Qt%1").arg(pattern)}, QDir::Dirs); if (match.isEmpty()) { // try non-prefixed pattern match = incDir.entryInfoList({pattern}, QDir::Dirs); if (match.isEmpty()) { qCWarning(KDEV_QMAKE) << "unhandled Qt module:" << module << pattern; continue; } } QString path = match.first().canonicalFilePath(); if (isPrivate) { path += '/' + m_qtVersion + '/' + match.first().fileName() + "/private/"; } if (!list.contains(path)) { list << path; } } } if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("thread"))) { addPathsForVariable(QStringLiteral("QMAKE_INCDIR_THREAD"), &list); } if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("x11"))) { addPathsForVariable(QStringLiteral("QMAKE_INCDIR_X11"), &list); } addPathsForVariable(QStringLiteral("MOC_DIR"), &list, outPwd()); addPathsForVariable(QStringLiteral("OBJECTS_DIR"), &list, outPwd()); addPathsForVariable(QStringLiteral("UI_DIR"), &list, outPwd()); ifDebug(qCDebug(KDEV_QMAKE) << "final list:" << list;) return list; } // Scan QMAKE_C*FLAGS for -F and -iframework and QMAKE_LFLAGS for good measure. Time will // tell if we need to scan the release/debug/... specific versions of QMAKE_C*FLAGS. // Also include QT_INSTALL_LIBS which corresponds to Qt's framework directory on OS X. QStringList QMakeProjectFile::frameworkDirectories() const { const auto variablesToCheck = {QStringLiteral("QMAKE_CFLAGS"), QStringLiteral("QMAKE_CXXFLAGS"), QStringLiteral("QMAKE_LFLAGS")}; const QLatin1String fOption("-F"); const QLatin1String iframeworkOption("-iframework"); QStringList fwDirs; foreach (const auto& var, variablesToCheck) { bool storeArg = false; foreach (const auto& arg, variableValues(var)) { if (arg == fOption || arg == iframeworkOption) { // detached -F/-iframework arg; set a warrant to store the next argument storeArg = true; } else { if (arg.startsWith(fOption)) { fwDirs << arg.mid(fOption.size()); } else if (arg.startsWith(iframeworkOption)) { fwDirs << arg.mid(iframeworkOption.size()); } else if (storeArg) { fwDirs << arg; } // cancel any outstanding warrants to store the next argument storeArg = false; } } } #ifdef Q_OS_OSX fwDirs << m_qtLibDir; #endif return fwDirs; } QStringList QMakeProjectFile::extraArguments() const { const auto variablesToCheck = {QStringLiteral("QMAKE_CXXFLAGS")}; - const auto prefixes = { QLatin1String("-F"), QLatin1String("-iframework"), QLatin1String("-I"), QLatin1String("-D") }; + const auto prefixes = { "-F", "-iframework", "-I", "-D" }; QStringList args; foreach (const auto& var, variablesToCheck) { foreach (const auto& arg, variableValues(var)) { - for (const auto& prefix: prefixes) { - if (!arg.startsWith(prefix)) { - args << arg; - } + auto argHasPrefix = [arg](const char* prefix) { + return arg.startsWith(prefix); + }; + if ( !std::any_of(prefixes.begin(), prefixes.end(), argHasPrefix)) { + args << arg; } } } return args; } QStringList QMakeProjectFile::files() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching files";) QStringList list; foreach (const QString& variable, QMakeProjectFile::FileVariables) { foreach (const QString& value, variableValues(variable)) { list += resolveFileName(value); } } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "files";) return list; } QStringList QMakeProjectFile::filesForTarget(const QString& s) const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching files";) QStringList list; if (variableValues(QStringLiteral("INSTALLS")).contains(s)) { const QStringList files = variableValues(s + ".files"); if (!files.isEmpty()) { foreach (const QString& val, files) { list += QStringList(resolveFileName(val)); } } } if (!variableValues(QStringLiteral("INSTALLS")).contains(s) || s == QLatin1String("target")) { foreach (const QString& variable, QMakeProjectFile::FileVariables) { foreach (const QString& value, variableValues(variable)) { list += QStringList(resolveFileName(value)); } } } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "files";) return list; } QString QMakeProjectFile::getTemplate() const { QString templ = QStringLiteral("app"); if (!variableValues(QStringLiteral("TEMPLATE")).isEmpty()) { templ = variableValues(QStringLiteral("TEMPLATE")).first(); } return templ; } QStringList QMakeProjectFile::targets() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching targets";) QStringList list; list += variableValues(QStringLiteral("TARGET")); if (list.isEmpty() && getTemplate() != QLatin1String("subdirs")) { list += QFileInfo(absoluteFile()).baseName(); } foreach (const QString& target, variableValues("INSTALLS")) { if (!target.isEmpty() && target != QLatin1String("target")) list << target; } if (list.removeAll(QString())) { // remove empty targets - which is probably a bug... qCWarning(KDEV_QMAKE) << "got empty entry in TARGET of file" << absoluteFile(); } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "targets";) return list; } QMakeProjectFile::~QMakeProjectFile() { // TODO: delete cache, specs, ...? } QStringList QMakeProjectFile::resolveVariable(const QString& variable, VariableInfo::VariableType type) const { if (type == VariableInfo::QtConfigVariable) { if (m_mkspecs->isQMakeInternalVariable(variable)) { return QStringList() << m_mkspecs->qmakeInternalVariable(variable); } else { qCWarning(KDEV_QMAKE) << "unknown QtConfig Variable:" << variable; return QStringList(); } } return QMakeFile::resolveVariable(variable, type); } QMakeMkSpecs* QMakeProjectFile::mkSpecs() const { return m_mkspecs; } QMakeCache* QMakeProjectFile::qmakeCache() const { return m_cache; } QList QMakeProjectFile::defines() const { QList d; foreach (QString def, variableMap()["DEFINES"]) { int pos = def.indexOf('='); if (pos >= 0) { // a value is attached to define d.append(DefinePair(def.left(pos), def.right(def.length() - (pos + 1)))); } else { // a value-less define d.append(DefinePair(def, QLatin1String(""))); } } return d; } QString QMakeProjectFile::pwd() const { return absoluteDir(); } QString QMakeProjectFile::outPwd() const { if (!project()) { return absoluteDir(); } else { return QMakeConfig::buildDirFromSrc(project(), KDevelop::Path(absoluteDir())).toLocalFile(); } } QString QMakeProjectFile::proFile() const { return absoluteFile(); } QString QMakeProjectFile::proFilePwd() const { return absoluteDir(); } diff --git a/plugins/qmljs/CMakeLists.txt b/plugins/qmljs/CMakeLists.txt index 0e1ce53a40..5bb4281acd 100644 --- a/plugins/qmljs/CMakeLists.txt +++ b/plugins/qmljs/CMakeLists.txt @@ -1,34 +1,30 @@ -include_directories( - ${KDevelop_SOURCE_DIR}/languages/plugins -) - add_subdirectory(3rdparty/qtcreator-libs) add_subdirectory(duchain) add_subdirectory(nodejsmodules) if(BUILD_TESTING) add_subdirectory(tests) endif() add_subdirectory(codecompletion) ecm_qt_declare_logging_category(kdevqmljslanguagesupport_LOG_SRCS HEADER debug.h IDENTIFIER KDEV_QMLJS CATEGORY_NAME "kdevelop.languages.qmljs" ) kdevplatform_add_plugin(kdevqmljslanguagesupport JSON kdevqmljs.json SOURCES qmljsparsejob.cpp qmljshighlighting.cpp kdevqmljsplugin.cpp navigation/propertypreviewwidget.cpp ${kdevqmljslanguagesupport_LOG_SRCS} ) target_link_libraries(kdevqmljslanguagesupport kdevqmljsduchain kdevqmljscompletion Qt5::Widgets Qt5::QuickWidgets KF5::Declarative KDev::DefinesAndIncludesManager ) install(DIRECTORY navigation/propertywidgets DESTINATION ${KDE_INSTALL_DATADIR}/kdevqmljssupport)