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