18#include "clang/Tooling/ArgumentsAdjusters.h"
19#include "clang/Tooling/CompilationDatabase.h"
20#include "clang/Tooling/CompilationDatabasePluginRegistry.h"
21#include "clang/Tooling/JSONCompilationDatabase.h"
22#include "clang/Tooling/Tooling.h"
23#include "llvm/ADT/PointerIntPair.h"
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/ADT/ScopeExit.h"
26#include "llvm/ADT/SmallString.h"
27#include "llvm/ADT/StringMap.h"
28#include "llvm/Support/Path.h"
29#include "llvm/Support/VirtualFileSystem.h"
30#include "llvm/TargetParser/Host.h"
33#include <condition_variable>
47void actOnAllParentDirectories(
PathRef FileName,
48 llvm::function_ref<
bool(
PathRef)> Action) {
56tooling::CompileCommand
58 std::vector<std::string> Argv = {
"clang"};
62 auto FileExtension = llvm::sys::path::extension(
File);
63 if (FileExtension.empty() || FileExtension ==
".h")
64 Argv.push_back(
"-xobjective-c++-header");
65 Argv.push_back(std::string(
File));
68 : llvm::sys::path::parent_path(
File),
69 llvm::sys::path::filename(
File), std::move(Argv),
71 Cmd.Heuristic =
"clangd fallback";
87 using stopwatch = std::chrono::steady_clock;
96 CachedFile(llvm::StringRef Parent, llvm::StringRef Rel) {
97 llvm::SmallString<256>
Path = Parent;
98 llvm::sys::path::append(
Path, Rel);
99 this->Path =
Path.str().str();
102 size_t Size = NoFileCached;
103 llvm::sys::TimePoint<> ModifiedTime;
106 static constexpr size_t NoFileCached = -1;
115 std::unique_ptr<llvm::MemoryBuffer>
Buffer;
118 LoadResult load(llvm::vfs::FileSystem &FS,
bool HasOldData);
123 std::atomic<stopwatch::rep> NoCDBAt = {
124 stopwatch::time_point::min().time_since_epoch().count()};
129 stopwatch::time_point CachePopulatedAt = stopwatch::time_point::min();
131 bool NeedsBroadcast =
false;
134 std::shared_ptr<tooling::CompilationDatabase> CDB;
136 CachedFile CompileCommandsJson;
137 CachedFile BuildCompileCommandsJson;
138 CachedFile CompileFlagsTxt;
145 CachedFile *ActiveCachedFile =
nullptr;
149 : CompileCommandsJson(
Path,
"compile_commands.json"),
150 BuildCompileCommandsJson(
Path,
"build/compile_commands.json"),
151 CompileFlagsTxt(
Path,
"compile_flags.txt"),
Path(
Path) {
152 assert(llvm::sys::path::is_absolute(
Path));
165 std::shared_ptr<const tooling::CompilationDatabase>
167 stopwatch::time_point FreshTime, stopwatch::time_point FreshTimeMissing) {
169 if (stopwatch::time_point(stopwatch::duration(NoCDBAt.load())) >
171 ShouldBroadcast =
false;
175 std::lock_guard<std::mutex> Lock(Mu);
176 llvm::scope_exit RequestBroadcast([&, OldCDB(CDB.get())] {
178 if (CDB != nullptr && CDB.get() != OldCDB)
179 NeedsBroadcast = true;
180 else if (CDB == nullptr)
181 NeedsBroadcast = false;
183 if (!ShouldBroadcast)
185 ShouldBroadcast = NeedsBroadcast;
186 NeedsBroadcast = false;
190 if (CachePopulatedAt > FreshTime)
193 if (load(*TFS.
view(std::nullopt))) {
195 CachePopulatedAt = stopwatch::now();
196 NoCDBAt.store((CDB ? stopwatch::time_point::min() : CachePopulatedAt)
206 bool load(llvm::vfs::FileSystem &FS);
210DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::load(
211 llvm::vfs::FileSystem &FS,
bool HasOldData) {
212 auto Stat = FS.status(
Path);
213 if (!Stat || !Stat->isRegularFile()) {
219 if (HasOldData && Stat->getLastModificationTime() == ModifiedTime &&
220 Stat->getSize() == Size)
222 auto Buf = FS.getBufferForFile(Path);
223 if (!Buf || (*Buf)->getBufferSize() != Stat->getSize()) {
230 elog(
"Failed to read {0}: {1}", Path,
231 Buf ?
"size changed" : Buf.getError().message());
236 if (HasOldData && NewContentHash == ContentHash) {
238 ModifiedTime = Stat->getLastModificationTime();
242 Size = (*Buf)->getBufferSize();
243 ModifiedTime = Stat->getLastModificationTime();
244 ContentHash = NewContentHash;
249static std::unique_ptr<tooling::CompilationDatabase>
251 if (
auto CDB = tooling::JSONCompilationDatabase::loadFromBuffer(
252 Data,
Error, tooling::JSONCommandLineSyntax::AutoDetect)) {
260 auto FS = llvm::vfs::getRealFileSystem();
261 return tooling::inferMissingCompileCommands(
262 expandResponseFiles(std::move(CDB), std::move(FS)));
266static std::unique_ptr<tooling::CompilationDatabase>
268 return tooling::FixedCompilationDatabase::loadFromBuffer(
269 llvm::sys::path::parent_path(
Path), Data,
Error);
272bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
273 llvm::vfs::FileSystem &FS) {
274 dlog(
"Probing directory {0}",
Path);
284 std::unique_ptr<tooling::CompilationDatabase> (*Parser)(
289 for (
const auto &
Entry : {CDBFile{&CompileCommandsJson,
parseJSON},
290 CDBFile{&BuildCompileCommandsJson,
parseJSON},
292 bool Active = ActiveCachedFile == Entry.File;
293 auto Loaded = Entry.File->load(FS, Active);
294 switch (Loaded.Result) {
297 log(
"Unloaded compilation database from {0}", Entry.File->Path);
298 ActiveCachedFile =
nullptr;
307 assert(Active &&
"CachedFile may not return 'same data' if !HasOldData");
312 CDB = Entry.Parser(Entry.File->Path, Loaded.Buffer->getBuffer(),
Error);
314 log(
"{0} compilation database from {1}", Active ?
"Reloaded" :
"Loaded",
317 elog(
"Failed to load compilation database from {0}: {1}",
318 Entry.File->Path,
Error);
319 ActiveCachedFile = Entry.File;
330 if (CachePopulatedAt > stopwatch::time_point::min())
332 for (
const auto &Entry :
333 tooling::CompilationDatabasePluginRegistry::entries()) {
335 if (Entry.getName() ==
"fixed-compilation-database" ||
336 Entry.getName() ==
"json-compilation-database")
338 auto Plugin = Entry.instantiate();
339 if (
auto CDB = Plugin->loadFromDirectory(
Path,
Error)) {
340 log(
"Loaded compilation database from {0} with plugin {1}",
Path,
342 this->CDB = std::move(CDB);
347 dlog(
"No compilation database at {0}",
Path);
356 this->Opts.ContextProvider = [](llvm::StringRef) {
357 return Context::current().clone();
364std::optional<tooling::CompileCommand>
366 CDBLookupRequest Req;
368 Req.ShouldBroadcast =
true;
369 auto Now = std::chrono::steady_clock::now();
370 Req.FreshTime = Now - Opts.RevalidateAfter;
371 Req.FreshTimeMissing = Now - Opts.RevalidateMissingAfter;
373 auto Res = lookupCDB(Req);
375 log(
"Failed to find compilation database for {0}",
File);
379 auto Candidates = Res->CDB->getCompileCommands(
File);
380 if (!Candidates.empty())
381 return std::move(Candidates.front());
386std::vector<DirectoryBasedGlobalCompilationDatabase::DirectoryCache *>
387DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
388 llvm::ArrayRef<llvm::StringRef> Dirs)
const {
389 std::vector<std::string> FoldedDirs;
390 FoldedDirs.reserve(Dirs.size());
391 for (
const auto &Dir : Dirs) {
393 if (!llvm::sys::path::is_absolute(Dir))
394 elog(
"Trying to cache CDB for relative {0}");
399 std::vector<DirectoryCache *> Ret;
400 Ret.reserve(Dirs.size());
402 std::lock_guard<std::mutex> Lock(DirCachesMutex);
403 for (
unsigned I = 0; I < Dirs.size(); ++I)
404 Ret.push_back(&DirCaches.try_emplace(FoldedDirs[I], Dirs[I]).first->second);
408std::optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
409DirectoryBasedGlobalCompilationDatabase::lookupCDB(
410 CDBLookupRequest Request)
const {
411 assert(llvm::sys::path::is_absolute(Request.FileName) &&
412 "path must be absolute");
415 std::vector<llvm::StringRef> SearchDirs;
419 WithContext WithProvidedContext(Opts.ContextProvider(Request.FileName));
421 switch (Spec.Policy) {
425 Storage = *Spec.FixedCDBPath;
426 SearchDirs = {Storage};
433 actOnAllParentDirectories(Storage, [&](llvm::StringRef Dir) {
434 SearchDirs.push_back(Dir);
440 std::shared_ptr<const tooling::CompilationDatabase> CDB =
nullptr;
441 bool ShouldBroadcast =
false;
443 for (
DirectoryCache *Candidate : getDirectoryCaches(SearchDirs)) {
444 bool CandidateShouldBroadcast = Request.ShouldBroadcast;
445 if ((CDB = Candidate->get(Opts.TFS, CandidateShouldBroadcast,
446 Request.FreshTime, Request.FreshTimeMissing))) {
447 DirCache = Candidate;
448 ShouldBroadcast = CandidateShouldBroadcast;
456 CDBLookupResult Result;
457 Result.CDB = std::move(CDB);
458 Result.PI.SourceRoot = DirCache->Path;
461 broadcastCDB(Result);
474 SmallString<256> CWD;
475 llvm::sys::fs::current_path(CWD);
476 this->FallbackWorkingDirectory = std::string(CWD);
492 std::condition_variable CV;
495 std::atomic<bool> ShouldStop = {
false};
497 CDBLookupResult Lookup;
500 std::deque<Task> Queue;
501 std::optional<Task> ActiveTask;
506 std::unique_lock<std::mutex> Lock(Mu);
508 bool Stopping =
false;
510 return (Stopping = ShouldStop.load(std::memory_order_acquire)) ||
518 ActiveTask = std::move(Queue.front());
524 process(ActiveTask->Lookup);
533 void process(
const CDBLookupResult &T);
537 : Parent(Parent), Thread([this] {
run(); }) {}
541 assert(!Lookup.PI.SourceRoot.empty());
542 std::lock_guard<std::mutex> Lock(Mu);
544 llvm::erase_if(Queue, [&](
const Task &T) {
545 return T.Lookup.PI.SourceRoot == Lookup.PI.SourceRoot;
553 std::unique_lock<std::mutex> Lock(Mu);
554 return wait(Lock, CV, Timeout,
555 [&] {
return Queue.empty() && !ActiveTask; });
560 std::lock_guard<std::mutex> Lock(Mu);
561 ShouldStop.store(
true, std::memory_order_release);
582 llvm::StringRef ThisDir;
589 DirInfo *Parent =
nullptr;
591 llvm::StringMap<DirInfo> Dirs;
594 using SearchPath = llvm::PointerIntPair<DirInfo *, 1>;
598 DirInfo *addParents(llvm::StringRef FilePath) {
599 DirInfo *Leaf =
nullptr;
600 DirInfo *Child =
nullptr;
601 actOnAllParentDirectories(FilePath, [&](llvm::StringRef Dir) {
602 auto &
Info = Dirs[Dir];
608 Child->Parent = &
Info;
612 return Info.Parent !=
nullptr;
623 std::vector<llvm::StringRef> DirKeys;
624 std::vector<DirInfo *> DirValues;
625 DirKeys.reserve(Dirs.size() + 1);
626 DirValues.reserve(Dirs.size());
627 for (
auto &E : Dirs) {
628 DirKeys.push_back(E.first());
629 DirValues.push_back(&E.second);
635 DirKeys.push_back(ThisDir);
636 auto DirCaches = Parent.getDirectoryCaches(DirKeys);
638 DirCaches.pop_back();
641 for (
unsigned I = 0; I < DirKeys.size(); ++I) {
642 DirValues[I]->Cache = DirCaches[I];
643 if (DirCaches[I] == ThisCache)
644 DirValues[I]->State = DirInfo::TargetCDB;
649 bool shouldInclude(SearchPath P) {
650 DirInfo *
Info = P.getPointer();
653 if (
Info->State == DirInfo::Unknown) {
654 assert(
Info->Cache &&
"grabCaches() should have filled this");
657 constexpr auto IgnoreCache = std::chrono::steady_clock::time_point::max();
659 bool ShouldBroadcast =
false;
661 nullptr !=
Info->Cache->get(Parent.Opts.TFS, ShouldBroadcast,
664 Info->State = Exists ? DirInfo::OtherCDB : DirInfo::Missing;
667 if (
Info->State != DirInfo::Missing)
668 return Info->State == DirInfo::TargetCDB;
670 if (!P.getInt() || !
Info->Parent)
673 return shouldInclude(SearchPath(
Info->Parent, 1));
679 : ThisDir(ThisDir), Parent(Parent) {}
681 std::vector<std::string>
filter(std::vector<std::string> AllFiles,
682 std::atomic<bool> &ShouldStop) {
683 std::vector<std::string> Filtered;
685 auto ExitEarly = [&] {
686 if (ShouldStop.load(std::memory_order_acquire)) {
687 log(
"Giving up on broadcasting CDB, as we're shutting down");
694 std::vector<SearchPath> SearchPaths(AllFiles.size());
695 for (
unsigned I = 0; I < AllFiles.size(); ++I) {
696 if (Parent.Opts.CompileCommandsDir) {
697 SearchPaths[I].setPointer(&Dirs[*Parent.Opts.CompileCommandsDir]);
702 WithContext WithProvidedContent(Parent.Opts.ContextProvider(AllFiles[I]));
709 SearchPaths[I].setInt(1);
710 SearchPaths[I].setPointer(addParents(AllFiles[I]));
720 for (
unsigned I = 0; I < AllFiles.size(); ++I) {
723 if (shouldInclude(SearchPaths[I]))
724 Filtered.push_back(std::move(AllFiles[I]));
730void DirectoryBasedGlobalCompilationDatabase::BroadcastThread::process(
731 const CDBLookupResult &T) {
732 vlog(
"Broadcasting compilation database from {0}", T.PI.SourceRoot);
733 std::vector<std::string> GovernedFiles =
734 Filter(T.PI.SourceRoot, Parent).filter(T.CDB->getAllFiles(), ShouldStop);
735 if (!GovernedFiles.empty())
736 Parent.OnCommandChanged.broadcast(std::move(GovernedFiles));
739void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
740 CDBLookupResult Result)
const {
741 assert(Result.CDB &&
"Trying to broadcast an invalid CDB!");
742 Broadcaster->enqueue(Result);
747 return Broadcaster->blockUntilIdle(Timeout);
750std::optional<ProjectInfo>
752 CDBLookupRequest Req;
754 Req.ShouldBroadcast =
false;
755 Req.FreshTime = Req.FreshTimeMissing =
756 std::chrono::steady_clock::time_point::min();
757 auto Res = lookupCDB(Req);
763std::unique_ptr<ProjectModules>
765 CDBLookupRequest Req;
767 Req.ShouldBroadcast =
false;
768 Req.FreshTime = Req.FreshTimeMissing =
769 std::chrono::steady_clock::time_point::min();
770 auto Res = lookupCDB(Req);
778 std::vector<std::string> FallbackFlags,
782 Mangler(std::
move(Mangler)), FallbackFlags(std::
move(FallbackFlags)) {}
784std::optional<tooling::CompileCommand>
786 std::optional<tooling::CompileCommand> Cmd;
788 std::lock_guard<std::mutex> Lock(Mutex);
790 if (It != Commands.end())
799 auto FS = llvm::vfs::getRealFileSystem();
800 auto Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
801 ? llvm::cl::TokenizeWindowsCommandLine
802 : llvm::cl::TokenizeGNUCommandLine;
806 tooling::addExpandedResponseFiles(Cmd->CommandLine, Cmd->Directory,
820 std::lock_guard<std::mutex> Lock(Mutex);
821 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
822 FallbackFlags.end());
829 std::optional<tooling::CompileCommand> Cmd) {
835 std::unique_lock<std::mutex> Lock(Mutex);
837 if (
auto [It, Inserted] =
838 Commands.try_emplace(CanonPath, std::move(*Cmd));
840 if (It->second == *Cmd)
845 Commands.erase(CanonPath);
851std::unique_ptr<ProjectModules>
855 log(
"Failed to get compilation Database for {0}",
File);
858 MDB->setCommandMangler([&Mangler = Mangler](tooling::CompileCommand &Command,
860 Mangler(Command, CommandPath);
870 BaseChanged = Base->watch([
this](
const std::vector<std::string> Changes) {
871 OnCommandChanged.broadcast(Changes);
876 std::unique_ptr<GlobalCompilationDatabase> Base,
879 BaseOwner = std::move(Base);
882std::optional<tooling::CompileCommand>
886 return Base->getCompileCommand(
File);
892 return Base->getProjectInfo(
File);
895std::unique_ptr<ProjectModules>
899 return Base->getProjectModules(
File);
905 return Base->getFallbackCommand(
File);
911 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)
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)
std::unique_ptr< ProjectModules > getProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
Creates the project-modules facade used by clangd.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
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