12#include "clang/DependencyScanning/DependencyScanningService.h"
13#include "clang/Frontend/TextDiagnosticPrinter.h"
14#include "clang/Tooling/DependencyScanningTool.h"
15#include "clang/Tooling/Tooling.h"
16#include "llvm/ADT/SmallString.h"
17#include "llvm/ADT/StringMap.h"
18#include "llvm/ADT/StringSet.h"
19#include "llvm/Support/CommandLine.h"
20#include "llvm/Support/Path.h"
21#include "llvm/TargetParser/Host.h"
27 llvm::SmallString<128> Result(
Path);
28 llvm::sys::path::remove_dots(Result,
true);
29 llvm::sys::path::native(Result, llvm::sys::path::Style::posix);
37 llvm::SmallString<128> Result;
38 if (llvm::sys::path::is_absolute(
Path) || WorkingDir.empty())
42 llvm::sys::path::append(Result,
Path);
45 return normalizePath(Result).str().str();
52struct ParsedCompileCommandInfo {
53 std::string SourceFile;
54 std::optional<std::string> OutputModuleFile;
56 llvm::StringMap<std::string> RequiredModuleFiles;
61std::optional<ParsedCompileCommandInfo>
62parseCompileCommandInfo(tooling::CompileCommand Cmd,
const ThreadsafeFS &TFS) {
63 auto FS = TFS.view(std::nullopt);
64 auto Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
65 ? llvm::cl::TokenizeWindowsCommandLine
66 : llvm::cl::TokenizeGNUCommandLine;
67 tooling::addExpandedResponseFiles(Cmd.CommandLine, Cmd.Directory, Tokenizer,
70 ParsedCompileCommandInfo Result;
71 Result.SourceFile = normalizePath(Cmd.Filename, Cmd.Directory);
73 bool SawPrecompile =
false;
74 for (
size_t I = 1; I < Cmd.CommandLine.size(); ++I) {
75 llvm::StringRef Arg = Cmd.CommandLine[I];
76 if (Arg ==
"--precompile") {
81 if (Arg.consume_front(
"-fmodule-output=")) {
82 Result.OutputModuleFile = normalizePath(Arg, Cmd.Directory);
85 if (Arg ==
"-fmodule-output" && I + 1 < Cmd.CommandLine.size()) {
86 Result.OutputModuleFile =
87 normalizePath(Cmd.CommandLine[++I], Cmd.Directory);
90 if (SawPrecompile && Arg ==
"-o" && I + 1 < Cmd.CommandLine.size()) {
91 Result.OutputModuleFile =
92 normalizePath(Cmd.CommandLine[++I], Cmd.Directory);
95 if (SawPrecompile && Arg.starts_with(
"-o") && Arg.size() > 2) {
96 Result.OutputModuleFile = normalizePath(Arg.drop_front(2), Cmd.Directory);
100 if (!Arg.consume_front(
"-fmodule-file="))
103 auto Sep = Arg.find(
'=');
104 if (Sep == llvm::StringRef::npos || Sep == 0 || Sep + 1 == Arg.size())
107 Result.RequiredModuleFiles[Arg.take_front(Sep)] =
108 normalizePath(Arg.drop_front(Sep + 1), Cmd.Directory);
114std::optional<tooling::CompileCommand>
115getCompileCommandForFile(
const clang::tooling::CompilationDatabase &CDB,
118 auto Candidates = CDB.getCompileCommands(FilePath);
119 if (Candidates.empty())
125 tooling::CompileCommand Cmd = std::move(Candidates.front());
128 Mangler(Cmd, FilePath);
150class ModuleDependencyScanner {
152 ModuleDependencyScanner(
153 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
154 const ThreadsafeFS &TFS)
155 : CDB(CDB), Service([&TFS] {
156 dependencies::DependencyScanningServiceOptions Opts;
157 Opts.MakeVFS = [&] {
return TFS.view(std::nullopt); };
158 Opts.Mode = dependencies::ScanningMode::CanonicalPreprocessing;
159 Opts.EmitWarnings =
false;
160 Opts.ReportAbsolutePaths =
false;
165 struct ModuleDependencyInfo {
167 std::optional<std::string> ModuleName;
169 std::vector<std::string> RequiredModules;
173 std::optional<ModuleDependencyInfo>
191 PathRef getSourceForModuleName(llvm::StringRef ModuleName)
const;
195 std::vector<std::string>
200 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB;
203 bool GlobalScanned =
false;
205 clang::dependencies::DependencyScanningService Service;
210 llvm::StringMap<std::string> ModuleNameToSource;
213std::optional<ModuleDependencyScanner::ModuleDependencyInfo>
214ModuleDependencyScanner::scan(
PathRef FilePath,
216 auto Cmd = getCompileCommandForFile(*CDB, FilePath, Mangler);
220 using namespace clang::tooling;
222 DependencyScanningTool ScanningTool(Service);
225 llvm::raw_string_ostream OS(S);
226 DiagnosticOptions DiagOpts;
227 DiagOpts.ShowCarets =
false;
228 TextDiagnosticPrinter DiagConsumer(OS, DiagOpts);
230 std::optional<P1689Rule> ScanningResult =
231 ScanningTool.getP1689ModuleDependencyFile(*Cmd, Cmd->Directory,
234 if (!ScanningResult) {
235 elog(
"Scanning modules dependencies for {0} failed: {1}", FilePath, S);
237 for (
auto &Arg : Cmd->CommandLine)
238 Cmdline += Arg +
" ";
239 elog(
"The command line the scanning tool use is: {0}", Cmdline);
243 ModuleDependencyInfo Result;
245 if (ScanningResult->Provides) {
246 Result.ModuleName = ScanningResult->Provides->ModuleName;
248 auto [Iter, Inserted] = ModuleNameToSource.try_emplace(
249 ScanningResult->Provides->ModuleName, FilePath);
252 !
pathEqual(normalizePath(Iter->second), normalizePath(FilePath))) {
253 elog(
"Detected multiple source files ({0}, {1}) declaring the same "
255 "Now clangd may find the wrong source in such case.",
256 Iter->second, FilePath, ScanningResult->Provides->ModuleName);
260 for (
auto &Required : ScanningResult->Requires)
261 Result.RequiredModules.push_back(Required.ModuleName);
266void ModuleDependencyScanner::globalScan(
271 for (
auto &File : CDB->getAllFiles())
274 GlobalScanned =
true;
277PathRef ModuleDependencyScanner::getSourceForModuleName(
278 llvm::StringRef ModuleName)
const {
281 "We should only call getSourceForModuleName after calling globalScan()");
283 if (
auto It = ModuleNameToSource.find(ModuleName);
284 It != ModuleNameToSource.end())
290std::vector<std::string> ModuleDependencyScanner::getRequiredModules(
292 auto ScanningResult = scan(File, Mangler);
296 return ScanningResult->RequiredModules;
310 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
312 : Scanner(CDB, TFS) {}
317 return Scanner.getRequiredModules(
File, Mangler);
321 this->Mangler = std::move(Mangler);
327 PathRef RequiredSourceFile)
override {
328 Scanner.globalScan(Mangler);
329 return Scanner.getSourceForModuleName(ModuleName).str();
333 auto ScanningResult = Scanner.scan(
File, Mangler);
334 if (!ScanningResult || !ScanningResult->ModuleName)
337 return *ScanningResult->ModuleName;
348 ModuleDependencyScanner Scanner;
380 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
382 : CDB(std::
move(CDB)), TFS(TFS) {}
385 auto Parsed = parseFileCommand(
File);
389 std::vector<std::string> Result;
390 Result.reserve(Parsed->RequiredModuleFiles.size());
391 for (
const auto &Required : Parsed->RequiredModuleFiles)
392 Result.push_back(Required.getKey().str());
397 indexProducerCommands();
398 auto It = SourceToModuleName.find(
400 if (It == SourceToModuleName.end() || It->second.Ambiguous)
402 return It->second.Name;
406 indexProducerCommands();
407 auto It = ModuleNameToDistinctSources.find(ModuleName);
408 if (It == ModuleNameToDistinctSources.end())
415 PathRef RequiredSourceFile)
override {
416 auto Parsed = parseFileCommand(RequiredSourceFile);
420 auto It = Parsed->RequiredModuleFiles.find(ModuleName);
421 if (It == Parsed->RequiredModuleFiles.end())
424 indexProducerCommands();
426 if (SourceIt == PCMToSource.end())
429 return SourceIt->second;
433 this->Mangler = std::move(Mangler);
434 ProducerCommandsIndexed =
false;
436 ModuleNameToDistinctSources.clear();
437 SourceToModuleName.clear();
443 std::optional<ParsedCompileCommandInfo> parseFileCommand(
PathRef File)
const {
444 auto Cmd = getCompileCommandForFile(*CDB,
File, Mangler);
447 return parseCompileCommandInfo(std::move(*Cmd), TFS);
457 void indexProducerCommands() {
458 if (ProducerCommandsIndexed)
461 std::vector<ParsedCompileCommandInfo> ParsedCommands;
462 auto AllFiles = CDB->getAllFiles();
463 ParsedCommands.reserve(AllFiles.size());
464 for (
const auto &File : AllFiles) {
465 auto Parsed = parseFileCommand(File);
469 if (Parsed->OutputModuleFile)
470 PCMToSource[maybeCaseFoldPath(*Parsed->OutputModuleFile)] =
473 ParsedCommands.push_back(std::move(*Parsed));
476 for (
const auto &Parsed : ParsedCommands) {
477 for (
const auto &Required : Parsed.RequiredModuleFiles) {
479 PCMToSource.find(maybeCaseFoldPath(Required.getValue()));
480 if (SourceIt == PCMToSource.end())
482 ModuleNameToDistinctSources[Required.getKey()].insert(
483 maybeCaseFoldPath(SourceIt->second));
486 SourceToModuleName[maybeCaseFoldPath(SourceIt->second)];
487 if (Recovered.Name.empty())
488 Recovered.Name = Required.getKey().str();
489 else if (Recovered.Name != Required.getKey()) {
490 if (!Recovered.Ambiguous) {
491 elog(
"Detected conflicting module names ('{0}' and '{1}') for "
492 "the same module file {2} produced by source {3}",
493 Recovered.Name, Required.getKey(), Required.getValue(),
496 Recovered.Ambiguous =
true;
501 ProducerCommandsIndexed =
true;
504 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB;
505 const ThreadsafeFS &TFS;
506 CommandMangler Mangler;
507 bool ProducerCommandsIndexed =
false;
509 llvm::StringMap<std::string> PCMToSource;
511 using DistinctSourceSet = llvm::StringSet<>;
512 llvm::StringMap<DistinctSourceSet> ModuleNameToDistinctSources;
514 struct RecoveredModuleName {
516 bool Ambiguous =
false;
518 llvm::StringMap<RecoveredModuleName> SourceToModuleName;
531 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
540 return Scanning->getRequiredModules(
File);
545 return Scanning->getModuleNameForSource(
File);
549 PathRef RequiredSourceFile)
override {
550 auto FromCompileCommands =
551 CompileCommands->getSourceForModuleName(ModuleName, RequiredSourceFile);
556 if (!FromCompileCommands.empty() &&
557 Scanning->getModuleNameForSource(FromCompileCommands) == ModuleName)
558 return FromCompileCommands;
560 return Scanning->getSourceForModuleName(ModuleName, RequiredSourceFile);
564 auto FromCompileCommands = CompileCommands->getModuleNameState(ModuleName);
566 return FromCompileCommands;
567 return Scanning->getModuleNameState(ModuleName);
571 this->Mangler = std::move(Mangler);
572 auto ForwardMangler = [
this](tooling::CompileCommand &Command,
575 this->Mangler(Command, CommandPath);
577 CompileCommands->setCommandMangler(ForwardMangler);
578 Scanning->setCommandMangler(std::move(ForwardMangler));
582 std::unique_ptr<CompileCommandsProjectModules> CompileCommands;
583 std::unique_ptr<ScanningAllProjectModules> Scanning;
626 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
628 return std::make_unique<CompoundProjectModules>(std::move(CDB), TFS);
void elog(const char *Fmt, Ts &&... Vals)
Reads project module information directly from compile commands.
CompileCommandsProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
std::string getModuleNameForSource(PathRef File) override
void setCommandMangler(CommandMangler Mangler) override
std::string getSourceForModuleName(llvm::StringRef ModuleName, PathRef RequiredSourceFile) override
ModuleNameState getModuleNameState(llvm::StringRef ModuleName) override
std::vector< std::string > getRequiredModules(PathRef File) override
std::vector< std::string > getRequiredModules(PathRef File) override
ModuleNameState getModuleNameState(llvm::StringRef ModuleName) override
std::string getModuleNameForSource(PathRef File) override
void setCommandMangler(CommandMangler Mangler) override
std::string getSourceForModuleName(llvm::StringRef ModuleName, PathRef RequiredSourceFile) override
CompoundProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
An interface to query the modules information in the project.
llvm::unique_function< void(tooling::CompileCommand &, PathRef) const > CommandMangler
TODO: The existing ScanningAllProjectModules is not efficient.
void setCommandMangler(CommandMangler Mangler) override
ScanningAllProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
std::string getModuleNameForSource(PathRef File) override
std::vector< std::string > getRequiredModules(PathRef File) override
ModuleNameState getModuleNameState(llvm::StringRef) override
std::string getSourceForModuleName(llvm::StringRef ModuleName, PathRef RequiredSourceFile) override
RequiredSourceFile is not used intentionally.
~ScanningAllProjectModules() override=default
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
std::string maybeCaseFoldPath(PathRef Path)
bool pathEqual(PathRef A, PathRef B)
llvm::StringRef PathRef
A typedef to represent a ref to file path.
std::string Path
A typedef to represent a file path.
std::unique_ptr< ProjectModules > getProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
Creates the project-modules facade used by clangd.