xref: /llvm-project/clang-tools-extra/clangd/ScanningProjectModules.cpp (revision 2b0e2255d6067872e844ff07d67342a6c97d8049)
1fe6c2400SChuanqi Xu //===------------------ ProjectModules.h -------------------------*- C++-*-===//
2fe6c2400SChuanqi Xu //
3fe6c2400SChuanqi Xu // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4fe6c2400SChuanqi Xu // See https://llvm.org/LICENSE.txt for license information.
5fe6c2400SChuanqi Xu // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6fe6c2400SChuanqi Xu //
7fe6c2400SChuanqi Xu //===----------------------------------------------------------------------===//
8fe6c2400SChuanqi Xu 
9fe6c2400SChuanqi Xu #include "ProjectModules.h"
10fe6c2400SChuanqi Xu #include "support/Logger.h"
11fe6c2400SChuanqi Xu #include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
12fe6c2400SChuanqi Xu #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
13fe6c2400SChuanqi Xu 
14fe6c2400SChuanqi Xu namespace clang::clangd {
15fe6c2400SChuanqi Xu namespace {
16fe6c2400SChuanqi Xu /// A scanner to query the dependency information for C++20 Modules.
17fe6c2400SChuanqi Xu ///
18fe6c2400SChuanqi Xu /// The scanner can scan a single file with `scan(PathRef)` member function
19fe6c2400SChuanqi Xu /// or scan the whole project with `globalScan(vector<PathRef>)` member
20fe6c2400SChuanqi Xu /// function. See the comments of `globalScan` to see the details.
21fe6c2400SChuanqi Xu ///
22fe6c2400SChuanqi Xu /// The ModuleDependencyScanner can get the directly required module names for a
23fe6c2400SChuanqi Xu /// specific source file. Also the ModuleDependencyScanner can get the source
24fe6c2400SChuanqi Xu /// file declaring the primary module interface for a specific module name.
25fe6c2400SChuanqi Xu ///
26fe6c2400SChuanqi Xu /// IMPORTANT NOTE: we assume that every module unit is only declared once in a
27fe6c2400SChuanqi Xu /// source file in the project. But the assumption is not strictly true even
28fe6c2400SChuanqi Xu /// besides the invalid projects. The language specification requires that every
29fe6c2400SChuanqi Xu /// module unit should be unique in a valid program. But a project can contain
30fe6c2400SChuanqi Xu /// multiple programs. Then it is valid that we can have multiple source files
31fe6c2400SChuanqi Xu /// declaring the same module in a project as long as these source files don't
32fe6c2400SChuanqi Xu /// interfere with each other.
33fe6c2400SChuanqi Xu class ModuleDependencyScanner {
34fe6c2400SChuanqi Xu public:
35fe6c2400SChuanqi Xu   ModuleDependencyScanner(
36fe6c2400SChuanqi Xu       std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
37fe6c2400SChuanqi Xu       const ThreadsafeFS &TFS)
38fe6c2400SChuanqi Xu       : CDB(CDB), TFS(TFS),
39fe6c2400SChuanqi Xu         Service(tooling::dependencies::ScanningMode::CanonicalPreprocessing,
40fe6c2400SChuanqi Xu                 tooling::dependencies::ScanningOutputFormat::P1689) {}
41fe6c2400SChuanqi Xu 
42fe6c2400SChuanqi Xu   /// The scanned modules dependency information for a specific source file.
43fe6c2400SChuanqi Xu   struct ModuleDependencyInfo {
44fe6c2400SChuanqi Xu     /// The name of the module if the file is a module unit.
45fe6c2400SChuanqi Xu     std::optional<std::string> ModuleName;
46fe6c2400SChuanqi Xu     /// A list of names for the modules that the file directly depends.
47fe6c2400SChuanqi Xu     std::vector<std::string> RequiredModules;
48fe6c2400SChuanqi Xu   };
49fe6c2400SChuanqi Xu 
50fe6c2400SChuanqi Xu   /// Scanning the single file specified by \param FilePath.
51*2b0e2255SPetr Polezhaev   std::optional<ModuleDependencyInfo>
52*2b0e2255SPetr Polezhaev   scan(PathRef FilePath, const ProjectModules::CommandMangler &Mangler);
53fe6c2400SChuanqi Xu 
54fe6c2400SChuanqi Xu   /// Scanning every source file in the current project to get the
55fe6c2400SChuanqi Xu   /// <module-name> to <module-unit-source> map.
56fe6c2400SChuanqi Xu   /// TODO: We should find an efficient method to get the <module-name>
57fe6c2400SChuanqi Xu   /// to <module-unit-source> map. We can make it either by providing
58fe6c2400SChuanqi Xu   /// a global module dependency scanner to monitor every file. Or we
59fe6c2400SChuanqi Xu   /// can simply require the build systems (or even the end users)
60fe6c2400SChuanqi Xu   /// to provide the map.
61*2b0e2255SPetr Polezhaev   void globalScan(const ProjectModules::CommandMangler &Mangler);
62fe6c2400SChuanqi Xu 
63fe6c2400SChuanqi Xu   /// Get the source file from the module name. Note that the language
64fe6c2400SChuanqi Xu   /// guarantees all the module names are unique in a valid program.
65fe6c2400SChuanqi Xu   /// This function should only be called after globalScan.
66fe6c2400SChuanqi Xu   ///
67fe6c2400SChuanqi Xu   /// TODO: We should handle the case that there are multiple source files
68fe6c2400SChuanqi Xu   /// declaring the same module.
69fe6c2400SChuanqi Xu   PathRef getSourceForModuleName(llvm::StringRef ModuleName) const;
70fe6c2400SChuanqi Xu 
71fe6c2400SChuanqi Xu   /// Return the direct required modules. Indirect required modules are not
72fe6c2400SChuanqi Xu   /// included.
73*2b0e2255SPetr Polezhaev   std::vector<std::string>
74*2b0e2255SPetr Polezhaev   getRequiredModules(PathRef File,
75*2b0e2255SPetr Polezhaev                      const ProjectModules::CommandMangler &Mangler);
76fe6c2400SChuanqi Xu 
77fe6c2400SChuanqi Xu private:
78fe6c2400SChuanqi Xu   std::shared_ptr<const clang::tooling::CompilationDatabase> CDB;
79fe6c2400SChuanqi Xu   const ThreadsafeFS &TFS;
80fe6c2400SChuanqi Xu 
81fe6c2400SChuanqi Xu   // Whether the scanner has scanned the project globally.
82fe6c2400SChuanqi Xu   bool GlobalScanned = false;
83fe6c2400SChuanqi Xu 
84fe6c2400SChuanqi Xu   clang::tooling::dependencies::DependencyScanningService Service;
85fe6c2400SChuanqi Xu 
86fe6c2400SChuanqi Xu   // TODO: Add a scanning cache.
87fe6c2400SChuanqi Xu 
88fe6c2400SChuanqi Xu   // Map module name to source file path.
89fe6c2400SChuanqi Xu   llvm::StringMap<std::string> ModuleNameToSource;
90fe6c2400SChuanqi Xu };
91fe6c2400SChuanqi Xu 
92fe6c2400SChuanqi Xu std::optional<ModuleDependencyScanner::ModuleDependencyInfo>
93*2b0e2255SPetr Polezhaev ModuleDependencyScanner::scan(PathRef FilePath,
94*2b0e2255SPetr Polezhaev                               const ProjectModules::CommandMangler &Mangler) {
95fe6c2400SChuanqi Xu   auto Candidates = CDB->getCompileCommands(FilePath);
96fe6c2400SChuanqi Xu   if (Candidates.empty())
97fe6c2400SChuanqi Xu     return std::nullopt;
98fe6c2400SChuanqi Xu 
99fe6c2400SChuanqi Xu   // Choose the first candidates as the compile commands as the file.
100fe6c2400SChuanqi Xu   // Following the same logic with
101fe6c2400SChuanqi Xu   // DirectoryBasedGlobalCompilationDatabase::getCompileCommand.
102fe6c2400SChuanqi Xu   tooling::CompileCommand Cmd = std::move(Candidates.front());
103fe6c2400SChuanqi Xu 
104*2b0e2255SPetr Polezhaev   if (Mangler)
105*2b0e2255SPetr Polezhaev     Mangler(Cmd, FilePath);
106fe6c2400SChuanqi Xu 
107fe6c2400SChuanqi Xu   using namespace clang::tooling::dependencies;
108fe6c2400SChuanqi Xu 
109fe6c2400SChuanqi Xu   llvm::SmallString<128> FilePathDir(FilePath);
110fe6c2400SChuanqi Xu   llvm::sys::path::remove_filename(FilePathDir);
111fe6c2400SChuanqi Xu   DependencyScanningTool ScanningTool(Service, TFS.view(FilePathDir));
112fe6c2400SChuanqi Xu 
113fe6c2400SChuanqi Xu   llvm::Expected<P1689Rule> ScanningResult =
114fe6c2400SChuanqi Xu       ScanningTool.getP1689ModuleDependencyFile(Cmd, Cmd.Directory);
115fe6c2400SChuanqi Xu 
116fe6c2400SChuanqi Xu   if (auto E = ScanningResult.takeError()) {
117fe6c2400SChuanqi Xu     elog("Scanning modules dependencies for {0} failed: {1}", FilePath,
118fe6c2400SChuanqi Xu          llvm::toString(std::move(E)));
119fe6c2400SChuanqi Xu     return std::nullopt;
120fe6c2400SChuanqi Xu   }
121fe6c2400SChuanqi Xu 
122fe6c2400SChuanqi Xu   ModuleDependencyInfo Result;
123fe6c2400SChuanqi Xu 
124fe6c2400SChuanqi Xu   if (ScanningResult->Provides) {
125fe6c2400SChuanqi Xu     ModuleNameToSource[ScanningResult->Provides->ModuleName] = FilePath;
126fe6c2400SChuanqi Xu     Result.ModuleName = ScanningResult->Provides->ModuleName;
127fe6c2400SChuanqi Xu   }
128fe6c2400SChuanqi Xu 
129fe6c2400SChuanqi Xu   for (auto &Required : ScanningResult->Requires)
130fe6c2400SChuanqi Xu     Result.RequiredModules.push_back(Required.ModuleName);
131fe6c2400SChuanqi Xu 
132fe6c2400SChuanqi Xu   return Result;
133fe6c2400SChuanqi Xu }
134fe6c2400SChuanqi Xu 
135*2b0e2255SPetr Polezhaev void ModuleDependencyScanner::globalScan(
136*2b0e2255SPetr Polezhaev     const ProjectModules::CommandMangler &Mangler) {
137fe6c2400SChuanqi Xu   for (auto &File : CDB->getAllFiles())
138*2b0e2255SPetr Polezhaev     scan(File, Mangler);
139fe6c2400SChuanqi Xu 
140fe6c2400SChuanqi Xu   GlobalScanned = true;
141fe6c2400SChuanqi Xu }
142fe6c2400SChuanqi Xu 
143fe6c2400SChuanqi Xu PathRef ModuleDependencyScanner::getSourceForModuleName(
144fe6c2400SChuanqi Xu     llvm::StringRef ModuleName) const {
145fe6c2400SChuanqi Xu   assert(
146fe6c2400SChuanqi Xu       GlobalScanned &&
147fe6c2400SChuanqi Xu       "We should only call getSourceForModuleName after calling globalScan()");
148fe6c2400SChuanqi Xu 
149fe6c2400SChuanqi Xu   if (auto It = ModuleNameToSource.find(ModuleName);
150fe6c2400SChuanqi Xu       It != ModuleNameToSource.end())
151fe6c2400SChuanqi Xu     return It->second;
152fe6c2400SChuanqi Xu 
153fe6c2400SChuanqi Xu   return {};
154fe6c2400SChuanqi Xu }
155fe6c2400SChuanqi Xu 
156*2b0e2255SPetr Polezhaev std::vector<std::string> ModuleDependencyScanner::getRequiredModules(
157*2b0e2255SPetr Polezhaev     PathRef File, const ProjectModules::CommandMangler &Mangler) {
158*2b0e2255SPetr Polezhaev   auto ScanningResult = scan(File, Mangler);
159fe6c2400SChuanqi Xu   if (!ScanningResult)
160fe6c2400SChuanqi Xu     return {};
161fe6c2400SChuanqi Xu 
162fe6c2400SChuanqi Xu   return ScanningResult->RequiredModules;
163fe6c2400SChuanqi Xu }
164fe6c2400SChuanqi Xu } // namespace
165fe6c2400SChuanqi Xu 
166fe6c2400SChuanqi Xu /// TODO: The existing `ScanningAllProjectModules` is not efficient. See the
167fe6c2400SChuanqi Xu /// comments in ModuleDependencyScanner for detail.
168fe6c2400SChuanqi Xu ///
169fe6c2400SChuanqi Xu /// In the future, we wish the build system can provide a well design
170fe6c2400SChuanqi Xu /// compilation database for modules then we can query that new compilation
171fe6c2400SChuanqi Xu /// database directly. Or we need to have a global long-live scanner to detect
172fe6c2400SChuanqi Xu /// the state of each file.
173fe6c2400SChuanqi Xu class ScanningAllProjectModules : public ProjectModules {
174fe6c2400SChuanqi Xu public:
175fe6c2400SChuanqi Xu   ScanningAllProjectModules(
176fe6c2400SChuanqi Xu       std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
177fe6c2400SChuanqi Xu       const ThreadsafeFS &TFS)
178fe6c2400SChuanqi Xu       : Scanner(CDB, TFS) {}
179fe6c2400SChuanqi Xu 
180fe6c2400SChuanqi Xu   ~ScanningAllProjectModules() override = default;
181fe6c2400SChuanqi Xu 
182fe6c2400SChuanqi Xu   std::vector<std::string> getRequiredModules(PathRef File) override {
183*2b0e2255SPetr Polezhaev     return Scanner.getRequiredModules(File, Mangler);
184*2b0e2255SPetr Polezhaev   }
185*2b0e2255SPetr Polezhaev 
186*2b0e2255SPetr Polezhaev   void setCommandMangler(CommandMangler Mangler) override {
187*2b0e2255SPetr Polezhaev     this->Mangler = std::move(Mangler);
188fe6c2400SChuanqi Xu   }
189fe6c2400SChuanqi Xu 
190fe6c2400SChuanqi Xu   /// RequiredSourceFile is not used intentionally. See the comments of
191fe6c2400SChuanqi Xu   /// ModuleDependencyScanner for detail.
192fe6c2400SChuanqi Xu   PathRef
193fe6c2400SChuanqi Xu   getSourceForModuleName(llvm::StringRef ModuleName,
194fe6c2400SChuanqi Xu                          PathRef RequiredSourceFile = PathRef()) override {
195*2b0e2255SPetr Polezhaev     Scanner.globalScan(Mangler);
196fe6c2400SChuanqi Xu     return Scanner.getSourceForModuleName(ModuleName);
197fe6c2400SChuanqi Xu   }
198fe6c2400SChuanqi Xu 
199fe6c2400SChuanqi Xu private:
200fe6c2400SChuanqi Xu   ModuleDependencyScanner Scanner;
201*2b0e2255SPetr Polezhaev   CommandMangler Mangler;
202fe6c2400SChuanqi Xu };
203fe6c2400SChuanqi Xu 
204fe6c2400SChuanqi Xu std::unique_ptr<ProjectModules> scanningProjectModules(
205fe6c2400SChuanqi Xu     std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,
206fe6c2400SChuanqi Xu     const ThreadsafeFS &TFS) {
207fe6c2400SChuanqi Xu   return std::make_unique<ScanningAllProjectModules>(CDB, TFS);
208fe6c2400SChuanqi Xu }
209fe6c2400SChuanqi Xu 
210fe6c2400SChuanqi Xu } // namespace clang::clangd
211