| 1 | //===--- GlobalCompilationDatabase.h -----------------------------*- C++-*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H |
| 10 | #define |
| 11 | |
| 12 | #include "ProjectModules.h" |
| 13 | #include "support/Function.h" |
| 14 | #include "support/Path.h" |
| 15 | #include "support/Threading.h" |
| 16 | #include "support/ThreadsafeFS.h" |
| 17 | #include "clang/Tooling/ArgumentsAdjusters.h" |
| 18 | #include "clang/Tooling/CompilationDatabase.h" |
| 19 | #include "llvm/ADT/FunctionExtras.h" |
| 20 | #include "llvm/ADT/StringMap.h" |
| 21 | #include <memory> |
| 22 | #include <mutex> |
| 23 | #include <optional> |
| 24 | #include <vector> |
| 25 | |
| 26 | namespace clang { |
| 27 | namespace clangd { |
| 28 | |
| 29 | struct ProjectInfo { |
| 30 | // The directory in which the compilation database was discovered. |
| 31 | // Empty if directory-based compilation database discovery was not used. |
| 32 | std::string SourceRoot; |
| 33 | }; |
| 34 | |
| 35 | /// Provides compilation arguments used for parsing C and C++ files. |
| 36 | class GlobalCompilationDatabase { |
| 37 | public: |
| 38 | virtual ~GlobalCompilationDatabase() = default; |
| 39 | |
| 40 | /// If there are any known-good commands for building this file, returns one. |
| 41 | virtual std::optional<tooling::CompileCommand> |
| 42 | getCompileCommand(PathRef File) const = 0; |
| 43 | |
| 44 | /// Finds the closest project to \p File. |
| 45 | virtual std::optional<ProjectInfo> getProjectInfo(PathRef File) const { |
| 46 | return std::nullopt; |
| 47 | } |
| 48 | |
| 49 | /// Get the modules in the closest project to \p File |
| 50 | virtual std::unique_ptr<ProjectModules> |
| 51 | getProjectModules(PathRef File) const { |
| 52 | return nullptr; |
| 53 | } |
| 54 | |
| 55 | /// Makes a guess at how to build a file. |
| 56 | /// The default implementation just runs clang on the file. |
| 57 | /// Clangd should treat the results as unreliable. |
| 58 | virtual tooling::CompileCommand getFallbackCommand(PathRef File) const; |
| 59 | |
| 60 | /// If the CDB does any asynchronous work, wait for it to complete. |
| 61 | /// For use in tests. |
| 62 | virtual bool blockUntilIdle(Deadline D) const { return true; } |
| 63 | |
| 64 | using CommandChanged = Event<std::vector<std::string>>; |
| 65 | /// The callback is notified when files may have new compile commands. |
| 66 | /// The argument is a list of full file paths. |
| 67 | CommandChanged::Subscription watch(CommandChanged::Listener L) const { |
| 68 | return OnCommandChanged.observe(L: std::move(L)); |
| 69 | } |
| 70 | |
| 71 | protected: |
| 72 | mutable CommandChanged OnCommandChanged; |
| 73 | }; |
| 74 | |
| 75 | // Helper class for implementing GlobalCompilationDatabases that wrap others. |
| 76 | class DelegatingCDB : public GlobalCompilationDatabase { |
| 77 | public: |
| 78 | DelegatingCDB(const GlobalCompilationDatabase *Base); |
| 79 | DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base); |
| 80 | |
| 81 | std::optional<tooling::CompileCommand> |
| 82 | getCompileCommand(PathRef File) const override; |
| 83 | |
| 84 | std::optional<ProjectInfo> getProjectInfo(PathRef File) const override; |
| 85 | |
| 86 | std::unique_ptr<ProjectModules> |
| 87 | getProjectModules(PathRef File) const override; |
| 88 | |
| 89 | tooling::CompileCommand getFallbackCommand(PathRef File) const override; |
| 90 | |
| 91 | bool blockUntilIdle(Deadline D) const override; |
| 92 | |
| 93 | private: |
| 94 | const GlobalCompilationDatabase *Base; |
| 95 | std::unique_ptr<GlobalCompilationDatabase> BaseOwner; |
| 96 | CommandChanged::Subscription BaseChanged; |
| 97 | }; |
| 98 | |
| 99 | /// Gets compile args from tooling::CompilationDatabases built for parent |
| 100 | /// directories. |
| 101 | class DirectoryBasedGlobalCompilationDatabase |
| 102 | : public GlobalCompilationDatabase { |
| 103 | public: |
| 104 | struct Options { |
| 105 | Options(const ThreadsafeFS &TFS) : TFS(TFS) {} |
| 106 | |
| 107 | const ThreadsafeFS &TFS; |
| 108 | // Frequency to check whether e.g. compile_commands.json has changed. |
| 109 | std::chrono::steady_clock::duration RevalidateAfter = |
| 110 | std::chrono::seconds(5); |
| 111 | // Frequency to check whether e.g. compile_commands.json has been created. |
| 112 | // (This is more expensive to check frequently, as we check many locations). |
| 113 | std::chrono::steady_clock::duration RevalidateMissingAfter = |
| 114 | std::chrono::seconds(30); |
| 115 | // Used to provide per-file configuration. |
| 116 | std::function<Context(llvm::StringRef)> ContextProvider; |
| 117 | // Only look for a compilation database in this one fixed directory. |
| 118 | // FIXME: fold this into config/context mechanism. |
| 119 | std::optional<Path> CompileCommandsDir; |
| 120 | }; |
| 121 | |
| 122 | DirectoryBasedGlobalCompilationDatabase(const Options &Opts); |
| 123 | ~DirectoryBasedGlobalCompilationDatabase() override; |
| 124 | |
| 125 | /// Scans File's parents looking for compilation databases. |
| 126 | /// Any extra flags will be added. |
| 127 | /// Might trigger OnCommandChanged, if CDB wasn't broadcasted yet. |
| 128 | std::optional<tooling::CompileCommand> |
| 129 | getCompileCommand(PathRef File) const override; |
| 130 | |
| 131 | /// Returns the path to first directory containing a compilation database in |
| 132 | /// \p File's parents. |
| 133 | std::optional<ProjectInfo> getProjectInfo(PathRef File) const override; |
| 134 | |
| 135 | std::unique_ptr<ProjectModules> |
| 136 | getProjectModules(PathRef File) const override; |
| 137 | |
| 138 | bool blockUntilIdle(Deadline Timeout) const override; |
| 139 | |
| 140 | private: |
| 141 | Options Opts; |
| 142 | |
| 143 | class DirectoryCache; |
| 144 | // Keyed by possibly-case-folded directory path. |
| 145 | // We can hand out pointers as they're stable and entries are never removed. |
| 146 | mutable llvm::StringMap<DirectoryCache> DirCaches; |
| 147 | mutable std::mutex DirCachesMutex; |
| 148 | |
| 149 | std::vector<DirectoryCache *> |
| 150 | getDirectoryCaches(llvm::ArrayRef<llvm::StringRef> Dirs) const; |
| 151 | |
| 152 | struct CDBLookupRequest { |
| 153 | PathRef FileName; |
| 154 | // Whether this lookup should trigger discovery of the CDB found. |
| 155 | bool ShouldBroadcast = false; |
| 156 | // Cached results newer than this are considered fresh and not checked |
| 157 | // against disk. |
| 158 | std::chrono::steady_clock::time_point FreshTime; |
| 159 | std::chrono::steady_clock::time_point FreshTimeMissing; |
| 160 | }; |
| 161 | struct CDBLookupResult { |
| 162 | std::shared_ptr<const tooling::CompilationDatabase> CDB; |
| 163 | ProjectInfo PI; |
| 164 | }; |
| 165 | std::optional<CDBLookupResult> lookupCDB(CDBLookupRequest Request) const; |
| 166 | |
| 167 | class BroadcastThread; |
| 168 | std::unique_ptr<BroadcastThread> Broadcaster; |
| 169 | |
| 170 | // Performs broadcast on governed files. |
| 171 | void broadcastCDB(CDBLookupResult Res) const; |
| 172 | |
| 173 | // cache test calls lookupCDB directly to ensure valid/invalid times. |
| 174 | friend class DirectoryBasedGlobalCompilationDatabaseCacheTest; |
| 175 | }; |
| 176 | |
| 177 | /// Extracts system include search path from drivers matching QueryDriverGlobs |
| 178 | /// and adds them to the compile flags. |
| 179 | /// Returns null when \p QueryDriverGlobs is empty. |
| 180 | using = llvm::unique_function<void( |
| 181 | tooling::CompileCommand &, llvm::StringRef) const>; |
| 182 | SystemIncludeExtractorFn |
| 183 | (llvm::ArrayRef<std::string> QueryDriverGlobs); |
| 184 | |
| 185 | /// Wraps another compilation database, and supports overriding the commands |
| 186 | /// using an in-memory mapping. |
| 187 | class OverlayCDB : public DelegatingCDB { |
| 188 | public: |
| 189 | // Makes adjustments to a tooling::CompileCommand which will be used to |
| 190 | // process a file (possibly different from the one in the command). |
| 191 | using CommandMangler = llvm::unique_function<void(tooling::CompileCommand &, |
| 192 | StringRef File) const>; |
| 193 | |
| 194 | // Base may be null, in which case no entries are inherited. |
| 195 | // FallbackFlags are added to the fallback compile command. |
| 196 | // Adjuster is applied to all commands, fallback or not. |
| 197 | OverlayCDB(const GlobalCompilationDatabase *Base, |
| 198 | std::vector<std::string> FallbackFlags = {}, |
| 199 | CommandMangler Mangler = nullptr); |
| 200 | |
| 201 | std::optional<tooling::CompileCommand> |
| 202 | getCompileCommand(PathRef File) const override; |
| 203 | tooling::CompileCommand getFallbackCommand(PathRef File) const override; |
| 204 | |
| 205 | /// Sets or clears the compilation command for a particular file. |
| 206 | /// Returns true if the command was changed (including insertion and removal), |
| 207 | /// false if it was unchanged. |
| 208 | bool |
| 209 | setCompileCommand(PathRef File, |
| 210 | std::optional<tooling::CompileCommand> CompilationCommand); |
| 211 | |
| 212 | std::unique_ptr<ProjectModules> |
| 213 | getProjectModules(PathRef File) const override; |
| 214 | |
| 215 | private: |
| 216 | mutable std::mutex Mutex; |
| 217 | llvm::StringMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */ |
| 218 | CommandMangler Mangler; |
| 219 | std::vector<std::string> FallbackFlags; |
| 220 | }; |
| 221 | |
| 222 | } // namespace clangd |
| 223 | } // namespace clang |
| 224 | |
| 225 | #endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H |
| 226 | |