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>
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) {
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()) {
215 return {LoadResult::FileNotFound,
nullptr};
218 if (HasOldData && Stat->getLastModificationTime() == ModifiedTime &&
219 Stat->getSize() == Size)
220 return {LoadResult::FoundSameData,
nullptr};
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());
231 return {LoadResult::TransientError,
nullptr};
235 if (HasOldData && NewContentHash == ContentHash) {
237 ModifiedTime = Stat->getLastModificationTime();
238 return {LoadResult::FoundSameData,
nullptr};
241 Size = (*Buf)->getBufferSize();
242 ModifiedTime = Stat->getLastModificationTime();
243 ContentHash = NewContentHash;
244 return {LoadResult::FoundNewData, std::move(*Buf)};
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}",
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);
362std::optional<tooling::CompileCommand>
364 CDBLookupRequest Req;
366 Req.ShouldBroadcast =
true;
367 auto Now = std::chrono::steady_clock::now();
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;
440 DirectoryCache *DirCache =
nullptr;
441 for (DirectoryCache *
Candidate : getDirectoryCaches(SearchDirs)) {
442 bool CandidateShouldBroadcast = Request.ShouldBroadcast;
443 if ((CDB =
Candidate->get(Opts.
TFS, CandidateShouldBroadcast,
444 Request.FreshTime, Request.FreshTimeMissing))) {
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);
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));
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]);
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 Commands[CanonPath] = std::move(*Cmd);
821 Commands.erase(CanonPath);
829 BaseChanged = Base->watch([
this](
const std::vector<std::string>
Changes) {
836 BaseOwner = std::move(Base);
839std::optional<tooling::CompileCommand>
852std::unique_ptr<ProjectModules>
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.
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)
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.
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.
virtual std::optional< ProjectInfo > getProjectInfo(PathRef File) const
Finds the closest project to File.
virtual std::unique_ptr< ProjectModules > getProjectModules(PathRef File) const
Get the modules in 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::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.
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