Files
llvm-project/clang-tools-extra/clangd/ModulesBuilder.cpp
Chuanqi Xu c49b1773b2 [clangd] [C++20] [Modules] Introduce GC for clangd built modules (#193973)
This patch introduces simple GC for clangd built module files to avoid
the clangd built module cache to increase infinitely.

The strategy is, in a clangd built module file cache, if the clangd
built module (we think all PCM files in clangd cache are built by
clangd) was not accessed in a time (by default 3 day, controlled by
--modules-builder-versioned-gc-threshold-seconds),clangd will remove it.

The strategy is not perfect. e.g., I heard in some systems, the atime
was forbid or not update. But given a trade off between usability and
maintainability. I feel the current stategy is fine.

AI assisted.
2026-04-24 14:02:13 +00:00

1269 lines
48 KiB
C++

//===----------------- ModulesBuilder.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 "ModulesBuilder.h"
#include "Compiler.h"
#include "SourceCode.h"
#include "support/Logger.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Serialization/ASTReader.h"
#include "clang/Serialization/ModuleCache.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LockFileManager.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include <chrono>
#include <ctime>
namespace clang {
namespace clangd {
namespace {
llvm::cl::opt<bool> DebugModulesBuilder(
"debug-modules-builder",
llvm::cl::desc("Don't remove clangd's built module files for debugging. "
"Remember to remove them later after debugging."),
llvm::cl::init(false));
llvm::cl::opt<unsigned> VersionedModuleFileGCThresholdSeconds(
"modules-builder-versioned-gc-threshold-seconds",
llvm::cl::desc("Delete versioned copy-on-read module files whose last "
"access time is older than this many seconds."),
llvm::cl::init(3 * 24 * 60 * 60));
//===----------------------------------------------------------------------===//
// Persistent Module Cache Layout.
//
// clangd publishes prerequisite BMIs into a stable on-disk cache so later
// builders can reuse them across sessions. Cache entries are grouped by a
// readable module-unit source directory name plus a hash of the normalized
// source path, and are further separated by a hash of the full compile
// command, which keeps incompatible BMI variants apart.
//
// module-unit source
// |
// v
// cache root
// |
// +-- <module-unit-source-name>-<source-hash>
// |
// +-- <command-hash>
// |
// +-- <primary-module>[-<partition>].pcm
//===----------------------------------------------------------------------===//
std::string hashStringForCache(llvm::StringRef Content) {
return llvm::toHex(digest(Content));
}
std::string normalizePathForCache(PathRef Path) {
llvm::SmallString<256> Normalized(Path);
llvm::sys::path::remove_dots(Normalized, /*remove_dot_dot=*/true);
return maybeCaseFoldPath(Normalized);
}
/// Returns the root directory used for persistent module cache storage.
/// Prefer a project-local cache so different clangd sessions working on the
/// same source tree can reuse BMIs. Fall back to the user cache directory, and
/// finally to a non-ephemeral temp directory when no better cache root exists.
llvm::SmallString<256>
getModuleCacheRoot(PathRef ModuleUnitFileName,
const GlobalCompilationDatabase &CDB) {
llvm::SmallString<256> Result;
if (auto PI = CDB.getProjectInfo(ModuleUnitFileName);
PI && !PI->SourceRoot.empty()) {
Result = PI->SourceRoot;
llvm::sys::path::append(Result, ".cache", "clangd", "modules");
return Result;
}
if (llvm::sys::path::cache_directory(Result)) {
llvm::sys::path::append(Result, "clangd", "modules");
return Result;
}
llvm::sys::path::system_temp_directory(/*erasedOnReboot=*/false, Result);
llvm::sys::path::append(Result, "clangd", "modules");
return Result;
}
/// Returns the directory holding source-scoped lock files for the persistent
/// module cache. Placing locks beside the cache ensures all builders sharing
/// the cache also synchronize through the same lock namespace.
llvm::SmallString<256>
getModuleCacheLocksDirectory(PathRef ModuleUnitFileName,
const GlobalCompilationDatabase &CDB) {
llvm::SmallString<256> Result = getModuleCacheRoot(ModuleUnitFileName, CDB);
llvm::sys::path::append(Result, ".locks");
return Result;
}
std::string getModuleUnitSourcePathHash(PathRef ModuleUnitFileName) {
return hashStringForCache(normalizePathForCache(ModuleUnitFileName));
}
std::string getModuleUnitSourceDirectoryName(PathRef ModuleUnitFileName) {
std::string Result = llvm::sys::path::filename(ModuleUnitFileName).str();
Result.push_back('-');
Result.append(getModuleUnitSourcePathHash(ModuleUnitFileName));
return Result;
}
std::string getCompileCommandStringHash(const tooling::CompileCommand &Cmd) {
std::string SerializedCommand;
SerializedCommand.reserve(Cmd.Directory.size() + Cmd.Filename.size() +
Cmd.CommandLine.size() * 16);
// The module-unit source path is already encoded in the parent cache
// directory. Output is rewritten while staging the BMI, so hash only the
// semantic compile command to keep the cache key stable across rebuilds.
SerializedCommand.append(Cmd.Directory);
SerializedCommand.push_back('\0');
for (const auto &Arg : Cmd.CommandLine) {
SerializedCommand.append(Arg);
SerializedCommand.push_back('\0');
}
return hashStringForCache(SerializedCommand);
}
/// Returns the directory for a persistent BMI built from a specific module
/// unit source and compile command. The directory name keeps a readable source
/// basename alongside the source-hash, and the command-hash keeps incompatible
/// command lines apart.
llvm::SmallString<256>
getModuleFilesDirectory(PathRef ModuleUnitFileName,
const tooling::CompileCommand &Cmd,
const GlobalCompilationDatabase &CDB) {
llvm::SmallString<256> Result = getModuleCacheRoot(ModuleUnitFileName, CDB);
llvm::sys::path::append(Result,
getModuleUnitSourceDirectoryName(ModuleUnitFileName),
getCompileCommandStringHash(Cmd));
return Result;
}
/// Returns the lock file path guarding publication of BMIs for a module unit
/// source. Builders targeting the same source-hash serialize through this path.
llvm::SmallString<256>
getModuleSourceHashLockPath(PathRef ModuleUnitFileName,
const GlobalCompilationDatabase &CDB) {
llvm::SmallString<256> Result =
getModuleCacheLocksDirectory(ModuleUnitFileName, CDB);
llvm::sys::path::append(Result,
getModuleUnitSourcePathHash(ModuleUnitFileName));
return Result;
}
/// Returns a unique temporary path used to stage a BMI before atomically
/// publishing it to the stable cache path.
llvm::SmallString<256> getTemporaryModuleFilePath(PathRef ModuleFilePath) {
llvm::SmallString<256> ResultPattern(ModuleFilePath);
ResultPattern.append(".tmp-%%-%%-%%-%%-%%-%%");
llvm::SmallString<256> Result;
llvm::sys::fs::createUniquePath(ResultPattern, Result,
/*MakeAbsolute=*/false);
return Result;
}
std::string getModuleFileVersionTimestamp() {
const auto Now = std::chrono::system_clock::now();
const auto Micros = std::chrono::duration_cast<std::chrono::microseconds>(
Now.time_since_epoch()) %
std::chrono::seconds(1);
const std::time_t CalendarTime = std::chrono::system_clock::to_time_t(Now);
std::tm LocalTime;
#ifdef _WIN32
localtime_s(&LocalTime, &CalendarTime);
#else
localtime_r(&CalendarTime, &LocalTime);
#endif
return llvm::formatv("{0:04}{1:02}{2:02}-{3:02}{4:02}{5:02}-{6:06}",
LocalTime.tm_year + 1900, LocalTime.tm_mon + 1,
LocalTime.tm_mday, LocalTime.tm_hour, LocalTime.tm_min,
LocalTime.tm_sec, Micros.count())
.str();
}
llvm::SmallString<256>
getCopyOnReadModuleFilePath(PathRef PublishedModuleFile) {
llvm::SmallString<256> Result(PublishedModuleFile);
llvm::sys::path::remove_filename(Result);
llvm::sys::path::append(
Result,
llvm::formatv("{0}-{1}{2}", llvm::sys::path::stem(PublishedModuleFile),
getModuleFileVersionTimestamp(),
llvm::sys::path::extension(PublishedModuleFile))
.str());
return Result;
}
/// Ensures the lock anchor file exists before LockFileManager tries to acquire
/// ownership, creating parent directories as needed.
llvm::Error ensureLockAnchorFileExists(PathRef LockPath) {
llvm::SmallString<256> LockParent(LockPath);
llvm::sys::path::remove_filename(LockParent);
if (std::error_code EC = llvm::sys::fs::create_directories(LockParent))
return llvm::createStringError(llvm::formatv(
"Failed to create lock directory {0}: {1}", LockParent, EC.message()));
int FD = -1;
if (std::error_code EC = llvm::sys::fs::openFileForWrite(
LockPath, FD, llvm::sys::fs::CD_OpenAlways))
return llvm::createStringError(llvm::formatv(
"Failed to open lock file anchor {0}: {1}", LockPath, EC.message()));
llvm::sys::Process::SafelyCloseFileDescriptor(FD);
return llvm::Error::success();
}
//===----------------------------------------------------------------------===//
// Persistent Module Cache Locking.
//
// Builders targeting the same module-unit source share a source-hash lock.
// This serializes in-place replacement of stale cache entries and final publish
// of the stable BMI path, while still allowing unrelated module sources to be
// built concurrently.
//
// builder A builder B
// | |
// +---- lock(source) ----->|
// | |
// | build/publish BMI | wait
// | |
// +---- unlock ----------->|
// | reuse or rebuild
//===----------------------------------------------------------------------===//
/// Serializes publication and in-place replacement of persistent BMIs for a
/// single module-unit source across multiple builders.
class ScopedModuleSourceLock {
public:
static llvm::Expected<ScopedModuleSourceLock>
acquire(PathRef ModuleUnitFileName, const GlobalCompilationDatabase &CDB) {
constexpr auto LockWaitInterval = std::chrono::seconds(10);
llvm::SmallString<256> LockPath =
getModuleSourceHashLockPath(ModuleUnitFileName, CDB);
if (llvm::Error Err = ensureLockAnchorFileExists(LockPath))
return std::move(Err);
auto Waited = std::chrono::seconds::zero();
while (true) {
auto Lock = std::make_unique<llvm::LockFileManager>(LockPath);
auto TryLock = Lock->tryLock();
if (!TryLock)
return TryLock.takeError();
if (*TryLock)
return ScopedModuleSourceLock(std::move(Lock));
switch (Lock->waitForUnlockFor(LockWaitInterval)) {
case llvm::WaitForUnlockResult::Success:
case llvm::WaitForUnlockResult::OwnerDied:
continue;
case llvm::WaitForUnlockResult::Timeout:
Waited += LockWaitInterval;
log("Still waiting for module lock {0} after {1}s", LockPath,
Waited.count());
continue;
}
llvm_unreachable("Unhandled lock wait result");
}
}
private:
explicit ScopedModuleSourceLock(std::unique_ptr<llvm::LockFileManager> Lock)
: Lock(std::move(Lock)) {}
std::unique_ptr<llvm::LockFileManager> Lock;
};
// Get the stable published module file path under \param ModuleFilesPrefix.
std::string getModuleFilePath(llvm::StringRef ModuleName,
PathRef ModuleFilesPrefix) {
llvm::SmallString<256> ModuleFilePath(ModuleFilesPrefix);
auto [PrimaryModuleName, PartitionName] = ModuleName.split(':');
llvm::sys::path::append(ModuleFilePath, PrimaryModuleName);
if (!PartitionName.empty()) {
ModuleFilePath.append("-");
ModuleFilePath.append(PartitionName);
}
ModuleFilePath.append(".pcm");
return std::string(ModuleFilePath);
}
std::string getPublishedModuleFilePath(llvm::StringRef ModuleName,
PathRef ModuleFilesPrefix) {
return getModuleFilePath(ModuleName, ModuleFilesPrefix);
}
// FailedPrerequisiteModules - stands for the PrerequisiteModules which has
// errors happened during the building process.
class FailedPrerequisiteModules : public PrerequisiteModules {
public:
~FailedPrerequisiteModules() override = default;
// We shouldn't adjust the compilation commands based on
// FailedPrerequisiteModules.
void adjustHeaderSearchOptions(HeaderSearchOptions &Options) const override {}
// FailedPrerequisiteModules can never be reused.
bool
canReuse(const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) const override {
return false;
}
};
/// Represents a reference to a module file (*.pcm).
class ModuleFile {
protected:
ModuleFile(StringRef ModuleName, PathRef ModuleFilePath)
: ModuleName(ModuleName.str()), ModuleFilePath(ModuleFilePath.str()) {}
public:
ModuleFile() = delete;
ModuleFile(const ModuleFile &) = delete;
ModuleFile operator=(const ModuleFile &) = delete;
// The move constructor is needed for llvm::SmallVector.
ModuleFile(ModuleFile &&Other)
: ModuleName(std::move(Other.ModuleName)),
ModuleFilePath(std::move(Other.ModuleFilePath)) {
Other.ModuleName.clear();
Other.ModuleFilePath.clear();
}
ModuleFile &operator=(ModuleFile &&Other) {
if (this == &Other)
return *this;
this->~ModuleFile();
new (this) ModuleFile(std::move(Other));
return *this;
}
virtual ~ModuleFile() = default;
StringRef getModuleName() const { return ModuleName; }
StringRef getModuleFilePath() const { return ModuleFilePath; }
protected:
std::string ModuleName;
std::string ModuleFilePath;
};
/// Represents a prebuilt module file which is not owned by us.
class PrebuiltModuleFile : public ModuleFile {
private:
// private class to make sure the class can only be constructed by member
// functions.
struct CtorTag {};
public:
PrebuiltModuleFile(StringRef ModuleName, PathRef ModuleFilePath, CtorTag)
: ModuleFile(ModuleName, ModuleFilePath) {}
static std::shared_ptr<PrebuiltModuleFile> make(StringRef ModuleName,
PathRef ModuleFilePath) {
return std::make_shared<PrebuiltModuleFile>(ModuleName, ModuleFilePath,
CtorTag{});
}
};
//===----------------------------------------------------------------------===//
// Module File Ownership and Reuse.
//
// PrebuiltModuleFile refers to BMIs supplied directly by the compile command.
// BuiltModuleFile refers to BMIs produced by clangd and published into the
// persistent cache. Object lifetime does not control filesystem lifetime for
// BuiltModuleFile; cache files remain on disk for reuse across builders. The
// versioned copies handed to clang for actual reads are owned by
// CopyOnReadModuleFile and are deleted when the last reader releases them.
//
// Copy-on-read keeps the published BMI path stable for future builders while
// avoiding in-place replacement races for active readers. clangd never hands
// the stable cache entry directly to parsing code. Instead, once a published
// BMI is known to be up to date, clangd copies it to a versioned sibling path
// and gives that copy to readers. Rebuilding only mutates the stable cache
// entry; existing readers keep their own immutable copy until the last
// shared_ptr reference drops and the copy-on-read file is deleted.
//
// compile command ---------> PrebuiltModuleFile
//
// clangd build -> publish -> BuiltModuleFile ------------> stable cache path
// (M.pcm)
// |
// +-> copy for read -> CopyOnReadModuleFile
// (M-<timestamp>.pcm)
// -> handed to clang readers
// -> removed on last release
//
// later builder -----------> reuse stable cache path ----> copy for read
//===----------------------------------------------------------------------===//
/// Represents a module file built and published by clangd into its persistent
/// cache.
class BuiltModuleFile final : public ModuleFile {
private:
// private class to make sure the class can only be constructed by member
// functions.
struct CtorTag {};
public:
BuiltModuleFile(StringRef ModuleName, PathRef ModuleFilePath, CtorTag)
: ModuleFile(ModuleName, ModuleFilePath) {}
static std::shared_ptr<BuiltModuleFile> make(StringRef ModuleName,
PathRef ModuleFilePath) {
return std::make_shared<BuiltModuleFile>(ModuleName, ModuleFilePath,
CtorTag{});
}
};
/// Represents a versioned copy of a published BMI handed to clangd readers.
/// The copy is removed when the last reader releases it.
class CopyOnReadModuleFile final : public ModuleFile {
private:
struct CtorTag {};
public:
CopyOnReadModuleFile(StringRef ModuleName, PathRef ModuleFilePath, CtorTag)
: ModuleFile(ModuleName, ModuleFilePath) {}
~CopyOnReadModuleFile() override {
if (!ModuleFilePath.empty() && !DebugModulesBuilder)
if (std::error_code EC = llvm::sys::fs::remove(ModuleFilePath))
vlog("Failed to remove copy-on-read module file {0}: {1}",
ModuleFilePath, EC.message());
}
static std::shared_ptr<CopyOnReadModuleFile> make(StringRef ModuleName,
PathRef ModuleFilePath) {
return std::make_shared<CopyOnReadModuleFile>(ModuleName, ModuleFilePath,
CtorTag{});
}
};
// ReusablePrerequisiteModules - stands for PrerequisiteModules for which all
// the required modules are built successfully. All the module files
// are owned by the modules builder.
class ReusablePrerequisiteModules : public PrerequisiteModules {
public:
ReusablePrerequisiteModules() = default;
ReusablePrerequisiteModules(const ReusablePrerequisiteModules &Other) =
default;
ReusablePrerequisiteModules &
operator=(const ReusablePrerequisiteModules &) = default;
ReusablePrerequisiteModules(ReusablePrerequisiteModules &&) = delete;
ReusablePrerequisiteModules
operator=(ReusablePrerequisiteModules &&) = delete;
~ReusablePrerequisiteModules() override = default;
void adjustHeaderSearchOptions(HeaderSearchOptions &Options) const override {
// Appending all built module files.
for (const auto &RequiredModule : RequiredModules)
Options.PrebuiltModuleFiles.insert_or_assign(
RequiredModule->getModuleName().str(),
RequiredModule->getModuleFilePath().str());
}
std::string getAsString() const {
std::string Result;
llvm::raw_string_ostream OS(Result);
for (const auto &MF : RequiredModules) {
OS << "-fmodule-file=" << MF->getModuleName() << "="
<< MF->getModuleFilePath() << " ";
}
return Result;
}
bool canReuse(const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) const override;
bool isModuleUnitBuilt(llvm::StringRef ModuleName) const {
return BuiltModuleNames.contains(ModuleName);
}
void addModuleFile(std::shared_ptr<const ModuleFile> MF) {
BuiltModuleNames.insert(MF->getModuleName());
RequiredModules.emplace_back(std::move(MF));
}
private:
llvm::SmallVector<std::shared_ptr<const ModuleFile>, 8> RequiredModules;
// A helper class to speedup the query if a module is built.
llvm::StringSet<> BuiltModuleNames;
};
bool IsModuleFileUpToDate(PathRef ModuleFilePath,
const PrerequisiteModules &RequisiteModules,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
HeaderSearchOptions HSOpts;
RequisiteModules.adjustHeaderSearchOptions(HSOpts);
HSOpts.ForceCheckCXX20ModulesInputFiles = true;
HSOpts.ValidateASTInputFilesContent = true;
clang::clangd::IgnoreDiagnostics IgnoreDiags;
DiagnosticOptions DiagOpts;
IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
CompilerInstance::createDiagnostics(*VFS, DiagOpts, &IgnoreDiags,
/*ShouldOwnClient=*/false);
LangOptions LangOpts;
LangOpts.SkipODRCheckInGMF = true;
FileManager FileMgr(FileSystemOptions(), VFS);
SourceManager SourceMgr(*Diags, FileMgr);
HeaderSearch HeaderInfo(HSOpts, SourceMgr, *Diags, LangOpts,
/*Target=*/nullptr);
PreprocessorOptions PPOpts;
TrivialModuleLoader ModuleLoader;
Preprocessor PP(PPOpts, *Diags, LangOpts, SourceMgr, HeaderInfo,
ModuleLoader);
std::shared_ptr<ModuleCache> ModCache = createCrossProcessModuleCache();
PCHContainerOperations PCHOperations;
CodeGenOptions CodeGenOpts;
ASTReader Reader(
PP, *ModCache, /*ASTContext=*/nullptr, PCHOperations.getRawReader(),
CodeGenOpts, {},
/*isysroot=*/"",
/*DisableValidationKind=*/DisableValidationForModuleKind::None,
/*AllowASTWithCompilerErrors=*/false,
/*AllowConfigurationMismatch=*/false,
/*ValidateSystemInputs=*/false,
/*ForceValidateUserInputs=*/true,
/*ValidateASTInputFilesContent=*/true);
// We don't need any listener here. By default it will use a validator
// listener.
Reader.setListener(nullptr);
// Use ARR_OutOfDate so that ReadAST returns OutOfDate instead of Failure
// when input files are modified. This allows us to detect staleness
// without treating it as a hard error.
// ReadAST will validate all input files internally and return OutOfDate
// if any file is modified.
return Reader.ReadAST(ModuleFileName::makeExplicit(ModuleFilePath),
serialization::MK_MainFile, SourceLocation(),
ASTReader::ARR_OutOfDate) == ASTReader::Success;
}
bool IsModuleFilesUpToDate(
llvm::SmallVector<PathRef> ModuleFilePaths,
const PrerequisiteModules &RequisiteModules,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
return llvm::all_of(
ModuleFilePaths, [&RequisiteModules, VFS](auto ModuleFilePath) {
return IsModuleFileUpToDate(ModuleFilePath, RequisiteModules, VFS);
});
}
/// Builds a BMI into a temporary file and publishes it to `ModuleFilePath`.
/// If another builder wins the publish race first, reports that through
/// `PublishedExistingModuleFile` so the caller can validate and reuse it.
llvm::Expected<std::shared_ptr<BuiltModuleFile>>
buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName,
tooling::CompileCommand Cmd, PathRef ModuleFilePath,
const ThreadsafeFS &TFS,
const ReusablePrerequisiteModules &BuiltModuleFiles,
bool &PublishedExistingModuleFile) {
PublishedExistingModuleFile = false;
llvm::SmallString<256> ModuleFilesPrefix(ModuleFilePath);
llvm::sys::path::remove_filename(ModuleFilesPrefix);
if (std::error_code EC = llvm::sys::fs::create_directories(ModuleFilesPrefix))
return llvm::createStringError(
llvm::formatv("Failed to create module cache directory {0}: {1}",
ModuleFilesPrefix, EC.message()));
llvm::SmallString<256> TemporaryModuleFilePath =
getTemporaryModuleFilePath(ModuleFilePath);
auto RemoveTemporaryModuleFile = llvm::scope_exit([&] {
if (!TemporaryModuleFilePath.empty() && !DebugModulesBuilder)
llvm::sys::fs::remove(TemporaryModuleFilePath);
});
(void)RemoveTemporaryModuleFile;
Cmd.Output = TemporaryModuleFilePath.str().str();
ParseInputs Inputs;
Inputs.TFS = &TFS;
Inputs.CompileCommand = std::move(Cmd);
IgnoreDiagnostics IgnoreDiags;
auto CI = buildCompilerInvocation(Inputs, IgnoreDiags);
if (!CI)
return llvm::createStringError("Failed to build compiler invocation");
auto FS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
auto Buf = FS->getBufferForFile(Inputs.CompileCommand.Filename);
if (!Buf)
return llvm::createStringError("Failed to create buffer");
// In clang's driver, we will suppress the check for ODR violation in GMF.
// See the implementation of RenderModulesOptions in Clang.cpp.
CI->getLangOpts().SkipODRCheckInGMF = true;
// Hash the contents of input files and store the hash value to the BMI files.
// So that we can check if the files are still valid when we want to reuse the
// BMI files.
CI->getHeaderSearchOpts().ValidateASTInputFilesContent = true;
BuiltModuleFiles.adjustHeaderSearchOptions(CI->getHeaderSearchOpts());
CI->getFrontendOpts().OutputFile = Inputs.CompileCommand.Output;
auto Clang =
prepareCompilerInstance(std::move(CI), /*Preamble=*/nullptr,
std::move(*Buf), std::move(FS), IgnoreDiags);
if (!Clang)
return llvm::createStringError("Failed to prepare compiler instance");
GenerateReducedModuleInterfaceAction Action;
Clang->ExecuteAction(Action);
if (Clang->getDiagnostics().hasErrorOccurred()) {
std::string Cmds;
for (const auto &Arg : Inputs.CompileCommand.CommandLine) {
if (!Cmds.empty())
Cmds += " ";
Cmds += Arg;
}
clangd::vlog("Failed to compile {0} with command: {1}", ModuleUnitFileName,
Cmds);
std::string BuiltModuleFilesStr = BuiltModuleFiles.getAsString();
if (!BuiltModuleFilesStr.empty())
clangd::vlog("The actual used module files built by clangd is {0}",
BuiltModuleFilesStr);
return llvm::createStringError(
llvm::formatv("Failed to compile {0}. Use '--log=verbose' to view "
"detailed failure reasons. It is helpful to use "
"'--debug-modules-builder' flag to keep the clangd's "
"built module files to reproduce the failure for "
"debugging. Remember to remove them after debugging.",
ModuleUnitFileName));
}
if (std::error_code EC =
llvm::sys::fs::rename(TemporaryModuleFilePath, ModuleFilePath)) {
if (!llvm::sys::fs::exists(ModuleFilePath))
return llvm::createStringError(
llvm::formatv("Failed to publish module file {0}: {1}",
ModuleFilePath, EC.message()));
// Another builder already published the stable cache entry. Drop our
// staged BMI and let the caller revalidate the published path.
PublishedExistingModuleFile = true;
} else {
// Rename consumed the staging file into the stable cache path. Clear it so
// the scope-exit cleanup does not try to remove the published BMI.
TemporaryModuleFilePath.clear();
}
return BuiltModuleFile::make(ModuleName, ModuleFilePath);
}
llvm::Expected<std::shared_ptr<CopyOnReadModuleFile>>
copyModuleFileForRead(llvm::StringRef ModuleName,
PathRef PublishedModuleFilePath) {
llvm::SmallString<256> VersionedModuleFilePath =
getCopyOnReadModuleFilePath(PublishedModuleFilePath);
if (std::error_code EC = llvm::sys::fs::copy_file(PublishedModuleFilePath,
VersionedModuleFilePath))
return llvm::createStringError(llvm::formatv(
"Failed to copy module file {0} to {1}: {2}", PublishedModuleFilePath,
VersionedModuleFilePath, EC.message()));
return CopyOnReadModuleFile::make(ModuleName, VersionedModuleFilePath);
}
bool ReusablePrerequisiteModules::canReuse(
const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) const {
if (RequiredModules.empty())
return true;
llvm::SmallVector<llvm::StringRef> BMIPaths;
for (auto &MF : RequiredModules)
BMIPaths.push_back(MF->getModuleFilePath());
return IsModuleFilesUpToDate(BMIPaths, *this, VFS);
}
//===----------------------------------------------------------------------===//
// In-Memory Module File Cache.
//
// This cache deduplicates BMIs within a single builder instance. Its key
// mirrors the persistent cache layout: module name, module-unit source, and
// compile command hash. That prevents a builder from reusing a BMI built under
// a different command line.
//
// (module name,
// module-unit source,
// command hash)
// |
// v
// ModuleFileCache
// |
// +-- hit -> reuse in current builder
// |
// +-- miss -> probe persistent cache / rebuild
//===----------------------------------------------------------------------===//
/// In-memory cache for module files built by clangd. Entries are keyed by
/// module name, module-unit source, and compile-command hash so persistent BMI
/// variants do not collide.
class ModuleFileCache {
public:
ModuleFileCache(const GlobalCompilationDatabase &CDB) : CDB(CDB) {}
const GlobalCompilationDatabase &getCDB() const { return CDB; }
std::shared_ptr<const ModuleFile> getModule(StringRef ModuleName,
PathRef ModuleUnitSource,
llvm::StringRef CommandHash);
void add(StringRef ModuleName, PathRef ModuleUnitSource,
llvm::StringRef CommandHash,
std::shared_ptr<const ModuleFile> ModuleFile) {
std::lock_guard<std::mutex> Lock(ModuleFilesMutex);
ModuleFiles[cacheKey(ModuleName, ModuleUnitSource, CommandHash)] =
ModuleFile;
}
void remove(StringRef ModuleName, PathRef ModuleUnitSource,
llvm::StringRef CommandHash);
private:
static std::string cacheKey(StringRef ModuleName, PathRef ModuleUnitSource,
llvm::StringRef CommandHash) {
std::string Key;
Key.reserve(ModuleName.size() + ModuleUnitSource.size() +
CommandHash.size() + 2);
Key.append(ModuleName);
Key.push_back('\0');
Key.append(maybeCaseFoldPath(ModuleUnitSource));
Key.push_back('\0');
Key.append(CommandHash);
return Key;
}
const GlobalCompilationDatabase &CDB;
llvm::StringMap<std::weak_ptr<const ModuleFile>> ModuleFiles;
std::mutex ModuleFilesMutex;
};
std::shared_ptr<const ModuleFile>
ModuleFileCache::getModule(StringRef ModuleName, PathRef ModuleUnitSource,
llvm::StringRef CommandHash) {
std::lock_guard<std::mutex> Lock(ModuleFilesMutex);
auto Iter =
ModuleFiles.find(cacheKey(ModuleName, ModuleUnitSource, CommandHash));
if (Iter == ModuleFiles.end())
return nullptr;
if (auto Res = Iter->second.lock())
return Res;
ModuleFiles.erase(Iter);
return nullptr;
}
void ModuleFileCache::remove(StringRef ModuleName, PathRef ModuleUnitSource,
llvm::StringRef CommandHash) {
std::lock_guard<std::mutex> Lock(ModuleFilesMutex);
ModuleFiles.erase(cacheKey(ModuleName, ModuleUnitSource, CommandHash));
}
class ModuleNameToSourceCache {
public:
std::string getUniqueSourceForModuleName(llvm::StringRef ModuleName) {
std::lock_guard<std::mutex> Lock(CacheMutex);
auto Iter = ModuleNameToUniqueSourceCache.find(ModuleName);
if (Iter != ModuleNameToUniqueSourceCache.end())
return Iter->second;
return "";
}
void addUniqueEntry(llvm::StringRef ModuleName, PathRef Source) {
std::lock_guard<std::mutex> Lock(CacheMutex);
ModuleNameToUniqueSourceCache[ModuleName] = Source.str();
}
void eraseUniqueEntry(llvm::StringRef ModuleName) {
std::lock_guard<std::mutex> Lock(CacheMutex);
ModuleNameToUniqueSourceCache.erase(ModuleName);
}
std::string getMultipleSourceForModuleName(llvm::StringRef ModuleName,
PathRef RequiredSrcFile) {
std::lock_guard<std::mutex> Lock(CacheMutex);
auto Outer = ModuleNameToMultipleSourceCache.find(ModuleName);
if (Outer == ModuleNameToMultipleSourceCache.end())
return "";
auto Inner = Outer->second.find(maybeCaseFoldPath(RequiredSrcFile));
if (Inner == Outer->second.end())
return "";
return Inner->second;
}
void addMultipleEntry(llvm::StringRef ModuleName, PathRef RequiredSrcFile,
PathRef Source) {
std::lock_guard<std::mutex> Lock(CacheMutex);
ModuleNameToMultipleSourceCache[ModuleName]
[maybeCaseFoldPath(RequiredSrcFile)] =
Source.str();
}
void eraseMultipleEntry(llvm::StringRef ModuleName, PathRef RequiredSrcFile) {
std::lock_guard<std::mutex> Lock(CacheMutex);
auto Outer = ModuleNameToMultipleSourceCache.find(ModuleName);
if (Outer == ModuleNameToMultipleSourceCache.end())
return;
Outer->second.erase(maybeCaseFoldPath(RequiredSrcFile));
if (Outer->second.empty())
ModuleNameToMultipleSourceCache.erase(Outer);
}
private:
std::mutex CacheMutex;
llvm::StringMap<std::string> ModuleNameToUniqueSourceCache;
// Map from module name to a map from required source to module unit source
// which declares the corresponding module name.
// This looks inefficiency. We can only assume there won't too many duplicated
// module names with different module units in a project.
llvm::StringMap<llvm::StringMap<std::string>> ModuleNameToMultipleSourceCache;
};
class CachingProjectModules : public ProjectModules {
public:
CachingProjectModules(std::unique_ptr<ProjectModules> MDB,
ModuleNameToSourceCache &Cache)
: MDB(std::move(MDB)), Cache(Cache) {
assert(this->MDB && "CachingProjectModules should only be created with a "
"valid underlying ProjectModules");
}
std::vector<std::string> getRequiredModules(PathRef File) override {
return MDB->getRequiredModules(File);
}
std::string getModuleNameForSource(PathRef File) override {
return MDB->getModuleNameForSource(File);
}
ModuleNameState getModuleNameState(llvm::StringRef ModuleName) override {
return MDB->getModuleNameState(ModuleName);
}
std::string getSourceForModuleName(llvm::StringRef ModuleName,
PathRef RequiredSrcFile) override {
auto ModuleState = MDB->getModuleNameState(ModuleName);
if (ModuleState == ModuleNameState::Multiple) {
std::string CachedResult =
Cache.getMultipleSourceForModuleName(ModuleName, RequiredSrcFile);
// Verify Cached Result by seeing if the source declaring the same module
// as we query.
if (!CachedResult.empty()) {
std::string ModuleNameOfCachedSource =
MDB->getModuleNameForSource(CachedResult);
if (ModuleNameOfCachedSource == ModuleName)
return CachedResult;
// Cached Result is invalid. Clear it.
Cache.eraseMultipleEntry(ModuleName, RequiredSrcFile);
}
auto Result = MDB->getSourceForModuleName(ModuleName, RequiredSrcFile);
if (!Result.empty())
Cache.addMultipleEntry(ModuleName, RequiredSrcFile, Result);
return Result;
}
// For unknown module name state, assume it is unique. This may give user
// higher usability.
assert(ModuleState == ModuleNameState::Unique ||
ModuleState == ModuleNameState::Unknown);
std::string CachedResult = Cache.getUniqueSourceForModuleName(ModuleName);
// Verify Cached Result by seeing if the source declaring the same module
// as we query.
if (!CachedResult.empty()) {
std::string ModuleNameOfCachedSource =
MDB->getModuleNameForSource(CachedResult);
if (ModuleNameOfCachedSource == ModuleName)
return CachedResult;
// Cached Result is invalid. Clear it.
Cache.eraseUniqueEntry(ModuleName);
}
auto Result = MDB->getSourceForModuleName(ModuleName, RequiredSrcFile);
if (!Result.empty())
Cache.addUniqueEntry(ModuleName, Result);
return Result;
}
private:
std::unique_ptr<ProjectModules> MDB;
ModuleNameToSourceCache &Cache;
};
/// Collect the directly and indirectly required module names for \param
/// ModuleName in topological order. The \param ModuleName is guaranteed to
/// be the last element in \param ModuleNames.
llvm::SmallVector<std::string> getAllRequiredModules(PathRef RequiredSource,
CachingProjectModules &MDB,
StringRef ModuleName) {
llvm::SmallVector<std::string> ModuleNames;
llvm::StringSet<> ModuleNamesSet;
auto VisitDeps = [&](StringRef ModuleName, auto Visitor) -> void {
ModuleNamesSet.insert(ModuleName);
for (StringRef RequiredModuleName : MDB.getRequiredModules(
MDB.getSourceForModuleName(ModuleName, RequiredSource)))
if (ModuleNamesSet.insert(RequiredModuleName).second)
Visitor(RequiredModuleName, Visitor);
ModuleNames.push_back(ModuleName.str());
};
VisitDeps(ModuleName, VisitDeps);
return ModuleNames;
}
/// Collects cache roots to scan during constructor-time GC.
/// Scans one cache root and returns all `.pcm` files under it.
std::vector<std::string> collectModuleFiles(PathRef CacheRoot) {
std::vector<std::string> Result;
std::error_code EC;
for (llvm::sys::fs::recursive_directory_iterator It(CacheRoot, EC), End;
It != End && !EC; It.increment(EC)) {
if (llvm::sys::path::extension(It->path()) != ".pcm")
continue;
Result.push_back(It->path());
}
if (EC)
log("Failed to scan module cache directory {0}: {1}", CacheRoot,
EC.message());
return Result;
}
/// Performs one GC pass over a persistent module cache root.
void garbageCollectModuleCache(PathRef CacheRoot) {
for (const auto &ModuleFilePath : collectModuleFiles(CacheRoot)) {
llvm::sys::fs::file_status Status;
if (std::error_code EC = llvm::sys::fs::status(ModuleFilePath, Status)) {
log("Failed to stat cached module file {0} for GC: {1}", ModuleFilePath,
EC.message());
continue;
}
llvm::sys::TimePoint<> LastAccess = Status.getLastAccessedTime();
llvm::sys::TimePoint<> Now = std::chrono::system_clock::now();
if (LastAccess > Now)
continue;
auto Age =
std::chrono::duration_cast<std::chrono::seconds>(Now - LastAccess);
auto Threshold =
std::chrono::seconds(VersionedModuleFileGCThresholdSeconds);
if (Age <= Threshold)
continue;
if (!llvm::sys::fs::exists(ModuleFilePath))
continue;
constexpr llvm::StringLiteral Reason = "file older than GC threshold";
if (std::error_code EC = llvm::sys::fs::remove(ModuleFilePath)) {
log("Failed to remove cached module file {0} ({1}): {2}", ModuleFilePath,
Reason, EC.message());
continue;
}
log("Removed cached module file {0} ({1})", ModuleFilePath, Reason);
}
}
} // namespace
class ModulesBuilder::ModulesBuilderImpl {
public:
ModulesBuilderImpl(const GlobalCompilationDatabase &CDB) : Cache(CDB) {}
ModuleNameToSourceCache &getProjectModulesCache() {
return ProjectModulesCache;
}
const GlobalCompilationDatabase &getCDB() const { return Cache.getCDB(); }
llvm::Error
getOrBuildModuleFile(PathRef RequiredSource, StringRef ModuleName,
const ThreadsafeFS &TFS, CachingProjectModules &MDB,
ReusablePrerequisiteModules &BuiltModuleFiles);
private:
/// Try to get prebuilt module files from the compilation database.
void getPrebuiltModuleFile(StringRef ModuleName, PathRef ModuleUnitFileName,
const ThreadsafeFS &TFS,
ReusablePrerequisiteModules &BuiltModuleFiles);
/// Runs GC once for the cache root owning a project root.
void garbageCollectModuleCacheForProjectRoot(PathRef ProjectRoot);
ModuleFileCache Cache;
ModuleNameToSourceCache ProjectModulesCache;
std::mutex GarbageCollectedProjectRootsMutex;
llvm::StringSet<> GarbageCollectedProjectRoots;
};
void ModulesBuilder::ModulesBuilderImpl::
garbageCollectModuleCacheForProjectRoot(PathRef ProjectRoot) {
if (ProjectRoot.empty())
return;
std::string NormalizedProjectRoot = normalizePathForCache(ProjectRoot);
{
// If the project root lives in GarbageCollectedProjectRoots, it implies
// we've already started GC on the cache root.
std::lock_guard<std::mutex> Lock(GarbageCollectedProjectRootsMutex);
if (!GarbageCollectedProjectRoots.insert(NormalizedProjectRoot).second)
return;
}
llvm::SmallString<256> CacheRoot(ProjectRoot);
llvm::sys::path::append(CacheRoot, ".cache", "clangd", "modules");
log("Running GC pass for clangd built module files under {0} with age "
"threshold {1} seconds (adjust with --modules-builder-versioned-gc-"
"threshold-seconds)",
CacheRoot, VersionedModuleFileGCThresholdSeconds);
garbageCollectModuleCache(CacheRoot);
log("Done running GC pass for clangd built module files under {0}",
CacheRoot);
}
void ModulesBuilder::ModulesBuilderImpl::getPrebuiltModuleFile(
StringRef ModuleName, PathRef ModuleUnitFileName, const ThreadsafeFS &TFS,
ReusablePrerequisiteModules &BuiltModuleFiles) {
auto Cmd = getCDB().getCompileCommand(ModuleUnitFileName);
if (!Cmd)
return;
ParseInputs Inputs;
Inputs.TFS = &TFS;
Inputs.CompileCommand = std::move(*Cmd);
IgnoreDiagnostics IgnoreDiags;
auto CI = buildCompilerInvocation(Inputs, IgnoreDiags);
if (!CI)
return;
// We don't need to check if the module files are in ModuleCache or adding
// them to the module cache. As even if the module files are in the module
// cache, we still need to validate them. And it looks not helpful to add them
// to the module cache, since we may always try to get the prebuilt module
// files before building the module files by ourselves.
for (auto &[ModuleName, ModuleFilePath] :
CI->getHeaderSearchOpts().PrebuiltModuleFiles) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
continue;
// Convert relative path to absolute path based on the compilation directory
llvm::SmallString<256> AbsoluteModuleFilePath;
if (llvm::sys::path::is_relative(ModuleFilePath)) {
AbsoluteModuleFilePath = Inputs.CompileCommand.Directory;
llvm::sys::path::append(AbsoluteModuleFilePath, ModuleFilePath);
} else
AbsoluteModuleFilePath = ModuleFilePath;
if (IsModuleFileUpToDate(AbsoluteModuleFilePath, BuiltModuleFiles,
TFS.view(std::nullopt))) {
log("Reusing prebuilt module file {0} of module {1} for {2}",
AbsoluteModuleFilePath, ModuleName, ModuleUnitFileName);
BuiltModuleFiles.addModuleFile(
PrebuiltModuleFile::make(ModuleName, AbsoluteModuleFilePath));
}
}
}
llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile(
PathRef RequiredSource, StringRef ModuleName, const ThreadsafeFS &TFS,
CachingProjectModules &MDB, ReusablePrerequisiteModules &BuiltModuleFiles) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
return llvm::Error::success();
std::string ModuleUnitFileName =
MDB.getSourceForModuleName(ModuleName, RequiredSource);
/// It is possible that we're meeting third party modules (modules whose
/// source are not in the project. e.g, the std module may be a third-party
/// module for most project) or something wrong with the implementation of
/// ProjectModules.
/// FIXME: How should we treat third party modules here? If we want to ignore
/// third party modules, we should return true instead of false here.
/// Currently we simply bail out.
if (ModuleUnitFileName.empty())
return llvm::createStringError(
llvm::formatv("Don't get the module unit for module {0}", ModuleName));
/// Try to get prebuilt module files from the compilation database first. This
/// helps to avoid building the module files that are already built by the
/// compiler.
getPrebuiltModuleFile(ModuleName, ModuleUnitFileName, TFS, BuiltModuleFiles);
// Get Required modules in topological order.
auto ReqModuleNames = getAllRequiredModules(RequiredSource, MDB, ModuleName);
for (llvm::StringRef ReqModuleName : ReqModuleNames) {
if (BuiltModuleFiles.isModuleUnitBuilt(ReqModuleName))
continue;
std::string ReqFileName =
MDB.getSourceForModuleName(ReqModuleName, RequiredSource);
auto Cmd = getCDB().getCompileCommand(ReqFileName);
if (!Cmd)
return llvm::createStringError(
llvm::formatv("No compile command for {0}", ReqFileName));
if (auto PI = getCDB().getProjectInfo(ReqFileName);
PI && !PI->SourceRoot.empty())
garbageCollectModuleCacheForProjectRoot(PI->SourceRoot);
const std::string CommandHash = getCompileCommandStringHash(*Cmd);
const std::string PublishedModuleFilePath = getPublishedModuleFilePath(
ReqModuleName, getModuleFilesDirectory(ReqFileName, *Cmd, getCDB()));
// Keep the source-scoped lock while probing and validating cached BMIs so
// stale-file replacement and final publication stay serialized.
auto SourceLock = ScopedModuleSourceLock::acquire(ReqFileName, getCDB());
if (!SourceLock)
return SourceLock.takeError();
std::shared_ptr<const ModuleFile> Cached =
Cache.getModule(ReqModuleName, ReqFileName, CommandHash);
if (Cached) {
if (IsModuleFileUpToDate(Cached->getModuleFilePath(), BuiltModuleFiles,
TFS.view(std::nullopt))) {
log("Reusing module {0} from {1}", ReqModuleName,
Cached->getModuleFilePath());
BuiltModuleFiles.addModuleFile(std::move(Cached));
continue;
}
Cache.remove(ReqModuleName, ReqFileName, CommandHash);
}
if (llvm::sys::fs::exists(PublishedModuleFilePath)) {
if (IsModuleFileUpToDate(PublishedModuleFilePath, BuiltModuleFiles,
TFS.view(std::nullopt))) {
log("Reusing persistent module {0} from {1}", ReqModuleName,
PublishedModuleFilePath);
auto Materialized =
copyModuleFileForRead(ReqModuleName, PublishedModuleFilePath);
if (llvm::Error Err = Materialized.takeError())
return Err;
Cache.add(ReqModuleName, ReqFileName, CommandHash, *Materialized);
BuiltModuleFiles.addModuleFile(std::move(*Materialized));
continue;
}
// The persistent module file is stale. Remove it and build a new one.
std::error_code EC = llvm::sys::fs::remove(PublishedModuleFilePath);
if (EC)
return llvm::createStringError(
llvm::formatv("Failed to remove stale module file {0}: {1}",
PublishedModuleFilePath, EC.message()));
}
bool PublishedExistingModuleFile = false;
llvm::Expected<std::shared_ptr<BuiltModuleFile>> MF = buildModuleFile(
ReqModuleName, ReqFileName, std::move(*Cmd), PublishedModuleFilePath,
TFS, BuiltModuleFiles, PublishedExistingModuleFile);
if (llvm::Error Err = MF.takeError())
return Err;
if (PublishedExistingModuleFile &&
!IsModuleFileUpToDate(PublishedModuleFilePath, BuiltModuleFiles,
TFS.view(std::nullopt))) {
return llvm::createStringError(
llvm::formatv("Published module file {0} is stale after lock wait",
PublishedModuleFilePath));
}
auto Materialized =
copyModuleFileForRead(ReqModuleName, PublishedModuleFilePath);
if (llvm::Error Err = Materialized.takeError())
return Err;
log("Built module {0} to {1}", ReqModuleName,
(*Materialized)->getModuleFilePath());
Cache.add(ReqModuleName, ReqFileName, CommandHash, *Materialized);
BuiltModuleFiles.addModuleFile(std::move(*Materialized));
}
return llvm::Error::success();
}
bool ModulesBuilder::hasRequiredModules(PathRef File) {
std::unique_ptr<ProjectModules> MDB = Impl->getCDB().getProjectModules(File);
if (!MDB)
return false;
CachingProjectModules CachedMDB(std::move(MDB),
Impl->getProjectModulesCache());
return !CachedMDB.getRequiredModules(File).empty();
}
std::unique_ptr<PrerequisiteModules>
ModulesBuilder::buildPrerequisiteModulesFor(PathRef File,
const ThreadsafeFS &TFS) {
std::unique_ptr<ProjectModules> MDB = Impl->getCDB().getProjectModules(File);
if (!MDB) {
elog("Failed to get Project Modules information for {0}", File);
return std::make_unique<FailedPrerequisiteModules>();
}
CachingProjectModules CachedMDB(std::move(MDB),
Impl->getProjectModulesCache());
std::vector<std::string> RequiredModuleNames =
CachedMDB.getRequiredModules(File);
if (RequiredModuleNames.empty())
return std::make_unique<ReusablePrerequisiteModules>();
auto RequiredModules = std::make_unique<ReusablePrerequisiteModules>();
for (llvm::StringRef RequiredModuleName : RequiredModuleNames) {
// Return early if there is any error.
if (llvm::Error Err = Impl->getOrBuildModuleFile(
File, RequiredModuleName, TFS, CachedMDB, *RequiredModules.get())) {
elog("Failed to build module {0}; due to {1}", RequiredModuleName,
toString(std::move(Err)));
return std::make_unique<FailedPrerequisiteModules>();
}
}
return std::move(RequiredModules);
}
ModulesBuilder::ModulesBuilder(const GlobalCompilationDatabase &CDB) {
Impl = std::make_unique<ModulesBuilderImpl>(CDB);
}
ModulesBuilder::~ModulesBuilder() {}
} // namespace clangd
} // namespace clang