diff --git a/languages/clang/clangparsejob.cpp b/languages/clang/clangparsejob.cpp index 715d686d40..6b09ebf8e2 100644 --- a/languages/clang/clangparsejob.cpp +++ b/languages/clang/clangparsejob.cpp @@ -1,379 +1,379 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 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 "clangparsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clangsettings/clangsettingsmanager.h" #include "duchain/clanghelpers.h" #include "duchain/clangpch.h" #include "duchain/duchainutils.h" #include "duchain/parsesession.h" #include "duchain/clangindex.h" #include "duchain/clangparsingenvironmentfile.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "clangsupport.h" #include "duchain/documentfinderhelpers.h" #include #include #include #include #include #include using namespace KDevelop; namespace { QString findConfigFile(const QString& forFile, const QString& configFileName) { QDir dir = QFileInfo(forFile).dir(); while (dir.exists()) { const QFileInfo customIncludePaths(dir, configFileName); if (customIncludePaths.exists()) { return customIncludePaths.absoluteFilePath(); } if (!dir.cdUp()) { break; } } return {}; } Path::List readPathListFile(const QString& filepath) { if (filepath.isEmpty()) { return {}; } QFile f(filepath); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { return {}; } const QString text = QString::fromLocal8Bit(f.readAll()); const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); Path::List paths(lines.length()); std::transform(lines.begin(), lines.end(), paths.begin(), [] (const QString& line) { return Path(line); }); return paths; } /** * File should contain the header to precompile and use while parsing * @returns the first path in the file */ Path userDefinedPchIncludeForFile(const QString& sourcefile) { static const QString pchIncludeFilename = QStringLiteral(".kdev_pch_include"); const auto paths = readPathListFile(findConfigFile(sourcefile, pchIncludeFilename)); return paths.isEmpty() ? Path() : paths.first(); } ProjectFileItem* findProjectFileItem(const IndexedString& url, bool* hasBuildSystemInfo) { ProjectFileItem* file = nullptr; *hasBuildSystemInfo = false; for (auto project: ICore::self()->projectController()->projects()) { auto files = project->filesForPath(url); if (files.isEmpty()) { continue; } file = files.last(); // A file might be defined in different targets. // Prefer file items defined inside a target with non-empty includes. for (auto f: files) { if (!dynamic_cast(f->parent())) { continue; } file = f; if (!IDefinesAndIncludesManager::manager()->includes(f, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) { break; } } } if (file && file->project()) { if (auto bsm = file->project()->buildSystemManager()) { *hasBuildSystemInfo = bsm->hasIncludesOrDefines(file); } } return file; } ClangParsingEnvironmentFile* parsingEnvironmentFile(const TopDUContext* context) { return dynamic_cast(context->parsingEnvironmentFile().data()); } DocumentChangeTracker* trackerForUrl(const IndexedString& url) { return ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); } } ClangParseJob::ClangParseJob(const IndexedString& url, ILanguageSupport* languageSupport) : ParseJob(url, languageSupport) { const auto tuUrl = clang()->index()->translationUnitForUrl(url); bool hasBuildSystemInfo; if (auto file = findProjectFileItem(tuUrl, &hasBuildSystemInfo)) { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(file)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(file)); m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(file)); } else { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(tuUrl.str())); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(tuUrl.str())); - m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(nullptr)); + m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(tuUrl.str())); } const bool isSource = ClangHelpers::isSource(tuUrl.str()); m_environment.setQuality( isSource ? (hasBuildSystemInfo ? ClangParsingEnvironment::BuildSystem : ClangParsingEnvironment::Source) : ClangParsingEnvironment::Unknown ); m_environment.setTranslationUnitUrl(tuUrl); Path::List projectPaths; const auto& projects = ICore::self()->projectController()->projects(); projectPaths.reserve(projects.size()); foreach (auto project, projects) { projectPaths.append(project->path()); } m_environment.setProjectPaths(projectPaths); foreach(auto document, ICore::self()->documentController()->openDocuments()) { auto textDocument = document->textDocument(); if (!textDocument || !textDocument->isModified() || !textDocument->url().isLocalFile() || !DocumentFinderHelpers::mimeTypesList().contains(textDocument->mimeType())) { continue; } m_unsavedFiles << UnsavedFile(textDocument->url().toLocalFile(), textDocument->textLines(textDocument->documentRange())); const IndexedString indexedUrl(textDocument->url()); m_unsavedRevisions.insert(indexedUrl, ModificationRevision::revisionForFile(indexedUrl)); if (indexedUrl == tuUrl) { m_tuDocumentIsUnsaved = true; } } if (auto tracker = trackerForUrl(url)) { tracker->reset(); } } ClangSupport* ClangParseJob::clang() const { return static_cast(languageSupport()); } void ClangParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { QReadLocker parseLock(languageSupport()->parseLock()); if (abortRequested()) { return; } { const auto tuUrlStr = m_environment.translationUnitUrl().str(); if (!m_tuDocumentIsUnsaved && !QFile::exists(tuUrlStr)) { // maybe we requested a parse job some time ago but now the file // does not exist anymore. return early then clang()->index()->unpinTranslationUnitForUrl(document()); return; } m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includesInBackground(tuUrlStr)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->definesInBackground(tuUrlStr)); m_environment.setPchInclude(userDefinedPchIncludeForFile(tuUrlStr)); } if (abortRequested()) { return; } // NOTE: we must have all declarations, contexts and uses available for files that are opened in the editor // it is very hard to check this for all included files of this TU, and previously lead to problems // when we tried to skip function bodies as an optimization for files that where not open in the editor. // now, we always build everything, which is correct but a tad bit slower. we can try to optimize later. setMinimumFeatures(static_cast(minimumFeatures() | TopDUContext::AllDeclarationsContextsAndUses)); if (minimumFeatures() & AttachASTWithoutUpdating) { // The context doesn't need to be updated, but has no AST attached (restored from disk), // so attach AST to it, without updating DUChain ParseSession session(createSessionData()); DUChainWriteLocker lock; auto ctx = DUChainUtils::standardContextForUrl(document().toUrl()); if (!ctx) { clangDebug() << "Lost context while attaching AST"; return; } ctx->setAst(IAstContainer::Ptr(session.data())); if (minimumFeatures() & UpdateHighlighting) { lock.unlock(); languageSupport()->codeHighlighting()->highlightDUChain(ctx); } return; } { UrlParseLock urlLock(document()); if (abortRequested() || !isUpdateRequired(ParseSession::languageString())) { return; } } ParseSession session(ClangIntegration::DUChainUtils::findParseSessionData(document(), m_environment.translationUnitUrl())); if (abortRequested()) { return; } if (!session.data() || !session.reparse(m_unsavedFiles, m_environment)) { session.setData(createSessionData()); } if (!session.unit()) { // failed to parse file, unpin and don't try again clang()->index()->unpinTranslationUnitForUrl(document()); return; } if (!clang_getFile(session.unit(), document().byteArray().constData())) { // this parse job's document does not exist in the pinned translation unit // so we need to unpin and re-add this document // Ideally we'd reset m_environment and session, but this is much simpler // and shouldn't be a common case clang()->index()->unpinTranslationUnitForUrl(document()); if (!(minimumFeatures() & Rescheduled)) { auto features = static_cast(minimumFeatures() | Rescheduled); ICore::self()->languageController()->backgroundParser()->addDocument(document(), features, priority()); } return; } Imports imports = ClangHelpers::tuImports(session.unit()); IncludeFileContexts includedFiles; if (auto pch = clang()->index()->pch(m_environment)) { auto pchFile = pch->mapFile(session.unit()); includedFiles = pch->mapIncludes(session.unit()); includedFiles.insert(pchFile, pch->context()); auto tuFile = clang_getFile(session.unit(), m_environment.translationUnitUrl().byteArray().constData()); imports.insert(tuFile, { pchFile, CursorInRevision(0, 0) } ); } if (abortRequested()) { return; } auto context = ClangHelpers::buildDUChain(session.mainFile(), imports, session, minimumFeatures(), includedFiles, clang()->index()); setDuChain(context); if (abortRequested()) { return; } if (context) { if (minimumFeatures() & TopDUContext::AST) { DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } #ifdef QT_DEBUG DUChainReadLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); // verify that features and environment where properly set in ClangHelpers::buildDUChain Q_ASSERT(file->featuresSatisfied(TopDUContext::Features(minimumFeatures() & ~TopDUContext::ForceUpdateRecursive))); if (trackerForUrl(context->url())) { Q_ASSERT(file->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses)); } #endif } foreach(const auto& context, includedFiles) { if (!context) { continue; } { // prefer the editor modification revision, instead of the on-disk revision auto it = m_unsavedRevisions.find(context->url()); if (it != m_unsavedRevisions.end()) { DUChainWriteLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); file->setModificationRevision(it.value()); } } if (trackerForUrl(context->url())) { if (clang()->index()->translationUnitForUrl(context->url()) == m_environment.translationUnitUrl()) { // cache the parse session and the contained translation unit for this chain // this then allows us to quickly reparse the document if it is changed by // the user // otherwise no editor component is open for this document and we can dispose // the TU to save memory // share the session data with all contexts that are pinned to this TU DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } languageSupport()->codeHighlighting()->highlightDUChain(context); } } } ParseSessionData::Ptr ClangParseJob::createSessionData() const { return ParseSessionData::Ptr(new ParseSessionData(m_unsavedFiles, clang()->index(), m_environment, ParseSessionData::NoOption)); } const ParsingEnvironment* ClangParseJob::environment() const { return &m_environment; } diff --git a/languages/clang/clangsettings/clangsettingsmanager.cpp b/languages/clang/clangsettings/clangsettingsmanager.cpp index acb7a5c5ad..e6d7e1c678 100644 --- a/languages/clang/clangsettings/clangsettingsmanager.cpp +++ b/languages/clang/clangsettings/clangsettingsmanager.cpp @@ -1,128 +1,133 @@ /* * This file is part of KDevelop * * Copyright 2015 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "clangsettingsmanager.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { const QString settingsGroup = QStringLiteral("Clang Settings"); const QString macros = QStringLiteral("macros"); const QString lookAhead = QStringLiteral("lookAhead"); const QString forwardDeclare = QStringLiteral("forwardDeclare"); AssistantsSettings readAssistantsSettings(KConfig* cfg) { auto grp = cfg->group(settingsGroup); AssistantsSettings settings; settings.forwardDeclare = grp.readEntry(forwardDeclare, true); return settings; } CodeCompletionSettings readCodeCompletionSettings(KConfig* cfg) { auto grp = cfg->group(settingsGroup); CodeCompletionSettings settings; settings.macros = grp.readEntry(macros, true); settings.lookAhead = grp.readEntry(lookAhead, false); return settings; } } ClangSettingsManager* ClangSettingsManager::self() { static ClangSettingsManager manager; return &manager; } AssistantsSettings ClangSettingsManager::assistantsSettings() const { auto cfg = ICore::self()->activeSession()->config(); return readAssistantsSettings(cfg.data()); } CodeCompletionSettings ClangSettingsManager::codeCompletionSettings() const { if (m_enableTesting) { CodeCompletionSettings settings; settings.lookAhead = true; settings.macros = true; return settings; } auto cfg = ICore::self()->activeSession()->config(); return readCodeCompletionSettings(cfg.data()); } ParserSettings ClangSettingsManager::parserSettings(KDevelop::ProjectBaseItem* item) const { return {IDefinesAndIncludesManager::manager()->parserArguments(item)}; } +ParserSettings ClangSettingsManager::parserSettings(const QString& path) const +{ + return {IDefinesAndIncludesManager::manager()->parserArguments(path)}; +} + ClangSettingsManager::ClangSettingsManager() {} bool ParserSettings::isCpp() const { return parserOptions.contains(QStringLiteral("-std=c++")); } QVector ParserSettings::toClangAPI() const { // TODO: This is not efficient. auto list = parserOptions.split(QLatin1Char(' '), QString::SkipEmptyParts); QVector result; result.reserve(list.size()); std::transform(list.constBegin(), list.constEnd(), std::back_inserter(result), [] (const QString &argument) { return argument.toUtf8(); }); return result; } bool ParserSettings::operator==(const ParserSettings& rhs) const { return parserOptions == rhs.parserOptions; } diff --git a/languages/clang/clangsettings/clangsettingsmanager.h b/languages/clang/clangsettings/clangsettingsmanager.h index a819ddfd6c..1a0c8c9a8a 100644 --- a/languages/clang/clangsettings/clangsettingsmanager.h +++ b/languages/clang/clangsettings/clangsettingsmanager.h @@ -1,78 +1,80 @@ /* * This file is part of KDevelop * * Copyright 2015 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef CLANGSETTINGSMANAGER_H #define CLANGSETTINGSMANAGER_H #include #include #include "clangprivateexport.h" class KConfig; namespace KDevelop { class ProjectBaseItem; class IProject; } struct ParserSettings { QString parserOptions; bool isCpp() const; QVector toClangAPI() const; bool operator==(const ParserSettings& rhs) const; }; Q_DECLARE_METATYPE(ParserSettings); struct CodeCompletionSettings { bool macros = true; bool lookAhead = false; }; struct AssistantsSettings { bool forwardDeclare = true; }; class KDEVCLANGPRIVATE_EXPORT ClangSettingsManager { public: static ClangSettingsManager* self(); AssistantsSettings assistantsSettings() const; CodeCompletionSettings codeCompletionSettings() const; ParserSettings parserSettings(KDevelop::ProjectBaseItem* item) const; + ParserSettings parserSettings(const QString& path) const; + private: ClangSettingsManager(); bool m_enableTesting = false; friend class CodeCompletionTestBase; }; #endif // CLANGSETTINGSMANAGER_H diff --git a/languages/clang/duchain/parsesession.cpp b/languages/clang/duchain/parsesession.cpp index 444c388272..f42fc6be01 100644 --- a/languages/clang/duchain/parsesession.cpp +++ b/languages/clang/duchain/parsesession.cpp @@ -1,451 +1,451 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2013 Kevin Funk 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 "parsesession.h" #include #include "clangproblem.h" #include "clangdiagnosticevaluator.h" #include "todoextractor.h" #include "clanghelpers.h" #include "clangindex.h" #include "clangparsingenvironment.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QVector extraArgs() { const auto extraArgsString = QString::fromLatin1(qgetenv("KDEV_CLANG_EXTRA_ARGUMENTS")); const auto extraArgs = KShell::splitArgs(extraArgsString); // transform to list of QByteArrays QVector result; result.reserve(extraArgs.size()); foreach (const QString& arg, extraArgs) { result << arg.toLatin1(); } clangDebug() << "Passing extra arguments to clang:" << result; return result; } QVector argsForSession(const QString& path, ParseSessionData::Options options, const ParserSettings& parserSettings) { QMimeDatabase db; if(db.mimeTypeForFile(path).name() == QStringLiteral("text/x-objcsrc")) { return {QByteArrayLiteral("-xobjective-c++")}; } if (parserSettings.parserOptions.isEmpty()) { // The parserOptions can be empty for some unit tests that use ParseSession directly - auto defaultArguments = ClangSettingsManager::self()->parserSettings(nullptr).toClangAPI(); + auto defaultArguments = ClangSettingsManager::self()->parserSettings(path).toClangAPI(); Q_ASSERT(!defaultArguments.isEmpty()); defaultArguments.append(QByteArrayLiteral("-nostdinc")); defaultArguments.append(QByteArrayLiteral("-nostdinc++")); defaultArguments.append(QByteArrayLiteral("-xc++")); return defaultArguments; } auto result = parserSettings.toClangAPI(); result.append(QByteArrayLiteral("-nostdinc")); result.append(QByteArrayLiteral("-nostdinc++")); if (options & ParseSessionData::PrecompiledHeader) { result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++-header") : QByteArrayLiteral("-xc-header")); return result; } result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++") : QByteArrayLiteral("-xc")); return result; } void addIncludes(QVector* args, QVector* otherArgs, const Path::List& includes, const char* cliSwitch) { foreach (const Path& url, includes) { if (url.isEmpty()) { continue; } QFileInfo info(url.toLocalFile()); QByteArray path = url.toLocalFile().toUtf8(); if (info.isFile()) { path.prepend("-include"); } else { path.prepend(cliSwitch); } otherArgs->append(path); args->append(path.constData()); } } QVector toClangApi(const QVector& unsavedFiles) { QVector unsaved; unsaved.reserve(unsavedFiles.size()); std::transform(unsavedFiles.begin(), unsavedFiles.end(), std::back_inserter(unsaved), [] (const UnsavedFile& file) { return file.toClangApi(); }); return unsaved; } bool needGccCompatibility(const ClangParsingEnvironment& environment) { const auto& defines = environment.defines(); // TODO: potentially do the same for the intel compiler? return defines.contains(QStringLiteral("__GNUC__")) && !environment.defines().contains(QStringLiteral("__clang__")); } bool hasQtIncludes(const Path::List& includePaths) { return std::find_if(includePaths.begin(), includePaths.end(), [] (const Path& path) { return path.lastPathSegment() == QLatin1String("QtCore"); }) != includePaths.end(); } } ParseSessionData::ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, const ClangParsingEnvironment& environment, Options options) : m_file(nullptr) , m_unit(nullptr) { unsigned int flags = CXTranslationUnit_CXXChainedPCH | CXTranslationUnit_DetailedPreprocessingRecord #if CINDEX_VERSION_MINOR >= 34 | CXTranslationUnit_KeepGoing #endif ; if (options.testFlag(SkipFunctionBodies)) { flags |= CXTranslationUnit_SkipFunctionBodies; } if (options.testFlag(PrecompiledHeader)) { flags |= CXTranslationUnit_ForSerialization; } else { flags |= CXTranslationUnit_CacheCompletionResults | CXTranslationUnit_PrecompiledPreamble; if (environment.quality() == ClangParsingEnvironment::Unknown) { flags |= CXTranslationUnit_Incomplete; } } const auto tuUrl = environment.translationUnitUrl(); Q_ASSERT(!tuUrl.isEmpty()); const auto arguments = argsForSession(tuUrl.str(), options, environment.parserSettings()); QVector clangArguments; const auto& includes = environment.includes(); const auto& pchInclude = environment.pchInclude(); // uses QByteArray as smart-pointer for const char* ownership QVector smartArgs; smartArgs.reserve(includes.system.size() + includes.project.size() + pchInclude.isValid() + arguments.size() + 1); clangArguments.reserve(smartArgs.size()); std::transform(arguments.constBegin(), arguments.constEnd(), std::back_inserter(clangArguments), [] (const QByteArray &argument) { return argument.constData(); }); // NOTE: the PCH include must come before all other includes! if (pchInclude.isValid()) { clangArguments << "-include"; QByteArray pchFile = pchInclude.toLocalFile().toUtf8(); smartArgs << pchFile; clangArguments << pchFile.constData(); } if (needGccCompatibility(environment)) { const auto compatFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevclangsupport/gcc_compat.h")).toUtf8(); if (!compatFile.isEmpty()) { smartArgs << compatFile; clangArguments << "-include" << compatFile.constData(); } } if (hasQtIncludes(includes.system)) { const auto wrappedQtHeaders = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevclangsupport/wrappedQtHeaders"), QStandardPaths::LocateDirectory).toUtf8(); if (!wrappedQtHeaders.isEmpty()) { smartArgs << wrappedQtHeaders; clangArguments << "-isystem" << wrappedQtHeaders.constData(); const auto qtCore = wrappedQtHeaders + "/QtCore"; smartArgs << qtCore; clangArguments << "-isystem" << qtCore.constData(); } } addIncludes(&clangArguments, &smartArgs, includes.system, "-isystem"); addIncludes(&clangArguments, &smartArgs, includes.project, "-I"); smartArgs << writeDefinesFile(environment.defines()); clangArguments << "-imacros" << smartArgs.last().constData(); // append extra args from environment variable static const auto extraArgs = ::extraArgs(); foreach (const QByteArray& arg, extraArgs) { clangArguments << arg.constData(); } QVector unsaved; //For PrecompiledHeader, we don't want unsaved contents (and contents.isEmpty()) if (!options.testFlag(PrecompiledHeader)) { unsaved = toClangApi(unsavedFiles); } // debugging: print hypothetical clang invocation including args (for easy c&p for local testing) if (qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_ARGS")) { QTextStream out(stdout); out << "Invocation: clang"; foreach (const auto& arg, clangArguments) { out << " " << arg; } out << " " << tuUrl.byteArray().constData() << "\n"; } const CXErrorCode code = clang_parseTranslationUnit2( index->index(), tuUrl.byteArray().constData(), clangArguments.constData(), clangArguments.size(), unsaved.data(), unsaved.size(), flags, &m_unit ); if (code != CXError_Success) { qWarning() << "clang_parseTranslationUnit2 return with error code" << code; if (!qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DIAGS")) { qWarning() << " (start KDevelop with `KDEV_CLANG_DISPLAY_DIAGS=1 kdevelop` to see more diagnostics)"; } } if (m_unit) { setUnit(m_unit); m_environment = environment; if (options.testFlag(PrecompiledHeader)) { clang_saveTranslationUnit(m_unit, (tuUrl.byteArray() + ".pch").constData(), CXSaveTranslationUnit_None); } } else { qWarning() << "Failed to parse translation unit:" << tuUrl; } } ParseSessionData::~ParseSessionData() { clang_disposeTranslationUnit(m_unit); } QByteArray ParseSessionData::writeDefinesFile(const QMap& defines) { m_definesFile.open(); Q_ASSERT(m_definesFile.isWritable()); QTextStream definesStream(&m_definesFile); // don't show warnings about redefined macros definesStream << "#pragma clang system_header\n"; for (auto it = defines.begin(); it != defines.end(); ++it) { definesStream << QStringLiteral("#define ") << it.key() << ' ' << it.value() << '\n'; } return m_definesFile.fileName().toUtf8(); } void ParseSessionData::setUnit(CXTranslationUnit unit) { m_unit = unit; if (m_unit) { const ClangString unitFile(clang_getTranslationUnitSpelling(unit)); m_file = clang_getFile(m_unit, unitFile.c_str()); } else { m_file = nullptr; } } ClangParsingEnvironment ParseSessionData::environment() const { return m_environment; } ParseSession::ParseSession(const ParseSessionData::Ptr& data) : d(data) { if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSession::~ParseSession() { if (d) { d->m_mutex.unlock(); } } void ParseSession::setData(const ParseSessionData::Ptr& data) { if (data == d) { return; } if (d) { d->m_mutex.unlock(); } d = data; if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSessionData::Ptr ParseSession::data() const { return d; } IndexedString ParseSession::languageString() { static const IndexedString lang("Clang"); return lang; } QList ParseSession::problemsForFile(CXFile file) const { if (!d) { return {}; } QList problems; // extra clang diagnostics const uint numDiagnostics = clang_getNumDiagnostics(d->m_unit); problems.reserve(numDiagnostics); for (uint i = 0; i < numDiagnostics; ++i) { auto diagnostic = clang_getDiagnostic(d->m_unit, i); CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); // missing-include problems are so severe in clang that we always propagate // them to this document, to ensure that the user will see the error. if (diagnosticFile != file && ClangDiagnosticEvaluator::diagnosticType(diagnostic) != ClangDiagnosticEvaluator::IncludeFileNotFoundProblem) { continue; } ProblemPointer problem(ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit)); problems << problem; clang_disposeDiagnostic(diagnostic); } // other problem sources TodoExtractor extractor(unit(), file); problems << extractor.problems(); #if CINDEX_VERSION_MINOR > 30 // note that the below warning is triggered on every reparse when there is a precompiled preamble // see also TestDUChain::testReparseIncludeGuard const QString path = QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath(); const IndexedString indexedPath(path); if (ClangHelpers::isHeader(path) && !clang_isFileMultipleIncludeGuarded(unit(), file) && !clang_Location_isInSystemHeader(clang_getLocationForOffset(d->m_unit, file, 0))) { ProblemPointer problem(new Problem); problem->setSeverity(IProblem::Warning); problem->setDescription(i18n("Header is not guarded against multiple inclusions")); problem->setExplanation(i18n("The given header is not guarded against multiple inclusions, " "either with the conventional #ifndef/#define/#endif macro guards or with #pragma once.")); problem->setFinalLocation({indexedPath, KTextEditor::Range()}); problem->setSource(IProblem::Preprocessor); problems << problem; // TODO: Easy to add an assistant here that adds the guards -- any takers? } #endif return problems; } CXTranslationUnit ParseSession::unit() const { return d ? d->m_unit : nullptr; } CXFile ParseSession::file(const QByteArray& path) const { return clang_getFile(unit(), path.constData()); } CXFile ParseSession::mainFile() const { return d ? d->m_file : nullptr; } bool ParseSession::reparse(const QVector& unsavedFiles, const ClangParsingEnvironment& environment) { if (!d || environment != d->m_environment) { return false; } auto unsaved = toClangApi(unsavedFiles); const auto code = clang_reparseTranslationUnit(d->m_unit, unsaved.size(), unsaved.data(), clang_defaultReparseOptions(d->m_unit)); if (code != CXError_Success) { qWarning() << "clang_reparseTranslationUnit return with error code" << code; // if error code != 0 => clang_reparseTranslationUnit invalidates the old translation unit => clean up clang_disposeTranslationUnit(d->m_unit); d->setUnit(nullptr); return false; } // update state d->setUnit(d->m_unit); return true; } ClangParsingEnvironment ParseSession::environment() const { return d->m_environment; } diff --git a/languages/clang/tests/test_duchain.cpp b/languages/clang/tests/test_duchain.cpp index ac67f840ce..996c43a1c7 100644 --- a/languages/clang/tests/test_duchain.cpp +++ b/languages/clang/tests/test_duchain.cpp @@ -1,1665 +1,1666 @@ /* * Copyright 2014 Milian Wolff * Copyright 2014 Kevin Funk * Copyright 2015 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #include "test_duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "duchain/clangparsingenvironmentfile.h" #include "duchain/clangparsingenvironment.h" #include "duchain/parsesession.h" #include #include QTEST_MAIN(TestDUChain); using namespace KDevelop; class TestEnvironmentProvider final : public IDefinesAndIncludesManager::BackgroundProvider { public: ~TestEnvironmentProvider() override = default; QHash< QString, QString > definesInBackground(const QString& /*path*/) const override { return defines; } Path::List includesInBackground(const QString& /*path*/) const override { return includes; } IDefinesAndIncludesManager::Type type() const override { return IDefinesAndIncludesManager::UserDefined; } QHash defines; Path::List includes; }; TestDUChain::~TestDUChain() = default; void TestDUChain::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); auto core = TestCore::initialize(); delete core->projectController(); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } void TestDUChain::cleanup() { if (m_provider) { IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data()); } } void TestDUChain::init() { m_provider.reset(new TestEnvironmentProvider); IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data()); } struct ExpectedComment { QString identifier; QString comment; }; Q_DECLARE_METATYPE(ExpectedComment) Q_DECLARE_METATYPE(AbstractType::WhichType) void TestDUChain::testComments() { QFETCH(QString, code); QFETCH(ExpectedComment, expectedComment); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto candidates = top->findDeclarations(QualifiedIdentifier(expectedComment.identifier)); QVERIFY(!candidates.isEmpty()); auto decl = candidates.first(); QString comment = QString::fromLocal8Bit(decl->comment()); comment = KDevelop::htmlToPlainText(comment, KDevelop::CompleteMode); QCOMPARE(comment, expectedComment.comment); } void TestDUChain::testComments_data() { QTest::addColumn("code"); QTest::addColumn("expectedComment"); // note: Clang only retrieves the comments when in doxygen-style format (i.e. '///', '/**', '///<') QTest::newRow("invalid1") << "//this is foo\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("invalid2") << "/*this is foo*/\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("basic1") << "///this is foo\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("basic2") << "/**this is foo*/\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("enumerator") << "enum Foo { bar1, ///localDeclarations().size(), 2); auto decl = file.topContext()->localDeclarations()[1]; QVERIFY(decl); auto function = dynamic_cast(decl); QVERIFY(function); auto functionType = function->type(); QVERIFY(functionType); QEXPECT_FAIL("namespace", "The ElaboratedType is not exposed through the libclang interface, not much we can do here", Abort); QVERIFY(functionType->returnType()->whichType() != AbstractType::TypeDelayed); QEXPECT_FAIL("typedef", "After using clang_getCanonicalType on ElaboratedType all typedef information get's stripped away", Continue); QCOMPARE(functionType->returnType()->whichType(), type); } void TestDUChain::testElaboratedType_data() { QTest::addColumn("code"); QTest::addColumn("type"); QTest::newRow("namespace") << "namespace NS{struct Type{};} struct NS::Type foo();" << AbstractType::TypeStructure; QTest::newRow("enum") << "enum Enum{}; enum Enum foo();" << AbstractType::TypeEnumeration; QTest::newRow("typedef") << "namespace NS{typedef int type;} NS::type foo();" << AbstractType::TypeAlias; } void TestDUChain::testInclude() { TestFile header("int foo() { return 42; }\n", "h"); // NOTE: header is _not_ explictly being parsed, instead the impl job does that TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); impl.parse(TopDUContext::AllDeclarationsContextsAndUses); auto implCtx = impl.topContext(); QVERIFY(implCtx); DUChainReadLocker lock; QCOMPARE(implCtx->localDeclarations().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); } void TestDUChain::testMissingInclude() { auto code = R"( #pragma once #include "missing1.h" template class A { T a; }; #include "missing2.h" class B : public A { }; )"; // NOTE: This fails and needs fixing. If the include of "missing2.h" // above is commented out, then it doesn't fail. Maybe // clang stops processing when it encounters the second missing // header, or similar. TestFile header(code, "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n", "cpp", &header); QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); DUChainReadLocker lock; auto top = impl.topContext(); QVERIFY(top); QCOMPARE(top->importedParentContexts().count(), 1); TopDUContext* headerCtx = dynamic_cast(top->importedParentContexts().first().context(top)); QVERIFY(headerCtx); QCOMPARE(headerCtx->url(), header.url()); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("", "Second missing header isn't reported", Continue); #endif QCOMPARE(headerCtx->problems().count(), 2); QCOMPARE(headerCtx->localDeclarations().count(), 2); auto a = dynamic_cast(headerCtx->localDeclarations().first()); QVERIFY(a); auto b = dynamic_cast(headerCtx->localDeclarations().last()); QVERIFY(b); #if CINDEX_VERSION_MINOR < 34 QEXPECT_FAIL("", "Base class isn't assigned correctly", Continue); #endif QCOMPARE(b->baseClassesSize(), 1u); #if CINDEX_VERSION_MINOR < 34 // at least the one problem we have should have been propagated QCOMPARE(top->problems().count(), 1); #else // two errors: // /tmp/testfile_f32415.h:3:10: error: 'missing1.h' file not found // /tmp/testfile_f32415.h:11:10: error: 'missing2.h' file not found QCOMPARE(top->problems().count(), 2); #endif } QByteArray createCode(const QByteArray& prefix, const int functions) { QByteArray code; code += "#ifndef " + prefix + "_H\n"; code += "#define " + prefix + "_H\n"; for (int i = 0; i < functions; ++i) { code += "void myFunc_" + prefix + "(int arg1, char arg2, const char* arg3);\n"; } code += "#endif\n"; return code; } void TestDUChain::testIncludeLocking() { TestFile header1(createCode("Header1", 1000), "h"); TestFile header2(createCode("Header2", 1000), "h"); TestFile header3(createCode("Header3", 1000), "h"); ICore::self()->languageController()->backgroundParser()->setThreadCount(3); TestFile impl1("#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl2("#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl3("#include \"" + header3.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); impl1.parse(TopDUContext::AllDeclarationsContextsAndUses); impl2.parse(TopDUContext::AllDeclarationsContextsAndUses); impl3.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(impl1.waitForParsed(5000)); QVERIFY(impl2.waitForParsed(5000)); QVERIFY(impl3.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(DUChain::self()->chainForDocument(header1.url())); QVERIFY(DUChain::self()->chainForDocument(header2.url())); QVERIFY(DUChain::self()->chainForDocument(header3.url())); } void TestDUChain::testReparse() { TestFile file("int main() { int i = 42; return i; }", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); DeclarationPointer mainDecl; DeclarationPointer iDecl; for (int i = 0; i < 3; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 1); DUContext *exprContext = file.topContext()->childContexts().first()->childContexts().first(); QCOMPARE(exprContext->localDeclarations().size(), 1); if (i) { QVERIFY(mainDecl); QCOMPARE(mainDecl.data(), file.topContext()->localDeclarations().first()); QVERIFY(iDecl); QCOMPARE(iDecl.data(), exprContext->localDeclarations().first()); } mainDecl = file.topContext()->localDeclarations().first(); iDecl = exprContext->localDeclarations().first(); QVERIFY(mainDecl->uses().isEmpty()); QCOMPARE(iDecl->uses().size(), 1); QCOMPARE(iDecl->uses().begin()->size(), 1); if (i == 1) { file.setFileContents("int main()\n{\nfloat i = 13; return i - 5;\n}\n"); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseError() { TestFile file("int i = 1 / 0;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); if (!i) { QCOMPARE(file.topContext()->problems().size(), 1); file.setFileContents("int i = 0;\n"); } else { QCOMPARE(file.topContext()->problems().size(), 0); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testTemplate() { TestFile file("template struct foo { T bar; };\n" "int main() { foo myFoo; return myFoo.bar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 2); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); auto mainCtx = file.topContext()->localDeclarations().last()->internalContext()->childContexts().first(); QVERIFY(mainCtx); auto myFoo = mainCtx->localDeclarations().first(); QVERIFY(myFoo); QCOMPARE(myFoo->abstractType()->toString().remove(' '), QStringLiteral("foo")); } void TestDUChain::testNamespace() { TestFile file("namespace foo { struct bar { int baz; }; }\n" "int main() { foo::bar myBar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 1); DUContext* top = file.topContext().data(); DUContext* mainCtx = file.topContext()->childContexts().last(); auto foo = top->localDeclarations().first(); QCOMPARE(foo->qualifiedIdentifier().toString(), QString("foo")); DUContext* fooCtx = file.topContext()->childContexts().first(); QCOMPARE(fooCtx->localScopeIdentifier().toString(), QString("foo")); QCOMPARE(fooCtx->scopeIdentifier(true).toString(), QString("foo")); QCOMPARE(fooCtx->localDeclarations().size(), 1); auto bar = fooCtx->localDeclarations().first(); QCOMPARE(bar->qualifiedIdentifier().toString(), QString("foo::bar")); QCOMPARE(fooCtx->childContexts().size(), 1); DUContext* barCtx = fooCtx->childContexts().first(); QCOMPARE(barCtx->localScopeIdentifier().toString(), QString("bar")); QCOMPARE(barCtx->scopeIdentifier(true).toString(), QString("foo::bar")); QCOMPARE(barCtx->localDeclarations().size(), 1); auto baz = barCtx->localDeclarations().first(); QCOMPARE(baz->qualifiedIdentifier().toString(), QString("foo::bar::baz")); for (auto ctx : {top, mainCtx}) { QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar::baz")).size(), 1); } } void TestDUChain::testAutoTypeDeduction() { TestFile file(R"( const volatile auto foo = 5; template struct myTemplate {}; myTemplate& > templRefParam; auto autoTemplRefParam = templRefParam; )", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 4); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); Declaration* decl = ctx->findDeclarations(QualifiedIdentifier("foo"))[0]; QCOMPARE(decl->identifier(), Identifier("foo")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); #if CINDEX_VERSION_MINOR < 31 QCOMPARE(decl->toString(), QStringLiteral("const volatile auto foo")); #else QCOMPARE(decl->toString(), QStringLiteral("const volatile int foo")); #endif decl = ctx->findDeclarations(QualifiedIdentifier("autoTemplRefParam"))[0]; QVERIFY(decl); QVERIFY(decl->abstractType()); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "Auto type is not exposed via LibClang", Continue); #endif QCOMPARE(decl->abstractType()->toString(), QStringLiteral("myTemplate< myTemplate< int >& >")); } void TestDUChain::testTypeDeductionInTemplateInstantiation() { // see: http://clang-developers.42468.n3.nabble.com/RFC-missing-libclang-query-functions-features-td2504253.html TestFile file("template struct foo { T member; } foo f; auto i = f.member;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 3); Declaration* decl = 0; // check 'foo' declaration decl = ctx->localDeclarations()[0]; QVERIFY(decl); QCOMPARE(decl->identifier(), Identifier("foo")); // check type of 'member' inside declaration-scope QCOMPARE(ctx->childContexts().size(), 1); DUContext* fooCtx = ctx->childContexts().first(); QVERIFY(fooCtx); // Should there really be two declarations? QCOMPARE(fooCtx->localDeclarations().size(), 2); decl = fooCtx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("member")); // check type of 'member' in definition of 'f' decl = ctx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("f")); decl = ctx->localDeclarations()[2]; QCOMPARE(decl->identifier(), Identifier("i")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); } void TestDUChain::testVirtualMemberFunction() { //Forward-declarations with "struct" or "class" are considered equal, so make sure the override is detected correctly. TestFile file("struct S {}; struct A { virtual S* ret(); }; struct B : public A { virtual S* ret(); };", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->childContexts()[2]->localDeclarations().count(), 1); Declaration* decl = top->childContexts()[2]->localDeclarations()[0]; QCOMPARE(decl->identifier(), Identifier("ret")); QVERIFY(DUChainUtils::getOverridden(decl)); } void TestDUChain::testBaseClasses() { TestFile file("class Base {}; class Inherited : public Base {};", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); ClassDeclaration* inheritedDecl = dynamic_cast(top->localDeclarations()[1]); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->baseClassesSize(), 1u); QCOMPARE(baseDecl->uses().count(), 1); QCOMPARE(baseDecl->uses().first().count(), 1); QCOMPARE(baseDecl->uses().first().first(), RangeInRevision(0, 40, 0, 44)); } void TestDUChain::testReparseBaseClasses() { TestFile file("struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseBaseClassesTemplates() { TestFile file("template struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testGetInheriters_data() { QTest::addColumn("code"); QTest::newRow("inline") << "struct Base { struct Inner {}; }; struct Inherited : Base, Base::Inner {};"; QTest::newRow("outline") << "struct Base { struct Inner; }; struct Base::Inner {}; struct Inherited : Base, Base::Inner {};"; } void TestDUChain::testGetInheriters() { QFETCH(QString, code); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); DUContext* baseCtx = baseDecl->internalContext(); QVERIFY(baseCtx); QCOMPARE(baseCtx->localDeclarations().count(), 1); Declaration* innerDecl = baseCtx->localDeclarations().first(); QCOMPARE(innerDecl->identifier(), Identifier("Inner")); if (auto forward = dynamic_cast(innerDecl)) { innerDecl = forward->resolve(top); } QVERIFY(dynamic_cast(innerDecl)); Declaration* inheritedDecl = top->localDeclarations().last(); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); uint maxAllowedSteps = uint(-1); auto baseInheriters = DUChainUtils::getInheriters(baseDecl, maxAllowedSteps); QCOMPARE(baseInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto innerInheriters = DUChainUtils::getInheriters(innerDecl, maxAllowedSteps); QCOMPARE(innerInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto inheritedInheriters = DUChainUtils::getInheriters(inheritedDecl, maxAllowedSteps); QCOMPARE(inheritedInheriters.count(), 0); } void TestDUChain::testGlobalFunctionDeclaration() { TestFile file("void foo(int arg1, char arg2);\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); file.waitForParsed(); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); QCOMPARE(file.topContext()->childContexts().size(), 1); QVERIFY(!file.topContext()->childContexts().first()->inSymbolTable()); } void TestDUChain::testFunctionDefinitionVsDeclaration() { TestFile file("void func(); void func() {}\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto funcDecl = file.topContext()->localDeclarations()[0]; QVERIFY(!funcDecl->isDefinition()); QVERIFY(!dynamic_cast(funcDecl)); auto funcDef = file.topContext()->localDeclarations()[1]; QVERIFY(dynamic_cast(funcDef)); QVERIFY(funcDef->isDefinition()); } void TestDUChain::testEnsureNoDoubleVisit() { // On some language construct, we may up visiting the same cursor multiple times // Example: "struct SomeStruct {} s;" // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // decl: "struct SomeStruct s " of kind VarDecl (9) in main.cpp@[(1,1),(1,19)] // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // // => We end up visiting the StructDecl twice (or more) // That's because we use clang_visitChildren not just on the translation unit cursor. // Apparently just "recursing" vs. "visiting children explicitly" // results in a different AST traversal TestFile file("struct SomeStruct {} s;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); // there should only be one declaration for "SomeStruct" auto candidates = top->findDeclarations(QualifiedIdentifier("SomeStruct")); QCOMPARE(candidates.size(), 1); } void TestDUChain::testParsingEnvironment() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(file.topContext()->parsingEnvironmentFile().data())); QCOMPARE(envFile->features(), astFeatures); QVERIFY(envFile->featuresSatisfied(astFeatures)); QCOMPARE(envFile->environmentQuality(), ClangParsingEnvironment::Source); // if no environment is given, no update should be triggered QVERIFY(!envFile->needsUpdate()); // same env should also not trigger a reparse ClangParsingEnvironment env = session.environment(); QCOMPARE(env.quality(), ClangParsingEnvironment::Source); QVERIFY(!envFile->needsUpdate(&env)); // but changing the environment should trigger an update env.addIncludes(Path::List() << Path("/foo/bar/baz")); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // setting the environment quality higher should require an update env.setQuality(ClangParsingEnvironment::BuildSystem); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // changing defines requires an update env.addDefines(QHash{ { "foo", "bar" } }); QVERIFY(envFile->needsUpdate(&env)); // but only when changing the defines for the envFile's TU const auto barTU = IndexedString("bar.cpp"); const auto oldTU = env.translationUnitUrl(); env.setTranslationUnitUrl(barTU); QCOMPARE(env.translationUnitUrl(), barTU); QVERIFY(!envFile->needsUpdate(&env)); env.setTranslationUnitUrl(oldTU); QVERIFY(envFile->needsUpdate(&env)); // update it again envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); lastEnv = env; // now compare against a lower quality environment // in such a case, we do not want to trigger an update env.setQuality(ClangParsingEnvironment::Unknown); env.setTranslationUnitUrl(barTU); QVERIFY(!envFile->needsUpdate(&env)); // even when the environment changes env.addIncludes(Path::List() << Path("/lalalala")); QVERIFY(!envFile->needsUpdate(&env)); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); QVERIFY(DUChain::self()->environmentFileForDocument(indexed)); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(DUChain::self()->environmentFileForDocument(indexed).data())); QVERIFY(envFile); QCOMPARE(envFile->features(), features); QVERIFY(envFile->featuresSatisfied(features)); QVERIFY(!envFile->needsUpdate(&lastEnv)); DUChain::self()->removeDocumentChain(indexed.data()); } } void TestDUChain::testActiveDocumentHasASTAttached() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); QVERIFY(top->ast()); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); } QUrl url; { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(!ctx->ast()); url = ctx->url().toUrl(); } QVERIFY(!QFileInfo::exists(url.toLocalFile())); QFile file(url.toLocalFile()); file.open(QIODevice::WriteOnly); Q_ASSERT(file.isOpen()); auto document = ICore::self()->documentController()->openDocument(url); QVERIFY(document); ICore::self()->documentController()->activateDocument(document); QApplication::processEvents(); ICore::self()->languageController()->backgroundParser()->parseDocuments(); QThread::sleep(1); document->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(ctx->ast()); } DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(indexed.data()); } void TestDUChain::testSystemIncludes() { ClangParsingEnvironment env; Path::List projectIncludes = { Path("/projects/1"), Path("/projects/1/sub"), Path("/projects/2"), Path("/projects/2/sub") }; env.addIncludes(projectIncludes); auto includes = env.includes(); // no project paths set, so everything is considered a system include QCOMPARE(includes.system, projectIncludes); QVERIFY(includes.project.isEmpty()); Path::List systemIncludes = { Path("/sys"), Path("/sys/sub") }; env.addIncludes(systemIncludes); includes = env.includes(); QCOMPARE(includes.system, projectIncludes + systemIncludes); QVERIFY(includes.project.isEmpty()); Path::List projects = { Path("/projects/1"), Path("/projects/2") }; env.setProjectPaths(projects); // now the list should be properly separated QCOMPARE(env.projectPaths(), projects); includes = env.includes(); QCOMPARE(includes.system, systemIncludes); QCOMPARE(includes.project, projectIncludes); } void TestDUChain::benchDUChainBuilder() { QBENCHMARK_ONCE { TestFile file( "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(60000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); } } void TestDUChain::testReparseWithAllDeclarationsContextsAndUses() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto dec = file.topContext()->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies is disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); } file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(500)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto mainDecl = file.topContext()->localDeclarations()[1]; QVERIFY(mainDecl->uses().isEmpty()); auto foo = file.topContext()->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); } } void TestDUChain::testReparseOnDocumentActivated() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; auto ctx = file.topContext(); QVERIFY(ctx); QCOMPARE(ctx->childContexts().size(), 2); QCOMPARE(ctx->localDeclarations().size(), 2); auto dec = ctx->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies was disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); QVERIFY(!ctx->ast()); } auto backgroundParser = ICore::self()->languageController()->backgroundParser(); QVERIFY(!backgroundParser->isQueued(file.url())); auto doc = ICore::self()->documentController()->openDocument(file.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file.url())); QSignalSpy spy(backgroundParser, &BackgroundParser::parseJobFinished); spy.wait(); doc->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = file.topContext(); QCOMPARE(ctx->features() & TopDUContext::AllDeclarationsContextsAndUses, static_cast(TopDUContext::AllDeclarationsContextsAndUses)); QVERIFY(ctx->topContext()->ast()); } } void TestDUChain::testReparseInclude() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); // Use TopDUContext::AST to imitate that document is opened in the editor, so that ClangParseJob can store translation unit, that'll be used for reparsing. impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsAndContexts|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->importedParentContexts().size(), 1); } impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->localDeclarations().size(), 1); QCOMPARE(implCtx->importedParentContexts().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); } void TestDUChain::testReparseChangeEnvironment() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); uint hashes[3] = {0, 0, 0}; for (int i = 0; i < 3; ++i) { impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(impl.topContext()); auto env = dynamic_cast(impl.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); hashes[i] = env->environmentHash(); QVERIFY(hashes[i]); // we should never end up with multiple env files or chains in memory for these files QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); } // in every run, we expect the environment to have changed for (int j = 0; j < i; ++j) { QVERIFY(hashes[i] != hashes[j]); } if (i == 0) { // 1) change defines m_provider->defines.insert("foooooooo", "baaar!"); } else if (i == 1) { // 2) change includes m_provider->includes.append(Path("/foo/bar/asdf/lalala")); } // 3) stop } } void TestDUChain::testMacrosRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testMultiLineMacroRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x\n);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testNestedMacroRanges() { TestFile file("#define INNER int var; var = 0;\n#define MACRO() INNER\nint main(){MACRO(\n);}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto main = file.topContext()->localDeclarations()[2]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto var = mainCtx->localDeclarations().first(); QVERIFY(var); QCOMPARE(var->range(), RangeInRevision(2,11,2,11)); QCOMPARE(var->uses().size(), 1); QCOMPARE(var->uses().begin()->first(), RangeInRevision(2,11,2,11)); } void TestDUChain::testNestedImports() { TestFile B("#pragma once\nint B();\n", "h"); TestFile C("#pragma once\n#include \"" + B.url().byteArray() + "\"\nint C();\n", "h"); TestFile A("#include \"" + B.url().byteArray() + "\"\n" + "#include \"" + C.url().byteArray() + "\"\nint A();\n", "cpp"); A.parse(); QVERIFY(A.waitForParsed(5000)); DUChainReadLocker lock; auto BCtx = DUChain::self()->chainForDocument(B.url().toUrl()); QVERIFY(BCtx); QVERIFY(BCtx->importedParentContexts().isEmpty()); auto CCtx = DUChain::self()->chainForDocument(C.url().toUrl()); QVERIFY(CCtx); QCOMPARE(CCtx->importedParentContexts().size(), 1); QVERIFY(CCtx->imports(BCtx, CursorInRevision(1, 10))); auto ACtx = A.topContext(); QVERIFY(ACtx); QCOMPARE(ACtx->importedParentContexts().size(), 2); QVERIFY(ACtx->imports(BCtx, CursorInRevision(0, 10))); QVERIFY(ACtx->imports(CCtx, CursorInRevision(1, 10))); } void TestDUChain::testEnvironmentWithDifferentOrderOfElements() { TestFile file("int main();\n", "cpp"); m_provider->includes.clear(); m_provider->includes.append(Path("/path1")); m_provider->includes.append(Path("/path2")); m_provider->defines.clear(); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); uint previousHash = 0; for (int i: {0, 1, 2, 3}) { file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); auto env = dynamic_cast(file.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); if (previousHash) { if (i == 3) { QVERIFY(previousHash != env->environmentHash()); } else { QCOMPARE(previousHash, env->environmentHash()); } } previousHash = env->environmentHash(); QVERIFY(previousHash); } if (i == 0) { //Change order of defines. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); } else if (i == 1) { //Add the same macros twice. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); } else if (i == 2) { //OTOH order of includes should change hash of the environment. m_provider->includes.clear(); m_provider->includes.append(Path("/path2")); m_provider->includes.append(Path("/path1")); } } } void TestDUChain::testReparseMacro() { TestFile file("#define DECLARE(a) typedef struct a##_ {} *a;\nDECLARE(D);\nD d;", "cpp"); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 5); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,15)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,7)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); auto structTypedef = file.topContext()->localDeclarations()[3]; QVERIFY(structTypedef); QCOMPARE(structTypedef->range(), RangeInRevision(1,8,1,9)); QCOMPARE(structTypedef->uses().size(), 1); QCOMPARE(structTypedef->uses().begin()->first(), RangeInRevision(2,0,2,1)); } void TestDUChain::testGotoStatement() { TestFile file("int main() {\ngoto label;\ngoto label;\nlabel: return 0;}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); auto main = file.topContext()->localDeclarations()[0]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto label = mainCtx->localDeclarations().first(); QVERIFY(label); QCOMPARE(label->range(), RangeInRevision(3,0,3,5)); QCOMPARE(label->uses().size(), 1); QCOMPARE(label->uses().begin()->first(), RangeInRevision(1,5,1,10)); QCOMPARE(label->uses().begin()->last(), RangeInRevision(2,5,2,10)); } void TestDUChain::testRangesOfOperatorsInsideMacro() { TestFile file("class Test{public: Test& operator++(int);};\n#define MACRO(var) var++;\nint main(){\nTest tst; MACRO(tst)}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto testClass = file.topContext()->localDeclarations()[0]; QVERIFY(testClass); auto operatorPlusPlus = testClass->internalContext()->localDeclarations().first(); QVERIFY(operatorPlusPlus); QCOMPARE(operatorPlusPlus->uses().size(), 1); QCOMPARE(operatorPlusPlus->uses().begin()->first(), RangeInRevision(3,10,3,10)); } void TestDUChain::testUsesCreatedForDeclarations() { auto code = R"(template void functionTemplate(T); template void functionTemplate(U) {} namespace NS { class Class{}; } using NS::Class; Class function(); NS::Class function() { return {}; } int main () { functionTemplate(int()); function(); } )"; TestFile file(code, "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto functionTemplate = file.topContext()->findDeclarations(QualifiedIdentifier("functionTemplate")); QVERIFY(!functionTemplate.isEmpty()); auto functionTemplateDeclaration = DUChainUtils::declarationForDefinition(functionTemplate.first()); QVERIFY(!functionTemplateDeclaration->isDefinition()); #if CINDEX_VERSION_MINOR < 29 QEXPECT_FAIL("", "No API in LibClang to determine function template type", Continue); #endif QCOMPARE(functionTemplateDeclaration->uses().count(), 1); auto function = file.topContext()->findDeclarations(QualifiedIdentifier("function")); QVERIFY(!function.isEmpty()); auto functionDeclaration = DUChainUtils::declarationForDefinition(function.first()); QVERIFY(!functionDeclaration->isDefinition()); QCOMPARE(functionDeclaration->uses().count(), 1); } void TestDUChain::testReparseIncludeGuard() { TestFile header("#ifndef GUARD\n#define GUARD\nint something;\n#endif\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n", "cpp", &header); impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } } void TestDUChain::testExternC() { auto code = R"(extern "C" { void foo(); })"; TestFile file(code, "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(!top->findDeclarations(QualifiedIdentifier("foo")).isEmpty()); } void TestDUChain::testReparseUnchanged_data() { QTest::addColumn("headerCode"); QTest::addColumn("implCode"); QTest::newRow("include-guards") << R"( #ifndef GUARD #define GUARD int something; #endif )" << R"( #include "%1" )"; QTest::newRow("template-default-parameters") << R"( #ifndef TEST_H #define TEST_H template class dummy; template class dummy { int field[T]; }; #endif )" << R"( #include "%1" int main(int, char **) { dummy<> x; (void)x; } )"; } void TestDUChain::testReparseUnchanged() { QFETCH(QString, headerCode); QFETCH(QString, implCode); TestFile header(headerCode, "h"); TestFile impl(implCode.arg(header.url().str()), "cpp", &header); auto checkProblems = [&] (bool reparsed) { DUChainReadLocker lock; auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(headerCtx->problems().isEmpty()); auto implCtx = DUChain::self()->chainForDocument(impl.url()); QVERIFY(implCtx); if (reparsed && CINDEX_VERSION_MINOR > 29 && CINDEX_VERSION_MINOR < 33) { QEXPECT_FAIL("template-default-parameters", "the precompiled preamble messes the default template parameters up in clang 3.7", Continue); } QVERIFY(implCtx->problems().isEmpty()); }; impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); checkProblems(false); impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); checkProblems(true); } void TestDUChain::testTypeAliasTemplate() { TestFile file("template using TypeAliasTemplate = T;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto templateAlias = file.topContext()->localDeclarations().last(); QVERIFY(templateAlias); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "TypeAliasTemplate is not exposed via LibClang", Abort); #endif QVERIFY(templateAlias->abstractType()); QCOMPARE(templateAlias->abstractType()->toString(), QStringLiteral("TypeAliasTemplate")); } static bool containsErrors(const QList& problems) { auto it = std::find_if(problems.begin(), problems.end(), [] (const Problem::Ptr& problem) { return problem->severity() == Problem::Error; }); return it != problems.end(); } static bool expectedXmmintrinErrors(const QList& problems) { foreach (const auto& problem, problems) { if (problem->severity() == Problem::Error && !problem->description().contains("Cannot initialize a parameter of type")) { return false; } } return true; } static void verifyNoErrors(TopDUContext* top, QSet& checked) { const auto problems = top->problems(); if (containsErrors(problems)) { qDebug() << top->url() << top->problems(); if (top->url().str().endsWith("xmmintrin.h") && expectedXmmintrinErrors(problems)) { QEXPECT_FAIL("", "there are still some errors in xmmintrin.h b/c some clang provided intrinsincs are more strict than the GCC ones.", Continue); QVERIFY(false); } else { QFAIL("parse error detected"); } } const auto imports = top->importedParentContexts(); foreach (const auto& import, imports) { auto ctx = import.context(top); QVERIFY(ctx); auto importedTop = ctx->topContext(); if (checked.contains(importedTop)) { continue; } checked.insert(importedTop); verifyNoErrors(importedTop, checked); } } void TestDUChain::testGccCompatibility() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Compiler").writeEntry("Name", "GCC"); m_projectController->addProject(project); { + // TODO: Also test in C mode. Currently it doesn't work (some intrinsics missing?) TestFile file(R"( #include int main() { return 0; } - )", "c", project, dir.path()); + )", "cpp", project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QSet checked; verifyNoErrors(file.topContext(), checked); } m_projectController->closeAllProjects(); } void TestDUChain::testQtIntegration() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir includeDir; { QDir dir(includeDir.path()); dir.mkdir("QtCore"); // create the file but don't put anything in it QFile header(includeDir.path() + "/QtCore/qobjectdefs.h"); QVERIFY(header.open(QIODevice::WriteOnly | QIODevice::Text)); } QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Includes").writeEntry("1", QString(includeDir.path() + "/QtCore")); m_projectController->addProject(project); { TestFile file(R"( #define slots #define signals #define Q_SLOTS #define Q_SIGNALS #include struct MyObject { public: void other1(); public slots: void slot1(); signals: void signal1(); private Q_SLOTS: void slot2(); Q_SIGNALS: void signal2(); public: void other2(); }; )", "cpp", project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); const auto methods = top->childContexts().last()->localDeclarations(); QCOMPARE(methods.size(), 6); foreach(auto method, methods) { auto classFunction = dynamic_cast(method); QVERIFY(classFunction); auto id = classFunction->identifier().toString(); QCOMPARE(classFunction->isSignal(), id.startsWith(QLatin1String("signal"))); QCOMPARE(classFunction->isSlot(), id.startsWith(QLatin1String("slot"))); } } m_projectController->closeAllProjects(); } diff --git a/languages/clang/tests/test_files.cpp b/languages/clang/tests/test_files.cpp index a265a96da3..bdf8fd6117 100644 --- a/languages/clang/tests/test_files.cpp +++ b/languages/clang/tests/test_files.cpp @@ -1,97 +1,96 @@ /************************************************************************************* * Copyright (C) 2013 by Milian Wolff * * Copyright (C) 2013 Olivier de Gaalon * * * * 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 "test_files.h" #include "duchain/builder.h" #include #include #include #include #include #include #include #include "testfilepaths.h" //Include all used json tests, otherwise "Test not found" #include #include #include #include #include using namespace KDevelop; QTEST_MAIN(TestFiles) void TestFiles::initTestCase() { qputenv("KDEV_CLANG_JSON_TEST_RUN", "1"); qputenv("KDEV_CLANG_EXTRA_ARGUMENTS", "-Wno-unused-variable -Wno-unused-parameter -Wno-unused-comparison -Wno-unused-value -Wno-unused-private-field -Wno-ignored-attributes"); QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({"kdevclangsupport"}); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); CodeRepresentation::setDiskChangesForbidden(true); } void TestFiles::cleanupTestCase() { TestCore::shutdown(); } void TestFiles::testFiles_data() { QTest::addColumn("fileName"); const QString testDirPath = TEST_FILES_DIR; const QStringList files = QDir(testDirPath).entryList({"*.h", "*.cpp", "*.c"}, QDir::Files); foreach (const QString& file, files) { QTest::newRow(file.toUtf8().constData()) << QString(testDirPath + '/' + file); } } void TestFiles::testFiles() { QFETCH(QString, fileName); const IndexedString indexedFileName(fileName); ReferencedTopDUContext top = DUChain::self()->waitForUpdate(indexedFileName, TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(top); DUChainReadLocker lock; DeclarationValidator validator; top->visit(validator); QVERIFY(validator.testsPassed()); foreach(auto problem, top->problems()) { qDebug() << problem; } if (!QTest::currentDataTag() || strcmp("invalid.cpp", QTest::currentDataTag()) != 0) { - QEXPECT_FAIL("purec.c", "not working properly yet", Continue); QVERIFY(top->problems().isEmpty()); } } diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp index 8fb93371f5..24e532a62e 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp @@ -1,224 +1,234 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "compilerprovider.h" #include "../debugarea.h" #include "compilerfactories.h" #include "settingsmanager.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { class NoCompiler : public ICompiler { public: NoCompiler(): ICompiler(i18n("None"), QString(), QString(), false) {} QHash< QString, QString > defines(const QString&) const override { return {}; } Path::List includes(const QString&) const override { return {}; } }; static CompilerPointer createDummyCompiler() { static CompilerPointer compiler(new NoCompiler()); return compiler; } ConfigEntry configForItem(KDevelop::ProjectBaseItem* item) { if(!item){ return {}; } const Path itemPath = item->path(); const Path rootDirectory = item->project()->path(); auto paths = SettingsManager::globalInstance()->readPaths(item->project()->projectConfiguration().data()); ConfigEntry config; Path closestPath; // find config entry closest to the requested item for (const auto& entry : paths) { auto configEntry = entry; Path targetDirectory = rootDirectory; targetDirectory.addPath(entry.path); if (targetDirectory == itemPath) { return configEntry; } if (targetDirectory.isParentOf(itemPath)) { if (config.path.isEmpty() || targetDirectory.segments().size() > closestPath.segments().size()) { config = configEntry; closestPath = targetDirectory; } } } return config; } } CompilerProvider::CompilerProvider( SettingsManager* settings, QObject* parent ) : QObject( parent ) , m_settings(settings) { m_factories.append(CompilerFactoryPointer(new GccFactory())); m_factories.append(CompilerFactoryPointer(new ClangFactory())); #ifdef _WIN32 m_factories.append(CompilerFactoryPointer(new MsvcFactory())); #endif if (!QStandardPaths::findExecutable( "clang" ).isEmpty()) { m_factories[1]->registerDefaultCompilers(this); } if (!QStandardPaths::findExecutable( "gcc" ).isEmpty()) { m_factories[0]->registerDefaultCompilers(this); } #ifdef _WIN32 if (!QStandardPaths::findExecutable("cl.exe").isEmpty()) { m_factories[2]->registerDefaultCompilers(this); } #endif registerCompiler(createDummyCompiler()); retrieveUserDefinedCompilers(); } CompilerProvider::~CompilerProvider() = default; QHash CompilerProvider::defines( ProjectBaseItem* item ) const { auto config = configForItem(item); - return config.compiler->defines(config.parserArguments); + auto languageType = Utils::Cpp; + if (item) { + languageType = Utils::languageType(item->path(), config.parserArguments.parseAmbiguousAsCPP); + } + + return config.compiler->defines(languageType == Utils::C ? config.parserArguments.cArguments : config.parserArguments.cppArguments); } Path::List CompilerProvider::includes( ProjectBaseItem* item ) const { auto config = configForItem(item); - return config.compiler->includes(config.parserArguments); + auto languageType = Utils::Cpp; + if (item) { + languageType = Utils::languageType(item->path(), config.parserArguments.parseAmbiguousAsCPP); + } + + return config.compiler->includes(languageType == Utils::C ? config.parserArguments.cArguments : config.parserArguments.cppArguments); } IDefinesAndIncludesManager::Type CompilerProvider::type() const { return IDefinesAndIncludesManager::CompilerSpecific; } CompilerPointer CompilerProvider::checkCompilerExists( const CompilerPointer& compiler ) const { //This may happen for opened for the first time projects if ( !compiler ) { for ( auto& compiler : m_compilers ) { if ( QStandardPaths::findExecutable( compiler->path() ).isEmpty() ) { continue; } return compiler; } } else { for ( auto it = m_compilers.constBegin(); it != m_compilers.constEnd(); it++ ) { if ( (*it)->name() == compiler->name() ) { return *it; } } } return createDummyCompiler(); } QVector< CompilerPointer > CompilerProvider::compilers() const { return m_compilers; } CompilerPointer CompilerProvider::compilerForItem( KDevelop::ProjectBaseItem* item ) const { auto compiler = configForItem(item).compiler; Q_ASSERT(compiler); return compiler; } bool CompilerProvider::registerCompiler(const CompilerPointer& compiler) { if (!compiler) { return false; } for(auto c: m_compilers){ if (c->name() == compiler->name()) { return false; } } m_compilers.append(compiler); return true; } void CompilerProvider::unregisterCompiler(const CompilerPointer& compiler) { if (!compiler->editable()) { return; } for (int i = 0; i < m_compilers.count(); i++) { if (m_compilers[i]->name() == compiler->name()) { m_compilers.remove(i); break; } } } QVector< CompilerFactoryPointer > CompilerProvider::compilerFactories() const { return m_factories; } void CompilerProvider::retrieveUserDefinedCompilers() { auto compilers = m_settings->userDefinedCompilers(); for (auto c : compilers) { registerCompiler(c); } } diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp index a49ce05a27..590ba4a7c5 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp @@ -1,361 +1,416 @@ /* * This file is part of KDevelop * * Copyright 2010 Andreas Pakulat * 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 "settingsmanager.h" #include #include #include #include +#include #include #include #include #include #include #include #include "compilerprovider.h" using namespace KDevelop; namespace { namespace ConfigConstants { const QString configKey = QLatin1String( "CustomDefinesAndIncludes" ); const QString definesKey = QLatin1String( "Defines" ); const QString includesKey = QLatin1String( "Includes" ); const QString projectPathPrefix = QLatin1String( "ProjectPath" ); const QString projectPathKey = QLatin1String( "Path" ); const QString customBuildSystemGroup = QLatin1String( "CustomBuildSystem" ); const QString definesAndIncludesGroup = QLatin1String( "Defines And Includes" ); const QString compilersGroup = QLatin1String( "Compilers" ); const QString compilerNameKey = QLatin1String( "Name" ); const QString compilerPathKey = QLatin1String( "Path" ); const QString compilerTypeKey = QLatin1String( "Type" ); -QString parserArguments() +QString parserArgumentsCPP() { return QStringLiteral("parserArguments"); } +QString parserArgumentsC() +{ + return QStringLiteral("parserArgumentsC"); +} + +QString parseAmbiguousAsCPP() +{ + return QStringLiteral("parseAmbiguousAsCPP"); +} } // the grouplist is randomly sorted b/c it uses QSet internally // we sort the keys here, as the structure is properly defined for us QStringList sorted(QStringList list) { std::sort(list.begin(), list.end()); return list; } -QString defaultArguments() +ParserArguments defaultArguments() { - return QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11"); + const static ParserArguments arguments + { + QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c99"), + QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11"), + true + }; + + return arguments; } CompilerPointer createCompilerFromConfig(KConfigGroup& cfg) { auto grp = cfg.group("Compiler"); auto name = grp.readEntry( ConfigConstants::compilerNameKey, QString() ); if (name.isEmpty()) { return SettingsManager::globalInstance()->provider()->checkCompilerExists({}); } for (auto c : SettingsManager::globalInstance()->provider()->compilers()) { if (c->name() == name) { return c; } } // Otherwise we have no such compiler registered (broken config file), return default one return SettingsManager::globalInstance()->provider()->checkCompilerExists({}); } void writeCompilerToConfig(KConfigGroup& cfg, const CompilerPointer& compiler) { Q_ASSERT(compiler); auto grp = cfg.group("Compiler"); // Store only compiler name, path and type retrieved from registered compilers grp.writeEntry(ConfigConstants::compilerNameKey, compiler->name()); } void doWriteSettings( KConfigGroup grp, const QList& paths ) { int pathIndex = 0; for ( const auto& path : paths ) { KConfigGroup pathgrp = grp.group( ConfigConstants::projectPathPrefix + QString::number( pathIndex++ ) ); pathgrp.writeEntry(ConfigConstants::projectPathKey, path.path); - pathgrp.writeEntry(ConfigConstants::parserArguments(), path.parserArguments); + pathgrp.writeEntry(ConfigConstants::parserArgumentsCPP(), path.parserArguments.cppArguments); + pathgrp.writeEntry(ConfigConstants::parserArgumentsC(), path.parserArguments.cArguments); + pathgrp.writeEntry(ConfigConstants::parseAmbiguousAsCPP(), path.parserArguments.parseAmbiguousAsCPP); { int index = 0; KConfigGroup includes(pathgrp.group(ConfigConstants::includesKey)); for( auto it = path.includes.begin() ; it != path.includes.end(); ++it){ includes.writeEntry(QString::number(++index), *it); } } { KConfigGroup defines(pathgrp.group(ConfigConstants::definesKey)); for (auto it = path.defines.begin(); it != path.defines.end(); ++it) { defines.writeEntry(it.key(), it.value()); } } writeCompilerToConfig(pathgrp, path.compiler); } } /// @param remove if true all read entries will be removed from the config file QList doReadSettings( KConfigGroup grp, bool remove = false ) { QList paths; for( const QString &grpName : sorted(grp.groupList()) ) { if ( !grpName.startsWith( ConfigConstants::projectPathPrefix ) ) { continue; } KConfigGroup pathgrp = grp.group( grpName ); ConfigEntry path; path.path = pathgrp.readEntry( ConfigConstants::projectPathKey, "" ); - path.parserArguments = pathgrp.readEntry(ConfigConstants::parserArguments(), defaultArguments()); + path.parserArguments.cppArguments = pathgrp.readEntry(ConfigConstants::parserArgumentsCPP(), defaultArguments().cppArguments); + path.parserArguments.cArguments = pathgrp.readEntry(ConfigConstants::parserArgumentsC(), defaultArguments().cArguments); + path.parserArguments.parseAmbiguousAsCPP = pathgrp.readEntry(ConfigConstants::parseAmbiguousAsCPP(), defaultArguments().parseAmbiguousAsCPP); - if (path.parserArguments.isEmpty()) { - path.parserArguments = defaultArguments(); + if (path.parserArguments.cppArguments.isEmpty()) { + path.parserArguments.cppArguments = defaultArguments().cppArguments; + } + + if (path.parserArguments.cArguments.isEmpty()) { + path.parserArguments.cArguments = defaultArguments().cArguments; } { // defines // Backwards compatibility with old config style if(pathgrp.hasKey(ConfigConstants::definesKey)) { QByteArray tmp = pathgrp.readEntry( ConfigConstants::definesKey, QByteArray() ); QDataStream s( tmp ); s.setVersion( QDataStream::Qt_4_5 ); // backwards compatible reading QHash defines; s >> defines; path.setDefines(defines); } else { KConfigGroup defines(pathgrp.group(ConfigConstants::definesKey)); QMap defMap = defines.entryMap(); path.defines.reserve(defMap.size()); for(auto it = defMap.constBegin(); it != defMap.constEnd(); ++it) { QString key = it.key(); if(key.isEmpty()) { // Toss out the invalid key and value since a valid // value needs a valid key continue; } else { path.defines.insert(key, it.value()); } } } } { // includes // Backwards compatibility with old config style if(pathgrp.hasKey(ConfigConstants::includesKey)){ QByteArray tmp = pathgrp.readEntry( ConfigConstants::includesKey, QByteArray() ); QDataStream s( tmp ); s.setVersion( QDataStream::Qt_4_5 ); s >> path.includes; } else { KConfigGroup includes(pathgrp.group(ConfigConstants::includesKey)); QMap incMap = includes.entryMap(); for(auto it = incMap.begin(); it != incMap.end(); ++it){ QString value = it.value(); if(value.isEmpty()){ continue; } path.includes += value; } } } path.compiler = createCompilerFromConfig(pathgrp); if ( remove ) { pathgrp.deleteGroup(); } - Q_ASSERT(!path.parserArguments.isEmpty()); + Q_ASSERT(!path.parserArguments.cppArguments.isEmpty()); + Q_ASSERT(!path.parserArguments.cArguments.isEmpty()); paths << path; } return paths; } /** * Reads and converts paths from old (Custom Build System's) format to the current one. * @return all converted paths (if any) */ QList convertedPaths( KConfig* cfg ) { KConfigGroup group = cfg->group( ConfigConstants::customBuildSystemGroup ); if ( !group.isValid() ) return {}; QList paths; foreach( const QString &grpName, sorted(group.groupList()) ) { KConfigGroup subgroup = group.group( grpName ); if ( !subgroup.isValid() ) continue; paths += doReadSettings( subgroup, true ); } return paths; } } void ConfigEntry::setDefines(const QHash& newDefines) { defines.clear(); defines.reserve(newDefines.size()); for (auto it = newDefines.begin(); it != newDefines.end(); ++it) { defines[it.key()] = it.value().toString(); } } SettingsManager::SettingsManager() : m_provider(this) {} SettingsManager::~SettingsManager() {} SettingsManager* SettingsManager::globalInstance() { Q_ASSERT(QThread::currentThread() == qApp->thread()); static SettingsManager s_globalInstance; return &s_globalInstance; } CompilerProvider* SettingsManager::provider() { return &m_provider; } const CompilerProvider* SettingsManager::provider() const { return &m_provider; } void SettingsManager::writePaths( KConfig* cfg, const QList< ConfigEntry >& paths ) { Q_ASSERT(QThread::currentThread() == qApp->thread()); KConfigGroup grp = cfg->group( ConfigConstants::configKey ); if ( !grp.isValid() ) return; grp.deleteGroup(); doWriteSettings( grp, paths ); } QList SettingsManager::readPaths( KConfig* cfg ) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); auto converted = convertedPaths( cfg ); if ( !converted.isEmpty() ) { const_cast(this)->writePaths( cfg, converted ); return converted; } KConfigGroup grp = cfg->group( ConfigConstants::configKey ); if ( !grp.isValid() ) { return {}; } return doReadSettings( grp ); } bool SettingsManager::needToReparseCurrentProject( KConfig* cfg ) const { auto grp = cfg->group( ConfigConstants::definesAndIncludesGroup ); return grp.readEntry( "reparse", true ); } void SettingsManager::writeUserDefinedCompilers(const QVector< CompilerPointer >& compilers) { QVector< CompilerPointer > editableCompilers; for (const auto& compiler : compilers) { if (!compiler->editable()) { continue; } editableCompilers.append(compiler); } KConfigGroup config = KSharedConfig::openConfig()->group(ConfigConstants::compilersGroup); config.deleteGroup(); config.writeEntry("number", editableCompilers.count()); int i = 0; for (const auto& compiler : editableCompilers) { KConfigGroup grp = config.group(QString::number(i)); ++i; grp.writeEntry(ConfigConstants::compilerNameKey, compiler->name()); grp.writeEntry(ConfigConstants::compilerPathKey, compiler->path()); grp.writeEntry(ConfigConstants::compilerTypeKey, compiler->factoryName()); } config.sync(); } QVector< CompilerPointer > SettingsManager::userDefinedCompilers() const { QVector< CompilerPointer > compilers; KConfigGroup config = KSharedConfig::openConfig()->group(ConfigConstants::compilersGroup); int count = config.readEntry("number", 0); for (int i = 0; i < count; i++) { KConfigGroup grp = config.group(QString::number(i)); auto name = grp.readEntry(ConfigConstants::compilerNameKey, QString()); auto path = grp.readEntry(ConfigConstants::compilerPathKey, QString()); auto type = grp.readEntry(ConfigConstants::compilerTypeKey, QString()); auto cf = m_provider.compilerFactories(); for (auto f : cf) { if (f->name() == type) { auto compiler = f->createCompiler(name, path); compilers.append(compiler); } } } return compilers; } -QString SettingsManager::defaultParserArguments() const +ParserArguments SettingsManager::defaultParserArguments() const { return defaultArguments(); } ConfigEntry::ConfigEntry(const QString& path) : path(path) , compiler(SettingsManager::globalInstance()->provider()->checkCompilerExists({})) , parserArguments(defaultArguments()) {} + +namespace Utils { +LanguageType languageType(const KDevelop::Path& path, bool treatAmbiguousAsCPP) +{ + QMimeDatabase db; + const auto mimeType = db.mimeTypeForFile(path.path()).name(); + if (mimeType == QStringLiteral("text/x-csrc") || + mimeType == QStringLiteral("text/x-chdr") ) { + if (treatAmbiguousAsCPP) { + if (path.lastPathSegment().endsWith(QLatin1String(".h"), Qt::CaseInsensitive)) { + return Cpp; + } + } + + return C; + } + + if (mimeType == QStringLiteral("text/x-c++src") || + mimeType == QStringLiteral("text/x-c++hdr") ) { + return Cpp; + } + + if (mimeType == QStringLiteral("text/x-objcsrc")) { + return ObjC; + } + + return Other; +} +} diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h index a9bfa31cbb..0ee5177868 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h +++ b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h @@ -1,76 +1,107 @@ /* * 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. * */ #ifndef SETTINGSMANAGER_H #define SETTINGSMANAGER_H #include "../idefinesandincludesmanager.h" #include "compilerprovider.h" #include "icompiler.h" class KConfig; namespace KDevelop { class IProject; class ProjectBaseItem; } +struct ParserArguments +{ + ParserArguments(const QString& cArguments, const QString& cppArguments, bool parseAmbiguousAsCPP) + : cArguments(cArguments) + , cppArguments(cppArguments) + , parseAmbiguousAsCPP(parseAmbiguousAsCPP) + {} + + ParserArguments() = default; + + QString cArguments; + QString cppArguments; + bool parseAmbiguousAsCPP = true; +}; + +Q_DECLARE_METATYPE(ParserArguments); + struct ConfigEntry { QString path; QStringList includes; KDevelop::Defines defines; CompilerPointer compiler; - QString parserArguments; + ParserArguments parserArguments; ConfigEntry( const QString& path = QString() ); // FIXME: get rid of this but stay backwards compatible void setDefines(const QHash& defines); }; +namespace Utils +{ +enum LanguageType +{ + C, + Cpp, + ObjC, + + Other = 100 +}; + +LanguageType languageType(const KDevelop::Path& path, bool treatAmbiguousAsCPP = true); +} + class SettingsManager { public: ~SettingsManager(); QList readPaths(KConfig* cfg) const; void writePaths(KConfig* cfg, const QList& paths); QVector userDefinedCompilers() const; void writeUserDefinedCompilers(const QVector& compilers); bool needToReparseCurrentProject( KConfig* cfg ) const; - QString defaultParserArguments() const; + ParserArguments defaultParserArguments() const; CompilerProvider* provider(); const CompilerProvider* provider() const; static SettingsManager* globalInstance(); private: SettingsManager(); CompilerProvider m_provider; }; #endif // SETTINGSMANAGER_H diff --git a/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp b/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp index 06c4db4999..ebceb4dc96 100644 --- a/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp +++ b/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp @@ -1,312 +1,321 @@ /* * 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 #include using namespace KDevelop; namespace { ///@return: The ConfigEntry, with includes/defines from @p paths for all parent folders of @p item. static ConfigEntry findConfigForItem(QList 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 != ".") { 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 (ret.parserArguments.isEmpty() || targetDirectory.segments().size() > closestPath.segments().size()) { + if (targetDirectory.segments().size() > closestPath.segments().size()) { ret.parserArguments = entry.parserArguments; closestPath = targetDirectory; } } } ret.includes.removeDuplicates(); - Q_ASSERT(!ret.parserArguments.isEmpty()); + 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()); } } } K_PLUGIN_FACTORY_WITH_JSON(DefinesAndIncludesManagerFactory, "kdevdefinesandincludesmanager.json", registerPlugin(); ) DefinesAndIncludesManager::DefinesAndIncludesManager( QObject* parent, const QVariantList& ) : IPlugin("kdevdefinesandincludesmanager", parent ) , m_settings(SettingsManager::globalInstance()) , m_noProjectIPM(new NoProjectIncludePathsManager()) { KDEV_USE_EXTENSION_INTERFACE(IDefinesAndIncludesManager); registerProvider(m_settings->provider()); } 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) { includes += provider->includes(item); } } includes += m_noProjectIPM->includesAndDefines(item->path().path()).first; return includes; } 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; } 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; } 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 { - if(!item){ - return m_settings->defaultParserArguments(); - } + Q_ASSERT(item); Q_ASSERT(QThread::currentThread() == qApp->thread()); auto cfg = item->project()->projectConfiguration().data(); - return findConfigForItem(m_settings->readPaths(cfg), item).parserArguments; + const auto arguments = findConfigForItem(m_settings->readPaths(cfg), item).parserArguments; + auto languageType = Utils::languageType(item->path(), arguments.parseAmbiguousAsCPP); + + return languageType == Utils::C ? arguments.cArguments : arguments.cppArguments; +} + +QString DefinesAndIncludesManager::parserArguments(const QString& path) const +{ + const auto args = m_settings->defaultParserArguments(); + auto languageType = Utils::languageType(Path(path)); + return languageType == Utils::C ? args.cArguments : args.cppArguments; } 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/languages/plugins/custom-definesandincludes/definesandincludesmanager.h b/languages/plugins/custom-definesandincludes/definesandincludesmanager.h index 3e661917e7..6d0d210819 100644 --- a/languages/plugins/custom-definesandincludes/definesandincludesmanager.h +++ b/languages/plugins/custom-definesandincludes/definesandincludesmanager.h @@ -1,82 +1,83 @@ /* * 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. * */ #ifndef CUSTOMDEFINESANDINCLUDESMANAGER_H #define CUSTOMDEFINESANDINCLUDESMANAGER_H #include #include #include #include #include "idefinesandincludesmanager.h" #include "compilerprovider/settingsmanager.h" class CompilerProvider; class NoProjectIncludePathsManager; /// @brief: Class for retrieving custom defines and includes. class DefinesAndIncludesManager : public KDevelop::IPlugin, public KDevelop::IDefinesAndIncludesManager { Q_OBJECT Q_INTERFACES( KDevelop::IDefinesAndIncludesManager ) public: explicit DefinesAndIncludesManager( QObject* parent, const QVariantList& args = QVariantList() ); ~DefinesAndIncludesManager() override; ///@return list of all custom defines for @p item KDevelop::Defines defines( KDevelop::ProjectBaseItem* item, Type type ) const override; ///@return list of all custom includes for @p item KDevelop::Path::List includes( KDevelop::ProjectBaseItem* item, Type type ) const override; KDevelop::Defines defines( const QString& path ) const override; KDevelop::Path::List includes( const QString& path ) const override; void registerProvider( Provider* provider ) override; bool unregisterProvider( Provider* provider ) override; KDevelop::Path::List includesInBackground( const QString& path ) const override; KDevelop::Defines definesInBackground(const QString& path) const override; void registerBackgroundProvider(BackgroundProvider* provider) override; bool unregisterBackgroundProvider(BackgroundProvider* provider) override; QString parserArguments(KDevelop::ProjectBaseItem* item) const override; + QString parserArguments(const QString& path) const override; void openConfigurationDialog( const QString& pathToFile ) override; int perProjectConfigPages() const override; KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; KDevelop::ConfigPage* configPage(int number, QWidget *parent) override; int configPages() const override; private: QVector m_providers; QVector m_backgroundProviders; SettingsManager* m_settings; QScopedPointer m_noProjectIPM; }; #endif // CUSTOMDEFINESANDINCLUDESMANAGER_H diff --git a/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h b/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h index 2f22288ac6..5da71f2e2a 100644 --- a/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h +++ b/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h @@ -1,194 +1,194 @@ /* * 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. */ #ifndef IDEFINESANDINCLUDES_H #define IDEFINESANDINCLUDES_H #include #include #include #include #include #include #include #include namespace KDevelop { class ProjectBaseItem; class IProject; using Defines = QHash; /** An interface that provides language plugins with include directories/files and defines. * Call IDefinesAndIncludesManager::manager() to get the instance of the plugin. **/ class IDefinesAndIncludesManager { public: /// The type of includes/defines enum Type { CompilerSpecific = 1, ///< Those that compiler provides ProjectSpecific = 2, ///< Those that project manager provides UserDefined = 4, ///< Those that user defines All = CompilerSpecific | ProjectSpecific | UserDefined }; /** * Class that actually does all the work of calculating i/d. * * Implement one in e.g. project manager and register it with @see registerProvider * To unregister it use @see unregisterProvider * * @sa BackgroundProvider **/ class Provider { public: virtual ~Provider() = default; virtual Defines defines( ProjectBaseItem* item ) const = 0; virtual Path::List includes( ProjectBaseItem* item ) const = 0; /// @return the type of i/d this provider provides virtual Type type() const = 0; }; /** * Use this as base class for provider, if computing includes/defines can takes a long time (e.g. parsing Makefile by running make). * * This provider will be queried for includes/defines in a background thread. * * @sa Provider **/ class BackgroundProvider { public: virtual ~BackgroundProvider() = default; virtual Path::List includesInBackground( const QString& path ) const = 0; virtual Defines definesInBackground( const QString& path ) const = 0; /// @return the type of i/d this provider provides virtual Type type() const = 0; }; ///@param item project item ///@return list of defines for @p item ///NOTE: call it from the foreground thread only. virtual Defines defines( ProjectBaseItem* item, Type type = All ) const = 0; ///@param item project item ///@return list of include directories/files for @p item ///NOTE: call it from the foreground thread only. virtual Path::List includes( ProjectBaseItem* item, Type type = All ) const = 0; ///@param path path to an out-of-project file. ///@return list of defines for @p path ///NOTE: call it from the foreground thread only. virtual Defines defines( const QString& path ) const = 0; ///@param path path to an out-of-project file. ///@return list of include directories/files for @p path ///NOTE: call it from the foreground thread only. virtual Path::List includes( const QString& path ) const = 0; /** * Computes include directories in background thread. * * This is especially useful for CustomMake projects. * * Call it from background thread if possible. **/ virtual Path::List includesInBackground( const QString& path ) const = 0; /** * Computes defined macros in background thread. * * Call it from background thread if possible. **/ virtual Defines definesInBackground( const QString& path ) const = 0; /** * @param item project item. Use nullptr to get default arguments * @return The parser command-line arguments used to parse the @p item - * - * NOTE: Call it from the main thread only, the default arguments can also be retrieved from a background thread */ virtual QString parserArguments(ProjectBaseItem* item) const = 0; + virtual QString parserArguments(const QString& path) const = 0; + ///@return the instance of the plugin. inline static IDefinesAndIncludesManager* manager(); virtual ~IDefinesAndIncludesManager() = default; /** * Register the @p provider */ virtual void registerProvider(Provider* provider) = 0; /** * Unregister the provider * * @return true on success, false otherwise (e.g. if not registered) */ virtual bool unregisterProvider(Provider* provider) = 0; /** * Use this to register the background provider * * This provider will be queried for includes/defines in a background thread. */ virtual void registerBackgroundProvider(BackgroundProvider* provider) = 0; /** * Unregister the background provider. * * @sa registerBackgroundProvider */ virtual bool unregisterBackgroundProvider(BackgroundProvider* provider) = 0; /// Opens a configuration dialog for @p pathToFile to modify include directories/files and defined macros. virtual void openConfigurationDialog(const QString& pathToFile) = 0; }; inline IDefinesAndIncludesManager* IDefinesAndIncludesManager::manager() { static QPointer manager; if (!manager) { manager = ICore::self()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IDefinesAndIncludesManager") ); } Q_ASSERT(manager); auto extension = manager->extension(); return extension; } } Q_DECLARE_INTERFACE( KDevelop::IDefinesAndIncludesManager, "org.kdevelop.IDefinesAndIncludesManager" ) #endif diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.cpp b/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.cpp index e673269c65..00d05c6222 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.cpp +++ b/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.cpp @@ -1,130 +1,163 @@ /* * This file is part of KDevelop * * Copyright 2015 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "parserwidget.h" #include "ui_parserwidget.h" #include "compilerprovider/settingsmanager.h" #include namespace { QString languageStandard(const QString& arguments) { int idx = arguments.indexOf("-std="); if(idx == -1){ return QStringLiteral("c++11"); } idx += 5; int end = arguments.indexOf(' ', idx) != -1 ? arguments.indexOf(' ', idx) : arguments.size(); return arguments.mid(idx, end - idx); } bool isCustomParserArguments(const QString& arguments, const QStringList& standards) { const auto defaultArguments = SettingsManager::globalInstance()->defaultParserArguments(); auto standard = languageStandard(arguments); auto tmpArgs(arguments); tmpArgs.replace(standard, "c++11"); - if (tmpArgs == defaultArguments && standards.contains(standard)) { + if (tmpArgs == defaultArguments.cppArguments && standards.contains(standard)) { return false; } return true; } const int customProfileIdx = 0; } ParserWidget::ParserWidget(QWidget* parent) : QWidget(parent) , m_ui(new Ui::ParserWidget()) { m_ui->setupUi(this); - connect(m_ui->parserOptions, &QLineEdit::textEdited, this, &ParserWidget::textEdited); - connect(m_ui->languageStandards, static_castparserOptionsC, &QLineEdit::textEdited, this, &ParserWidget::textEdited); + connect(m_ui->parserOptionsCpp, &QLineEdit::textEdited, this, &ParserWidget::textEdited); + connect(m_ui->parseHeadersInPlainC, &QCheckBox::stateChanged, this, &ParserWidget::textEdited); + connect(m_ui->languageStandardsC, static_cast(&QComboBox::activated), this, -&ParserWidget::languageStandardChanged); +&ParserWidget::languageStandardChangedC); + connect(m_ui->languageStandardsCpp, static_cast(&QComboBox::activated), this, +&ParserWidget::languageStandardChangedCpp); updateEnablements(); } ParserWidget::~ParserWidget() = default; void ParserWidget::textEdited() { emit changed(); } -void ParserWidget::languageStandardChanged(const QString& standard) +void ParserWidget::languageStandardChangedC(const QString& standard) { - if (m_ui->languageStandards->currentIndex() == customProfileIdx) { - m_ui->parserOptions->setText(SettingsManager::globalInstance()->defaultParserArguments()); + if (m_ui->languageStandardsC->currentIndex() == customProfileIdx) { + m_ui->parserOptionsC->setText(SettingsManager::globalInstance()->defaultParserArguments().cArguments); } else { - auto text = SettingsManager::globalInstance()->defaultParserArguments(); + auto text = SettingsManager::globalInstance()->defaultParserArguments().cArguments; auto currentStandard = languageStandard(text); - m_ui->parserOptions->setText(text.replace(currentStandard, standard)); + m_ui->parserOptionsC->setText(text.replace(currentStandard, standard)); } textEdited(); updateEnablements(); } -void ParserWidget::setParserArguments(const QString& arguments) +void ParserWidget::languageStandardChangedCpp(const QString& standard) { - QStringList standards; - for (int i = 1; i < m_ui->languageStandards->count(); i++) { - standards << m_ui->languageStandards->itemText(i); - } - - if (isCustomParserArguments(arguments, standards)) { - m_ui->languageStandards->setCurrentIndex(customProfileIdx); + if (m_ui->languageStandardsCpp->currentIndex() == customProfileIdx) { + m_ui->parserOptionsCpp->setText(SettingsManager::globalInstance()->defaultParserArguments().cppArguments); } else { - m_ui->languageStandards->setCurrentText(languageStandard(arguments)); + auto text = SettingsManager::globalInstance()->defaultParserArguments().cppArguments; + auto currentStandard = languageStandard(text); + m_ui->parserOptionsCpp->setText(text.replace(currentStandard, standard)); } - m_ui->parserOptions->setText(arguments); + textEdited(); updateEnablements(); } -QString ParserWidget::parserArguments() const +void ParserWidget::setParserArguments(const ParserArguments& arguments) { - return m_ui->parserOptions->text(); + auto setArguments = [this](QComboBox* languageStandards, QLineEdit* parserOptions, const QString& arguments) { + QStringList standards; + for (int i = 1; i < languageStandards->count(); i++) { + standards << languageStandards->itemText(i); + } + + if (isCustomParserArguments(arguments, standards)) { + languageStandards->setCurrentIndex(customProfileIdx); + } else { + languageStandards->setCurrentText(languageStandard(arguments)); + } + + parserOptions->setText(arguments); + }; + + setArguments(m_ui->languageStandardsCpp, m_ui->parserOptionsCpp, arguments.cppArguments); + setArguments(m_ui->languageStandardsC, m_ui->parserOptionsC, arguments.cArguments); + + m_ui->parseHeadersInPlainC->setChecked(!arguments.parseAmbiguousAsCPP); + + updateEnablements(); +} + +ParserArguments ParserWidget::parserArguments() const +{ + return {m_ui->parserOptionsC->text(), m_ui->parserOptionsCpp->text(), !m_ui->parseHeadersInPlainC->isChecked()}; } void ParserWidget::updateEnablements() { - if (m_ui->languageStandards->currentIndex() == customProfileIdx) { - m_ui->parserOptions->setEnabled(true); + if (m_ui->languageStandardsCpp->currentIndex() == customProfileIdx) { + m_ui->parserOptionsCpp->setEnabled(true); + } else { + m_ui->parserOptionsCpp->setEnabled(false); + } + + if (m_ui->languageStandardsC->currentIndex() == customProfileIdx) { + m_ui->parserOptionsC->setEnabled(true); } else { - m_ui->parserOptions->setEnabled(false); + m_ui->parserOptionsC->setEnabled(false); } } diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.h b/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.h index ef5d96da72..9688dd3e02 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.h +++ b/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.h @@ -1,62 +1,65 @@ /* * This file is part of KDevelop * * Copyright 2015 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #ifndef PARSERWIDGET_H #define PARSERWIDGET_H #include #include namespace Ui { class ParserWidget; } namespace KDevelop { class IProject; } +struct ParserArguments; + class ParserWidget : public QWidget { Q_OBJECT public: ParserWidget(QWidget* parent); ~ParserWidget() override; - void setParserArguments(const QString& arguments); - QString parserArguments() const; + void setParserArguments(const ParserArguments& arguments); + ParserArguments parserArguments() const; signals: void changed(); private slots: void textEdited(); - void languageStandardChanged(const QString& standard); + void languageStandardChangedC(const QString& standard); + void languageStandardChangedCpp(const QString& standard); void updateEnablements(); private: QScopedPointer m_ui; }; #endif // PARSERWIDGET_H diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.ui b/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.ui index e1aa1992e7..959d13225d 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.ui +++ b/languages/plugins/custom-definesandincludes/kcm_widget/parserwidget.ui @@ -1,132 +1,244 @@ ParserWidget 0 0 579 407 Form - + + + + + <html><head/><body><p>Check this if you want <code>*.h<code/> headers to be parsed in plain C mode. By default they are parsed as C++ headers.</p></body></html> + + + Parse *.h headers in plain C + + + - Profi&le: + C++ Profi&le: - languageStandards + languageStandardsCpp - + 0 0 100 0 <html><head/><body><p>Choose language profile. </p><p>Use &quot;Custom&quot; profile to modify parser command-line arguments</p></body></html> Custom c99 gnu99 c11 gnu11 c++03 c++11 c++14 - Command-line ar&guments: + C++ co&mmand-line arguments: - parserOptions + parserOptionsCpp - + + + + 100 + 0 + + + + + + + + C Profi&le: + + + languageStandardsCpp + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + <html><head/><body><p>Choose language profile. </p><p>Use &quot;Custom&quot; profile to modify parser command-line arguments</p></body></html> + + + + Custom + + + + + c99 + + + + + gnu99 + + + + + c11 + + + + + gnu11 + + + + + c++03 + + + + + c++11 + + + + + c++14 + + + + + + + + C co&mmand-line arguments: + + + parserOptionsC + + + + + 100 0 + + + + Qt::Vertical + + + + 20 + 40 + + + + - + Qt::Vertical 20 406 diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp index 676748e4f9..99871ee29c 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp +++ b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.cpp @@ -1,238 +1,239 @@ /************************************************************************ * * * Copyright 2010 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 or version 3 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, see . * ************************************************************************/ #include "projectpathsmodel.h" #include #include #include #include using namespace KDevelop; ProjectPathsModel::ProjectPathsModel( QObject* parent ) : QAbstractListModel( parent ), project( 0 ) { } void ProjectPathsModel::setProject(IProject* w_project) { project = w_project; } QVariant ProjectPathsModel::data( const QModelIndex& index, int role ) const { if( !index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) { return QVariant(); } const ConfigEntry& pathConfig = projectPaths.at( index.row() ); switch( role ) { case IncludesDataRole: return pathConfig.includes; break; case DefinesDataRole: return QVariant::fromValue(pathConfig.defines); break; case Qt::EditRole: return sanitizePath( pathConfig.path, true, false ); break; case Qt::DisplayRole: { const QString& path = pathConfig.path; return (path == ".") ? "(project root)" : path; break; } case FullUrlDataRole: return QVariant::fromValue(QUrl::fromUserInput( sanitizePath( pathConfig.path, true, false ) )); break; case CompilerDataRole: return QVariant::fromValue(pathConfig.compiler); break; case ParserArgumentsRole: - return pathConfig.parserArguments; + return QVariant::fromValue(pathConfig.parserArguments); break; default: break; } return QVariant(); } int ProjectPathsModel::rowCount( const QModelIndex& parent ) const { if( parent.isValid() ) { return 0; } return projectPaths.count(); } bool ProjectPathsModel::setData( const QModelIndex& index, const QVariant& value, int role ) { if( !index.isValid() || index.row() < 0 || index.row() >= rowCount() || index.column() != 0 ) { return false; } // Do not allow to change path of the first entry; instead, add a new one in that case if( index.row() == 0 && ( role == Qt::EditRole || role == Qt::DisplayRole || role == FullUrlDataRole ) ) { QString addedPath = sanitizePath( value.toString(), false ); // Do not allow duplicates foreach( const ConfigEntry& existingConfig, projectPaths ) { if( addedPath == existingConfig.path ) { return false; } } projectPaths.insert( 1, sanitizePath( value.toString(), false ) ); emit dataChanged( this->index( 1, 0 ), this->index( projectPaths.count() - 1, 0 ) ); return true; } ConfigEntry& pathConfig = projectPaths[ index.row() ]; switch( role ) { case IncludesDataRole: pathConfig.includes = value.toStringList(); break; case DefinesDataRole: pathConfig.defines = value.value(); break; case Qt::EditRole: pathConfig.path = sanitizePath( value.toString(), false ); break; case Qt::DisplayRole: pathConfig.path = sanitizePath( value.toString(), true ); break; case FullUrlDataRole: pathConfig.path = sanitizeUrl( value.value() ); break; case CompilerDataRole: pathConfig.compiler = value.value(); break; case ParserArgumentsRole: - pathConfig.parserArguments = value.toString(); + pathConfig.parserArguments = value.value(); break; default: return false; break; } emit dataChanged( index, index ); return true; } Qt::ItemFlags ProjectPathsModel::flags( const QModelIndex& index ) const { if( !index.isValid() ) { return 0; } if( index.row() == 0 ) { return Qt::ItemFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled ); } return Qt::ItemFlags( Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled ); } QList< ConfigEntry > ProjectPathsModel::paths() const { return projectPaths; } void ProjectPathsModel::setPaths(const QList< ConfigEntry >& paths ) { beginResetModel(); projectPaths.clear(); foreach( const ConfigEntry& existingPathConfig, paths ) { // Sanitize the path of loaded config ConfigEntry config = existingPathConfig; bool rootPath = config.path == "." ? true : false; config.path = sanitizePath(rootPath ? QString() : config.path ); addPathInternal(config, rootPath); } addPathInternal( sanitizePath( QString() ), true ); // add an empty "root" config entry if one does not exist endResetModel(); } bool ProjectPathsModel::removeRows( int row, int count, const QModelIndex& parent ) { if( row >= 0 && count > 0 && row < rowCount() ) { beginRemoveRows( parent, row, row + count - 1 ); for( int i = 0; i < count; ++i ) { if( projectPaths.at(row).path == "." ) { continue; // we won't remove the root item } projectPaths.removeAt(row); } endRemoveRows(); return true; } return false; } void ProjectPathsModel::addPath( const QUrl &url ) { if( !project->path().isParentOf(KDevelop::Path(url)) ) { return; } beginInsertRows( QModelIndex(), rowCount(), rowCount() ); addPathInternal( sanitizeUrl(url), false ); endInsertRows(); } void ProjectPathsModel::addPathInternal( const ConfigEntry& config, bool prepend ) { - Q_ASSERT(!config.parserArguments.isEmpty()); + Q_ASSERT(!config.parserArguments.cppArguments.isEmpty()); + Q_ASSERT(!config.parserArguments.cArguments.isEmpty()); // Do not allow duplicates foreach( const ConfigEntry& existingConfig, projectPaths ) { if( config.path == existingConfig.path ) { return; } } if( prepend ) { projectPaths.prepend( config ); } else { projectPaths.append( config ); } } QString ProjectPathsModel::sanitizeUrl( QUrl url, bool needRelative ) const { Q_ASSERT( project ); if (needRelative) { const auto relativePath = project->path().relativePath(KDevelop::Path(url)); return relativePath.isEmpty() ? QStringLiteral(".") : relativePath; } return url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments).toString(QUrl::PreferLocalFile); } QString ProjectPathsModel::sanitizePath( const QString& path, bool expectRelative, bool needRelative ) const { Q_ASSERT( project ); Q_ASSERT( expectRelative || project->inProject(IndexedString(path)) ); QUrl url; if( expectRelative ) { url = KDevelop::Path(project->path(), path).toUrl(); } else { url = QUrl::fromUserInput(path); } return sanitizeUrl( url, needRelative ); } diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.h b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.h index c98c39463e..689c9d2bc7 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.h +++ b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathsmodel.h @@ -1,64 +1,64 @@ /************************************************************************ * * * Copyright 2010 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 or version 3 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, see . * ************************************************************************/ #ifndef PROJECTPATHSMODEL_H #define PROJECTPATHSMODEL_H #include #include #include #include "../compilerprovider/settingsmanager.h" namespace KDevelop { class IProject; } class ProjectPathsModel : public QAbstractListModel { Q_OBJECT public: enum SpecialRoles { IncludesDataRole = Qt::UserRole + 1, DefinesDataRole = Qt::UserRole + 2, FullUrlDataRole = Qt::UserRole + 3, CompilerDataRole = Qt::UserRole + 4, - ParserArgumentsRole = CompilerDataRole + 1 + ParserArgumentsRole = Qt::UserRole + 5 }; ProjectPathsModel( QObject* parent = 0 ); void setProject( KDevelop::IProject* w_project ); void setPaths( const QList< ConfigEntry >& paths ); void addPath( const QUrl &url ); QList paths() const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; bool removeRows( int row, int count, const QModelIndex& parent = QModelIndex() ) override; private: QList projectPaths; KDevelop::IProject* project; void addPathInternal( const ConfigEntry& config, bool prepend ); QString sanitizePath( const QString& path, bool expectRelative = true, bool needRelative = true ) const; QString sanitizeUrl( QUrl url, bool needRelative = true ) const; }; #endif // PROJECTPATHSMODEL_H diff --git a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp index 5cf8a2ec7c..ed8234fdb1 100644 --- a/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp +++ b/languages/plugins/custom-definesandincludes/kcm_widget/projectpathswidget.cpp @@ -1,306 +1,307 @@ /************************************************************************ * * * Copyright 2010 Andreas Pakulat * * 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, see . * ************************************************************************/ #include "projectpathswidget.h" #include #include #include #include #include #include #include #include #include "../compilerprovider/compilerprovider.h" +#include "../compilerprovider/settingsmanager.h" #include "ui_projectpathswidget.h" #include "ui_batchedit.h" #include "projectpathsmodel.h" #include "debugarea.h" using namespace KDevelop; namespace { enum PageType { IncludesPage, DefinesPage, ParserArgumentsPage }; } ProjectPathsWidget::ProjectPathsWidget( QWidget* parent ) : QWidget(parent), ui(new Ui::ProjectPathsWidget), pathsModel(new ProjectPathsModel(this)) { ui->setupUi( this ); ui->addPath->setIcon(QIcon::fromTheme("list-add")); ui->removePath->setIcon(QIcon::fromTheme("list-remove")); // hack taken from kurlrequester, make the buttons a bit less in height so they better match the url-requester ui->addPath->setFixedHeight( ui->projectPaths->sizeHint().height() ); ui->removePath->setFixedHeight( ui->projectPaths->sizeHint().height() ); connect( ui->addPath, &QPushButton::clicked, this, &ProjectPathsWidget::addProjectPath ); connect( ui->removePath, &QPushButton::clicked, this, &ProjectPathsWidget::deleteProjectPath ); connect( ui->batchEdit, &QPushButton::clicked, this, &ProjectPathsWidget::batchEdit ); ui->projectPaths->setModel( pathsModel ); connect( ui->projectPaths, static_cast(&KComboBox::currentIndexChanged), this, &ProjectPathsWidget::projectPathSelected ); connect( pathsModel, &ProjectPathsModel::dataChanged, this, &ProjectPathsWidget::changed ); connect( pathsModel, &ProjectPathsModel::rowsInserted, this, &ProjectPathsWidget::changed ); connect( pathsModel, &ProjectPathsModel::rowsRemoved, this, &ProjectPathsWidget::changed ); connect( ui->compiler, static_cast(&QComboBox::activated), this, &ProjectPathsWidget::changed ); connect( ui->compiler, static_cast(&QComboBox::activated), this, &ProjectPathsWidget::changeCompilerForPath ); connect( ui->includesWidget, static_cast(&IncludesWidget::includesChanged), this, &ProjectPathsWidget::includesChanged ); connect( ui->definesWidget, static_cast(&DefinesWidget::definesChanged), this, &ProjectPathsWidget::definesChanged ); connect(ui->languageParameters, &QTabWidget::currentChanged, this, &ProjectPathsWidget::tabChanged); connect(ui->parserWidget, &ParserWidget::changed, this, &ProjectPathsWidget::parserArgumentsChanged); tabChanged(IncludesPage); } QList ProjectPathsWidget::paths() const { return pathsModel->paths(); } void ProjectPathsWidget::setPaths( const QList& paths ) { bool b = blockSignals( true ); clear(); pathsModel->setPaths( paths ); blockSignals( b ); ui->projectPaths->setCurrentIndex(0); // at least a project root item is present ui->languageParameters->setCurrentIndex(0); // Set compilers ui->compiler->clear(); auto settings = SettingsManager::globalInstance(); auto compilers = settings->provider()->compilers(); for (int i = 0 ; i < compilers.count(); ++i) { Q_ASSERT(compilers[i]); if (!compilers[i]) { continue; } ui->compiler->addItem(compilers[i]->name()); QVariant val; val.setValue(compilers[i]); ui->compiler->setItemData(i, val); } projectPathSelected(0); updateEnablements(); } void ProjectPathsWidget::definesChanged( const Defines& defines ) { definesAndIncludesDebug() << "defines changed"; updatePathsModel( QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole ); } void ProjectPathsWidget::includesChanged( const QStringList& includes ) { definesAndIncludesDebug() << "includes changed"; updatePathsModel( includes, ProjectPathsModel::IncludesDataRole ); } void ProjectPathsWidget::parserArgumentsChanged() { - updatePathsModel(ui->parserWidget->parserArguments(), ProjectPathsModel::ParserArgumentsRole); + updatePathsModel(QVariant::fromValue(ui->parserWidget->parserArguments()), ProjectPathsModel::ParserArgumentsRole); } void ProjectPathsWidget::updatePathsModel(const QVariant& newData, int role) { QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0, QModelIndex() ); if( idx.isValid() ) { bool b = pathsModel->setData( idx, newData, role ); if( b ) { emit changed(); } } } void ProjectPathsWidget::projectPathSelected( int index ) { if( index < 0 && pathsModel->rowCount() > 0 ) { index = 0; } Q_ASSERT(index >= 0); const QModelIndex midx = pathsModel->index( index, 0 ); ui->includesWidget->setIncludes( pathsModel->data( midx, ProjectPathsModel::IncludesDataRole ).toStringList() ); ui->definesWidget->setDefines( pathsModel->data( midx, ProjectPathsModel::DefinesDataRole ).value() ); Q_ASSERT(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value()); ui->compiler->setCurrentText(pathsModel->data(midx, ProjectPathsModel::CompilerDataRole).value()->name()); - ui->parserWidget->setParserArguments(pathsModel->data(midx, ProjectPathsModel::ParserArgumentsRole ).toString()); + ui->parserWidget->setParserArguments(pathsModel->data(midx, ProjectPathsModel::ParserArgumentsRole).value()); updateEnablements(); } void ProjectPathsWidget::clear() { bool sigDisabled = ui->projectPaths->blockSignals( true ); pathsModel->setPaths( QList() ); ui->includesWidget->clear(); ui->definesWidget->clear(); updateEnablements(); ui->projectPaths->blockSignals( sigDisabled ); } void ProjectPathsWidget::addProjectPath() { const QUrl directory = pathsModel->data(pathsModel->index(0, 0), ProjectPathsModel::FullUrlDataRole).value(); QFileDialog dlg(this, tr("Select Project Path"), directory.toLocalFile()); dlg.setFileMode(QFileDialog::Directory); dlg.setOption(QFileDialog::ShowDirsOnly); dlg.exec(); pathsModel->addPath(dlg.selectedUrls().value(0)); ui->projectPaths->setCurrentIndex(pathsModel->rowCount() - 1); updateEnablements(); } void ProjectPathsWidget::deleteProjectPath() { const QModelIndex idx = pathsModel->index( ui->projectPaths->currentIndex(), 0 ); if( KMessageBox::questionYesNo( this, i18n("Are you sure you want to remove the configuration for the path '%1'?", pathsModel->data( idx, Qt::DisplayRole ).toString() ), "Remove Path Configuration" ) == KMessageBox::Yes ) { pathsModel->removeRows( ui->projectPaths->currentIndex(), 1 ); } updateEnablements(); } void ProjectPathsWidget::setProject(KDevelop::IProject* w_project) { pathsModel->setProject( w_project ); ui->includesWidget->setProject( w_project ); } void ProjectPathsWidget::updateEnablements() { // Disable removal of the project root entry which is always first in the list ui->removePath->setEnabled( ui->projectPaths->currentIndex() > 0 ); } void ProjectPathsWidget::batchEdit() { Ui::BatchEdit be; QDialog dialog(this); be.setupUi(&dialog); const int index = qMax(ui->projectPaths->currentIndex(), 0); const QModelIndex midx = pathsModel->index(index, 0); if (!midx.isValid()) { return; } bool includesTab = ui->languageParameters->currentIndex() == 0; if (includesTab) { auto includes = pathsModel->data(midx, ProjectPathsModel::IncludesDataRole).toStringList(); be.textEdit->setPlainText(includes.join("\n")); dialog.setWindowTitle(i18n("Edit include directories/files")); } else { auto defines = pathsModel->data(midx, ProjectPathsModel::DefinesDataRole).value(); for (auto it = defines.constBegin(); it != defines.constEnd(); it++) { be.textEdit->append(it.key() + "=" + it.value()); } dialog.setWindowTitle(i18n("Edit defined macros")); } if (dialog.exec() != QDialog::Accepted) { return; } if (includesTab) { auto includes = be.textEdit->toPlainText().split('\n', QString::SkipEmptyParts); for (auto& s : includes) { s = s.trimmed(); } pathsModel->setData(midx, includes, ProjectPathsModel::IncludesDataRole); } else { auto list = be.textEdit->toPlainText().split('\n', QString::SkipEmptyParts); Defines defines; for (auto& d : list) { //This matches: a=b, a=, a QRegExp r("^([^=]+)(=(.*))?$"); if (!r.exactMatch(d)) { continue; } defines[r.cap(1).trimmed()] = r.cap(3).trimmed(); } pathsModel->setData(midx, QVariant::fromValue(defines), ProjectPathsModel::DefinesDataRole); } projectPathSelected(index); } void ProjectPathsWidget::setCurrentCompiler(const QString& name) { for (int i = 0 ; i < ui->compiler->count(); ++i) { if(ui->compiler->itemText(i) == name) { ui->compiler->setCurrentIndex(i); } } } CompilerPointer ProjectPathsWidget::currentCompiler() const { return ui->compiler->itemData(ui->compiler->currentIndex()).value(); } void ProjectPathsWidget::tabChanged(int idx) { if (idx == ParserArgumentsPage) { ui->batchEdit->setVisible(false); ui->compilerBox->setVisible(true); ui->configureLabel->setText(i18n("Configure C/C++ parser")); } else { ui->batchEdit->setVisible(true); ui->compilerBox->setVisible(false); ui->configureLabel->setText(i18n("Configure which macros and include directories/files will be added to the parser during project parsing:")); } } void ProjectPathsWidget::changeCompilerForPath() { for (int idx = 0; idx < pathsModel->rowCount(); idx++) { const QModelIndex midx = pathsModel->index(idx, 0); if (pathsModel->data(midx, Qt::DisplayRole) == ui->projectPaths->currentText()) { pathsModel->setData(midx, QVariant::fromValue(currentCompiler()), ProjectPathsModel::CompilerDataRole); break; } } } diff --git a/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp b/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp index 83d649f09c..bc2942102c 100644 --- a/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp +++ b/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp @@ -1,155 +1,155 @@ /************************************************************************ * * * Copyright 2010 Andreas Pakulat * * 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 or version 3 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, see . * ************************************************************************/ #include "test_definesandincludes.h" #include "projectsgenerator.h" #include #include #include #include #include #include #include #include #include "idefinesandincludesmanager.h" using namespace KDevelop; static IProject* s_currentProject = nullptr; void TestDefinesAndIncludes::cleanupTestCase() { TestCore::shutdown(); } void TestDefinesAndIncludes::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void TestDefinesAndIncludes::cleanup() { ICore::self()->projectController()->closeProject( s_currentProject ); } void TestDefinesAndIncludes::loadSimpleProject() { s_currentProject = ProjectsGenerator::GenerateSimpleProject(); QVERIFY( s_currentProject ); auto manager = IDefinesAndIncludesManager::manager(); QVERIFY( manager ); const auto actualIncludes = manager->includes( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ); const auto actualDefines = manager->defines( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ); qDebug() << actualDefines << actualIncludes; QCOMPARE( actualIncludes, Path::List() << Path( "/usr/include/mydir") ); Defines defines; defines.insert( "_DEBUG", "" ); defines.insert( "VARIABLE", "VALUE" ); QCOMPARE( actualDefines, defines ); QVERIFY(!manager->parserArguments(s_currentProject->projectItem()).isEmpty()); - QVERIFY(!manager->parserArguments(nullptr).isEmpty()); + QVERIFY(!manager->parserArguments(QStringLiteral("/some/path/to/file.cpp")).isEmpty()); } void TestDefinesAndIncludes::loadMultiPathProject() { s_currentProject = ProjectsGenerator::GenerateMultiPathProject(); QVERIFY( s_currentProject ); auto manager = IDefinesAndIncludesManager::manager(); QVERIFY( manager ); Path::List includes = Path::List() << Path("/usr/include/otherdir"); QHash defines; defines.insert("SOURCE", "CONTENT"); defines.insert("_COPY", ""); QCOMPARE( manager->includes( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ), includes ); QCOMPARE( manager->defines( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ), defines ); QVERIFY(!manager->parserArguments(s_currentProject->projectItem()).isEmpty()); ProjectBaseItem* mainfile = 0; for (const auto& file: s_currentProject->fileSet() ) { for (auto i: s_currentProject->filesForPath(file)) { if( i->text() == "main.cpp" ) { mainfile = i; break; } } } QVERIFY(mainfile); includes.prepend(Path("/usr/local/include/mydir")); defines.insert("BUILD", "debug"); qDebug() << includes << "VS" << manager->includes( mainfile, IDefinesAndIncludesManager::UserDefined ); qDebug() << mainfile << mainfile->path(); QCOMPARE(manager->includes( mainfile, IDefinesAndIncludesManager::UserDefined ), includes); QCOMPARE(defines, manager->defines( mainfile, IDefinesAndIncludesManager::UserDefined )); QVERIFY(!manager->parserArguments(mainfile).isEmpty()); } void TestDefinesAndIncludes::testNoProjectIncludeDirectories() { s_currentProject = ProjectsGenerator::GenerateSimpleProjectWithOutOfProjectFiles(); QVERIFY(s_currentProject); auto manager = KDevelop::IDefinesAndIncludesManager::manager(); QVERIFY(manager); auto projectIncludes = manager->includes(s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined); Path includePath1(s_currentProject->path().path() + QDir::separator() + "include1.h"); Path includePath2(s_currentProject->path().path() + QDir::separator() + "include2.h"); QVERIFY(!projectIncludes.contains(includePath1)); QVERIFY(!projectIncludes.contains(includePath2)); auto noProjectIncludes = manager->includes(s_currentProject->path().path() + "/src/main.cpp"); QVERIFY(noProjectIncludes.contains(includePath1)); QVERIFY(noProjectIncludes.contains(includePath2)); QVERIFY(!manager->parserArguments(s_currentProject->projectItem()).isEmpty()); } void TestDefinesAndIncludes::testEmptyProject() { s_currentProject = ProjectsGenerator::GenerateEmptyProject(); QVERIFY(s_currentProject); auto manager = KDevelop::IDefinesAndIncludesManager::manager(); QVERIFY(manager); auto projectIncludes = manager->includes(s_currentProject->projectItem()); auto projectDefines = manager->defines(s_currentProject->projectItem()); auto parserArguments = manager->parserArguments(s_currentProject->projectItem()); QVERIFY(!projectIncludes.isEmpty()); QVERIFY(!projectDefines.isEmpty()); QVERIFY(!parserArguments.isEmpty()); } QTEST_MAIN(TestDefinesAndIncludes)