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"
34#include <condition_variable>
48void actOnAllParentDirectories(
PathRef FileName,
49 llvm::function_ref<
bool(
PathRef)> Action) {
57tooling::CompileCommand
59 std::vector<std::string> Argv = {
"clang"};
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));
69 : llvm::sys::path::parent_path(
File),
70 llvm::sys::path::filename(
File), std::move(Argv),
72 Cmd.Heuristic =
"clangd fallback";
88 using stopwatch = std::chrono::steady_clock;
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();
103 size_t Size = NoFileCached;
104 llvm::sys::TimePoint<> ModifiedTime;
107 static constexpr size_t NoFileCached = -1;
116 std::unique_ptr<llvm::MemoryBuffer>
Buffer;
119 LoadResult load(llvm::vfs::FileSystem &FS,
bool HasOldData);
124 std::atomic<stopwatch::rep> NoCDBAt = {
125 stopwatch::time_point::min().time_since_epoch().count()};
130 stopwatch::time_point CachePopulatedAt = stopwatch::time_point::min();
132 bool NeedsBroadcast =
false;
135 std::shared_ptr<tooling::CompilationDatabase> CDB;
137 CachedFile CompileCommandsJson;
138 CachedFile BuildCompileCommandsJson;
139 CachedFile CompileFlagsTxt;
146 CachedFile *ActiveCachedFile =
nullptr;
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));
166 std::shared_ptr<const tooling::CompilationDatabase>
168 stopwatch::time_point FreshTime, stopwatch::time_point FreshTimeMissing) {
170 if (stopwatch::time_point(stopwatch::duration(NoCDBAt.load())) >
172 ShouldBroadcast =
false;
176 std::lock_guard<std::mutex> Lock(Mu);
177 auto RequestBroadcast = llvm::make_scope_exit([&, OldCDB(CDB.get())] {
179 if (CDB != nullptr && CDB.get() != OldCDB)
180 NeedsBroadcast = true;
181 else if (CDB == nullptr)
182 NeedsBroadcast = false;
184 if (!ShouldBroadcast)
186 ShouldBroadcast = NeedsBroadcast;
187 NeedsBroadcast = false;
191 if (CachePopulatedAt > FreshTime)
194 if (load(*TFS.
view(std::nullopt))) {
196 CachePopulatedAt = stopwatch::now();
197 NoCDBAt.store((CDB ? stopwatch::time_point::min() : CachePopulatedAt)
207 bool load(llvm::vfs::FileSystem &FS);
211DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::load(
212 llvm::vfs::FileSystem &FS,
bool HasOldData) {
213 auto Stat = FS.status(
Path);
214 if (!Stat || !Stat->isRegularFile()) {
220 if (HasOldData && Stat->getLastModificationTime() == ModifiedTime &&
221 Stat->getSize() == Size)
223 auto Buf = FS.getBufferForFile(Path);
224 if (!Buf || (*Buf)->getBufferSize() != Stat->getSize()) {
231 elog(
"Failed to read {0}: {1}", Path,
232 Buf ?
"size changed" : Buf.getError().message());
237 if (HasOldData && NewContentHash == ContentHash) {
239 ModifiedTime = Stat->getLastModificationTime();
243 Size = (*Buf)->getBufferSize();
244 ModifiedTime = Stat->getLastModificationTime();
245 ContentHash = NewContentHash;
250static std::unique_ptr<tooling::CompilationDatabase>
252 if (
auto CDB = tooling::JSONCompilationDatabase::loadFromBuffer(
253 Data,
Error, tooling::JSONCommandLineSyntax::AutoDetect)) {
261 auto FS = llvm::vfs::getRealFileSystem();
262 return tooling::inferMissingCompileCommands(
263 expandResponseFiles(std::move(CDB), std::move(FS)));
267static std::unique_ptr<tooling::CompilationDatabase>
269 return tooling::FixedCompilationDatabase::loadFromBuffer(
270 llvm::sys::path::parent_path(
Path), Data,
Error);
273bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
274 llvm::vfs::FileSystem &FS) {
275 dlog(
"Probing directory {0}",
Path);
285 std::unique_ptr<tooling::CompilationDatabase> (*Parser)(
290 for (
const auto &
Entry : {CDBFile{&CompileCommandsJson,
parseJSON},
291 CDBFile{&BuildCompileCommandsJson,
parseJSON},
293 bool Active = ActiveCachedFile == Entry.File;
294 auto Loaded = Entry.File->load(FS, Active);
295 switch (Loaded.Result) {
298 log(
"Unloaded compilation database from {0}", Entry.File->Path);
299 ActiveCachedFile =
nullptr;
308 assert(Active &&
"CachedFile may not return 'same data' if !HasOldData");
313 CDB = Entry.Parser(Entry.File->Path, Loaded.Buffer->getBuffer(),
Error);
315 log(
"{0} compilation database from {1}", Active ?
"Reloaded" :
"Loaded",
318 elog(
"Failed to load compilation database from {0}: {1}",
319 Entry.File->Path,
Error);
320 ActiveCachedFile = Entry.File;
331 if (CachePopulatedAt > stopwatch::time_point::min())
333 for (
const auto &Entry :
334 tooling::CompilationDatabasePluginRegistry::entries()) {
336 if (Entry.getName() ==
"fixed-compilation-database" ||
337 Entry.getName() ==
"json-compilation-database")
339 auto Plugin = Entry.instantiate();
340 if (
auto CDB = Plugin->loadFromDirectory(
Path,
Error)) {
341 log(
"Loaded compilation database from {0} with plugin {1}",
Path,
343 this->CDB = std::move(CDB);
348 dlog(
"No compilation database at {0}",
Path);
357 this->Opts.ContextProvider = [](llvm::StringRef) {
358 return Context::current().clone();
365std::optional<tooling::CompileCommand>
367 CDBLookupRequest Req;
369 Req.ShouldBroadcast =
true;
370 auto Now = std::chrono::steady_clock::now();
371 Req.FreshTime = Now - Opts.RevalidateAfter;
372 Req.FreshTimeMissing = Now - Opts.RevalidateMissingAfter;
374 auto Res = lookupCDB(Req);
376 log(
"Failed to find compilation database for {0}",
File);
380 auto Candidates = Res->CDB->getCompileCommands(
File);
381 if (!Candidates.empty())
382 return std::move(Candidates.front());
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) {
394 if (!llvm::sys::path::is_absolute(Dir))
395 elog(
"Trying to cache CDB for relative {0}");
400 std::vector<DirectoryCache *> Ret;
401 Ret.reserve(Dirs.size());
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);
409std::optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
410DirectoryBasedGlobalCompilationDatabase::lookupCDB(
411 CDBLookupRequest Request)
const {
412 assert(llvm::sys::path::is_absolute(Request.FileName) &&
413 "path must be absolute");
416 std::vector<llvm::StringRef> SearchDirs;
420 WithContext WithProvidedContext(Opts.ContextProvider(Request.FileName));
422 switch (Spec.Policy) {
426 Storage = *Spec.FixedCDBPath;
427 SearchDirs = {Storage};
434 actOnAllParentDirectories(Storage, [&](llvm::StringRef Dir) {
435 SearchDirs.push_back(Dir);
441 std::shared_ptr<const tooling::CompilationDatabase> CDB =
nullptr;
442 bool ShouldBroadcast =
false;
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;
457 CDBLookupResult Result;
458 Result.CDB = std::move(CDB);
459 Result.PI.SourceRoot = DirCache->Path;
462 broadcastCDB(Result);
475 SmallString<256> CWD;
476 llvm::sys::fs::current_path(CWD);
477 this->FallbackWorkingDirectory = std::string(CWD);
493 std::condition_variable CV;
496 std::atomic<bool> ShouldStop = {
false};
498 CDBLookupResult Lookup;
501 std::deque<Task> Queue;
502 std::optional<Task> ActiveTask;
507 std::unique_lock<std::mutex> Lock(Mu);
509 bool Stopping =
false;
511 return (Stopping = ShouldStop.load(std::memory_order_acquire)) ||
519 ActiveTask = std::move(Queue.front());
525 process(ActiveTask->Lookup);
534 void process(
const CDBLookupResult &T);
538 : Parent(Parent), Thread([this] {
run(); }) {}
542 assert(!Lookup.PI.SourceRoot.empty());
543 std::lock_guard<std::mutex> Lock(Mu);
545 llvm::erase_if(Queue, [&](
const Task &T) {
546 return T.Lookup.PI.SourceRoot == Lookup.PI.SourceRoot;
554 std::unique_lock<std::mutex> Lock(Mu);
555 return wait(Lock, CV, Timeout,
556 [&] {
return Queue.empty() && !ActiveTask; });
561 std::lock_guard<std::mutex> Lock(Mu);
562 ShouldStop.store(
true, std::memory_order_release);
583 llvm::StringRef ThisDir;
590 DirInfo *Parent =
nullptr;
592 llvm::StringMap<DirInfo> Dirs;
595 using SearchPath = llvm::PointerIntPair<DirInfo *, 1>;
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];
609 Child->Parent = &
Info;
613 return Info.Parent !=
nullptr;
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);
636 DirKeys.push_back(ThisDir);
637 auto DirCaches = Parent.getDirectoryCaches(DirKeys);
639 DirCaches.pop_back();
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;
650 bool shouldInclude(SearchPath P) {
651 DirInfo *
Info = P.getPointer();
654 if (
Info->State == DirInfo::Unknown) {
655 assert(
Info->Cache &&
"grabCaches() should have filled this");
658 constexpr auto IgnoreCache = std::chrono::steady_clock::time_point::max();
660 bool ShouldBroadcast =
false;
662 nullptr !=
Info->Cache->get(Parent.Opts.TFS, ShouldBroadcast,
665 Info->State = Exists ? DirInfo::OtherCDB : DirInfo::Missing;
668 if (
Info->State != DirInfo::Missing)
669 return Info->State == DirInfo::TargetCDB;
671 if (!P.getInt() || !
Info->Parent)
674 return shouldInclude(SearchPath(
Info->Parent, 1));
680 : ThisDir(ThisDir), Parent(Parent) {}
682 std::vector<std::string>
filter(std::vector<std::string> AllFiles,
683 std::atomic<bool> &ShouldStop) {
684 std::vector<std::string> Filtered;
686 auto ExitEarly = [&] {
687 if (ShouldStop.load(std::memory_order_acquire)) {
688 log(
"Giving up on broadcasting CDB, as we're shutting down");
695 std::vector<SearchPath> SearchPaths(AllFiles.size());
696 for (
unsigned I = 0; I < AllFiles.size(); ++I) {
697 if (Parent.Opts.CompileCommandsDir) {
698 SearchPaths[I].setPointer(&Dirs[*Parent.Opts.CompileCommandsDir]);
703 WithContext WithProvidedContent(Parent.Opts.ContextProvider(AllFiles[I]));
710 SearchPaths[I].setInt(1);
711 SearchPaths[I].setPointer(addParents(AllFiles[I]));
721 for (
unsigned I = 0; I < AllFiles.size(); ++I) {
724 if (shouldInclude(SearchPaths[I]))
725 Filtered.push_back(std::move(AllFiles[I]));
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));
740void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
741 CDBLookupResult Result)
const {
742 assert(Result.CDB &&
"Trying to broadcast an invalid CDB!");
743 Broadcaster->enqueue(Result);
748 return Broadcaster->blockUntilIdle(Timeout);
751std::optional<ProjectInfo>
753 CDBLookupRequest Req;
755 Req.ShouldBroadcast =
false;
756 Req.FreshTime = Req.FreshTimeMissing =
757 std::chrono::steady_clock::time_point::min();
758 auto Res = lookupCDB(Req);
764std::unique_ptr<ProjectModules>
766 CDBLookupRequest Req;
768 Req.ShouldBroadcast =
false;
769 Req.FreshTime = Req.FreshTimeMissing =
770 std::chrono::steady_clock::time_point::min();
771 auto Res = lookupCDB(Req);
779 std::vector<std::string> FallbackFlags,
783 Mangler(std::
move(Mangler)), FallbackFlags(std::
move(FallbackFlags)) {}
785std::optional<tooling::CompileCommand>
787 std::optional<tooling::CompileCommand> Cmd;
789 std::lock_guard<std::mutex> Lock(Mutex);
791 if (It != Commands.end())
800 auto FS = llvm::vfs::getRealFileSystem();
801 auto Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
802 ? llvm::cl::TokenizeWindowsCommandLine
803 : llvm::cl::TokenizeGNUCommandLine;
807 tooling::addExpandedResponseFiles(Cmd->CommandLine, Cmd->Directory,
821 std::lock_guard<std::mutex> Lock(Mutex);
822 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
823 FallbackFlags.end());
830 std::optional<tooling::CompileCommand> Cmd) {
836 std::unique_lock<std::mutex> Lock(Mutex);
838 if (
auto [It, Inserted] =
839 Commands.try_emplace(CanonPath, std::move(*Cmd));
841 if (It->second == *Cmd)
846 Commands.erase(CanonPath);
852std::unique_ptr<ProjectModules>
856 log(
"Failed to get compilation Database for {0}",
File);
859 MDB->setCommandMangler([&Mangler = Mangler](tooling::CompileCommand &Command,
861 Mangler(Command, CommandPath);
871 BaseChanged = Base->watch([
this](
const std::vector<std::string> Changes) {
872 OnCommandChanged.broadcast(Changes);
877 std::unique_ptr<GlobalCompilationDatabase> Base,
880 BaseOwner = std::move(Base);
883std::optional<tooling::CompileCommand>
887 return Base->getCompileCommand(
File);
893 return Base->getProjectInfo(
File);
896std::unique_ptr<ProjectModules>
900 return Base->getProjectModules(
File);
906 return Base->getFallbackCommand(
File);
912 return Base->blockUntilIdle(D);
A context is an immutable container for per-request data that must be propagated through layers that ...
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< 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)
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)
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.
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.
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)
CommandChanged OnCommandChanged
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.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
@ Info
An information message.
std::string maybeCaseFoldPath(PathRef Path)
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)
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.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
std::string Path
A typedef to represent a file path.
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)
std::array< uint8_t, 8 > FileDigest
void elog(const char *Fmt, Ts &&... Vals)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
enum clang::clangd::Config::CDBSearchSpec::@161062317271326205360254020036256036377317332042 Policy
std::optional< std::string > FixedCDBPath
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.
CDBSearchSpec CDBSearch
Where to search for compilation databases for this file's flags.
enum clang::clangd::DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::LoadResult::@105304303162072107165316122121134350023026320055 Result
std::unique_ptr< llvm::MemoryBuffer > Buffer
void applyFallbackWorkingDirectory(std::optional< std::string > FallbackWorkingDirectory)
std::function< Context(llvm::StringRef)> ContextProvider
std::optional< Path > CompileCommandsDir
std::optional< std::string > FallbackWorkingDirectory