https://github.com/naveen-seth updated https://github.com/llvm/llvm-project/pull/152770
>From f9dad8d99616e0799c318dda1a8e50351021f169 Mon Sep 17 00:00:00 2001 From: Naveen Seth Hanig <naveen.ha...@outlook.com> Date: Fri, 22 Aug 2025 04:58:18 +0200 Subject: [PATCH] [clang][modules-driver] Add dependency scan and dependency graph This patch is part of a series to support driver-managed module builds. It adds support for the discovery of module dependencies from within the driver and for generation of the module dependency graph. The dependency scan and graph support both Clang modules and C++ named modules. The generated dependency graph can be output in Graphviz DOT format as a remark. RFC discussing linking the driver against additional libraries: https://discourse.llvm.org/t/rfc-driver-link-the-driver-against-clangdependencyscanning-clangast-clangfrontend-clangserialization-and-clanglex RFC for driver-managed module builds: https://discourse.llvm.org/t/rfc-modules-support-simple-c-20-modules-use-from-the-clang-driver-without-a-build-system --- .../clang/Basic/DiagnosticDriverKinds.td | 12 + .../include/clang/Driver/DependencyScanner.h | 387 ++++++++ clang/lib/Driver/CMakeLists.txt | 2 + clang/lib/Driver/DependencyScanner.cpp | 859 ++++++++++++++++++ clang/lib/Driver/Driver.cpp | 20 +- .../modules-driver-dep-scan-diagnostics.cpp | 25 + .../modules-driver-dep-scan-graphviz.cpp | 88 ++ .../modules-driver-duplicate-named-module.cpp | 21 + 8 files changed, 1413 insertions(+), 1 deletion(-) create mode 100644 clang/include/clang/Driver/DependencyScanner.h create mode 100644 clang/lib/Driver/DependencyScanner.cpp create mode 100644 clang/test/Driver/modules-driver-dep-scan-diagnostics.cpp create mode 100644 clang/test/Driver/modules-driver-dep-scan-graphviz.cpp create mode 100644 clang/test/Driver/modules-driver-duplicate-named-module.cpp diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index 6df8f9932f30f..daa696455d599 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -587,6 +587,18 @@ def remark_found_cxx20_module_usage : Remark< def remark_performing_driver_managed_module_build : Remark< "performing driver managed module build">, InGroup<ModulesDriver>; +def err_failed_depdendency_scan : Error< + "failed to perform dependency scan">; +def remark_failed_dependency_scan_for_input : Remark< + "dependency scan failed for source input '%0'">, + InGroup<ModulesDriver>; +def err_mod_graph_named_module_redefinition : Error< + "duplicate definitions of C++20 named module '%0' in '%1' and '%2'">; +def err_building_depdendency_graph : Error< + "failed to construct the module dependency graph">; +def remark_printing_module_graph : Remark< + "printing module dependency graph">, + InGroup<ModulesDriver>; def warn_drv_delayed_template_parsing_after_cxx20 : Warning< "-fdelayed-template-parsing is deprecated after C++20">, diff --git a/clang/include/clang/Driver/DependencyScanner.h b/clang/include/clang/Driver/DependencyScanner.h new file mode 100644 index 0000000000000..35bae7aab23b4 --- /dev/null +++ b/clang/include/clang/Driver/DependencyScanner.h @@ -0,0 +1,387 @@ +//===- DependencyScanner.h - Module dependency discovery --------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file defines the module dependency graph and dependency-scanning +/// functionality +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_DRIVER_DEPENDENCYSCANNER_H +#define LLVM_CLANG_DRIVER_DEPENDENCYSCANNER_H + +#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" +#include "llvm/ADT/DirectedGraph.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/Allocator.h" + +namespace llvm::opt { +class DerivedArgList; +} // namespace llvm::opt + +namespace clang { +class DiagnosticsEngine; +namespace driver { +class Driver; +} // namespace driver +} // namespace clang + +namespace clang::driver::dependencies { + +using ClangModuleDeps = tooling::dependencies::ModuleDeps; +using ClangModuleGraph = tooling::dependencies::ModuleDepsGraph; +using tooling::dependencies::ModuleID; +using tooling::dependencies::TranslationUnitDeps; + +//===----------------------------------------------------------------------===// +// Dependency Scan +//===----------------------------------------------------------------------===// + +class DependencyScanError : public llvm::ErrorInfo<DependencyScanError> { +public: + static char ID; + + void log(llvm::raw_ostream &OS) const override { + OS << "error while performing dependency scan\n"; + } + + std::error_code convertToErrorCode() const override { + return llvm::errc::not_supported; + } +}; + +/// Represents the dependency scanning result for a single source input. +struct TranslationUnitScanResult { + TranslationUnitScanResult(size_t JobIndex, std::string &&Filename, + TranslationUnitDeps &&TUDeps) + : JobIndex(JobIndex), Filename(std::move(Filename)), + TUDeps(std::move(TUDeps)) {} + /// Index of the job associated with this scan result, which the driver will + /// later create. + size_t JobIndex; + + /// The source input for which the scan was run. + std::string Filename; + + /// The full dependencies and Clang module graph for this input. + TranslationUnitDeps TUDeps; +}; + +/// Computes module dependencies for the given driver command line. +/// +/// \param ClangExecutable - The path to the main clang executable. +/// \param Diags - The driver's diagnostics engine. +/// \param Args - The driver's command line. +/// +/// \returns A vector of scan results (one per scannable source input), or an +/// error if any input fails to scan. The order of scan results is +/// deterministic. +llvm::Expected<llvm::SmallVector<TranslationUnitScanResult, 0>> +scanDependencies(llvm::StringRef ClangExecutable, + clang::DiagnosticsEngine &Diags, + const llvm::opt::DerivedArgList &Args); + +//===----------------------------------------------------------------------===// +// Module Dependency Graph +//===----------------------------------------------------------------------===// + +class MDGNode; +class MDGEdge; +using MDGNodeBase = llvm::DGNode<MDGNode, MDGEdge>; +using MDGEdgeBase = llvm::DGEdge<MDGNode, MDGEdge>; +using ModuleDepGraphBase = llvm::DirectedGraph<MDGNode, MDGEdge>; + +/// Abstract base class for all node kinds in the module dependency graph. +class MDGNode : public MDGNodeBase { +public: + enum class NodeKind { + Root, + ClangModule, + NamedCXXModule, + NonModule, + }; + + explicit MDGNode(NodeKind Kind) : Kind(Kind) {} + + /// Returns this node's kind. + NodeKind getKind() const { return Kind; } + + /// Returns the list of Clang modules this module/translation unit directly + /// depends on. + virtual llvm::ArrayRef<ModuleID> getClangModuleDeps() const { return {}; } + + /// Returns the list of C++20 named modules this translation unit directly + /// depends + virtual llvm::ArrayRef<std::string> getCXXNamedModuleDeps() const { + return {}; + } + +protected: + virtual ~MDGNode() = 0; + +private: + const NodeKind Kind; +}; + +/// Represents the root node of the module dependency graph. +/// +/// The root node only serves as an entry point for graph traversal. +/// It should have an edge to each node that would otherwise have no incoming +/// edges, ensuring there is always a path from the root to any node in the +/// graph. +/// There should be exactly one such root node in a given graph. +class RootMDGNode final : public MDGNode { +public: + RootMDGNode() : MDGNode(NodeKind::Root) {} + + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + return N->getKind() == NodeKind::Root; + } +}; + +/// Base class defining common functionality for nodes that represent a +/// translation unit from a command line source input. +class SourceInputBackedMDGNode : public MDGNode { +protected: + explicit SourceInputBackedMDGNode( + const NodeKind Kind, const TranslationUnitScanResult &BackingScanResult) + : MDGNode(Kind), BackingScanResult(&BackingScanResult) {} + + /// The backing scan result, owned by the module dependency graph. + const TranslationUnitScanResult *BackingScanResult; + +public: + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + auto K = N->getKind(); + return K == NodeKind::NonModule || K == NodeKind::NamedCXXModule; + } + + /// Returns the path of this translation unit's main source file. + llvm::StringRef getFilename() const { return BackingScanResult->Filename; } + + /// Returns the index of the -cc1 driver job for this translation unit, which + /// the driver will later create. + size_t getJobIndex() const { return BackingScanResult->JobIndex; } + + llvm::ArrayRef<std::string> getCXXNamedModuleDeps() const override { + return BackingScanResult->TUDeps.NamedModuleDeps; + } + + llvm::ArrayRef<ModuleID> getClangModuleDeps() const override { + return BackingScanResult->TUDeps.ClangModuleDeps; + } +}; + +/// Subclass of MDGNode representing a translation unit that does not provide +/// any module. +class NonModuleMDGNode final : public SourceInputBackedMDGNode { +public: + explicit NonModuleMDGNode(const TranslationUnitScanResult &BackingScanResult) + : SourceInputBackedMDGNode(NodeKind::NonModule, BackingScanResult) {} + + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + return N->getKind() == NodeKind::NonModule; + } +}; + +/// Subclass of MDGNode representing a translation unit that provides a C++20 +/// named module. +class NamedCXXModuleMDGNode final : public SourceInputBackedMDGNode { +public: + explicit NamedCXXModuleMDGNode( + const TranslationUnitScanResult &BackingScanResult) + : SourceInputBackedMDGNode(NodeKind::NamedCXXModule, BackingScanResult) {} + + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + return N->getKind() == NodeKind::NamedCXXModule; + } + + /// Returns the unique identifier for the named C++ module this translation + /// unit exports. + const ModuleID &getModuleID() const { return BackingScanResult->TUDeps.ID; } +}; + +/// Subclass of MDGNode representing a Clang module unit. +class ClangModuleMDGNode final : public MDGNode { +public: + explicit ClangModuleMDGNode( + const ClangModuleDeps &BackingModuleDeps) + : MDGNode(NodeKind::ClangModule), BackingModuleDeps(&BackingModuleDeps) {} + + /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc. + static bool classof(const MDGNode *N) { + return N->getKind() == NodeKind::ClangModule; + } + + /// Returns the unique identifier for this Clang module. + const ModuleID &getModuleID() const { return BackingModuleDeps->ID; } + + /// Returns the list of Clang modules this module unit directly depends on. + llvm::ArrayRef<ModuleID> getClangModuleDeps() const override { + return BackingModuleDeps->ClangModuleDeps; + } + +private: + /// The backing scan result, owned by the module dependency graph. + const ClangModuleDeps *BackingModuleDeps; +}; + +/// Represents an import relation in the module dependency graph, directed from +/// the imported module to the importer. +class MDGEdge : public MDGEdgeBase { +public: + explicit MDGEdge(MDGNode &N) : MDGEdgeBase(N) {} + MDGEdge() = delete; +}; + +namespace detail { +class ModuleDepGraphBuilder; +} // namespace detail + +/// A directed graph describing module import relationships. +/// +/// The graph owns its nodes, edges, and the dependency scan results from which +/// it was created. +/// Non-root nodes provide a view into the backing scan results. +class ModuleDepGraph : public ModuleDepGraphBase { +public: + explicit ModuleDepGraph( + llvm::SmallVectorImpl<TranslationUnitScanResult> &&ScanResults) + : BackingScanResults(std::move(ScanResults)) {} + + MDGNode *getRoot() { return Root; } + + const MDGNode *getRoot() const { return Root; } + +private: + friend class detail::ModuleDepGraphBuilder; + + llvm::BumpPtrAllocator BumpPtrAlloc; + llvm::SmallVector<TranslationUnitScanResult, 0> BackingScanResults; + RootMDGNode *Root = nullptr; +}; + +/// Build a module dependency graph from the given \c ScanResults. +/// +/// \returns The constructed graph, or an error if conflicting module +/// definitions are found. +llvm::Expected<ModuleDepGraph> +buildModuleDepGraph(llvm::SmallVectorImpl<TranslationUnitScanResult> &&Scans, + DiagnosticsEngine &Diags); + +void writeModuleDepGraph(raw_ostream &OS, const ModuleDepGraph &G); + +} // namespace clang::driver::dependencies + +//===----------------------------------------------------------------------===// +// Module Dependency Graph: GraphTraits specialization +//===----------------------------------------------------------------------===// + +namespace llvm { +/// non-const versions of the GraphTraits specializations for ModuleDepGraph. +template <> struct GraphTraits<clang::driver::dependencies::MDGNode *> { + using NodeTy = clang::driver::dependencies::MDGNode; + using NodeRef = NodeTy *; + + static NodeRef MDGGetTargetNode(clang::driver::dependencies::MDGEdgeBase *E) { + return &E->getTargetNode(); + } + + // Provide a mapped iterator so that GraphTraits-based implementations can + // find the target nodes without explicitly going through the edges. + using ChildIteratorType = + mapped_iterator<NodeTy::iterator, decltype(&MDGGetTargetNode)>; + using ChildEdgeIteratorType = NodeTy::iterator; + + static NodeRef getEntryNode(NodeRef N) { return N; } + + static ChildIteratorType child_begin(NodeRef N) { + return ChildIteratorType(N->begin(), &MDGGetTargetNode); + } + + static ChildIteratorType child_end(NodeRef N) { + return ChildIteratorType(N->end(), &MDGGetTargetNode); + } + + static ChildEdgeIteratorType child_edge_begin(NodeRef N) { + return N->begin(); + } + static ChildEdgeIteratorType child_edge_end(NodeRef N) { return N->end(); } +}; + +template <> +struct GraphTraits<clang::driver::dependencies::ModuleDepGraph *> + : GraphTraits<clang::driver::dependencies::MDGNode *> { + using GraphTy = clang::driver::dependencies::ModuleDepGraph; + using GraphRef = GraphTy *; + using NodeRef = clang::driver::dependencies::MDGNode *; + + using nodes_iterator = GraphTy::iterator; + + static NodeRef getEntryNode(GraphRef G) { return G->getRoot(); } + + static nodes_iterator nodes_begin(GraphRef G) { return G->begin(); } + + static nodes_iterator nodes_end(GraphRef G) { return G->end(); } +}; + +/// const versions of the GraphTrait specializations for ModuleDepGraph. +template <> struct GraphTraits<const clang::driver::dependencies::MDGNode *> { + using NodeTy = const clang::driver::dependencies::MDGNode; + using NodeRef = NodeTy *; + + static NodeRef + MDGGetTargetNode(const clang::driver::dependencies::MDGEdgeBase *E) { + return &E->getTargetNode(); + } + + // Provide a mapped iterator so that GraphTraits-based implementations can + // find the target nodes without explicitly going through the edges. + using ChildIteratorType = + mapped_iterator<NodeTy::const_iterator, decltype(&MDGGetTargetNode)>; + using ChildEdgeIteratorType = NodeTy::const_iterator; + + static NodeRef getEntryNode(NodeRef N) { return N; } + + static ChildIteratorType child_begin(NodeRef N) { + return ChildIteratorType(N->begin(), &MDGGetTargetNode); + } + + static ChildIteratorType child_end(NodeRef N) { + return ChildIteratorType(N->end(), &MDGGetTargetNode); + } + + static ChildEdgeIteratorType child_edge_begin(NodeRef N) { + return N->begin(); + } + + static ChildEdgeIteratorType child_edge_end(NodeRef N) { return N->end(); } +}; + +template <> +struct GraphTraits<const clang::driver::dependencies::ModuleDepGraph *> + : GraphTraits<const clang::driver::dependencies::MDGNode *> { + using GraphTy = const clang::driver::dependencies::ModuleDepGraph; + using GraphRef = GraphTy *; + using NodeRef = const clang::driver::dependencies::MDGNode *; + + using nodes_iterator = GraphTy::const_iterator; + + static NodeRef getEntryNode(GraphRef G) { return G->getRoot(); } + + static nodes_iterator nodes_begin(GraphRef G) { return G->begin(); } + + static nodes_iterator nodes_end(GraphRef G) { return G->end(); } +}; +} // namespace llvm + +#endif diff --git a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt index 7c4f70b966c48..4c0c1a7a6a5ee 100644 --- a/clang/lib/Driver/CMakeLists.txt +++ b/clang/lib/Driver/CMakeLists.txt @@ -18,6 +18,7 @@ add_clang_library(clangDriver Action.cpp Compilation.cpp Distro.cpp + DependencyScanner.cpp Driver.cpp DriverOptions.cpp Job.cpp @@ -98,6 +99,7 @@ add_clang_library(clangDriver LINK_LIBS clangBasic + clangDependencyScanning clangLex ${system_libs} ) diff --git a/clang/lib/Driver/DependencyScanner.cpp b/clang/lib/Driver/DependencyScanner.cpp new file mode 100644 index 0000000000000..751c13432bc9c --- /dev/null +++ b/clang/lib/Driver/DependencyScanner.cpp @@ -0,0 +1,859 @@ +//===- DependencyScanner.cpp - Module dependency discovery ----------------===// +// +// 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 "clang/Driver/DependencyScanner.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/InputInfo.h" +#include "clang/Driver/Job.h" +#include "clang/Driver/Options.h" +#include "clang/Driver/Tool.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/TypeSwitch.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/DOTGraphTraits.h" +#include "llvm/Support/GraphWriter.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/TargetParser/Host.h" + +using namespace clang::tooling::dependencies; +using namespace clang; +using namespace llvm::opt; + +//===----------------------------------------------------------------------===// +// Dependency Scan Diagnostic Reporting Utilities +//===----------------------------------------------------------------------===// + +namespace { +/// Utility struct to represent a CharSourceRange inside of a +/// StandaloneDiagnostic. +struct SourceOffsetRange { + SourceOffsetRange(CharSourceRange Range, const SourceManager &SrcMgr, + const LangOptions &LangOpts); + unsigned Begin = 0; + unsigned End = 0; + bool IsTokenRange = false; +}; + +/// Utility struct to represent a FixItHint inside of a StandaloneDiagnostic. +struct StandaloneFixIt { + StandaloneFixIt(const SourceManager &SrcMgr, const LangOptions &LangOpts, + const FixItHint &FixIt); + + SourceOffsetRange RemoveRange; + SourceOffsetRange InsertFromRange; + std::string CodeToInsert; + bool BeforePreviousInsertions = false; +}; + +/// Represents a StoredDiagnostic in a form that can be retained until after its +/// SourceManager has been destroyed. +/// +/// Source locations are stored as combination filename and offsets into that +/// file. +/// To report the diagnostic, it must first be translated back into a +/// StoredDiagnostic with a new associated SourceManager. +struct StandaloneDiagnostic { + explicit StandaloneDiagnostic(const StoredDiagnostic &StoredDiag); + + LangOptions LangOpts; + SrcMgr::CharacteristicKind FileKind; + DiagnosticsEngine::Level Level; + unsigned ID = 0; + unsigned FileOffset = 0; + std::string Filename; + std::string Message; + SmallVector<SourceOffsetRange, 0> Ranges; + SmallVector<StandaloneFixIt, 0> FixIts; +}; +} // anonymous namespace + +SourceOffsetRange::SourceOffsetRange(CharSourceRange Range, + const SourceManager &SrcMgr, + const LangOptions &LangOpts) + : IsTokenRange(Range.isTokenRange()) { + const auto FileRange = Lexer::makeFileCharRange(Range, SrcMgr, LangOpts); + Begin = SrcMgr.getFileOffset(FileRange.getBegin()); + End = SrcMgr.getFileOffset(FileRange.getEnd()); +} + +StandaloneFixIt::StandaloneFixIt(const SourceManager &SrcMgr, + const LangOptions &LangOpts, + const FixItHint &FixIt) + : RemoveRange(FixIt.RemoveRange, SrcMgr, LangOpts), + InsertFromRange(FixIt.InsertFromRange, SrcMgr, LangOpts), + CodeToInsert(FixIt.CodeToInsert), + BeforePreviousInsertions(FixIt.BeforePreviousInsertions) {} + +/// If a custom working directory is set for \c SrcMgr, this returns the +/// absolute path of \c Filename to make it independent. +/// Otherwise, returns the original string. +static std::string canonicalizeFilename(const SourceManager &SrcMgr, + StringRef Filename) { + SmallString<256> Abs(Filename); + if (!llvm::sys::path::is_absolute(Abs)) { + if (const auto &CWD = + SrcMgr.getFileManager().getFileSystemOpts().WorkingDir; + !CWD.empty()) + llvm::sys::fs::make_absolute(CWD, Abs); + } + return std::string(Abs.str()); +} + +// FIXME: LangOpts is not properly saved because the LangOptions is not +// copyable! +StandaloneDiagnostic::StandaloneDiagnostic(const StoredDiagnostic &StoredDiag) + : Level(StoredDiag.getLevel()), ID(StoredDiag.getID()), + Message(StoredDiag.getMessage()) { + const FullSourceLoc &FullLoc = StoredDiag.getLocation(); + // This is not an invalid diagnostic; invalid SourceLocations are used to + // represent diagnostics without a specific SourceLocation. + if (FullLoc.isInvalid()) + return; + + const auto &SrcMgr = FullLoc.getManager(); + FileKind = SrcMgr.getFileCharacteristic(static_cast<SourceLocation>(FullLoc)); + const auto FileLoc = SrcMgr.getFileLoc(static_cast<SourceLocation>(FullLoc)); + FileOffset = SrcMgr.getFileOffset(FileLoc); + const auto PathRef = SrcMgr.getFilename(FileLoc); + assert(!PathRef.empty() && "diagnostic with location has no source file?"); + Filename = canonicalizeFilename(SrcMgr, PathRef); + + Ranges.reserve(StoredDiag.getRanges().size()); + for (const auto &Range : StoredDiag.getRanges()) + Ranges.emplace_back(Range, SrcMgr, LangOpts); + + FixIts.reserve(StoredDiag.getFixIts().size()); + for (const auto &FixIt : StoredDiag.getFixIts()) + FixIts.emplace_back(SrcMgr, LangOpts, FixIt); +} + +/// Translates \c StandaloneDiag into a StoredDiagnostic, associating it with +/// the provided FileManager and SourceManager. +static StoredDiagnostic +translateStandaloneDiag(FileManager &FileMgr, SourceManager &SrcMgr, + StandaloneDiagnostic &&StandaloneDiag) { + const auto FileRef = FileMgr.getOptionalFileRef(StandaloneDiag.Filename); + if (!FileRef) + return StoredDiagnostic(StandaloneDiag.Level, StandaloneDiag.ID, + std::move(StandaloneDiag.Message)); + + const auto FileID = + SrcMgr.getOrCreateFileID(*FileRef, StandaloneDiag.FileKind); + const auto FileLoc = SrcMgr.getLocForStartOfFile(FileID); + assert(FileLoc.isValid() && "StandaloneDiagnostic should only use FilePath " + "for encoding a valid source location."); + const auto DiagLoc = FileLoc.getLocWithOffset(StandaloneDiag.FileOffset); + const FullSourceLoc Loc(DiagLoc, SrcMgr); + + auto ConvertOffsetRange = [&](const SourceOffsetRange &Range) { + return CharSourceRange(SourceRange(FileLoc.getLocWithOffset(Range.Begin), + FileLoc.getLocWithOffset(Range.End)), + Range.IsTokenRange); + }; + SmallVector<CharSourceRange> TranslatedRanges; + TranslatedRanges.reserve(StandaloneDiag.Ranges.size()); + llvm::transform(StandaloneDiag.Ranges, std::back_inserter(TranslatedRanges), + ConvertOffsetRange); + + SmallVector<FixItHint> TranslatedFixIts; + TranslatedFixIts.reserve(StandaloneDiag.FixIts.size()); + for (const auto &FixIt : StandaloneDiag.FixIts) { + FixItHint TranslatedFixIt; + TranslatedFixIt.CodeToInsert = std::string(FixIt.CodeToInsert); + TranslatedFixIt.RemoveRange = ConvertOffsetRange(FixIt.RemoveRange); + TranslatedFixIt.InsertFromRange = ConvertOffsetRange(FixIt.InsertFromRange); + TranslatedFixIt.BeforePreviousInsertions = FixIt.BeforePreviousInsertions; + TranslatedFixIts.push_back(std::move(TranslatedFixIt)); + } + + return StoredDiagnostic(StandaloneDiag.Level, StandaloneDiag.ID, + StandaloneDiag.Message, Loc, TranslatedRanges, + TranslatedFixIts); +} + +namespace { +/// RAII utility to report StandaloneDiagnostics through a DiagnosticsEngine. +/// +// The driver's DiagnosticEngine usually does not have a SourceManager at this +/// point of building the compilation, in which case the StandaloneDiagReporter +/// supplies its own. +class StandaloneDiagReporter { +public: + explicit StandaloneDiagReporter(DiagnosticsEngine &Diags) : Diags(Diags) { + if (!Diags.hasSourceManager()) { + FileSystemOptions Opts; + Opts.WorkingDir = "."; + OwnedFileMgr = llvm::makeIntrusiveRefCnt<FileManager>(std::move(Opts)); + OwnedSrcMgr = + llvm::makeIntrusiveRefCnt<SourceManager>(Diags, *OwnedFileMgr); + } + } + + void Report(StandaloneDiagnostic &&StandaloneDiag) const { + const auto StoredDiag = translateStandaloneDiag( + getFileManager(), getSourceManager(), std::move(StandaloneDiag)); + Diags.getClient()->BeginSourceFile(StandaloneDiag.LangOpts, nullptr); + Diags.Report(StoredDiag); + Diags.getClient()->EndSourceFile(); + } + +private: + DiagnosticsEngine &Diags; + IntrusiveRefCntPtr<FileManager> OwnedFileMgr; + IntrusiveRefCntPtr<SourceManager> OwnedSrcMgr; + + FileManager &getFileManager() const { + if (OwnedFileMgr) + return *OwnedFileMgr; + return Diags.getSourceManager().getFileManager(); + } + + SourceManager &getSourceManager() const { + if (OwnedSrcMgr) + return *OwnedSrcMgr; + return Diags.getSourceManager(); + } +}; +} // anonymous namespace + +namespace { +/// Collects diagnostics in a form that can be retained until after their +/// associated source manager is destroyed. +class StandaloneDiagCollector : public DiagnosticConsumer { +public: + void BeginSourceFile(const LangOptions &LangOpts, + const Preprocessor *PP = nullptr) override {} + + void HandleDiagnostic(DiagnosticsEngine::Level Level, + const Diagnostic &Info) override { + StoredDiagnostic StoredDiag(Level, Info); + StandaloneDiags.emplace_back(StoredDiag); + } + + void EndSourceFile() override {} + + SmallVector<StandaloneDiagnostic, 0> takeDiags() { + SmallVector<StandaloneDiagnostic, 0> Out; + Out.swap(StandaloneDiags); + return Out; + } + +private: + SmallVector<StandaloneDiagnostic, 0> StandaloneDiags; +}; +} // anonymous namespace + +//===----------------------------------------------------------------------===// +// Dependency Scan: Scanning Task Generation +//===----------------------------------------------------------------------===// + +namespace { +/// Represents a dependency scanning task for a single source input. +struct CC1ScanTask { + CC1ScanTask(size_t JobIndex, std::string &&Filename, + std::vector<std::string> &&CC1CommandLine) + : JobIndex(JobIndex), Filename(std::move(Filename)), + CC1CommandLine(std::move(CC1CommandLine)) {} + + /// Index of the corresponding job inside the replicated driver from which + /// this scanning task was generated. This index is used to correlate the + /// produced scan result with the job that the calling driver will later + /// create. + size_t JobIndex; + + /// The source input for which this task was generated for. + std::string Filename; + + /// The -cc1 command line to invoke the dependency scan with. + std::vector<std::string> CC1CommandLine; +}; +} // anonymous namespace + +/// Non-consuming version of llvm::opt::ArgList::AddAllArgsExcept. +/// +/// Append all arguments onto the \c Output as strings, except those which match +/// any option defined in \c ExcludedIds. +static void addAllArgsExcept(const DerivedArgList &Args, + ArrayRef<OptSpecifier> ExcludedIds, + ArgStringList &Output) { + using namespace llvm::opt; + auto IsNotExcluded = [&](const Arg *Arg) -> bool { + return llvm::none_of(ExcludedIds, [&](const OptSpecifier Id) { + return Arg->getOption().matches(Id); + }); + }; + for (const auto *Arg : llvm::make_filter_range(Args, IsNotExcluded)) + Arg->render(Args, Output); +} + +static ArgStringList buildDummyDriverCommandLine(StringRef ClangProgramPath, + const DerivedArgList &Args) { + using namespace driver::options; + const SmallVector<OptSpecifier, 1> ExcludedIds{OPT_ccc_print_phases}; + ArgStringList CommandLine{ClangProgramPath.data()}; + addAllArgsExcept(Args, ExcludedIds, CommandLine); + CommandLine.push_back("-fno-modules-driver"); + return CommandLine; +} + +/// Returns true if a driver job is a viable as dependency scan input. +static bool isJobForDepScan(const driver::Command &Job) { + if (StringRef(Job.getCreator().getName()) != "clang") + return false; + auto IsSrcInput = [](const driver::InputInfo &II) -> bool { + return isSrcFile(II.getType()); + }; + return llvm::all_of(Job.getInputInfos(), IsSrcInput); +} + +/// Given a -cc1 driver job, generates its full command line. +static std::vector<std::string> +buildCC1CommandLineFromJob(const driver::Command &Job, + DiagnosticsEngine &Diags) { + CompilerInvocation CI; + CompilerInvocation::CreateFromArgs(CI, Job.getArguments(), Diags); + std::vector<std::string> CL{"clang", "-cc1"}; + CI.generateCC1CommandLine([&](const Twine &Arg) { CL.push_back(Arg.str()); }); + return CL; +} + +/// Generates the list of dependency scanning tasks from the given driver +/// command line. +/// +/// The calling driver has not constructed its compilation jobs yet, but we +/// require the resulting -cc1 command lines for this compilation as inputs +/// to the dependency scan. +/// To obtain them, we create a replica of the calling driver and let it +/// construct the full compilation. We then extract -cc1 jobs from that +/// compilation. +static SmallVector<CC1ScanTask, 0> +buildScanTasksFromDummyDriver(StringRef ClangExecutable, + DiagnosticsEngine &Diags, + const DerivedArgList &Args) { + SmallVector<CC1ScanTask, 0> Out; + + const auto DummyDriver = std::make_unique<driver::Driver>( + ClangExecutable, llvm::sys::getDefaultTargetTriple(), Diags); + // This has already been handled by the calling driver. + DummyDriver->setCheckInputsExist(false); + DummyDriver->setTitle("Driver for dependency scan -cc1 input generation"); + const auto DummyDriverCL = buildDummyDriverCommandLine(ClangExecutable, Args); + std::unique_ptr<driver::Compilation> C( + DummyDriver->BuildCompilation(DummyDriverCL)); + if (!C) + return Out; + if (C->containsError()) { + llvm::reportFatalInternalError( + "failed to construct the compilation required to generate -cc1 input " + "command lines for the dependency scan"); + } + + for (const auto &[Index, Job] : llvm::enumerate(C->getJobs())) { + if (!isJobForDepScan(Job)) + continue; + // Heuristic: pick the first source input as the primary input. + std::string Filename; + if (!Job.getInputInfos().empty()) + Filename = Job.getInputInfos().front().getFilename(); + auto CC1Command = buildCC1CommandLineFromJob(Job, Diags); + Out.emplace_back(Index, std::move(Filename), std::move(CC1Command)); + } + + return Out; +} + +//===----------------------------------------------------------------------===// +// Dependency Scan +//===----------------------------------------------------------------------===// + +namespace clang { +namespace driver::dependencies { + +char DependencyScanError::ID = 0; + +namespace { +/// A simple dependency action controller that only provides module lookup for +/// Clang modules. +class ModuleLookupActionController : public DependencyActionController { +public: + ModuleLookupActionController() { + Driver::getDefaultModuleCachePath(ModulesCachePath); + } + + std::string lookupModuleOutput(const ClangModuleDeps &MD, + ModuleOutputKind Kind) override { + if (Kind == ModuleOutputKind::ModuleFile) + return constructPCMPath(MD.ID); + // Driver command lines which cause this should be handled either in + // Driver::handleArguments and rejected or in + // buildCommandLineForDummyDriver and modified. + llvm::reportFatalInternalError( + "call to lookupModuleOutput with unexpected ModuleOutputKind"); + } + +private: + SmallString<128> ModulesCachePath; + + std::string constructPCMPath(const ModuleID &MID) const { + SmallString<256> ExplicitPCMPath(ModulesCachePath); + llvm::sys::path::append(ExplicitPCMPath, Twine(MID.ModuleName) + "-" + + MID.ContextHash + ".pcm"); + return std::string(ExplicitPCMPath); + } +}; + +struct ScanResultInternal { + /// On success, the full dependencies and Clang module graph for the input. + std::optional<TranslationUnitDeps> MaybeTUDeps; + + /// Diagnostics omitted by the dependency scanning tool. + SmallVector<StandaloneDiagnostic, 0> Diagnostics; +}; +} // anonymous namespace + +/// Runs the dependency scanning tool for every task using work-stealing +/// concurrency. +static SmallVector<ScanResultInternal, 0> +scanModuleDependenciesInternal(const ArrayRef<CC1ScanTask> Tasks) { + SmallVector<ScanResultInternal, 0> Out(Tasks.size()); + + const IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = + llvm::vfs::createPhysicalFileSystem(); + DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, + ScanningOutputFormat::Full); + + std::atomic<size_t> NextIndex = 0; + auto GetNextTaskIndex = [&]() -> std::optional<size_t> { + if (const auto CurIndex = NextIndex.fetch_add(1, std::memory_order_relaxed); + CurIndex < Tasks.size()) + return CurIndex; + return std::nullopt; + }; + + auto RunScanningWorker = [&]() -> void { + DependencyScanningWorker Worker(Service, FS); + llvm::DenseSet<ModuleID> AlreadySeenModules; + constexpr StringRef CWD("."); + + while (auto MaybeIndex = GetNextTaskIndex()) { + const size_t I = *MaybeIndex; + const auto &CC1CommandLine = Tasks[I].CC1CommandLine; + ModuleLookupActionController LookupController; + StandaloneDiagCollector DiagConsumer; + FullDependencyConsumer FullDepsConsumer(AlreadySeenModules); + + const bool Success = + Worker.computeDependencies(CWD, CC1CommandLine, FullDepsConsumer, + LookupController, DiagConsumer); + Out[I].MaybeTUDeps = + Success ? std::optional(FullDepsConsumer.takeTranslationUnitDeps()) + : std::nullopt; + Out[I].Diagnostics = DiagConsumer.takeDiags(); + } + }; + + if (Tasks.size() <= 1) + RunScanningWorker(); + else { + llvm::DefaultThreadPool Pool; + // Avoid launching more threads than inputs. + const size_t Concurrency = + std::min(static_cast<size_t>(Pool.getMaxConcurrency()), Tasks.size()); + for (size_t I = 0; I < Concurrency; ++I) + Pool.async(RunScanningWorker); + Pool.wait(); + } + + return Out; +} + +Expected<SmallVector<TranslationUnitScanResult, 0>> +scanDependencies(StringRef ClangExecutable, DiagnosticsEngine &Diags, + const DerivedArgList &Args) { + auto ScanTasks = buildScanTasksFromDummyDriver(ClangExecutable, Diags, Args); + auto InternalScanResultList = scanModuleDependenciesInternal(ScanTasks); + assert(ScanTasks.size() == InternalScanResultList.size() && + "expected a scan result for every scan task"); + + SmallVector<TranslationUnitScanResult, 0> Out; + Out.reserve(InternalScanResultList.size()); + + const StandaloneDiagReporter DiagReporter(Diags); + bool HasFailure = false; + + for (size_t I = 0, E = ScanTasks.size(); I < E; ++I) { + auto &Item = ScanTasks[I]; + auto &[MaybeTUDeps, Diagnostics] = InternalScanResultList[I]; + + for (auto &StandaloneDiag : Diagnostics) + DiagReporter.Report(std::move(StandaloneDiag)); + + if (!MaybeTUDeps) { + HasFailure = true; + Diags.Report(diag::remark_failed_dependency_scan_for_input) + << Item.Filename; + continue; + } + + Out.emplace_back(Item.JobIndex, std::move(Item.Filename), + std::move(*MaybeTUDeps)); + } + + if (HasFailure) + return llvm::make_error<DependencyScanError>(); + return Out; +} + +//===----------------------------------------------------------------------===// +// Module Dependency Graph +//===----------------------------------------------------------------------===// + +MDGNode::~MDGNode() = default; + +namespace detail { +class ModuleDepGraphBuilder { +public: + explicit ModuleDepGraphBuilder( + SmallVectorImpl<TranslationUnitScanResult> &&ScanResults, + DiagnosticsEngine &Diags) + : Graph(std::move(ScanResults)), + BackingScanResults(Graph.BackingScanResults), + Allocator(Graph.BumpPtrAlloc), Diags(Diags) {} + + /// Builds the full module dependency graph. + /// + /// \returns false on error (with diagnostics reported to \c DiagConsumer). + bool build(); + + /// Take ownership of the constructed dependency graph. + ModuleDepGraph takeGraph() { return std::move(Graph); } + +private: + ModuleDepGraph Graph; + const SmallVectorImpl<TranslationUnitScanResult> &BackingScanResults; + llvm::BumpPtrAllocator &Allocator; + + DiagnosticsEngine &Diags; + + /// Allocation helper using the Graph's allocator. + template <typename MDGComponent, typename... Args> + MDGComponent *makeWithGraphAlloc(Args &&...args) { + return new (Allocator.Allocate(sizeof(MDGComponent), alignof(MDGComponent))) + MDGComponent(std::forward<Args>(args)...); + } + + /// Lookup maps used for connecting the nodes. + llvm::DenseMap<ModuleID, MDGNode *> ClangModuleNodeMap; + llvm::StringMap<NamedCXXModuleMDGNode *> CXXNamedModuleMap; + + bool buildNodes(); + void addClangModuleGraph(const ClangModuleGraph &ModuleGraph); + void addClangModuleNode(const ClangModuleDeps &ModuleDeps); + void addNonModuleNode(const TranslationUnitScanResult &ScanResult); + bool addCXXNamedModuleNode(const TranslationUnitScanResult &ScanResult); + + void connectNodes(); + bool connectDepsForNode(MDGNode *Importer); + void addImportEdge(MDGNode &Imported, MDGNode &Importer); +}; + +bool ModuleDepGraphBuilder::build() { + if (!buildNodes()) + return false; + connectNodes(); + return true; +} + +/// Construct every node in the graph from the collected scan results and +/// \returns false if an error occurred. +bool ModuleDepGraphBuilder::buildNodes() { + Graph.Root = makeWithGraphAlloc<RootMDGNode>(); + for (const auto &ScanResult : BackingScanResults) { + const auto &TUDeps = ScanResult.TUDeps; + addClangModuleGraph(TUDeps.ModuleGraph); + // If ModuleName is empty, this translation unit is not a module. + if (!TUDeps.ID.ModuleName.empty()) { + bool Success = addCXXNamedModuleNode(ScanResult); + if (!Success) + return false; + } else { + addNonModuleNode(ScanResult); + } + } + return true; +} + +/// Adds nodes for any entry in this Clang module graph which we haven't seen +/// yet. +void ModuleDepGraphBuilder::addClangModuleGraph( + const tooling::dependencies::ModuleDepsGraph &ClangModuleGraph) { + for (const auto &ClangModuleDeps : ClangModuleGraph) { + const ModuleID &ID = ClangModuleDeps.ID; + // Skip if we have already added a node for this graph entry. + if (ClangModuleNodeMap.contains(ID)) + continue; + addClangModuleNode(ClangModuleDeps); + } +} + +/// Adds a node representing the Clang module unit described by \c +/// ClangModuleDeps. +void ModuleDepGraphBuilder::addClangModuleNode( + const ClangModuleDeps &ClangModuleDeps) { + auto *Node = makeWithGraphAlloc<ClangModuleMDGNode>(ClangModuleDeps); + const auto [_, Inserted] = + ClangModuleNodeMap.try_emplace(ClangModuleDeps.ID, Node); + assert(Inserted && "Duplicate nodes for a single Clang module!"); + Graph.addNode(*Node); +} + +/// Adds a node representing the C++ named module unit described by \c +/// ScanResult. +/// +/// \returns false on error if a node for the same module interface already +/// exists in the graph. +bool ModuleDepGraphBuilder::addCXXNamedModuleNode( + const TranslationUnitScanResult &ScanResult) { + auto *Node = makeWithGraphAlloc<NamedCXXModuleMDGNode>(ScanResult); + StringRef ModuleName = ScanResult.TUDeps.ID.ModuleName; + + const auto [It, Inserted] = CXXNamedModuleMap.try_emplace(ModuleName, Node); + if (!Inserted) { + StringRef ExistingFile = It->second->getFilename(); + StringRef ThisFile = ScanResult.Filename; + Diags.Report(diag::err_mod_graph_named_module_redefinition) + << ModuleName << ThisFile << ExistingFile; + return false; + } + + Graph.addNode(*Node); + return true; +} + +/// Adds a node representing the non-module translation unit described by \c +/// ScanResult. +void ModuleDepGraphBuilder::addNonModuleNode( + const TranslationUnitScanResult &ScanResult) { + auto *Node = makeWithGraphAlloc<NonModuleMDGNode>(ScanResult); + Graph.addNode(*Node); +} + +/// Wire dependency edges for every node in the graph. +/// +/// Edges are directed from the imported module to the importing unit. +/// If a node has no incoming edges, connects it to the root. +void ModuleDepGraphBuilder::connectNodes() { + for (auto *Node : nodes(&Graph)) { + bool HasIncomingEdge = connectDepsForNode(Node); + if (!HasIncomingEdge) + addImportEdge(*Graph.getRoot(), *Node); + } +} + +// Connect all dependencies for a node and \returns true if any edge was added. +bool ModuleDepGraphBuilder::connectDepsForNode(MDGNode *Importer) { + bool HasAnyImport = false; + // Connect Clang module dependencies. + for (const auto &MID : Importer->getClangModuleDeps()) { + if (MDGNode *Imported = ClangModuleNodeMap.lookup(MID)) { + addImportEdge(*Imported, *Importer); + HasAnyImport = true; + } + } + // Connect C++20 named module dependencies. + for (const auto &Name : Importer->getCXXNamedModuleDeps()) { + if (auto *Imported = CXXNamedModuleMap.lookup(Name)) { + addImportEdge(*Imported, *Importer); + HasAnyImport = true; + } + } + return HasAnyImport; +} + +/// Creates and adds an edge from the \c Imported to the \c Importer. +void ModuleDepGraphBuilder::addImportEdge(MDGNode &Imported, + MDGNode &Importer) { + auto *Edge = makeWithGraphAlloc<MDGEdge>(Importer); + Imported.addEdge(*Edge); +} + +} // namespace detail + +llvm::Expected<ModuleDepGraph> +buildModuleDepGraph(SmallVectorImpl<TranslationUnitScanResult> &&Scans, + DiagnosticsEngine &Diags) { + detail::ModuleDepGraphBuilder Builder(std::move(Scans), Diags); + if (!Builder.build()) + return llvm::make_error<DependencyScanError>(); + return Builder.takeGraph(); +} +} // namespace driver::dependencies +} // namespace clang + +//===----------------------------------------------------------------------===// +// Module Dependency Graph: GraphViz Output +//===----------------------------------------------------------------------===// + +namespace deps = clang::driver::dependencies; + +namespace llvm { +template <> +struct DOTGraphTraits<const deps::ModuleDepGraph *> : DefaultDOTGraphTraits { + explicit DOTGraphTraits(bool IsSimple = false) + : DefaultDOTGraphTraits(IsSimple) {} + + static std::string getGraphName(const deps::ModuleDepGraph *) { + return "Module Dependency Graph"; + } + + static std::string getGraphProperties(const deps::ModuleDepGraph *) { + return "\tnode [shape=Mrecord];\n"; + } + + static bool isNodeHidden(const deps::MDGNode *N, + const deps::ModuleDepGraph *) { + return isa<deps::RootMDGNode>(N); + } + + static std::string getNodeIdentifier(const deps::MDGNode *N, + const deps::ModuleDepGraph *) { + llvm::SmallString<128> Buf; + llvm::raw_svector_ostream OS(Buf); + llvm::TypeSwitch<const deps::MDGNode *>(N) + .Case<deps::ClangModuleMDGNode, deps::NamedCXXModuleMDGNode>( + [&](auto *N) { OS << N->getModuleID().ModuleName; }) + .Case<deps::NonModuleMDGNode>([&](auto *N) { OS << N->getFilename(); }) + .Default([](auto *) { llvm_unreachable("Unhandled MDGNode kind!"); }); + OS << " (" << getNodeKindStr(N->getKind()) << ")"; + + return std::string(OS.str()); + } + + static std::string getNodeLabel(const deps::MDGNode *N, + const deps::ModuleDepGraph *) { + SmallString<128> Buf; + raw_svector_ostream OS(Buf); + OS << "Type: " << getNodeKindStr(N->getKind()) << " \\| "; + + auto PrintFilename = [](raw_ostream &OS, StringRef Filename) { + OS << "Filename: " << Filename; + }; + auto PrintModuleName = [](raw_ostream &OS, StringRef ModuleName) { + OS << "Provides: " << ModuleName; + }; + llvm::TypeSwitch<const deps::MDGNode *>(N) + .Case<const deps::ClangModuleMDGNode>( + [&](auto *N) { PrintModuleName(OS, N->getModuleID().ModuleName); }) + .Case<const deps::NamedCXXModuleMDGNode>([&](auto *N) { + PrintModuleName(OS, N->getModuleID().ModuleName); + OS << " \\| "; + PrintFilename(OS, N->getFilename()); + }) + .Case<const deps::NonModuleMDGNode>( + [&](auto *N) { PrintFilename(OS, N->getFilename()); }) + .Default([](auto *) { + llvm::reportFatalInternalError("Unhandled MDGNode kind!"); + }); + + return std::string(OS.str()); + } + +private: + static StringRef getNodeKindStr(deps::MDGNode::NodeKind Kind) { + using NodeKind = deps::MDGNode::NodeKind; + switch (Kind) { + case NodeKind::NamedCXXModule: + return "C++20 module"; + case NodeKind::ClangModule: + return "Clang module"; + case NodeKind::NonModule: + return "Non-module source"; + default: + llvm::reportFatalInternalError("Unexpected MDGNode kind!"); + }; + } +}; + +/// GraphWriter specialization for ModuleDepGraph. +/// +/// Uses human-readable identifiers instead of raw pointers and separates node +/// definitions from import edges for a more remark-friendly output. +template <> +class GraphWriter<const deps::ModuleDepGraph *> + : public GraphWriterBase<const deps::ModuleDepGraph *, + GraphWriter<const deps::ModuleDepGraph *>> { +public: + using GraphType = const deps::ModuleDepGraph *; + using Base = GraphWriterBase<GraphType, GraphWriter<GraphType>>; + + GraphWriter(llvm::raw_ostream &O, const GraphType &G, bool IsSimple) + : Base(O, G, IsSimple) {} + + void writeNodes(); + +private: + using Base::DOTTraits; + using Base::GTraits; + using Base::NodeRef; + + DenseMap<NodeRef, std::string> NodeIDMap; + + void writeNodeDefinitions(ArrayRef<NodeRef> Nodes); + void writeNodeRelations(ArrayRef<NodeRef> Nodes); +}; + +void GraphWriter<const deps::ModuleDepGraph *>::writeNodes() { + auto IsNodeVisible = [&](NodeRef N) { return !DTraits.isNodeHidden(N, G); }; + const auto VisibleNodeRange = + llvm::make_filter_range(nodes(G), IsNodeVisible); + const SmallVector<NodeRef, 0> VisibleNodes(VisibleNodeRange); + + writeNodeDefinitions(VisibleNodes); + writeNodeRelations(VisibleNodes); +} + +void GraphWriter<const deps::ModuleDepGraph *>::writeNodeDefinitions( + ArrayRef<NodeRef> Nodes) { + for (const auto &Node : Nodes) { + const auto NodeID = DTraits.getNodeIdentifier(Node, G); + const auto NodeLabel = DTraits.getNodeLabel(Node, G); + O << "\t\"" << DOT::EscapeString(NodeID) << "\" [ " + << DTraits.getNodeAttributes(Node, G) << "label=\"{ " + << DOT::EscapeString(NodeLabel) << " }\"];\n"; + NodeIDMap.try_emplace(Node, std::move(NodeID)); + } + O << "\n"; +} + +void GraphWriter<const deps::ModuleDepGraph *>::writeNodeRelations( + ArrayRef<NodeRef> Nodes) { + for (const auto &Node : Nodes) { + const auto &SourceNodeID = NodeIDMap.at(Node); + for (const auto &Edge : Node->getEdges()) { + const auto *TargetNode = GTraits::MDGGetTargetNode(Edge); + const auto &TargetNodeID = NodeIDMap.at(TargetNode); + O << "\t\"" << DOT::EscapeString(SourceNodeID) << "\" -> \"" + << DOT::EscapeString(TargetNodeID) << "\";\n"; + } + } +} +} // namespace llvm + +namespace clang { +namespace driver::dependencies { +void writeModuleDepGraph(llvm::raw_ostream &OS, const ModuleDepGraph &G) { + llvm::WriteGraph(OS, &G); +} +} // namespace driver::dependencies +} // namespace clang diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index d682ffc832c83..351501035b0c7 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -58,6 +58,7 @@ #include "clang/Config/config.h" #include "clang/Driver/Action.h" #include "clang/Driver/Compilation.h" +#include "clang/Driver/DependencyScanner.h" #include "clang/Driver/InputInfo.h" #include "clang/Driver/Job.h" #include "clang/Driver/Options.h" @@ -4672,7 +4673,24 @@ void Driver::BuildDriverManagedModuleBuildActions( Compilation &C, llvm::opt::DerivedArgList &Args, const InputList &Inputs, ActionList &Actions) const { Diags.Report(diag::remark_performing_driver_managed_module_build); - return; + auto ScanResults = + dependencies::scanDependencies(getClangProgramPath(), Diags, Args); + if (!ScanResults) { + llvm::consumeError(ScanResults.takeError()); + Diags.Report(diag::err_failed_depdendency_scan); + return; + } + + auto DepGraph = + dependencies::buildModuleDepGraph(std::move(*ScanResults), Diags); + if (!DepGraph) { + llvm::consumeError(DepGraph.takeError()); + Diags.Report(diag::err_building_depdendency_graph); + return; + } + Diags.Report(diag::remark_printing_module_graph); + if (!Diags.isLastDiagnosticIgnored()) + dependencies::writeModuleDepGraph(llvm::errs(), *DepGraph); } /// Returns the canonical name for the offloading architecture when using a HIP diff --git a/clang/test/Driver/modules-driver-dep-scan-diagnostics.cpp b/clang/test/Driver/modules-driver-dep-scan-diagnostics.cpp new file mode 100644 index 0000000000000..6f2c5cbe60b16 --- /dev/null +++ b/clang/test/Driver/modules-driver-dep-scan-diagnostics.cpp @@ -0,0 +1,25 @@ +// Tests that the module dependency scan properly outputs diagnostics which +// were collected during the dependency scan. + +// RUN: split-file %s %t + +// RUN: %clang -ccc-print-phases -fmodules -fmodules-driver -Rmodules-driver \ +// RUN: -fmodule-map-file=%t/module.modulemap %t/main.cpp 2>&1 \ +// RUN: | FileCheck --check-prefixes=CHECK %s + +//--- module.modulemap +module a { header "a.h" } + +//--- a.h +#include "doesnotexist.h" +// CHECK: a.h:{{.*}}:{{.*}}: fatal error: 'doesnotexist.h' file not found + +//--- main.cpp +// Diagnostics collected during the dependency scan need to be translated to +// and from a representation that can outlive the compiler invocation they +// were generated by. +// We test that the diagnostics source location is translated correctly: +//----10|-------20|--------30| +/*just some space*/ #include "a.h" +// CHECK: main.cpp:6:30: fatal error: could not build module 'a' + diff --git a/clang/test/Driver/modules-driver-dep-scan-graphviz.cpp b/clang/test/Driver/modules-driver-dep-scan-graphviz.cpp new file mode 100644 index 0000000000000..602f9bf6d56dc --- /dev/null +++ b/clang/test/Driver/modules-driver-dep-scan-graphviz.cpp @@ -0,0 +1,88 @@ +// Tests that the module dependency scan and the module dependency graph +// generation are correct. + +// RUN: split-file %s %t + +// RUN: %clang -std=c++23 -fmodules -fmodules-driver -Rmodules-driver \ +// RUN: -fmodule-map-file=%t/module.modulemap %t/main.cpp \ +// RUN: %t/A.cpp %t/A-B.cpp %t/A-C.cpp %t/B.cpp 2>&1 \ +// RUN: | sed 's:\\\\\?:/:g' \ +// RUN: | FileCheck -DPREFIX=%t --check-prefixes=CHECK %s + +// CHECK: clang: remark: printing module dependency graph [-Rmodules-driver] +// CHECK-NEXT: digraph "Module Dependency Graph" { +// CHECK-NEXT: label="Module Dependency Graph"; +// CHECK-NEXT: node [shape=Mrecord]; +// +// CHECK: "transitive1 (Clang module)" [ label="{ Type: Clang module | Provides: transitive1 }"]; +// CHECK-NEXT: "transitive2 (Clang module)" [ label="{ Type: Clang module | Provides: transitive2 }"]; +// CHECK-NEXT: "direct1 (Clang module)" [ label="{ Type: Clang module | Provides: direct1 }"]; +// CHECK-NEXT: "direct2 (Clang module)" [ label="{ Type: Clang module | Provides: direct2 }"]; +// CHECK-NEXT: "root (Clang module)" [ label="{ Type: Clang module | Provides: root }"]; +// CHECK-NEXT: "[[PREFIX]]/main.cpp (Non-module source)" [ label="{ Type: Non-module source | Filename: [[PREFIX]]/main.cpp }"]; +// CHECK-NEXT: "A (C++20 module)" [ label="{ Type: C++20 module | Provides: A | Filename: [[PREFIX]]/A.cpp }"]; +// CHECK-NEXT: "A:B (C++20 module)" [ label="{ Type: C++20 module | Provides: A:B | Filename: [[PREFIX]]/A-B.cpp }"]; +// CHECK-NEXT: "A:C (C++20 module)" [ label="{ Type: C++20 module | Provides: A:C | Filename: [[PREFIX]]/A-C.cpp }"]; +// CHECK-NEXT: "B (C++20 module)" [ label="{ Type: C++20 module | Provides: B | Filename: [[PREFIX]]/B.cpp }"]; +// +// CHECK: "transitive1 (Clang module)" -> "direct1 (Clang module)"; +// CHECK-NEXT: "transitive1 (Clang module)" -> "direct2 (Clang module)"; +// CHECK-NEXT: "transitive2 (Clang module)" -> "direct1 (Clang module)"; +// CHECK-NEXT: "direct1 (Clang module)" -> "root (Clang module)"; +// CHECK-NEXT: "direct1 (Clang module)" -> "A:B (C++20 module)"; +// CHECK-NEXT: "direct2 (Clang module)" -> "root (Clang module)"; +// CHECK-NEXT: "root (Clang module)" -> "[[PREFIX]]/main.cpp (Non-module source)"; +// CHECK-NEXT: "root (Clang module)" -> "B (C++20 module)"; +// CHECK-NEXT: "A (C++20 module)" -> "B (C++20 module)"; +// CHECK-NEXT: "A:B (C++20 module)" -> "A (C++20 module)"; +// CHECK-NEXT: "A:C (C++20 module)" -> "A (C++20 module)"; +// CHECK-NEXT: "B (C++20 module)" -> "[[PREFIX]]/main.cpp (Non-module source)"; +// CHECK-NEXT: } + +//--- module.modulemap +module root { header "root.h" } +module direct1 { header "direct1.h" } +module direct2 { header "direct2.h" } +module transitive1 { header "transitive1.h" } +module transitive2 { header "transitive2.h" } + +//--- root.h +#include "direct1.h" +#include "direct2.h" + +//--- direct1.h +#include "transitive1.h" +#include "transitive2.h" + +//--- direct2.h +#include "transitive1.h" + +//--- transitive1.h +// empty + +//--- transitive2.h +// empty + +//--- A.cpp +export module A; +export import :B; +import :C; + +//--- A-B.cpp +module; +#include "direct1.h" +export module A:B; + +//--- A-C.cpp +export module A:C; + +//--- B.cpp +module; +#include "root.h" +export module B; +import A; + +//--- main.cpp +#include "root.h" +import B; + diff --git a/clang/test/Driver/modules-driver-duplicate-named-module.cpp b/clang/test/Driver/modules-driver-duplicate-named-module.cpp new file mode 100644 index 0000000000000..bc86cb7217ffb --- /dev/null +++ b/clang/test/Driver/modules-driver-duplicate-named-module.cpp @@ -0,0 +1,21 @@ +// Verify that the modules driver rejects ambiguous module definitions. + +// RUN: split-file %s %t + +// RUN: not %clang -std=c++23 -fmodules -fmodules-driver -Rmodules-driver \ +// RUN: %t/main.cpp %t/A.cpp %t/AlsoA.cpp 2>&1 \ +// RUN: | sed 's:\\\\\?:/:g' \ +// RUN: | FileCheck -DPREFIX=%t --check-prefixes=CHECK %s + +/// CHECK: clang: error: duplicate definitions of C++20 named module 'A' in '[[PREFIX]]/AlsoA.cpp' and '[[PREFIX]]/A.cpp' +/// CHECK: clang: error: failed to construct the module dependency graph + +//--- main.cpp +import module A; + +//--- A.cpp +export module A; + +//--- AlsoA.cpp +export module A; + _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits