//===------------------ ProjectModules.h -------------------------*- C++-*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "ProjectModules.h" #include "support/Logger.h" #include "clang/Tooling/DependencyScanning/DependencyScanningService.h" #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" namespace clang::clangd { namespace { /// A scanner to query the dependency information for C++20 Modules. /// /// The scanner can scan a single file with `scan(PathRef)` member function /// or scan the whole project with `globalScan(vector)` member /// function. See the comments of `globalScan` to see the details. /// /// The ModuleDependencyScanner can get the directly required module names for a /// specific source file. Also the ModuleDependencyScanner can get the source /// file declaring the primary module interface for a specific module name. /// /// IMPORTANT NOTE: we assume that every module unit is only declared once in a /// source file in the project. But the assumption is not strictly true even /// besides the invalid projects. The language specification requires that every /// module unit should be unique in a valid program. But a project can contain /// multiple programs. Then it is valid that we can have multiple source files /// declaring the same module in a project as long as these source files don't /// interfere with each other. class ModuleDependencyScanner { public: ModuleDependencyScanner( std::shared_ptr CDB, const ThreadsafeFS &TFS) : CDB(CDB), TFS(TFS), Service(tooling::dependencies::ScanningMode::CanonicalPreprocessing, tooling::dependencies::ScanningOutputFormat::P1689) {} /// The scanned modules dependency information for a specific source file. struct ModuleDependencyInfo { /// The name of the module if the file is a module unit. std::optional ModuleName; /// A list of names for the modules that the file directly depends. std::vector RequiredModules; }; /// Scanning the single file specified by \param FilePath. std::optional scan(PathRef FilePath, const ProjectModules::CommandMangler &Mangler); /// Scanning every source file in the current project to get the /// to map. /// TODO: We should find an efficient method to get the /// to map. We can make it either by providing /// a global module dependency scanner to monitor every file. Or we /// can simply require the build systems (or even the end users) /// to provide the map. void globalScan(const ProjectModules::CommandMangler &Mangler); /// Get the source file from the module name. Note that the language /// guarantees all the module names are unique in a valid program. /// This function should only be called after globalScan. /// /// TODO: We should handle the case that there are multiple source files /// declaring the same module. PathRef getSourceForModuleName(llvm::StringRef ModuleName) const; /// Return the direct required modules. Indirect required modules are not /// included. std::vector getRequiredModules(PathRef File, const ProjectModules::CommandMangler &Mangler); private: std::shared_ptr CDB; const ThreadsafeFS &TFS; // Whether the scanner has scanned the project globally. bool GlobalScanned = false; clang::tooling::dependencies::DependencyScanningService Service; // TODO: Add a scanning cache. // Map module name to source file path. llvm::StringMap ModuleNameToSource; }; std::optional ModuleDependencyScanner::scan(PathRef FilePath, const ProjectModules::CommandMangler &Mangler) { auto Candidates = CDB->getCompileCommands(FilePath); if (Candidates.empty()) return std::nullopt; // Choose the first candidates as the compile commands as the file. // Following the same logic with // DirectoryBasedGlobalCompilationDatabase::getCompileCommand. tooling::CompileCommand Cmd = std::move(Candidates.front()); if (Mangler) Mangler(Cmd, FilePath); using namespace clang::tooling::dependencies; llvm::SmallString<128> FilePathDir(FilePath); llvm::sys::path::remove_filename(FilePathDir); DependencyScanningTool ScanningTool(Service, TFS.view(FilePathDir)); llvm::Expected ScanningResult = ScanningTool.getP1689ModuleDependencyFile(Cmd, Cmd.Directory); if (auto E = ScanningResult.takeError()) { elog("Scanning modules dependencies for {0} failed: {1}", FilePath, llvm::toString(std::move(E))); return std::nullopt; } ModuleDependencyInfo Result; if (ScanningResult->Provides) { ModuleNameToSource[ScanningResult->Provides->ModuleName] = FilePath; Result.ModuleName = ScanningResult->Provides->ModuleName; } for (auto &Required : ScanningResult->Requires) Result.RequiredModules.push_back(Required.ModuleName); return Result; } void ModuleDependencyScanner::globalScan( const ProjectModules::CommandMangler &Mangler) { if (GlobalScanned) return; for (auto &File : CDB->getAllFiles()) scan(File, Mangler); GlobalScanned = true; } PathRef ModuleDependencyScanner::getSourceForModuleName( llvm::StringRef ModuleName) const { assert( GlobalScanned && "We should only call getSourceForModuleName after calling globalScan()"); if (auto It = ModuleNameToSource.find(ModuleName); It != ModuleNameToSource.end()) return It->second; return {}; } std::vector ModuleDependencyScanner::getRequiredModules( PathRef File, const ProjectModules::CommandMangler &Mangler) { auto ScanningResult = scan(File, Mangler); if (!ScanningResult) return {}; return ScanningResult->RequiredModules; } } // namespace /// TODO: The existing `ScanningAllProjectModules` is not efficient. See the /// comments in ModuleDependencyScanner for detail. /// /// In the future, we wish the build system can provide a well design /// compilation database for modules then we can query that new compilation /// database directly. Or we need to have a global long-live scanner to detect /// the state of each file. class ScanningAllProjectModules : public ProjectModules { public: ScanningAllProjectModules( std::shared_ptr CDB, const ThreadsafeFS &TFS) : Scanner(CDB, TFS) {} ~ScanningAllProjectModules() override = default; std::vector getRequiredModules(PathRef File) override { return Scanner.getRequiredModules(File, Mangler); } void setCommandMangler(CommandMangler Mangler) override { this->Mangler = std::move(Mangler); } /// RequiredSourceFile is not used intentionally. See the comments of /// ModuleDependencyScanner for detail. std::string getSourceForModuleName(llvm::StringRef ModuleName, PathRef RequiredSourceFile) override { Scanner.globalScan(Mangler); return Scanner.getSourceForModuleName(ModuleName).str(); } std::string getModuleNameForSource(PathRef File) override { auto ScanningResult = Scanner.scan(File, Mangler); if (!ScanningResult || !ScanningResult->ModuleName) return {}; return *ScanningResult->ModuleName; } private: ModuleDependencyScanner Scanner; CommandMangler Mangler; }; std::unique_ptr scanningProjectModules( std::shared_ptr CDB, const ThreadsafeFS &TFS) { return std::make_unique(CDB, TFS); } } // namespace clang::clangd