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
> scan(PathRef FilePath
);
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.
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.
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;
70 /// Return the direct required modules. Indirect required modules are not
72 std::vector
<std::string
> getRequiredModules(PathRef File
);
75 std::shared_ptr
<const clang::tooling::CompilationDatabase
> CDB
;
76 const ThreadsafeFS
&TFS
;
78 // Whether the scanner has scanned the project globally.
79 bool GlobalScanned
= false;
81 clang::tooling::dependencies::DependencyScanningService Service
;
83 // TODO: Add a scanning cache.
85 // Map module name to source file path.
86 llvm::StringMap
<std::string
> ModuleNameToSource
;
89 std::optional
<ModuleDependencyScanner::ModuleDependencyInfo
>
90 ModuleDependencyScanner::scan(PathRef FilePath
) {
91 auto Candidates
= CDB
->getCompileCommands(FilePath
);
92 if (Candidates
.empty())
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());
100 static int StaticForMainAddr
; // Just an address in this process.
101 Cmd
.CommandLine
.push_back("-resource-dir=" +
102 CompilerInvocation::GetResourcesPath(
103 "clangd", (void *)&StaticForMainAddr
));
105 using namespace clang::tooling::dependencies
;
107 llvm::SmallString
<128> FilePathDir(FilePath
);
108 llvm::sys::path::remove_filename(FilePathDir
);
109 DependencyScanningTool
ScanningTool(Service
, TFS
.view(FilePathDir
));
111 llvm::Expected
<P1689Rule
> ScanningResult
=
112 ScanningTool
.getP1689ModuleDependencyFile(Cmd
, Cmd
.Directory
);
114 if (auto E
= ScanningResult
.takeError()) {
115 elog("Scanning modules dependencies for {0} failed: {1}", FilePath
,
116 llvm::toString(std::move(E
)));
120 ModuleDependencyInfo Result
;
122 if (ScanningResult
->Provides
) {
123 ModuleNameToSource
[ScanningResult
->Provides
->ModuleName
] = FilePath
;
124 Result
.ModuleName
= ScanningResult
->Provides
->ModuleName
;
127 for (auto &Required
: ScanningResult
->Requires
)
128 Result
.RequiredModules
.push_back(Required
.ModuleName
);
133 void ModuleDependencyScanner::globalScan() {
134 for (auto &File
: CDB
->getAllFiles())
137 GlobalScanned
= true;
140 PathRef
ModuleDependencyScanner::getSourceForModuleName(
141 llvm::StringRef ModuleName
) const {
144 "We should only call getSourceForModuleName after calling globalScan()");
146 if (auto It
= ModuleNameToSource
.find(ModuleName
);
147 It
!= ModuleNameToSource
.end())
153 std::vector
<std::string
>
154 ModuleDependencyScanner::getRequiredModules(PathRef File
) {
155 auto ScanningResult
= scan(File
);
159 return ScanningResult
->RequiredModules
;
163 /// TODO: The existing `ScanningAllProjectModules` is not efficient. See the
164 /// comments in ModuleDependencyScanner for detail.
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.
170 class ScanningAllProjectModules
: public ProjectModules
{
172 ScanningAllProjectModules(
173 std::shared_ptr
<const clang::tooling::CompilationDatabase
> CDB
,
174 const ThreadsafeFS
&TFS
)
175 : Scanner(CDB
, TFS
) {}
177 ~ScanningAllProjectModules() override
= default;
179 std::vector
<std::string
> getRequiredModules(PathRef File
) override
{
180 return Scanner
.getRequiredModules(File
);
183 /// RequiredSourceFile is not used intentionally. See the comments of
184 /// ModuleDependencyScanner for detail.
186 getSourceForModuleName(llvm::StringRef ModuleName
,
187 PathRef RequiredSourceFile
= PathRef()) override
{
188 Scanner
.globalScan();
189 return Scanner
.getSourceForModuleName(ModuleName
);
193 ModuleDependencyScanner Scanner
;
196 std::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
);
202 } // namespace clang::clangd