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 | |
14 | namespace clang::clangd { |
15 | namespace { |
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. |
33 | class ModuleDependencyScanner { |
34 | public: |
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 | |
77 | private: |
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 | |
92 | std::optional<ModuleDependencyScanner::ModuleDependencyInfo> |
93 | ModuleDependencyScanner::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 | |
135 | void 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 | |
146 | PathRef 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 | |
159 | std::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. |
176 | class ScanningAllProjectModules : public ProjectModules { |
177 | public: |
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 | |
209 | private: |
210 | ModuleDependencyScanner Scanner; |
211 | CommandMangler Mangler; |
212 | }; |
213 | |
214 | std::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 | |