clang-tools 20.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/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> scan(PathRef FilePath);
52
53 /// Scanning every source file in the current project to get the
54 /// <module-name> to <module-unit-source> map.
55 /// TODO: We should find an efficient method to get the <module-name>
56 /// to <module-unit-source> map. We can make it either by providing
57 /// a global module dependency scanner to monitor every file. Or we
58 /// can simply require the build systems (or even the end users)
59 /// to provide the map.
60 void globalScan();
61
62 /// Get the source file from the module name. Note that the language
63 /// guarantees all the module names are unique in a valid program.
64 /// This function should only be called after globalScan.
65 ///
66 /// TODO: We should handle the case that there are multiple source files
67 /// declaring the same module.
68 PathRef getSourceForModuleName(llvm::StringRef ModuleName) const;
69
70 /// Return the direct required modules. Indirect required modules are not
71 /// included.
72 std::vector<std::string> getRequiredModules(PathRef File);
73
74private:
75 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB;
76 const ThreadsafeFS &TFS;
77
78 // Whether the scanner has scanned the project globally.
79 bool GlobalScanned = false;
80
81 clang::tooling::dependencies::DependencyScanningService Service;
82
83 // TODO: Add a scanning cache.
84
85 // Map module name to source file path.
86 llvm::StringMap<std::string> ModuleNameToSource;
87};
88
89std::optional<ModuleDependencyScanner::ModuleDependencyInfo>
90ModuleDependencyScanner::scan(PathRef FilePath) {
91 auto Candidates = CDB->getCompileCommands(FilePath);
92 if (Candidates.empty())
93 return std::nullopt;
94
95 // Choose the first candidates as the compile commands as the file.
96 // Following the same logic with
97 // DirectoryBasedGlobalCompilationDatabase::getCompileCommand.
98 tooling::CompileCommand Cmd = std::move(Candidates.front());
99
100 static int StaticForMainAddr; // Just an address in this process.
101 Cmd.CommandLine.push_back("-resource-dir=" +
102 CompilerInvocation::GetResourcesPath(
103 "clangd", (void *)&StaticForMainAddr));
104
105 using namespace clang::tooling::dependencies;
106
107 llvm::SmallString<128> FilePathDir(FilePath);
108 llvm::sys::path::remove_filename(FilePathDir);
109 DependencyScanningTool ScanningTool(Service, TFS.view(FilePathDir));
110
111 llvm::Expected<P1689Rule> ScanningResult =
112 ScanningTool.getP1689ModuleDependencyFile(Cmd, Cmd.Directory);
113
114 if (auto E = ScanningResult.takeError()) {
115 elog("Scanning modules dependencies for {0} failed: {1}", FilePath,
116 llvm::toString(std::move(E)));
117 return std::nullopt;
118 }
119
120 ModuleDependencyInfo Result;
121
122 if (ScanningResult->Provides) {
123 ModuleNameToSource[ScanningResult->Provides->ModuleName] = FilePath;
124 Result.ModuleName = ScanningResult->Provides->ModuleName;
125 }
126
127 for (auto &Required : ScanningResult->Requires)
128 Result.RequiredModules.push_back(Required.ModuleName);
129
130 return Result;
131}
132
133void ModuleDependencyScanner::globalScan() {
134 for (auto &File : CDB->getAllFiles())
135 scan(File);
136
137 GlobalScanned = true;
138}
139
140PathRef ModuleDependencyScanner::getSourceForModuleName(
141 llvm::StringRef ModuleName) const {
142 assert(
143 GlobalScanned &&
144 "We should only call getSourceForModuleName after calling globalScan()");
145
146 if (auto It = ModuleNameToSource.find(ModuleName);
147 It != ModuleNameToSource.end())
148 return It->second;
149
150 return {};
151}
152
153std::vector<std::string>
154ModuleDependencyScanner::getRequiredModules(PathRef File) {
155 auto ScanningResult = scan(File);
156 if (!ScanningResult)
157 return {};
158
159 return ScanningResult->RequiredModules;
160}
161} // namespace
162
163/// TODO: The existing `ScanningAllProjectModules` is not efficient. See the
164/// comments in ModuleDependencyScanner for detail.
165///
166/// In the future, we wish the build system can provide a well design
167/// compilation database for modules then we can query that new compilation
168/// database directly. Or we need to have a global long-live scanner to detect
169/// the state of each file.
171public:
173 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
174 const ThreadsafeFS &TFS)
175 : Scanner(CDB, TFS) {}
176
177 ~ScanningAllProjectModules() override = default;
178
179 std::vector<std::string> getRequiredModules(PathRef File) override {
180 return Scanner.getRequiredModules(File);
181 }
182
183 /// RequiredSourceFile is not used intentionally. See the comments of
184 /// ModuleDependencyScanner for detail.
185 PathRef
186 getSourceForModuleName(llvm::StringRef ModuleName,
187 PathRef RequiredSourceFile = PathRef()) override {
188 Scanner.globalScan();
189 return Scanner.getSourceForModuleName(ModuleName);
190 }
191
192private:
193 ModuleDependencyScanner Scanner;
194};
195
196std::unique_ptr<ProjectModules> scanningProjectModules(
197 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
198 const ThreadsafeFS &TFS) {
199 return std::make_unique<ScanningAllProjectModules>(CDB, TFS);
200}
201
202} // namespace clang::clangd
const Expr * E
An interface to query the modules information in the project.
TODO: The existing ScanningAllProjectModules is not efficient.
PathRef getSourceForModuleName(llvm::StringRef ModuleName, PathRef RequiredSourceFile=PathRef()) override
RequiredSourceFile is not used intentionally.
ScanningAllProjectModules(std::shared_ptr< const clang::tooling::CompilationDatabase > CDB, const ThreadsafeFS &TFS)
std::vector< std::string > getRequiredModules(PathRef File) override
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
Definition: ThreadsafeFS.h:26
llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > view(std::nullopt_t CWD) const
Obtain a vfs::FileSystem with an arbitrary initial working directory.
Definition: ThreadsafeFS.h:32
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
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:61