837 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			837 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--- GlobalCompilationDatabase.cpp ---------------------------*- C++-*-===//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#include "GlobalCompilationDatabase.h"
 | 
						|
#include "Config.h"
 | 
						|
#include "FS.h"
 | 
						|
#include "SourceCode.h"
 | 
						|
#include "support/Logger.h"
 | 
						|
#include "support/Path.h"
 | 
						|
#include "support/Threading.h"
 | 
						|
#include "support/ThreadsafeFS.h"
 | 
						|
#include "clang/Frontend/CompilerInvocation.h"
 | 
						|
#include "clang/Tooling/ArgumentsAdjusters.h"
 | 
						|
#include "clang/Tooling/CompilationDatabase.h"
 | 
						|
#include "clang/Tooling/CompilationDatabasePluginRegistry.h"
 | 
						|
#include "clang/Tooling/JSONCompilationDatabase.h"
 | 
						|
#include "llvm/ADT/None.h"
 | 
						|
#include "llvm/ADT/Optional.h"
 | 
						|
#include "llvm/ADT/PointerIntPair.h"
 | 
						|
#include "llvm/ADT/STLExtras.h"
 | 
						|
#include "llvm/ADT/ScopeExit.h"
 | 
						|
#include "llvm/ADT/SmallString.h"
 | 
						|
#include "llvm/ADT/StringMap.h"
 | 
						|
#include "llvm/Support/FileSystem.h"
 | 
						|
#include "llvm/Support/FileUtilities.h"
 | 
						|
#include "llvm/Support/Path.h"
 | 
						|
#include "llvm/Support/Program.h"
 | 
						|
#include "llvm/Support/VirtualFileSystem.h"
 | 
						|
#include <atomic>
 | 
						|
#include <chrono>
 | 
						|
#include <condition_variable>
 | 
						|
#include <mutex>
 | 
						|
#include <string>
 | 
						|
#include <tuple>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace clangd {
 | 
						|
namespace {
 | 
						|
 | 
						|
// Runs the given action on all parent directories of filename, starting from
 | 
						|
// deepest directory and going up to root. Stops whenever action succeeds.
 | 
						|
void actOnAllParentDirectories(PathRef FileName,
 | 
						|
                               llvm::function_ref<bool(PathRef)> Action) {
 | 
						|
  for (auto Path = absoluteParent(FileName); !Path.empty() && !Action(Path);
 | 
						|
       Path = absoluteParent(Path))
 | 
						|
    ;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
tooling::CompileCommand
 | 
						|
GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
 | 
						|
  std::vector<std::string> Argv = {"clang"};
 | 
						|
  // Clang treats .h files as C by default and files without extension as linker
 | 
						|
  // input, resulting in unhelpful diagnostics.
 | 
						|
  // Parsing as Objective C++ is friendly to more cases.
 | 
						|
  auto FileExtension = llvm::sys::path::extension(File);
 | 
						|
  if (FileExtension.empty() || FileExtension == ".h")
 | 
						|
    Argv.push_back("-xobjective-c++-header");
 | 
						|
  Argv.push_back(std::string(File));
 | 
						|
  tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File),
 | 
						|
                              llvm::sys::path::filename(File), std::move(Argv),
 | 
						|
                              /*Output=*/"");
 | 
						|
  Cmd.Heuristic = "clangd fallback";
 | 
						|
  return Cmd;
 | 
						|
}
 | 
						|
 | 
						|
// Loads and caches the CDB from a single directory.
 | 
						|
//
 | 
						|
// This class is threadsafe, which is to say we have independent locks for each
 | 
						|
// directory we're searching for a CDB.
 | 
						|
// Loading is deferred until first access.
 | 
						|
//
 | 
						|
// The DirectoryBasedCDB keeps a map from path => DirectoryCache.
 | 
						|
// Typical usage is to:
 | 
						|
//  - 1) determine all the paths that might be searched
 | 
						|
//  - 2) acquire the map lock and get-or-create all the DirectoryCache entries
 | 
						|
//  - 3) release the map lock and query the caches as desired
 | 
						|
class DirectoryBasedGlobalCompilationDatabase::DirectoryCache {
 | 
						|
  using stopwatch = std::chrono::steady_clock;
 | 
						|
 | 
						|
  // CachedFile is used to read a CDB file on disk (e.g. compile_commands.json).
 | 
						|
  // It specializes in being able to quickly bail out if the file is unchanged,
 | 
						|
  // which is the common case.
 | 
						|
  // Internally, it stores file metadata so a stat() can verify it's unchanged.
 | 
						|
  // We don't actually cache the content as it's not needed - if the file is
 | 
						|
  // unchanged then the previous CDB is valid.
 | 
						|
  struct CachedFile {
 | 
						|
    CachedFile(llvm::StringRef Parent, llvm::StringRef Rel) {
 | 
						|
      llvm::SmallString<256> Path = Parent;
 | 
						|
      llvm::sys::path::append(Path, Rel);
 | 
						|
      this->Path = Path.str().str();
 | 
						|
    }
 | 
						|
    std::string Path;
 | 
						|
    size_t Size = NoFileCached;
 | 
						|
    llvm::sys::TimePoint<> ModifiedTime;
 | 
						|
    FileDigest ContentHash;
 | 
						|
 | 
						|
    static constexpr size_t NoFileCached = -1;
 | 
						|
 | 
						|
    struct LoadResult {
 | 
						|
      enum {
 | 
						|
        FileNotFound,
 | 
						|
        TransientError,
 | 
						|
        FoundSameData,
 | 
						|
        FoundNewData,
 | 
						|
      } Result;
 | 
						|
      std::unique_ptr<llvm::MemoryBuffer> Buffer; // Set only if FoundNewData
 | 
						|
    };
 | 
						|
 | 
						|
    LoadResult load(llvm::vfs::FileSystem &FS, bool HasOldData);
 | 
						|
  };
 | 
						|
 | 
						|
  // If we've looked for a CDB here and found none, the time when that happened.
 | 
						|
  // (Atomics make it possible for get() to return without taking a lock)
 | 
						|
  std::atomic<stopwatch::rep> NoCDBAt = {
 | 
						|
      stopwatch::time_point::min().time_since_epoch().count()};
 | 
						|
 | 
						|
  // Guards the following cache state.
 | 
						|
  std::mutex Mu;
 | 
						|
  // When was the cache last known to be in sync with disk state?
 | 
						|
  stopwatch::time_point CachePopulatedAt = stopwatch::time_point::min();
 | 
						|
  // Whether a new CDB has been loaded but not broadcast yet.
 | 
						|
  bool NeedsBroadcast = false;
 | 
						|
  // Last loaded CDB, meaningful if CachePopulatedAt was ever set.
 | 
						|
  // shared_ptr so we can overwrite this when callers are still using the CDB.
 | 
						|
  std::shared_ptr<tooling::CompilationDatabase> CDB;
 | 
						|
  // File metadata for the CDB files we support tracking directly.
 | 
						|
  CachedFile CompileCommandsJson;
 | 
						|
  CachedFile BuildCompileCommandsJson;
 | 
						|
  CachedFile CompileFlagsTxt;
 | 
						|
  // CachedFile member corresponding to CDB.
 | 
						|
  //   CDB  | ACF  | Scenario
 | 
						|
  //   null | null | no CDB found, or initial empty cache
 | 
						|
  //   set  | null | CDB was loaded via generic plugin interface
 | 
						|
  //   null | set  | found known CDB file, but parsing it failed
 | 
						|
  //   set  | set  | CDB was parsed from a known file
 | 
						|
  CachedFile *ActiveCachedFile = nullptr;
 | 
						|
 | 
						|
public:
 | 
						|
  DirectoryCache(llvm::StringRef Path)
 | 
						|
      : CompileCommandsJson(Path, "compile_commands.json"),
 | 
						|
        BuildCompileCommandsJson(Path, "build/compile_commands.json"),
 | 
						|
        CompileFlagsTxt(Path, "compile_flags.txt"), Path(Path) {
 | 
						|
    assert(llvm::sys::path::is_absolute(Path));
 | 
						|
  }
 | 
						|
 | 
						|
  // Absolute canonical path that we're the cache for. (Not case-folded).
 | 
						|
  const std::string Path;
 | 
						|
 | 
						|
  // Get the CDB associated with this directory.
 | 
						|
  // ShouldBroadcast:
 | 
						|
  //  - as input, signals whether the caller is willing to broadcast a
 | 
						|
  //    newly-discovered CDB. (e.g. to trigger background indexing)
 | 
						|
  //  - as output, signals whether the caller should do so.
 | 
						|
  // (If a new CDB is discovered and ShouldBroadcast is false, we mark the
 | 
						|
  // CDB as needing broadcast, and broadcast it next time we can).
 | 
						|
  std::shared_ptr<const tooling::CompilationDatabase>
 | 
						|
  get(const ThreadsafeFS &TFS, bool &ShouldBroadcast,
 | 
						|
      stopwatch::time_point FreshTime, stopwatch::time_point FreshTimeMissing) {
 | 
						|
    // Fast path for common case without taking lock.
 | 
						|
    if (stopwatch::time_point(stopwatch::duration(NoCDBAt.load())) >
 | 
						|
        FreshTimeMissing) {
 | 
						|
      ShouldBroadcast = false;
 | 
						|
      return nullptr;
 | 
						|
    }
 | 
						|
 | 
						|
    std::lock_guard<std::mutex> Lock(Mu);
 | 
						|
    auto RequestBroadcast = llvm::make_scope_exit([&, OldCDB(CDB.get())] {
 | 
						|
      // If we loaded a new CDB, it should be broadcast at some point.
 | 
						|
      if (CDB != nullptr && CDB.get() != OldCDB)
 | 
						|
        NeedsBroadcast = true;
 | 
						|
      else if (CDB == nullptr) // nothing to broadcast anymore!
 | 
						|
        NeedsBroadcast = false;
 | 
						|
      // If we have something to broadcast, then do so iff allowed.
 | 
						|
      if (!ShouldBroadcast)
 | 
						|
        return;
 | 
						|
      ShouldBroadcast = NeedsBroadcast;
 | 
						|
      NeedsBroadcast = false;
 | 
						|
    });
 | 
						|
 | 
						|
    // If our cache is valid, serve from it.
 | 
						|
    if (CachePopulatedAt > FreshTime)
 | 
						|
      return CDB;
 | 
						|
 | 
						|
    if (/*MayCache=*/load(*TFS.view(/*CWD=*/llvm::None))) {
 | 
						|
      // Use new timestamp, as loading may be slow.
 | 
						|
      CachePopulatedAt = stopwatch::now();
 | 
						|
      NoCDBAt.store((CDB ? stopwatch::time_point::min() : CachePopulatedAt)
 | 
						|
                        .time_since_epoch()
 | 
						|
                        .count());
 | 
						|
    }
 | 
						|
 | 
						|
    return CDB;
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  // Updates `CDB` from disk state. Returns false on failure.
 | 
						|
  bool load(llvm::vfs::FileSystem &FS);
 | 
						|
};
 | 
						|
 | 
						|
DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::LoadResult
 | 
						|
DirectoryBasedGlobalCompilationDatabase::DirectoryCache::CachedFile::load(
 | 
						|
    llvm::vfs::FileSystem &FS, bool HasOldData) {
 | 
						|
  auto Stat = FS.status(Path);
 | 
						|
  if (!Stat || !Stat->isRegularFile()) {
 | 
						|
    Size = NoFileCached;
 | 
						|
    ContentHash = {};
 | 
						|
    return {LoadResult::FileNotFound, nullptr};
 | 
						|
  }
 | 
						|
  // If both the size and mtime match, presume unchanged without reading.
 | 
						|
  if (HasOldData && Stat->getLastModificationTime() == ModifiedTime &&
 | 
						|
      Stat->getSize() == Size)
 | 
						|
    return {LoadResult::FoundSameData, nullptr};
 | 
						|
  auto Buf = FS.getBufferForFile(Path);
 | 
						|
  if (!Buf || (*Buf)->getBufferSize() != Stat->getSize()) {
 | 
						|
    // Don't clear the cache - possible we're seeing inconsistent size as the
 | 
						|
    // file is being recreated. If it ends up identical later, great!
 | 
						|
    //
 | 
						|
    // This isn't a complete solution: if we see a partial file but stat/read
 | 
						|
    // agree on its size, we're ultimately going to have spurious CDB reloads.
 | 
						|
    // May be worth fixing if generators don't write atomically (CMake does).
 | 
						|
    elog("Failed to read {0}: {1}", Path,
 | 
						|
         Buf ? "size changed" : Buf.getError().message());
 | 
						|
    return {LoadResult::TransientError, nullptr};
 | 
						|
  }
 | 
						|
 | 
						|
  FileDigest NewContentHash = digest((*Buf)->getBuffer());
 | 
						|
  if (HasOldData && NewContentHash == ContentHash) {
 | 
						|
    // mtime changed but data is the same: avoid rebuilding the CDB.
 | 
						|
    ModifiedTime = Stat->getLastModificationTime();
 | 
						|
    return {LoadResult::FoundSameData, nullptr};
 | 
						|
  }
 | 
						|
 | 
						|
  Size = (*Buf)->getBufferSize();
 | 
						|
  ModifiedTime = Stat->getLastModificationTime();
 | 
						|
  ContentHash = NewContentHash;
 | 
						|
  return {LoadResult::FoundNewData, std::move(*Buf)};
 | 
						|
}
 | 
						|
 | 
						|
// Adapt CDB-loading functions to a common interface for DirectoryCache::load().
 | 
						|
static std::unique_ptr<tooling::CompilationDatabase>
 | 
						|
parseJSON(PathRef Path, llvm::StringRef Data, std::string &Error) {
 | 
						|
  if (auto CDB = tooling::JSONCompilationDatabase::loadFromBuffer(
 | 
						|
          Data, Error, tooling::JSONCommandLineSyntax::AutoDetect)) {
 | 
						|
    // FS used for expanding response files.
 | 
						|
    // FIXME: ExpandResponseFilesDatabase appears not to provide the usual
 | 
						|
    // thread-safety guarantees, as the access to FS is not locked!
 | 
						|
    // For now, use the real FS, which is known to be threadsafe (if we don't
 | 
						|
    // use/change working directory, which ExpandResponseFilesDatabase doesn't).
 | 
						|
    auto FS = llvm::vfs::getRealFileSystem();
 | 
						|
    return tooling::inferTargetAndDriverMode(
 | 
						|
        tooling::inferMissingCompileCommands(
 | 
						|
            expandResponseFiles(std::move(CDB), std::move(FS))));
 | 
						|
  }
 | 
						|
  return nullptr;
 | 
						|
}
 | 
						|
static std::unique_ptr<tooling::CompilationDatabase>
 | 
						|
parseFixed(PathRef Path, llvm::StringRef Data, std::string &Error) {
 | 
						|
  return tooling::FixedCompilationDatabase::loadFromBuffer(
 | 
						|
      llvm::sys::path::parent_path(Path), Data, Error);
 | 
						|
}
 | 
						|
 | 
						|
bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
 | 
						|
    llvm::vfs::FileSystem &FS) {
 | 
						|
  dlog("Probing directory {0}", Path);
 | 
						|
  std::string Error;
 | 
						|
 | 
						|
  // Load from the specially-supported compilation databases (JSON + Fixed).
 | 
						|
  // For these, we know the files they read and cache their metadata so we can
 | 
						|
  // cheaply validate whether they've changed, and hot-reload if they have.
 | 
						|
  // (As a bonus, these are also VFS-clean)!
 | 
						|
  struct CDBFile {
 | 
						|
    CachedFile *File;
 | 
						|
    // Wrapper for {Fixed,JSON}CompilationDatabase::loadFromBuffer.
 | 
						|
    llvm::function_ref<std::unique_ptr<tooling::CompilationDatabase>(
 | 
						|
        PathRef,
 | 
						|
        /*Data*/ llvm::StringRef,
 | 
						|
        /*ErrorMsg*/ std::string &)>
 | 
						|
        Parser;
 | 
						|
  };
 | 
						|
  for (const auto &Entry : {CDBFile{&CompileCommandsJson, parseJSON},
 | 
						|
                            CDBFile{&BuildCompileCommandsJson, parseJSON},
 | 
						|
                            CDBFile{&CompileFlagsTxt, parseFixed}}) {
 | 
						|
    bool Active = ActiveCachedFile == Entry.File;
 | 
						|
    auto Loaded = Entry.File->load(FS, Active);
 | 
						|
    switch (Loaded.Result) {
 | 
						|
    case CachedFile::LoadResult::FileNotFound:
 | 
						|
      if (Active) {
 | 
						|
        log("Unloaded compilation database from {0}", Entry.File->Path);
 | 
						|
        ActiveCachedFile = nullptr;
 | 
						|
        CDB = nullptr;
 | 
						|
      }
 | 
						|
      // Continue looking at other candidates.
 | 
						|
      break;
 | 
						|
    case CachedFile::LoadResult::TransientError:
 | 
						|
      // File existed but we couldn't read it. Reuse the cache, retry later.
 | 
						|
      return false; // Load again next time.
 | 
						|
    case CachedFile::LoadResult::FoundSameData:
 | 
						|
      assert(Active && "CachedFile may not return 'same data' if !HasOldData");
 | 
						|
      // This is the critical file, and it hasn't changed.
 | 
						|
      return true;
 | 
						|
    case CachedFile::LoadResult::FoundNewData:
 | 
						|
      // We have a new CDB!
 | 
						|
      CDB = Entry.Parser(Entry.File->Path, Loaded.Buffer->getBuffer(), Error);
 | 
						|
      if (CDB)
 | 
						|
        log("{0} compilation database from {1}", Active ? "Reloaded" : "Loaded",
 | 
						|
            Entry.File->Path);
 | 
						|
      else
 | 
						|
        elog("Failed to load compilation database from {0}: {1}",
 | 
						|
             Entry.File->Path, Error);
 | 
						|
      ActiveCachedFile = Entry.File;
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Fall back to generic handling of compilation databases.
 | 
						|
  // We don't know what files they read, so can't efficiently check whether
 | 
						|
  // they need to be reloaded. So we never do that.
 | 
						|
  // FIXME: the interface doesn't provide a way to virtualize FS access.
 | 
						|
 | 
						|
  // Don't try these more than once. If we've scanned before, we're done.
 | 
						|
  if (CachePopulatedAt > stopwatch::time_point::min())
 | 
						|
    return true;
 | 
						|
  for (const auto &Entry :
 | 
						|
       tooling::CompilationDatabasePluginRegistry::entries()) {
 | 
						|
    // Avoid duplicating the special cases handled above.
 | 
						|
    if (Entry.getName() == "fixed-compilation-database" ||
 | 
						|
        Entry.getName() == "json-compilation-database")
 | 
						|
      continue;
 | 
						|
    auto Plugin = Entry.instantiate();
 | 
						|
    if (auto CDB = Plugin->loadFromDirectory(Path, Error)) {
 | 
						|
      log("Loaded compilation database from {0} with plugin {1}", Path,
 | 
						|
          Entry.getName());
 | 
						|
      this->CDB = std::move(CDB);
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    // Don't log Error here, it's usually just "couldn't find <file>".
 | 
						|
  }
 | 
						|
  dlog("No compilation database at {0}", Path);
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
DirectoryBasedGlobalCompilationDatabase::
 | 
						|
    DirectoryBasedGlobalCompilationDatabase(const Options &Opts)
 | 
						|
    : Opts(Opts), Broadcaster(std::make_unique<BroadcastThread>(*this)) {
 | 
						|
  if (!this->Opts.ContextProvider)
 | 
						|
    this->Opts.ContextProvider = [](llvm::StringRef) {
 | 
						|
      return Context::current().clone();
 | 
						|
    };
 | 
						|
}
 | 
						|
 | 
						|
DirectoryBasedGlobalCompilationDatabase::
 | 
						|
    ~DirectoryBasedGlobalCompilationDatabase() = default;
 | 
						|
 | 
						|
llvm::Optional<tooling::CompileCommand>
 | 
						|
DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const {
 | 
						|
  CDBLookupRequest Req;
 | 
						|
  Req.FileName = File;
 | 
						|
  Req.ShouldBroadcast = true;
 | 
						|
  auto Now = std::chrono::steady_clock::now();
 | 
						|
  Req.FreshTime = Now - Opts.RevalidateAfter;
 | 
						|
  Req.FreshTimeMissing = Now - Opts.RevalidateMissingAfter;
 | 
						|
 | 
						|
  auto Res = lookupCDB(Req);
 | 
						|
  if (!Res) {
 | 
						|
    log("Failed to find compilation database for {0}", File);
 | 
						|
    return llvm::None;
 | 
						|
  }
 | 
						|
 | 
						|
  auto Candidates = Res->CDB->getCompileCommands(File);
 | 
						|
  if (!Candidates.empty())
 | 
						|
    return std::move(Candidates.front());
 | 
						|
 | 
						|
  return None;
 | 
						|
}
 | 
						|
 | 
						|
std::vector<DirectoryBasedGlobalCompilationDatabase::DirectoryCache *>
 | 
						|
DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
 | 
						|
    llvm::ArrayRef<llvm::StringRef> Dirs) const {
 | 
						|
  std::vector<std::string> FoldedDirs;
 | 
						|
  FoldedDirs.reserve(Dirs.size());
 | 
						|
  for (const auto &Dir : Dirs) {
 | 
						|
#ifndef NDEBUG
 | 
						|
    if (!llvm::sys::path::is_absolute(Dir))
 | 
						|
      elog("Trying to cache CDB for relative {0}");
 | 
						|
#endif
 | 
						|
    FoldedDirs.push_back(maybeCaseFoldPath(Dir));
 | 
						|
  }
 | 
						|
 | 
						|
  std::vector<DirectoryCache *> Ret;
 | 
						|
  Ret.reserve(Dirs.size());
 | 
						|
 | 
						|
  std::lock_guard<std::mutex> Lock(DirCachesMutex);
 | 
						|
  for (unsigned I = 0; I < Dirs.size(); ++I)
 | 
						|
    Ret.push_back(&DirCaches.try_emplace(FoldedDirs[I], Dirs[I]).first->second);
 | 
						|
  return Ret;
 | 
						|
}
 | 
						|
 | 
						|
llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
 | 
						|
DirectoryBasedGlobalCompilationDatabase::lookupCDB(
 | 
						|
    CDBLookupRequest Request) const {
 | 
						|
  assert(llvm::sys::path::is_absolute(Request.FileName) &&
 | 
						|
         "path must be absolute");
 | 
						|
 | 
						|
  std::string Storage;
 | 
						|
  std::vector<llvm::StringRef> SearchDirs;
 | 
						|
  if (Opts.CompileCommandsDir) // FIXME: unify this case with config.
 | 
						|
    SearchDirs = {Opts.CompileCommandsDir.getValue()};
 | 
						|
  else {
 | 
						|
    WithContext WithProvidedContext(Opts.ContextProvider(Request.FileName));
 | 
						|
    const auto &Spec = Config::current().CompileFlags.CDBSearch;
 | 
						|
    switch (Spec.Policy) {
 | 
						|
    case Config::CDBSearchSpec::NoCDBSearch:
 | 
						|
      return llvm::None;
 | 
						|
    case Config::CDBSearchSpec::FixedDir:
 | 
						|
      Storage = Spec.FixedCDBPath.getValue();
 | 
						|
      SearchDirs = {Storage};
 | 
						|
      break;
 | 
						|
    case Config::CDBSearchSpec::Ancestors:
 | 
						|
      // Traverse the canonical version to prevent false positives. i.e.:
 | 
						|
      // src/build/../a.cc can detect a CDB in /src/build if not
 | 
						|
      // canonicalized.
 | 
						|
      Storage = removeDots(Request.FileName);
 | 
						|
      actOnAllParentDirectories(Storage, [&](llvm::StringRef Dir) {
 | 
						|
        SearchDirs.push_back(Dir);
 | 
						|
        return false;
 | 
						|
      });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  std::shared_ptr<const tooling::CompilationDatabase> CDB = nullptr;
 | 
						|
  bool ShouldBroadcast = false;
 | 
						|
  DirectoryCache *DirCache = nullptr;
 | 
						|
  for (DirectoryCache *Candidate : getDirectoryCaches(SearchDirs)) {
 | 
						|
    bool CandidateShouldBroadcast = Request.ShouldBroadcast;
 | 
						|
    if ((CDB = Candidate->get(Opts.TFS, CandidateShouldBroadcast,
 | 
						|
                              Request.FreshTime, Request.FreshTimeMissing))) {
 | 
						|
      DirCache = Candidate;
 | 
						|
      ShouldBroadcast = CandidateShouldBroadcast;
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (!CDB)
 | 
						|
    return llvm::None;
 | 
						|
 | 
						|
  CDBLookupResult Result;
 | 
						|
  Result.CDB = std::move(CDB);
 | 
						|
  Result.PI.SourceRoot = DirCache->Path;
 | 
						|
 | 
						|
  if (ShouldBroadcast)
 | 
						|
    broadcastCDB(Result);
 | 
						|
  return Result;
 | 
						|
}
 | 
						|
 | 
						|
// The broadcast thread announces files with new compile commands to the world.
 | 
						|
// Primarily this is used to enqueue them for background indexing.
 | 
						|
//
 | 
						|
// It's on a separate thread because:
 | 
						|
//  - otherwise it would block the first parse of the initial file
 | 
						|
//  - we need to enumerate all files in the CDB, of which there are many
 | 
						|
//  - we (will) have to evaluate config for every file in the CDB, which is slow
 | 
						|
class DirectoryBasedGlobalCompilationDatabase::BroadcastThread {
 | 
						|
  class Filter;
 | 
						|
  DirectoryBasedGlobalCompilationDatabase &Parent;
 | 
						|
 | 
						|
  std::mutex Mu;
 | 
						|
  std::condition_variable CV;
 | 
						|
  // Shutdown flag (CV is notified after writing).
 | 
						|
  // This is atomic so that broadcasts can also observe it and abort early.
 | 
						|
  std::atomic<bool> ShouldStop = {false};
 | 
						|
  struct Task {
 | 
						|
    CDBLookupResult Lookup;
 | 
						|
    Context Ctx;
 | 
						|
  };
 | 
						|
  std::deque<Task> Queue;
 | 
						|
  llvm::Optional<Task> ActiveTask;
 | 
						|
  std::thread Thread; // Must be last member.
 | 
						|
 | 
						|
  // Thread body: this is just the basic queue procesing boilerplate.
 | 
						|
  void run() {
 | 
						|
    std::unique_lock<std::mutex> Lock(Mu);
 | 
						|
    while (true) {
 | 
						|
      bool Stopping = false;
 | 
						|
      CV.wait(Lock, [&] {
 | 
						|
        return (Stopping = ShouldStop.load(std::memory_order_acquire)) ||
 | 
						|
               !Queue.empty();
 | 
						|
      });
 | 
						|
      if (Stopping) {
 | 
						|
        Queue.clear();
 | 
						|
        CV.notify_all();
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      ActiveTask = std::move(Queue.front());
 | 
						|
      Queue.pop_front();
 | 
						|
 | 
						|
      Lock.unlock();
 | 
						|
      {
 | 
						|
        WithContext WithCtx(std::move(ActiveTask->Ctx));
 | 
						|
        process(ActiveTask->Lookup);
 | 
						|
      }
 | 
						|
      Lock.lock();
 | 
						|
      ActiveTask.reset();
 | 
						|
      CV.notify_all();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Inspects a new CDB and broadcasts the files it owns.
 | 
						|
  void process(const CDBLookupResult &T);
 | 
						|
 | 
						|
public:
 | 
						|
  BroadcastThread(DirectoryBasedGlobalCompilationDatabase &Parent)
 | 
						|
      : Parent(Parent), Thread([this] { run(); }) {}
 | 
						|
 | 
						|
  void enqueue(CDBLookupResult Lookup) {
 | 
						|
    {
 | 
						|
      assert(!Lookup.PI.SourceRoot.empty());
 | 
						|
      std::lock_guard<std::mutex> Lock(Mu);
 | 
						|
      // New CDB takes precedence over any queued one for the same directory.
 | 
						|
      llvm::erase_if(Queue, [&](const Task &T) {
 | 
						|
        return T.Lookup.PI.SourceRoot == Lookup.PI.SourceRoot;
 | 
						|
      });
 | 
						|
      Queue.push_back({std::move(Lookup), Context::current().clone()});
 | 
						|
    }
 | 
						|
    CV.notify_all();
 | 
						|
  }
 | 
						|
 | 
						|
  bool blockUntilIdle(Deadline Timeout) {
 | 
						|
    std::unique_lock<std::mutex> Lock(Mu);
 | 
						|
    return wait(Lock, CV, Timeout,
 | 
						|
                [&] { return Queue.empty() && !ActiveTask.hasValue(); });
 | 
						|
  }
 | 
						|
 | 
						|
  ~BroadcastThread() {
 | 
						|
    {
 | 
						|
      std::lock_guard<std::mutex> Lock(Mu);
 | 
						|
      ShouldStop.store(true, std::memory_order_release);
 | 
						|
    }
 | 
						|
    CV.notify_all();
 | 
						|
    Thread.join();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
// The DirBasedCDB associates each file with a specific CDB.
 | 
						|
// When a CDB is discovered, it may claim to describe files that we associate
 | 
						|
// with a different CDB. We do not want to broadcast discovery of these, and
 | 
						|
// trigger background indexing of them.
 | 
						|
//
 | 
						|
// We must filter the list, and check whether they are associated with this CDB.
 | 
						|
// This class attempts to do so efficiently.
 | 
						|
//
 | 
						|
// Roughly, it:
 | 
						|
//  - loads the config for each file, and determines the relevant search path
 | 
						|
//  - gathers all directories that are part of any search path
 | 
						|
//  - (lazily) checks for a CDB in each such directory at most once
 | 
						|
//  - walks the search path for each file and determines whether to include it.
 | 
						|
class DirectoryBasedGlobalCompilationDatabase::BroadcastThread::Filter {
 | 
						|
  llvm::StringRef ThisDir;
 | 
						|
  DirectoryBasedGlobalCompilationDatabase &Parent;
 | 
						|
 | 
						|
  // Keep track of all directories we might check for CDBs.
 | 
						|
  struct DirInfo {
 | 
						|
    DirectoryCache *Cache = nullptr;
 | 
						|
    enum { Unknown, Missing, TargetCDB, OtherCDB } State = Unknown;
 | 
						|
    DirInfo *Parent = nullptr;
 | 
						|
  };
 | 
						|
  llvm::StringMap<DirInfo> Dirs;
 | 
						|
 | 
						|
  // A search path starts at a directory, and either includes ancestors or not.
 | 
						|
  using SearchPath = llvm::PointerIntPair<DirInfo *, 1>;
 | 
						|
 | 
						|
  // Add all ancestor directories of FilePath to the tracked set.
 | 
						|
  // Returns the immediate parent of the file.
 | 
						|
  DirInfo *addParents(llvm::StringRef FilePath) {
 | 
						|
    DirInfo *Leaf = nullptr;
 | 
						|
    DirInfo *Child = nullptr;
 | 
						|
    actOnAllParentDirectories(FilePath, [&](llvm::StringRef Dir) {
 | 
						|
      auto &Info = Dirs[Dir];
 | 
						|
      // If this is the first iteration, then this node is the overall result.
 | 
						|
      if (!Leaf)
 | 
						|
        Leaf = &Info;
 | 
						|
      // Fill in the parent link from the previous iteration to this parent.
 | 
						|
      if (Child)
 | 
						|
        Child->Parent = &Info;
 | 
						|
      // Keep walking, whether we inserted or not, if parent link is missing.
 | 
						|
      // (If it's present, parent links must be present up to the root, so stop)
 | 
						|
      Child = &Info;
 | 
						|
      return Info.Parent != nullptr;
 | 
						|
    });
 | 
						|
    return Leaf;
 | 
						|
  }
 | 
						|
 | 
						|
  // Populates DirInfo::Cache (and State, if it is TargetCDB).
 | 
						|
  void grabCaches() {
 | 
						|
    // Fast path out if there were no files, or CDB loading is off.
 | 
						|
    if (Dirs.empty())
 | 
						|
      return;
 | 
						|
 | 
						|
    std::vector<llvm::StringRef> DirKeys;
 | 
						|
    std::vector<DirInfo *> DirValues;
 | 
						|
    DirKeys.reserve(Dirs.size() + 1);
 | 
						|
    DirValues.reserve(Dirs.size());
 | 
						|
    for (auto &E : Dirs) {
 | 
						|
      DirKeys.push_back(E.first());
 | 
						|
      DirValues.push_back(&E.second);
 | 
						|
    }
 | 
						|
 | 
						|
    // Also look up the cache entry for the CDB we're broadcasting.
 | 
						|
    // Comparing DirectoryCache pointers is more robust than checking string
 | 
						|
    // equality, e.g. reuses the case-sensitivity handling.
 | 
						|
    DirKeys.push_back(ThisDir);
 | 
						|
    auto DirCaches = Parent.getDirectoryCaches(DirKeys);
 | 
						|
    const DirectoryCache *ThisCache = DirCaches.back();
 | 
						|
    DirCaches.pop_back();
 | 
						|
    DirKeys.pop_back();
 | 
						|
 | 
						|
    for (unsigned I = 0; I < DirKeys.size(); ++I) {
 | 
						|
      DirValues[I]->Cache = DirCaches[I];
 | 
						|
      if (DirCaches[I] == ThisCache)
 | 
						|
        DirValues[I]->State = DirInfo::TargetCDB;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Should we include a file from this search path?
 | 
						|
  bool shouldInclude(SearchPath P) {
 | 
						|
    DirInfo *Info = P.getPointer();
 | 
						|
    if (!Info)
 | 
						|
      return false;
 | 
						|
    if (Info->State == DirInfo::Unknown) {
 | 
						|
      assert(Info->Cache && "grabCaches() should have filled this");
 | 
						|
      // Given that we know that CDBs have been moved/generated, don't trust
 | 
						|
      // caches. (This should be rare, so it's OK to add a little latency).
 | 
						|
      constexpr auto IgnoreCache = std::chrono::steady_clock::time_point::max();
 | 
						|
      // Don't broadcast CDBs discovered while broadcasting!
 | 
						|
      bool ShouldBroadcast = false;
 | 
						|
      bool Exists =
 | 
						|
          nullptr != Info->Cache->get(Parent.Opts.TFS, ShouldBroadcast,
 | 
						|
                                      /*FreshTime=*/IgnoreCache,
 | 
						|
                                      /*FreshTimeMissing=*/IgnoreCache);
 | 
						|
      Info->State = Exists ? DirInfo::OtherCDB : DirInfo::Missing;
 | 
						|
    }
 | 
						|
    // If we have a CDB, include the file if it's the target CDB only.
 | 
						|
    if (Info->State != DirInfo::Missing)
 | 
						|
      return Info->State == DirInfo::TargetCDB;
 | 
						|
    // If we have no CDB and no relevant parent, don't include the file.
 | 
						|
    if (!P.getInt() || !Info->Parent)
 | 
						|
      return false;
 | 
						|
    // Walk up to the next parent.
 | 
						|
    return shouldInclude(SearchPath(Info->Parent, 1));
 | 
						|
  }
 | 
						|
 | 
						|
public:
 | 
						|
  Filter(llvm::StringRef ThisDir,
 | 
						|
         DirectoryBasedGlobalCompilationDatabase &Parent)
 | 
						|
      : ThisDir(ThisDir), Parent(Parent) {}
 | 
						|
 | 
						|
  std::vector<std::string> filter(std::vector<std::string> AllFiles,
 | 
						|
                                  std::atomic<bool> &ShouldStop) {
 | 
						|
    std::vector<std::string> Filtered;
 | 
						|
    // Allow for clean early-exit of the slow parts.
 | 
						|
    auto ExitEarly = [&] {
 | 
						|
      if (ShouldStop.load(std::memory_order_acquire)) {
 | 
						|
        log("Giving up on broadcasting CDB, as we're shutting down");
 | 
						|
        Filtered.clear();
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
      return false;
 | 
						|
    };
 | 
						|
    // Compute search path for each file.
 | 
						|
    std::vector<SearchPath> SearchPaths(AllFiles.size());
 | 
						|
    for (unsigned I = 0; I < AllFiles.size(); ++I) {
 | 
						|
      if (Parent.Opts.CompileCommandsDir) { // FIXME: unify with config
 | 
						|
        SearchPaths[I].setPointer(
 | 
						|
            &Dirs[Parent.Opts.CompileCommandsDir.getValue()]);
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      if (ExitEarly()) // loading config may be slow
 | 
						|
        return Filtered;
 | 
						|
      WithContext WithProvidedContent(Parent.Opts.ContextProvider(AllFiles[I]));
 | 
						|
      const Config::CDBSearchSpec &Spec =
 | 
						|
          Config::current().CompileFlags.CDBSearch;
 | 
						|
      switch (Spec.Policy) {
 | 
						|
      case Config::CDBSearchSpec::NoCDBSearch:
 | 
						|
        break;
 | 
						|
      case Config::CDBSearchSpec::Ancestors:
 | 
						|
        SearchPaths[I].setInt(/*Recursive=*/1);
 | 
						|
        SearchPaths[I].setPointer(addParents(AllFiles[I]));
 | 
						|
        break;
 | 
						|
      case Config::CDBSearchSpec::FixedDir:
 | 
						|
        SearchPaths[I].setPointer(&Dirs[Spec.FixedCDBPath.getValue()]);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    // Get the CDB cache for each dir on the search path, but don't load yet.
 | 
						|
    grabCaches();
 | 
						|
    // Now work out which files we want to keep, loading CDBs where needed.
 | 
						|
    for (unsigned I = 0; I < AllFiles.size(); ++I) {
 | 
						|
      if (ExitEarly()) // loading CDBs may be slow
 | 
						|
        return Filtered;
 | 
						|
      if (shouldInclude(SearchPaths[I]))
 | 
						|
        Filtered.push_back(std::move(AllFiles[I]));
 | 
						|
    }
 | 
						|
    return Filtered;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
void DirectoryBasedGlobalCompilationDatabase::BroadcastThread::process(
 | 
						|
    const CDBLookupResult &T) {
 | 
						|
  vlog("Broadcasting compilation database from {0}", T.PI.SourceRoot);
 | 
						|
  std::vector<std::string> GovernedFiles =
 | 
						|
      Filter(T.PI.SourceRoot, Parent).filter(T.CDB->getAllFiles(), ShouldStop);
 | 
						|
  if (!GovernedFiles.empty())
 | 
						|
    Parent.OnCommandChanged.broadcast(std::move(GovernedFiles));
 | 
						|
}
 | 
						|
 | 
						|
void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
 | 
						|
    CDBLookupResult Result) const {
 | 
						|
  assert(Result.CDB && "Trying to broadcast an invalid CDB!");
 | 
						|
  Broadcaster->enqueue(Result);
 | 
						|
}
 | 
						|
 | 
						|
bool DirectoryBasedGlobalCompilationDatabase::blockUntilIdle(
 | 
						|
    Deadline Timeout) const {
 | 
						|
  return Broadcaster->blockUntilIdle(Timeout);
 | 
						|
}
 | 
						|
 | 
						|
llvm::Optional<ProjectInfo>
 | 
						|
DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const {
 | 
						|
  CDBLookupRequest Req;
 | 
						|
  Req.FileName = File;
 | 
						|
  Req.ShouldBroadcast = false;
 | 
						|
  Req.FreshTime = Req.FreshTimeMissing =
 | 
						|
      std::chrono::steady_clock::time_point::min();
 | 
						|
  auto Res = lookupCDB(Req);
 | 
						|
  if (!Res)
 | 
						|
    return llvm::None;
 | 
						|
  return Res->PI;
 | 
						|
}
 | 
						|
 | 
						|
OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
 | 
						|
                       std::vector<std::string> FallbackFlags,
 | 
						|
                       tooling::ArgumentsAdjuster Adjuster)
 | 
						|
    : DelegatingCDB(Base), ArgsAdjuster(std::move(Adjuster)),
 | 
						|
      FallbackFlags(std::move(FallbackFlags)) {}
 | 
						|
 | 
						|
llvm::Optional<tooling::CompileCommand>
 | 
						|
OverlayCDB::getCompileCommand(PathRef File) const {
 | 
						|
  llvm::Optional<tooling::CompileCommand> Cmd;
 | 
						|
  {
 | 
						|
    std::lock_guard<std::mutex> Lock(Mutex);
 | 
						|
    auto It = Commands.find(removeDots(File));
 | 
						|
    if (It != Commands.end())
 | 
						|
      Cmd = It->second;
 | 
						|
  }
 | 
						|
  if (!Cmd)
 | 
						|
    Cmd = DelegatingCDB::getCompileCommand(File);
 | 
						|
  if (!Cmd)
 | 
						|
    return llvm::None;
 | 
						|
  if (ArgsAdjuster)
 | 
						|
    Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename);
 | 
						|
  return Cmd;
 | 
						|
}
 | 
						|
 | 
						|
tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
 | 
						|
  auto Cmd = DelegatingCDB::getFallbackCommand(File);
 | 
						|
  std::lock_guard<std::mutex> Lock(Mutex);
 | 
						|
  Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
 | 
						|
                         FallbackFlags.end());
 | 
						|
  if (ArgsAdjuster)
 | 
						|
    Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename);
 | 
						|
  return Cmd;
 | 
						|
}
 | 
						|
 | 
						|
void OverlayCDB::setCompileCommand(
 | 
						|
    PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) {
 | 
						|
  // We store a canonical version internally to prevent mismatches between set
 | 
						|
  // and get compile commands. Also it assures clients listening to broadcasts
 | 
						|
  // doesn't receive different names for the same file.
 | 
						|
  std::string CanonPath = removeDots(File);
 | 
						|
  {
 | 
						|
    std::unique_lock<std::mutex> Lock(Mutex);
 | 
						|
    if (Cmd)
 | 
						|
      Commands[CanonPath] = std::move(*Cmd);
 | 
						|
    else
 | 
						|
      Commands.erase(CanonPath);
 | 
						|
  }
 | 
						|
  OnCommandChanged.broadcast({CanonPath});
 | 
						|
}
 | 
						|
 | 
						|
DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base)
 | 
						|
    : Base(Base) {
 | 
						|
  if (Base)
 | 
						|
    BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
 | 
						|
      OnCommandChanged.broadcast(Changes);
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
DelegatingCDB::DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base)
 | 
						|
    : DelegatingCDB(Base.get()) {
 | 
						|
  BaseOwner = std::move(Base);
 | 
						|
}
 | 
						|
 | 
						|
llvm::Optional<tooling::CompileCommand>
 | 
						|
DelegatingCDB::getCompileCommand(PathRef File) const {
 | 
						|
  if (!Base)
 | 
						|
    return llvm::None;
 | 
						|
  return Base->getCompileCommand(File);
 | 
						|
}
 | 
						|
 | 
						|
llvm::Optional<ProjectInfo> DelegatingCDB::getProjectInfo(PathRef File) const {
 | 
						|
  if (!Base)
 | 
						|
    return llvm::None;
 | 
						|
  return Base->getProjectInfo(File);
 | 
						|
}
 | 
						|
 | 
						|
tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File) const {
 | 
						|
  if (!Base)
 | 
						|
    return GlobalCompilationDatabase::getFallbackCommand(File);
 | 
						|
  return Base->getFallbackCommand(File);
 | 
						|
}
 | 
						|
 | 
						|
bool DelegatingCDB::blockUntilIdle(Deadline D) const {
 | 
						|
  if (!Base)
 | 
						|
    return true;
 | 
						|
  return Base->blockUntilIdle(D);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace clangd
 | 
						|
} // namespace clang
 |