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