1//===------------------ ProjectModules.h -------------------------*- C++-*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "ProjectModules.h"
10#include "support/Logger.h"
11#include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
12#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
13
14namespace clang::clangd {
15namespace {
16/// A scanner to query the dependency information for C++20 Modules.
17///
18/// The scanner can scan a single file with `scan(PathRef)` member function
19/// or scan the whole project with `globalScan(vector<PathRef>)` member
20/// function. See the comments of `globalScan` to see the details.
21///
22/// The ModuleDependencyScanner can get the directly required module names for a
23/// specific source file. Also the ModuleDependencyScanner can get the source
24/// file declaring the primary module interface for a specific module name.
25///
26/// IMPORTANT NOTE: we assume that every module unit is only declared once in a
27/// source file in the project. But the assumption is not strictly true even
28/// besides the invalid projects. The language specification requires that every
29/// module unit should be unique in a valid program. But a project can contain
30/// multiple programs. Then it is valid that we can have multiple source files
31/// declaring the same module in a project as long as these source files don't
32/// interfere with each other.
33class ModuleDependencyScanner {
34public:
35 ModuleDependencyScanner(
36 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
37 const ThreadsafeFS &TFS)
38 : CDB(CDB), TFS(TFS),
39 Service(tooling::dependencies::ScanningMode::CanonicalPreprocessing,
40 tooling::dependencies::ScanningOutputFormat::P1689) {}
41
42 /// The scanned modules dependency information for a specific source file.
43 struct ModuleDependencyInfo {
44 /// The name of the module if the file is a module unit.
45 std::optional<std::string> ModuleName;
46 /// A list of names for the modules that the file directly depends.
47 std::vector<std::string> RequiredModules;
48 };
49
50 /// Scanning the single file specified by \param FilePath.
51 std::optional<ModuleDependencyInfo>
52 scan(PathRef FilePath, const ProjectModules::CommandMangler &Mangler);
53
54 /// Scanning every source file in the current project to get the
55 /// <module-name> to <module-unit-source> map.
56 /// TODO: We should find an efficient method to get the <module-name>
57 /// to <module-unit-source> map. We can make it either by providing
58 /// a global module dependency scanner to monitor every file. Or we
59 /// can simply require the build systems (or even the end users)
60 /// to provide the map.
61 void globalScan(const ProjectModules::CommandMangler &Mangler);
62
63 /// Get the source file from the module name. Note that the language
64 /// guarantees all the module names are unique in a valid program.
65 /// This function should only be called after globalScan.
66 ///
67 /// TODO: We should handle the case that there are multiple source files
68 /// declaring the same module.
69 PathRef getSourceForModuleName(llvm::StringRef ModuleName) const;
70
71 /// Return the direct required modules. Indirect required modules are not
72 /// included.
73 std::vector<std::string>
74 getRequiredModules(PathRef File,
75 const ProjectModules::CommandMangler &Mangler);
76
77private:
78 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB;
79 const ThreadsafeFS &TFS;
80
81 // Whether the scanner has scanned the project globally.
82 bool GlobalScanned = false;
83
84 clang::tooling::dependencies::DependencyScanningService Service;
85
86 // TODO: Add a scanning cache.
87
88 // Map module name to source file path.
89 llvm::StringMap<std::string> ModuleNameToSource;
90};
91
92std::optional<ModuleDependencyScanner::ModuleDependencyInfo>
93ModuleDependencyScanner::scan(PathRef FilePath,
94 const ProjectModules::CommandMangler &Mangler) {
95 auto Candidates = CDB->getCompileCommands(FilePath);
96 if (Candidates.empty())
97 return std::nullopt;
98
99 // Choose the first candidates as the compile commands as the file.
100 // Following the same logic with
101 // DirectoryBasedGlobalCompilationDatabase::getCompileCommand.
102 tooling::CompileCommand Cmd = std::move(Candidates.front());
103
104 if (Mangler)
105 Mangler(Cmd, FilePath);
106
107 using namespace clang::tooling::dependencies;
108
109 llvm::SmallString<128> FilePathDir(FilePath);
110 llvm::sys::path::remove_filename(path&: FilePathDir);
111 DependencyScanningTool ScanningTool(Service, TFS.view(CWD: FilePathDir));
112
113 llvm::Expected<P1689Rule> ScanningResult =
114 ScanningTool.getP1689ModuleDependencyFile(Command: Cmd, CWD: Cmd.Directory);
115
116 if (auto E = ScanningResult.takeError()) {
117 elog(Fmt: "Scanning modules dependencies for {0} failed: {1}", Vals&: FilePath,
118 Vals: llvm::toString(E: std::move(E)));
119 return std::nullopt;
120 }
121
122 ModuleDependencyInfo Result;
123
124 if (ScanningResult->Provides) {
125 ModuleNameToSource[ScanningResult->Provides->ModuleName] = FilePath;
126 Result.ModuleName = ScanningResult->Provides->ModuleName;
127 }
128
129 for (auto &Required : ScanningResult->Requires)
130 Result.RequiredModules.push_back(x: Required.ModuleName);
131
132 return Result;
133}
134
135void ModuleDependencyScanner::globalScan(
136 const ProjectModules::CommandMangler &Mangler) {
137 if (GlobalScanned)
138 return;
139
140 for (auto &File : CDB->getAllFiles())
141 scan(FilePath: File, Mangler);
142
143 GlobalScanned = true;
144}
145
146PathRef ModuleDependencyScanner::getSourceForModuleName(
147 llvm::StringRef ModuleName) const {
148 assert(
149 GlobalScanned &&
150 "We should only call getSourceForModuleName after calling globalScan()");
151
152 if (auto It = ModuleNameToSource.find(Key: ModuleName);
153 It != ModuleNameToSource.end())
154 return It->second;
155
156 return {};
157}
158
159std::vector<std::string> ModuleDependencyScanner::getRequiredModules(
160 PathRef File, const ProjectModules::CommandMangler &Mangler) {
161 auto ScanningResult = scan(FilePath: File, Mangler);
162 if (!ScanningResult)
163 return {};
164
165 return ScanningResult->RequiredModules;
166}
167} // namespace
168
169/// TODO: The existing `ScanningAllProjectModules` is not efficient. See the
170/// comments in ModuleDependencyScanner for detail.
171///
172/// In the future, we wish the build system can provide a well design
173/// compilation database for modules then we can query that new compilation
174/// database directly. Or we need to have a global long-live scanner to detect
175/// the state of each file.
176class ScanningAllProjectModules : public ProjectModules {
177public:
178 ScanningAllProjectModules(
179 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
180 const ThreadsafeFS &TFS)
181 : Scanner(CDB, TFS) {}
182
183 ~ScanningAllProjectModules() override = default;
184
185 std::vector<std::string> getRequiredModules(PathRef File) override {
186 return Scanner.getRequiredModules(File, Mangler);
187 }
188
189 void setCommandMangler(CommandMangler Mangler) override {
190 this->Mangler = std::move(Mangler);
191 }
192
193 /// RequiredSourceFile is not used intentionally. See the comments of
194 /// ModuleDependencyScanner for detail.
195 std::string getSourceForModuleName(llvm::StringRef ModuleName,
196 PathRef RequiredSourceFile) override {
197 Scanner.globalScan(Mangler);
198 return Scanner.getSourceForModuleName(ModuleName).str();
199 }
200
201 std::string getModuleNameForSource(PathRef File) override {
202 auto ScanningResult = Scanner.scan(FilePath: File, Mangler);
203 if (!ScanningResult || !ScanningResult->ModuleName)
204 return {};
205
206 return *ScanningResult->ModuleName;
207 }
208
209private:
210 ModuleDependencyScanner Scanner;
211 CommandMangler Mangler;
212};
213
214std::unique_ptr<ProjectModules> scanningProjectModules(
215 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
216 const ThreadsafeFS &TFS) {
217 return std::make_unique<ScanningAllProjectModules>(args&: CDB, args: TFS);
218}
219
220} // namespace clang::clangd
221

source code of clang-tools-extra/clangd/ScanningProjectModules.cpp