17#include "clang/Tooling/ArgumentsAdjusters.h"
18#include "clang/Tooling/CompilationDatabase.h"
19#include "clang/Tooling/CompilationDatabasePluginRegistry.h"
20#include "clang/Tooling/JSONCompilationDatabase.h"
21#include "llvm/ADT/PointerIntPair.h"
22#include "llvm/ADT/STLExtras.h"
23#include "llvm/ADT/ScopeExit.h"
24#include "llvm/ADT/SmallString.h"
25#include "llvm/ADT/StringMap.h"
26#include "llvm/Support/Path.h"
27#include "llvm/Support/VirtualFileSystem.h"
30#include <condition_variable>
52tooling::CompileCommand
54 std::vector<std::string> Argv = {
"clang"};
58 auto FileExtension = llvm::sys::path::extension(
File);
59 if (FileExtension.empty() || FileExtension ==
".h")
60 Argv.push_back(
"-xobjective-c++-header");
61 Argv.push_back(std::string(
File));
62 tooling::CompileCommand Cmd(llvm::sys::path::parent_path(
File),
63 llvm::sys::path::filename(
File), std::move(Argv),
65 Cmd.Heuristic =
"clangd fallback";
81 using stopwatch = std::chrono::steady_clock;
90 CachedFile(llvm::StringRef
Parent, llvm::StringRef Rel) {
92 llvm::sys::path::append(
Path, Rel);
93 this->Path =
Path.str().str();
96 size_t Size = NoFileCached;
97 llvm::sys::TimePoint<> ModifiedTime;
100 static constexpr size_t NoFileCached = -1;
109 std::unique_ptr<llvm::MemoryBuffer>
Buffer;
112 LoadResult load(llvm::vfs::FileSystem &FS,
bool HasOldData);
117 std::atomic<stopwatch::rep> NoCDBAt = {
118 stopwatch::time_point::min().time_since_epoch().count()};
123 stopwatch::time_point CachePopulatedAt = stopwatch::time_point::min();
125 bool NeedsBroadcast =
false;
128 std::shared_ptr<tooling::CompilationDatabase> CDB;
130 CachedFile CompileCommandsJson;
131 CachedFile BuildCompileCommandsJson;
132 CachedFile CompileFlagsTxt;
139 CachedFile *ActiveCachedFile =
nullptr;
143 : CompileCommandsJson(
Path,
"compile_commands.json"),
144 BuildCompileCommandsJson(
Path,
"build/compile_commands.json"),
145 CompileFlagsTxt(
Path,
"compile_flags.txt"),
Path(
Path) {
146 assert(llvm::sys::path::is_absolute(
Path));
159 std::shared_ptr<const tooling::CompilationDatabase>
161 stopwatch::time_point FreshTime, stopwatch::time_point FreshTimeMissing) {
163 if (stopwatch::time_point(stopwatch::duration(NoCDBAt.load())) >
165 ShouldBroadcast =
false;
169 std::lock_guard<std::mutex> Lock(Mu);
170 auto RequestBroadcast = llvm::make_scope_exit([&, OldCDB(CDB.get())] {
172 if (CDB != nullptr && CDB.get() != OldCDB)
173 NeedsBroadcast = true;
174 else if (CDB == nullptr)
175 NeedsBroadcast = false;
177 if (!ShouldBroadcast)
179 ShouldBroadcast = NeedsBroadcast;
180 NeedsBroadcast = false;
184 if (CachePopulatedAt > FreshTime)
187 if (load(*TFS.view(std::nullopt))) {
189 CachePopulatedAt = stopwatch::now();
190 NoCDBAt.store((CDB ? stopwatch::time_point::min() : CachePopulatedAt)
200 bool load(llvm::vfs::FileSystem &FS);
204DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::load(
205 llvm::vfs::FileSystem &FS,
bool HasOldData) {
206 auto Stat = FS.status(
Path);
207 if (!Stat || !Stat->isRegularFile()) {
210 return {LoadResult::FileNotFound,
nullptr};
213 if (HasOldData && Stat->getLastModificationTime() == ModifiedTime &&
214 Stat->getSize() == Size)
215 return {LoadResult::FoundSameData,
nullptr};
216 auto Buf = FS.getBufferForFile(
Path);
217 if (!Buf || (*Buf)->getBufferSize() != Stat->getSize()) {
224 elog(
"Failed to read {0}: {1}",
Path,
225 Buf ?
"size changed" : Buf.getError().message());
226 return {LoadResult::TransientError,
nullptr};
230 if (HasOldData && NewContentHash == ContentHash) {
232 ModifiedTime = Stat->getLastModificationTime();
233 return {LoadResult::FoundSameData,
nullptr};
236 Size = (*Buf)->getBufferSize();
237 ModifiedTime = Stat->getLastModificationTime();
238 ContentHash = NewContentHash;
239 return {LoadResult::FoundNewData, std::move(*Buf)};
243static std::unique_ptr<tooling::CompilationDatabase>
245 if (
auto CDB = tooling::JSONCompilationDatabase::loadFromBuffer(
246 Data,
Error, tooling::JSONCommandLineSyntax::AutoDetect)) {
247 return tooling::inferMissingCompileCommands(std::move(CDB));
251static std::unique_ptr<tooling::CompilationDatabase>
253 return tooling::FixedCompilationDatabase::loadFromBuffer(
254 llvm::sys::path::parent_path(
Path), Data,
Error);
257bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
258 llvm::vfs::FileSystem &FS) {
259 dlog(
"Probing directory {0}",
Path);
269 std::unique_ptr<tooling::CompilationDatabase> (*Parser)(
274 for (
const auto &
Entry : {CDBFile{&CompileCommandsJson,
parseJSON},
275 CDBFile{&BuildCompileCommandsJson,
parseJSON},
277 bool Active = ActiveCachedFile ==
Entry.File;
278 auto Loaded =
Entry.File->load(FS, Active);
279 switch (Loaded.Result) {
282 log(
"Unloaded compilation database from {0}",
Entry.File->Path);
283 ActiveCachedFile =
nullptr;
292 assert(Active &&
"CachedFile may not return 'same data' if !HasOldData");
297 CDB =
Entry.Parser(
Entry.File->Path, Loaded.Buffer->getBuffer(),
Error);
299 log(
"{0} compilation database from {1}", Active ?
"Reloaded" :
"Loaded",
302 elog(
"Failed to load compilation database from {0}: {1}",
304 ActiveCachedFile =
Entry.File;
315 if (CachePopulatedAt > stopwatch::time_point::min())
317 for (
const auto &
Entry :
318 tooling::CompilationDatabasePluginRegistry::entries()) {
320 if (
Entry.getName() ==
"fixed-compilation-database" ||
321 Entry.getName() ==
"json-compilation-database")
323 auto Plugin =
Entry.instantiate();
324 if (
auto CDB = Plugin->loadFromDirectory(
Path,
Error)) {
325 log(
"Loaded compilation database from {0} with plugin {1}",
Path,
327 this->CDB = std::move(CDB);
332 dlog(
"No compilation database at {0}",
Path);
348std::optional<tooling::CompileCommand>
350 CDBLookupRequest Req;
352 Req.ShouldBroadcast =
true;
353 auto Now = std::chrono::steady_clock::now();
357 auto Res = lookupCDB(Req);
359 log(
"Failed to find compilation database for {0}",
File);
363 auto Candidates = Res->CDB->getCompileCommands(
File);
364 if (!Candidates.empty())
365 return std::move(Candidates.front());
370std::vector<DirectoryBasedGlobalCompilationDatabase::DirectoryCache *>
371DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
372 llvm::ArrayRef<llvm::StringRef> Dirs)
const {
373 std::vector<std::string> FoldedDirs;
374 FoldedDirs.reserve(Dirs.size());
375 for (
const auto &Dir : Dirs) {
377 if (!llvm::sys::path::is_absolute(Dir))
378 elog(
"Trying to cache CDB for relative {0}");
383 std::vector<DirectoryCache *> Ret;
384 Ret.reserve(Dirs.size());
386 std::lock_guard<std::mutex> Lock(DirCachesMutex);
387 for (
unsigned I = 0; I < Dirs.size(); ++I)
388 Ret.push_back(&DirCaches.try_emplace(FoldedDirs[I], Dirs[I]).first->second);
392std::optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
393DirectoryBasedGlobalCompilationDatabase::lookupCDB(
394 CDBLookupRequest Request)
const {
395 assert(llvm::sys::path::is_absolute(Request.FileName) &&
396 "path must be absolute");
399 std::vector<llvm::StringRef> SearchDirs;
403 WithContext WithProvidedContext(Opts.
ContextProvider(Request.FileName));
405 switch (Spec.Policy) {
409 Storage = *Spec.FixedCDBPath;
410 SearchDirs = {Storage};
417 actOnAllParentDirectories(Storage, [&](llvm::StringRef Dir) {
418 SearchDirs.push_back(Dir);
424 std::shared_ptr<const tooling::CompilationDatabase> CDB =
nullptr;
425 bool ShouldBroadcast =
false;
426 DirectoryCache *DirCache =
nullptr;
427 for (DirectoryCache *
Candidate : getDirectoryCaches(SearchDirs)) {
428 bool CandidateShouldBroadcast = Request.ShouldBroadcast;
429 if ((CDB =
Candidate->get(Opts.
TFS, CandidateShouldBroadcast,
430 Request.FreshTime, Request.FreshTimeMissing))) {
432 ShouldBroadcast = CandidateShouldBroadcast;
440 CDBLookupResult Result;
441 Result.CDB = std::move(CDB);
442 Result.PI.SourceRoot = DirCache->Path;
445 broadcastCDB(Result);
461 std::condition_variable CV;
464 std::atomic<bool> ShouldStop = {
false};
466 CDBLookupResult Lookup;
469 std::deque<Task> Queue;
470 std::optional<Task> ActiveTask;
475 std::unique_lock<std::mutex> Lock(Mu);
477 bool Stopping =
false;
479 return (Stopping = ShouldStop.load(std::memory_order_acquire)) ||
487 ActiveTask = std::move(Queue.front());
493 process(ActiveTask->Lookup);
502 void process(
const CDBLookupResult &T);
510 assert(!Lookup.PI.SourceRoot.empty());
511 std::lock_guard<std::mutex> Lock(Mu);
513 llvm::erase_if(Queue, [&](
const Task &T) {
514 return T.Lookup.PI.SourceRoot == Lookup.PI.SourceRoot;
522 std::unique_lock<std::mutex> Lock(Mu);
523 return wait(Lock, CV, Timeout,
524 [&] {
return Queue.empty() && !ActiveTask; });
529 std::lock_guard<std::mutex> Lock(Mu);
530 ShouldStop.store(
true, std::memory_order_release);
551 llvm::StringRef ThisDir;
558 DirInfo *
Parent =
nullptr;
560 llvm::StringMap<DirInfo> Dirs;
563 using SearchPath = llvm::PointerIntPair<DirInfo *, 1>;
567 DirInfo *addParents(llvm::StringRef FilePath) {
568 DirInfo *Leaf =
nullptr;
569 DirInfo *Child =
nullptr;
570 actOnAllParentDirectories(FilePath, [&](llvm::StringRef Dir) {
571 auto &
Info = Dirs[Dir];
577 Child->Parent = &
Info;
581 return Info.Parent !=
nullptr;
592 std::vector<llvm::StringRef> DirKeys;
593 std::vector<DirInfo *> DirValues;
594 DirKeys.reserve(Dirs.size() + 1);
595 DirValues.reserve(Dirs.size());
596 for (
auto &
E : Dirs) {
597 DirKeys.push_back(
E.first());
598 DirValues.push_back(&
E.second);
604 DirKeys.push_back(ThisDir);
605 auto DirCaches =
Parent.getDirectoryCaches(DirKeys);
607 DirCaches.pop_back();
610 for (
unsigned I = 0; I < DirKeys.size(); ++I) {
611 DirValues[I]->Cache = DirCaches[I];
612 if (DirCaches[I] == ThisCache)
613 DirValues[I]->State = DirInfo::TargetCDB;
618 bool shouldInclude(SearchPath P) {
619 DirInfo *
Info = P.getPointer();
622 if (
Info->State == DirInfo::Unknown) {
623 assert(
Info->Cache &&
"grabCaches() should have filled this");
626 constexpr auto IgnoreCache = std::chrono::steady_clock::time_point::max();
628 bool ShouldBroadcast =
false;
630 nullptr !=
Info->Cache->get(
Parent.Opts.TFS, ShouldBroadcast,
633 Info->State = Exists ? DirInfo::OtherCDB : DirInfo::Missing;
636 if (
Info->State != DirInfo::Missing)
637 return Info->State == DirInfo::TargetCDB;
639 if (!P.getInt() || !
Info->Parent)
642 return shouldInclude(SearchPath(
Info->Parent, 1));
650 std::vector<std::string>
filter(std::vector<std::string> AllFiles,
651 std::atomic<bool> &ShouldStop) {
652 std::vector<std::string> Filtered;
654 auto ExitEarly = [&] {
655 if (ShouldStop.load(std::memory_order_acquire)) {
656 log(
"Giving up on broadcasting CDB, as we're shutting down");
663 std::vector<SearchPath> SearchPaths(AllFiles.size());
664 for (
unsigned I = 0; I < AllFiles.size(); ++I) {
665 if (
Parent.Opts.CompileCommandsDir) {
666 SearchPaths[I].setPointer(&Dirs[*
Parent.Opts.CompileCommandsDir]);
678 SearchPaths[I].setInt(1);
679 SearchPaths[I].setPointer(addParents(AllFiles[I]));
689 for (
unsigned I = 0; I < AllFiles.size(); ++I) {
692 if (shouldInclude(SearchPaths[I]))
693 Filtered.push_back(std::move(AllFiles[I]));
699void DirectoryBasedGlobalCompilationDatabase::BroadcastThread::process(
700 const CDBLookupResult &T) {
701 vlog(
"Broadcasting compilation database from {0}", T.PI.SourceRoot);
702 std::vector<std::string> GovernedFiles =
703 Filter(T.PI.SourceRoot, Parent).filter(T.CDB->getAllFiles(), ShouldStop);
704 if (!GovernedFiles.empty())
708void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
709 CDBLookupResult Result)
const {
710 assert(Result.CDB &&
"Trying to broadcast an invalid CDB!");
711 Broadcaster->enqueue(Result);
716 return Broadcaster->blockUntilIdle(Timeout);
719std::optional<ProjectInfo>
721 CDBLookupRequest Req;
723 Req.ShouldBroadcast =
false;
724 Req.FreshTime = Req.FreshTimeMissing =
725 std::chrono::steady_clock::time_point::min();
726 auto Res = lookupCDB(Req);
733 std::vector<std::string> FallbackFlags,
736 FallbackFlags(std::move(FallbackFlags)) {}
738std::optional<tooling::CompileCommand>
740 std::optional<tooling::CompileCommand> Cmd;
742 std::lock_guard<std::mutex> Lock(Mutex);
744 if (It != Commands.end())
758 std::lock_guard<std::mutex> Lock(Mutex);
759 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
760 FallbackFlags.end());
767 std::optional<tooling::CompileCommand> Cmd) {
773 std::unique_lock<std::mutex> Lock(Mutex);
775 Commands[CanonPath] = std::move(*Cmd);
777 Commands.erase(CanonPath);
785 BaseChanged = Base->watch([
this](
const std::vector<std::string>
Changes) {
792 BaseOwner = std::move(Base);
795std::optional<tooling::CompileCommand>
include_cleaner::Header Missing
A context is an immutable container for per-request data that must be propagated through layers that ...
Context clone() const
Clone this context object.
static const Context & current()
Returns the context for the current thread, creating it if needed.
A point in time we can wait for.
tooling::CompileCommand getFallbackCommand(PathRef File) const override
Makes a guess at how to build a file.
DelegatingCDB(const GlobalCompilationDatabase *Base)
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.
Filter(llvm::StringRef ThisDir, DirectoryBasedGlobalCompilationDatabase &Parent)
std::vector< std::string > filter(std::vector< std::string > AllFiles, std::atomic< bool > &ShouldStop)
bool blockUntilIdle(Deadline Timeout)
void enqueue(CDBLookupResult Lookup)
BroadcastThread(DirectoryBasedGlobalCompilationDatabase &Parent)
DirectoryCache(llvm::StringRef Path)
std::shared_ptr< const tooling::CompilationDatabase > get(const ThreadsafeFS &TFS, bool &ShouldBroadcast, stopwatch::time_point FreshTime, stopwatch::time_point FreshTimeMissing)
Gets compile args from tooling::CompilationDatabases built for parent directories.
bool blockUntilIdle(Deadline Timeout) const override
If the CDB does any asynchronous work, wait for it to complete.
DirectoryBasedGlobalCompilationDatabase(const Options &Opts)
~DirectoryBasedGlobalCompilationDatabase() override
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.
void broadcast(const T &V)
Provides compilation arguments used for parsing C and C++ files.
virtual tooling::CompileCommand getFallbackCommand(PathRef File) const
Makes a guess at how to build a file.
virtual std::optional< ProjectInfo > getProjectInfo(PathRef File) const
Finds the closest project to File.
virtual bool blockUntilIdle(Deadline D) const
If the CDB does any asynchronous work, wait for it to complete.
virtual std::optional< tooling::CompileCommand > getCompileCommand(PathRef File) const =0
If there are any known-good commands for building this file, returns one.
CommandChanged OnCommandChanged
tooling::CompileCommand getFallbackCommand(PathRef File) const override
Makes a guess at how to build a file.
void setCompileCommand(PathRef File, std::optional< tooling::CompileCommand > CompilationCommand)
Sets or clears the compilation command for a particular file.
std::optional< tooling::CompileCommand > getCompileCommand(PathRef File) const override
If there are any known-good commands for building this file, returns one.
llvm::unique_function< void(tooling::CompileCommand &, StringRef File) const > CommandMangler
OverlayCDB(const GlobalCompilationDatabase *Base, std::vector< std::string > FallbackFlags={}, CommandMangler Mangler=nullptr)
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
WithContext replaces Context::current() with a provided scope.
@ Info
An information message.
std::string maybeCaseFoldPath(PathRef Path)
std::string Path
A typedef to represent a file path.
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)
std::array< uint8_t, 8 > FileDigest
PathRef absoluteParent(PathRef Path)
Variant of parent_path that operates only on absolute paths.
void log(const char *Fmt, Ts &&... Vals)
void wait(std::unique_lock< std::mutex > &Lock, std::condition_variable &CV, Deadline D)
Wait once on CV for the specified duration.
Path removeDots(PathRef File)
Returns a version of File that doesn't contain dots and dot dots.
static std::unique_ptr< tooling::CompilationDatabase > parseFixed(PathRef Path, llvm::StringRef Data, std::string &Error)
llvm::StringRef PathRef
A typedef to represent a ref to file path.
void elog(const char *Fmt, Ts &&... Vals)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
enum clang::clangd::Config::CDBSearchSpec::@10 Policy
std::optional< std::string > FixedCDBPath
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
CDBSearchSpec CDBSearch
Where to search for compilation databases for this file's flags.
struct clang::clangd::Config::@2 CompileFlags
Controls how the compile command for the current file is determined.
enum clang::clangd::DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::LoadResult::@14 Result
std::unique_ptr< llvm::MemoryBuffer > Buffer
std::chrono::steady_clock::duration RevalidateAfter
std::chrono::steady_clock::duration RevalidateMissingAfter
std::function< Context(llvm::StringRef)> ContextProvider
std::optional< Path > CompileCommandsDir