Mercurial > hg > CbC > CbC_llvm
comparison clang-tools-extra/clangd/GlobalCompilationDatabase.cpp @ 221:79ff65ed7e25
LLVM12 Original
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Tue, 15 Jun 2021 19:15:29 +0900 |
parents | 0572611fdcc8 |
children | c4bab56944e8 |
comparison
equal
deleted
inserted
replaced
220:42394fc6a535 | 221:79ff65ed7e25 |
---|---|
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 // | 6 // |
7 //===----------------------------------------------------------------------===// | 7 //===----------------------------------------------------------------------===// |
8 | 8 |
9 #include "GlobalCompilationDatabase.h" | 9 #include "GlobalCompilationDatabase.h" |
10 #include "Config.h" | |
10 #include "FS.h" | 11 #include "FS.h" |
12 #include "SourceCode.h" | |
11 #include "support/Logger.h" | 13 #include "support/Logger.h" |
12 #include "support/Path.h" | 14 #include "support/Path.h" |
15 #include "support/Threading.h" | |
16 #include "support/ThreadsafeFS.h" | |
13 #include "clang/Frontend/CompilerInvocation.h" | 17 #include "clang/Frontend/CompilerInvocation.h" |
14 #include "clang/Tooling/ArgumentsAdjusters.h" | 18 #include "clang/Tooling/ArgumentsAdjusters.h" |
15 #include "clang/Tooling/CompilationDatabase.h" | 19 #include "clang/Tooling/CompilationDatabase.h" |
20 #include "clang/Tooling/CompilationDatabasePluginRegistry.h" | |
21 #include "clang/Tooling/JSONCompilationDatabase.h" | |
16 #include "llvm/ADT/None.h" | 22 #include "llvm/ADT/None.h" |
17 #include "llvm/ADT/Optional.h" | 23 #include "llvm/ADT/Optional.h" |
24 #include "llvm/ADT/PointerIntPair.h" | |
18 #include "llvm/ADT/STLExtras.h" | 25 #include "llvm/ADT/STLExtras.h" |
26 #include "llvm/ADT/ScopeExit.h" | |
19 #include "llvm/ADT/SmallString.h" | 27 #include "llvm/ADT/SmallString.h" |
28 #include "llvm/ADT/StringMap.h" | |
20 #include "llvm/Support/FileSystem.h" | 29 #include "llvm/Support/FileSystem.h" |
21 #include "llvm/Support/FileUtilities.h" | 30 #include "llvm/Support/FileUtilities.h" |
22 #include "llvm/Support/Path.h" | 31 #include "llvm/Support/Path.h" |
23 #include "llvm/Support/Program.h" | 32 #include "llvm/Support/Program.h" |
33 #include "llvm/Support/VirtualFileSystem.h" | |
34 #include <atomic> | |
35 #include <chrono> | |
36 #include <condition_variable> | |
37 #include <mutex> | |
24 #include <string> | 38 #include <string> |
25 #include <tuple> | 39 #include <tuple> |
26 #include <vector> | 40 #include <vector> |
27 | 41 |
28 namespace clang { | 42 namespace clang { |
31 | 45 |
32 // Runs the given action on all parent directories of filename, starting from | 46 // Runs the given action on all parent directories of filename, starting from |
33 // deepest directory and going up to root. Stops whenever action succeeds. | 47 // deepest directory and going up to root. Stops whenever action succeeds. |
34 void actOnAllParentDirectories(PathRef FileName, | 48 void actOnAllParentDirectories(PathRef FileName, |
35 llvm::function_ref<bool(PathRef)> Action) { | 49 llvm::function_ref<bool(PathRef)> Action) { |
36 for (auto Path = llvm::sys::path::parent_path(FileName); | 50 for (auto Path = absoluteParent(FileName); !Path.empty() && !Action(Path); |
37 !Path.empty() && !Action(Path); | 51 Path = absoluteParent(Path)) |
38 Path = llvm::sys::path::parent_path(Path)) | |
39 ; | 52 ; |
40 } | 53 } |
41 | 54 |
42 } // namespace | 55 } // namespace |
43 | 56 |
56 /*Output=*/""); | 69 /*Output=*/""); |
57 Cmd.Heuristic = "clangd fallback"; | 70 Cmd.Heuristic = "clangd fallback"; |
58 return Cmd; | 71 return Cmd; |
59 } | 72 } |
60 | 73 |
74 // Loads and caches the CDB from a single directory. | |
75 // | |
76 // This class is threadsafe, which is to say we have independent locks for each | |
77 // directory we're searching for a CDB. | |
78 // Loading is deferred until first access. | |
79 // | |
80 // The DirectoryBasedCDB keeps a map from path => DirectoryCache. | |
81 // Typical usage is to: | |
82 // - 1) determine all the paths that might be searched | |
83 // - 2) acquire the map lock and get-or-create all the DirectoryCache entries | |
84 // - 3) release the map lock and query the caches as desired | |
85 class DirectoryBasedGlobalCompilationDatabase::DirectoryCache { | |
86 using stopwatch = std::chrono::steady_clock; | |
87 | |
88 // CachedFile is used to read a CDB file on disk (e.g. compile_commands.json). | |
89 // It specializes in being able to quickly bail out if the file is unchanged, | |
90 // which is the common case. | |
91 // Internally, it stores file metadata so a stat() can verify it's unchanged. | |
92 // We don't actually cache the content as it's not needed - if the file is | |
93 // unchanged then the previous CDB is valid. | |
94 struct CachedFile { | |
95 CachedFile(llvm::StringRef Parent, llvm::StringRef Rel) { | |
96 llvm::SmallString<256> Path = Parent; | |
97 llvm::sys::path::append(Path, Rel); | |
98 this->Path = Path.str().str(); | |
99 } | |
100 std::string Path; | |
101 size_t Size = NoFileCached; | |
102 llvm::sys::TimePoint<> ModifiedTime; | |
103 FileDigest ContentHash; | |
104 | |
105 static constexpr size_t NoFileCached = -1; | |
106 | |
107 struct LoadResult { | |
108 enum { | |
109 FileNotFound, | |
110 TransientError, | |
111 FoundSameData, | |
112 FoundNewData, | |
113 } Result; | |
114 std::unique_ptr<llvm::MemoryBuffer> Buffer; // Set only if FoundNewData | |
115 }; | |
116 | |
117 LoadResult load(llvm::vfs::FileSystem &FS, bool HasOldData); | |
118 }; | |
119 | |
120 // If we've looked for a CDB here and found none, the time when that happened. | |
121 // (Atomics make it possible for get() to return without taking a lock) | |
122 std::atomic<stopwatch::rep> NoCDBAt = { | |
123 stopwatch::time_point::min().time_since_epoch().count()}; | |
124 | |
125 // Guards the following cache state. | |
126 std::mutex Mu; | |
127 // When was the cache last known to be in sync with disk state? | |
128 stopwatch::time_point CachePopulatedAt = stopwatch::time_point::min(); | |
129 // Whether a new CDB has been loaded but not broadcast yet. | |
130 bool NeedsBroadcast = false; | |
131 // Last loaded CDB, meaningful if CachePopulatedAt was ever set. | |
132 // shared_ptr so we can overwrite this when callers are still using the CDB. | |
133 std::shared_ptr<tooling::CompilationDatabase> CDB; | |
134 // File metadata for the CDB files we support tracking directly. | |
135 CachedFile CompileCommandsJson; | |
136 CachedFile BuildCompileCommandsJson; | |
137 CachedFile CompileFlagsTxt; | |
138 // CachedFile member corresponding to CDB. | |
139 // CDB | ACF | Scenario | |
140 // null | null | no CDB found, or initial empty cache | |
141 // set | null | CDB was loaded via generic plugin interface | |
142 // null | set | found known CDB file, but parsing it failed | |
143 // set | set | CDB was parsed from a known file | |
144 CachedFile *ActiveCachedFile = nullptr; | |
145 | |
146 public: | |
147 DirectoryCache(llvm::StringRef Path) | |
148 : CompileCommandsJson(Path, "compile_commands.json"), | |
149 BuildCompileCommandsJson(Path, "build/compile_commands.json"), | |
150 CompileFlagsTxt(Path, "compile_flags.txt"), Path(Path) { | |
151 assert(llvm::sys::path::is_absolute(Path)); | |
152 } | |
153 | |
154 // Absolute canonical path that we're the cache for. (Not case-folded). | |
155 const std::string Path; | |
156 | |
157 // Get the CDB associated with this directory. | |
158 // ShouldBroadcast: | |
159 // - as input, signals whether the caller is willing to broadcast a | |
160 // newly-discovered CDB. (e.g. to trigger background indexing) | |
161 // - as output, signals whether the caller should do so. | |
162 // (If a new CDB is discovered and ShouldBroadcast is false, we mark the | |
163 // CDB as needing broadcast, and broadcast it next time we can). | |
164 std::shared_ptr<const tooling::CompilationDatabase> | |
165 get(const ThreadsafeFS &TFS, bool &ShouldBroadcast, | |
166 stopwatch::time_point FreshTime, stopwatch::time_point FreshTimeMissing) { | |
167 // Fast path for common case without taking lock. | |
168 if (stopwatch::time_point(stopwatch::duration(NoCDBAt.load())) > | |
169 FreshTimeMissing) { | |
170 ShouldBroadcast = false; | |
171 return nullptr; | |
172 } | |
173 | |
174 std::lock_guard<std::mutex> Lock(Mu); | |
175 auto RequestBroadcast = llvm::make_scope_exit([&, OldCDB(CDB.get())] { | |
176 // If we loaded a new CDB, it should be broadcast at some point. | |
177 if (CDB != nullptr && CDB.get() != OldCDB) | |
178 NeedsBroadcast = true; | |
179 else if (CDB == nullptr) // nothing to broadcast anymore! | |
180 NeedsBroadcast = false; | |
181 // If we have something to broadcast, then do so iff allowed. | |
182 if (!ShouldBroadcast) | |
183 return; | |
184 ShouldBroadcast = NeedsBroadcast; | |
185 NeedsBroadcast = false; | |
186 }); | |
187 | |
188 // If our cache is valid, serve from it. | |
189 if (CachePopulatedAt > FreshTime) | |
190 return CDB; | |
191 | |
192 if (/*MayCache=*/load(*TFS.view(/*CWD=*/llvm::None))) { | |
193 // Use new timestamp, as loading may be slow. | |
194 CachePopulatedAt = stopwatch::now(); | |
195 NoCDBAt.store((CDB ? stopwatch::time_point::min() : CachePopulatedAt) | |
196 .time_since_epoch() | |
197 .count()); | |
198 } | |
199 | |
200 return CDB; | |
201 } | |
202 | |
203 private: | |
204 // Updates `CDB` from disk state. Returns false on failure. | |
205 bool load(llvm::vfs::FileSystem &FS); | |
206 }; | |
207 | |
208 DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::LoadResult | |
209 DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::load( | |
210 llvm::vfs::FileSystem &FS, bool HasOldData) { | |
211 auto Stat = FS.status(Path); | |
212 if (!Stat || !Stat->isRegularFile()) { | |
213 Size = NoFileCached; | |
214 ContentHash = {}; | |
215 return {LoadResult::FileNotFound, nullptr}; | |
216 } | |
217 // If both the size and mtime match, presume unchanged without reading. | |
218 if (HasOldData && Stat->getLastModificationTime() == ModifiedTime && | |
219 Stat->getSize() == Size) | |
220 return {LoadResult::FoundSameData, nullptr}; | |
221 auto Buf = FS.getBufferForFile(Path); | |
222 if (!Buf || (*Buf)->getBufferSize() != Stat->getSize()) { | |
223 // Don't clear the cache - possible we're seeing inconsistent size as the | |
224 // file is being recreated. If it ends up identical later, great! | |
225 // | |
226 // This isn't a complete solution: if we see a partial file but stat/read | |
227 // agree on its size, we're ultimately going to have spurious CDB reloads. | |
228 // May be worth fixing if generators don't write atomically (CMake does). | |
229 elog("Failed to read {0}: {1}", Path, | |
230 Buf ? "size changed" : Buf.getError().message()); | |
231 return {LoadResult::TransientError, nullptr}; | |
232 } | |
233 | |
234 FileDigest NewContentHash = digest((*Buf)->getBuffer()); | |
235 if (HasOldData && NewContentHash == ContentHash) { | |
236 // mtime changed but data is the same: avoid rebuilding the CDB. | |
237 ModifiedTime = Stat->getLastModificationTime(); | |
238 return {LoadResult::FoundSameData, nullptr}; | |
239 } | |
240 | |
241 Size = (*Buf)->getBufferSize(); | |
242 ModifiedTime = Stat->getLastModificationTime(); | |
243 ContentHash = NewContentHash; | |
244 return {LoadResult::FoundNewData, std::move(*Buf)}; | |
245 } | |
246 | |
247 // Adapt CDB-loading functions to a common interface for DirectoryCache::load(). | |
248 static std::unique_ptr<tooling::CompilationDatabase> | |
249 parseJSON(PathRef Path, llvm::StringRef Data, std::string &Error) { | |
250 if (auto CDB = tooling::JSONCompilationDatabase::loadFromBuffer( | |
251 Data, Error, tooling::JSONCommandLineSyntax::AutoDetect)) { | |
252 // FS used for expanding response files. | |
253 // FIXME: ExpandResponseFilesDatabase appears not to provide the usual | |
254 // thread-safety guarantees, as the access to FS is not locked! | |
255 // For now, use the real FS, which is known to be threadsafe (if we don't | |
256 // use/change working directory, which ExpandResponseFilesDatabase doesn't). | |
257 auto FS = llvm::vfs::getRealFileSystem(); | |
258 return tooling::inferTargetAndDriverMode( | |
259 tooling::inferMissingCompileCommands( | |
260 expandResponseFiles(std::move(CDB), std::move(FS)))); | |
261 } | |
262 return nullptr; | |
263 } | |
264 static std::unique_ptr<tooling::CompilationDatabase> | |
265 parseFixed(PathRef Path, llvm::StringRef Data, std::string &Error) { | |
266 return tooling::FixedCompilationDatabase::loadFromBuffer( | |
267 llvm::sys::path::parent_path(Path), Data, Error); | |
268 } | |
269 | |
270 bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load( | |
271 llvm::vfs::FileSystem &FS) { | |
272 dlog("Probing directory {0}", Path); | |
273 std::string Error; | |
274 | |
275 // Load from the specially-supported compilation databases (JSON + Fixed). | |
276 // For these, we know the files they read and cache their metadata so we can | |
277 // cheaply validate whether they've changed, and hot-reload if they have. | |
278 // (As a bonus, these are also VFS-clean)! | |
279 struct CDBFile { | |
280 CachedFile *File; | |
281 // Wrapper for {Fixed,JSON}CompilationDatabase::loadFromBuffer. | |
282 llvm::function_ref<std::unique_ptr<tooling::CompilationDatabase>( | |
283 PathRef, | |
284 /*Data*/ llvm::StringRef, | |
285 /*ErrorMsg*/ std::string &)> | |
286 Parser; | |
287 }; | |
288 for (const auto &Entry : {CDBFile{&CompileCommandsJson, parseJSON}, | |
289 CDBFile{&BuildCompileCommandsJson, parseJSON}, | |
290 CDBFile{&CompileFlagsTxt, parseFixed}}) { | |
291 bool Active = ActiveCachedFile == Entry.File; | |
292 auto Loaded = Entry.File->load(FS, Active); | |
293 switch (Loaded.Result) { | |
294 case CachedFile::LoadResult::FileNotFound: | |
295 if (Active) { | |
296 log("Unloaded compilation database from {0}", Entry.File->Path); | |
297 ActiveCachedFile = nullptr; | |
298 CDB = nullptr; | |
299 } | |
300 // Continue looking at other candidates. | |
301 break; | |
302 case CachedFile::LoadResult::TransientError: | |
303 // File existed but we couldn't read it. Reuse the cache, retry later. | |
304 return false; // Load again next time. | |
305 case CachedFile::LoadResult::FoundSameData: | |
306 assert(Active && "CachedFile may not return 'same data' if !HasOldData"); | |
307 // This is the critical file, and it hasn't changed. | |
308 return true; | |
309 case CachedFile::LoadResult::FoundNewData: | |
310 // We have a new CDB! | |
311 CDB = Entry.Parser(Entry.File->Path, Loaded.Buffer->getBuffer(), Error); | |
312 if (CDB) | |
313 log("{0} compilation database from {1}", Active ? "Reloaded" : "Loaded", | |
314 Entry.File->Path); | |
315 else | |
316 elog("Failed to load compilation database from {0}: {1}", | |
317 Entry.File->Path, Error); | |
318 ActiveCachedFile = Entry.File; | |
319 return true; | |
320 } | |
321 } | |
322 | |
323 // Fall back to generic handling of compilation databases. | |
324 // We don't know what files they read, so can't efficiently check whether | |
325 // they need to be reloaded. So we never do that. | |
326 // FIXME: the interface doesn't provide a way to virtualize FS access. | |
327 | |
328 // Don't try these more than once. If we've scanned before, we're done. | |
329 if (CachePopulatedAt > stopwatch::time_point::min()) | |
330 return true; | |
331 for (const auto &Entry : | |
332 tooling::CompilationDatabasePluginRegistry::entries()) { | |
333 // Avoid duplicating the special cases handled above. | |
334 if (Entry.getName() == "fixed-compilation-database" || | |
335 Entry.getName() == "json-compilation-database") | |
336 continue; | |
337 auto Plugin = Entry.instantiate(); | |
338 if (auto CDB = Plugin->loadFromDirectory(Path, Error)) { | |
339 log("Loaded compilation database from {0} with plugin {1}", Path, | |
340 Entry.getName()); | |
341 this->CDB = std::move(CDB); | |
342 return true; | |
343 } | |
344 // Don't log Error here, it's usually just "couldn't find <file>". | |
345 } | |
346 dlog("No compilation database at {0}", Path); | |
347 return true; | |
348 } | |
349 | |
61 DirectoryBasedGlobalCompilationDatabase:: | 350 DirectoryBasedGlobalCompilationDatabase:: |
62 DirectoryBasedGlobalCompilationDatabase( | 351 DirectoryBasedGlobalCompilationDatabase(const Options &Opts) |
63 llvm::Optional<Path> CompileCommandsDir) | 352 : Opts(Opts), Broadcaster(std::make_unique<BroadcastThread>(*this)) { |
64 : CompileCommandsDir(std::move(CompileCommandsDir)) {} | 353 if (!this->Opts.ContextProvider) |
354 this->Opts.ContextProvider = [](llvm::StringRef) { | |
355 return Context::current().clone(); | |
356 }; | |
357 } | |
65 | 358 |
66 DirectoryBasedGlobalCompilationDatabase:: | 359 DirectoryBasedGlobalCompilationDatabase:: |
67 ~DirectoryBasedGlobalCompilationDatabase() = default; | 360 ~DirectoryBasedGlobalCompilationDatabase() = default; |
68 | 361 |
69 llvm::Optional<tooling::CompileCommand> | 362 llvm::Optional<tooling::CompileCommand> |
70 DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const { | 363 DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const { |
71 CDBLookupRequest Req; | 364 CDBLookupRequest Req; |
72 Req.FileName = File; | 365 Req.FileName = File; |
73 Req.ShouldBroadcast = true; | 366 Req.ShouldBroadcast = true; |
367 auto Now = std::chrono::steady_clock::now(); | |
368 Req.FreshTime = Now - Opts.RevalidateAfter; | |
369 Req.FreshTimeMissing = Now - Opts.RevalidateMissingAfter; | |
74 | 370 |
75 auto Res = lookupCDB(Req); | 371 auto Res = lookupCDB(Req); |
76 if (!Res) { | 372 if (!Res) { |
77 log("Failed to find compilation database for {0}", File); | 373 log("Failed to find compilation database for {0}", File); |
78 return llvm::None; | 374 return llvm::None; |
83 return std::move(Candidates.front()); | 379 return std::move(Candidates.front()); |
84 | 380 |
85 return None; | 381 return None; |
86 } | 382 } |
87 | 383 |
88 // For platforms where paths are case-insensitive (but case-preserving), | 384 std::vector<DirectoryBasedGlobalCompilationDatabase::DirectoryCache *> |
89 // we need to do case-insensitive comparisons and use lowercase keys. | 385 DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches( |
90 // FIXME: Make Path a real class with desired semantics instead. | 386 llvm::ArrayRef<llvm::StringRef> Dirs) const { |
91 // This class is not the only place this problem exists. | 387 std::vector<std::string> FoldedDirs; |
92 // FIXME: Mac filesystems default to case-insensitive, but may be sensitive. | 388 FoldedDirs.reserve(Dirs.size()); |
93 | 389 for (const auto &Dir : Dirs) { |
94 static std::string maybeCaseFoldPath(PathRef Path) { | 390 #ifndef NDEBUG |
95 #if defined(_WIN32) || defined(__APPLE__) | 391 if (!llvm::sys::path::is_absolute(Dir)) |
96 return Path.lower(); | 392 elog("Trying to cache CDB for relative {0}"); |
97 #else | |
98 return std::string(Path); | |
99 #endif | 393 #endif |
100 } | 394 FoldedDirs.push_back(maybeCaseFoldPath(Dir)); |
101 | 395 } |
102 static bool pathEqual(PathRef A, PathRef B) { | 396 |
103 #if defined(_WIN32) || defined(__APPLE__) | 397 std::vector<DirectoryCache *> Ret; |
104 return A.equals_lower(B); | 398 Ret.reserve(Dirs.size()); |
105 #else | 399 |
106 return A == B; | 400 std::lock_guard<std::mutex> Lock(DirCachesMutex); |
107 #endif | 401 for (unsigned I = 0; I < Dirs.size(); ++I) |
108 } | 402 Ret.push_back(&DirCaches.try_emplace(FoldedDirs[I], Dirs[I]).first->second); |
109 | 403 return Ret; |
110 DirectoryBasedGlobalCompilationDatabase::CachedCDB & | |
111 DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const { | |
112 // FIXME(ibiryukov): Invalidate cached compilation databases on changes | |
113 auto Key = maybeCaseFoldPath(Dir); | |
114 auto R = CompilationDatabases.try_emplace(Key); | |
115 if (R.second) { // Cache miss, try to load CDB. | |
116 CachedCDB &Entry = R.first->second; | |
117 std::string Error; | |
118 Entry.Path = std::string(Dir); | |
119 Entry.CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error); | |
120 // Check for $src/build, the conventional CMake build root. | |
121 // Probe existence first to avoid each plugin doing IO if it doesn't exist. | |
122 if (!CompileCommandsDir && !Entry.CDB) { | |
123 llvm::SmallString<256> BuildDir = Dir; | |
124 llvm::sys::path::append(BuildDir, "build"); | |
125 if (llvm::sys::fs::is_directory(BuildDir)) { | |
126 vlog("Found candidate build directory {0}", BuildDir); | |
127 Entry.CDB = | |
128 tooling::CompilationDatabase::loadFromDirectory(BuildDir, Error); | |
129 } | |
130 } | |
131 if (Entry.CDB) | |
132 log("Loaded compilation database from {0}", Dir); | |
133 } | |
134 return R.first->second; | |
135 } | 404 } |
136 | 405 |
137 llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult> | 406 llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult> |
138 DirectoryBasedGlobalCompilationDatabase::lookupCDB( | 407 DirectoryBasedGlobalCompilationDatabase::lookupCDB( |
139 CDBLookupRequest Request) const { | 408 CDBLookupRequest Request) const { |
140 assert(llvm::sys::path::is_absolute(Request.FileName) && | 409 assert(llvm::sys::path::is_absolute(Request.FileName) && |
141 "path must be absolute"); | 410 "path must be absolute"); |
142 | 411 |
412 std::string Storage; | |
413 std::vector<llvm::StringRef> SearchDirs; | |
414 if (Opts.CompileCommandsDir) // FIXME: unify this case with config. | |
415 SearchDirs = {Opts.CompileCommandsDir.getValue()}; | |
416 else { | |
417 WithContext WithProvidedContext(Opts.ContextProvider(Request.FileName)); | |
418 const auto &Spec = Config::current().CompileFlags.CDBSearch; | |
419 switch (Spec.Policy) { | |
420 case Config::CDBSearchSpec::NoCDBSearch: | |
421 return llvm::None; | |
422 case Config::CDBSearchSpec::FixedDir: | |
423 Storage = Spec.FixedCDBPath.getValue(); | |
424 SearchDirs = {Storage}; | |
425 break; | |
426 case Config::CDBSearchSpec::Ancestors: | |
427 // Traverse the canonical version to prevent false positives. i.e.: | |
428 // src/build/../a.cc can detect a CDB in /src/build if not | |
429 // canonicalized. | |
430 Storage = removeDots(Request.FileName); | |
431 actOnAllParentDirectories(Storage, [&](llvm::StringRef Dir) { | |
432 SearchDirs.push_back(Dir); | |
433 return false; | |
434 }); | |
435 } | |
436 } | |
437 | |
438 std::shared_ptr<const tooling::CompilationDatabase> CDB = nullptr; | |
143 bool ShouldBroadcast = false; | 439 bool ShouldBroadcast = false; |
440 DirectoryCache *DirCache = nullptr; | |
441 for (DirectoryCache *Candidate : getDirectoryCaches(SearchDirs)) { | |
442 bool CandidateShouldBroadcast = Request.ShouldBroadcast; | |
443 if ((CDB = Candidate->get(Opts.TFS, CandidateShouldBroadcast, | |
444 Request.FreshTime, Request.FreshTimeMissing))) { | |
445 DirCache = Candidate; | |
446 ShouldBroadcast = CandidateShouldBroadcast; | |
447 break; | |
448 } | |
449 } | |
450 | |
451 if (!CDB) | |
452 return llvm::None; | |
453 | |
144 CDBLookupResult Result; | 454 CDBLookupResult Result; |
145 | 455 Result.CDB = std::move(CDB); |
146 { | 456 Result.PI.SourceRoot = DirCache->Path; |
147 std::lock_guard<std::mutex> Lock(Mutex); | 457 |
148 CachedCDB *Entry = nullptr; | |
149 if (CompileCommandsDir) { | |
150 Entry = &getCDBInDirLocked(*CompileCommandsDir); | |
151 } else { | |
152 // Traverse the canonical version to prevent false positives. i.e.: | |
153 // src/build/../a.cc can detect a CDB in /src/build if not canonicalized. | |
154 // FIXME(sammccall): this loop is hot, use a union-find-like structure. | |
155 actOnAllParentDirectories(removeDots(Request.FileName), | |
156 [&](PathRef Path) { | |
157 Entry = &getCDBInDirLocked(Path); | |
158 return Entry->CDB != nullptr; | |
159 }); | |
160 } | |
161 | |
162 if (!Entry || !Entry->CDB) | |
163 return llvm::None; | |
164 | |
165 // Mark CDB as broadcasted to make sure discovery is performed once. | |
166 if (Request.ShouldBroadcast && !Entry->SentBroadcast) { | |
167 Entry->SentBroadcast = true; | |
168 ShouldBroadcast = true; | |
169 } | |
170 | |
171 Result.CDB = Entry->CDB.get(); | |
172 Result.PI.SourceRoot = Entry->Path; | |
173 } | |
174 | |
175 // FIXME: Maybe make the following part async, since this can block retrieval | |
176 // of compile commands. | |
177 if (ShouldBroadcast) | 458 if (ShouldBroadcast) |
178 broadcastCDB(Result); | 459 broadcastCDB(Result); |
179 return Result; | 460 return Result; |
180 } | 461 } |
181 | 462 |
463 // The broadcast thread announces files with new compile commands to the world. | |
464 // Primarily this is used to enqueue them for background indexing. | |
465 // | |
466 // It's on a separate thread because: | |
467 // - otherwise it would block the first parse of the initial file | |
468 // - we need to enumerate all files in the CDB, of which there are many | |
469 // - we (will) have to evaluate config for every file in the CDB, which is slow | |
470 class DirectoryBasedGlobalCompilationDatabase::BroadcastThread { | |
471 class Filter; | |
472 DirectoryBasedGlobalCompilationDatabase &Parent; | |
473 | |
474 std::mutex Mu; | |
475 std::condition_variable CV; | |
476 // Shutdown flag (CV is notified after writing). | |
477 // This is atomic so that broadcasts can also observe it and abort early. | |
478 std::atomic<bool> ShouldStop = {false}; | |
479 struct Task { | |
480 CDBLookupResult Lookup; | |
481 Context Ctx; | |
482 }; | |
483 std::deque<Task> Queue; | |
484 llvm::Optional<Task> ActiveTask; | |
485 std::thread Thread; // Must be last member. | |
486 | |
487 // Thread body: this is just the basic queue procesing boilerplate. | |
488 void run() { | |
489 std::unique_lock<std::mutex> Lock(Mu); | |
490 while (true) { | |
491 bool Stopping = false; | |
492 CV.wait(Lock, [&] { | |
493 return (Stopping = ShouldStop.load(std::memory_order_acquire)) || | |
494 !Queue.empty(); | |
495 }); | |
496 if (Stopping) { | |
497 Queue.clear(); | |
498 CV.notify_all(); | |
499 return; | |
500 } | |
501 ActiveTask = std::move(Queue.front()); | |
502 Queue.pop_front(); | |
503 | |
504 Lock.unlock(); | |
505 { | |
506 WithContext WithCtx(std::move(ActiveTask->Ctx)); | |
507 process(ActiveTask->Lookup); | |
508 } | |
509 Lock.lock(); | |
510 ActiveTask.reset(); | |
511 CV.notify_all(); | |
512 } | |
513 } | |
514 | |
515 // Inspects a new CDB and broadcasts the files it owns. | |
516 void process(const CDBLookupResult &T); | |
517 | |
518 public: | |
519 BroadcastThread(DirectoryBasedGlobalCompilationDatabase &Parent) | |
520 : Parent(Parent), Thread([this] { run(); }) {} | |
521 | |
522 void enqueue(CDBLookupResult Lookup) { | |
523 { | |
524 assert(!Lookup.PI.SourceRoot.empty()); | |
525 std::lock_guard<std::mutex> Lock(Mu); | |
526 // New CDB takes precedence over any queued one for the same directory. | |
527 llvm::erase_if(Queue, [&](const Task &T) { | |
528 return T.Lookup.PI.SourceRoot == Lookup.PI.SourceRoot; | |
529 }); | |
530 Queue.push_back({std::move(Lookup), Context::current().clone()}); | |
531 } | |
532 CV.notify_all(); | |
533 } | |
534 | |
535 bool blockUntilIdle(Deadline Timeout) { | |
536 std::unique_lock<std::mutex> Lock(Mu); | |
537 return wait(Lock, CV, Timeout, | |
538 [&] { return Queue.empty() && !ActiveTask.hasValue(); }); | |
539 } | |
540 | |
541 ~BroadcastThread() { | |
542 { | |
543 std::lock_guard<std::mutex> Lock(Mu); | |
544 ShouldStop.store(true, std::memory_order_release); | |
545 } | |
546 CV.notify_all(); | |
547 Thread.join(); | |
548 } | |
549 }; | |
550 | |
551 // The DirBasedCDB associates each file with a specific CDB. | |
552 // When a CDB is discovered, it may claim to describe files that we associate | |
553 // with a different CDB. We do not want to broadcast discovery of these, and | |
554 // trigger background indexing of them. | |
555 // | |
556 // We must filter the list, and check whether they are associated with this CDB. | |
557 // This class attempts to do so efficiently. | |
558 // | |
559 // Roughly, it: | |
560 // - loads the config for each file, and determines the relevant search path | |
561 // - gathers all directories that are part of any search path | |
562 // - (lazily) checks for a CDB in each such directory at most once | |
563 // - walks the search path for each file and determines whether to include it. | |
564 class DirectoryBasedGlobalCompilationDatabase::BroadcastThread::Filter { | |
565 llvm::StringRef ThisDir; | |
566 DirectoryBasedGlobalCompilationDatabase &Parent; | |
567 | |
568 // Keep track of all directories we might check for CDBs. | |
569 struct DirInfo { | |
570 DirectoryCache *Cache = nullptr; | |
571 enum { Unknown, Missing, TargetCDB, OtherCDB } State = Unknown; | |
572 DirInfo *Parent = nullptr; | |
573 }; | |
574 llvm::StringMap<DirInfo> Dirs; | |
575 | |
576 // A search path starts at a directory, and either includes ancestors or not. | |
577 using SearchPath = llvm::PointerIntPair<DirInfo *, 1>; | |
578 | |
579 // Add all ancestor directories of FilePath to the tracked set. | |
580 // Returns the immediate parent of the file. | |
581 DirInfo *addParents(llvm::StringRef FilePath) { | |
582 DirInfo *Leaf = nullptr; | |
583 DirInfo *Child = nullptr; | |
584 actOnAllParentDirectories(FilePath, [&](llvm::StringRef Dir) { | |
585 auto &Info = Dirs[Dir]; | |
586 // If this is the first iteration, then this node is the overall result. | |
587 if (!Leaf) | |
588 Leaf = &Info; | |
589 // Fill in the parent link from the previous iteration to this parent. | |
590 if (Child) | |
591 Child->Parent = &Info; | |
592 // Keep walking, whether we inserted or not, if parent link is missing. | |
593 // (If it's present, parent links must be present up to the root, so stop) | |
594 Child = &Info; | |
595 return Info.Parent != nullptr; | |
596 }); | |
597 return Leaf; | |
598 } | |
599 | |
600 // Populates DirInfo::Cache (and State, if it is TargetCDB). | |
601 void grabCaches() { | |
602 // Fast path out if there were no files, or CDB loading is off. | |
603 if (Dirs.empty()) | |
604 return; | |
605 | |
606 std::vector<llvm::StringRef> DirKeys; | |
607 std::vector<DirInfo *> DirValues; | |
608 DirKeys.reserve(Dirs.size() + 1); | |
609 DirValues.reserve(Dirs.size()); | |
610 for (auto &E : Dirs) { | |
611 DirKeys.push_back(E.first()); | |
612 DirValues.push_back(&E.second); | |
613 } | |
614 | |
615 // Also look up the cache entry for the CDB we're broadcasting. | |
616 // Comparing DirectoryCache pointers is more robust than checking string | |
617 // equality, e.g. reuses the case-sensitivity handling. | |
618 DirKeys.push_back(ThisDir); | |
619 auto DirCaches = Parent.getDirectoryCaches(DirKeys); | |
620 const DirectoryCache *ThisCache = DirCaches.back(); | |
621 DirCaches.pop_back(); | |
622 DirKeys.pop_back(); | |
623 | |
624 for (unsigned I = 0; I < DirKeys.size(); ++I) { | |
625 DirValues[I]->Cache = DirCaches[I]; | |
626 if (DirCaches[I] == ThisCache) | |
627 DirValues[I]->State = DirInfo::TargetCDB; | |
628 } | |
629 } | |
630 | |
631 // Should we include a file from this search path? | |
632 bool shouldInclude(SearchPath P) { | |
633 DirInfo *Info = P.getPointer(); | |
634 if (!Info) | |
635 return false; | |
636 if (Info->State == DirInfo::Unknown) { | |
637 assert(Info->Cache && "grabCaches() should have filled this"); | |
638 // Given that we know that CDBs have been moved/generated, don't trust | |
639 // caches. (This should be rare, so it's OK to add a little latency). | |
640 constexpr auto IgnoreCache = std::chrono::steady_clock::time_point::max(); | |
641 // Don't broadcast CDBs discovered while broadcasting! | |
642 bool ShouldBroadcast = false; | |
643 bool Exists = | |
644 nullptr != Info->Cache->get(Parent.Opts.TFS, ShouldBroadcast, | |
645 /*FreshTime=*/IgnoreCache, | |
646 /*FreshTimeMissing=*/IgnoreCache); | |
647 Info->State = Exists ? DirInfo::OtherCDB : DirInfo::Missing; | |
648 } | |
649 // If we have a CDB, include the file if it's the target CDB only. | |
650 if (Info->State != DirInfo::Missing) | |
651 return Info->State == DirInfo::TargetCDB; | |
652 // If we have no CDB and no relevant parent, don't include the file. | |
653 if (!P.getInt() || !Info->Parent) | |
654 return false; | |
655 // Walk up to the next parent. | |
656 return shouldInclude(SearchPath(Info->Parent, 1)); | |
657 } | |
658 | |
659 public: | |
660 Filter(llvm::StringRef ThisDir, | |
661 DirectoryBasedGlobalCompilationDatabase &Parent) | |
662 : ThisDir(ThisDir), Parent(Parent) {} | |
663 | |
664 std::vector<std::string> filter(std::vector<std::string> AllFiles, | |
665 std::atomic<bool> &ShouldStop) { | |
666 std::vector<std::string> Filtered; | |
667 // Allow for clean early-exit of the slow parts. | |
668 auto ExitEarly = [&] { | |
669 if (ShouldStop.load(std::memory_order_acquire)) { | |
670 log("Giving up on broadcasting CDB, as we're shutting down"); | |
671 Filtered.clear(); | |
672 return true; | |
673 } | |
674 return false; | |
675 }; | |
676 // Compute search path for each file. | |
677 std::vector<SearchPath> SearchPaths(AllFiles.size()); | |
678 for (unsigned I = 0; I < AllFiles.size(); ++I) { | |
679 if (Parent.Opts.CompileCommandsDir) { // FIXME: unify with config | |
680 SearchPaths[I].setPointer( | |
681 &Dirs[Parent.Opts.CompileCommandsDir.getValue()]); | |
682 continue; | |
683 } | |
684 if (ExitEarly()) // loading config may be slow | |
685 return Filtered; | |
686 WithContext WithProvidedContent(Parent.Opts.ContextProvider(AllFiles[I])); | |
687 const Config::CDBSearchSpec &Spec = | |
688 Config::current().CompileFlags.CDBSearch; | |
689 switch (Spec.Policy) { | |
690 case Config::CDBSearchSpec::NoCDBSearch: | |
691 break; | |
692 case Config::CDBSearchSpec::Ancestors: | |
693 SearchPaths[I].setInt(/*Recursive=*/1); | |
694 SearchPaths[I].setPointer(addParents(AllFiles[I])); | |
695 break; | |
696 case Config::CDBSearchSpec::FixedDir: | |
697 SearchPaths[I].setPointer(&Dirs[Spec.FixedCDBPath.getValue()]); | |
698 break; | |
699 } | |
700 } | |
701 // Get the CDB cache for each dir on the search path, but don't load yet. | |
702 grabCaches(); | |
703 // Now work out which files we want to keep, loading CDBs where needed. | |
704 for (unsigned I = 0; I < AllFiles.size(); ++I) { | |
705 if (ExitEarly()) // loading CDBs may be slow | |
706 return Filtered; | |
707 if (shouldInclude(SearchPaths[I])) | |
708 Filtered.push_back(std::move(AllFiles[I])); | |
709 } | |
710 return Filtered; | |
711 } | |
712 }; | |
713 | |
714 void DirectoryBasedGlobalCompilationDatabase::BroadcastThread::process( | |
715 const CDBLookupResult &T) { | |
716 vlog("Broadcasting compilation database from {0}", T.PI.SourceRoot); | |
717 std::vector<std::string> GovernedFiles = | |
718 Filter(T.PI.SourceRoot, Parent).filter(T.CDB->getAllFiles(), ShouldStop); | |
719 if (!GovernedFiles.empty()) | |
720 Parent.OnCommandChanged.broadcast(std::move(GovernedFiles)); | |
721 } | |
722 | |
182 void DirectoryBasedGlobalCompilationDatabase::broadcastCDB( | 723 void DirectoryBasedGlobalCompilationDatabase::broadcastCDB( |
183 CDBLookupResult Result) const { | 724 CDBLookupResult Result) const { |
184 assert(Result.CDB && "Trying to broadcast an invalid CDB!"); | 725 assert(Result.CDB && "Trying to broadcast an invalid CDB!"); |
185 | 726 Broadcaster->enqueue(Result); |
186 std::vector<std::string> AllFiles = Result.CDB->getAllFiles(); | 727 } |
187 // We assume CDB in CompileCommandsDir owns all of its entries, since we don't | 728 |
188 // perform any search in parent paths whenever it is set. | 729 bool DirectoryBasedGlobalCompilationDatabase::blockUntilIdle( |
189 if (CompileCommandsDir) { | 730 Deadline Timeout) const { |
190 assert(*CompileCommandsDir == Result.PI.SourceRoot && | 731 return Broadcaster->blockUntilIdle(Timeout); |
191 "Trying to broadcast a CDB outside of CompileCommandsDir!"); | |
192 OnCommandChanged.broadcast(std::move(AllFiles)); | |
193 return; | |
194 } | |
195 | |
196 llvm::StringMap<bool> DirectoryHasCDB; | |
197 { | |
198 std::lock_guard<std::mutex> Lock(Mutex); | |
199 // Uniquify all parent directories of all files. | |
200 for (llvm::StringRef File : AllFiles) { | |
201 actOnAllParentDirectories(File, [&](PathRef Path) { | |
202 auto It = DirectoryHasCDB.try_emplace(Path); | |
203 // Already seen this path, and all of its parents. | |
204 if (!It.second) | |
205 return true; | |
206 | |
207 CachedCDB &Entry = getCDBInDirLocked(Path); | |
208 It.first->second = Entry.CDB != nullptr; | |
209 return pathEqual(Path, Result.PI.SourceRoot); | |
210 }); | |
211 } | |
212 } | |
213 | |
214 std::vector<std::string> GovernedFiles; | |
215 for (llvm::StringRef File : AllFiles) { | |
216 // A file is governed by this CDB if lookup for the file would find it. | |
217 // Independent of whether it has an entry for that file or not. | |
218 actOnAllParentDirectories(File, [&](PathRef Path) { | |
219 if (DirectoryHasCDB.lookup(Path)) { | |
220 if (pathEqual(Path, Result.PI.SourceRoot)) | |
221 // Make sure listeners always get a canonical path for the file. | |
222 GovernedFiles.push_back(removeDots(File)); | |
223 // Stop as soon as we hit a CDB. | |
224 return true; | |
225 } | |
226 return false; | |
227 }); | |
228 } | |
229 | |
230 OnCommandChanged.broadcast(std::move(GovernedFiles)); | |
231 } | 732 } |
232 | 733 |
233 llvm::Optional<ProjectInfo> | 734 llvm::Optional<ProjectInfo> |
234 DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const { | 735 DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const { |
235 CDBLookupRequest Req; | 736 CDBLookupRequest Req; |
236 Req.FileName = File; | 737 Req.FileName = File; |
237 Req.ShouldBroadcast = false; | 738 Req.ShouldBroadcast = false; |
739 Req.FreshTime = Req.FreshTimeMissing = | |
740 std::chrono::steady_clock::time_point::min(); | |
238 auto Res = lookupCDB(Req); | 741 auto Res = lookupCDB(Req); |
239 if (!Res) | 742 if (!Res) |
240 return llvm::None; | 743 return llvm::None; |
241 return Res->PI; | 744 return Res->PI; |
242 } | 745 } |
243 | 746 |
244 OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, | 747 OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, |
245 std::vector<std::string> FallbackFlags, | 748 std::vector<std::string> FallbackFlags, |
246 tooling::ArgumentsAdjuster Adjuster) | 749 tooling::ArgumentsAdjuster Adjuster) |
247 : Base(Base), ArgsAdjuster(std::move(Adjuster)), | 750 : DelegatingCDB(Base), ArgsAdjuster(std::move(Adjuster)), |
248 FallbackFlags(std::move(FallbackFlags)) { | 751 FallbackFlags(std::move(FallbackFlags)) {} |
249 if (Base) | |
250 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) { | |
251 OnCommandChanged.broadcast(Changes); | |
252 }); | |
253 } | |
254 | 752 |
255 llvm::Optional<tooling::CompileCommand> | 753 llvm::Optional<tooling::CompileCommand> |
256 OverlayCDB::getCompileCommand(PathRef File) const { | 754 OverlayCDB::getCompileCommand(PathRef File) const { |
257 llvm::Optional<tooling::CompileCommand> Cmd; | 755 llvm::Optional<tooling::CompileCommand> Cmd; |
258 { | 756 { |
259 std::lock_guard<std::mutex> Lock(Mutex); | 757 std::lock_guard<std::mutex> Lock(Mutex); |
260 auto It = Commands.find(removeDots(File)); | 758 auto It = Commands.find(removeDots(File)); |
261 if (It != Commands.end()) | 759 if (It != Commands.end()) |
262 Cmd = It->second; | 760 Cmd = It->second; |
263 } | 761 } |
264 if (!Cmd && Base) | 762 if (!Cmd) |
265 Cmd = Base->getCompileCommand(File); | 763 Cmd = DelegatingCDB::getCompileCommand(File); |
266 if (!Cmd) | 764 if (!Cmd) |
267 return llvm::None; | 765 return llvm::None; |
268 if (ArgsAdjuster) | 766 if (ArgsAdjuster) |
269 Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename); | 767 Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename); |
270 return Cmd; | 768 return Cmd; |
271 } | 769 } |
272 | 770 |
273 tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const { | 771 tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const { |
274 auto Cmd = Base ? Base->getFallbackCommand(File) | 772 auto Cmd = DelegatingCDB::getFallbackCommand(File); |
275 : GlobalCompilationDatabase::getFallbackCommand(File); | |
276 std::lock_guard<std::mutex> Lock(Mutex); | 773 std::lock_guard<std::mutex> Lock(Mutex); |
277 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(), | 774 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(), |
278 FallbackFlags.end()); | 775 FallbackFlags.end()); |
279 if (ArgsAdjuster) | 776 if (ArgsAdjuster) |
280 Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename); | 777 Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename); |
295 Commands.erase(CanonPath); | 792 Commands.erase(CanonPath); |
296 } | 793 } |
297 OnCommandChanged.broadcast({CanonPath}); | 794 OnCommandChanged.broadcast({CanonPath}); |
298 } | 795 } |
299 | 796 |
300 llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const { | 797 DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base) |
301 { | 798 : Base(Base) { |
302 std::lock_guard<std::mutex> Lock(Mutex); | |
303 auto It = Commands.find(removeDots(File)); | |
304 if (It != Commands.end()) | |
305 return ProjectInfo{}; | |
306 } | |
307 if (Base) | 799 if (Base) |
308 return Base->getProjectInfo(File); | 800 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) { |
309 | 801 OnCommandChanged.broadcast(Changes); |
310 return llvm::None; | 802 }); |
311 } | 803 } |
804 | |
805 DelegatingCDB::DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base) | |
806 : DelegatingCDB(Base.get()) { | |
807 BaseOwner = std::move(Base); | |
808 } | |
809 | |
810 llvm::Optional<tooling::CompileCommand> | |
811 DelegatingCDB::getCompileCommand(PathRef File) const { | |
812 if (!Base) | |
813 return llvm::None; | |
814 return Base->getCompileCommand(File); | |
815 } | |
816 | |
817 llvm::Optional<ProjectInfo> DelegatingCDB::getProjectInfo(PathRef File) const { | |
818 if (!Base) | |
819 return llvm::None; | |
820 return Base->getProjectInfo(File); | |
821 } | |
822 | |
823 tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File) const { | |
824 if (!Base) | |
825 return GlobalCompilationDatabase::getFallbackCommand(File); | |
826 return Base->getFallbackCommand(File); | |
827 } | |
828 | |
829 bool DelegatingCDB::blockUntilIdle(Deadline D) const { | |
830 if (!Base) | |
831 return true; | |
832 return Base->blockUntilIdle(D); | |
833 } | |
834 | |
312 } // namespace clangd | 835 } // namespace clangd |
313 } // namespace clang | 836 } // namespace clang |