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 | |