1 //===------------------ ProjectModules.h -------------------------*- C++-*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "ProjectModules.h"
10 #include "support/Logger.h"
11 #include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
12 #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
14 namespace clang::clangd
{
16 /// A scanner to query the dependency information for C++20 Modules.
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.
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.
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
{
35 ModuleDependencyScanner(
36 std::shared_ptr
<const clang::tooling::CompilationDatabase
> CDB
,
37 const ThreadsafeFS
&TFS
)
39 Service(tooling::dependencies::ScanningMode::CanonicalPreprocessing
,
40 tooling::dependencies::ScanningOutputFormat::P1689
) {}
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
;
50 /// Scanning the single file specified by \param FilePath.
51 std::optional
<ModuleDependencyInfo
>
52 scan(PathRef FilePath
, const ProjectModules::CommandMangler
&Mangler
);
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
);
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.
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;
71 /// Return the direct required modules. Indirect required modules are not
73 std::vector
<std::string
>
74 getRequiredModules(PathRef File
,
75 const ProjectModules::CommandMangler
&Mangler
);
78 std::shared_ptr
<const clang::tooling::CompilationDatabase
> CDB
;
79 const ThreadsafeFS
&TFS
;
81 // Whether the scanner has scanned the project globally.
82 bool GlobalScanned
= false;
84 clang::tooling::dependencies::DependencyScanningService Service
;
86 // TODO: Add a scanning cache.
88 // Map module name to source file path.
89 llvm::StringMap
<std::string
> ModuleNameToSource
;
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())
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());
105 Mangler(Cmd
, FilePath
);
107 using namespace clang::tooling::dependencies
;
109 llvm::SmallString
<128> FilePathDir(FilePath
);
110 llvm::sys::path::remove_filename(FilePathDir
);
111 DependencyScanningTool
ScanningTool(Service
, TFS
.view(FilePathDir
));
113 llvm::Expected
<P1689Rule
> ScanningResult
=
114 ScanningTool
.getP1689ModuleDependencyFile(Cmd
, Cmd
.Directory
);
116 if (auto E
= ScanningResult
.takeError()) {
117 elog("Scanning modules dependencies for {0} failed: {1}", FilePath
,
118 llvm::toString(std::move(E
)));
122 ModuleDependencyInfo Result
;
124 if (ScanningResult
->Provides
) {
125 ModuleNameToSource
[ScanningResult
->Provides
->ModuleName
] = FilePath
;
126 Result
.ModuleName
= ScanningResult
->Provides
->ModuleName
;
129 for (auto &Required
: ScanningResult
->Requires
)
130 Result
.RequiredModules
.push_back(Required
.ModuleName
);
135 void ModuleDependencyScanner::globalScan(
136 const ProjectModules::CommandMangler
&Mangler
) {
137 for (auto &File
: CDB
->getAllFiles())
140 GlobalScanned
= true;
143 PathRef
ModuleDependencyScanner::getSourceForModuleName(
144 llvm::StringRef ModuleName
) const {
147 "We should only call getSourceForModuleName after calling globalScan()");
149 if (auto It
= ModuleNameToSource
.find(ModuleName
);
150 It
!= ModuleNameToSource
.end())
156 std::vector
<std::string
> ModuleDependencyScanner::getRequiredModules(
157 PathRef File
, const ProjectModules::CommandMangler
&Mangler
) {
158 auto ScanningResult
= scan(File
, Mangler
);
162 return ScanningResult
->RequiredModules
;
166 /// TODO: The existing `ScanningAllProjectModules` is not efficient. See the
167 /// comments in ModuleDependencyScanner for detail.
169 /// In the future, we wish the build system can provide a well design
170 /// compilation database for modules then we can query that new compilation
171 /// database directly. Or we need to have a global long-live scanner to detect
172 /// the state of each file.
173 class ScanningAllProjectModules
: public ProjectModules
{
175 ScanningAllProjectModules(
176 std::shared_ptr
<const clang::tooling::CompilationDatabase
> CDB
,
177 const ThreadsafeFS
&TFS
)
178 : Scanner(CDB
, TFS
) {}
180 ~ScanningAllProjectModules() override
= default;
182 std::vector
<std::string
> getRequiredModules(PathRef File
) override
{
183 return Scanner
.getRequiredModules(File
, Mangler
);
186 void setCommandMangler(CommandMangler Mangler
) override
{
187 this->Mangler
= std::move(Mangler
);
190 /// RequiredSourceFile is not used intentionally. See the comments of
191 /// ModuleDependencyScanner for detail.
193 getSourceForModuleName(llvm::StringRef ModuleName
,
194 PathRef RequiredSourceFile
= PathRef()) override
{
195 Scanner
.globalScan(Mangler
);
196 return Scanner
.getSourceForModuleName(ModuleName
);
200 ModuleDependencyScanner Scanner
;
201 CommandMangler Mangler
;
204 std::unique_ptr
<ProjectModules
> scanningProjectModules(
205 std::shared_ptr
<const clang::tooling::CompilationDatabase
> CDB
,
206 const ThreadsafeFS
&TFS
) {
207 return std::make_unique
<ScanningAllProjectModules
>(CDB
, TFS
);
210 } // namespace clang::clangd