CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
631 lines
23 KiB
C++
631 lines
23 KiB
C++
//===------------------ ProjectModules.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 "ProjectModules.h"
|
|
#include "Compiler.h"
|
|
#include "support/Logger.h"
|
|
#include "clang/DependencyScanning/DependencyScanningService.h"
|
|
#include "clang/Tooling/DependencyScanningTool.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/TargetParser/Host.h"
|
|
|
|
namespace clang::clangd {
|
|
namespace {
|
|
|
|
llvm::SmallString<128> normalizePath(PathRef Path) {
|
|
llvm::SmallString<128> Result(Path);
|
|
llvm::sys::path::remove_dots(Result, /*remove_dot_dot=*/true);
|
|
llvm::sys::path::native(Result, llvm::sys::path::Style::posix);
|
|
return Result;
|
|
}
|
|
|
|
std::string normalizePath(PathRef Path, PathRef WorkingDir) {
|
|
if (Path.empty())
|
|
return {};
|
|
|
|
llvm::SmallString<128> Result;
|
|
if (llvm::sys::path::is_absolute(Path) || WorkingDir.empty())
|
|
Result = Path;
|
|
else {
|
|
Result = WorkingDir;
|
|
llvm::sys::path::append(Result, Path);
|
|
}
|
|
|
|
return normalizePath(Result).str().str();
|
|
}
|
|
|
|
/// The information related to modules parsed from compile commands.
|
|
/// Including the source file, the module file it produces (if it is a
|
|
/// producer), and the module and the corresponding module files it
|
|
/// requires (if it is a consumer)
|
|
struct ParsedCompileCommandInfo {
|
|
std::string SourceFile;
|
|
std::optional<std::string> OutputModuleFile;
|
|
// Map from required module name to the module file path.
|
|
llvm::StringMap<std::string> RequiredModuleFiles;
|
|
};
|
|
|
|
/// Get ParsedCompileCommandInfo by looking at the '--precompile',
|
|
/// '-fmodule-file=' and '-fmodule-file=' commands in the compile command.
|
|
std::optional<ParsedCompileCommandInfo>
|
|
parseCompileCommandInfo(tooling::CompileCommand Cmd, const ThreadsafeFS &TFS) {
|
|
auto FS = TFS.view(std::nullopt);
|
|
auto Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
|
|
? llvm::cl::TokenizeWindowsCommandLine
|
|
: llvm::cl::TokenizeGNUCommandLine;
|
|
tooling::addExpandedResponseFiles(Cmd.CommandLine, Cmd.Directory, Tokenizer,
|
|
*FS);
|
|
|
|
ParsedCompileCommandInfo Result;
|
|
Result.SourceFile = normalizePath(Cmd.Filename, Cmd.Directory);
|
|
|
|
bool SawPrecompile = false;
|
|
for (size_t I = 1; I < Cmd.CommandLine.size(); ++I) {
|
|
llvm::StringRef Arg = Cmd.CommandLine[I];
|
|
if (Arg == "--precompile") {
|
|
SawPrecompile = true;
|
|
continue;
|
|
}
|
|
|
|
if (Arg.consume_front("-fmodule-output=")) {
|
|
Result.OutputModuleFile = normalizePath(Arg, Cmd.Directory);
|
|
continue;
|
|
}
|
|
if (Arg == "-fmodule-output" && I + 1 < Cmd.CommandLine.size()) {
|
|
Result.OutputModuleFile =
|
|
normalizePath(Cmd.CommandLine[++I], Cmd.Directory);
|
|
continue;
|
|
}
|
|
if (SawPrecompile && Arg == "-o" && I + 1 < Cmd.CommandLine.size()) {
|
|
Result.OutputModuleFile =
|
|
normalizePath(Cmd.CommandLine[++I], Cmd.Directory);
|
|
continue;
|
|
}
|
|
if (SawPrecompile && Arg.starts_with("-o") && Arg.size() > 2) {
|
|
Result.OutputModuleFile = normalizePath(Arg.drop_front(2), Cmd.Directory);
|
|
continue;
|
|
}
|
|
|
|
if (!Arg.consume_front("-fmodule-file="))
|
|
continue;
|
|
|
|
auto Sep = Arg.find('=');
|
|
if (Sep == llvm::StringRef::npos || Sep == 0 || Sep + 1 == Arg.size())
|
|
continue;
|
|
|
|
Result.RequiredModuleFiles[Arg.take_front(Sep)] =
|
|
normalizePath(Arg.drop_front(Sep + 1), Cmd.Directory);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
std::optional<tooling::CompileCommand>
|
|
getCompileCommandForFile(const clang::tooling::CompilationDatabase &CDB,
|
|
PathRef FilePath,
|
|
const ProjectModules::CommandMangler &Mangler) {
|
|
auto Candidates = CDB.getCompileCommands(FilePath);
|
|
if (Candidates.empty())
|
|
return std::nullopt;
|
|
|
|
// Choose the first candidates as the compile commands as the file.
|
|
// Following the same logic with
|
|
// DirectoryBasedGlobalCompilationDatabase::getCompileCommand.
|
|
tooling::CompileCommand Cmd = std::move(Candidates.front());
|
|
|
|
if (Mangler)
|
|
Mangler(Cmd, FilePath);
|
|
|
|
return Cmd;
|
|
}
|
|
|
|
/// A scanner to query the dependency information for C++20 Modules.
|
|
///
|
|
/// The scanner can scan a single file with `scan(PathRef)` member function
|
|
/// or scan the whole project with `globalScan(vector<PathRef>)` member
|
|
/// function. See the comments of `globalScan` to see the details.
|
|
///
|
|
/// The ModuleDependencyScanner can get the directly required module names for a
|
|
/// specific source file. Also the ModuleDependencyScanner can get the source
|
|
/// file declaring the primary module interface for a specific module name.
|
|
///
|
|
/// IMPORTANT NOTE: we assume that every module unit is only declared once in a
|
|
/// source file in the project. But the assumption is not strictly true even
|
|
/// besides the invalid projects. The language specification requires that every
|
|
/// module unit should be unique in a valid program. But a project can contain
|
|
/// multiple programs. Then it is valid that we can have multiple source files
|
|
/// declaring the same module in a project as long as these source files don't
|
|
/// interfere with each other.
|
|
class ModuleDependencyScanner {
|
|
public:
|
|
ModuleDependencyScanner(
|
|
std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
|
|
const ThreadsafeFS &TFS)
|
|
: CDB(CDB), Service([&TFS] {
|
|
dependencies::DependencyScanningServiceOptions Opts;
|
|
Opts.MakeVFS = [&] { return TFS.view(std::nullopt); };
|
|
Opts.Mode = dependencies::ScanningMode::CanonicalPreprocessing;
|
|
Opts.EmitWarnings = false;
|
|
Opts.ReportAbsolutePaths = false;
|
|
return Opts;
|
|
}()) {}
|
|
|
|
/// The scanned modules dependency information for a specific source file.
|
|
struct ModuleDependencyInfo {
|
|
/// The name of the module if the file is a module unit.
|
|
std::optional<std::string> ModuleName;
|
|
/// A list of names for the modules that the file directly depends.
|
|
std::vector<std::string> RequiredModules;
|
|
};
|
|
|
|
/// Scanning the single file specified by \param FilePath.
|
|
std::optional<ModuleDependencyInfo>
|
|
scan(PathRef FilePath, const ProjectModules::CommandMangler &Mangler);
|
|
|
|
/// Scanning every source file in the current project to get the
|
|
/// <module-name> to <module-unit-source> map.
|
|
/// TODO: We should find an efficient method to get the <module-name>
|
|
/// to <module-unit-source> map. We can make it either by providing
|
|
/// a global module dependency scanner to monitor every file. Or we
|
|
/// can simply require the build systems (or even the end users)
|
|
/// to provide the map.
|
|
void globalScan(const ProjectModules::CommandMangler &Mangler);
|
|
|
|
/// Get the source file from the module name. Note that the language
|
|
/// guarantees all the module names are unique in a valid program.
|
|
/// This function should only be called after globalScan.
|
|
///
|
|
/// TODO: We should handle the case that there are multiple source files
|
|
/// declaring the same module.
|
|
PathRef getSourceForModuleName(llvm::StringRef ModuleName) const;
|
|
|
|
/// Return the direct required modules. Indirect required modules are not
|
|
/// included.
|
|
std::vector<std::string>
|
|
getRequiredModules(PathRef File,
|
|
const ProjectModules::CommandMangler &Mangler);
|
|
|
|
private:
|
|
std::shared_ptr<const clang::tooling::CompilationDatabase> CDB;
|
|
|
|
// Whether the scanner has scanned the project globally.
|
|
bool GlobalScanned = false;
|
|
|
|
clang::dependencies::DependencyScanningService Service;
|
|
|
|
// TODO: Add a scanning cache.
|
|
|
|
// Map module name to source file path.
|
|
llvm::StringMap<std::string> ModuleNameToSource;
|
|
};
|
|
|
|
std::optional<ModuleDependencyScanner::ModuleDependencyInfo>
|
|
ModuleDependencyScanner::scan(PathRef FilePath,
|
|
const ProjectModules::CommandMangler &Mangler) {
|
|
auto Cmd = getCompileCommandForFile(*CDB, FilePath, Mangler);
|
|
if (!Cmd)
|
|
return std::nullopt;
|
|
|
|
using namespace clang::tooling;
|
|
|
|
DependencyScanningTool ScanningTool(Service);
|
|
|
|
std::string S;
|
|
llvm::raw_string_ostream OS(S);
|
|
DiagnosticOptions DiagOpts;
|
|
DiagOpts.ShowCarets = false;
|
|
TextDiagnosticPrinter DiagConsumer(OS, DiagOpts);
|
|
|
|
std::optional<P1689Rule> ScanningResult =
|
|
ScanningTool.getP1689ModuleDependencyFile(*Cmd, Cmd->Directory,
|
|
DiagConsumer);
|
|
|
|
if (!ScanningResult) {
|
|
elog("Scanning modules dependencies for {0} failed: {1}", FilePath, S);
|
|
std::string Cmdline;
|
|
for (auto &Arg : Cmd->CommandLine)
|
|
Cmdline += Arg + " ";
|
|
elog("The command line the scanning tool use is: {0}", Cmdline);
|
|
return std::nullopt;
|
|
}
|
|
|
|
ModuleDependencyInfo Result;
|
|
|
|
if (ScanningResult->Provides) {
|
|
Result.ModuleName = ScanningResult->Provides->ModuleName;
|
|
|
|
auto [Iter, Inserted] = ModuleNameToSource.try_emplace(
|
|
ScanningResult->Provides->ModuleName, FilePath);
|
|
|
|
if (!Inserted &&
|
|
!pathEqual(normalizePath(Iter->second), normalizePath(FilePath))) {
|
|
elog("Detected multiple source files ({0}, {1}) declaring the same "
|
|
"module: '{2}'. "
|
|
"Now clangd may find the wrong source in such case.",
|
|
Iter->second, FilePath, ScanningResult->Provides->ModuleName);
|
|
}
|
|
}
|
|
|
|
for (auto &Required : ScanningResult->Requires)
|
|
Result.RequiredModules.push_back(Required.ModuleName);
|
|
|
|
return Result;
|
|
}
|
|
|
|
void ModuleDependencyScanner::globalScan(
|
|
const ProjectModules::CommandMangler &Mangler) {
|
|
if (GlobalScanned)
|
|
return;
|
|
|
|
for (auto &File : CDB->getAllFiles())
|
|
scan(File, Mangler);
|
|
|
|
GlobalScanned = true;
|
|
}
|
|
|
|
PathRef ModuleDependencyScanner::getSourceForModuleName(
|
|
llvm::StringRef ModuleName) const {
|
|
assert(
|
|
GlobalScanned &&
|
|
"We should only call getSourceForModuleName after calling globalScan()");
|
|
|
|
if (auto It = ModuleNameToSource.find(ModuleName);
|
|
It != ModuleNameToSource.end())
|
|
return It->second;
|
|
|
|
return {};
|
|
}
|
|
|
|
std::vector<std::string> ModuleDependencyScanner::getRequiredModules(
|
|
PathRef File, const ProjectModules::CommandMangler &Mangler) {
|
|
auto ScanningResult = scan(File, Mangler);
|
|
if (!ScanningResult)
|
|
return {};
|
|
|
|
return ScanningResult->RequiredModules;
|
|
}
|
|
} // namespace
|
|
|
|
/// TODO: The existing `ScanningAllProjectModules` is not efficient. See the
|
|
/// comments in ModuleDependencyScanner for detail.
|
|
///
|
|
/// In the future, we wish the build system can provide a well design
|
|
/// compilation database for modules then we can query that new compilation
|
|
/// database directly. Or we need to have a global long-live scanner to detect
|
|
/// the state of each file.
|
|
class ScanningAllProjectModules : public ProjectModules {
|
|
public:
|
|
ScanningAllProjectModules(
|
|
std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
|
|
const ThreadsafeFS &TFS)
|
|
: Scanner(CDB, TFS) {}
|
|
|
|
~ScanningAllProjectModules() override = default;
|
|
|
|
std::vector<std::string> getRequiredModules(PathRef File) override {
|
|
return Scanner.getRequiredModules(File, Mangler);
|
|
}
|
|
|
|
void setCommandMangler(CommandMangler Mangler) override {
|
|
this->Mangler = std::move(Mangler);
|
|
}
|
|
|
|
/// RequiredSourceFile is not used intentionally. See the comments of
|
|
/// ModuleDependencyScanner for detail.
|
|
std::string getSourceForModuleName(llvm::StringRef ModuleName,
|
|
PathRef RequiredSourceFile) override {
|
|
Scanner.globalScan(Mangler);
|
|
return Scanner.getSourceForModuleName(ModuleName).str();
|
|
}
|
|
|
|
std::string getModuleNameForSource(PathRef File) override {
|
|
auto ScanningResult = Scanner.scan(File, Mangler);
|
|
if (!ScanningResult || !ScanningResult->ModuleName)
|
|
return {};
|
|
|
|
return *ScanningResult->ModuleName;
|
|
}
|
|
|
|
// Determining Unique/Multiple needs a global scan; return Unknown for cost
|
|
// reasons. We will have other ProjectModules implementations can determine
|
|
// this more efficiently.
|
|
ModuleNameState getModuleNameState(llvm::StringRef /*ModuleName*/) override {
|
|
return ModuleNameState::Unknown;
|
|
}
|
|
|
|
private:
|
|
ModuleDependencyScanner Scanner;
|
|
CommandMangler Mangler;
|
|
};
|
|
|
|
/// Reads project module information directly from compile commands.
|
|
///
|
|
/// The key observation is that compile commands may already encode the mapping
|
|
/// between a TU, the module names it imports, and the BMI paths it uses:
|
|
/// - producers may spell the BMI path with `--precompile -o <bmi>` or
|
|
/// `-fmodule-output=<bmi>`
|
|
/// - consumers may spell the mapping from module name to BMI path with
|
|
/// `-fmodule-file=<module>=<bmi>`
|
|
///
|
|
/// When that information is present, we can answer
|
|
/// `getSourceForModuleName(ModuleName, RequiredSourceFile)` by first looking up
|
|
/// the BMI path the consumer TU uses for `ModuleName`, and then mapping that
|
|
/// BMI path back to the module unit source that produced it. This avoids the
|
|
/// older scanning-only approach of guessing the module unit from the module
|
|
/// name alone.
|
|
///
|
|
/// One subtle point is that producer commands alone do not reliably tell us the
|
|
/// module name associated with a BMI path. In practice this backend learns that
|
|
/// association from consumer `-fmodule-file=` entries, and then uses the BMI
|
|
/// path to recover the producer source file. That is why indexing is built from
|
|
/// both producer and consumer commands.
|
|
///
|
|
/// Note that compilation database can be stale, so results from this backend
|
|
/// should be treated as preferred hints rather than unquestionable truth.
|
|
/// The compound layer below validates or falls back when needed.
|
|
class CompileCommandsProjectModules : public ProjectModules {
|
|
public:
|
|
CompileCommandsProjectModules(
|
|
std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
|
|
const ThreadsafeFS &TFS)
|
|
: CDB(std::move(CDB)), TFS(TFS) {}
|
|
|
|
std::vector<std::string> getRequiredModules(PathRef File) override {
|
|
auto Parsed = parseFileCommand(File);
|
|
if (!Parsed)
|
|
return {};
|
|
|
|
std::vector<std::string> Result;
|
|
Result.reserve(Parsed->RequiredModuleFiles.size());
|
|
for (const auto &Required : Parsed->RequiredModuleFiles)
|
|
Result.push_back(Required.getKey().str());
|
|
return Result;
|
|
}
|
|
|
|
std::string getModuleNameForSource(PathRef File) override {
|
|
indexProducerCommands();
|
|
auto It = SourceToModuleName.find(
|
|
maybeCaseFoldPath(normalizePath(File, /*WorkingDir=*/{})));
|
|
if (It == SourceToModuleName.end() || It->second.Ambiguous)
|
|
return {};
|
|
return It->second.Name;
|
|
}
|
|
|
|
ModuleNameState getModuleNameState(llvm::StringRef ModuleName) override {
|
|
indexProducerCommands();
|
|
auto It = ModuleNameToDistinctSources.find(ModuleName);
|
|
if (It == ModuleNameToDistinctSources.end())
|
|
return ModuleNameState::Unknown;
|
|
return It->second.size() > 1 ? ModuleNameState::Multiple
|
|
: ModuleNameState::Unique;
|
|
}
|
|
|
|
std::string getSourceForModuleName(llvm::StringRef ModuleName,
|
|
PathRef RequiredSourceFile) override {
|
|
auto Parsed = parseFileCommand(RequiredSourceFile);
|
|
if (!Parsed)
|
|
return {};
|
|
|
|
auto It = Parsed->RequiredModuleFiles.find(ModuleName);
|
|
if (It == Parsed->RequiredModuleFiles.end())
|
|
return {};
|
|
|
|
indexProducerCommands();
|
|
auto SourceIt = PCMToSource.find(maybeCaseFoldPath(It->second));
|
|
if (SourceIt == PCMToSource.end())
|
|
return {};
|
|
|
|
return SourceIt->second;
|
|
}
|
|
|
|
void setCommandMangler(CommandMangler Mangler) override {
|
|
this->Mangler = std::move(Mangler);
|
|
ProducerCommandsIndexed = false;
|
|
PCMToSource.clear();
|
|
ModuleNameToDistinctSources.clear();
|
|
SourceToModuleName.clear();
|
|
}
|
|
|
|
private:
|
|
/// Parses the compile command for \p File into the module information
|
|
/// encoded in the command line.
|
|
std::optional<ParsedCompileCommandInfo> parseFileCommand(PathRef File) const {
|
|
auto Cmd = getCompileCommandForFile(*CDB, File, Mangler);
|
|
if (!Cmd)
|
|
return std::nullopt;
|
|
return parseCompileCommandInfo(std::move(*Cmd), TFS);
|
|
}
|
|
|
|
/// Builds indexes from producer and consumer compile commands.
|
|
///
|
|
/// Compile commands are parsed once up front. The first pass records which
|
|
/// source file produces each BMI path. The second pass walks consumer
|
|
/// commands, uses `-fmodule-file=` information to associate module names with
|
|
/// those BMI paths, and then records which producer source files are
|
|
/// referenced for each module name.
|
|
void indexProducerCommands() {
|
|
if (ProducerCommandsIndexed)
|
|
return;
|
|
|
|
std::vector<ParsedCompileCommandInfo> ParsedCommands;
|
|
auto AllFiles = CDB->getAllFiles();
|
|
ParsedCommands.reserve(AllFiles.size());
|
|
for (const auto &File : AllFiles) {
|
|
auto Parsed = parseFileCommand(File);
|
|
if (!Parsed)
|
|
continue;
|
|
|
|
if (Parsed->OutputModuleFile)
|
|
PCMToSource[maybeCaseFoldPath(*Parsed->OutputModuleFile)] =
|
|
Parsed->SourceFile;
|
|
|
|
ParsedCommands.push_back(std::move(*Parsed));
|
|
}
|
|
|
|
for (const auto &Parsed : ParsedCommands) {
|
|
for (const auto &Required : Parsed.RequiredModuleFiles) {
|
|
auto SourceIt =
|
|
PCMToSource.find(maybeCaseFoldPath(Required.getValue()));
|
|
if (SourceIt == PCMToSource.end())
|
|
continue;
|
|
ModuleNameToDistinctSources[Required.getKey()].insert(
|
|
maybeCaseFoldPath(SourceIt->second));
|
|
|
|
auto &Recovered =
|
|
SourceToModuleName[maybeCaseFoldPath(SourceIt->second)];
|
|
if (Recovered.Name.empty())
|
|
Recovered.Name = Required.getKey().str();
|
|
else if (Recovered.Name != Required.getKey()) {
|
|
if (!Recovered.Ambiguous) {
|
|
elog("Detected conflicting module names ('{0}' and '{1}') for "
|
|
"the same module file {2} produced by source {3}",
|
|
Recovered.Name, Required.getKey(), Required.getValue(),
|
|
SourceIt->second);
|
|
}
|
|
Recovered.Ambiguous = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
ProducerCommandsIndexed = true;
|
|
}
|
|
|
|
std::shared_ptr<const clang::tooling::CompilationDatabase> CDB;
|
|
const ThreadsafeFS &TFS;
|
|
CommandMangler Mangler;
|
|
bool ProducerCommandsIndexed = false;
|
|
|
|
llvm::StringMap<std::string> PCMToSource;
|
|
|
|
using DistinctSourceSet = llvm::StringSet<>;
|
|
llvm::StringMap<DistinctSourceSet> ModuleNameToDistinctSources;
|
|
|
|
struct RecoveredModuleName {
|
|
std::string Name;
|
|
bool Ambiguous = false;
|
|
};
|
|
llvm::StringMap<RecoveredModuleName> SourceToModuleName;
|
|
};
|
|
|
|
/// Combines the compile-commands backend with the scanning backend.
|
|
///
|
|
/// For getSourceForModuleName, it prefers compile-command-derived results when
|
|
/// available to avoid scanning the whole project, but validates them against
|
|
/// scanning results to avoid returning stale information. For other queries,
|
|
/// it returns scanning results directly as scanning information is update to
|
|
/// date.
|
|
class CompoundProjectModules : public ProjectModules {
|
|
public:
|
|
CompoundProjectModules(
|
|
std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
|
|
const ThreadsafeFS &TFS)
|
|
: CompileCommands(
|
|
std::make_unique<CompileCommandsProjectModules>(CDB, TFS)),
|
|
Scanning(
|
|
std::make_unique<ScanningAllProjectModules>(std::move(CDB), TFS)) {}
|
|
|
|
std::vector<std::string> getRequiredModules(PathRef File) override {
|
|
// Return scanning results directly as it is fast enough and up to date.
|
|
return Scanning->getRequiredModules(File);
|
|
}
|
|
|
|
std::string getModuleNameForSource(PathRef File) override {
|
|
// Return scanning results directly as it is fast enough and up to date.
|
|
return Scanning->getModuleNameForSource(File);
|
|
}
|
|
|
|
std::string getSourceForModuleName(llvm::StringRef ModuleName,
|
|
PathRef RequiredSourceFile) override {
|
|
auto FromCompileCommands =
|
|
CompileCommands->getSourceForModuleName(ModuleName, RequiredSourceFile);
|
|
// Check if the source still declares the module.
|
|
// This is to validate compile-command-derived results may be stale and
|
|
// scan a single file is fast enough. We just don't want to scan the project
|
|
// entirely.
|
|
if (!FromCompileCommands.empty() &&
|
|
Scanning->getModuleNameForSource(FromCompileCommands) == ModuleName)
|
|
return FromCompileCommands;
|
|
|
|
return Scanning->getSourceForModuleName(ModuleName, RequiredSourceFile);
|
|
}
|
|
|
|
ModuleNameState getModuleNameState(llvm::StringRef ModuleName) override {
|
|
auto FromCompileCommands = CompileCommands->getModuleNameState(ModuleName);
|
|
if (FromCompileCommands != ModuleNameState::Unknown)
|
|
return FromCompileCommands;
|
|
return Scanning->getModuleNameState(ModuleName);
|
|
}
|
|
|
|
void setCommandMangler(CommandMangler Mangler) override {
|
|
this->Mangler = std::move(Mangler);
|
|
auto ForwardMangler = [this](tooling::CompileCommand &Command,
|
|
PathRef CommandPath) {
|
|
if (this->Mangler)
|
|
this->Mangler(Command, CommandPath);
|
|
};
|
|
CompileCommands->setCommandMangler(ForwardMangler);
|
|
Scanning->setCommandMangler(std::move(ForwardMangler));
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<CompileCommandsProjectModules> CompileCommands;
|
|
std::unique_ptr<ScanningAllProjectModules> Scanning;
|
|
CommandMangler Mangler;
|
|
};
|
|
|
|
/// Creates the project-modules facade used by clangd.
|
|
///
|
|
/// The implementation is intentionally layered:
|
|
///
|
|
/// CompoundProjectModules
|
|
/// / \
|
|
/// v v
|
|
/// CompileCommands ScanningAllProjectModules
|
|
/// ProjectModules |
|
|
/// | v
|
|
/// | ModuleDependencyScanner
|
|
/// |
|
|
/// +-- preferred specifically for recovering the source file for a module
|
|
/// | name in the context of a consumer TU, because compile commands
|
|
/// | encode `module name -> BMI -> producer source`
|
|
/// |
|
|
/// +-- scanning remains fallback/validation for stale or missing data
|
|
///
|
|
/// - `CompileCommandsProjectModules` reads module relationships that the build
|
|
/// system already made explicit in compile commands. In particular, it uses
|
|
/// producer-side BMI output paths together with consumer-side
|
|
/// `-fmodule-file=<module>=<bmi>` entries to recover the module unit source a
|
|
/// TU actually depends on. This is the preferred source because it can
|
|
/// distinguish different module producers for the same module name when
|
|
/// different translation units reference different BMIs.
|
|
/// - `ScanningAllProjectModules` derives module information by scanning source
|
|
/// files. It is more expensive, but it can still answer queries that are not
|
|
/// present in compile commands and validate compile-command-derived results.
|
|
/// - `CompoundProjectModules` arbitrates between the two backends on a
|
|
/// per-query basis. Compile commands are especially valuable for
|
|
/// `getSourceForModuleName()` because they preserve the consumer TU's actual
|
|
/// `module name -> BMI` choice. Other queries may still fall back to, or be
|
|
/// validated by, scanning because compile-command information may be
|
|
/// incomplete or stale.
|
|
///
|
|
/// This split keeps the logic simple: compile commands provide precision when
|
|
/// available, while scanning preserves compatibility with projects that have
|
|
/// incomplete module information in their compilation database.
|
|
std::unique_ptr<ProjectModules> getProjectModules(
|
|
std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
|
|
const ThreadsafeFS &TFS) {
|
|
return std::make_unique<CompoundProjectModules>(std::move(CDB), TFS);
|
|
}
|
|
|
|
} // namespace clang::clangd
|