clang-tools 23.0.0git
GlobalCompilationDatabase.cpp
Go to the documentation of this file.
1//===--- GlobalCompilationDatabase.cpp ---------------------------*- 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
10#include "Config.h"
11#include "FS.h"
12#include "ProjectModules.h"
13#include "SourceCode.h"
14#include "support/Logger.h"
15#include "support/Path.h"
16#include "support/Threading.h"
18#include "clang/Tooling/ArgumentsAdjusters.h"
19#include "clang/Tooling/CompilationDatabase.h"
20#include "clang/Tooling/CompilationDatabasePluginRegistry.h"
21#include "clang/Tooling/JSONCompilationDatabase.h"
22#include "clang/Tooling/Tooling.h"
23#include "llvm/ADT/PointerIntPair.h"
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/ADT/ScopeExit.h"
26#include "llvm/ADT/SmallString.h"
27#include "llvm/ADT/StringMap.h"
28#include "llvm/Support/Path.h"
29#include "llvm/Support/VirtualFileSystem.h"
30#include "llvm/TargetParser/Host.h"
31#include <atomic>
32#include <chrono>
33#include <condition_variable>
34#include <deque>
35#include <mutex>
36#include <optional>
37#include <string>
38#include <tuple>
39#include <vector>
40
41namespace clang {
42namespace clangd {
43namespace {
44
45// Runs the given action on all parent directories of filename, starting from
46// deepest directory and going up to root. Stops whenever action succeeds.
47void actOnAllParentDirectories(PathRef FileName,
48 llvm::function_ref<bool(PathRef)> Action) {
49 for (auto Path = absoluteParent(FileName); !Path.empty() && !Action(Path);
51 ;
52}
53
54} // namespace
55
56tooling::CompileCommand
58 std::vector<std::string> Argv = {"clang"};
59 // Clang treats .h files as C by default and files without extension as linker
60 // input, resulting in unhelpful diagnostics.
61 // Parsing as Objective C++ is friendly to more cases.
62 auto FileExtension = llvm::sys::path::extension(File);
63 if (FileExtension.empty() || FileExtension == ".h")
64 Argv.push_back("-xobjective-c++-header");
65 Argv.push_back(std::string(File));
66 tooling::CompileCommand Cmd(FallbackWorkingDirectory
68 : llvm::sys::path::parent_path(File),
69 llvm::sys::path::filename(File), std::move(Argv),
70 /*Output=*/"");
71 Cmd.Heuristic = "clangd fallback";
72 return Cmd;
73}
74
75// Loads and caches the CDB from a single directory.
76//
77// This class is threadsafe, which is to say we have independent locks for each
78// directory we're searching for a CDB.
79// Loading is deferred until first access.
80//
81// The DirectoryBasedCDB keeps a map from path => DirectoryCache.
82// Typical usage is to:
83// - 1) determine all the paths that might be searched
84// - 2) acquire the map lock and get-or-create all the DirectoryCache entries
85// - 3) release the map lock and query the caches as desired
87 using stopwatch = std::chrono::steady_clock;
88
89 // CachedFile is used to read a CDB file on disk (e.g. compile_commands.json).
90 // It specializes in being able to quickly bail out if the file is unchanged,
91 // which is the common case.
92 // Internally, it stores file metadata so a stat() can verify it's unchanged.
93 // We don't actually cache the content as it's not needed - if the file is
94 // unchanged then the previous CDB is valid.
95 struct CachedFile {
96 CachedFile(llvm::StringRef Parent, llvm::StringRef Rel) {
97 llvm::SmallString<256> Path = Parent;
98 llvm::sys::path::append(Path, Rel);
99 this->Path = Path.str().str();
100 }
101 std::string Path;
102 size_t Size = NoFileCached;
103 llvm::sys::TimePoint<> ModifiedTime;
104 FileDigest ContentHash;
105
106 static constexpr size_t NoFileCached = -1;
107
108 struct LoadResult {
109 enum {
115 std::unique_ptr<llvm::MemoryBuffer> Buffer; // Set only if FoundNewData
116 };
117
118 LoadResult load(llvm::vfs::FileSystem &FS, bool HasOldData);
119 };
120
121 // If we've looked for a CDB here and found none, the time when that happened.
122 // (Atomics make it possible for get() to return without taking a lock)
123 std::atomic<stopwatch::rep> NoCDBAt = {
124 stopwatch::time_point::min().time_since_epoch().count()};
125
126 // Guards the following cache state.
127 std::mutex Mu;
128 // When was the cache last known to be in sync with disk state?
129 stopwatch::time_point CachePopulatedAt = stopwatch::time_point::min();
130 // Whether a new CDB has been loaded but not broadcast yet.
131 bool NeedsBroadcast = false;
132 // Last loaded CDB, meaningful if CachePopulatedAt was ever set.
133 // shared_ptr so we can overwrite this when callers are still using the CDB.
134 std::shared_ptr<tooling::CompilationDatabase> CDB;
135 // File metadata for the CDB files we support tracking directly.
136 CachedFile CompileCommandsJson;
137 CachedFile BuildCompileCommandsJson;
138 CachedFile CompileFlagsTxt;
139 // CachedFile member corresponding to CDB.
140 // CDB | ACF | Scenario
141 // null | null | no CDB found, or initial empty cache
142 // set | null | CDB was loaded via generic plugin interface
143 // null | set | found known CDB file, but parsing it failed
144 // set | set | CDB was parsed from a known file
145 CachedFile *ActiveCachedFile = nullptr;
146
147public:
148 DirectoryCache(llvm::StringRef Path)
149 : CompileCommandsJson(Path, "compile_commands.json"),
150 BuildCompileCommandsJson(Path, "build/compile_commands.json"),
151 CompileFlagsTxt(Path, "compile_flags.txt"), Path(Path) {
152 assert(llvm::sys::path::is_absolute(Path));
153 }
154
155 // Absolute canonical path that we're the cache for. (Not case-folded).
156 const std::string Path;
157
158 // Get the CDB associated with this directory.
159 // ShouldBroadcast:
160 // - as input, signals whether the caller is willing to broadcast a
161 // newly-discovered CDB. (e.g. to trigger background indexing)
162 // - as output, signals whether the caller should do so.
163 // (If a new CDB is discovered and ShouldBroadcast is false, we mark the
164 // CDB as needing broadcast, and broadcast it next time we can).
165 std::shared_ptr<const tooling::CompilationDatabase>
166 get(const ThreadsafeFS &TFS, bool &ShouldBroadcast,
167 stopwatch::time_point FreshTime, stopwatch::time_point FreshTimeMissing) {
168 // Fast path for common case without taking lock.
169 if (stopwatch::time_point(stopwatch::duration(NoCDBAt.load())) >
170 FreshTimeMissing) {
171 ShouldBroadcast = false;
172 return nullptr;
173 }
174
175 std::lock_guard<std::mutex> Lock(Mu);
176 llvm::scope_exit RequestBroadcast([&, OldCDB(CDB.get())] {
177 // If we loaded a new CDB, it should be broadcast at some point.
178 if (CDB != nullptr && CDB.get() != OldCDB)
179 NeedsBroadcast = true;
180 else if (CDB == nullptr) // nothing to broadcast anymore!
181 NeedsBroadcast = false;
182 // If we have something to broadcast, then do so iff allowed.
183 if (!ShouldBroadcast)
184 return;
185 ShouldBroadcast = NeedsBroadcast;
186 NeedsBroadcast = false;
187 });
188
189 // If our cache is valid, serve from it.
190 if (CachePopulatedAt > FreshTime)
191 return CDB;
192
193 if (/*MayCache=*/load(*TFS.view(/*CWD=*/std::nullopt))) {
194 // Use new timestamp, as loading may be slow.
195 CachePopulatedAt = stopwatch::now();
196 NoCDBAt.store((CDB ? stopwatch::time_point::min() : CachePopulatedAt)
197 .time_since_epoch()
198 .count());
199 }
200
201 return CDB;
202 }
203
204private:
205 // Updates `CDB` from disk state. Returns false on failure.
206 bool load(llvm::vfs::FileSystem &FS);
207};
208
210DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::load(
211 llvm::vfs::FileSystem &FS, bool HasOldData) {
212 auto Stat = FS.status(Path);
213 if (!Stat || !Stat->isRegularFile()) {
214 Size = NoFileCached;
215 ContentHash = {};
216 return {LoadResult::FileNotFound, nullptr};
217 }
218 // If both the size and mtime match, presume unchanged without reading.
219 if (HasOldData && Stat->getLastModificationTime() == ModifiedTime &&
220 Stat->getSize() == Size)
221 return {LoadResult::FoundSameData, nullptr};
222 auto Buf = FS.getBufferForFile(Path);
223 if (!Buf || (*Buf)->getBufferSize() != Stat->getSize()) {
224 // Don't clear the cache - possible we're seeing inconsistent size as the
225 // file is being recreated. If it ends up identical later, great!
226 //
227 // This isn't a complete solution: if we see a partial file but stat/read
228 // agree on its size, we're ultimately going to have spurious CDB reloads.
229 // May be worth fixing if generators don't write atomically (CMake does).
230 elog("Failed to read {0}: {1}", Path,
231 Buf ? "size changed" : Buf.getError().message());
232 return {LoadResult::TransientError, nullptr};
233 }
234
235 FileDigest NewContentHash = digest((*Buf)->getBuffer());
236 if (HasOldData && NewContentHash == ContentHash) {
237 // mtime changed but data is the same: avoid rebuilding the CDB.
238 ModifiedTime = Stat->getLastModificationTime();
239 return {LoadResult::FoundSameData, nullptr};
240 }
241
242 Size = (*Buf)->getBufferSize();
243 ModifiedTime = Stat->getLastModificationTime();
244 ContentHash = NewContentHash;
245 return {LoadResult::FoundNewData, std::move(*Buf)};
246}
247
248// Adapt CDB-loading functions to a common interface for DirectoryCache::load().
249static std::unique_ptr<tooling::CompilationDatabase>
250parseJSON(PathRef Path, llvm::StringRef Data, std::string &Error) {
251 if (auto CDB = tooling::JSONCompilationDatabase::loadFromBuffer(
252 Data, Error, tooling::JSONCommandLineSyntax::AutoDetect)) {
253 // FS used for expanding response files.
254 // FIXME: ExpandResponseFilesDatabase appears not to provide the usual
255 // thread-safety guarantees, as the access to FS is not locked!
256 // For now, use the real FS, which is known to be threadsafe (if we don't
257 // use/change working directory, which ExpandResponseFilesDatabase doesn't).
258 // NOTE: response files have to be expanded before inference because
259 // inference needs full command line to check/fix driver mode and file type.
260 auto FS = llvm::vfs::getRealFileSystem();
261 return tooling::inferMissingCompileCommands(
262 expandResponseFiles(std::move(CDB), std::move(FS)));
263 }
264 return nullptr;
265}
266static std::unique_ptr<tooling::CompilationDatabase>
267parseFixed(PathRef Path, llvm::StringRef Data, std::string &Error) {
268 return tooling::FixedCompilationDatabase::loadFromBuffer(
269 llvm::sys::path::parent_path(Path), Data, Error);
270}
271
272bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
273 llvm::vfs::FileSystem &FS) {
274 dlog("Probing directory {0}", Path);
275 std::string Error;
276
277 // Load from the specially-supported compilation databases (JSON + Fixed).
278 // For these, we know the files they read and cache their metadata so we can
279 // cheaply validate whether they've changed, and hot-reload if they have.
280 // (As a bonus, these are also VFS-clean)!
281 struct CDBFile {
282 CachedFile *File;
283 // Wrapper for {Fixed,JSON}CompilationDatabase::loadFromBuffer.
284 std::unique_ptr<tooling::CompilationDatabase> (*Parser)(
285 PathRef,
286 /*Data*/ llvm::StringRef,
287 /*ErrorMsg*/ std::string &);
288 };
289 for (const auto &Entry : {CDBFile{&CompileCommandsJson, parseJSON},
290 CDBFile{&BuildCompileCommandsJson, parseJSON},
291 CDBFile{&CompileFlagsTxt, parseFixed}}) {
292 bool Active = ActiveCachedFile == Entry.File;
293 auto Loaded = Entry.File->load(FS, Active);
294 switch (Loaded.Result) {
296 if (Active) {
297 log("Unloaded compilation database from {0}", Entry.File->Path);
298 ActiveCachedFile = nullptr;
299 CDB = nullptr;
300 }
301 // Continue looking at other candidates.
302 break;
304 // File existed but we couldn't read it. Reuse the cache, retry later.
305 return false; // Load again next time.
307 assert(Active && "CachedFile may not return 'same data' if !HasOldData");
308 // This is the critical file, and it hasn't changed.
309 return true;
311 // We have a new CDB!
312 CDB = Entry.Parser(Entry.File->Path, Loaded.Buffer->getBuffer(), Error);
313 if (CDB)
314 log("{0} compilation database from {1}", Active ? "Reloaded" : "Loaded",
315 Entry.File->Path);
316 else
317 elog("Failed to load compilation database from {0}: {1}",
318 Entry.File->Path, Error);
319 ActiveCachedFile = Entry.File;
320 return true;
321 }
322 }
323
324 // Fall back to generic handling of compilation databases.
325 // We don't know what files they read, so can't efficiently check whether
326 // they need to be reloaded. So we never do that.
327 // FIXME: the interface doesn't provide a way to virtualize FS access.
328
329 // Don't try these more than once. If we've scanned before, we're done.
330 if (CachePopulatedAt > stopwatch::time_point::min())
331 return true;
332 for (const auto &Entry :
333 tooling::CompilationDatabasePluginRegistry::entries()) {
334 // Avoid duplicating the special cases handled above.
335 if (Entry.getName() == "fixed-compilation-database" ||
336 Entry.getName() == "json-compilation-database")
337 continue;
338 auto Plugin = Entry.instantiate();
339 if (auto CDB = Plugin->loadFromDirectory(Path, Error)) {
340 log("Loaded compilation database from {0} with plugin {1}", Path,
341 Entry.getName());
342 this->CDB = std::move(CDB);
343 return true;
344 }
345 // Don't log Error here, it's usually just "couldn't find <file>".
346 }
347 dlog("No compilation database at {0}", Path);
348 return true;
349}
350
354 Broadcaster(std::make_unique<BroadcastThread>(*this)) {
355 if (!this->Opts.ContextProvider)
356 this->Opts.ContextProvider = [](llvm::StringRef) {
357 return Context::current().clone();
358 };
359}
360
363
364std::optional<tooling::CompileCommand>
366 CDBLookupRequest Req;
367 Req.FileName = File;
368 Req.ShouldBroadcast = true;
369 auto Now = std::chrono::steady_clock::now();
370 Req.FreshTime = Now - Opts.RevalidateAfter;
371 Req.FreshTimeMissing = Now - Opts.RevalidateMissingAfter;
372
373 auto Res = lookupCDB(Req);
374 if (!Res) {
375 log("Failed to find compilation database for {0}", File);
376 return std::nullopt;
377 }
378
379 auto Candidates = Res->CDB->getCompileCommands(File);
380 if (!Candidates.empty())
381 return std::move(Candidates.front());
382
383 return std::nullopt;
384}
385
386std::vector<DirectoryBasedGlobalCompilationDatabase::DirectoryCache *>
387DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
388 llvm::ArrayRef<llvm::StringRef> Dirs) const {
389 std::vector<std::string> FoldedDirs;
390 FoldedDirs.reserve(Dirs.size());
391 for (const auto &Dir : Dirs) {
392#ifndef NDEBUG
393 if (!llvm::sys::path::is_absolute(Dir))
394 elog("Trying to cache CDB for relative {0}");
395#endif
396 FoldedDirs.push_back(maybeCaseFoldPath(Dir));
397 }
398
399 std::vector<DirectoryCache *> Ret;
400 Ret.reserve(Dirs.size());
401
402 std::lock_guard<std::mutex> Lock(DirCachesMutex);
403 for (unsigned I = 0; I < Dirs.size(); ++I)
404 Ret.push_back(&DirCaches.try_emplace(FoldedDirs[I], Dirs[I]).first->second);
405 return Ret;
406}
407
408std::optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
409DirectoryBasedGlobalCompilationDatabase::lookupCDB(
410 CDBLookupRequest Request) const {
411 assert(llvm::sys::path::is_absolute(Request.FileName) &&
412 "path must be absolute");
413
414 std::string Storage;
415 std::vector<llvm::StringRef> SearchDirs;
416 if (Opts.CompileCommandsDir) // FIXME: unify this case with config.
417 SearchDirs = {*Opts.CompileCommandsDir};
418 else {
419 WithContext WithProvidedContext(Opts.ContextProvider(Request.FileName));
420 const auto &Spec = Config::current().CompileFlags.CDBSearch;
421 switch (Spec.Policy) {
423 return std::nullopt;
425 Storage = *Spec.FixedCDBPath;
426 SearchDirs = {Storage};
427 break;
429 // Traverse the canonical version to prevent false positives. i.e.:
430 // src/build/../a.cc can detect a CDB in /src/build if not
431 // canonicalized.
432 Storage = removeDots(Request.FileName);
433 actOnAllParentDirectories(Storage, [&](llvm::StringRef Dir) {
434 SearchDirs.push_back(Dir);
435 return false;
436 });
437 }
438 }
439
440 std::shared_ptr<const tooling::CompilationDatabase> CDB = nullptr;
441 bool ShouldBroadcast = false;
442 DirectoryCache *DirCache = nullptr;
443 for (DirectoryCache *Candidate : getDirectoryCaches(SearchDirs)) {
444 bool CandidateShouldBroadcast = Request.ShouldBroadcast;
445 if ((CDB = Candidate->get(Opts.TFS, CandidateShouldBroadcast,
446 Request.FreshTime, Request.FreshTimeMissing))) {
447 DirCache = Candidate;
448 ShouldBroadcast = CandidateShouldBroadcast;
449 break;
450 }
451 }
452
453 if (!CDB)
454 return std::nullopt;
455
456 CDBLookupResult Result;
457 Result.CDB = std::move(CDB);
458 Result.PI.SourceRoot = DirCache->Path;
459
460 if (ShouldBroadcast)
461 broadcastCDB(Result);
462 return Result;
463}
464
467 std::optional<std::string> FallbackWorkingDirectory) {
469 this->FallbackWorkingDirectory = *FallbackWorkingDirectory;
470 else {
471 // Clangd is running in strong workspace mode but the client didn't
472 // specify a workspace path in the `initialize` request.
473 // Fallback to current working directory.
474 SmallString<256> CWD;
475 llvm::sys::fs::current_path(CWD);
476 this->FallbackWorkingDirectory = std::string(CWD);
477 }
478}
479
480// The broadcast thread announces files with new compile commands to the world.
481// Primarily this is used to enqueue them for background indexing.
482//
483// It's on a separate thread because:
484// - otherwise it would block the first parse of the initial file
485// - we need to enumerate all files in the CDB, of which there are many
486// - we (will) have to evaluate config for every file in the CDB, which is slow
488 class Filter;
490
491 std::mutex Mu;
492 std::condition_variable CV;
493 // Shutdown flag (CV is notified after writing).
494 // This is atomic so that broadcasts can also observe it and abort early.
495 std::atomic<bool> ShouldStop = {false};
496 struct Task {
497 CDBLookupResult Lookup;
498 Context Ctx;
499 };
500 std::deque<Task> Queue;
501 std::optional<Task> ActiveTask;
502 std::thread Thread; // Must be last member.
503
504 // Thread body: this is just the basic queue procesing boilerplate.
505 void run() {
506 std::unique_lock<std::mutex> Lock(Mu);
507 while (true) {
508 bool Stopping = false;
509 CV.wait(Lock, [&] {
510 return (Stopping = ShouldStop.load(std::memory_order_acquire)) ||
511 !Queue.empty();
512 });
513 if (Stopping) {
514 Queue.clear();
515 CV.notify_all();
516 return;
517 }
518 ActiveTask = std::move(Queue.front());
519 Queue.pop_front();
520
521 Lock.unlock();
522 {
523 WithContext WithCtx(std::move(ActiveTask->Ctx));
524 process(ActiveTask->Lookup);
525 }
526 Lock.lock();
527 ActiveTask.reset();
528 CV.notify_all();
529 }
530 }
531
532 // Inspects a new CDB and broadcasts the files it owns.
533 void process(const CDBLookupResult &T);
534
535public:
537 : Parent(Parent), Thread([this] { run(); }) {}
538
539 void enqueue(CDBLookupResult Lookup) {
540 {
541 assert(!Lookup.PI.SourceRoot.empty());
542 std::lock_guard<std::mutex> Lock(Mu);
543 // New CDB takes precedence over any queued one for the same directory.
544 llvm::erase_if(Queue, [&](const Task &T) {
545 return T.Lookup.PI.SourceRoot == Lookup.PI.SourceRoot;
546 });
547 Queue.push_back({std::move(Lookup), Context::current().clone()});
548 }
549 CV.notify_all();
550 }
551
552 bool blockUntilIdle(Deadline Timeout) {
553 std::unique_lock<std::mutex> Lock(Mu);
554 return wait(Lock, CV, Timeout,
555 [&] { return Queue.empty() && !ActiveTask; });
556 }
557
559 {
560 std::lock_guard<std::mutex> Lock(Mu);
561 ShouldStop.store(true, std::memory_order_release);
562 }
563 CV.notify_all();
564 Thread.join();
565 }
566};
567
568// The DirBasedCDB associates each file with a specific CDB.
569// When a CDB is discovered, it may claim to describe files that we associate
570// with a different CDB. We do not want to broadcast discovery of these, and
571// trigger background indexing of them.
572//
573// We must filter the list, and check whether they are associated with this CDB.
574// This class attempts to do so efficiently.
575//
576// Roughly, it:
577// - loads the config for each file, and determines the relevant search path
578// - gathers all directories that are part of any search path
579// - (lazily) checks for a CDB in each such directory at most once
580// - walks the search path for each file and determines whether to include it.
582 llvm::StringRef ThisDir;
584
585 // Keep track of all directories we might check for CDBs.
586 struct DirInfo {
587 DirectoryCache *Cache = nullptr;
588 enum { Unknown, Missing, TargetCDB, OtherCDB } State = Unknown;
589 DirInfo *Parent = nullptr;
590 };
591 llvm::StringMap<DirInfo> Dirs;
592
593 // A search path starts at a directory, and either includes ancestors or not.
594 using SearchPath = llvm::PointerIntPair<DirInfo *, 1>;
595
596 // Add all ancestor directories of FilePath to the tracked set.
597 // Returns the immediate parent of the file.
598 DirInfo *addParents(llvm::StringRef FilePath) {
599 DirInfo *Leaf = nullptr;
600 DirInfo *Child = nullptr;
601 actOnAllParentDirectories(FilePath, [&](llvm::StringRef Dir) {
602 auto &Info = Dirs[Dir];
603 // If this is the first iteration, then this node is the overall result.
604 if (!Leaf)
605 Leaf = &Info;
606 // Fill in the parent link from the previous iteration to this parent.
607 if (Child)
608 Child->Parent = &Info;
609 // Keep walking, whether we inserted or not, if parent link is missing.
610 // (If it's present, parent links must be present up to the root, so stop)
611 Child = &Info;
612 return Info.Parent != nullptr;
613 });
614 return Leaf;
615 }
616
617 // Populates DirInfo::Cache (and State, if it is TargetCDB).
618 void grabCaches() {
619 // Fast path out if there were no files, or CDB loading is off.
620 if (Dirs.empty())
621 return;
622
623 std::vector<llvm::StringRef> DirKeys;
624 std::vector<DirInfo *> DirValues;
625 DirKeys.reserve(Dirs.size() + 1);
626 DirValues.reserve(Dirs.size());
627 for (auto &E : Dirs) {
628 DirKeys.push_back(E.first());
629 DirValues.push_back(&E.second);
630 }
631
632 // Also look up the cache entry for the CDB we're broadcasting.
633 // Comparing DirectoryCache pointers is more robust than checking string
634 // equality, e.g. reuses the case-sensitivity handling.
635 DirKeys.push_back(ThisDir);
636 auto DirCaches = Parent.getDirectoryCaches(DirKeys);
637 const DirectoryCache *ThisCache = DirCaches.back();
638 DirCaches.pop_back();
639 DirKeys.pop_back();
640
641 for (unsigned I = 0; I < DirKeys.size(); ++I) {
642 DirValues[I]->Cache = DirCaches[I];
643 if (DirCaches[I] == ThisCache)
644 DirValues[I]->State = DirInfo::TargetCDB;
645 }
646 }
647
648 // Should we include a file from this search path?
649 bool shouldInclude(SearchPath P) {
650 DirInfo *Info = P.getPointer();
651 if (!Info)
652 return false;
653 if (Info->State == DirInfo::Unknown) {
654 assert(Info->Cache && "grabCaches() should have filled this");
655 // Given that we know that CDBs have been moved/generated, don't trust
656 // caches. (This should be rare, so it's OK to add a little latency).
657 constexpr auto IgnoreCache = std::chrono::steady_clock::time_point::max();
658 // Don't broadcast CDBs discovered while broadcasting!
659 bool ShouldBroadcast = false;
660 bool Exists =
661 nullptr != Info->Cache->get(Parent.Opts.TFS, ShouldBroadcast,
662 /*FreshTime=*/IgnoreCache,
663 /*FreshTimeMissing=*/IgnoreCache);
664 Info->State = Exists ? DirInfo::OtherCDB : DirInfo::Missing;
665 }
666 // If we have a CDB, include the file if it's the target CDB only.
667 if (Info->State != DirInfo::Missing)
668 return Info->State == DirInfo::TargetCDB;
669 // If we have no CDB and no relevant parent, don't include the file.
670 if (!P.getInt() || !Info->Parent)
671 return false;
672 // Walk up to the next parent.
673 return shouldInclude(SearchPath(Info->Parent, 1));
674 }
675
676public:
677 Filter(llvm::StringRef ThisDir,
679 : ThisDir(ThisDir), Parent(Parent) {}
680
681 std::vector<std::string> filter(std::vector<std::string> AllFiles,
682 std::atomic<bool> &ShouldStop) {
683 std::vector<std::string> Filtered;
684 // Allow for clean early-exit of the slow parts.
685 auto ExitEarly = [&] {
686 if (ShouldStop.load(std::memory_order_acquire)) {
687 log("Giving up on broadcasting CDB, as we're shutting down");
688 Filtered.clear();
689 return true;
690 }
691 return false;
692 };
693 // Compute search path for each file.
694 std::vector<SearchPath> SearchPaths(AllFiles.size());
695 for (unsigned I = 0; I < AllFiles.size(); ++I) {
696 if (Parent.Opts.CompileCommandsDir) { // FIXME: unify with config
697 SearchPaths[I].setPointer(&Dirs[*Parent.Opts.CompileCommandsDir]);
698 continue;
699 }
700 if (ExitEarly()) // loading config may be slow
701 return Filtered;
702 WithContext WithProvidedContent(Parent.Opts.ContextProvider(AllFiles[I]));
703 const Config::CDBSearchSpec &Spec =
704 Config::current().CompileFlags.CDBSearch;
705 switch (Spec.Policy) {
707 break;
709 SearchPaths[I].setInt(/*Recursive=*/1);
710 SearchPaths[I].setPointer(addParents(AllFiles[I]));
711 break;
713 SearchPaths[I].setPointer(&Dirs[*Spec.FixedCDBPath]);
714 break;
715 }
716 }
717 // Get the CDB cache for each dir on the search path, but don't load yet.
718 grabCaches();
719 // Now work out which files we want to keep, loading CDBs where needed.
720 for (unsigned I = 0; I < AllFiles.size(); ++I) {
721 if (ExitEarly()) // loading CDBs may be slow
722 return Filtered;
723 if (shouldInclude(SearchPaths[I]))
724 Filtered.push_back(std::move(AllFiles[I]));
725 }
726 return Filtered;
727 }
728};
729
730void DirectoryBasedGlobalCompilationDatabase::BroadcastThread::process(
731 const CDBLookupResult &T) {
732 vlog("Broadcasting compilation database from {0}", T.PI.SourceRoot);
733 std::vector<std::string> GovernedFiles =
734 Filter(T.PI.SourceRoot, Parent).filter(T.CDB->getAllFiles(), ShouldStop);
735 if (!GovernedFiles.empty())
736 Parent.OnCommandChanged.broadcast(std::move(GovernedFiles));
737}
738
739void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
740 CDBLookupResult Result) const {
741 assert(Result.CDB && "Trying to broadcast an invalid CDB!");
742 Broadcaster->enqueue(Result);
743}
744
746 Deadline Timeout) const {
747 return Broadcaster->blockUntilIdle(Timeout);
748}
749
750std::optional<ProjectInfo>
752 CDBLookupRequest Req;
753 Req.FileName = File;
754 Req.ShouldBroadcast = false;
755 Req.FreshTime = Req.FreshTimeMissing =
756 std::chrono::steady_clock::time_point::min();
757 auto Res = lookupCDB(Req);
758 if (!Res)
759 return std::nullopt;
760 return Res->PI;
761}
762
763std::unique_ptr<ProjectModules>
765 CDBLookupRequest Req;
766 Req.FileName = File;
767 Req.ShouldBroadcast = false;
768 Req.FreshTime = Req.FreshTimeMissing =
769 std::chrono::steady_clock::time_point::min();
770 auto Res = lookupCDB(Req);
771 if (!Res)
772 return {};
773
774 return clang::clangd::getProjectModules(Res->CDB, Opts.TFS);
775}
776
778 std::vector<std::string> FallbackFlags,
779 CommandMangler Mangler,
780 std::optional<std::string> FallbackWorkingDirectory)
782 Mangler(std::move(Mangler)), FallbackFlags(std::move(FallbackFlags)) {}
783
784std::optional<tooling::CompileCommand>
786 std::optional<tooling::CompileCommand> Cmd;
787 {
788 std::lock_guard<std::mutex> Lock(Mutex);
789 auto It = Commands.find(removeDots(File));
790 if (It != Commands.end())
791 Cmd = It->second;
792 }
793 if (Cmd) {
794 // FS used for expanding response files.
795 // FIXME: ExpandResponseFiles appears not to provide the usual
796 // thread-safety guarantees, as the access to FS is not locked!
797 // For now, use the real FS, which is known to be threadsafe (if we don't
798 // use/change working directory, which ExpandResponseFiles doesn't).
799 auto FS = llvm::vfs::getRealFileSystem();
800 auto Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
801 ? llvm::cl::TokenizeWindowsCommandLine
802 : llvm::cl::TokenizeGNUCommandLine;
803 // Compile command pushed via LSP protocol may have response files that need
804 // to be expanded before further processing. For CDB for files it happens in
805 // the main CDB when reading it from the JSON file.
806 tooling::addExpandedResponseFiles(Cmd->CommandLine, Cmd->Directory,
807 Tokenizer, *FS);
808 }
809 if (!Cmd)
811 if (!Cmd)
812 return std::nullopt;
813 if (Mangler)
814 Mangler(*Cmd, File);
815 return Cmd;
816}
817
818tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
820 std::lock_guard<std::mutex> Lock(Mutex);
821 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
822 FallbackFlags.end());
823 if (Mangler)
824 Mangler(Cmd, File);
825 return Cmd;
826}
827
829 std::optional<tooling::CompileCommand> Cmd) {
830 // We store a canonical version internally to prevent mismatches between set
831 // and get compile commands. Also it assures clients listening to broadcasts
832 // doesn't receive different names for the same file.
833 std::string CanonPath = removeDots(File);
834 {
835 std::unique_lock<std::mutex> Lock(Mutex);
836 if (Cmd) {
837 if (auto [It, Inserted] =
838 Commands.try_emplace(CanonPath, std::move(*Cmd));
839 !Inserted) {
840 if (It->second == *Cmd)
841 return false;
842 It->second = *Cmd;
843 }
844 } else
845 Commands.erase(CanonPath);
846 }
847 OnCommandChanged.broadcast({CanonPath});
848 return true;
849}
850
851std::unique_ptr<ProjectModules>
854 if (!MDB) {
855 log("Failed to get compilation Database for {0}", File);
856 return {};
857 }
858 MDB->setCommandMangler([&Mangler = Mangler](tooling::CompileCommand &Command,
859 PathRef CommandPath) {
860 Mangler(Command, CommandPath);
861 });
862 return MDB;
863}
864
866 const GlobalCompilationDatabase *Base,
867 std::optional<std::string> FallbackWorkingDirectory)
869 if (Base)
870 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
871 OnCommandChanged.broadcast(Changes);
872 });
873}
874
876 std::unique_ptr<GlobalCompilationDatabase> Base,
877 std::optional<std::string> FallbackWorkingDirectory)
879 BaseOwner = std::move(Base);
880}
881
882std::optional<tooling::CompileCommand>
884 if (!Base)
885 return std::nullopt;
886 return Base->getCompileCommand(File);
887}
888
889std::optional<ProjectInfo> DelegatingCDB::getProjectInfo(PathRef File) const {
890 if (!Base)
891 return std::nullopt;
892 return Base->getProjectInfo(File);
893}
894
895std::unique_ptr<ProjectModules>
897 if (!Base)
898 return nullptr;
899 return Base->getProjectModules(File);
900}
901
902tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File) const {
903 if (!Base)
905 return Base->getFallbackCommand(File);
906}
907
909 if (!Base)
910 return true;
911 return Base->blockUntilIdle(D);
912}
913
914} // namespace clangd
915} // namespace clang
#define dlog(...)
Definition Logger.h:101
A context is an immutable container for per-request data that must be propagated through layers that ...
Definition Context.h:69
static const Context & current()
Returns the context for the current thread, creating it if needed.
Definition Context.cpp:27
A point in time we can wait for.
Definition Threading.h:46
tooling::CompileCommand getFallbackCommand(PathRef File) const override
Makes a guess at how to build a file.
DelegatingCDB(const GlobalCompilationDatabase *Base, std::optional< std::string > FallbackWorkingDirectory=std::nullopt)
std::optional< tooling::CompileCommand > getCompileCommand(PathRef File) const override
If there are any known-good commands for building this file, returns one.
bool blockUntilIdle(Deadline D) const override
If the CDB does any asynchronous work, wait for it to complete.
std::optional< ProjectInfo > getProjectInfo(PathRef File) const override
Finds the closest project to File.
std::unique_ptr< ProjectModules > getProjectModules(PathRef File) const override
Get the modules in the closest project to File.
Filter(llvm::StringRef ThisDir, DirectoryBasedGlobalCompilationDatabase &Parent)
std::vector< std::string > filter(std::vector< std::string > AllFiles, std::atomic< bool > &ShouldStop)
std::shared_ptr< const tooling::CompilationDatabase > get(const ThreadsafeFS &TFS, bool &ShouldBroadcast, stopwatch::time_point FreshTime, stopwatch::time_point FreshTimeMissing)
bool blockUntilIdle(Deadline Timeout) const override
If the CDB does any asynchronous work, wait for it to complete.
std::unique_ptr< ProjectModules > getProjectModules(PathRef File) const override
Get the modules in the closest project to File.
std::optional< tooling::CompileCommand > getCompileCommand(PathRef File) const override
Scans File's parents looking for compilation databases.
std::optional< ProjectInfo > getProjectInfo(PathRef File) const override
Returns the path to first directory containing a compilation database in File's parents.
Provides compilation arguments used for parsing C and C++ files.
std::optional< std::string > FallbackWorkingDirectory
virtual tooling::CompileCommand getFallbackCommand(PathRef File) const
Makes a guess at how to build a file.
GlobalCompilationDatabase(std::optional< std::string > FallbackWorkingDirectory=std::nullopt)
llvm::unique_function< void(tooling::CompileCommand &, StringRef File) const > CommandMangler
bool setCompileCommand(PathRef File, std::optional< tooling::CompileCommand > CompilationCommand)
Sets or clears the compilation command for a particular file.
tooling::CompileCommand getFallbackCommand(PathRef File) const override
Makes a guess at how to build a file.
std::optional< tooling::CompileCommand > getCompileCommand(PathRef File) const override
If there are any known-good commands for building this file, returns one.
std::unique_ptr< ProjectModules > getProjectModules(PathRef File) const override
Get the modules in the closest project to File.
OverlayCDB(const GlobalCompilationDatabase *Base, std::vector< std::string > FallbackFlags={}, CommandMangler Mangler=nullptr, std::optional< std::string > FallbackWorkingDirectory=std::nullopt)
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > view(std::nullopt_t CWD) const
Obtain a vfs::FileSystem with an arbitrary initial working directory.
WithContext replaces Context::current() with a provided scope.
Definition Context.h:185
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
@ Info
An information message.
Definition Protocol.h:755
@ Error
An error message.
Definition Protocol.h:751
std::string maybeCaseFoldPath(PathRef Path)
Definition Path.cpp:18
FileDigest digest(llvm::StringRef Content)
static std::unique_ptr< tooling::CompilationDatabase > parseJSON(PathRef Path, llvm::StringRef Data, std::string &Error)
void vlog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:72
PathRef absoluteParent(PathRef Path)
Variant of parent_path that operates only on absolute paths.
Definition Path.cpp:22
void log(const char *Fmt, Ts &&... Vals)
Definition Logger.h:67
void wait(std::unique_lock< std::mutex > &Lock, std::condition_variable &CV, Deadline D)
Wait once on CV for the specified duration.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition Path.h:29
std::string Path
A typedef to represent a file path.
Definition Path.h:26
Path removeDots(PathRef File)
Returns a version of File that doesn't contain dots and dot dots.
Definition FS.cpp:116
static std::unique_ptr< tooling::CompilationDatabase > parseFixed(PathRef Path, llvm::StringRef Data, std::string &Error)
std::array< uint8_t, 8 > FileDigest
Definition SourceCode.h:42
void elog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:61
std::unique_ptr< ProjectModules > getProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
Creates the project-modules facade used by clangd.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
-clang-tidy
enum clang::clangd::Config::CDBSearchSpec::@161062317271326205360254020036256036377317332042 Policy
std::optional< std::string > FixedCDBPath
Definition Config.h:60
struct clang::clangd::Config::@347104204155140144054042115114221214347344026246 CompileFlags
Controls how the compile command for the current file is determined.
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
Definition Config.cpp:17
CDBSearchSpec CDBSearch
Where to search for compilation databases for this file's flags.
Definition Config.h:70
enum clang::clangd::DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::LoadResult::@105304303162072107165316122121134350023026320055 Result
void applyFallbackWorkingDirectory(std::optional< std::string > FallbackWorkingDirectory)