diff --git a/plugins/clang/duchain/parsesession.cpp b/plugins/clang/duchain/parsesession.cpp index 42105c4b48..b75d47460c 100644 --- a/plugins/clang/duchain/parsesession.cpp +++ b/plugins/clang/duchain/parsesession.cpp @@ -1,620 +1,622 @@ /* 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 "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 "headerguardassistant.h" #include #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()); for (const QString& arg : extraArgs) { result << arg.toLatin1(); } clangDebug() << "Passing extra arguments to clang:" << result; return result; } void sanitizeArguments(QVector& arguments) { // We remove the -Werror flag, and replace -Werror=foo by -Wfoo. // Warning as error may cause problem to the clang parser. const auto asError = QByteArrayLiteral("-Werror="); const auto documentation = QByteArrayLiteral("-Wdocumentation"); for (auto& argument : arguments) { if (argument == "-Werror") { argument.clear(); } else if (argument.startsWith(asError)) { // replace -Werror=foo by -Wfoo argument.remove(2, asError.length() - 2); } #if CINDEX_VERSION_MINOR < 100 // FIXME https://bugs.llvm.org/show_bug.cgi?id=35333 if (argument == documentation) { argument.clear(); } #endif } } QVector argsForSession(const QString& path, ParseSessionData::Options options, const ParserSettings& parserSettings) { QMimeDatabase db; if (db.mimeTypeForFile(path).name() == QLatin1String("text/x-objcsrc")) { return {QByteArrayLiteral("-xobjective-c++")}; } // TODO: No proper mime type detection possible yet // cf. https://bugs.freedesktop.org/show_bug.cgi?id=26913 if (path.endsWith(QLatin1String(".cl"), Qt::CaseInsensitive)) { return {QByteArrayLiteral("-xcl")}; } // TODO: No proper mime type detection possible yet // cf. https://bugs.freedesktop.org/show_bug.cgi?id=23700 if (path.endsWith(QLatin1String(".cu"), Qt::CaseInsensitive) || path.endsWith(QLatin1String(".cuh"), Qt::CaseInsensitive)) { auto result = parserSettings.toClangAPI(); result.append(QByteArrayLiteral("-xcuda")); return result; } if (parserSettings.parserOptions.isEmpty()) { // The parserOptions can be empty for some unit tests that use ParseSession directly auto defaultArguments = ClangSettingsManager::self()->parserSettings(path).toClangAPI(); defaultArguments.append(QByteArrayLiteral("-nostdinc")); defaultArguments.append(QByteArrayLiteral("-nostdinc++")); defaultArguments.append(QByteArrayLiteral("-xc++")); sanitizeArguments(defaultArguments); return defaultArguments; } auto result = parserSettings.toClangAPI(); result.append(QByteArrayLiteral("-nostdinc")); if (parserSettings.isCpp()) { result.append(QByteArrayLiteral("-nostdinc++")); } if (options & ParseSessionData::PrecompiledHeader) { result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++-header") : QByteArrayLiteral("-xc-header")); sanitizeArguments(result); return result; } result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++") : QByteArrayLiteral("-xc")); sanitizeArguments(result); return result; } void addIncludes(QVector* args, QVector* otherArgs, const Path::List& includes, const char* cliSwitch) { for (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()); } } void addFrameworkDirectories(QVector* args, QVector* otherArgs, const Path::List& frameworkDirectories, const char* cliSwitch) { for (const Path& url : frameworkDirectories) { if (url.isEmpty()) { continue; } QFileInfo info(url.toLocalFile()); if (!info.isDir()) { qCWarning(KDEV_CLANG) << "supposed framework directory is not a directory:" << url.pathOrUrl(); continue; } QByteArray path = url.toLocalFile().toUtf8(); otherArgs->append(cliSwitch); otherArgs->append(path); args->append(cliSwitch); 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 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_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 #if CINDEX_VERSION_MINOR >= 32 | CXTranslationUnit_CreatePreambleOnFirstParse #endif | 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 (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 QByteArray qtCore = wrappedQtHeaders + "/QtCore"; smartArgs << qtCore; clangArguments << "-isystem" << qtCore.constData(); } } addIncludes(&clangArguments, &smartArgs, includes.system, "-isystem"); addIncludes(&clangArguments, &smartArgs, includes.project, "-I"); const auto& frameworkDirectories = environment.frameworkDirectories(); addFrameworkDirectories(&clangArguments, &smartArgs, frameworkDirectories.system, "-iframework"); addFrameworkDirectories(&clangArguments, &smartArgs, frameworkDirectories.project, "-F"); // libclang cannot find it's builtin dir automatically, we have to specify it manually smartArgs << ClangHelpers::clangBuiltinIncludePath().toUtf8(); clangArguments << "-isystem" << smartArgs.last().constData(); - smartArgs << writeDefinesFile(environment.defines()); - clangArguments << "-imacros" << smartArgs.last().constData(); + if (!environment.defines().isEmpty()) { + smartArgs << writeDefinesFile(environment.defines()); + clangArguments << "-imacros" << smartArgs.last().constData(); + } if (!environment.workingDirectory().isEmpty()) { QByteArray workingDirectory = environment.workingDirectory().toLocalFile().toUtf8(); workingDirectory.prepend("-working-directory"); smartArgs << workingDirectory; clangArguments << workingDirectory.constData(); } // append extra args from environment variable static const auto extraArgs = ::extraArgs(); for (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"; for (const auto& arg : qAsConst(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) { qCWarning(KDEV_CLANG) << "clang_parseTranslationUnit2 return with error code" << code; if (!qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DIAGS")) { qCWarning(KDEV_CLANG) << " (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, QByteArray(tuUrl.byteArray() + ".pch").constData(), CXSaveTranslationUnit_None); } } else { qCWarning(KDEV_CLANG) << "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) { if (it.key().startsWith(QLatin1String("__has_include(")) || it.key().startsWith(QLatin1String("__has_include_next("))) { continue; } definesStream << QLatin1String("#define ") << it.key() << ' ' << it.value() << '\n'; } } m_definesFile.close(); if (qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DEFINES")) { QFile f(m_definesFile.fileName()); f.open(QIODevice::ReadOnly); Q_ASSERT(f.isReadable()); QTextStream out(stdout); out << "Defines file: " << f.fileName() << "\n" << f.readAll() << f.size() << "\n VS defines:" << defines.size() << "\n"; } return m_definesFile.fileName().toUtf8(); } void ParseSessionData::setUnit(CXTranslationUnit unit) { m_unit = unit; m_diagnosticsCache.clear(); 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; } ClangProblem::Ptr ParseSession::getOrCreateProblem(int indexInTU, CXDiagnostic diagnostic) const { auto& problem = d->m_diagnosticsCache[indexInTU]; if (!problem) { problem = ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit); } return problem; } ClangProblem::Ptr ParseSession::createExternalProblem(int indexInTU, CXDiagnostic diagnostic, const KLocalizedString& descriptionTemplate, int childProblemFinalLocationIndex) const { // Make a copy of the original (cached) problem since it is modified later auto problem = ClangProblem::Ptr(new ClangProblem(*getOrCreateProblem(indexInTU, diagnostic))); // Insert a copy of the parent problem (without child problems) as the first // child problem to preserve its location. auto* problemCopy = new ClangProblem(); problemCopy->setSource(problem->source()); problemCopy->setFinalLocation(problem->finalLocation()); problemCopy->setFinalLocationMode(problem->finalLocationMode()); problemCopy->setDescription(problem->description()); problemCopy->setExplanation(problem->explanation()); problemCopy->setSeverity(problem->severity()); auto childProblems = problem->diagnostics(); childProblems.prepend(IProblem::Ptr(problemCopy)); problem->setDiagnostics(childProblems); // Override the problem's finalLocation with that of the child problem in this document. // This is required to make the problem show up in the problem reporter for this // file, since it filters by finalLocation. It will also lead the user to the correct // location when clicking the problem and cause proper error highlighting. int index = (childProblemFinalLocationIndex >= 0) ? (1 + childProblemFinalLocationIndex) : (childProblems.size() - 1); problem->setFinalLocation(childProblems[index]->finalLocation()); problem->setDescription(descriptionTemplate.subs(problem->description()).toString()); return problem; } QList ParseSession::createRequestedHereProblems(int indexInTU, CXDiagnostic diagnostic, CXFile file) const { QList results; auto childDiagnostics = clang_getChildDiagnostics(diagnostic); auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); for (uint j = 0; j < numChildDiagnostics; ++j) { auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); CXSourceLocation childLocation = clang_getDiagnosticLocation(childDiagnostic); CXFile childDiagnosticFile; clang_getFileLocation(childLocation, &childDiagnosticFile, nullptr, nullptr, nullptr); if (childDiagnosticFile == file) { QString description = ClangString(clang_getDiagnosticSpelling(childDiagnostic)).toString(); if (description.endsWith(QLatin1String("requested here"))) { // Note: Using the index j here assumes a 1:1 mapping from clang child diagnostics to KDevelop // problem diagnostics (i.e., child problems). If we wanted to avoid making this assumption, we'd have // to use ClangDiagnosticEvaluator::createProblem() first and then search within its // child problems to find the correct index. results << createExternalProblem(indexInTU, diagnostic, ki18n("Requested here: %1"), j); } } } return results; } 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); d->m_diagnosticsCache.resize(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); auto requestedHereProblems = createRequestedHereProblems(i, diagnostic, file); for (const auto& ptr : requestedHereProblems) { problems.append(static_cast(ptr)); } // 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; } problems << ((diagnosticFile == file) ? getOrCreateProblem(i, diagnostic) : createExternalProblem(i, diagnostic, ki18n("In included file: %1"))); 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))) { QExplicitlySharedDataPointer problem(new StaticAssistantProblem); 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.")); const KTextEditor::Range problemRange(0, 0, KDevelop::createCodeRepresentation(indexedPath)->lines(), 0); problem->setFinalLocation(DocumentRange{indexedPath, problemRange}); problem->setSource(IProblem::Preprocessor); problem->setSolutionAssistant(KDevelop::IAssistant::Ptr(new HeaderGuardAssistant(d->m_unit, file))); problems << problem; } #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) { qCWarning(KDEV_CLANG) << "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/plugins/clang/tests/test_files.cpp b/plugins/clang/tests/test_files.cpp index 84844f7952..a27e32c4d9 100644 --- a/plugins/clang/tests/test_files.cpp +++ b/plugins/clang/tests/test_files.cpp @@ -1,118 +1,130 @@ /************************************************************************************* * 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 #include #include #include #include #include #include #include "testfilepaths.h" #include "testprovider.h" //Include all used json tests, otherwise "Test not found" #include #include #include #include #include #include +#include using namespace KDevelop; QTEST_MAIN(TestFiles) +namespace { +bool isCudaAvailable() +{ + return QProcess::execute(QStringLiteral("clang"), {QStringLiteral("-xcuda"), QStringLiteral("-fsyntax-only"), QProcess::nullDevice()}) == 0; +} +} + 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_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({"kdevclangsupport"}); TestCore::initialize(Core::NoUi); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); CodeRepresentation::setDiskChangesForbidden(true); m_provider = new TestEnvironmentProvider; IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider); } void TestFiles::cleanupTestCase() { delete m_provider; TestCore::shutdown(); } void TestFiles::cleanup() { m_provider->clear(); } void TestFiles::testFiles_data() { QTest::addColumn("fileName"); const QString testDirPath = TEST_FILES_DIR; - const QStringList files = QDir(testDirPath).entryList({"*.h", "*.cpp", "*.c", "*.cl", "*.cu"}, QDir::Files); + auto patterns = QStringList{"*.h", "*.cpp", "*.c", "*.cl"}; + if (isCudaAvailable()) { + patterns.append("*.cu"); + } + const QStringList files = QDir(testDirPath).entryList(patterns, QDir::Files); for (const QString& file : files) { QTest::newRow(file.toUtf8().constData()) << QString(testDirPath + '/' + file); } } void TestFiles::testFiles() { QFETCH(QString, fileName); if (QTest::currentDataTag() == QLatin1String("lambdas.cpp")) { m_provider->parserArguments += "-std=c++14"; } const IndexedString indexedFileName(fileName); ReferencedTopDUContext top = DUChain::self()->waitForUpdate(indexedFileName, TopDUContext::AllDeclarationsContextsAndUses); if (strcmp("test.cl", QTest::currentDataTag()) == 0) { if (!top) { QSKIP("Likely outdated shared-mime-info around, which doesn't know about the text/x-opencl-src mime type"); } } QVERIFY(top); DUChainReadLocker lock; DeclarationValidator validator; top->visit(validator); const auto problems = top->problems(); for (auto& problem : problems) { qDebug() << problem; } QEXPECT_FAIL("lambdas.cpp", "capture with identifier and initializer aren't visited apparently", Abort); QVERIFY(validator.testsPassed()); if (!QTest::currentDataTag() || strcmp("invalid.cpp", QTest::currentDataTag()) != 0) { QVERIFY(top->problems().isEmpty()); } } diff --git a/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp b/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp index bfcaafb822..b7a28f1e3c 100644 --- a/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp +++ b/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp @@ -1,238 +1,238 @@ /* * 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 "gcclikecompiler.h" #include #include #include #include #include #include #include using namespace KDevelop; namespace { QString languageOption(Utils::LanguageType type) { switch (type) { case Utils::C: return QStringLiteral("-xc"); case Utils::Cpp: return QStringLiteral("-xc++"); case Utils::OpenCl: return QStringLiteral("-xcl"); case Utils::Cuda: return QStringLiteral("-xcuda"); case Utils::ObjC: return QStringLiteral("-xobjective-c"); case Utils::ObjCpp: return QStringLiteral("-xobjective-c++"); default: Q_UNREACHABLE(); } } QString languageStandard(const QString& arguments, Utils::LanguageType type) { // TODO: handle -ansi flag: In C mode, this is equivalent to -std=c90. In C++ mode, it is equivalent to -std=c++98. const QRegularExpression regexp(QStringLiteral("-std=(\\S+)")); auto result = regexp.match(arguments); if (result.hasMatch()) return result.captured(0); switch (type) { case Utils::C: case Utils::ObjC: return QStringLiteral("-std=c99"); case Utils::Cpp: case Utils::ObjCpp: case Utils::Cuda: return QStringLiteral("-std=c++11"); case Utils::OpenCl: return QStringLiteral("-cl-std=CL1.1"); default: Q_UNREACHABLE(); } } } Defines GccLikeCompiler::defines(Utils::LanguageType type, const QString& arguments) const { - auto& data = m_definesIncludes[arguments]; + auto& data = m_definesIncludes[type][arguments]; if (!data.definedMacros.isEmpty() ) { return data.definedMacros; } // #define a 1 // #define a QRegExp defineExpression(QStringLiteral("#define\\s+(\\S+)(?:\\s+(.*)\\s*)?")); const auto rt = ICore::self()->runtimeController()->currentRuntime(); QProcess proc; proc.setProcessChannelMode( QProcess::MergedChannels ); // TODO: what about -mXXX or -target= flags, some of these change search paths/defines const QStringList compilerArguments{ languageOption(type), languageStandard(arguments, type), QStringLiteral("-dM"), QStringLiteral("-E"), QStringLiteral("-"), }; proc.setStandardInputFile(QProcess::nullDevice()); proc.setProgram(path()); proc.setArguments(compilerArguments); rt->startProcess(&proc); if ( !proc.waitForStarted( 2000 ) || !proc.waitForFinished( 2000 ) ) { qCDebug(DEFINESANDINCLUDES) << "Unable to read standard macro definitions from "<< path(); return {}; } if (proc.exitCode() != 0) { qCWarning(DEFINESANDINCLUDES) << "error while fetching defines for the compiler:" << path() << proc.readAll(); return {}; } while ( proc.canReadLine() ) { auto line = proc.readLine(); if ( defineExpression.indexIn(QString::fromUtf8(line)) != -1 ) { data.definedMacros[defineExpression.cap( 1 )] = defineExpression.cap( 2 ).trimmed(); } } return data.definedMacros; } Path::List GccLikeCompiler::includes(Utils::LanguageType type, const QString& arguments) const { - auto& data = m_definesIncludes[arguments]; + auto& data = m_definesIncludes[type][arguments]; if ( !data.includePaths.isEmpty() ) { return data.includePaths; } const auto rt = ICore::self()->runtimeController()->currentRuntime(); QProcess proc; proc.setProcessChannelMode( QProcess::MergedChannels ); // The following command will spit out a bunch of information we don't care // about before spitting out the include paths. The parts we care about // look like this: // #include "..." search starts here: // #include <...> search starts here: // /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2 // /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/i486-linux-gnu // /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/backward // /usr/local/include // /usr/lib/gcc/i486-linux-gnu/4.1.2/include // /usr/include // End of search list. const QStringList compilerArguments{ languageOption(type), languageStandard(arguments, type), QStringLiteral("-E"), QStringLiteral("-v"), QStringLiteral("-"), }; proc.setStandardInputFile(QProcess::nullDevice()); proc.setProgram(path()); proc.setArguments(compilerArguments); rt->startProcess(&proc); if ( !proc.waitForStarted( 2000 ) || !proc.waitForFinished( 2000 ) ) { qCDebug(DEFINESANDINCLUDES) << "Unable to read standard include paths from " << path(); return {}; } if (proc.exitCode() != 0) { qCWarning(DEFINESANDINCLUDES) << "error while fetching includes for the compiler:" << path() << proc.readAll(); return {}; } // We'll use the following constants to know what we're currently parsing. enum Status { Initial, FirstSearch, Includes, Finished }; Status mode = Initial; const auto output = QString::fromLocal8Bit( proc.readAllStandardOutput() ); const auto lines = output.splitRef(QLatin1Char('\n')); for (const auto& line : lines) { switch ( mode ) { case Initial: if ( line.indexOf( QLatin1String("#include \"...\"") ) != -1 ) { mode = FirstSearch; } break; case FirstSearch: if ( line.indexOf( QLatin1String("#include <...>") ) != -1 ) { mode = Includes; } break; case Includes: //Detect the include-paths by the first space that is prepended. Reason: The list may contain relative paths like "." if (!line.startsWith(QLatin1Char(' '))) { // We've reached the end of the list. mode = Finished; } else { // This is an include path, add it to the list. auto hostPath = rt->pathInHost(Path(QFileInfo(line.trimmed().toString()).canonicalFilePath())); // but skip folders with compiler builtins, we cannot parse these with clang if (!QFile::exists(hostPath.toLocalFile() + QLatin1String("/cpuid.h"))) { data.includePaths << Path(QFileInfo(hostPath.toLocalFile()).canonicalFilePath()); } } break; default: break; } if ( mode == Finished ) { break; } } return data.includePaths; } void GccLikeCompiler::invalidateCache() { m_definesIncludes.clear(); } GccLikeCompiler::GccLikeCompiler(const QString& name, const QString& path, bool editable, const QString& factoryName): ICompiler(name, path, factoryName, editable) { connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &GccLikeCompiler::invalidateCache); } diff --git a/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.h b/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.h index 3431eaa390..0ab03aed23 100644 --- a/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.h +++ b/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.h @@ -1,52 +1,52 @@ /* * 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 . * */ #ifndef GCCLIKECOMPILER_H #define GCCLIKECOMPILER_H #include "icompiler.h" class GccLikeCompiler : public QObject, public ICompiler { Q_OBJECT public: GccLikeCompiler( const QString& name, const QString& path, bool editable, const QString& factoryName ); KDevelop::Defines defines(Utils::LanguageType type, const QString& arguments) const override; KDevelop::Path::List includes(Utils::LanguageType type, const QString& arguments) const override; private: void invalidateCache(); struct DefinesIncludes { KDevelop::Defines definedMacros; KDevelop::Path::List includePaths; }; /// List of defines/includes per arguments - mutable QHash m_definesIncludes; + mutable QHash> m_definesIncludes; }; #endif // GCCLIKECOMPILER_H