diff --git a/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp b/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp index e1e74816b6..090eb2d96f 100644 --- a/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp +++ b/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp @@ -1,330 +1,340 @@ /* * 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 "debug.h" #include "compilerfactories.h" #include "settingsmanager.h" #include #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(Utils::LanguageType, const QString&) const override { return {}; } Path::List includes(Utils::LanguageType, const QString&) const override { return {}; } }; static CompilerPointer createDummyCompiler() { static CompilerPointer compiler(new NoCompiler()); return compiler; } ConfigEntry configForItem(KDevelop::ProjectBaseItem* item) { if(!item){ return ConfigEntry(); } const Path itemPath = item->path(); const Path rootDirectory = item->project()->path(); const 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; } + +QString parserArguments( const ConfigEntry& config, Utils::LanguageType languageType, ProjectBaseItem* item ) +{ + auto args = config.parserArguments[languageType]; + if (item) { + args += QLatin1Char(' '); + args += item->project()->buildSystemManager()->extraArguments(item); + } + return args; +} } ProjectTargetItem* findCompiledTarget(ProjectBaseItem* item) { const auto targets = item->targetList(); for (auto* item : targets) { if (item->type() == ProjectBaseItem::ExecutableTarget || item->type() == ProjectBaseItem::LibraryTarget) { return item; } } const auto folders = item->folderList(); for (auto* folder: folders) { auto target = findCompiledTarget(folder); if (target) return target; } return nullptr; } CompilerProvider::CompilerProvider( SettingsManager* settings, QObject* parent ) : QObject( parent ) , m_settings(settings) { m_factories = { CompilerFactoryPointer(new GccFactory()), CompilerFactoryPointer(new ClangFactory()), #ifdef _WIN32 CompilerFactoryPointer(new MsvcFactory()), #endif }; if (!QStandardPaths::findExecutable( QStringLiteral("clang") ).isEmpty()) { m_factories[1]->registerDefaultCompilers(this); } if (!QStandardPaths::findExecutable( QStringLiteral("gcc") ).isEmpty()) { m_factories[0]->registerDefaultCompilers(this); } #ifdef _WIN32 if (!QStandardPaths::findExecutable(QStringLiteral("cl.exe")).isEmpty()) { m_factories[2]->registerDefaultCompilers(this); } #endif registerCompiler(createDummyCompiler()); retrieveUserDefinedCompilers(); connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, [this]() { m_defaultProvider.clear(); }); connect(ICore::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &CompilerProvider::projectChanged); connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &CompilerProvider::projectChanged); } CompilerProvider::~CompilerProvider() = default; void CompilerProvider::projectChanged(KDevelop::IProject* p) { const auto target = findCompiledTarget(p->projectItem()); if (!target) return; auto path = p->buildSystemManager()->compiler(target); qCDebug(DEFINESANDINCLUDES) << "found compiler" << path; if (path.isEmpty()) return; Q_ASSERT(QDir::isAbsolutePath(path.toLocalFile())); const auto pathString = path.toLocalFile(); auto it = std::find_if(m_compilers.begin(), m_compilers.end(), [&pathString](const CompilerPointer& compiler) { return compiler->path() == pathString; }); if (it != m_compilers.end()) { m_defaultProvider = *it; return; } //we need to search, sdk compiler names are weird: arm-linux-androideabi-g++ for (auto& factory : qAsConst(m_factories)) { if (factory->isSupported(path)) { auto compiler = factory->createCompiler(path.lastPathSegment(), pathString); registerCompiler(compiler); m_defaultProvider = compiler; } } qCDebug(DEFINESANDINCLUDES) << "using compiler" << m_defaultProvider << path; } QHash CompilerProvider::defines( const QString& path ) const { auto config = configForItem(nullptr); auto languageType = Utils::languageType(path, config.parserArguments.parseAmbiguousAsCPP); // If called on files that we can't compile, return an empty set of defines. if (languageType == Utils::Other) { return {}; } - return config.compiler->defines(languageType, config.parserArguments[languageType]); + return config.compiler->defines(languageType, parserArguments(config, languageType, nullptr)); } QHash CompilerProvider::defines( ProjectBaseItem* item ) const { auto config = configForItem(item); auto languageType = Utils::Cpp; if (item) { languageType = Utils::languageType(item->path().path(), config.parserArguments.parseAmbiguousAsCPP); } // If called on files that we can't compile, return an empty set of defines. if (languageType == Utils::Other) { return {}; } - return config.compiler->defines(languageType, config.parserArguments[languageType]); + return config.compiler->defines(languageType, parserArguments(config, languageType, item)); } Path::List CompilerProvider::includes( const QString& path ) const { auto config = configForItem(nullptr); auto languageType = Utils::languageType(path, config.parserArguments.parseAmbiguousAsCPP); // If called on files that we can't compile, return an empty set of includes. if (languageType == Utils::Other) { return {}; } - return config.compiler->includes(languageType, config.parserArguments[languageType]); + return config.compiler->includes(languageType, parserArguments(config, languageType, nullptr)); } Path::List CompilerProvider::includes( ProjectBaseItem* item ) const { auto config = configForItem(item); auto languageType = Utils::Cpp; if (item) { languageType = Utils::languageType(item->path().path(), config.parserArguments.parseAmbiguousAsCPP); } // If called on files that we can't compile, return an empty set of includes. if (languageType == Utils::Other) { return {}; } - return config.compiler->includes(languageType, config.parserArguments[languageType]); + return config.compiler->includes(languageType, parserArguments(config, languageType, item)); } Path::List CompilerProvider::frameworkDirectories( const QString& /* path */ ) const { return {}; } Path::List CompilerProvider::frameworkDirectories( ProjectBaseItem* /* item */ ) const { return {}; } IDefinesAndIncludesManager::Type CompilerProvider::type() const { return IDefinesAndIncludesManager::CompilerSpecific; } CompilerPointer CompilerProvider::defaultCompiler() const { if (m_defaultProvider) return m_defaultProvider; auto rt = ICore::self()->runtimeController()->currentRuntime(); for ( const CompilerPointer& compiler : m_compilers ) { if (rt->findExecutable(compiler->path()).isEmpty()) continue; m_defaultProvider = compiler; break; } if (!m_defaultProvider) m_defaultProvider = createDummyCompiler(); qCDebug(DEFINESANDINCLUDES) << "new default compiler" << rt->name() << m_defaultProvider->name() << m_defaultProvider->path(); return m_defaultProvider; } 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 : qAsConst(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() { const auto compilers = m_settings->userDefinedCompilers(); for (auto& c : compilers) { registerCompiler(c); } } diff --git a/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp b/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp index b7a28f1e3c..fc02e524eb 100644 --- a/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp +++ b/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp @@ -1,238 +1,240 @@ /* * 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+)")); + // NOTE: we put the greedy .* in front to find the last occurrence + const QRegularExpression regexp(QStringLiteral(".*(-std=\\S+)")); auto result = regexp.match(arguments); - if (result.hasMatch()) - return result.captured(0); + if (result.hasMatch()) { + return result.captured(1); + } 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[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(); + qCDebug(DEFINESANDINCLUDES) << "Unable to read standard macro definitions from "<< path() << compilerArguments; return {}; } if (proc.exitCode() != 0) { - qCWarning(DEFINESANDINCLUDES) << "error while fetching defines for the compiler:" << path() << proc.readAll(); + qCWarning(DEFINESANDINCLUDES) << "error while fetching defines for the compiler:" << path() << compilerArguments << 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[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); }