xref: /llvm-project/clang-tools-extra/clangd/ScanningProjectModules.cpp (revision 2b0e2255d6067872e844ff07d67342a6c97d8049)
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(FilePathDir);
111   DependencyScanningTool ScanningTool(Service, TFS.view(FilePathDir));
112 
113   llvm::Expected<P1689Rule> ScanningResult =
114       ScanningTool.getP1689ModuleDependencyFile(Cmd, Cmd.Directory);
115 
116   if (auto E = ScanningResult.takeError()) {
117     elog("Scanning modules dependencies for {0} failed: {1}", FilePath,
118          llvm::toString(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(Required.ModuleName);
131 
132   return Result;
133 }
134 
135 void ModuleDependencyScanner::globalScan(
136     const ProjectModules::CommandMangler &Mangler) {
137   for (auto &File : CDB->getAllFiles())
138     scan(File, Mangler);
139 
140   GlobalScanned = true;
141 }
142 
143 PathRef ModuleDependencyScanner::getSourceForModuleName(
144     llvm::StringRef ModuleName) const {
145   assert(
146       GlobalScanned &&
147       "We should only call getSourceForModuleName after calling globalScan()");
148 
149   if (auto It = ModuleNameToSource.find(ModuleName);
150       It != ModuleNameToSource.end())
151     return It->second;
152 
153   return {};
154 }
155 
156 std::vector<std::string> ModuleDependencyScanner::getRequiredModules(
157     PathRef File, const ProjectModules::CommandMangler &Mangler) {
158   auto ScanningResult = scan(File, Mangler);
159   if (!ScanningResult)
160     return {};
161 
162   return ScanningResult->RequiredModules;
163 }
164 } // namespace
165 
166 /// TODO: The existing `ScanningAllProjectModules` is not efficient. See the
167 /// comments in ModuleDependencyScanner for detail.
168 ///
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 {
174 public:
175   ScanningAllProjectModules(
176       std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
177       const ThreadsafeFS &TFS)
178       : Scanner(CDB, TFS) {}
179 
180   ~ScanningAllProjectModules() override = default;
181 
182   std::vector<std::string> getRequiredModules(PathRef File) override {
183     return Scanner.getRequiredModules(File, Mangler);
184   }
185 
186   void setCommandMangler(CommandMangler Mangler) override {
187     this->Mangler = std::move(Mangler);
188   }
189 
190   /// RequiredSourceFile is not used intentionally. See the comments of
191   /// ModuleDependencyScanner for detail.
192   PathRef
193   getSourceForModuleName(llvm::StringRef ModuleName,
194                          PathRef RequiredSourceFile = PathRef()) override {
195     Scanner.globalScan(Mangler);
196     return Scanner.getSourceForModuleName(ModuleName);
197   }
198 
199 private:
200   ModuleDependencyScanner Scanner;
201   CommandMangler Mangler;
202 };
203 
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);
208 }
209 
210 } // namespace clang::clangd
211