230 lines · cpp
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-exception6//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 14namespace clang::clangd {15namespace {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 function19/// or scan the whole project with `globalScan(vector<PathRef>)` member20/// function. See the comments of `globalScan` to see the details.21///22/// The ModuleDependencyScanner can get the directly required module names for a23/// specific source file. Also the ModuleDependencyScanner can get the source24/// 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 a27/// source file in the project. But the assumption is not strictly true even28/// besides the invalid projects. The language specification requires that every29/// module unit should be unique in a valid program. But a project can contain30/// multiple programs. Then it is valid that we can have multiple source files31/// declaring the same module in a project as long as these source files don't32/// interfere with each other.33class ModuleDependencyScanner {34public: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 the55 /// <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 providing58 /// a global module dependency scanner to monitor every file. Or we59 /// 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 language64 /// 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 files68 /// declaring the same module.69 PathRef getSourceForModuleName(llvm::StringRef ModuleName) const;70 71 /// Return the direct required modules. Indirect required modules are not72 /// included.73 std::vector<std::string>74 getRequiredModules(PathRef File,75 const ProjectModules::CommandMangler &Mangler);76 77private: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 92std::optional<ModuleDependencyScanner::ModuleDependencyInfo>93ModuleDependencyScanner::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 with101 // 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 Result.ModuleName = ScanningResult->Provides->ModuleName;126 127 auto [Iter, Inserted] = ModuleNameToSource.try_emplace(128 ScanningResult->Provides->ModuleName, FilePath);129 130 if (!Inserted && Iter->second != FilePath) {131 elog("Detected multiple source files ({0}, {1}) declaring the same "132 "module: '{2}'. "133 "Now clangd may find the wrong source in such case.",134 Iter->second, FilePath, ScanningResult->Provides->ModuleName);135 }136 }137 138 for (auto &Required : ScanningResult->Requires)139 Result.RequiredModules.push_back(Required.ModuleName);140 141 return Result;142}143 144void ModuleDependencyScanner::globalScan(145 const ProjectModules::CommandMangler &Mangler) {146 if (GlobalScanned)147 return;148 149 for (auto &File : CDB->getAllFiles())150 scan(File, Mangler);151 152 GlobalScanned = true;153}154 155PathRef ModuleDependencyScanner::getSourceForModuleName(156 llvm::StringRef ModuleName) const {157 assert(158 GlobalScanned &&159 "We should only call getSourceForModuleName after calling globalScan()");160 161 if (auto It = ModuleNameToSource.find(ModuleName);162 It != ModuleNameToSource.end())163 return It->second;164 165 return {};166}167 168std::vector<std::string> ModuleDependencyScanner::getRequiredModules(169 PathRef File, const ProjectModules::CommandMangler &Mangler) {170 auto ScanningResult = scan(File, Mangler);171 if (!ScanningResult)172 return {};173 174 return ScanningResult->RequiredModules;175}176} // namespace177 178/// TODO: The existing `ScanningAllProjectModules` is not efficient. See the179/// comments in ModuleDependencyScanner for detail.180///181/// In the future, we wish the build system can provide a well design182/// compilation database for modules then we can query that new compilation183/// database directly. Or we need to have a global long-live scanner to detect184/// the state of each file.185class ScanningAllProjectModules : public ProjectModules {186public:187 ScanningAllProjectModules(188 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,189 const ThreadsafeFS &TFS)190 : Scanner(CDB, TFS) {}191 192 ~ScanningAllProjectModules() override = default;193 194 std::vector<std::string> getRequiredModules(PathRef File) override {195 return Scanner.getRequiredModules(File, Mangler);196 }197 198 void setCommandMangler(CommandMangler Mangler) override {199 this->Mangler = std::move(Mangler);200 }201 202 /// RequiredSourceFile is not used intentionally. See the comments of203 /// ModuleDependencyScanner for detail.204 std::string getSourceForModuleName(llvm::StringRef ModuleName,205 PathRef RequiredSourceFile) override {206 Scanner.globalScan(Mangler);207 return Scanner.getSourceForModuleName(ModuleName).str();208 }209 210 std::string getModuleNameForSource(PathRef File) override {211 auto ScanningResult = Scanner.scan(File, Mangler);212 if (!ScanningResult || !ScanningResult->ModuleName)213 return {};214 215 return *ScanningResult->ModuleName;216 }217 218private:219 ModuleDependencyScanner Scanner;220 CommandMangler Mangler;221};222 223std::unique_ptr<ProjectModules> scanningProjectModules(224 std::shared_ptr<const clang::tooling::CompilationDatabase> CDB,225 const ThreadsafeFS &TFS) {226 return std::make_unique<ScanningAllProjectModules>(CDB, TFS);227}228 229} // namespace clang::clangd230