clang-tools 23.0.0git
ScanningProjectModules.cpp
Go to the documentation of this file.
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/DependencyScanning/DependencyScanningService.h"
12#include "clang/Tooling/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), Service([&TFS] {
39 dependencies::DependencyScanningServiceOptions Opts;
40 Opts.MakeVFS = [&] { return TFS.view(std::nullopt); };
41 Opts.Mode = dependencies::ScanningMode::CanonicalPreprocessing;
42 Opts.Format = dependencies::ScanningOutputFormat::P1689;
43 return Opts;
44 }()) {}
45
46 /// The scanned modules dependency information for a specific source file.
47 struct ModuleDependencyInfo {
48 /// The name of the module if the file is a module unit.
49 std::optional<std::string> ModuleName;
50 /// A list of names for the modules that the file directly depends.
51 std::vector<std::string> RequiredModules;
52 };
53
54 /// Scanning the single file specified by \param FilePath.
55 std::optional<ModuleDependencyInfo>
56 scan(PathRef FilePath, const ProjectModules::CommandMangler &Mangler);
57
58 /// Scanning every source file in the current project to get the
59 /// <module-name> to <module-unit-source> map.
60 /// TODO: We should find an efficient method to get the <module-name>
61 /// to <module-unit-source> map. We can make it either by providing
62 /// a global module dependency scanner to monitor every file. Or we
63 /// can simply require the build systems (or even the end users)
64 /// to provide the map.
65 void globalScan(const ProjectModules::CommandMangler &Mangler);
66
67 /// Get the source file from the module name. Note that the language
68 /// guarantees all the module names are unique in a valid program.
69 /// This function should only be called after globalScan.
70 ///
71 /// TODO: We should handle the case that there are multiple source files
72 /// declaring the same module.
73 PathRef getSourceForModuleName(llvm::StringRef ModuleName) const;
74
75 /// Return the direct required modules. Indirect required modules are not
76 /// included.
77 std::vector<std::string>
78 getRequiredModules(PathRef File,
79 const ProjectModules::CommandMangler &Mangler);
80
81private:
82 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB;
83
84 // Whether the scanner has scanned the project globally.
85 bool GlobalScanned = false;
86
87 clang::dependencies::DependencyScanningService Service;
88
89 // TODO: Add a scanning cache.
90
91 // Map module name to source file path.
92 llvm::StringMap<std::string> ModuleNameToSource;
93};
94
95std::optional<ModuleDependencyScanner::ModuleDependencyInfo>
96ModuleDependencyScanner::scan(PathRef FilePath,
97 const ProjectModules::CommandMangler &Mangler) {
98 auto Candidates = CDB->getCompileCommands(FilePath);
99 if (Candidates.empty())
100 return std::nullopt;
101
102 // Choose the first candidates as the compile commands as the file.
103 // Following the same logic with
104 // DirectoryBasedGlobalCompilationDatabase::getCompileCommand.
105 tooling::CompileCommand Cmd = std::move(Candidates.front());
106
107 if (Mangler)
108 Mangler(Cmd, FilePath);
109
110 using namespace clang::tooling;
111
112 DependencyScanningTool ScanningTool(Service);
113
114 std::string S;
115 llvm::raw_string_ostream OS(S);
116 DiagnosticOptions DiagOpts;
117 DiagOpts.ShowCarets = false;
118 TextDiagnosticPrinter DiagConsumer(OS, DiagOpts);
119
120 std::optional<P1689Rule> ScanningResult =
121 ScanningTool.getP1689ModuleDependencyFile(Cmd, Cmd.Directory,
122 DiagConsumer);
123
124 if (!ScanningResult) {
125 elog("Scanning modules dependencies for {0} failed: {1}", FilePath, S);
126 std::string Cmdline;
127 for (auto &Arg : Cmd.CommandLine)
128 Cmdline += Arg + " ";
129 elog("The command line the scanning tool use is: {0}", Cmdline);
130 return std::nullopt;
131 }
132
133 ModuleDependencyInfo Result;
134
135 if (ScanningResult->Provides) {
136 Result.ModuleName = ScanningResult->Provides->ModuleName;
137
138 auto [Iter, Inserted] = ModuleNameToSource.try_emplace(
139 ScanningResult->Provides->ModuleName, FilePath);
140
141 if (!Inserted && Iter->second != FilePath) {
142 elog("Detected multiple source files ({0}, {1}) declaring the same "
143 "module: '{2}'. "
144 "Now clangd may find the wrong source in such case.",
145 Iter->second, FilePath, ScanningResult->Provides->ModuleName);
146 }
147 }
148
149 for (auto &Required : ScanningResult->Requires)
150 Result.RequiredModules.push_back(Required.ModuleName);
151
152 return Result;
153}
154
155void ModuleDependencyScanner::globalScan(
156 const ProjectModules::CommandMangler &Mangler) {
157 if (GlobalScanned)
158 return;
159
160 for (auto &File : CDB->getAllFiles())
161 scan(File, Mangler);
162
163 GlobalScanned = true;
164}
165
166PathRef ModuleDependencyScanner::getSourceForModuleName(
167 llvm::StringRef ModuleName) const {
168 assert(
169 GlobalScanned &&
170 "We should only call getSourceForModuleName after calling globalScan()");
171
172 if (auto It = ModuleNameToSource.find(ModuleName);
173 It != ModuleNameToSource.end())
174 return It->second;
175
176 return {};
177}
178
179std::vector<std::string> ModuleDependencyScanner::getRequiredModules(
181 auto ScanningResult = scan(File, Mangler);
182 if (!ScanningResult)
183 return {};
184
185 return ScanningResult->RequiredModules;
186}
187} // namespace
188
189/// TODO: The existing `ScanningAllProjectModules` is not efficient. See the
190/// comments in ModuleDependencyScanner for detail.
191///
192/// In the future, we wish the build system can provide a well design
193/// compilation database for modules then we can query that new compilation
194/// database directly. Or we need to have a global long-live scanner to detect
195/// the state of each file.
197public:
199 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
200 const ThreadsafeFS &TFS)
201 : Scanner(CDB, TFS) {}
202
203 ~ScanningAllProjectModules() override = default;
204
205 std::vector<std::string> getRequiredModules(PathRef File) override {
206 return Scanner.getRequiredModules(File, Mangler);
207 }
208
209 void setCommandMangler(CommandMangler Mangler) override {
210 this->Mangler = std::move(Mangler);
211 }
212
213 /// RequiredSourceFile is not used intentionally. See the comments of
214 /// ModuleDependencyScanner for detail.
215 std::string getSourceForModuleName(llvm::StringRef ModuleName,
216 PathRef RequiredSourceFile) override {
217 Scanner.globalScan(Mangler);
218 return Scanner.getSourceForModuleName(ModuleName).str();
219 }
220
221 std::string getModuleNameForSource(PathRef File) override {
222 auto ScanningResult = Scanner.scan(File, Mangler);
223 if (!ScanningResult || !ScanningResult->ModuleName)
224 return {};
225
226 return *ScanningResult->ModuleName;
227 }
228
229private:
230 ModuleDependencyScanner Scanner;
231 CommandMangler Mangler;
232};
233
234std::unique_ptr<ProjectModules> scanningProjectModules(
235 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
236 const ThreadsafeFS &TFS) {
237 return std::make_unique<ScanningAllProjectModules>(CDB, TFS);
238}
239
240} // namespace clang::clangd
void elog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:61
An interface to query the modules information in the project.
llvm::unique_function< void(tooling::CompileCommand &, PathRef) const > CommandMangler
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
std::string getSourceForModuleName(llvm::StringRef ModuleName, PathRef RequiredSourceFile) override
RequiredSourceFile is not used intentionally.
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
std::unique_ptr< ProjectModules > scanningProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
Providing modules information for the project by scanning every file.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition Path.h:29