| 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(path&: FilePathDir); |
| 111 | DependencyScanningTool ScanningTool(Service, TFS.view(CWD: FilePathDir)); |
| 112 | |
| 113 | llvm::Expected<P1689Rule> ScanningResult = |
| 114 | ScanningTool.getP1689ModuleDependencyFile(Command: Cmd, CWD: Cmd.Directory); |
| 115 | |
| 116 | if (auto E = ScanningResult.takeError()) { |
| 117 | elog(Fmt: "Scanning modules dependencies for {0} failed: {1}" , Vals&: FilePath, |
| 118 | Vals: llvm::toString(E: 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(x: Required.ModuleName); |
| 131 | |
| 132 | return Result; |
| 133 | } |
| 134 | |
| 135 | void ModuleDependencyScanner::globalScan( |
| 136 | const ProjectModules::CommandMangler &Mangler) { |
| 137 | if (GlobalScanned) |
| 138 | return; |
| 139 | |
| 140 | for (auto &File : CDB->getAllFiles()) |
| 141 | scan(FilePath: File, Mangler); |
| 142 | |
| 143 | GlobalScanned = true; |
| 144 | } |
| 145 | |
| 146 | PathRef ModuleDependencyScanner::getSourceForModuleName( |
| 147 | llvm::StringRef ModuleName) const { |
| 148 | assert( |
| 149 | GlobalScanned && |
| 150 | "We should only call getSourceForModuleName after calling globalScan()" ); |
| 151 | |
| 152 | if (auto It = ModuleNameToSource.find(Key: ModuleName); |
| 153 | It != ModuleNameToSource.end()) |
| 154 | return It->second; |
| 155 | |
| 156 | return {}; |
| 157 | } |
| 158 | |
| 159 | std::vector<std::string> ModuleDependencyScanner::getRequiredModules( |
| 160 | PathRef File, const ProjectModules::CommandMangler &Mangler) { |
| 161 | auto ScanningResult = scan(FilePath: File, Mangler); |
| 162 | if (!ScanningResult) |
| 163 | return {}; |
| 164 | |
| 165 | return ScanningResult->RequiredModules; |
| 166 | } |
| 167 | } // namespace |
| 168 | |
| 169 | /// TODO: The existing `ScanningAllProjectModules` is not efficient. See the |
| 170 | /// comments in ModuleDependencyScanner for detail. |
| 171 | /// |
| 172 | /// In the future, we wish the build system can provide a well design |
| 173 | /// compilation database for modules then we can query that new compilation |
| 174 | /// database directly. Or we need to have a global long-live scanner to detect |
| 175 | /// the state of each file. |
| 176 | class ScanningAllProjectModules : public ProjectModules { |
| 177 | public: |
| 178 | ScanningAllProjectModules( |
| 179 | std::shared_ptr<const clang::tooling::CompilationDatabase> CDB, |
| 180 | const ThreadsafeFS &TFS) |
| 181 | : Scanner(CDB, TFS) {} |
| 182 | |
| 183 | ~ScanningAllProjectModules() override = default; |
| 184 | |
| 185 | std::vector<std::string> getRequiredModules(PathRef File) override { |
| 186 | return Scanner.getRequiredModules(File, Mangler); |
| 187 | } |
| 188 | |
| 189 | void setCommandMangler(CommandMangler Mangler) override { |
| 190 | this->Mangler = std::move(Mangler); |
| 191 | } |
| 192 | |
| 193 | /// RequiredSourceFile is not used intentionally. See the comments of |
| 194 | /// ModuleDependencyScanner for detail. |
| 195 | std::string getSourceForModuleName(llvm::StringRef ModuleName, |
| 196 | PathRef RequiredSourceFile) override { |
| 197 | Scanner.globalScan(Mangler); |
| 198 | return Scanner.getSourceForModuleName(ModuleName).str(); |
| 199 | } |
| 200 | |
| 201 | std::string getModuleNameForSource(PathRef File) override { |
| 202 | auto ScanningResult = Scanner.scan(FilePath: File, Mangler); |
| 203 | if (!ScanningResult || !ScanningResult->ModuleName) |
| 204 | return {}; |
| 205 | |
| 206 | return *ScanningResult->ModuleName; |
| 207 | } |
| 208 | |
| 209 | private: |
| 210 | ModuleDependencyScanner Scanner; |
| 211 | CommandMangler Mangler; |
| 212 | }; |
| 213 | |
| 214 | std::unique_ptr<ProjectModules> scanningProjectModules( |
| 215 | std::shared_ptr<const clang::tooling::CompilationDatabase> CDB, |
| 216 | const ThreadsafeFS &TFS) { |
| 217 | return std::make_unique<ScanningAllProjectModules>(args&: CDB, args: TFS); |
| 218 | } |
| 219 | |
| 220 | } // namespace clang::clangd |
| 221 | |