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));
67 tooling::CompileCommand Cmd(llvm::sys::path::parent_path(
File),
68 llvm::sys::path::filename(
File), std::move(Argv),
70 Cmd.Heuristic =
"clangd fallback";
86 using stopwatch = std::chrono::steady_clock;
95 CachedFile(llvm::StringRef Parent, llvm::StringRef Rel) {
96 llvm::SmallString<256>
Path = Parent;
97 llvm::sys::path::append(
Path, Rel);
98 this->Path =
Path.str().str();
101 size_t Size = NoFileCached;
102 llvm::sys::TimePoint<> ModifiedTime;
105 static constexpr size_t NoFileCached = -1;
114 std::unique_ptr<llvm::MemoryBuffer>
Buffer;
117 LoadResult load(llvm::vfs::FileSystem &FS,
bool HasOldData);
122 std::atomic<stopwatch::rep> NoCDBAt = {
123 stopwatch::time_point::min().time_since_epoch().count()};
128 stopwatch::time_point CachePopulatedAt = stopwatch::time_point::min();
130 bool NeedsBroadcast =
false;
133 std::shared_ptr<tooling::CompilationDatabase> CDB;
135 CachedFile CompileCommandsJson;
136 CachedFile BuildCompileCommandsJson;
137 CachedFile CompileFlagsTxt;
144 CachedFile *ActiveCachedFile =
nullptr;
148 : CompileCommandsJson(
Path,
"compile_commands.json"),
149 BuildCompileCommandsJson(
Path,
"build/compile_commands.json"),
150 CompileFlagsTxt(
Path,
"compile_flags.txt"),
Path(
Path) {
151 assert(llvm::sys::path::is_absolute(
Path));
164 std::shared_ptr<const tooling::CompilationDatabase>
166 stopwatch::time_point FreshTime, stopwatch::time_point FreshTimeMissing) {
168 if (stopwatch::time_point(stopwatch::duration(NoCDBAt.load())) >
170 ShouldBroadcast =
false;
174 std::lock_guard<std::mutex> Lock(Mu);
175 auto RequestBroadcast = llvm::make_scope_exit([&, OldCDB(CDB.get())] {
177 if (CDB != nullptr && CDB.get() != OldCDB)
178 NeedsBroadcast = true;
179 else if (CDB == nullptr)
180 NeedsBroadcast = false;
182 if (!ShouldBroadcast)
184 ShouldBroadcast = NeedsBroadcast;
185 NeedsBroadcast = false;
189 if (CachePopulatedAt > FreshTime)
192 if (load(*TFS.
view(std::nullopt))) {
194 CachePopulatedAt = stopwatch::now();
195 NoCDBAt.store((CDB ? stopwatch::time_point::min() : CachePopulatedAt)
205 bool load(llvm::vfs::FileSystem &FS);
209DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::load(
210 llvm::vfs::FileSystem &FS,
bool HasOldData) {
211 auto Stat = FS.status(
Path);
212 if (!Stat || !Stat->isRegularFile()) {
218 if (HasOldData && Stat->getLastModificationTime() == ModifiedTime &&
219 Stat->getSize() == Size)
221 auto Buf = FS.getBufferForFile(Path);
222 if (!Buf || (*Buf)->getBufferSize() != Stat->getSize()) {
229 elog(
"Failed to read {0}: {1}", Path,
230 Buf ?
"size changed" : Buf.getError().message());
235 if (HasOldData && NewContentHash == ContentHash) {
237 ModifiedTime = Stat->getLastModificationTime();
241 Size = (*Buf)->getBufferSize();
242 ModifiedTime = Stat->getLastModificationTime();
243 ContentHash = NewContentHash;
248static std::unique_ptr<tooling::CompilationDatabase>
250 if (
auto CDB = tooling::JSONCompilationDatabase::loadFromBuffer(
251 Data,
Error, tooling::JSONCommandLineSyntax::AutoDetect)) {
259 auto FS = llvm::vfs::getRealFileSystem();
260 return tooling::inferMissingCompileCommands(
261 expandResponseFiles(std::move(CDB), std::move(FS)));
265static std::unique_ptr<tooling::CompilationDatabase>
267 return tooling::FixedCompilationDatabase::loadFromBuffer(
268 llvm::sys::path::parent_path(
Path), Data,
Error);
271bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
272 llvm::vfs::FileSystem &FS) {
273 dlog(
"Probing directory {0}",
Path);
283 std::unique_ptr<tooling::CompilationDatabase> (*Parser)(
288 for (
const auto &
Entry : {CDBFile{&CompileCommandsJson,
parseJSON},
289 CDBFile{&BuildCompileCommandsJson,
parseJSON},
291 bool Active = ActiveCachedFile == Entry.File;
292 auto Loaded = Entry.File->load(FS, Active);
293 switch (Loaded.Result) {
296 log(
"Unloaded compilation database from {0}", Entry.File->Path);
297 ActiveCachedFile =
nullptr;
306 assert(Active &&
"CachedFile may not return 'same data' if !HasOldData");
311 CDB = Entry.Parser(Entry.File->Path, Loaded.Buffer->getBuffer(),
Error);
313 log(
"{0} compilation database from {1}", Active ?
"Reloaded" :
"Loaded",
316 elog(
"Failed to load compilation database from {0}: {1}",
317 Entry.File->Path,
Error);
318 ActiveCachedFile = Entry.File;
329 if (CachePopulatedAt > stopwatch::time_point::min())
331 for (
const auto &Entry :
332 tooling::CompilationDatabasePluginRegistry::entries()) {
334 if (Entry.getName() ==
"fixed-compilation-database" ||
335 Entry.getName() ==
"json-compilation-database")
337 auto Plugin = Entry.instantiate();
338 if (
auto CDB = Plugin->loadFromDirectory(
Path,
Error)) {
339 log(
"Loaded compilation database from {0} with plugin {1}",
Path,
341 this->CDB = std::move(CDB);
346 dlog(
"No compilation database at {0}",
Path);
354 this->Opts.ContextProvider = [](llvm::StringRef) {
355 return Context::current().clone();
362std::optional<tooling::CompileCommand>
364 CDBLookupRequest Req;
366 Req.ShouldBroadcast =
true;
367 auto Now = std::chrono::steady_clock::now();
368 Req.FreshTime = Now - Opts.RevalidateAfter;
369 Req.FreshTimeMissing = Now - Opts.RevalidateMissingAfter;
371 auto Res = lookupCDB(Req);
373 log(
"Failed to find compilation database for {0}",
File);
377 auto Candidates = Res->CDB->getCompileCommands(
File);
378 if (!Candidates.empty())
379 return std::move(Candidates.front());
384std::vector<DirectoryBasedGlobalCompilationDatabase::DirectoryCache *>
385DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
386 llvm::ArrayRef<llvm::StringRef> Dirs)
const {
387 std::vector<std::string> FoldedDirs;
388 FoldedDirs.reserve(Dirs.size());
389 for (
const auto &Dir : Dirs) {
391 if (!llvm::sys::path::is_absolute(Dir))
392 elog(
"Trying to cache CDB for relative {0}");
397 std::vector<DirectoryCache *> Ret;
398 Ret.reserve(Dirs.size());
400 std::lock_guard<std::mutex> Lock(DirCachesMutex);
401 for (
unsigned I = 0; I < Dirs.size(); ++I)
402 Ret.push_back(&DirCaches.try_emplace(FoldedDirs[I], Dirs[I]).first->second);
406std::optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
407DirectoryBasedGlobalCompilationDatabase::lookupCDB(
408 CDBLookupRequest Request)
const {
409 assert(llvm::sys::path::is_absolute(Request.FileName) &&
410 "path must be absolute");
413 std::vector<llvm::StringRef> SearchDirs;
417 WithContext WithProvidedContext(Opts.ContextProvider(Request.FileName));
419 switch (Spec.Policy) {
423 Storage = *Spec.FixedCDBPath;
424 SearchDirs = {Storage};
431 actOnAllParentDirectories(Storage, [&](llvm::StringRef Dir) {
432 SearchDirs.push_back(Dir);
438 std::shared_ptr<const tooling::CompilationDatabase> CDB =
nullptr;
439 bool ShouldBroadcast =
false;
441 for (
DirectoryCache *Candidate : getDirectoryCaches(SearchDirs)) {
442 bool CandidateShouldBroadcast = Request.ShouldBroadcast;
443 if ((CDB = Candidate->get(Opts.TFS, CandidateShouldBroadcast,
444 Request.FreshTime, Request.FreshTimeMissing))) {
445 DirCache = Candidate;
446 ShouldBroadcast = CandidateShouldBroadcast;
454 CDBLookupResult Result;
455 Result.CDB = std::move(CDB);
456 Result.PI.SourceRoot = DirCache->Path;
459 broadcastCDB(Result);
475 std::condition_variable CV;
478 std::atomic<bool> ShouldStop = {
false};
480 CDBLookupResult Lookup;
483 std::deque<Task> Queue;
484 std::optional<Task> ActiveTask;
489 std::unique_lock<std::mutex> Lock(Mu);
491 bool Stopping =
false;
493 return (Stopping = ShouldStop.load(std::memory_order_acquire)) ||
501 ActiveTask = std::move(Queue.front());
507 process(ActiveTask->Lookup);
516 void process(
const CDBLookupResult &T);
520 : Parent(Parent), Thread([this] {
run(); }) {}
524 assert(!Lookup.PI.SourceRoot.empty());
525 std::lock_guard<std::mutex> Lock(Mu);
527 llvm::erase_if(Queue, [&](
const Task &T) {
528 return T.Lookup.PI.SourceRoot == Lookup.PI.SourceRoot;
536 std::unique_lock<std::mutex> Lock(Mu);
537 return wait(Lock, CV, Timeout,
538 [&] {
return Queue.empty() && !ActiveTask; });
543 std::lock_guard<std::mutex> Lock(Mu);
544 ShouldStop.store(
true, std::memory_order_release);
565 llvm::StringRef ThisDir;
572 DirInfo *Parent =
nullptr;
574 llvm::StringMap<DirInfo> Dirs;
577 using SearchPath = llvm::PointerIntPair<DirInfo *, 1>;
581 DirInfo *addParents(llvm::StringRef FilePath) {
582 DirInfo *Leaf =
nullptr;
583 DirInfo *Child =
nullptr;
584 actOnAllParentDirectories(FilePath, [&](llvm::StringRef Dir) {
585 auto &
Info = Dirs[Dir];
591 Child->Parent = &
Info;
595 return Info.Parent !=
nullptr;
606 std::vector<llvm::StringRef> DirKeys;
607 std::vector<DirInfo *> DirValues;
608 DirKeys.reserve(Dirs.size() + 1);
609 DirValues.reserve(Dirs.size());
610 for (
auto &E : Dirs) {
611 DirKeys.push_back(E.first());
612 DirValues.push_back(&E.second);
618 DirKeys.push_back(ThisDir);
619 auto DirCaches = Parent.getDirectoryCaches(DirKeys);
621 DirCaches.pop_back();
624 for (
unsigned I = 0; I < DirKeys.size(); ++I) {
625 DirValues[I]->Cache = DirCaches[I];
626 if (DirCaches[I] == ThisCache)
627 DirValues[I]->State = DirInfo::TargetCDB;
632 bool shouldInclude(SearchPath P) {
633 DirInfo *
Info = P.getPointer();
636 if (
Info->State == DirInfo::Unknown) {
637 assert(
Info->Cache &&
"grabCaches() should have filled this");
640 constexpr auto IgnoreCache = std::chrono::steady_clock::time_point::max();
642 bool ShouldBroadcast =
false;
644 nullptr !=
Info->Cache->get(Parent.Opts.TFS, ShouldBroadcast,
647 Info->State = Exists ? DirInfo::OtherCDB : DirInfo::Missing;
650 if (
Info->State != DirInfo::Missing)
651 return Info->State == DirInfo::TargetCDB;
653 if (!P.getInt() || !
Info->Parent)
656 return shouldInclude(SearchPath(
Info->Parent, 1));
662 : ThisDir(ThisDir), Parent(Parent) {}
664 std::vector<std::string>
filter(std::vector<std::string> AllFiles,
665 std::atomic<bool> &ShouldStop) {
666 std::vector<std::string> Filtered;
668 auto ExitEarly = [&] {
669 if (ShouldStop.load(std::memory_order_acquire)) {
670 log(
"Giving up on broadcasting CDB, as we're shutting down");
677 std::vector<SearchPath> SearchPaths(AllFiles.size());
678 for (
unsigned I = 0; I < AllFiles.size(); ++I) {
679 if (Parent.Opts.CompileCommandsDir) {
680 SearchPaths[I].setPointer(&Dirs[*Parent.Opts.CompileCommandsDir]);
685 WithContext WithProvidedContent(Parent.Opts.ContextProvider(AllFiles[I]));
692 SearchPaths[I].setInt(1);
693 SearchPaths[I].setPointer(addParents(AllFiles[I]));
703 for (
unsigned I = 0; I < AllFiles.size(); ++I) {
706 if (shouldInclude(SearchPaths[I]))
707 Filtered.push_back(std::move(AllFiles[I]));
713void DirectoryBasedGlobalCompilationDatabase::BroadcastThread::process(
714 const CDBLookupResult &T) {
715 vlog(
"Broadcasting compilation database from {0}", T.PI.SourceRoot);
716 std::vector<std::string> GovernedFiles =
717 Filter(T.PI.SourceRoot, Parent).filter(T.CDB->getAllFiles(), ShouldStop);
718 if (!GovernedFiles.empty())
722void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
723 CDBLookupResult Result)
const {
724 assert(Result.CDB &&
"Trying to broadcast an invalid CDB!");
725 Broadcaster->enqueue(Result);
730 return Broadcaster->blockUntilIdle(Timeout);
733std::optional<ProjectInfo>
735 CDBLookupRequest Req;
737 Req.ShouldBroadcast =
false;
738 Req.FreshTime = Req.FreshTimeMissing =
739 std::chrono::steady_clock::time_point::min();
740 auto Res = lookupCDB(Req);
746std::unique_ptr<ProjectModules>
748 CDBLookupRequest Req;
750 Req.ShouldBroadcast =
false;
751 Req.FreshTime = Req.FreshTimeMissing =
752 std::chrono::steady_clock::time_point::min();
753 auto Res = lookupCDB(Req);
761 std::vector<std::string> FallbackFlags,
764 FallbackFlags(std::
move(FallbackFlags)) {}
766std::optional<tooling::CompileCommand>
768 std::optional<tooling::CompileCommand> Cmd;
770 std::lock_guard<std::mutex> Lock(Mutex);
772 if (It != Commands.end())
781 auto FS = llvm::vfs::getRealFileSystem();
782 auto Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
783 ? llvm::cl::TokenizeWindowsCommandLine
784 : llvm::cl::TokenizeGNUCommandLine;
788 tooling::addExpandedResponseFiles(Cmd->CommandLine, Cmd->Directory,
802 std::lock_guard<std::mutex> Lock(Mutex);
803 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
804 FallbackFlags.end());
811 std::optional<tooling::CompileCommand> Cmd) {
817 std::unique_lock<std::mutex> Lock(Mutex);
819 if (
auto [It, Inserted] =
820 Commands.try_emplace(CanonPath, std::move(*Cmd));
822 if (It->second == *Cmd)
827 Commands.erase(CanonPath);
833std::unique_ptr<ProjectModules>
837 log(
"Failed to get compilation Database for {0}",
File);
840 MDB->setCommandMangler([&Mangler = Mangler](tooling::CompileCommand &Command,
842 Mangler(Command, CommandPath);
850 BaseChanged = Base->watch([
this](
const std::vector<std::string> Changes) {
851 OnCommandChanged.broadcast(Changes);
857 BaseOwner = std::move(Base);
860std::optional<tooling::CompileCommand>
864 return Base->getCompileCommand(
File);
870 return Base->getProjectInfo(
File);
873std::unique_ptr<ProjectModules>
877 return Base->getProjectModules(
File);
883 return Base->getFallbackCommand(
File);
889 return Base->blockUntilIdle(D);
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.
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.
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.
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)
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::@204212152173327223063121202366143221204051341004 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::@221333032074173032305210224173260176341176220132 CompileFlags
Controls how the compile command for the current file is determined.
enum clang::clangd::DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::LoadResult::@103312212152230257313210017223105141303370010220 Result
std::unique_ptr< llvm::MemoryBuffer > Buffer
std::function< Context(llvm::StringRef)> ContextProvider
std::optional< Path > CompileCommandsDir