brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.2 KiB · 672e996 Raw
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