diff --git a/languages/clang/clangparsejob.cpp b/languages/clang/clangparsejob.cpp index 8375eb5dee..655841f63b 100644 --- a/languages/clang/clangparsejob.cpp +++ b/languages/clang/clangparsejob.cpp @@ -1,380 +1,383 @@ /* 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); + *hasBuildSystemInfo = bsm->hasBuildInfo(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.addFrameworkDirectories(IDefinesAndIncludesManager::manager()->frameworkDirectories(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.addFrameworkDirectories(IDefinesAndIncludesManager::manager()->frameworkDirectories(tuUrl.str())); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(tuUrl.str())); 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.addFrameworkDirectories(IDefinesAndIncludesManager::manager()->frameworkDirectoriesInBackground(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(), [this] { return abortRequested(); }); 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/duchain/clangparsingenvironment.cpp b/languages/clang/duchain/clangparsingenvironment.cpp index b515037766..6b9f00fd0c 100644 --- a/languages/clang/duchain/clangparsingenvironment.cpp +++ b/languages/clang/duchain/clangparsingenvironment.cpp @@ -1,147 +1,169 @@ /* * This file is part of KDevelop * * Copyright 2014 Milian Wolff * * This program 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 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 "clangparsingenvironment.h" using namespace KDevelop; int ClangParsingEnvironment::type() const { return CppParsingEnvironment; } void ClangParsingEnvironment::setProjectPaths(const Path::List& projectPaths) { m_projectPaths = projectPaths; } Path::List ClangParsingEnvironment::projectPaths() const { return m_projectPaths; } void ClangParsingEnvironment::addIncludes(const Path::List& includes) { m_includes += includes; } -ClangParsingEnvironment::IncludePaths ClangParsingEnvironment::includes() const +void ClangParsingEnvironment::addFrameworkDirectories(const KDevelop::Path::List& frameworkDirectories) +{ + m_frameworkDirectories += frameworkDirectories; +} + +template +static PathType appendPaths(const KDevelop::Path::List &paths, const KDevelop::Path::List &projectPaths) { - IncludePaths ret; - ret.project.reserve(m_includes.size()); - ret.system.reserve(m_includes.size()); - foreach (const auto& path, m_includes) { + PathType ret; + ret.project.reserve(paths.size()); + ret.system.reserve(paths.size()); + foreach (const auto& path, paths) { bool inProject = false; - foreach (const auto& project, m_projectPaths) { + foreach (const auto& project, projectPaths) { if (project.isParentOf(path) || project == path) { inProject = true; break; } } if (inProject) { ret.project.append(path); } else { ret.system.append(path); } } return ret; } +ClangParsingEnvironment::IncludePaths ClangParsingEnvironment::includes() const +{ + return appendPaths(m_includes, m_projectPaths); +} + +ClangParsingEnvironment::FrameworkDirectories ClangParsingEnvironment::frameworkDirectories() const +{ + return appendPaths(m_frameworkDirectories, m_projectPaths); +} + void ClangParsingEnvironment::addDefines(const QHash& defines) { for (auto it = defines.constBegin(); it != defines.constEnd(); ++it) { m_defines[it.key()] = it.value(); } } QMap ClangParsingEnvironment::defines() const { return m_defines; } void ClangParsingEnvironment::setPchInclude(const Path& path) { m_pchInclude = path; } Path ClangParsingEnvironment::pchInclude() const { return m_pchInclude; } void ClangParsingEnvironment::setTranslationUnitUrl(const IndexedString& url) { m_tuUrl = url; } IndexedString ClangParsingEnvironment::translationUnitUrl() const { return m_tuUrl; } void ClangParsingEnvironment::setQuality(Quality quality) { m_quality = quality; } ClangParsingEnvironment::Quality ClangParsingEnvironment::quality() const { return m_quality; } uint ClangParsingEnvironment::hash() const { KDevHash hash; hash << m_defines.size(); for (auto it = m_defines.constBegin(); it != m_defines.constEnd(); ++it) { hash << qHash(it.key()) << qHash(it.value()); } hash << m_includes.size(); for (const auto& include : m_includes) { hash << qHash(include); } + hash << m_frameworkDirectories.size(); + for (const auto& fwDir : m_frameworkDirectories) { + hash << qHash(fwDir); + } + hash << qHash(m_pchInclude); hash << qHash(m_parserSettings.parserOptions); return hash; } bool ClangParsingEnvironment::operator==(const ClangParsingEnvironment& other) const { return m_defines == other.m_defines && m_includes == other.m_includes + && m_frameworkDirectories == other.m_frameworkDirectories && m_pchInclude == other.m_pchInclude && m_quality == other.m_quality && m_tuUrl == other.m_tuUrl && m_parserSettings == other.m_parserSettings; } void ClangParsingEnvironment::setParserSettings(const ParserSettings& parserSettings) { m_parserSettings = parserSettings; } ParserSettings ClangParsingEnvironment::parserSettings() const { return m_parserSettings; } diff --git a/languages/clang/duchain/clangparsingenvironment.h b/languages/clang/duchain/clangparsingenvironment.h index c689132504..ccd5d1e93c 100644 --- a/languages/clang/duchain/clangparsingenvironment.h +++ b/languages/clang/duchain/clangparsingenvironment.h @@ -1,112 +1,130 @@ /* * This file is part of KDevelop * * Copyright 2014 Milian Wolff * * This program 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 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 CLANGPARSINGENVIRONMENT_H #define CLANGPARSINGENVIRONMENT_H #include #include #include "clangprivateexport.h" #include "clangsettings/clangsettingsmanager.h" class KDEVCLANGPRIVATE_EXPORT ClangParsingEnvironment : public KDevelop::ParsingEnvironment { public: virtual ~ClangParsingEnvironment() = default; virtual int type() const override; /** * Sets the list of project paths. * * Any include path outside these project paths is considered * to be a system include. */ void setProjectPaths(const KDevelop::Path::List& projectPaths); KDevelop::Path::List projectPaths() const; /** * Add the given list of @p include paths to this environment. */ void addIncludes(const KDevelop::Path::List& includes); + /** + * Add the given list of @p framework-directories to this environment. + */ + void addFrameworkDirectories(const KDevelop::Path::List& frameworkDirectories); + struct IncludePaths { /// This list contains all include paths outside the known projects paths. KDevelop::Path::List system; /// This list contains all include paths inside the known projects paths. KDevelop::Path::List project; }; /** * Returns the list of includes, split into a list of system includes and project includes. */ IncludePaths includes() const; + struct FrameworkDirectories + { + /// This list contains all framework directories outside the known projects paths. + KDevelop::Path::List system; + /// This list contains all framework directories inside the known projects paths. + KDevelop::Path::List project; + }; + /** + * Returns the list of framework directories, split into a list of system paths and project paths. + */ + FrameworkDirectories frameworkDirectories() const; + void addDefines(const QHash& defines); QMap defines() const; void setPchInclude(const KDevelop::Path& path); KDevelop::Path pchInclude() const; void setTranslationUnitUrl(const KDevelop::IndexedString& url); KDevelop::IndexedString translationUnitUrl() const; enum Quality { Unknown, Source, BuildSystem }; void setQuality(Quality quality); Quality quality() const; void setParserSettings(const ParserSettings& arguments); ParserSettings parserSettings() const; /** * Hash all contents of this environment and return the result. * * This is useful for a quick comparison, and enough to store on-disk * to figure out if the environment changed or not. */ uint hash() const; bool operator==(const ClangParsingEnvironment& other) const; bool operator!=(const ClangParsingEnvironment& other) const { return !(*this == other); } private: KDevelop::Path::List m_projectPaths; KDevelop::Path::List m_includes; + KDevelop::Path::List m_frameworkDirectories; // NOTE: As elements in QHash stored in an unordered sequence, we're using QMap instead QMap m_defines; KDevelop::Path m_pchInclude; KDevelop::IndexedString m_tuUrl; Quality m_quality = Unknown; ParserSettings m_parserSettings; }; #endif // CLANGPARSINGENVIRONMENT_H diff --git a/languages/clang/duchain/parsesession.cpp b/languages/clang/duchain/parsesession.cpp index aae06619ba..d16d32b78e 100644 --- a/languages/clang/duchain/parsesession.cpp +++ b/languages/clang/duchain/parsesession.cpp @@ -1,453 +1,479 @@ /* 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(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")); if (parserSettings.isCpp()) { 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()); } } +void addFrameworkDirectories(QVector* args, QVector* otherArgs, + const Path::List& frameworkDirectories, const char* cliSwitch) +{ + foreach (const Path& url, frameworkDirectories) { + if (url.isEmpty()) { + continue; + } + + QFileInfo info(url.toLocalFile()); + if (!info.isDir()) { + qWarning() << "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 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"); + const auto& frameworkDirectories = environment.frameworkDirectories(); + addFrameworkDirectories(&clangArguments, &smartArgs, frameworkDirectories.system, "-iframework"); + addFrameworkDirectories(&clangArguments, &smartArgs, frameworkDirectories.project, "-F"); + 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/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp index 24e532a62e..aefcd3a93c 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp @@ -1,234 +1,239 @@ /* * 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); 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); 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); } +Path::List CompilerProvider::frameworkDirectories( ProjectBaseItem* item ) const +{ + return {}; +} + 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/compilerprovider.h b/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h index 7a5184ffd0..a76b59211c 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h +++ b/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h @@ -1,74 +1,75 @@ /* * 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 COMPILERSPROVIDER_H #define COMPILERSPROVIDER_H #include "icompilerfactory.h" #include class SettingsManager; class CompilerProvider : public QObject, public KDevelop::IDefinesAndIncludesManager::Provider { Q_OBJECT public: explicit CompilerProvider( SettingsManager* settings, QObject* parent = nullptr ); ~CompilerProvider() override; KDevelop::Defines defines( KDevelop::ProjectBaseItem* item ) const override; KDevelop::Path::List includes( KDevelop::ProjectBaseItem* item ) const override; + KDevelop::Path::List frameworkDirectories( KDevelop::ProjectBaseItem* item ) const override; KDevelop::IDefinesAndIncludesManager::Type type() const override; /// @return current compiler for the @p item CompilerPointer compilerForItem( KDevelop::ProjectBaseItem* item ) const; /// @return list of all available compilers QVector compilers() const; /** * Adds compiler to the list of available compilers * @return true on success (if there is no compiler with the same name registered). */ bool registerCompiler(const CompilerPointer& compiler); /// Removes compiler from the list of available compilers void unregisterCompiler( const CompilerPointer& compiler ); /// @return All available factories QVector compilerFactories() const; /// Checks wheter the @p compiler exist, if so returns it. Otherwise returns default compiler CompilerPointer checkCompilerExists( const CompilerPointer& compiler ) const; private Q_SLOTS: void retrieveUserDefinedCompilers(); private: QVector m_compilers; QVector m_factories; SettingsManager* m_settings; }; #endif // COMPILERSPROVIDER_H diff --git a/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp b/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp index ebceb4dc96..9ed74e83c1 100644 --- a/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp +++ b/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp @@ -1,321 +1,367 @@ /* * 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 (targetDirectory.segments().size() > closestPath.segments().size()) { ret.parserArguments = entry.parserArguments; closestPath = targetDirectory; } } } ret.includes.removeDuplicates(); Q_ASSERT(!ret.parserArguments.cppArguments.isEmpty()); Q_ASSERT(!ret.parserArguments.cArguments.isEmpty()); return ret; } void merge(Defines* target, const Defines& source) { if (target->isEmpty()) { *target = source; return; } for (auto it = source.constBegin(); it != source.constEnd(); ++it) { target->insert(it.key(), it.value()); } } } 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()); +#ifdef Q_OS_OSX + m_defaultFrameworkDirectories += Path(QStringLiteral("/Library/Frameworks")); + m_defaultFrameworkDirectories += Path(QStringLiteral("/System/Library/Frameworks")); +#endif } DefinesAndIncludesManager::~DefinesAndIncludesManager() = default; Defines DefinesAndIncludesManager::defines( ProjectBaseItem* item, Type type ) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); if (!item) { return m_settings->provider()->defines(nullptr); } Defines defines; for (auto provider : m_providers) { if (provider->type() & type) { merge(&defines, provider->defines(item)); } } if ( type & ProjectSpecific ) { auto buildManager = item->project()->buildSystemManager(); if ( buildManager ) { merge(&defines, buildManager->defines(item)); } } // Manually set defines have the highest priority and overwrite values of all other types of defines. if (type & UserDefined) { auto cfg = item->project()->projectConfiguration().data(); merge(&defines, findConfigForItem(m_settings->readPaths(cfg), item).defines); } merge(&defines, m_noProjectIPM->includesAndDefines(item->path().path()).second); return defines; } Path::List DefinesAndIncludesManager::includes( ProjectBaseItem* item, Type type ) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); if (!item) { return m_settings->provider()->includes(nullptr); } Path::List includes; if (type & UserDefined) { auto cfg = item->project()->projectConfiguration().data(); includes += KDevelop::toPathList(findConfigForItem(m_settings->readPaths(cfg), item).includes); } if ( type & ProjectSpecific ) { auto buildManager = item->project()->buildSystemManager(); if ( buildManager ) { includes += buildManager->includeDirectories(item); } } for (auto provider : m_providers) { if (provider->type() & type) { includes += provider->includes(item); } } includes += m_noProjectIPM->includesAndDefines(item->path().path()).first; return includes; } +Path::List DefinesAndIncludesManager::frameworkDirectories( ProjectBaseItem* item, Type type ) const +{ + Q_ASSERT(QThread::currentThread() == qApp->thread()); + + if (!item) { + return m_settings->provider()->frameworkDirectories(nullptr); + } + + Path::List frameworkDirectories = m_defaultFrameworkDirectories; + + if ( type & ProjectSpecific ) { + auto buildManager = item->project()->buildSystemManager(); + if ( buildManager ) { + frameworkDirectories += buildManager->frameworkDirectories(item); + } + } + + for (auto provider : m_providers) { + if (provider->type() & type) { + frameworkDirectories += provider->frameworkDirectories(item); + } + } + + return frameworkDirectories; +} + bool DefinesAndIncludesManager::unregisterProvider(IDefinesAndIncludesManager::Provider* provider) { int idx = m_providers.indexOf(provider); if (idx != -1) { m_providers.remove(idx); return true; } return false; } void DefinesAndIncludesManager::registerProvider(IDefinesAndIncludesManager::Provider* provider) { Q_ASSERT(provider); if (m_providers.contains(provider)) { return; } m_providers.push_back(provider); } Defines DefinesAndIncludesManager::defines(const QString& path) const { Defines ret = m_settings->provider()->defines(nullptr); merge(&ret, m_noProjectIPM->includesAndDefines(path).second); return ret; } Path::List DefinesAndIncludesManager::includes(const QString& path) const { return m_settings->provider()->includes(nullptr) + m_noProjectIPM->includesAndDefines(path).first; } +Path::List DefinesAndIncludesManager::frameworkDirectories(const QString& path) const +{ + return m_settings->provider()->frameworkDirectories(nullptr); +} + void DefinesAndIncludesManager::openConfigurationDialog(const QString& pathToFile) { if (auto project = KDevelop::ICore::self()->projectController()->findProjectForUrl(QUrl::fromLocalFile(pathToFile))) { KDevelop::ICore::self()->projectController()->configureProject(project); } else { m_noProjectIPM->openConfigurationDialog(pathToFile); } } Path::List DefinesAndIncludesManager::includesInBackground(const QString& path) const { Path::List includes; for (auto provider: m_backgroundProviders) { includes += provider->includesInBackground(path); } return includes; } +Path::List DefinesAndIncludesManager::frameworkDirectoriesInBackground(const QString& path) const +{ + Path::List fwDirs; + + for (auto provider: m_backgroundProviders) { + fwDirs += provider->frameworkDirectoriesInBackground(path); + } + + return fwDirs; +} + Defines DefinesAndIncludesManager::definesInBackground(const QString& path) const { QHash defines; for (auto provider: m_backgroundProviders) { auto result = provider->definesInBackground(path); for (auto it = result.constBegin(); it != result.constEnd(); it++) { defines[it.key()] = it.value(); } } merge(&defines, m_noProjectIPM->includesAndDefines(path).second); return defines; } bool DefinesAndIncludesManager::unregisterBackgroundProvider(IDefinesAndIncludesManager::BackgroundProvider* provider) { int idx = m_backgroundProviders.indexOf(provider); if (idx != -1) { m_backgroundProviders.remove(idx); return true; } return false; } void DefinesAndIncludesManager::registerBackgroundProvider(IDefinesAndIncludesManager::BackgroundProvider* provider) { Q_ASSERT(provider); if (m_backgroundProviders.contains(provider)) { return; } m_backgroundProviders.push_back(provider); } QString DefinesAndIncludesManager::parserArguments(KDevelop::ProjectBaseItem* item) const { Q_ASSERT(item); Q_ASSERT(QThread::currentThread() == qApp->thread()); auto cfg = item->project()->projectConfiguration().data(); const auto 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 6d0d210819..d3b7a575c1 100644 --- a/languages/plugins/custom-definesandincludes/definesandincludesmanager.h +++ b/languages/plugins/custom-definesandincludes/definesandincludesmanager.h @@ -1,83 +1,88 @@ /* * 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; + ///@return list of all custom framework directories for @p item + KDevelop::Path::List frameworkDirectories( KDevelop::ProjectBaseItem* item, Type type ) const override; KDevelop::Defines defines( const QString& path ) const override; KDevelop::Path::List includes( const QString& path ) const override; + KDevelop::Path::List frameworkDirectories(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::Path::List frameworkDirectoriesInBackground( 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; + KDevelop::Path::List m_defaultFrameworkDirectories; }; #endif // CUSTOMDEFINESANDINCLUDESMANAGER_H diff --git a/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h b/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h index 5da71f2e2a..6ee4611218 100644 --- a/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h +++ b/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h @@ -1,194 +1,217 @@ /* * 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; + virtual Path::List frameworkDirectories( 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 Path::List frameworkDirectoriesInBackground( 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 item project item + ///@return list of framework directories for @p item + ///NOTE: call it from the foreground thread only. + virtual Path::List frameworkDirectories( 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; + ///@param path path to an out-of-project file. + ///@return list of framework directories for @p path + ///NOTE: call it from the foreground thread only. + virtual Path::List frameworkDirectories( 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 framework directories in background thread. + * + * This is especially useful for CustomMake projects. + * + * Call it from background thread if possible. + **/ + virtual Path::List frameworkDirectoriesInBackground( 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 */ 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/projectmanagers/cmake/cmakeimportjsonjob.cpp b/projectmanagers/cmake/cmakeimportjsonjob.cpp index f064647803..50ed3b4a3f 100644 --- a/projectmanagers/cmake/cmakeimportjsonjob.cpp +++ b/projectmanagers/cmake/cmakeimportjsonjob.cpp @@ -1,205 +1,206 @@ /* KDevelop CMake Support * * Copyright 2014 Kevin Funk * * 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 "cmakeimportjsonjob.h" #include "cmakeutils.h" #include "cmakeprojectdata.h" #include "cmakemodelitems.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { CMakeJsonData importCommands(const Path& commandsFile) { // NOTE: to get compile_commands.json, you need -DCMAKE_EXPORT_COMPILE_COMMANDS=ON QFile f(commandsFile.toLocalFile()); bool r = f.open(QFile::ReadOnly|QFile::Text); if(!r) { qCWarning(CMAKE) << "Couldn't open commands file" << commandsFile; return {}; } qCDebug(CMAKE) << "Found commands file" << commandsFile; CMakeJsonData data; QJsonParseError error; const QJsonDocument document = QJsonDocument::fromJson(f.readAll(), &error); if (error.error) { qCWarning(CMAKE) << "Failed to parse JSON in commands file:" << error.errorString() << commandsFile; data.isValid = false; return data; } else if (!document.isArray()) { qCWarning(CMAKE) << "JSON document in commands file is not an array: " << commandsFile; data.isValid = false; return data; } MakeFileResolver resolver; static const QString KEY_COMMAND = QStringLiteral("command"); static const QString KEY_DIRECTORY = QStringLiteral("directory"); static const QString KEY_FILE = QStringLiteral("file"); foreach(const QJsonValue& value, document.array()) { if (!value.isObject()) { qCWarning(CMAKE) << "JSON command file entry is not an object:" << value; continue; } const QJsonObject entry = value.toObject(); if (!entry.contains(KEY_FILE) || !entry.contains(KEY_COMMAND) || !entry.contains(KEY_DIRECTORY)) { qCWarning(CMAKE) << "JSON command file entry does not contain required keys:" << entry; continue; } PathResolutionResult result = resolver.processOutput(entry[KEY_COMMAND].toString(), entry[KEY_DIRECTORY].toString()); CMakeFile ret; ret.includes = result.paths; + ret.frameworkDirectories = result.frameworkDirectories; ret.defines = result.defines; // NOTE: we use the canonical file path to prevent issues with symlinks in the path // leading to lookup failures const auto path = Path(QFileInfo(entry[KEY_FILE].toString()).canonicalFilePath()); data.files[path] = ret; } data.isValid = true; return data; } QVector importTestSuites(const Path &buildDir) { QVector ret; #pragma message("TODO use subdirs instead of this") foreach(const QFileInfo &info, QDir(buildDir.toLocalFile()).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { ret += importTestSuites(Path(buildDir, info.fileName())); } QFile file(buildDir.toLocalFile()+"/CTestTestfile.cmake"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return ret; } const QRegularExpression rx("add_test *\\((.+?) (.*?)\\) *$"); Q_ASSERT(rx.isValid()); for (; !file.atEnd();) { QByteArray line = file.readLine(); line.chop(1); const auto match = rx.match(QString::fromLocal8Bit(line)); if (match.hasMatch()) { Test test; QStringList args = KShell::splitArgs(match.captured(2)); test.name = match.captured(1); test.executable = Path(buildDir, args.takeFirst()); test.arguments = args; ret += test; } } return ret; } ImportData import(const Path& commandsFile, const Path &targetsFilePath, const QString &sourceDir, const KDevelop::Path &buildPath) { return ImportData { importCommands(commandsFile), CMake::enumerateTargets(targetsFilePath, sourceDir, buildPath), importTestSuites(buildPath) }; } } CMakeImportJob::CMakeImportJob(IProject* project, QObject* parent) : KJob(parent) , m_project(project) { connect(&m_futureWatcher, &QFutureWatcher::finished, this, &CMakeImportJob::importFinished); } CMakeImportJob::~CMakeImportJob() { } void CMakeImportJob::start() { auto commandsFile = CMake::commandsFile(project()); if (!QFileInfo::exists(commandsFile.toLocalFile())) { qCWarning(CMAKE) << "Could not import CMake project" << project()->path() << "('compile_commands.json' missing)"; emitResult(); return; } const Path currentBuildDir = CMake::currentBuildDir(m_project); Q_ASSERT (!currentBuildDir.isEmpty()); const Path targetsFilePath = CMake::targetDirectoriesFile(m_project); const QString sourceDir = m_project->path().toLocalFile(); auto future = QtConcurrent::run(import, commandsFile, targetsFilePath, sourceDir, currentBuildDir); m_futureWatcher.setFuture(future); } void CMakeImportJob::importFinished() { Q_ASSERT(m_project->thread() == QThread::currentThread()); Q_ASSERT(m_futureWatcher.isFinished()); auto future = m_futureWatcher.future(); auto data = future.result(); if (!data.json.isValid) { qCWarning(CMAKE) << "Could not import CMake project ('compile_commands.json' invalid)"; emitResult(); return; } m_data = data.json; m_targets = data.targets; m_testSuites = data.testSuites; qCDebug(CMAKE) << "Done importing, found" << m_data.files.count() << "entries for" << project()->path(); emitResult(); } IProject* CMakeImportJob::project() const { return m_project; } CMakeJsonData CMakeImportJob::jsonData() const { Q_ASSERT(!m_futureWatcher.isRunning()); return m_data; } #include "moc_cmakeimportjsonjob.cpp" diff --git a/projectmanagers/cmake/cmakemanager.cpp b/projectmanagers/cmake/cmakemanager.cpp index 5c15e2f270..717dabe93a 100644 --- a/projectmanagers/cmake/cmakemanager.cpp +++ b/projectmanagers/cmake/cmakemanager.cpp @@ -1,891 +1,896 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2013 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakemanager.h" #include "cmakeedit.h" #include "cmakeutils.h" #include "cmakeprojectdata.h" #include "duchain/cmakeparsejob.h" #include "cmakeimportjsonjob.h" #include "debug.h" #include "settings/cmakepreferences.h" #include #include "cmakecodecompletionmodel.h" #include "cmakenavigationwidget.h" #include "icmakedocumentation.h" #include "cmakemodelitems.h" #include "testing/ctestutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KDevelop::IProject*); using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportFactory, "kdevcmakemanager.json", registerPlugin(); ) const QString DIALOG_CAPTION = i18n("KDevelop - CMake Support"); CMakeManager::CMakeManager( QObject* parent, const QVariantList& ) : KDevelop::AbstractFileManagerPlugin( "kdevcmakemanager", parent ) , m_filter( new ProjectFilterManager( this ) ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBuildSystemManager ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::ILanguageSupport ) KDEV_USE_EXTENSION_INTERFACE( ICMakeManager) if (hasError()) { m_highlight = nullptr; return; } m_highlight = new KDevelop::CodeHighlighting(this); new CodeCompletion(this, new CMakeCodeCompletionModel(this), name()); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CMakeManager::projectClosing); connect(this, &KDevelop::AbstractFileManagerPlugin::folderAdded, this, &CMakeManager::folderAdded); // m_fileSystemChangeTimer = new QTimer(this); // m_fileSystemChangeTimer->setSingleShot(true); // m_fileSystemChangeTimer->setInterval(100); // connect(m_fileSystemChangeTimer,SIGNAL(timeout()),SLOT(filesystemBuffererTimeout())); } bool CMakeManager::hasError() const { return CMake::findExecutable().isEmpty(); } QString CMakeManager::errorDescription() const { return hasError() ? i18n("cmake is not installed") : QString(); } CMakeManager::~CMakeManager() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); } -bool CMakeManager::hasIncludesOrDefines(ProjectBaseItem* item) const +bool CMakeManager::hasBuildInfo(ProjectBaseItem* item) const { return m_projects[item->project()].jsonData.files.contains(item->path()); } Path CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const { // CMakeFolderItem *fi=dynamic_cast(item); // Path ret; // ProjectBaseItem* parent = fi ? fi->formerParent() : item->parent(); // if (parent) // ret=buildDirectory(parent); // else // ret=Path(CMake::currentBuildDir(item->project())); // // if(fi) // ret.addPath(fi->buildDir()); // return ret; return Path(CMake::currentBuildDir(item->project())); } KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project ) { CMake::checkForNeedingConfigure(project); return AbstractFileManagerPlugin::import(project); } KJob* CMakeManager::createImportJob(ProjectFolderItem* item) { auto project = item->project(); QList jobs; // create the JSON file if it doesn't exist auto commandsFile = CMake::commandsFile(project); if (!QFileInfo::exists(commandsFile.toLocalFile())) { qCDebug(CMAKE) << "couldn't find commands file:" << commandsFile << "- now trying to reconfigure"; jobs << builder()->configure(project); } // parse the JSON file CMakeImportJob* job = new CMakeImportJob(project, this); connect(job, &CMakeImportJob::result, this, &CMakeManager::importFinished); jobs << job; // generate the file system listing jobs << KDevelop::AbstractFileManagerPlugin::createImportJob(item); Q_ASSERT(!jobs.contains(nullptr)); ExecuteCompositeJob* composite = new ExecuteCompositeJob(this, jobs); // even if the cmake call failed, we want to load the project so that the project can be worked on composite->setAbortOnError(false); return composite; } // QList CMakeManager::parse(ProjectFolderItem*) // { return QList< ProjectFolderItem* >(); } // // QList CMakeManager::targets() const { QList ret; foreach(IProject* p, m_projects.keys()) { ret+=p->projectItem()->targetList(); } return ret; } CMakeFile CMakeManager::fileInformation(KDevelop::ProjectBaseItem* item) const { const CMakeJsonData & data = m_projects[item->project()].jsonData; QHash::const_iterator it = data.files.constFind(item->path()); if (it == data.files.constEnd()) { // if the item path contains a symlink, then we will not find it in the lookup table // as that only only stores canonicalized paths. Thus, we fallback to // to the canonicalized path and see if that brings up any matches const auto canonicalized = Path(QFileInfo(item->path().toLocalFile()).canonicalFilePath()); it = data.files.constFind(canonicalized); } if (it != data.files.constEnd()) { return *it; } else { // otherwise look for siblings and use the include paths of any we find const Path folder = item->folder() ? item->path() : item->path().parent(); for( it = data.files.constBegin(); it != data.files.constEnd(); ++it) { if (folder.isDirectParentOf(it.key())) { return *it; } } } // last-resort fallback: bubble up the parent chain, and keep looking for include paths if (auto parent = item->parent()) { return fileInformation(parent); } return {}; } Path::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).includes; } +Path::List CMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem *item) const +{ + return fileInformation(item).frameworkDirectories; +} + QHash CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const { return fileInformation(item).defines; } KDevelop::IProjectBuilder * CMakeManager::builder() const { IPlugin* i = core()->pluginController()->pluginForExtension( "org.kdevelop.IProjectBuilder", "KDevCMakeBuilder"); Q_ASSERT(i); KDevelop::IProjectBuilder* _builder = i->extension(); Q_ASSERT(_builder ); return _builder ; } bool CMakeManager::reload(KDevelop::ProjectFolderItem* folder) { qCDebug(CMAKE) << "reloading" << folder->path(); IProject* project = folder->project(); if (!project->isReady()) return false; KJob *job = createImportJob(folder); project->setReloadJob(job); ICore::self()->runController()->registerJob( job ); return true; } static void populateTargets(ProjectFolderItem* folder, const QHash& targets) { QStringList dirTargets = targets[folder->path()]; foreach (ProjectTargetItem* item, folder->targetList()) { if(!dirTargets.contains(item->text())) { delete item; } else { dirTargets.removeAll(item->text()); } } static QSet standardTargets = { QStringLiteral("edit_cache"), QStringLiteral("rebuild_cache"), QStringLiteral("list_install_components"), QStringLiteral("test"), //not really standard, but applicable for make and ninja QStringLiteral("install") }; foreach (const QString& name, dirTargets) { if (!name.endsWith(QLatin1String("_automoc")) && !standardTargets.contains(name) && !name.startsWith(QLatin1String("install/")) ) new CMakeTargetItem(folder, name); } foreach (ProjectFolderItem* children, folder->folderList()) { populateTargets(children, targets); } } void CMakeManager::importFinished(KJob* j) { CMakeImportJob* job = qobject_cast(j); Q_ASSERT(job); auto project = job->project(); if (job->error() != 0) { qCDebug(CMAKE) << "Import failed for project" << project->name() << job->errorText(); m_projects.remove(project); } qCDebug(CMAKE) << "Successfully imported project" << project->name(); CMakeProjectData data; data.watcher->addPath(CMake::commandsFile(project).toLocalFile()); data.watcher->addPath(CMake::targetDirectoriesFile(project).toLocalFile()); data.jsonData = job->jsonData(); data.targets = job->targets(); connect(data.watcher.data(), &QFileSystemWatcher::fileChanged, this, &CMakeManager::dirtyFile); connect(data.watcher.data(), &QFileSystemWatcher::directoryChanged, this, &CMakeManager::dirtyFile); m_projects[job->project()] = data; populateTargets(job->project()->projectItem(), job->targets()); CTestUtils::createTestSuites(job->testSuites(), project); } // void CMakeManager::deletedWatchedDirectory(IProject* p, const QUrl &dir) // { // if(p->folder().equals(dir, QUrl::CompareWithoutTrailingSlash)) { // ICore::self()->projectController()->closeProject(p); // } else { // if(dir.fileName()=="CMakeLists.txt") { // QList folders = p->foldersForUrl(dir.upUrl()); // foreach(ProjectFolderItem* folder, folders) // reload(folder); // } else { // qDeleteAll(p->itemsForUrl(dir)); // } // } // } // void CMakeManager::directoryChanged(const QString& dir) // { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // void CMakeManager::filesystemBuffererTimeout() // { // Q_FOREACH(const QString& file, m_fileSystemChangedBuffer) { // realDirectoryChanged(file); // } // m_fileSystemChangedBuffer.clear(); // } // void CMakeManager::realDirectoryChanged(const QString& dir) // { // QUrl path(dir); // IProject* p=ICore::self()->projectController()->findProjectForUrl(dir); // if(!p || !p->isReady()) { // if(p) { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // return; // } // // if(!QFile::exists(dir)) { // path.adjustPath(QUrl::AddTrailingSlash); // deletedWatchedDirectory(p, path); // } else // dirtyFile(dir); // } QList< KDevelop::ProjectTargetItem * > CMakeManager::targets(KDevelop::ProjectFolderItem * folder) const { return folder->targetList(); } QString CMakeManager::name() const { return languageName().str(); } IndexedString CMakeManager::languageName() { static IndexedString name("CMake"); return name; } KDevelop::ParseJob * CMakeManager::createParseJob(const IndexedString &url) { return new CMakeParseJob(url, this); } KDevelop::ICodeHighlighting* CMakeManager::codeHighlighting() const { return m_highlight; } // ContextMenuExtension CMakeManager::contextMenuExtension( KDevelop::Context* context ) // { // if( context->type() != KDevelop::Context::ProjectItemContext ) // return IPlugin::contextMenuExtension( context ); // // KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); // QList items = ctx->items(); // // if( items.isEmpty() ) // return IPlugin::contextMenuExtension( context ); // // m_clickedItems = items; // ContextMenuExtension menuExt; // if(items.count()==1 && dynamic_cast(items.first())) // { // QAction * action = new QAction( i18n( "Jump to Target Definition" ), this ); // connect( action, SIGNAL(triggered()), this, SLOT(jumpToDeclaration()) ); // menuExt.addAction( ContextMenuExtension::ProjectGroup, action ); // } // // return menuExt; // } // // void CMakeManager::jumpToDeclaration() // { // DUChainAttatched* du=dynamic_cast(m_clickedItems.first()); // if(du) // { // KTextEditor::Cursor c; // QUrl url; // { // KDevelop::DUChainReadLocker lock; // Declaration* decl = du->declaration().data(); // if(!decl) // return; // c = decl->rangeInCurrentRevision().start(); // url = decl->url().toUrl(); // } // // ICore::self()->documentController()->openDocument(url, c); // } // } // // // TODO: Port to Path API // bool CMakeManager::moveFilesAndFolders(const QList< ProjectBaseItem* > &items, ProjectFolderItem* toFolder) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Move files and folders within CMakeLists as follows:")); // // bool cmakeSuccessful = true; // CMakeFolderItem *nearestCMakeFolderItem = nearestCMakeFolder(toFolder); // IProject* project=toFolder->project(); // // QList movedUrls; // QList oldUrls; // foreach(ProjectBaseItem *movedItem, items) // { // QList dirtyItems = cmakeListedItemsAffectedByUrlChange(project, movedItem->url()); // QUrl movedItemNewUrl = toFolder->url(); // movedItemNewUrl.addPath(movedItem->baseName()); // if (movedItem->folder()) // movedItemNewUrl.adjustPath(QUrl::AddTrailingSlash); // foreach(ProjectBaseItem* dirtyItem, dirtyItems) // { // QUrl dirtyItemNewUrl = afterMoveUrl(dirtyItem->url(), movedItem->url(), movedItemNewUrl); // if (CMakeFolderItem* folder = dynamic_cast(dirtyItem)) // { // cmakeSuccessful &= changesWidgetRemoveCMakeFolder(folder, &changesWidget); // cmakeSuccessful &= changesWidgetAddFolder(dirtyItemNewUrl, nearestCMakeFolderItem, &changesWidget); // } // else if (dirtyItem->parent()->target()) // { // cmakeSuccessful &= changesWidgetMoveTargetFile(dirtyItem, dirtyItemNewUrl, &changesWidget); // } // } // // oldUrls += movedItem->url(); // movedUrls += movedItemNewUrl; // } // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort move?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // QList::const_iterator it1=oldUrls.constBegin(), it1End=oldUrls.constEnd(); // QList::const_iterator it2=movedUrls.constBegin(); // Q_ASSERT(oldUrls.size()==movedUrls.size()); // for(; it1!=it1End; ++it1, ++it2) // { // if (!KDevelop::renameUrl(project, *it1, *it2)) // return false; // // QList renamedItems = project->itemsForUrl(*it2); // bool dir = QFileInfo(it2->toLocalFile()).isDir(); // foreach(ProjectBaseItem* item, renamedItems) { // if(dir) // emit folderRenamed(Path(*it1), item->folder()); // else // emit fileRenamed(Path(*it1), item->file()); // } // } // // return true; // } // // bool CMakeManager::copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* toFolder) // { // IProject* project = toFolder->project(); // foreach(const Path& path, items) { // if (!KDevelop::copyUrl(project, path.toUrl(), toFolder->url())) // return false; // } // // return true; // } // // bool CMakeManager::removeFilesAndFolders(const QList &items) // { // using namespace CMakeEdit; // // IProject* p = 0; // QList urls; // foreach(ProjectBaseItem* item, items) // { // Q_ASSERT(item->folder() || item->file()); // // urls += item->url(); // if(!p) // p = item->project(); // } // // //First do CMakeLists changes // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Remove files and folders from CMakeLists as follows:")); // // bool cmakeSuccessful = changesWidgetRemoveItems(cmakeListedItemsAffectedByItemsChanged(items).toSet(), &changesWidget); // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort deletion?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = true; // //Then delete the files/folders // foreach(const QUrl& file, urls) // { // ret &= KDevelop::removeUrl(p, file, QDir(file.toLocalFile()).exists()); // } // // return ret; // } bool CMakeManager::removeFilesFromTargets(const QList &/*files*/) { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify project targets as follows:")); // // if (!files.isEmpty() && // changesWidgetRemoveFilesFromTargets(files, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges()) { // return true; // } return false; } // ProjectFolderItem* CMakeManager::addFolder(const Path& folder, ProjectFolderItem* parent) // { // using namespace CMakeEdit; // // CMakeFolderItem *cmakeParent = nearestCMakeFolder(parent); // if(!cmakeParent) // return 0; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Create folder '%1':", folder.lastPathSegment())); // // ///FIXME: use path in changes widget // changesWidgetAddFolder(folder.toUrl(), cmakeParent, &changesWidget); // // if(changesWidget.exec() && changesWidget.applyAllChanges()) // { // if(KDevelop::createFolder(folder.toUrl())) { //If saved we create the folder then the CMakeLists.txt file // Path newCMakeLists(folder, "CMakeLists.txt"); // KDevelop::createFile( newCMakeLists.toUrl() ); // } else // KMessageBox::error(0, i18n("Could not save the change."), // DIALOG_CAPTION); // } // // return 0; // } // // KDevelop::ProjectFileItem* CMakeManager::addFile( const Path& file, KDevelop::ProjectFolderItem* parent) // { // KDevelop::ProjectFileItem* created = 0; // if ( KDevelop::createFile(file.toUrl()) ) { // QList< ProjectFileItem* > files = parent->project()->filesForPath(IndexedString(file.pathOrUrl())); // if(!files.isEmpty()) // created = files.first(); // else // created = new KDevelop::ProjectFileItem( parent->project(), file, parent ); // } // return created; // } bool CMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &/*_files*/, ProjectTargetItem* /*target*/) { return false; // using namespace CMakeEdit; // // const QSet headerExt = QSet() << ".h" << ".hpp" << ".hxx"; // QList< ProjectFileItem* > files = _files; // for (int i = files.count() - 1; i >= 0; --i) // { // QString fileName = files[i]->fileName(); // QString fileExt = fileName.mid(fileName.lastIndexOf('.')); // QList sameUrlItems = files[i]->project()->itemsForUrl(files[i]->url()); // if (headerExt.contains(fileExt)) // files.removeAt(i); // else foreach(ProjectBaseItem* item, sameUrlItems) // { // if (item->parent() == target) // { // files.removeAt(i); // break; // } // } // } // // if(files.isEmpty()) // return true; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify target '%1' as follows:", target->baseName())); // // bool success = changesWidgetAddFilesToTarget(files, target, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges(); // // if(!success) // KMessageBox::error(0, i18n("CMakeLists changes failed."), DIALOG_CAPTION); // // return success; } // bool CMakeManager::renameFileOrFolder(ProjectBaseItem *item, const Path &newPath) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Rename '%1' to '%2':", item->text(), // newPath.lastPathSegment())); // // bool cmakeSuccessful = true, changedCMakeLists=false; // IProject* project=item->project(); // const Path oldPath=item->path(); // QUrl oldUrl=oldPath.toUrl(); // if (item->file()) // { // QList targetFiles = cmakeListedItemsAffectedByUrlChange(project, oldUrl); // foreach(ProjectBaseItem* targetFile, targetFiles) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetMoveTargetFile(targetFile, newPath.toUrl(), &changesWidget); // } // else if (CMakeFolderItem *folder = dynamic_cast(item)) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetRenameFolder(folder, newPath.toUrl(), &changesWidget); // // item->setPath(newPath); // if (changesWidget.hasDocuments() && cmakeSuccessful) { // changedCMakeLists = changesWidget.exec() && changesWidget.applyAllChanges(); // cmakeSuccessful &= changedCMakeLists; // } // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort rename?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = KDevelop::renameUrl(project, oldUrl, newPath.toUrl()); // if(!ret) { // item->setPath(oldPath); // } // return ret; // } // // bool CMakeManager::renameFile(ProjectFileItem *item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } // // bool CMakeManager::renameFolder(ProjectFolderItem* item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } QWidget* CMakeManager::specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) { KDevelop::TopDUContextPointer top= TopDUContextPointer(KDevelop::DUChain::self()->chainForDocument(url)); Declaration *decl=0; if(top) { int useAt=top->findUseAt(top->transformToLocalRevision(position)); if(useAt>=0) { Use u=top->uses()[useAt]; decl=u.usedDeclaration(top->topContext()); } } CMakeNavigationWidget* doc=0; if(decl) { doc=new CMakeNavigationWidget(top, decl); } else { const IDocument* d=ICore::self()->documentController()->documentForUrl(url); const KTextEditor::Document* e=d->textDocument(); KTextEditor::Cursor start=position, end=position, step(0,1); for(QChar i=e->characterAt(start); i.isLetter() || i=='_'; i=e->characterAt(start-=step)) {} start+=step; for(QChar i=e->characterAt(end); i.isLetter() || i=='_'; i=e->characterAt(end+=step)) {} QString id=e->text(KTextEditor::Range(start, end)); ICMakeDocumentation* docu=CMake::cmakeDocumentation(); if( docu ) { IDocumentation::Ptr desc=docu->description(id, url); if(desc) { doc=new CMakeNavigationWidget(top, desc); } } } return doc; } QPair CMakeManager::cacheValue(KDevelop::IProject* /*project*/, const QString& /*id*/) const { return QPair(); } // { // QPair ret; // if(project==0 && !m_projectsData.isEmpty()) // { // project=m_projectsData.keys().first(); // } // // // qCDebug(CMAKE) << "cache value " << id << project << (m_projectsData.contains(project) && m_projectsData[project].cache.contains(id)); // CMakeProjectData* data = m_projectsData[project]; // if(data && data->cache.contains(id)) // { // const CacheEntry& e=data->cache.value(id); // ret.first=e.value; // ret.second=e.doc; // } // return ret; // }Add // void CMakeManager::projectClosing(IProject* p) { m_projects.remove(p); // delete m_projectsData.take(p); // delete m_watchers.take(p); // // m_filter->remove(p); // // qCDebug(CMAKE) << "Project closed" << p; } // // QStringList CMakeManager::processGeneratorExpression(const QStringList& expr, IProject* project, ProjectTargetItem* target) const // { // QStringList ret; // const CMakeProjectData* data = m_projectsData[project]; // GenerationExpressionSolver exec(data->properties, data->targetAlias); // if(target) // exec.setTargetName(target->text()); // // exec.defineVariable("INSTALL_PREFIX", data->vm.value("CMAKE_INSTALL_PREFIX").join(QString())); // for(QStringList::const_iterator it = expr.constBegin(), itEnd = expr.constEnd(); it!=itEnd; ++it) { // QStringList val = exec.run(*it).split(';'); // ret += val; // } // return ret; // } /* void CMakeManager::addPending(const Path& path, CMakeFolderItem* folder) { m_pending.insert(path, folder); } CMakeFolderItem* CMakeManager::takePending(const Path& path) { return m_pending.take(path); } void CMakeManager::addWatcher(IProject* p, const QString& path) { if (QFileSystemWatcher* watcher = m_watchers.value(p)) { watcher->addPath(path); } else { qWarning() << "Could not find a watcher for project" << p << p->name() << ", path " << path; Q_ASSERT(false); } }*/ // CMakeProjectData CMakeManager::projectData(IProject* project) // { // Q_ASSERT(QThread::currentThread() == project->thread()); // CMakeProjectData* data = m_projectsData[project]; // if(!data) { // data = new CMakeProjectData; // m_projectsData[project] = data; // } // return *data; // } ProjectFilterManager* CMakeManager::filterManager() const { return m_filter; } void CMakeManager::dirtyFile(const QString& path) { qCDebug(CMAKE) << "dirty!" << path; //we initialize again hte project that sent the signal for(QHash::const_iterator it = m_projects.constBegin(), itEnd = m_projects.constEnd(); it!=itEnd; ++it) { if(it->watcher == sender()) { reload(it.key()->projectItem()); break; } } } void CMakeManager::folderAdded(KDevelop::ProjectFolderItem* folder) { populateTargets(folder, m_projects[folder->project()].targets); } ProjectFolderItem* CMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO: when we have data about targets, use folders with targets or similar if (QFile::exists(path.toLocalFile()+"/CMakeLists.txt")) return new KDevelop::ProjectBuildFolderItem( project, path, parent ); else return KDevelop::AbstractFileManagerPlugin::createFolderItem(project, path, parent); } int CMakeManager::perProjectConfigPages() const { return 1; } ConfigPage* CMakeManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CMakePreferences(this, options, parent); } return nullptr; } #include "cmakemanager.moc" diff --git a/projectmanagers/cmake/cmakemanager.h b/projectmanagers/cmake/cmakemanager.h index 3096b7dc6a..0a6571e863 100644 --- a/projectmanagers/cmake/cmakemanager.h +++ b/projectmanagers/cmake/cmakemanager.h @@ -1,170 +1,171 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2009 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEMANAGER_H #define CMAKEMANAGER_H #include #include #include #include #include #include #include #include #include #include #include "cmakeprojectdata.h" #include "icmakemanager.h" #include "cmakeprojectvisitor.h" class WaitAllJobs; class CMakeCommitChangesJob; struct CMakeProjectData; class QStandardItem; class QDir; class QObject; class CMakeHighlighting; class CMakeDocumentation; namespace KDevelop { class IProject; class IProjectBuilder; class ICodeHighlighting; class ProjectFolderItem; class ProjectBaseItem; class ProjectFileItem; class ProjectTargetItem; class ProjectFilterManager; class IProjectFilter; class ParseJob; class ContextMenuExtension; class Context; } class CMakeFolderItem; class CMakeManager : public KDevelop::AbstractFileManagerPlugin , public KDevelop::IBuildSystemManager , public KDevelop::ILanguageSupport , public ICMakeManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) Q_INTERFACES( KDevelop::IProjectFileManager ) Q_INTERFACES( KDevelop::ILanguageSupport ) Q_INTERFACES( ICMakeManager ) public: explicit CMakeManager( QObject* parent = 0, const QVariantList& args = QVariantList() ); ~CMakeManager() override; bool hasError() const override; QString errorDescription() const override; Features features() const override { return Features(Folders | Targets | Files ); } KDevelop::IProjectBuilder* builder() const override; - bool hasIncludesOrDefines(KDevelop::ProjectBaseItem*) const override; + bool hasBuildInfo(KDevelop::ProjectBaseItem*) const override; KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem *) const override; + KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem *item) const override; QHash defines(KDevelop::ProjectBaseItem *) const override; KDevelop::ProjectTargetItem* createTarget( const QString&, KDevelop::ProjectFolderItem* ) override { return 0; } virtual QList targets() const; QList targets(KDevelop::ProjectFolderItem* folder) const override; // virtual KDevelop::ProjectFolderItem* addFolder( const KDevelop::Path& folder, KDevelop::ProjectFolderItem* parent ); // virtual KDevelop::ProjectFileItem* addFile( const KDevelop::Path&, KDevelop::ProjectFolderItem* ); bool addFilesToTarget( const QList &files, KDevelop::ProjectTargetItem* target) override; bool removeTarget( KDevelop::ProjectTargetItem* ) override { return false; } bool removeFilesFromTargets( const QList &files ) override; // virtual bool removeFilesAndFolders( const QList &items); // // virtual bool renameFile(KDevelop::ProjectFileItem*, const KDevelop::Path&); // virtual bool renameFolder(KDevelop::ProjectFolderItem*, const KDevelop::Path&); // virtual bool moveFilesAndFolders(const QList< KDevelop::ProjectBaseItem* > &items, KDevelop::ProjectFolderItem *newParent); // virtual bool copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* newParent); // // virtual QList parse( KDevelop::ProjectFolderItem* dom ); KDevelop::ProjectFolderItem* import( KDevelop::IProject *project ) override; KJob* createImportJob(KDevelop::ProjectFolderItem* item) override; // bool reload(KDevelop::ProjectFolderItem*) override; // // virtual KDevelop::ContextMenuExtension contextMenuExtension( KDevelop::Context* context ); KDevelop::ProjectFolderItem* createFolderItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = 0) override; QPair cacheValue(KDevelop::IProject* project, const QString& id) const override; //LanguageSupport QString name() const override; KDevelop::ParseJob *createParseJob(const KDevelop::IndexedString &url) override; KDevelop::ICodeHighlighting* codeHighlighting() const override; QWidget* specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) override; // void addPending(const KDevelop::Path& path, CMakeFolderItem* folder); // CMakeFolderItem* takePending(const KDevelop::Path& path); // void addWatcher(KDevelop::IProject* p, const QString& path); // CMakeProjectData projectData(KDevelop::IProject* project); KDevelop::ProjectFilterManager* filterManager() const; static KDevelop::IndexedString languageName(); int perProjectConfigPages() const override; KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; signals: void folderRenamed(const KDevelop::Path& oldFolder, KDevelop::ProjectFolderItem* newFolder); void fileRenamed(const KDevelop::Path& oldFile, KDevelop::ProjectFileItem* newFile); private slots: // void dirtyFile(const QString& file); // // void jumpToDeclaration(); void projectClosing(KDevelop::IProject*); void dirtyFile(const QString& file); // // void directoryChanged(const QString& dir); // void filesystemBuffererTimeout(); void importFinished(KJob* job); private: CMakeFile fileInformation(KDevelop::ProjectBaseItem* item) const; void folderAdded(KDevelop::ProjectFolderItem* folder); QHash m_projects; KDevelop::ProjectFilterManager* m_filter; KDevelop::ICodeHighlighting* m_highlight; }; #endif diff --git a/projectmanagers/cmake/cmakeprojectdata.h b/projectmanagers/cmake/cmakeprojectdata.h index 60e8773d11..b85d6715b8 100644 --- a/projectmanagers/cmake/cmakeprojectdata.h +++ b/projectmanagers/cmake/cmakeprojectdata.h @@ -1,65 +1,66 @@ /* KDevelop CMake Support * * Copyright 2013 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEPROJECTDATA_H #define CMAKEPROJECTDATA_H #include #include #include #include "cmaketypes.h" #include /** * Represents any file in a cmake project that has been added * to the project. * * Contains the required information to compile it properly */ struct CMakeFile { KDevelop::Path::List includes; + KDevelop::Path::List frameworkDirectories; QHash defines; }; inline QDebug &operator<<(QDebug debug, const CMakeFile& file) { - debug << "CMakeFile(-I" << file.includes << ", -D" << file.defines << ")"; + debug << "CMakeFile(-I" << file.includes << ", -F" << file.frameworkDirectories << ", -D" << file.defines << ")"; return debug.maybeSpace(); } struct CMakeJsonData { QHash files; bool isValid = false; }; struct CMakeProjectData { CMakeProjectData() : watcher(new QFileSystemWatcher) {} ~CMakeProjectData() {} CMakeProperties properties; CacheValues cache; CMakeJsonData jsonData; QHash targets; QSharedPointer watcher; }; #endif diff --git a/projectmanagers/custom-buildsystem/custombuildsystemplugin.cpp b/projectmanagers/custom-buildsystem/custombuildsystemplugin.cpp index b04647ee1a..a445ce8024 100644 --- a/projectmanagers/custom-buildsystem/custombuildsystemplugin.cpp +++ b/projectmanagers/custom-buildsystem/custombuildsystemplugin.cpp @@ -1,196 +1,201 @@ /************************************************************************ * KDevelop4 Custom Buildsystem Support * * * * 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 "custombuildsystemplugin.h" #include #include #include #include #include #include #include #include #include "configconstants.h" #include "kcm_custombuildsystem.h" #include "config.h" #include "custombuildjob.h" using KDevelop::ProjectTargetItem; using KDevelop::ProjectFolderItem; using KDevelop::ProjectBuildFolderItem; using KDevelop::ProjectBaseItem; using KDevelop::ProjectFileItem; using KDevelop::IPlugin; using KDevelop::ICore; using KDevelop::IOutputView; using KDevelop::IProjectFileManager; using KDevelop::IProjectBuilder; using KDevelop::IProject; using KDevelop::Path; K_PLUGIN_FACTORY_WITH_JSON(CustomBuildSystemFactory, "kdevcustombuildsystem.json", registerPlugin(); ) CustomBuildSystem::CustomBuildSystem( QObject *parent, const QVariantList & ) : AbstractFileManagerPlugin( "kdevcustombuildsystem", parent ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectBuilder ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBuildSystemManager ) } CustomBuildSystem::~CustomBuildSystem() { } bool CustomBuildSystem::addFilesToTarget( const QList&, ProjectTargetItem* ) { return false; } -bool CustomBuildSystem::hasIncludesOrDefines( ProjectBaseItem* ) const +bool CustomBuildSystem::hasBuildInfo( ProjectBaseItem* ) const { return false; } KJob* CustomBuildSystem::build( ProjectBaseItem* dom ) { return new CustomBuildJob( this, dom, CustomBuildSystemTool::Build ); } Path CustomBuildSystem::buildDirectory( ProjectBaseItem* item ) const { Path p; if( item->folder() ) { p = item->path(); } else { ProjectBaseItem* parent = item; while( !parent->folder() ) { parent = parent->parent(); } p = parent->path(); } const QString relative = item->project()->path().relativePath(p); KConfigGroup grp = configuration( item->project() ); if(!grp.isValid()) { return Path(); } Path builddir(grp.readEntry( ConfigConstants::buildDirKey, QUrl() )); if(!builddir.isValid() ) // set builddir to default if project contains a buildDirKey that does not have a value { builddir = item->project()->path(); } builddir.addPath( relative ); return builddir; } IProjectBuilder* CustomBuildSystem::builder() const { return const_cast(dynamic_cast(this)); } KJob* CustomBuildSystem::clean( ProjectBaseItem* dom ) { return new CustomBuildJob( this, dom, CustomBuildSystemTool::Clean ); } KJob* CustomBuildSystem::configure( IProject* project ) { return new CustomBuildJob( this, project->projectItem(), CustomBuildSystemTool::Configure ); } ProjectTargetItem* CustomBuildSystem::createTarget( const QString&, ProjectFolderItem* ) { return 0; } QHash CustomBuildSystem::defines( ProjectBaseItem* ) const { return {}; } IProjectFileManager::Features CustomBuildSystem::features() const { return IProjectFileManager::Files | IProjectFileManager::Folders; } ProjectFolderItem* CustomBuildSystem::createFolderItem( IProject* project, const Path& path, ProjectBaseItem* parent ) { return new ProjectBuildFolderItem( project, path, parent ); } Path::List CustomBuildSystem::includeDirectories( ProjectBaseItem* ) const { return {}; } +Path::List CustomBuildSystem::frameworkDirectories( ProjectBaseItem* ) const +{ + return {}; +} + KJob* CustomBuildSystem::install( KDevelop::ProjectBaseItem* item, const QUrl &installPrefix ) { auto job = new CustomBuildJob( this, item, CustomBuildSystemTool::Install ); job->setInstallPrefix(installPrefix); return job; } KJob* CustomBuildSystem::prune( IProject* project ) { return new CustomBuildJob( this, project->projectItem(), CustomBuildSystemTool::Prune ); } bool CustomBuildSystem::removeFilesFromTargets( const QList& ) { return false; } bool CustomBuildSystem::removeTarget( ProjectTargetItem* ) { return false; } QList CustomBuildSystem::targets( ProjectFolderItem* ) const { return QList(); } KConfigGroup CustomBuildSystem::configuration( IProject* project ) const { KConfigGroup grp = project->projectConfiguration()->group( ConfigConstants::customBuildSystemGroup ); if(grp.isValid() && grp.hasKey(ConfigConstants::currentConfigKey)) return grp.group( grp.readEntry( ConfigConstants::currentConfigKey ) ); else return KConfigGroup(); } int CustomBuildSystem::perProjectConfigPages() const { return 1; } KDevelop::ConfigPage* CustomBuildSystem::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CustomBuildSystemKCModule(this, options, parent); } return nullptr; } #include "custombuildsystemplugin.moc" diff --git a/projectmanagers/custom-buildsystem/custombuildsystemplugin.h b/projectmanagers/custom-buildsystem/custombuildsystemplugin.h index 372b2830b9..832f1c0886 100644 --- a/projectmanagers/custom-buildsystem/custombuildsystemplugin.h +++ b/projectmanagers/custom-buildsystem/custombuildsystemplugin.h @@ -1,91 +1,92 @@ /************************************************************************ * KDevelop4 Custom Buildsystem Support * * * * 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 CUSTOMBUILDSYSTEMPLUGIN_H #define CUSTOMBUILDSYSTEMPLUGIN_H #include #include #include #include class KConfigGroup; class KDialogBase; namespace KDevelop { class ProjectBaseItem; class IOutputView; class IProject; } class CustomBuildSystem : public KDevelop::AbstractFileManagerPlugin, public KDevelop::IProjectBuilder, public KDevelop::IBuildSystemManager { Q_OBJECT Q_INTERFACES( KDevelop::IProjectBuilder ) Q_INTERFACES( KDevelop::IProjectFileManager ) Q_INTERFACES( KDevelop::IBuildSystemManager ) public: explicit CustomBuildSystem( QObject *parent = 0, const QVariantList &args = QVariantList() ); virtual ~CustomBuildSystem(); // ProjectBuilder API KJob* build( KDevelop::ProjectBaseItem* dom ) override; KJob* clean( KDevelop::ProjectBaseItem* dom ) override; KJob* prune( KDevelop::IProject* ) override; /// @p installPrefix will be passed as DESTDIR environment variable KJob* install( KDevelop::ProjectBaseItem* item, const QUrl &installPrefix ) override; KJob* configure( KDevelop::IProject* ) override; signals: void built( KDevelop::ProjectBaseItem *dom ); void installed( KDevelop::ProjectBaseItem* ); void cleaned( KDevelop::ProjectBaseItem* ); void failed( KDevelop::ProjectBaseItem *dom ); void configured( KDevelop::IProject* ); void pruned( KDevelop::IProject* ); // AbstractFileManagerPlugin API public: Features features() const override; virtual KDevelop::ProjectFolderItem* createFolderItem( KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = 0 ) override; // BuildSystemManager API public: bool addFilesToTarget( const QList& file, KDevelop::ProjectTargetItem* parent ) override; - bool hasIncludesOrDefines( KDevelop::ProjectBaseItem* ) const override; + bool hasBuildInfo( KDevelop::ProjectBaseItem* ) const override; KDevelop::Path buildDirectory( KDevelop::ProjectBaseItem* ) const override; IProjectBuilder* builder() const override; KDevelop::ProjectTargetItem* createTarget( const QString& target, KDevelop::ProjectFolderItem* parent ) override; QHash defines( KDevelop::ProjectBaseItem* ) const override; KDevelop::Path::List includeDirectories( KDevelop::ProjectBaseItem* ) const override; + KDevelop::Path::List frameworkDirectories( KDevelop::ProjectBaseItem* ) const override; bool removeFilesFromTargets( const QList& ) override; bool removeTarget( KDevelop::ProjectTargetItem* target ) override; QList targets( KDevelop::ProjectFolderItem* ) const override; KConfigGroup configuration( KDevelop::IProject* ) const; KConfigGroup findMatchingPathGroup( const KConfigGroup& cfg, KDevelop::ProjectBaseItem* item ) const; // IPlugin API public: virtual int perProjectConfigPages() const override; virtual KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; }; #endif diff --git a/projectmanagers/custommake/custommakemanager.cpp b/projectmanagers/custommake/custommakemanager.cpp index e2ce943703..682099c64f 100644 --- a/projectmanagers/custommake/custommakemanager.cpp +++ b/projectmanagers/custommake/custommakemanager.cpp @@ -1,315 +1,334 @@ /* KDevelop Custom Makefile Support * * Copyright 2007 Dukju Ahn * Copyright 2011 Milian Wolff * * 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. */ #include "custommakemanager.h" #include "custommakemodelitems.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(CUSTOMMAKE) Q_LOGGING_CATEGORY(CUSTOMMAKE, "kdevelop.projectmanagers.custommake") #include #include #include #include "../../languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h" #include "makefileresolver/makefileresolver.h" using namespace KDevelop; class CustomMakeProvider : public IDefinesAndIncludesManager::BackgroundProvider { public: CustomMakeProvider(CustomMakeManager* manager) : m_customMakeManager(manager) , m_resolver(new MakeFileResolver()) {} // NOTE: Fixes build failures for GCC versions <4.8. // cf. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53613 ~CustomMakeProvider() Q_DECL_NOEXCEPT override; QHash< QString, QString > definesInBackground(const QString&) const override { return {}; } - Path::List includesInBackground(const QString& path) const override + Path::List resolvePathInBackground(const QString& path, const bool isFrameworks) const { { QReadLocker lock(&m_lock); bool inProject = std::any_of(m_customMakeManager->m_projectPaths.constBegin(), m_customMakeManager->m_projectPaths.constEnd(), [&path](const QString& projectPath) { return path.startsWith(projectPath); } ); if (!inProject) { return {}; } } - return m_resolver->resolveIncludePath(path).paths; + if (isFrameworks) { + return m_resolver->resolveIncludePath(path).frameworkDirectories; + } else { + return m_resolver->resolveIncludePath(path).paths; + } + } + + Path::List includesInBackground(const QString& path) const override + { + return resolvePathInBackground(path, false); + } + + Path::List frameworkDirectoriesInBackground(const QString& path) const override + { + return resolvePathInBackground(path, true); } IDefinesAndIncludesManager::Type type() const override { return IDefinesAndIncludesManager::ProjectSpecific; } CustomMakeManager* m_customMakeManager; QScopedPointer m_resolver; mutable QReadWriteLock m_lock; }; // NOTE: Fixes build failures for GCC versions <4.8. // See above. CustomMakeProvider::~CustomMakeProvider() Q_DECL_NOEXCEPT = default; K_PLUGIN_FACTORY_WITH_JSON(CustomMakeSupportFactory, "kdevcustommakemanager.json", registerPlugin(); ) CustomMakeManager::CustomMakeManager( QObject *parent, const QVariantList& args ) : KDevelop::AbstractFileManagerPlugin( "kdevcustommakemanager", parent ) , m_builder( nullptr ) , m_provider(new CustomMakeProvider(this)) { Q_UNUSED(args) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBuildSystemManager ) setXMLFile( "kdevcustommakemanager.rc" ); // TODO use CustomMakeBuilder IPlugin* i = core()->pluginController()->pluginForExtension( "org.kdevelop.IMakeBuilder" ); Q_ASSERT(i); m_builder = i->extension(); Q_ASSERT(m_builder); connect(this, &CustomMakeManager::reloadedFileItem, this, &CustomMakeManager::reloadMakefile); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CustomMakeManager::projectClosing); IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data()); } CustomMakeManager::~CustomMakeManager() { } IProjectBuilder* CustomMakeManager::builder() const { Q_ASSERT(m_builder); return m_builder; } Path::List CustomMakeManager::includeDirectories(KDevelop::ProjectBaseItem*) const { return Path::List(); } +Path::List CustomMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem*) const +{ + return Path::List(); +} + QHash CustomMakeManager::defines(KDevelop::ProjectBaseItem*) const { return QHash(); } ProjectTargetItem* CustomMakeManager::createTarget(const QString& target, KDevelop::ProjectFolderItem *parent) { Q_UNUSED(target) Q_UNUSED(parent) return NULL; } bool CustomMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &files, ProjectTargetItem* parent) { Q_UNUSED( files ) Q_UNUSED( parent ) return false; } bool CustomMakeManager::removeTarget(KDevelop::ProjectTargetItem *target) { Q_UNUSED( target ) return false; } bool CustomMakeManager::removeFilesFromTargets(const QList< ProjectFileItem* > &targetFiles) { Q_UNUSED( targetFiles ) return false; } -bool CustomMakeManager::hasIncludesOrDefines(KDevelop::ProjectBaseItem* item) const +bool CustomMakeManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const { Q_UNUSED(item); return false; } Path CustomMakeManager::buildDirectory(KDevelop::ProjectBaseItem* item) const { ProjectFolderItem *fi=dynamic_cast(item); for(; !fi && item; ) { item=item->parent(); fi=dynamic_cast(item); } if(!fi) { return item->project()->path(); } return fi->path(); } QList CustomMakeManager::targets(KDevelop::ProjectFolderItem*) const { QList ret; return ret; } static bool isMakefile(const QString& fileName) { return ( fileName == QLatin1String("Makefile") || fileName == QLatin1String("makefile") || fileName == QLatin1String("GNUmakefile") || fileName == QLatin1String("BSDmakefile") ); } void CustomMakeManager::createTargetItems(IProject* project, const Path& path, ProjectBaseItem* parent) { Q_ASSERT(isMakefile(path.lastPathSegment())); foreach(const QString& target, parseCustomMakeFile( path )) { if (!isValid(Path(parent->path(), target), false, project)) { continue; } new CustomMakeTargetItem( project, target, parent ); } } ProjectFileItem* CustomMakeManager::createFileItem(IProject* project, const Path& path, ProjectBaseItem* parent) { ProjectFileItem* item = new ProjectFileItem(project, path, parent); if (isMakefile(path.lastPathSegment())){ createTargetItems(project, path, parent); } return item; } void CustomMakeManager::reloadMakefile(ProjectFileItem* file) { if( !isMakefile(file->path().lastPathSegment())){ return; } ProjectBaseItem* parent = file->parent(); // remove the items that are Makefile targets foreach(ProjectBaseItem* item, parent->children()){ if (item->target()){ delete item; } } // Recreate the targets. createTargetItems(parent->project(), file->path(), parent); } ProjectFolderItem* CustomMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO more faster algorithm. should determine whether this directory // contains makefile or not. return new KDevelop::ProjectBuildFolderItem( project, path, parent ); } KDevelop::ProjectFolderItem* CustomMakeManager::import(KDevelop::IProject *project) { if( project->path().isRemote() ) { //FIXME turn this into a real warning qCWarning(CUSTOMMAKE) << project->path() << "not a local file. Custom make support doesn't handle remote projects"; return 0; } { QWriteLocker lock(&m_provider->m_lock); m_projectPaths.insert(project->path().path()); } return AbstractFileManagerPlugin::import( project ); } ///////////////////////////////////////////////////////////////////////////// // private slots ///TODO: move to background thread, probably best would be to use a proper ParseJob QStringList CustomMakeManager::parseCustomMakeFile( const Path &makefile ) { if( !makefile.isValid() ) return QStringList(); QStringList ret; // the list of targets QFile f( makefile.toLocalFile() ); if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) ) { qCDebug(CUSTOMMAKE) << "could not open" << makefile; return ret; } QRegExp targetRe( "^ *([^\\t$.#]\\S+) *:?:(?!=).*$" ); targetRe.setMinimal( true ); QString str; QTextStream stream( &f ); while ( !stream.atEnd() ) { str = stream.readLine(); if ( targetRe.indexIn( str ) != -1 ) { QString tmpTarget = targetRe.cap( 1 ).simplified(); if ( ! ret.contains( tmpTarget ) ) ret.append( tmpTarget ); } } f.close(); return ret; } void CustomMakeManager::projectClosing(IProject* project) { QWriteLocker lock(&m_provider->m_lock); m_projectPaths.remove(project->path().path()); } void CustomMakeManager::unload() { IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data()); } #include "custommakemanager.moc" diff --git a/projectmanagers/custommake/custommakemanager.h b/projectmanagers/custommake/custommakemanager.h index 33c2997db5..1648b0111f 100644 --- a/projectmanagers/custommake/custommakemanager.h +++ b/projectmanagers/custommake/custommakemanager.h @@ -1,139 +1,144 @@ /* KDevelop Custom Makefile Support * * Copyright 2007 Dukju Ahn * Copyright 2011 Milian Wolff * * 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. */ #ifndef CUSTOMMAKEMANAGER_H #define CUSTOMMAKEMANAGER_H #include #include #include #include class IMakeBuilder; class CustomMakeProvider; class CustomMakeManager : public KDevelop::AbstractFileManagerPlugin, public KDevelop::IBuildSystemManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) public: explicit CustomMakeManager( QObject *parent = NULL, const QVariantList& args = QVariantList() ); ~CustomMakeManager() override; Features features() const override { return Features(Folders | Targets | Files); } KDevelop::ProjectFolderItem* import(KDevelop::IProject* project) override; /** * Provide access to the builder */ KDevelop::IProjectBuilder* builder() const override; /** * Provide a list of include directories. */ KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem*) const override; + /** + * Provide a list of framework directories. + */ + KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem*) const override; + /** * Provide a list of files that contain the preprocessor defines for the * project */ QHash defines(KDevelop::ProjectBaseItem*) const override; /** * Create a new target * * Creates the target specified by @p target to the folder @p parent and * modifies the underlying build system if needed */ KDevelop::ProjectTargetItem* createTarget(const QString& target, KDevelop::ProjectFolderItem *parent) override; /** * Add a file to a target * * Adds the file specified by @p file to the target @p parent and modifies * the underlying build system if needed. */ bool addFilesToTarget(const QList &files, KDevelop::ProjectTargetItem *parent) override; /** * Remove a target * * Removes the target specified by @p target and * modifies the underlying build system if needed. */ bool removeTarget(KDevelop::ProjectTargetItem *target) override; /** * Remove a file from a target * * Removes the file specified by @p file from the folder @p parent and * modifies the underlying build system if needed. The file is not removed * from the folder it is in */ bool removeFilesFromTargets(const QList&) override; /** * Test if @p item has any includes or defines from this BSM */ - bool hasIncludesOrDefines(KDevelop::ProjectBaseItem* item) const override; + bool hasBuildInfo(KDevelop::ProjectBaseItem* item) const override; /** * Get the toplevel build directory for the project */ KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; /** * Get a list of all the targets in this project * * The list returned by this function should be checked to verify it is not * empty before using it * * @return The list of targets for this project * @todo implement */ QList targets(KDevelop::ProjectFolderItem*) const override; protected: KDevelop::ProjectFileItem* createFileItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent) override; KDevelop::ProjectFolderItem* createFolderItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = 0) override; void unload() override; private slots: void reloadMakefile(KDevelop::ProjectFileItem *item); void projectClosing(KDevelop::IProject*); private: /** * Initialize targets by reading Makefile in @arg dir * @return Target lists in Makefile at @arg dir. */ QStringList parseCustomMakeFile( const KDevelop::Path &makefile ); void createTargetItems(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent); private: IMakeBuilder *m_builder; QScopedPointer m_provider; QSet m_projectPaths; friend class CustomMakeProvider; }; #endif diff --git a/projectmanagers/custommake/makefileresolver/makefileresolver.cpp b/projectmanagers/custommake/makefileresolver/makefileresolver.cpp index 97973d43e1..704f601822 100644 --- a/projectmanagers/custommake/makefileresolver/makefileresolver.cpp +++ b/projectmanagers/custommake/makefileresolver/makefileresolver.cpp @@ -1,752 +1,775 @@ /* * KDevelop C++ Language Support * * Copyright 2007 David Nolden * Copyright 2014 Kevin Funk * * This program 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 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 "makefileresolver.h" #include "helper.h" #include #include #include #include #include #include #include #include #include #include #include #include // #define VERBOSE #if defined(VERBOSE) #define ifTest(x) x #else #define ifTest(x) #endif const int maximumInternalResolutionDepth = 3; using namespace std; using namespace KDevelop; namespace { ///After how many seconds should we retry? static const int CACHE_FAIL_FOR_SECONDS = 200; static const int processTimeoutSeconds = 30; struct CacheEntry { CacheEntry() : failed(false) { } ModificationRevisionSet modificationTime; Path::List paths; + Path::List frameworkDirectories; QHash defines; QString errorMessage, longErrorMessage; bool failed; QMap failedFiles; QDateTime failTime; }; typedef QMap Cache; static Cache s_cache; static QMutex s_cacheMutex; } ///Helper-class used to fake file-modification times class FileModificationTimeWrapper { public: ///@param files list of files that should be fake-modified(modtime will be set to current time) explicit FileModificationTimeWrapper(const QStringList& files = QStringList(), const QString& workingDirectory = QString()) : m_newTime(QDateTime::currentDateTime()) { for (QStringList::const_iterator it = files.constBegin(); it != files.constEnd(); ++it) { ifTest(cout << "touching " << it->toUtf8().constData() << endl); QFileInfo fileinfo(QDir(workingDirectory), *it); if (!fileinfo.exists()) { cout << "File does not exist: " << it->toUtf8().constData() << "in working dir " << QDir::currentPath().toUtf8().constData() << "\n"; continue; } const QString filename = fileinfo.canonicalFilePath(); if (m_stat.contains(filename)) { cout << "Duplicate file: " << filename.toUtf8().constData() << endl; continue; } QFileInfo info(filename); if (info.exists()) { ///Success m_stat[filename] = info.lastModified(); ///change the modification-time to m_newTime if (Helper::changeAccessAndModificationTime(filename, m_newTime, m_newTime) != 0) { ifTest(cout << "failed to touch " << it->toUtf8().constData() << endl); } } } } ///Undo changed modification-times void unModify() { for (auto it = m_stat.constBegin(); it != m_stat.constEnd(); ++it) { ifTest(cout << "untouching " << it.key().toUtf8().constData() << endl); QFileInfo info(it.key()); if (info.exists()) { if (info.lastModified() == m_newTime) { ///Still the modtime that we've set, change it back if (Helper::changeAccessAndModificationTime(it.key(), info.lastRead(), *it) != 0) { perror("Resetting modification time"); ifTest(cout << "failed to untouch " << it.key().toUtf8().constData() << endl); } } else { ///The file was modified since we changed the modtime ifTest(cout << "will not untouch " << it.key().toUtf8().constData() << " because the modification-time has changed" << endl); } } else { perror("File status"); } } } ~FileModificationTimeWrapper() { unModify(); } private: QHash m_stat; QDateTime m_newTime; }; /** * Compatibility: * make/automake: Should work perfectly * cmake: Thanks to the path-recursion, this works with cmake(tested with version "2.4-patch 6" * with kdelibs out-of-source and with kdevelop4 in-source) * * unsermake: * unsermake is detected by reading the first line of the makefile. If it contains * "generated by unsermake" the following things are respected: * 1. Since unsermake does not have the -W command (which should tell it to recompile * the given file no matter whether it has been changed or not), the file-modification-time of * the file is changed temporarily and the --no-real-compare option is used to force recompilation. * 2. The targets seem to be called *.lo instead of *.o when using unsermake, so *.lo names are used. * example-(test)command: unsermake --no-real-compare -n myfile.lo **/ class SourcePathInformation { public: SourcePathInformation(const QString& path) : m_path(path) , m_isUnsermake(false) , m_shouldTouchFiles(false) { m_isUnsermake = isUnsermakePrivate(path); ifTest(if (m_isUnsermake) cout << "unsermake detected" << endl); } bool isUnsermake() const { return m_isUnsermake; } ///When this is set, the file-modification times are changed no matter whether it is unsermake or make void setShouldTouchFiles(bool b) { m_shouldTouchFiles = b; } QString getCommand(const QString& absoluteFile, const QString& workingDirectory, const QString& makeParameters) const { if (isUnsermake()) { return "unsermake -k --no-real-compare -n " + makeParameters; } else { QString relativeFile = Path(workingDirectory).relativePath(Path(absoluteFile)); return "make -k --no-print-directory -W \'" + absoluteFile + "\' -W \'" + relativeFile + "\' -n " + makeParameters; } } bool hasMakefile() const { QFileInfo makeFile(m_path, "Makefile"); return makeFile.exists(); } bool shouldTouchFiles() const { return isUnsermake() || m_shouldTouchFiles; } QStringList possibleTargets(const QString& targetBaseName) const { QStringList ret; ///@todo open the make-file, and read the target-names from there. if (isUnsermake()) { //unsermake breaks if the first given target does not exist, so in worst-case 2 calls are necessary ret << targetBaseName + ".lo"; ret << targetBaseName + ".o"; } else { //It would be nice if both targets could be processed in one call, the problem is the exit-status of make, so for now make has to be called twice. ret << targetBaseName + ".o"; ret << targetBaseName + ".lo"; //ret << targetBaseName + ".lo " + targetBaseName + ".o"; } ret << targetBaseName + ".ko"; return ret; } private: bool isUnsermakePrivate(const QString& path) { bool ret = false; QFileInfo makeFile(path, "Makefile"); QFile f(makeFile.absoluteFilePath()); if (f.open(QIODevice::ReadOnly)) { QString firstLine = f.readLine(128); if (firstLine.indexOf("generated by unsermake") != -1) { ret = true; } f.close(); } return ret; } QString m_path; bool m_isUnsermake; bool m_shouldTouchFiles; }; -void PathResolutionResult::mergeWith(const PathResolutionResult& rhs) +static void mergePaths(KDevelop::Path::List& destList, const KDevelop::Path::List& srcList) { - foreach(const Path& path, rhs.paths) { - if(!paths.contains(path)) - paths.append(path); + foreach (const Path& path, srcList) { + if (!destList.contains(path)) + destList.append(path); } +} + +void PathResolutionResult::mergeWith(const PathResolutionResult& rhs) +{ + mergePaths(paths, rhs.paths); + mergePaths(frameworkDirectories, rhs.frameworkDirectories); includePathDependency += rhs.includePathDependency; defines.unite(rhs.defines); } PathResolutionResult::PathResolutionResult(bool success, const QString& errorMessage, const QString& longErrorMessage) : success(success) , errorMessage(errorMessage) , longErrorMessage(longErrorMessage) {} PathResolutionResult::operator bool() const { return success; } ModificationRevisionSet MakeFileResolver::findIncludePathDependency(const QString& file) { QString oldSourceDir = m_source; QString oldBuildDir = m_build; Path currentWd(mapToBuild(file)); ModificationRevisionSet rev; while (currentWd.hasParent()) { currentWd = currentWd.parent(); QString path = currentWd.toLocalFile(); QFileInfo makeFile(QDir(path), "Makefile"); if (makeFile.exists()) { IndexedString makeFileStr(makeFile.filePath()); rev.addModificationRevision(makeFileStr, ModificationRevision::revisionForFile(makeFileStr)); break; } } setOutOfSourceBuildSystem(oldSourceDir, oldBuildDir); return rev; } bool MakeFileResolver::executeCommand(const QString& command, const QString& workingDirectory, QString& result) const { ifTest(cout << "executing " << command.toUtf8().constData() << endl); ifTest(cout << "in " << workingDirectory.toUtf8().constData() << endl); KProcess proc; proc.setWorkingDirectory(workingDirectory); proc.setOutputChannelMode(KProcess::MergedChannels); QStringList args(command.split(' ')); QString prog = args.takeFirst(); proc.setProgram(prog, args); int status = proc.execute(processTimeoutSeconds * 1000); result = proc.readAll(); return status == 0; } MakeFileResolver::MakeFileResolver() : m_isResolving(false) , m_outOfSource(false) { } ///More efficient solution: Only do exactly one call for each directory. During that call, mark all source-files as changed, and make all targets for those files. PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file) { if (file.isEmpty()) { // for unit tests with temporary files return PathResolutionResult(); } QFileInfo fi(file); return resolveIncludePath(fi.fileName(), fi.absolutePath()); } QString MakeFileResolver::mapToBuild(const QString &path) const { QString wd = QDir::cleanPath(path); if (m_outOfSource) { if (wd.startsWith(m_source) && !wd.startsWith(m_build)) { //Move the current working-directory out of source, into the build-system wd = QDir::cleanPath(m_build + '/' + wd.mid(m_source.length())); } } return wd; } void MakeFileResolver::clearCache() { QMutexLocker l(&s_cacheMutex); s_cache.clear(); } PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file, const QString& _workingDirectory, int maxStepsUp) { //Prefer this result when returning a "fail". The include-paths of this result will always be added. PathResolutionResult resultOnFail; if (m_isResolving) return PathResolutionResult(false, i18n("Tried include path resolution while another resolution process was still running")); //Make the working-directory absolute QString workingDirectory = _workingDirectory; if (QFileInfo(workingDirectory).isRelative()) { QUrl u = QUrl::fromLocalFile(QDir::currentPath()); if (workingDirectory == ".") workingDirectory = QString(); else if (workingDirectory.startsWith("./")) workingDirectory = workingDirectory.mid(2); if (!workingDirectory.isEmpty()) { u = u.adjusted(QUrl::StripTrailingSlash); u.setPath(u.path() + '/' + workingDirectory); } workingDirectory = u.toLocalFile(); } else workingDirectory = _workingDirectory; ifTest(cout << "working-directory: " << workingDirectory.toLocal8Bit().data() << " file: " << file.toLocal8Bit().data() << std::endl;) QDir sourceDir(workingDirectory); QDir dir = QDir(mapToBuild(sourceDir.absolutePath())); QFileInfo makeFile(dir, "Makefile"); if (!makeFile.exists()) { if (maxStepsUp > 0) { //If there is no makefile in this directory, go one up and re-try from there QFileInfo fileName(file); QString localName = sourceDir.dirName(); if (sourceDir.cdUp() && !fileName.isAbsolute()) { QString checkFor = localName + "/" + file; PathResolutionResult oneUp = resolveIncludePath(checkFor, sourceDir.path(), maxStepsUp-1); if (oneUp.success) { oneUp.mergeWith(resultOnFail); return oneUp; } } } - if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty()) + if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Makefile is missing in folder \"%1\"", dir.absolutePath()), i18n("Problem while trying to resolve include paths for %1", file)); } PushValue e(m_isResolving, true); Path::List cachedPaths; //If the call doesn't succeed, use the cached not up-to-date version + Path::List cachedFWDirs; QHash cachedDefines; ModificationRevisionSet dependency; dependency.addModificationRevision(IndexedString(makeFile.filePath()), ModificationRevision::revisionForFile(IndexedString(makeFile.filePath()))); dependency += resultOnFail.includePathDependency; Cache::iterator it; { QMutexLocker l(&s_cacheMutex); it = s_cache.find(dir.path()); if (it != s_cache.end()) { cachedPaths = it->paths; + cachedFWDirs = it->frameworkDirectories; cachedDefines = it->defines; if (dependency == it->modificationTime) { if (!it->failed) { //We have a valid cached result PathResolutionResult ret(true); ret.paths = it->paths; + ret.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(resultOnFail); return ret; } else { //We have a cached failed result. We should use that for some time but then try again. Return the failed result if: (there were too many tries within this folder OR this file was already tried) AND The last tries have not expired yet if (/*(it->failedFiles.size() > 3 || it->failedFiles.find(file) != it->failedFiles.end()) &&*/ it->failTime.secsTo(QDateTime::currentDateTime()) < CACHE_FAIL_FOR_SECONDS) { PathResolutionResult ret(false); //Fake that the result is ok ret.errorMessage = i18n("Cached: %1", it->errorMessage); ret.longErrorMessage = it->longErrorMessage; ret.paths = it->paths; + ret.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(resultOnFail); return ret; } else { //Try getting a correct result again } } } } } ///STEP 1: Prepare paths QString targetName; QFileInfo fi(file); QString absoluteFile = file; if (fi.isRelative()) absoluteFile = workingDirectory + '/' + file; absoluteFile = QDir::cleanPath(absoluteFile); int dot; if ((dot = file.lastIndexOf('.')) == -1) { - if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty()) + if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Filename %1 seems to be malformed", file)); } targetName = file.left(dot); QString wd = dir.path(); if (QFileInfo(wd).isRelative()) { wd = QDir::cleanPath(QDir::currentPath() + '/' + wd); } wd = mapToBuild(wd); SourcePathInformation source(wd); QStringList possibleTargets = source.possibleTargets(targetName); source.setShouldTouchFiles(true); //Think about whether this should be always enabled. I've enabled it for now so there's an even bigger chance that everything works. ///STEP 3: Try resolving the paths, by using once the absolute and once the relative file-path. Which kind is required differs from setup to setup. ///STEP 3.1: Try resolution using the absolute path PathResolutionResult res; //Try for each possible target res = resolveIncludePathInternal(absoluteFile, wd, possibleTargets.join(" "), source, maximumInternalResolutionDepth); if (!res) { ifTest(cout << "Try for absolute file " << absoluteFile.toLocal8Bit().data() << " and targets " << possibleTargets.join(", ").toLocal8Bit().data() << " failed: " << res.longErrorMessage.toLocal8Bit().data() << endl;) } res.includePathDependency = dependency; if (res.paths.isEmpty()) { res.paths = cachedPaths; //We failed, maybe there is an old cached result, use that. res.defines = cachedDefines; } + // a build command could contain only one or more -iframework or -F specifications. + if (res.frameworkDirectories.isEmpty()) { + res.frameworkDirectories = cachedFWDirs; + } { QMutexLocker l(&s_cacheMutex); if (it == s_cache.end()) it = s_cache.insert(dir.path(), CacheEntry()); CacheEntry& ce(*it); ce.paths = res.paths; + ce.frameworkDirectories = res.frameworkDirectories; ce.modificationTime = dependency; if (!res) { ce.failed = true; ce.errorMessage = res.errorMessage; ce.longErrorMessage = res.longErrorMessage; ce.failTime = QDateTime::currentDateTime(); ce.failedFiles[file] = true; } else { ce.failed = false; ce.failedFiles.clear(); } } - if (!res && (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty())) + if (!res && (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty())) return resultOnFail; return res; } static QRegularExpression includeRegularExpression() { static const QRegularExpression expression( - "\\s(?:--include-dir=|-I\\s*|-isystem\\s+)(" + "\\s(--include-dir=|-I\\s*|-isystem\\s+|-iframework\\s+|-F\\s*)(" "\\'.*\\'|\\\".*\\\"" //Matches "hello", 'hello', 'hello"hallo"', etc. "|" "((?:\\\\.)?([\\S^\\\\]?))+" //Matches /usr/I\ am\ a\ strange\ path/include ")(?=\\s)" ); Q_ASSERT(expression.isValid()); return expression; } PathResolutionResult MakeFileResolver::resolveIncludePathInternal(const QString& file, const QString& workingDirectory, const QString& makeParameters, const SourcePathInformation& source, int maxDepth) { --maxDepth; if (maxDepth < 0) return PathResolutionResult(false); QString processStdout; QStringList touchFiles; if (source.shouldTouchFiles()) { touchFiles << file; } FileModificationTimeWrapper touch(touchFiles, workingDirectory); QString fullOutput; executeCommand(source.getCommand(file, workingDirectory, makeParameters), workingDirectory, fullOutput); { QRegExp newLineRx("\\\\\\n"); fullOutput.replace(newLineRx, ""); } ///@todo collect multiple outputs at the same time for performance-reasons QString firstLine = fullOutput; int lineEnd; if ((lineEnd = fullOutput.indexOf('\n')) != -1) firstLine.truncate(lineEnd); //Only look at the first line of output /** * There's two possible cases this can currently handle. * 1.: gcc is called, with the parameters we are searching for (so we parse the parameters) * 2.: A recursive make is called, within another directory(so we follow the recursion and try again) "cd /foo/bar && make -f pi/pa/build.make pi/pa/po.o * */ ///STEP 1: Test if it is a recursive make-call // Do not search for recursive make-calls if we already have include-paths available. Happens in kernel modules. if (!includeRegularExpression().match(fullOutput).hasMatch()) { QRegExp makeRx("\\bmake\\s"); int offset = 0; while ((offset = makeRx.indexIn(firstLine, offset)) != -1) { QString prefix = firstLine.left(offset).trimmed(); if (prefix.endsWith("&&") || prefix.endsWith(';') || prefix.isEmpty()) { QString newWorkingDirectory = workingDirectory; ///Extract the new working-directory if (!prefix.isEmpty()) { if (prefix.endsWith("&&")) prefix.truncate(prefix.length() - 2); else if (prefix.endsWith(';')) prefix.truncate(prefix.length() - 1); ///Now test if what we have as prefix is a simple "cd /foo/bar" call. //In cases like "cd /media/data/kdedev/4.0/build/kdevelop && cd /media/data/kdedev/4.0/build/kdevelop" //We use the second directory. For t hat reason we search for the last index of "cd " int cdIndex = prefix.lastIndexOf("cd "); if (cdIndex != -1) { newWorkingDirectory = prefix.right(prefix.length() - 3 - cdIndex).trimmed(); if (QFileInfo(newWorkingDirectory).isRelative()) newWorkingDirectory = workingDirectory + '/' + newWorkingDirectory; newWorkingDirectory = QDir::cleanPath(newWorkingDirectory); } } if (newWorkingDirectory == workingDirectory) { return PathResolutionResult(false, i18n("Failed to extract new working directory"), i18n("Output was: %1", fullOutput)); } QFileInfo d(newWorkingDirectory); if (d.exists()) { ///The recursive working-directory exists. QString makeParams = firstLine.mid(offset+5); if (!makeParams.contains(';') && !makeParams.contains("&&")) { ///Looks like valid parameters ///Make the file-name absolute, so it can be referenced from any directory QString absoluteFile = file; if (QFileInfo(absoluteFile).isRelative()) absoluteFile = workingDirectory + '/' + file; Path absolutePath(absoluteFile); ///Try once with absolute, and if that fails with relative path of the file SourcePathInformation newSource(newWorkingDirectory); PathResolutionResult res = resolveIncludePathInternal(absolutePath.toLocalFile(), newWorkingDirectory, makeParams, newSource, maxDepth); if (res) return res; return resolveIncludePathInternal(Path(newWorkingDirectory).relativePath(absolutePath), newWorkingDirectory, makeParams , newSource, maxDepth); }else{ return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The parameter string \"%1\" does not seem to be valid. Output was: %2.", makeParams, fullOutput)); } } else { return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The directory \"%1\" does not exist. Output was: %2.", newWorkingDirectory, fullOutput)); } } else { return PathResolutionResult(false, i18n("Malformed recursive make call"), i18n("Output was: %1", fullOutput)); } ++offset; if (offset >= firstLine.length()) break; } } ///STEP 2: Search the output for include-paths PathResolutionResult ret = processOutput(fullOutput, workingDirectory); - if (ret.paths.isEmpty()) + if (ret.paths.isEmpty() && ret.frameworkDirectories.isEmpty()) return PathResolutionResult(false, i18n("Could not extract include paths from make output"), i18n("Folder: \"%1\" Command: \"%2\" Output: \"%3\"", workingDirectory, source.getCommand(file, workingDirectory, makeParameters), fullOutput)); return ret; } static QRegularExpression defineRegularExpression() { static const QRegularExpression pattern( "-D([^\\s=]+)(?:=(?:\"(.*?)(? 2)) { - //probable a quoted path - if (path.endsWith(path.left(1))) { - //Quotation is ok, remove it - path = path.mid(1, path.length() - 2); - } + //probable a quoted path + if (path.endsWith(path.left(1))) { + //Quotation is ok, remove it + path = path.mid(1, path.length() - 2); + } } if (QDir::isRelativePath(path)) path = workingDirectory + '/' + path; - - ret.paths << internPath(path); + const auto& internedPath = internPath(path); + const auto& type = match.captured(1); + const auto isFramework = type.startsWith(QLatin1String("-iframework")) + || type.startsWith(QLatin1String("-F")); + if (isFramework) { + ret.frameworkDirectories << internedPath; + } else { + ret.paths << internedPath; + } } } { const auto& defineRx = defineRegularExpression(); auto it = defineRx.globalMatch(fullOutput); while (it.hasNext()) { const auto match = it.next(); QString value; if (match.lastCapturedIndex() > 1) { value = unescape(match.capturedRef(match.lastCapturedIndex())); } ret.defines[internString(match.captured(1))] = internString(value); } } return ret; } void MakeFileResolver::resetOutOfSourceBuild() { m_outOfSource = false; } void MakeFileResolver::setOutOfSourceBuildSystem(const QString& source, const QString& build) { if (source == build) { resetOutOfSourceBuild(); return; } m_outOfSource = true; m_source = QDir::cleanPath(source); m_build = QDir::cleanPath(m_build); } Path MakeFileResolver::internPath(const QString& path) const { Path& ret = m_pathCache[path]; if (ret.isEmpty() != path.isEmpty()) { ret = Path(path); } return ret; } QString MakeFileResolver::internString(const QString& path) const { auto it = m_stringCache.constFind(path); if (it != m_stringCache.constEnd()) { return *it; } m_stringCache.insert(path); return path; } // kate: indent-width 2; tab-width 2; diff --git a/projectmanagers/custommake/makefileresolver/makefileresolver.h b/projectmanagers/custommake/makefileresolver/makefileresolver.h index debe977cff..b23ceed6eb 100644 --- a/projectmanagers/custommake/makefileresolver/makefileresolver.h +++ b/projectmanagers/custommake/makefileresolver/makefileresolver.h @@ -1,98 +1,102 @@ /* * KDevelop C++ Language Support * * Copyright 2007 David Nolden * Copyright 2014 Kevin Funk * * This program 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 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 INCLUDEPATHRESOLVER_H #define INCLUDEPATHRESOLVER_H #include #include #include struct PathResolutionResult { PathResolutionResult(bool success = false, const QString& errorMessage = QString(), const QString& longErrorMessage = QString()); bool success; QString errorMessage; QString longErrorMessage; KDevelop::ModificationRevisionSet includePathDependency; KDevelop::Path::List paths; + // the list of framework directories specified with explicit -iframework and/or -F arguments. + // Mainly for OS X, but available everywhere to avoid #ifdefs and + // because clang is an out-of-the-box cross-compiler. + KDevelop::Path::List frameworkDirectories; QHash defines; void mergeWith(const PathResolutionResult& rhs); operator bool() const; }; class SourcePathInformation; ///One resolution-try can issue up to 4 make-calls in worst case class MakeFileResolver { public: MakeFileResolver(); ///Same as below, but uses the directory of the file as working-directory. The argument must be absolute. PathResolutionResult resolveIncludePath( const QString& file ); ///The include-path is only computed once for a whole directory, then it is cached using the modification-time of the Makefile. ///source and build must be absolute paths void setOutOfSourceBuildSystem( const QString& source, const QString& build ); ///resets to in-source build system void resetOutOfSourceBuild(); static void clearCache(); KDevelop::ModificationRevisionSet findIncludePathDependency(const QString& file); void enableMakeResolution(bool enable); PathResolutionResult processOutput(const QString& fullOutput, const QString& workingDirectory) const; private: PathResolutionResult resolveIncludePath( const QString& file, const QString& workingDirectory, int maxStepsUp = 20 ); bool m_isResolving; bool m_outOfSource; QString mapToBuild(const QString &path) const; ///Executes the command using KProcess bool executeCommand( const QString& command, const QString& workingDirectory, QString& result ) const; ///file should be the name of the target, without extension(because that may be different) PathResolutionResult resolveIncludePathInternal( const QString& file, const QString& workingDirectory, const QString& makeParameters, const SourcePathInformation& source, int maxDepth ); QString m_source; QString m_build; // reuse cached instances of Paths and strings, to share memory where possible mutable QHash m_pathCache; mutable QSet m_stringCache; KDevelop::Path internPath(const QString& path) const; QString internString(const QString& string) const; }; #endif // kate: indent-width 2; tab-width 2; diff --git a/projectmanagers/custommake/makefileresolver/tests/test_custommake.cpp b/projectmanagers/custommake/makefileresolver/tests/test_custommake.cpp index 368e83ec4d..87799f28ba 100644 --- a/projectmanagers/custommake/makefileresolver/tests/test_custommake.cpp +++ b/projectmanagers/custommake/makefileresolver/tests/test_custommake.cpp @@ -1,99 +1,123 @@ /* * 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 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 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_custommake.h" #include #include #include #include #include #include #include "../makefileresolver.h" #include using namespace KDevelop; namespace { void createFile( QFile& file ) { file.remove(); if ( !file.open( QIODevice::ReadWrite ) ) { qFatal("Cannot create the file %s", file.fileName().toUtf8().data()); } } } void TestCustomMake::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestCustomMake::cleanupTestCase() { TestCore::shutdown(); } void TestCustomMake::testIncludeDirectories() { QTemporaryDir tempDir; { QFile file( tempDir.path() + "/Makefile" ); createFile( file ); QFile testfile( tempDir.path() + "/testfile.cpp" ); createFile(testfile); QTextStream stream1( &file ); stream1 << "testfile.o:\n\t g++ testfile.cpp -I/testFile1 -I /testFile2 -isystem /testFile3 --include-dir=/testFile4 -o testfile"; } MakeFileResolver mf; auto result = mf.resolveIncludePath(tempDir.path() + "/testfile.cpp"); if (!result.success) { qDebug() << result.errorMessage << result.longErrorMessage; QFAIL("Failed to resolve include path."); } QCOMPARE(result.paths.size(), 4); QVERIFY(result.paths.contains(Path("/testFile1"))); QVERIFY(result.paths.contains(Path("/testFile2"))); QVERIFY(result.paths.contains(Path("/testFile3"))); QVERIFY(result.paths.contains(Path("/testFile4"))); } +void TestCustomMake::testFrameworkDirectories() +{ + QTemporaryDir tempDir; + int expectedPaths = 2; + { + QFile file( tempDir.path() + "/Makefile" ); + createFile( file ); + QFile testfile( tempDir.path() + "/testfile.cpp" ); + createFile(testfile); + QTextStream stream1( &file ); + stream1 << "testfile.o:\n\t clang++ testfile.cpp -iframework /System/Library/Frameworks -F/Library/Frameworks -o testfile"; + } + + MakeFileResolver mf; + auto result = mf.resolveIncludePath(tempDir.path() + "/testfile.cpp"); + if (!result.success) { + qDebug() << result.errorMessage << result.longErrorMessage; + QFAIL("Failed to resolve include path."); + } + QCOMPARE(result.frameworkDirectories.size(), expectedPaths); + QVERIFY(result.frameworkDirectories.contains(Path("/System/Library/Frameworks"))); + QVERIFY(result.frameworkDirectories.contains(Path("/Library/Frameworks"))); +} + void TestCustomMake::testDefines() { MakeFileResolver mf; const auto result = mf.processOutput("-DFOO -DFOO=\\\"foo\\\" -DBAR=ASDF -DLALA=1 -DMEH=" " -DSTR=\"\\\"foo \\\\\\\" bar\\\"\" -DEND", QString()); QCOMPARE(result.defines.value("FOO", "not found"), QString("\"foo\"")); QCOMPARE(result.defines.value("BAR", "not found"), QString("ASDF")); QCOMPARE(result.defines.value("LALA", "not found"), QString("1")); QCOMPARE(result.defines.value("MEH", "not found"), QString()); QCOMPARE(result.defines.value("STR", "not found"), QString("\"foo \\\" bar\"")); QCOMPARE(result.defines.value("END", "not found"), QString()); } QTEST_GUILESS_MAIN(TestCustomMake) #include "moc_test_custommake.cpp" diff --git a/projectmanagers/custommake/makefileresolver/tests/test_custommake.h b/projectmanagers/custommake/makefileresolver/tests/test_custommake.h index 3ad0f36abf..113523a626 100644 --- a/projectmanagers/custommake/makefileresolver/tests/test_custommake.h +++ b/projectmanagers/custommake/makefileresolver/tests/test_custommake.h @@ -1,38 +1,39 @@ /* * 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 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 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 TEST_CUSTOMMAKE_H #define TEST_CUSTOMMAKE_H #include class TestCustomMake : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testIncludeDirectories(); + void testFrameworkDirectories(); void testDefines(); }; #endif // TEST_CUSTOMMAKE_H diff --git a/projectmanagers/qmake/qmakemanager.cpp b/projectmanagers/qmake/qmakemanager.cpp index 123b474f07..2ed766f65f 100644 --- a/projectmanagers/qmake/qmakemanager.cpp +++ b/projectmanagers/qmake/qmakemanager.cpp @@ -1,509 +1,521 @@ /* KDevelop QMake Support * * Copyright 2006 Andreas Pakulat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "qmakemanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qmakemodelitems.h" #include "qmakeprojectfile.h" #include "qmakecache.h" #include "qmakemkspecs.h" #include "qmakejob.h" #include "qmakebuilddirchooserdialog.h" #include "qmakeconfig.h" #include "qmakeutils.h" #include "debug.h" using namespace KDevelop; // BEGIN Helpers QMakeFolderItem* findQMakeFolderParent(ProjectBaseItem* item) { QMakeFolderItem* p = nullptr; while (!p && item) { p = dynamic_cast(item); item = item->parent(); } return p; } // END Helpers K_PLUGIN_FACTORY_WITH_JSON(QMakeSupportFactory, "kdevqmakemanager.json", registerPlugin();) QMakeProjectManager* QMakeProjectManager::m_self = nullptr; QMakeProjectManager* QMakeProjectManager::self() { return m_self; } QMakeProjectManager::QMakeProjectManager(QObject* parent, const QVariantList&) : AbstractFileManagerPlugin("kdevqmakemanager", parent) , IBuildSystemManager() , m_builder(nullptr) , m_runQMake(nullptr) { Q_ASSERT(!m_self); m_self = this; KDEV_USE_EXTENSION_INTERFACE(IBuildSystemManager) IPlugin* i = core()->pluginController()->pluginForExtension("org.kdevelop.IQMakeBuilder"); Q_ASSERT(i); m_builder = i->extension(); Q_ASSERT(m_builder); connect(this, SIGNAL(folderAdded(KDevelop::ProjectFolderItem*)), this, SLOT(slotFolderAdded(KDevelop::ProjectFolderItem*))); m_runQMake = new QAction(QIcon::fromTheme("qtlogo"), i18n("Run QMake"), this); connect(m_runQMake, SIGNAL(triggered(bool)), this, SLOT(slotRunQMake())); } QMakeProjectManager::~QMakeProjectManager() { m_self = nullptr; } IProjectFileManager::Features QMakeProjectManager::features() const { return Features(Folders | Targets | Files); } bool QMakeProjectManager::isValid(const Path& path, const bool isFolder, IProject* project) const { if (!isFolder && path.lastPathSegment().startsWith("Makefile")) { return false; } return AbstractFileManagerPlugin::isValid(path, isFolder, project); } Path QMakeProjectManager::buildDirectory(ProjectBaseItem* item) const { /// TODO: support includes by some other parent or sibling in a different file-tree-branch QMakeFolderItem* qmakeItem = findQMakeFolderParent(item); Path dir; if (qmakeItem) { if (!qmakeItem->parent()) { // build root item dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), qmakeItem->path()); } else { // build sub-item foreach (QMakeProjectFile* pro, qmakeItem->projectFiles()) { if (QDir(pro->absoluteDir()) == QFileInfo(qmakeItem->path().toUrl().toLocalFile() + '/').absoluteDir() || pro->hasSubProject(qmakeItem->path().toUrl().toLocalFile())) { // get path from project root and it to buildDir dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), Path(pro->absoluteDir())); break; } } } } qCDebug(KDEV_QMAKE) << "build dir for" << item->text() << item->path() << "is:" << dir; return dir; } ProjectFolderItem* QMakeProjectManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { if (!parent) { return projectRootItem(project, path); } else if (ProjectFolderItem* buildFolder = buildFolderItem(project, path, parent)) { // child folder in a qmake folder return buildFolder; } else { return AbstractFileManagerPlugin::createFolderItem(project, path, parent); } } ProjectFolderItem* QMakeProjectManager::projectRootItem(IProject* project, const Path& path) { QFileInfo fi(path.toLocalFile()); QDir dir(path.toLocalFile()); QStringList l = dir.entryList(QStringList() << "*.pro"); QString projectfile; if (l.count() && l.indexOf(project->name() + ".pro") != -1) projectfile = project->name() + ".pro"; if (l.isEmpty() || (l.count() && l.indexOf(fi.baseName() + ".pro") != -1)) { projectfile = fi.baseName() + ".pro"; } else { projectfile = l.first(); } QHash qmvars = QMakeUtils::queryQMake(project); const QString mkSpecFile = QMakeConfig::findBasicMkSpec(qmvars); Q_ASSERT(!mkSpecFile.isEmpty()); QMakeMkSpecs* mkspecs = new QMakeMkSpecs(mkSpecFile, qmvars); mkspecs->setProject(project); mkspecs->read(); QMakeCache* cache = findQMakeCache(project); if (cache) { cache->setMkSpecs(mkspecs); cache->read(); } Path proPath(path, projectfile); /// TODO: use Path in QMakeProjectFile QMakeProjectFile* scope = new QMakeProjectFile(proPath.toLocalFile()); scope->setProject(project); scope->setMkSpecs(mkspecs); if (cache) { scope->setQMakeCache(cache); } scope->read(); qCDebug(KDEV_QMAKE) << "top-level scope with variables:" << scope->variables(); auto item = new QMakeFolderItem(project, path); item->addProjectFile(scope); return item; } ProjectFolderItem* QMakeProjectManager::buildFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // find .pro or .pri files in dir QDir dir(path.toLocalFile()); QStringList projectFiles = dir.entryList(QStringList() << "*.pro" << "*.pri", QDir::Files); if (projectFiles.isEmpty()) { return nullptr; } auto folderItem = new QMakeFolderItem(project, path, parent); // TODO: included by not-parent file (in a nother file-tree-branch). QMakeFolderItem* qmakeParent = findQMakeFolderParent(parent); if (!qmakeParent) { // happens for bad qmake configurations return nullptr; } foreach (const QString& file, projectFiles) { const QString absFile = dir.absoluteFilePath(file); // TODO: multiple includes by different .pro's QMakeProjectFile* parentPro = nullptr; foreach (QMakeProjectFile* p, qmakeParent->projectFiles()) { if (p->hasSubProject(absFile)) { parentPro = p; break; } } if (!parentPro && file.endsWith(".pri")) { continue; } qCDebug(KDEV_QMAKE) << "add project file:" << absFile; if (parentPro) { qCDebug(KDEV_QMAKE) << "parent:" << parentPro->absoluteFile(); } else { qCDebug(KDEV_QMAKE) << "no parent, assume project root"; } auto qmscope = new QMakeProjectFile(absFile); qmscope->setProject(project); const QFileInfo info(absFile); const QDir d = info.dir(); /// TODO: cleanup if (parentPro) { // subdir if (QMakeCache* cache = findQMakeCache(project, Path(d.canonicalPath()))) { cache->setMkSpecs(parentPro->mkSpecs()); cache->read(); qmscope->setQMakeCache(cache); } else { qmscope->setQMakeCache(parentPro->qmakeCache()); } qmscope->setMkSpecs(parentPro->mkSpecs()); } else { // new project QMakeFolderItem* root = dynamic_cast(project->projectItem()); Q_ASSERT(root); qmscope->setMkSpecs(root->projectFiles().first()->mkSpecs()); if (root->projectFiles().first()->qmakeCache()) { qmscope->setQMakeCache(root->projectFiles().first()->qmakeCache()); } } if (qmscope->read()) { // TODO: only on read? folderItem->addProjectFile(qmscope); } else { delete qmscope; return nullptr; } } return folderItem; } void QMakeProjectManager::slotFolderAdded(ProjectFolderItem* folder) { QMakeFolderItem* qmakeParent = dynamic_cast(folder); if (!qmakeParent) { return; } qCDebug(KDEV_QMAKE) << "adding targets for" << folder->path(); foreach (QMakeProjectFile* pro, qmakeParent->projectFiles()) { foreach (const QString& s, pro->targets()) { if (!isValid(Path(folder->path(), s), false, folder->project())) { continue; } qCDebug(KDEV_QMAKE) << "adding target:" << s; Q_ASSERT(!s.isEmpty()); auto target = new QMakeTargetItem(pro, folder->project(), s, folder); foreach (const QString& path, pro->filesForTarget(s)) { new ProjectFileItem(folder->project(), Path(path), target); /// TODO: signal? } } } } ProjectFolderItem* QMakeProjectManager::import(IProject* project) { const Path dirName = project->path(); if (dirName.isRemote()) { // FIXME turn this into a real warning qCWarning(KDEV_QMAKE) << "not a local file. QMake support doesn't handle remote projects"; return nullptr; } QMakeUtils::checkForNeedingConfigure(project); ProjectFolderItem* ret = AbstractFileManagerPlugin::import(project); connect(projectWatcher(project), SIGNAL(dirty(QString)), this, SLOT(slotDirty(QString))); return ret; } void QMakeProjectManager::slotDirty(const QString& path) { if (!path.endsWith(".pro") && !path.endsWith(".pri")) { return; } QFileInfo info(path); if (!info.isFile()) { return; } const QUrl url = QUrl::fromLocalFile(path); if (!isValid(Path(url), false, nullptr)) { return; } IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if (!project) { // this can happen when we create/remove lots of files in a // sub dir of a project - ignore such cases for now return; } bool finished = false; foreach (ProjectFolderItem* folder, project->foldersForPath(IndexedString(KIO::upUrl(url)))) { if (QMakeFolderItem* qmakeFolder = dynamic_cast(folder)) { foreach (QMakeProjectFile* pro, qmakeFolder->projectFiles()) { if (pro->absoluteFile() == path) { // TODO: children // TODO: cache added qDebug() << "reloading" << pro << path; pro->read(); } } finished = true; } else if (ProjectFolderItem* newFolder = buildFolderItem(project, folder->path(), folder->parent())) { qDebug() << "changing from normal folder to qmake project folder:" << folder->path().toUrl(); // .pro / .pri file did not exist before while (folder->rowCount()) { newFolder->appendRow(folder->takeRow(0)); } folder->parent()->removeRow(folder->row()); folder = newFolder; finished = true; } if (finished) { // remove existing targets and readd them for (int i = 0; i < folder->rowCount(); ++i) { if (folder->child(i)->target()) { folder->removeRow(i); } } /// TODO: put into it's own function once we add more stuff to that slot slotFolderAdded(folder); break; } } } QList QMakeProjectManager::targets(ProjectFolderItem* item) const { Q_UNUSED(item) return QList(); } IProjectBuilder* QMakeProjectManager::builder() const { Q_ASSERT(m_builder); return m_builder; } -Path::List QMakeProjectManager::includeDirectories(ProjectBaseItem* item) const +Path::List QMakeProjectManager::collectDirectories(ProjectBaseItem* item, const bool collectIncludes) const { Path::List list; QMakeFolderItem* folder = findQMakeFolderParent(item); if (folder) { foreach (QMakeProjectFile* pro, folder->projectFiles()) { if (pro->files().contains(item->path().toLocalFile())) { - foreach (const QString& dir, pro->includeDirectories()) { + const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); + foreach (const QString& dir, directories) { Path path(dir); if (!list.contains(path)) { list << path; } } } } if (list.isEmpty()) { // fallback for new files, use all possible include dirs foreach (QMakeProjectFile* pro, folder->projectFiles()) { - foreach (const QString& dir, pro->includeDirectories()) { + const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); + foreach (const QString& dir, directories) { Path path(dir); if (!list.contains(path)) { list << path; } } } } // make sure the base dir is included if (!list.contains(folder->path())) { list << folder->path(); } // qCDebug(KDEV_QMAKE) << "include dirs for" << item->path() << ":" << list; } return list; } +Path::List QMakeProjectManager::includeDirectories(ProjectBaseItem* item) const +{ + return collectDirectories(item); +} + +Path::List QMakeProjectManager::frameworkDirectories(ProjectBaseItem* item) const +{ + return collectDirectories(item, false); +} + QHash QMakeProjectManager::defines(ProjectBaseItem* item) const { QHash d; QMakeFolderItem* folder = findQMakeFolderParent(item); if (!folder) { // happens for bad qmake configurations return d; } foreach (QMakeProjectFile* pro, folder->projectFiles()) { foreach (QMakeProjectFile::DefinePair def, pro->defines()) { d.insert(def.first, def.second); } } return d; } -bool QMakeProjectManager::hasIncludesOrDefines(KDevelop::ProjectBaseItem* item) const +bool QMakeProjectManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const { return findQMakeFolderParent(item); } QMakeCache* QMakeProjectManager::findQMakeCache(IProject* project, const Path& path) const { QDir curdir(QMakeConfig::buildDirFromSrc(project, !path.isValid() ? project->path() : path).toLocalFile()); curdir.makeAbsolute(); while (!curdir.exists(".qmake.cache") && !curdir.isRoot() && curdir.cdUp()) { qDebug() << curdir; } if (curdir.exists(".qmake.cache")) { qDebug() << "Found QMake cache in " << curdir.absolutePath(); return new QMakeCache(curdir.canonicalPath() + "/.qmake.cache"); } return nullptr; } ContextMenuExtension QMakeProjectManager::contextMenuExtension(Context* context) { ContextMenuExtension ext; if (context->hasType(Context::ProjectItemContext)) { ProjectItemContext* pic = dynamic_cast(context); Q_ASSERT(pic); if (pic->items().isEmpty()) { return ext; } m_actionItem = dynamic_cast(pic->items().first()); if (m_actionItem) { ext.addAction(ContextMenuExtension::ProjectGroup, m_runQMake); } } return ext; } void QMakeProjectManager::slotRunQMake() { Q_ASSERT(m_actionItem); Path srcDir = m_actionItem->path(); Path buildDir = QMakeConfig::buildDirFromSrc(m_actionItem->project(), srcDir); QMakeJob* job = new QMakeJob(srcDir.toLocalFile(), buildDir.toLocalFile(), this); job->setQMakePath(QMakeConfig::qmakeBinary(m_actionItem->project())); KConfigGroup cg(m_actionItem->project()->projectConfiguration(), QMakeConfig::CONFIG_GROUP); QString installPrefix = cg.readEntry(QMakeConfig::INSTALL_PREFIX, QString()); if (!installPrefix.isEmpty()) job->setInstallPrefix(installPrefix); job->setBuildType(cg.readEntry(QMakeConfig::BUILD_TYPE, 0)); job->setExtraArguments(cg.readEntry(QMakeConfig::EXTRA_ARGUMENTS, QString())); KDevelop::ICore::self()->runController()->registerJob(job); } #include "qmakemanager.moc" diff --git a/projectmanagers/qmake/qmakemanager.h b/projectmanagers/qmake/qmakemanager.h index e5e3266764..690f58c3a5 100644 --- a/projectmanagers/qmake/qmakemanager.h +++ b/projectmanagers/qmake/qmakemanager.h @@ -1,105 +1,108 @@ /* KDevelop QMake Support * * Copyright 2006 Andreas Pakulat * Copyright 2010 Milian Wolff * * 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 QMAKEMANAGER_H #define QMAKEMANAGER_H #include #include class QMakeFolderItem; class IQMakeBuilder; class QMakeCache; +class QMakeProjectFile; class QMakeProjectManager : public KDevelop::AbstractFileManagerPlugin, public KDevelop::IBuildSystemManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) public: explicit QMakeProjectManager( QObject* parent = nullptr, const QVariantList& args = QVariantList() ); ~QMakeProjectManager() override; static QMakeProjectManager* self(); KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override; //BEGIN AbstractFileManager KDevelop::ProjectFolderItem* import( KDevelop::IProject* project ) override; KDevelop::ProjectFolderItem* createFolderItem( KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = nullptr ) override; Features features() const override; bool isValid( const KDevelop::Path& path, const bool isFolder, KDevelop::IProject* project ) const override; //END AbstractFileManager //BEGIN IBuildSystemManager //TODO KDevelop::IProjectBuilder* builder() const override; KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; + KDevelop::Path::List collectDirectories(KDevelop::ProjectBaseItem*, const bool collectIncludes=true) const; KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem*) const override; + KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem* item) const override; QHash defines(KDevelop::ProjectBaseItem*) const override; - bool hasIncludesOrDefines(KDevelop::ProjectBaseItem*) const override; + bool hasBuildInfo(KDevelop::ProjectBaseItem*) const override; KDevelop::ProjectTargetItem* createTarget( const QString&, KDevelop::ProjectFolderItem* ) override { return nullptr; } bool addFilesToTarget(const QList&, KDevelop::ProjectTargetItem*) override { return false; } bool removeTarget( KDevelop::ProjectTargetItem* ) override { return false; } bool removeFilesFromTargets(const QList&) override { return false; } QList targets(KDevelop::ProjectFolderItem*) const override; //END IBuildSystemManager private slots: void slotFolderAdded( KDevelop::ProjectFolderItem* folder ); void slotRunQMake(); void slotDirty(const QString& path); private: KDevelop::ProjectFolderItem* projectRootItem( KDevelop::IProject* project, const KDevelop::Path& path ); KDevelop::ProjectFolderItem* buildFolderItem( KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent ); QMakeCache* findQMakeCache( KDevelop::IProject* project, const KDevelop::Path &path = {} ) const; IQMakeBuilder* m_builder; mutable QString m_qtIncludeDir; QAction* m_runQMake; QMakeFolderItem* m_actionItem; static QMakeProjectManager* m_self; }; #endif diff --git a/projectmanagers/qmake/qmakeprojectfile.cpp b/projectmanagers/qmake/qmakeprojectfile.cpp index 221eee9297..405a9e9161 100644 --- a/projectmanagers/qmake/qmakeprojectfile.cpp +++ b/projectmanagers/qmake/qmakeprojectfile.cpp @@ -1,390 +1,428 @@ /* KDevelop QMake Support * * Copyright 2006 Andreas Pakulat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "qmakeprojectfile.h" #include #include #include #include #include "debug.h" #include "parser/ast.h" #include "qmakecache.h" #include "qmakemkspecs.h" #include "qmakeconfig.h" #include #include #define ifDebug(x) QHash> QMakeProjectFile::m_qmakeQueryCache; const QStringList QMakeProjectFile::FileVariables = QStringList() << "IDLS" << "RESOURCES" << "IMAGES" << "LEXSOURCES" << "DISTFILES" << "YACCSOURCES" << "TRANSLATIONS" << "HEADERS" << "SOURCES" << "INTERFACES" << "FORMS"; QMakeProjectFile::QMakeProjectFile(const QString& projectfile) : QMakeFile(projectfile) , m_mkspecs(nullptr) , m_cache(nullptr) { } void QMakeProjectFile::setQMakeCache(QMakeCache* cache) { m_cache = cache; } void QMakeProjectFile::setMkSpecs(QMakeMkSpecs* mkspecs) { m_mkspecs = mkspecs; } bool QMakeProjectFile::read() { // default values // NOTE: if we already have such a var, e.g. in an include file, we must not overwrite it here! if (!m_variableValues.contains("QT")) { m_variableValues["QT"] = QStringList() << "core" << "gui"; } if (!m_variableValues.contains("CONFIG")) { m_variableValues["CONFIG"] = QStringList() << "qt"; } Q_ASSERT(m_mkspecs); foreach (const QString& var, m_mkspecs->variables()) { if (!m_variableValues.contains(var)) { m_variableValues[var] = m_mkspecs->variableValues(var); } } if (m_cache) { foreach (const QString& var, m_cache->variables()) { if (!m_variableValues.contains(var)) { m_variableValues[var] = m_cache->variableValues(var); } } } /// TODO: more special variables m_variableValues["PWD"] = QStringList() << pwd(); m_variableValues["_PRO_FILE_"] = QStringList() << proFile(); m_variableValues["_PRO_FILE_PWD_"] = QStringList() << proFilePwd(); m_variableValues["OUT_PWD"] = QStringList() << outPwd(); const QString qtInstallHeaders = QStringLiteral("QT_INSTALL_HEADERS"); const QString qtVersion = QStringLiteral("QT_VERSION"); + const QString qtInstallLibs = QStringLiteral("QT_INSTALL_LIBS"); const QString binary = QMakeConfig::qmakeBinary(project()); if (!m_qmakeQueryCache.contains(binary)) { - const auto queryResult = QMakeConfig::queryQMake(binary, {qtInstallHeaders, qtVersion}); + const auto queryResult = QMakeConfig::queryQMake(binary, {qtInstallHeaders, qtVersion, qtInstallLibs}); if (queryResult.isEmpty()) { qCWarning(KDEV_QMAKE) << "Failed to query qmake - bad qmake binary configured?" << binary; } m_qmakeQueryCache[binary] = queryResult; } const auto cachedQueryResult = m_qmakeQueryCache.value(binary); m_qtIncludeDir = cachedQueryResult.value(qtInstallHeaders); m_qtVersion = cachedQueryResult.value(qtVersion); + m_qtLibDir = cachedQueryResult.value(qtInstallLibs); return QMakeFile::read(); } QStringList QMakeProjectFile::subProjects() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching subprojects";) QStringList list; foreach (QString subdir, variableValues("SUBDIRS")) { QString fileOrPath; ifDebug(qCDebug(KDEV_QMAKE) << "Found value:" << subdir;) if (containsVariable(subdir + ".file") && !variableValues(subdir + ".file").isEmpty()) { subdir = variableValues(subdir + ".file").first(); } else if (containsVariable(subdir + ".subdir") && !variableValues(subdir + ".subdir").isEmpty()) { subdir = variableValues(subdir + ".subdir").first(); } if (subdir.endsWith(".pro")) { fileOrPath = resolveToSingleFileName(subdir.trimmed()); } else { fileOrPath = resolveToSingleFileName(subdir.trimmed()); } if (fileOrPath.isEmpty()) { qCWarning(KDEV_QMAKE) << "could not resolve subdir" << subdir << "to file or path, skipping"; continue; } list << fileOrPath; } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "subprojects";) return list; } bool QMakeProjectFile::hasSubProject(const QString& file) const { foreach (const QString& sub, subProjects()) { if (sub == file) { return true; } else if (QFileInfo(file).absoluteDir() == sub) { return true; } } return false; } void QMakeProjectFile::addPathsForVariable(const QString& variable, QStringList* list) const { const QStringList values = variableValues(variable); ifDebug(qCDebug(KDEV_QMAKE) << variable << values;) foreach (const QString& val, values) { QString path = resolveToSingleFileName(val); if (!path.isEmpty() && !list->contains(val)) { list->append(path); } } } QStringList QMakeProjectFile::includeDirectories() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching include dirs" << m_qtIncludeDir;) ifDebug(qCDebug(KDEV_QMAKE) << "CONFIG" << variableValues("CONFIG");) QStringList list; addPathsForVariable("INCLUDEPATH", &list); addPathsForVariable("QMAKE_INCDIR", &list); if (variableValues("CONFIG").contains("opengl")) { addPathsForVariable("QMAKE_INCDIR_OPENGL", &list); } if (variableValues("CONFIG").contains("qt")) { if (!list.contains(m_qtIncludeDir)) list << m_qtIncludeDir; QDir incDir(m_qtIncludeDir); auto modules = variableValues("QT"); if (!modules.isEmpty() && !modules.contains("core")) { // TODO: proper dependency tracking of modules // for now, at least include core if we include any other module modules << "core"; } // TODO: This is all very fragile, should rather read QMake module .pri files (e.g. qt_lib_core_private.pri) foreach (const QString& module, modules) { QString pattern = module; bool isPrivate = false; if (module.endsWith("-private")) { pattern.chop(qstrlen("-private")); isPrivate = true; } else if (module.endsWith("_private")) { // _private is less common, but still a valid suffix pattern.chop(qstrlen("_private")); isPrivate = true; } if (pattern == "qtestlib" || pattern == "testlib") { pattern = "QtTest"; } else if (pattern == "qaxcontainer") { pattern = "ActiveQt"; } else if (pattern == "qaxserver") { pattern = "ActiveQt"; } QFileInfoList match = incDir.entryInfoList({QString("Qt%1").arg(pattern)}, QDir::Dirs); if (match.isEmpty()) { // try non-prefixed pattern match = incDir.entryInfoList({pattern}, QDir::Dirs); if (match.isEmpty()) { qCWarning(KDEV_QMAKE) << "unhandled Qt module:" << module << pattern; continue; } } QString path = match.first().canonicalFilePath(); if (isPrivate) { path += '/' + m_qtVersion + '/' + match.first().fileName() + "/private/"; } if (!list.contains(path)) { list << path; } } } if (variableValues("CONFIG").contains("thread")) { addPathsForVariable("QMAKE_INCDIR_THREAD", &list); } if (variableValues("CONFIG").contains("x11")) { addPathsForVariable("QMAKE_INCDIR_X11", &list); } addPathsForVariable("MOC_DIR", &list); addPathsForVariable("OBJECTS_DIR", &list); addPathsForVariable("UI_DIR", &list); ifDebug(qCDebug(KDEV_QMAKE) << "final list:" << list;) return list; } +// Scan QMAKE_C*FLAGS for -F and -iframework and QMAKE_LFLAGS for good measure. Time will +// tell if we need to scan the release/debug/... specific versions of QMAKE_C*FLAGS. +// Also include QT_INSTALL_LIBS which corresponds to Qt's framework directory on OS X. +QStringList QMakeProjectFile::frameworkDirectories() const +{ + const auto variablesToCheck = {QStringLiteral("QMAKE_CFLAGS"), + QStringLiteral("QMAKE_CXXFLAGS"), + QStringLiteral("QMAKE_LFLAGS")}; + const QLatin1String fOption("-F"); + const QLatin1String iframeworkOption("-iframework"); + QStringList fwDirs; + foreach (const auto& var, variablesToCheck) { + bool storeArg = false; + foreach (const auto& arg, variableValues(var)) { + if (arg == fOption || arg == iframeworkOption) { + // detached -F/-iframework arg; set a warrant to store the next argument + storeArg = true; + } else { + if (arg.startsWith(fOption)) { + fwDirs << arg.mid(fOption.size()); + } else if (arg.startsWith(iframeworkOption)) { + fwDirs << arg.mid(iframeworkOption.size()); + } else if (storeArg) { + fwDirs << arg; + } + // cancel any outstanding warrants to store the next argument + storeArg = false; + } + } + } +#ifdef Q_OS_OSX + fwDirs << m_qtLibDir; +#endif + return fwDirs; +} + QStringList QMakeProjectFile::files() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching files";) QStringList list; foreach (const QString& variable, QMakeProjectFile::FileVariables) { foreach (const QString& value, variableValues(variable)) { list += resolveFileName(value); } } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "files";) return list; } QStringList QMakeProjectFile::filesForTarget(const QString& s) const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching files";) QStringList list; if (variableValues("INSTALLS").contains(s)) { const QStringList files = variableValues(s + ".files"); if (!files.isEmpty()) { foreach (const QString& val, files) { list += QStringList(resolveFileName(val)); } } } if (!variableValues("INSTALLS").contains(s) || s == "target") { foreach (const QString& variable, QMakeProjectFile::FileVariables) { foreach (const QString& value, variableValues(variable)) { list += QStringList(resolveFileName(value)); } } } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "files";) return list; } QString QMakeProjectFile::getTemplate() const { QString templ = "app"; if (!variableValues("TEMPLATE").isEmpty()) { templ = variableValues("TEMPLATE").first(); } return templ; } QStringList QMakeProjectFile::targets() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching targets";) QStringList list; list += variableValues("TARGET"); if (list.isEmpty() && getTemplate() != "subdirs") { list += QFileInfo(absoluteFile()).baseName(); } foreach (const QString& target, variableValues("INSTALLS")) { if (!target.isEmpty() && target != "target") list << target; } if (list.removeAll(QString())) { // remove empty targets - which is probably a bug... qCWarning(KDEV_QMAKE) << "got empty entry in TARGET of file" << absoluteFile(); } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "targets";) return list; } QMakeProjectFile::~QMakeProjectFile() { // TODO: delete cache, specs, ...? } QStringList QMakeProjectFile::resolveVariable(const QString& variable, VariableInfo::VariableType type) const { if (type == VariableInfo::QtConfigVariable) { if (m_mkspecs->isQMakeInternalVariable(variable)) { return QStringList() << m_mkspecs->qmakeInternalVariable(variable); } else { qCWarning(KDEV_QMAKE) << "unknown QtConfig Variable:" << variable; return QStringList(); } } return QMakeFile::resolveVariable(variable, type); } QMakeMkSpecs* QMakeProjectFile::mkSpecs() const { return m_mkspecs; } QMakeCache* QMakeProjectFile::qmakeCache() const { return m_cache; } QList QMakeProjectFile::defines() const { QList d; foreach (QString def, variableMap()["DEFINES"]) { int pos = def.indexOf('='); if (pos >= 0) { // a value is attached to define d.append(DefinePair(def.left(pos), def.right(def.length() - (pos + 1)))); } else { // a value-less define d.append(DefinePair(def, "")); } } return d; } QString QMakeProjectFile::pwd() const { return absoluteDir(); } QString QMakeProjectFile::outPwd() const { if (!project()) { return absoluteDir(); } else { return QMakeConfig::buildDirFromSrc(project(), KDevelop::Path(absoluteDir())).toLocalFile(); } } QString QMakeProjectFile::proFile() const { return absoluteFile(); } QString QMakeProjectFile::proFilePwd() const { return absoluteDir(); } diff --git a/projectmanagers/qmake/qmakeprojectfile.h b/projectmanagers/qmake/qmakeprojectfile.h index 027800c36a..d7f493d7d3 100644 --- a/projectmanagers/qmake/qmakeprojectfile.h +++ b/projectmanagers/qmake/qmakeprojectfile.h @@ -1,86 +1,89 @@ /* KDevelop QMake Support * * Copyright 2006 Andreas Pakulat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef QMAKEPROJECTFILE_H #define QMAKEPROJECTFILE_H #include "qmakefile.h" class QMakeMkSpecs; template class QList; class QMakeMkSpecs; class QMakeCache; namespace KDevelop { class IProject; } class QMakeProjectFile : public QMakeFile { public: typedef QPair< QString, QString > DefinePair; static const QStringList FileVariables; QMakeProjectFile( const QString& projectfile ); ~QMakeProjectFile(); bool read() override; QStringList subProjects() const; bool hasSubProject(const QString& file) const; QStringList files() const; QStringList filesForTarget( const QString& ) const; QStringList includeDirectories() const; + QStringList frameworkDirectories() const; QStringList targets() const; QString getTemplate() const; void setMkSpecs( QMakeMkSpecs* mkspecs ); QMakeMkSpecs* mkSpecs() const; void setQMakeCache( QMakeCache* cache ); QMakeCache* qmakeCache() const; virtual QStringList resolveVariable(const QString& variable, VariableInfo::VariableType type) const override; QList< DefinePair > defines() const; /// current pwd, e.g. absoluteDir even for included files virtual QString pwd() const; /// path to build dir for the current .pro file virtual QString outPwd() const; /// path to dir of current .pro file virtual QString proFilePwd() const; /// path to current .pro file virtual QString proFile() const; private: void addPathsForVariable(const QString& variable, QStringList* list) const; QMakeMkSpecs* m_mkspecs; QMakeCache* m_cache; static QHash > m_qmakeQueryCache; QString m_qtIncludeDir; QString m_qtVersion; + // On OS X, QT_INSTALL_LIBS is typically a framework directory and should thus be added to the framework search path + QString m_qtLibDir; }; #endif