https://github.com/naveen-seth created https://github.com/llvm/llvm-project/pull/152770
This patch is part of a series to support driver-managed module builds. It adds support for the discovery of module dependencies and for dependency graph generation from within the driver. The dependency scan and graph support both Clang modules and C++ named modules. The generated dependency graph can be output in the Graphviz format as a remark. This patch follows the discussion in the RFC linked below and links the driver against the following libraries: ``` clangDependencyScanning clangAST clangFrontend clangSerialization clangLex ``` 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 >From 89de9916577a76868aa74bad03a8de9fd6339623 Mon Sep 17 00:00:00 2001 From: Naveen Seth Hanig <naveen.ha...@outlook.com> Date: Mon, 21 Jul 2025 22:34:14 +0200 Subject: [PATCH 1/4] Reland Reland [clang][modules-driver] Add scanner to detect C++20 module presence (#147630) This patch is part of a series to natively support C++20 module usage from the Clang driver (without requiring an external build system). This introduces a new scanner that detects C++20 module usage in source files without using the preprocessor or lexer. For now, it is enabled only with the `-fmodules-driver` flag and serves solely diagnostic purposes. In the future, the scanner will be enabled for any (modules-driver compatible) compilation with two or more inputs, and will help the driver determine whether to implicitly enable the modules driver. Since the scanner adds very little overhead, we are also exploring enabling it for compilations with only a single input. This approach could allow us to detect `import std` usage in a single-file compilation, which would then activate the modules driver. For performance measurements on this, see https://github.com/naveen-seth/llvm-dev-cxx-modules-check-benchmark. RFC: https://discourse.llvm.org/t/rfc-modules-support-simple-c-20-modules-use-from-the-clang-driver-without-a-build-system This patch relands the reland (2d31fc8) for commit ded1426. The previous reland failed because the link dependency on clangLex was missing. Following the RFC linked below, the issue has now been addressed by properly linking against clangLex. https://discourse.llvm.org/t/rfc-driver-link-the-driver-against-clangdependencyscanning-clangast-clangfrontend-clangserialization-and-clanglex --- .../clang/Basic/DiagnosticDriverKinds.td | 7 + clang/include/clang/Basic/DiagnosticGroups.td | 1 + clang/include/clang/Driver/Driver.h | 32 +++ clang/include/clang/Driver/Options.td | 7 + .../clang/Lex/DependencyDirectivesScanner.h | 7 + clang/lib/Driver/CMakeLists.txt | 1 + clang/lib/Driver/Driver.cpp | 69 +++++++ clang/lib/Lex/DependencyDirectivesScanner.cpp | 50 +++++ ...ules-driver-cxx20-module-usage-scanner.cpp | 192 ++++++++++++++++++ 9 files changed, 366 insertions(+) create mode 100644 clang/test/Driver/modules-driver-cxx20-module-usage-scanner.cpp diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index 0f17f4aa761ea..6df8f9932f30f 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -581,6 +581,13 @@ def err_drv_reduced_module_output_overrided : Warning< "please consider use '-fmodule-output=' to specify the output file for reduced BMI explicitly">, InGroup<DiagGroup<"reduced-bmi-output-overrided">>; +def remark_found_cxx20_module_usage : Remark< + "found C++20 module usage in file '%0'">, + InGroup<ModulesDriver>; +def remark_performing_driver_managed_module_build : Remark< + "performing driver managed module build">, + InGroup<ModulesDriver>; + def warn_drv_delayed_template_parsing_after_cxx20 : Warning< "-fdelayed-template-parsing is deprecated after C++20">, InGroup<DiagGroup<"delayed-template-parsing-in-cxx20">>; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index ccb18aa37447e..78726ecc869db 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -628,6 +628,7 @@ def ModuleConflict : DiagGroup<"module-conflict">; def ModuleFileExtension : DiagGroup<"module-file-extension">; def ModuleIncludeDirectiveTranslation : DiagGroup<"module-include-translation">; def ModuleMap : DiagGroup<"module-map">; +def ModulesDriver : DiagGroup<"modules-driver">; def RoundTripCC1Args : DiagGroup<"round-trip-cc1-args">; def NewlineEOF : DiagGroup<"newline-eof">; def Nullability : DiagGroup<"nullability">; diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h index 4d32552b7f85f..b9b187ada8add 100644 --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -512,6 +512,9 @@ class Driver { /// BuildActions - Construct the list of actions to perform for the /// given arguments, which are only done for a single architecture. + /// If the compilation is an explicit module build, delegates to + /// BuildDriverManagedModuleBuildActions. Otherwise, BuildDefaultActions is + /// used. /// /// \param C - The compilation that is being built. /// \param Args - The input arguments. @@ -796,6 +799,35 @@ class Driver { /// compilation based on which -f(no-)?lto(=.*)? option occurs last. void setLTOMode(const llvm::opt::ArgList &Args); + /// BuildDefaultActions - Constructs the list of actions to perform + /// for the provided arguments, which are only done for a single architecture. + /// + /// \param C - The compilation that is being built. + /// \param Args - The input arguments. + /// \param Actions - The list to store the resulting actions onto. + void BuildDefaultActions(Compilation &C, llvm::opt::DerivedArgList &Args, + const InputList &Inputs, ActionList &Actions) const; + + /// BuildDriverManagedModuleBuildActions - Performs a dependency + /// scan and constructs the list of actions to perform for dependency order + /// and the provided arguments. This is only done for a single a architecture. + /// + /// \param C - The compilation that is being built. + /// \param Args - The input arguments. + /// \param Actions - The list to store the resulting actions onto. + void BuildDriverManagedModuleBuildActions(Compilation &C, + llvm::opt::DerivedArgList &Args, + const InputList &Inputs, + ActionList &Actions) const; + + /// Scans the leading lines of the C++ source inputs to detect C++20 module + /// usage. + /// + /// \returns True if module usage is detected, false otherwise, or an error on + /// read failure. + llvm::ErrorOr<bool> + ScanInputsForCXX20ModulesUsage(const InputList &Inputs) const; + /// Retrieves a ToolChain for a particular \p Target triple. /// /// Will cache ToolChains for the life of the driver object, and create them diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 6aab43c9ed57f..ae8ddb0c2de88 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3296,6 +3296,13 @@ defm modules_reduced_bmi : BoolOption<"f", "modules-reduced-bmi", PosFlag<SetTrue, [], [ClangOption, CC1Option], "Generate the reduced BMI">>; +def fmodules_driver : Flag<["-"], "fmodules-driver">, + Group<f_Group>, Visibility<[ClangOption]>, + HelpText<"Enable support for driver managed module builds (experimental)">; +def fno_modules_driver : Flag<["-"], "fno-modules-driver">, + Group<f_Group>, Visibility<[ClangOption]>, + HelpText<"Disable support for driver managed module builds (experimental)">; + def experimental_modules_reduced_bmi : Flag<["-"], "fexperimental-modules-reduced-bmi">, Group<f_Group>, Visibility<[ClangOption, CC1Option]>, Alias<fmodules_reduced_bmi>; diff --git a/clang/include/clang/Lex/DependencyDirectivesScanner.h b/clang/include/clang/Lex/DependencyDirectivesScanner.h index f9fec3998ca53..c0b742d652a03 100644 --- a/clang/include/clang/Lex/DependencyDirectivesScanner.h +++ b/clang/include/clang/Lex/DependencyDirectivesScanner.h @@ -135,6 +135,13 @@ void printDependencyDirectivesAsSource( ArrayRef<dependency_directives_scan::Directive> Directives, llvm::raw_ostream &OS); +/// Scan an input source buffer for C++20 named module usage. +/// +/// \param Source The input source buffer. +/// +/// \returns true if any C++20 named modules related directive was found. +bool scanInputForCXX20ModulesUsage(StringRef Source); + /// Functor that returns the dependency directives for a given file. class DependencyDirectivesGetter { public: diff --git a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt index 45782cbd9d16d..7c4f70b966c48 100644 --- a/clang/lib/Driver/CMakeLists.txt +++ b/clang/lib/Driver/CMakeLists.txt @@ -98,5 +98,6 @@ add_clang_library(clangDriver LINK_LIBS clangBasic + clangLex ${system_libs} ) diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index 8c0bba938a09b..555837056800d 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -66,6 +66,7 @@ #include "clang/Driver/Tool.h" #include "clang/Driver/ToolChain.h" #include "clang/Driver/Types.h" +#include "clang/Lex/DependencyDirectivesScanner.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallSet.h" @@ -4188,6 +4189,11 @@ void Driver::handleArguments(Compilation &C, DerivedArgList &Args, YcArg = nullptr; } + if (Args.hasArgNoClaim(options::OPT_fmodules_driver)) + // TODO: Check against all incompatible -fmodules-driver arguments + if (!ModulesModeCXX20 && !Args.hasArgNoClaim(options::OPT_fmodules)) + Args.eraseArg(options::OPT_fmodules_driver); + Arg *FinalPhaseArg; phases::ID FinalPhase = getFinalPhase(Args, &FinalPhaseArg); @@ -4314,6 +4320,35 @@ void Driver::handleArguments(Compilation &C, DerivedArgList &Args, } } +static bool hasCXXModuleInputType(const Driver::InputList &Inputs) { + const auto IsTypeCXXModule = [](const auto &Input) -> bool { + const auto TypeID = Input.first; + return (TypeID == types::TY_CXXModule); + }; + return llvm::any_of(Inputs, IsTypeCXXModule); +} + +llvm::ErrorOr<bool> +Driver::ScanInputsForCXX20ModulesUsage(const InputList &Inputs) const { + const auto CXXInputs = llvm::make_filter_range( + Inputs, [](const auto &Input) { return types::isCXX(Input.first); }); + + for (const auto &Input : CXXInputs) { + StringRef Filename = Input.second->getSpelling(); + auto ErrOrBuffer = VFS->getBufferForFile(Filename); + if (!ErrOrBuffer) + return ErrOrBuffer.getError(); + const auto Buffer = std::move(*ErrOrBuffer); + + if (scanInputForCXX20ModulesUsage(Buffer->getBuffer())) { + Diags.Report(diag::remark_found_cxx20_module_usage) << Filename; + return true; + } + } + + return false; +} + void Driver::BuildActions(Compilation &C, DerivedArgList &Args, const InputList &Inputs, ActionList &Actions) const { llvm::PrettyStackTraceString CrashInfo("Building compilation actions"); @@ -4325,6 +4360,33 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args, handleArguments(C, Args, Inputs, Actions); + if (Args.hasFlag(options::OPT_fmodules_driver, + options::OPT_fno_modules_driver, false)) { + // TODO: Move the logic for implicitly enabling explicit-module-builds out + // of -fmodules-driver once it is no longer experimental. + // Currently, this serves diagnostic purposes only. + bool UsesCXXModules = hasCXXModuleInputType(Inputs); + if (!UsesCXXModules) { + const auto ErrOrScanResult = ScanInputsForCXX20ModulesUsage(Inputs); + if (!ErrOrScanResult) { + Diags.Report(diag::err_cannot_open_file) + << ErrOrScanResult.getError().message(); + return; + } + UsesCXXModules = *ErrOrScanResult; + } + if (UsesCXXModules) + BuildDriverManagedModuleBuildActions(C, Args, Inputs, Actions); + return; + } + + BuildDefaultActions(C, Args, Inputs, Actions); +} + +void Driver::BuildDefaultActions(Compilation &C, DerivedArgList &Args, + const InputList &Inputs, + ActionList &Actions) const { + bool UseNewOffloadingDriver = C.isOffloadingHostKind(Action::OFK_OpenMP) || C.isOffloadingHostKind(Action::OFK_SYCL) || @@ -4608,6 +4670,13 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args, Args.ClaimAllArgs(options::OPT_cl_ignored_Group); } +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; +} + /// Returns the canonical name for the offloading architecture when using a HIP /// or CUDA architecture. static StringRef getCanonicalArchString(Compilation &C, diff --git a/clang/lib/Lex/DependencyDirectivesScanner.cpp b/clang/lib/Lex/DependencyDirectivesScanner.cpp index 9ccff5e3342d5..eee57c786442a 100644 --- a/clang/lib/Lex/DependencyDirectivesScanner.cpp +++ b/clang/lib/Lex/DependencyDirectivesScanner.cpp @@ -83,6 +83,8 @@ struct Scanner { /// \returns True on error. bool scan(SmallVectorImpl<Directive> &Directives); + friend bool clang::scanInputForCXX20ModulesUsage(StringRef Source); + private: /// Lexes next token and advances \p First and the \p Lexer. [[nodiscard]] dependency_directives_scan::Token & @@ -1075,3 +1077,51 @@ void clang::printDependencyDirectivesAsSource( } } } + +static void skipUntilMaybeCXX20ModuleDirective(const char *&First, + const char *const End) { + assert(First <= End); + while (First != End) { + if (*First == '#') { + ++First; + skipToNewlineRaw(First, End); + } + skipWhitespace(First, End); + if (const auto Len = isEOL(First, End)) { + First += Len; + continue; + } + break; + } +} + +bool clang::scanInputForCXX20ModulesUsage(StringRef Source) { + const char *First = Source.begin(); + const char *const End = Source.end(); + skipUntilMaybeCXX20ModuleDirective(First, End); + if (First == End) + return false; + + // Check if the next token can even be a module directive before creating a + // full lexer. + if (!(*First == 'i' || *First == 'e' || *First == 'm')) + return false; + + llvm::SmallVector<dependency_directives_scan::Token> Tokens; + Scanner S(StringRef(First, End - First), Tokens, nullptr, SourceLocation()); + S.TheLexer.setParsingPreprocessorDirective(true); + if (S.lexModule(First, End)) + return false; + auto IsCXXNamedModuleDirective = [](const DirectiveWithTokens &D) { + switch (D.Kind) { + case dependency_directives_scan::cxx_module_decl: + case dependency_directives_scan::cxx_import_decl: + case dependency_directives_scan::cxx_export_module_decl: + case dependency_directives_scan::cxx_export_import_decl: + return true; + default: + return false; + } + }; + return llvm::any_of(S.DirsWithToks, IsCXXNamedModuleDirective); +} diff --git a/clang/test/Driver/modules-driver-cxx20-module-usage-scanner.cpp b/clang/test/Driver/modules-driver-cxx20-module-usage-scanner.cpp new file mode 100644 index 0000000000000..a434587a78759 --- /dev/null +++ b/clang/test/Driver/modules-driver-cxx20-module-usage-scanner.cpp @@ -0,0 +1,192 @@ +// The driver never checks to implicitly enable the explicit module build +// support unless at least two input files are provided. +// To trigger the C++20 module usage check, we always pass a second dummy file +// as input. +// TODO: Remove -fmodules everywhere once implicitly enabled explicit module +// builds are supported. + +// RUN: split-file %s %t +//--- empty.cpp +// Nothing here + +//--- only-global.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/only-global.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK1 +// CHECK1: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +module; + +//--- only-import.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/only-import.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK2 +// CHECK2: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +import A; + +//--- only-export.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/only-export.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK3 +// CHECK3: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +export module A; + +//--- leading-line-comment.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-line-comment.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK4 +// CHECK4: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +// My line comment +import A; + +//--- leading-block-comment1.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-block-comment1.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK5 +// CHECK5: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +/*My block comment */ +import A; + +//--- leading-block-comment2.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-block-comment2.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK6 +// CHECK6: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +/*My line comment */ import A; + +//--- inline-block-comment1.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-block-comment1.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK7 +// CHECK7: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +export/*a comment*/module/*another comment*/A; + +//--- inline-block-comment2.cpp +// RUN: %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-block-comment2.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK8 +// CHECK8: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +module/*a comment*/; + +//--- leading-directives.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-directives.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK9 +// CHECK9: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +#define A +#undef A +#if A +#ifdef A +#elifdef A +#elifndef A +#endif +#ifndef A +#elif A +#else +#endif +#endif +#pragma once; +#include <iostream> +import m; + +//--- multiline-directive.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/multiline-directive.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK10 +// CHECK10: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +#define MACRO(a, \ + b) \ + call((a), \ + (b) +import a; + +//--- leading-line-splice.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-line-splice.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK11 +// CHECK11: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +\ +module; + +//--- leading-line-splice-trailing-whitespace.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/leading-line-splice-trailing-whitespace.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK12 +// CHECK12: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +// v This backslash has trailing whitespace. + \ +export module A; + +//--- comment-line-splice.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/comment-line-splice.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --allow-empty --check-prefix=CHECK13 +// CHECK13-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +// My comment continues next-line!\ +import A; + +//--- comment-line-splice-trailing-whitespace.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/comment-line-splice-trailing-whitespace.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --allow-empty --check-prefix=CHECK14 +// CHECK14-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +// My comment continues next-line! This backslash has trailing whitespace. -> \ +module; + +//--- line-splice-in-directive1.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/line-splice-in-directive1.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK15 +// CHECK15: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] + +module\ +; + +//--- line-splice-in-directive2.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/line-splice-in-directive2.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK16 +// CHECK16: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] + +export\ + module\ + A; + +//--- no-module-usage1.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/no-module-usage1.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --allow-empty --check-prefix=CHECK17 +// CHECK17-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +auto main() -> int {} + +//--- no-module-usage2.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/no-module-usage2.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --allow-empty --check-prefix=CHECK18 +// CHECK18-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +moduleStruct{}; + +//--- no-module-usage3.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/no-module-usage3.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --allow-empty --check-prefix=CHECK19 +// CHECK19-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +export_struct{}; + +//--- no-module-usage-namespace-import.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/no-module-usage-namespace-import.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --allow-empty --check-prefix=CHECK20 +// CHECK20-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +import::inner xi = {}; + +//--- no-module-usage-namespace-module.cpp +// RUN: %clang -std=c++23 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: %t/no-module-usage-namespace-module.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --allow-empty --check-prefix=CHECK21 +// CHECK21-NOT: remark: found C++20 module usage in file '{{.*}}' [-Rmodules-driver] +module::inner yi = {}; + +// RUN: not %clang -std=c++20 -ccc-print-phases -fmodules-driver -Rmodules-driver \ +// RUN: imaginary-file.cpp %t/empty.cpp 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK-NON-EXISTING-FILE-ERR +// CHECK-NON-EXISTING-FILE-ERR: clang: error: no such file or directory: 'imaginary-file.cpp' >From 4dfcbae861980772ff4e633ba168a0134289f159 Mon Sep 17 00:00:00 2001 From: Naveen Seth Hanig <naveen.ha...@outlook.com> Date: Fri, 8 Aug 2025 18:39:33 +0200 Subject: [PATCH 2/4] Fixup #152322: [Support] Enable CRTP for GraphWriter (NFC) Because commit 474bbc1 does not make the base class's members protected, this makes it impossible to partially specialize the base class using CRTP. --- llvm/include/llvm/Support/GraphWriter.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/llvm/include/llvm/Support/GraphWriter.h b/llvm/include/llvm/Support/GraphWriter.h index af2e5016298e6..58e5b59751b67 100644 --- a/llvm/include/llvm/Support/GraphWriter.h +++ b/llvm/include/llvm/Support/GraphWriter.h @@ -62,6 +62,7 @@ LLVM_ABI bool DisplayGraph(StringRef Filename, bool wait = true, GraphProgram::Name program = GraphProgram::DOT); template <typename GraphType, typename Derived> class GraphWriterBase { +protected: raw_ostream &O; const GraphType &G; bool RenderUsingHTML = false; @@ -73,6 +74,7 @@ template <typename GraphType, typename Derived> class GraphWriterBase { using child_iterator = typename GTraits::ChildIteratorType; DOTTraits DTraits; +private: static_assert(std::is_pointer_v<NodeRef>, "FIXME: Currently GraphWriterBase requires the NodeRef type to " "be a pointer.\nThe pointer usage should be moved to " >From 777d30e8308fbc614f760e147fa703a1f4f0bc22 Mon Sep 17 00:00:00 2001 From: Naveen Seth Hanig <naveen.ha...@outlook.com> Date: Fri, 8 Aug 2025 18:49:40 +0200 Subject: [PATCH 3/4] fixup! Reland Reland [clang][modules-driver] Add scanner to detect C++20 module presence (#147630) --- clang/lib/Driver/Driver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index 555837056800d..2abf2d4bb3eb0 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -4375,7 +4375,7 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args, } UsesCXXModules = *ErrOrScanResult; } - if (UsesCXXModules) + if (UsesCXXModules || Args.hasArg(options::OPT_fmodules)) BuildDriverManagedModuleBuildActions(C, Args, Inputs, Actions); return; } >From d5b66ab5bdf322b8a56287ac86ab7f8ca4b5e140 Mon Sep 17 00:00:00 2001 From: Naveen Seth Hanig <naveen.ha...@outlook.com> Date: Fri, 8 Aug 2025 18:54:13 +0200 Subject: [PATCH 4/4] [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 and for dependency graph generation from within the driver. The dependency scan and graph support both Clang modules and C++ named modules. The generated dependency graph can be output in the Graphviz format as a remark. This patch follows the discussion in the RFC linked below and links the driver against the following libraries: clangDependencyScanning clangAST clangFrontend clangSerialization clangLex 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 | 5 + .../include/clang/Driver/DependencyScanner.h | 388 ++++++++++ clang/lib/Driver/CMakeLists.txt | 2 + clang/lib/Driver/DependencyScanner.cpp | 696 ++++++++++++++++++ clang/lib/Driver/Driver.cpp | 17 +- .../modules-driver-dependency-graph.cpp | 88 +++ 6 files changed, 1195 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-dependency-graph.cpp diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index 6df8f9932f30f..68532ba58a03b 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -587,6 +587,11 @@ 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_dependency_scan : Error < + "failed to perform dependency scan">; +def remark_module_dependency_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..a2d56aba462ca --- /dev/null +++ b/clang/include/clang/Driver/DependencyScanner.h @@ -0,0 +1,388 @@ +#ifndef LLVM_CLANG_DRIVER_DEPENDENCYSCANNER_H +#define LLVM_CLANG_DRIVER_DEPENDENCYSCANNER_H + +#include "clang/Driver/Driver.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" +#include "llvm/ADT/DirectedGraph.h" +#include "llvm/Support/DOTGraphTraits.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/GraphWriter.h" + +namespace clang { +class SourceManager; +class CharSourceRange; +class DiagnosticsEngine; +} // namespace clang + +namespace llvm::opt { +class DerivedArgList; +} // namespace llvm::opt + +namespace clang { +namespace driver { +namespace dependencies { + +using clang::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; + } +}; + +/// Performs a full dependency scan for the given driver command line and +/// returns all scan results or an error on scanning failure. +/// +/// \param ClangProgramPath Path to the clang executable +/// \param Diags The calling driver's diagnostics engine +/// \param Args The calling driver's command line arguments +/// +/// \returns The scan results for all inputs, or an error if scanning fails +llvm::Expected<SmallVector<TranslationUnitDeps, 0>> +scanModuleDependencies(llvm::StringRef ClangProgramPath, + 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 MDGBase = llvm::DirectedGraph<MDGNode, MDGEdge>; + +/// Base class for module dependency graph nodes. +/// +/// Represents a node in the ModuleDepGraph, which can be a translation unit +/// which doesn't provide any module, a Clang module, or a C++ named module. +class MDGNode : public MDGNodeBase { +public: + enum class NodeKind { + ClangModule, + NonModuleTU, + CXXNamedModule, + }; + + using Command = tooling::dependencies::Command; + + MDGNode(const NodeKind K) : Kind(K) {} + MDGNode(const NodeKind K, std::vector<Command> Commands) + : Kind(K), Commands(Commands) {} + + virtual ~MDGNode() = 0; + + NodeKind getKind() const { return Kind; } + + ArrayRef<Command> getCommands() const { return Commands; } + +private: + const NodeKind Kind; + +protected: + std::vector<Command> Commands; +}; + +/// ClangModuleNode - represents a Clang module in the ModuleDepGraph. +class ClangModuleNode : public MDGNode { +public: + ClangModuleNode(StringRef ModuleName) + : MDGNode(NodeKind::ClangModule), ModuleName(ModuleName) {} + ~ClangModuleNode() = default; + + static bool classof(const MDGNode *N) { + return N->getKind() == NodeKind::ClangModule; + } + + StringRef getModuleName() const { return ModuleName; } + + void setCommands(std::vector<tooling::dependencies::Command> Commands) { + this->Commands = Commands; + } + +private: + std::string ModuleName; +}; + +/// NonModuleTUNode - represents a regular TU which doesn't provide any module, +/// in the ModuleDepGraph. +class NonModuleTUNode : public MDGNode { +public: + NonModuleTUNode(StringRef InputFile) + : MDGNode(NodeKind::NonModuleTU), InputFile(InputFile) {} + ~NonModuleTUNode() override = default; + + static bool classof(const MDGNode *N) { + return N->getKind() == NodeKind::NonModuleTU; + } + + StringRef getInputFile() const { return InputFile; } + +private: + const std::string InputFile; +}; + +/// CXXNamedModuleNode - represents a C++ named module node in the +/// ModuleDepGraph. +/// +/// Unresolved nodes are those discovered as imports but missing a module +/// definition. +class CXXNamedModuleNode : public MDGNode { +public: + CXXNamedModuleNode(StringRef ModuleName, StringRef InputFile = "") + : MDGNode(NodeKind::CXXNamedModule), InputFile(InputFile), + ModuleName(ModuleName) {} + ~CXXNamedModuleNode() = default; + + static bool classof(const MDGNode *N) { + return N->getKind() == NodeKind::CXXNamedModule; + } + + StringRef getInputFile() const { return InputFile; } + + StringRef getModuleName() const { return ModuleName; } + + bool isUnresolved() const { return InputFile.empty(); } + + void setInputFile(StringRef FilePath) { this->InputFile = FilePath; } + + void setCommands(std::vector<tooling::dependencies::Command> Commands) { + this->Commands = Commands; + } + +private: + std::string InputFile; + std::string ModuleName; +}; + +/// MDGEdge - represents an import relationship, directed from the importing +/// unit to the imported unit. +class MDGEdge : public MDGEdgeBase { +public: + MDGEdge() = delete; + MDGEdge(MDGNode &N) : MDGEdgeBase(N) {} +}; + +class ModuleDepGraphBuilder; + +/// ModuleDepGraph - A directed graph that represents dependency relationships +/// from dependency scan results, with ownership of its nodes and edges. +class ModuleDepGraph : public MDGBase { + friend ModuleDepGraphBuilder; + + template <typename NodeTy, typename... Args> + NodeTy *MakeWithBumpAlloc(Args &&...args); + + llvm::BumpPtrAllocator BumpPtrAlloc; +}; + +/// Fully constructs a ModuleDepGraph from the dependency scan results. +/// +/// \param ScanResults The list of scan results. +/// \param Inputs The calling drivers list of input list in its original order. +/// \param Path to the clang executable. +ModuleDepGraph +buildModuleDepGraph(SmallVectorImpl<TranslationUnitDeps> &&ScanResults, + clang::driver::Driver::InputList Inputs, + StringRef ClangProgramPath); + +} // namespace dependencies +} // namespace driver +} // namespace clang + +//===----------------------------------------------------------------------===// +// Module Dependency Graph: GraphTraits specialization +//===----------------------------------------------------------------------===// + +namespace llvm { +/// non-const versions of the GraphTrait specializations for MDG +template <> struct GraphTraits<clang::driver::dependencies::MDGNode *> { + using NodeRef = clang::driver::dependencies::MDGNode *; + + static NodeRef MDGGetTargetNode(clang::driver::dependencies::MDGEdgeBase *P) { + return &P->getTargetNode(); + } + + // Provide a mapped iterator so that the GraphTrait-based implementations can + // find the target nodes without having to explicitly go through the edges. + using ChildIteratorType = + mapped_iterator<clang::driver::dependencies::MDGNode::iterator, + decltype(&MDGGetTargetNode)>; + using ChildEdgeIteratorType = clang::driver::dependencies::MDGNode::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 *> + : public GraphTraits<clang::driver::dependencies::MDGNode *> { + using nodes_iterator = clang::driver::dependencies::ModuleDepGraph::iterator; + static NodeRef getEntryNode(clang::driver::dependencies::ModuleDepGraph *G) { + return *G->begin(); + } + static nodes_iterator + nodes_begin(clang::driver::dependencies::ModuleDepGraph *G) { + return G->begin(); + } + static nodes_iterator + nodes_end(clang::driver::dependencies::ModuleDepGraph *G) { + return G->end(); + } +}; + +/// const versions of the GraphTrait specializations for MDG +template <> struct GraphTraits<const clang::driver::dependencies::MDGNode *> { + using NodeRef = const clang::driver::dependencies::MDGNode *; + + static NodeRef + MDGGetTargetNode(const clang::driver::dependencies::MDGEdgeBase *P) { + return &P->getTargetNode(); + } + + using ChildIteratorType = + mapped_iterator<clang::driver::dependencies::MDGNode::const_iterator, + decltype(&MDGGetTargetNode)>; + using ChildEdgeIteratorType = + clang::driver::dependencies::MDGNode::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 *> + : public GraphTraits<const clang::driver::dependencies::MDGNode *> { + using nodes_iterator = + clang::driver::dependencies::ModuleDepGraph::const_iterator; + + static NodeRef + getEntryNode(const clang::driver::dependencies::ModuleDepGraph *G) { + return *G->begin(); + } + static nodes_iterator + nodes_begin(const clang::driver::dependencies::ModuleDepGraph *G) { + return G->begin(); + } + static nodes_iterator + nodes_end(const clang::driver::dependencies::ModuleDepGraph *G) { + return G->end(); + } +}; + +//===----------------------------------------------------------------------===// +// Module Dependency Graph: DOTGraphTraits & GraphWriter specializations +//===----------------------------------------------------------------------===// + +template <> +struct DOTGraphTraits<const clang::driver::dependencies::ModuleDepGraph *> + : public DefaultDOTGraphTraits { + DOTGraphTraits(bool isSimple = false) : DefaultDOTGraphTraits(isSimple) {} + + static StringRef + getGraphName(const clang::driver::dependencies::ModuleDepGraph *MDG) { + return "Module Dependency Graph"; + } + + static StringRef + getNodeKindLabel(const clang::driver::dependencies::NonModuleTUNode *N) { + return "Non-Module TU"; + } + + static StringRef + getNodeKindLabel(const clang::driver::dependencies::ClangModuleNode *N) { + return "Clang Module"; + } + + static StringRef + getNodeKindLabel(const clang::driver::dependencies::CXXNamedModuleNode *N) { + return "C++ Named Module"; + } + + static std::string + getNodeIdentifierLabel(const clang::driver::dependencies::MDGNode *N, + const clang::driver::dependencies::ModuleDepGraph *G) { + using namespace clang::driver::dependencies; + if (const auto *ClangModule = dyn_cast<ClangModuleNode>(N)) + return (Twine(getNodeKindLabel(ClangModule)) + " '" + + ClangModule->getModuleName() + "'") + .str(); + if (const auto *CXXNamedModule = dyn_cast<CXXNamedModuleNode>(N)) + return (Twine(getNodeKindLabel(CXXNamedModule)) + " '" + + CXXNamedModule->getModuleName() + "'") + .str(); + if (const auto *NonModuleTU = dyn_cast<NonModuleTUNode>(N)) + return (Twine(getNodeKindLabel(NonModuleTU)) + " '" + + NonModuleTU->getInputFile() + "'") + .str(); + llvm_unreachable("Unhandled MDGNode kind!"); + } + + static std::string + getGraphProperties(const clang::driver::dependencies::ModuleDepGraph *G) { + return "\tnode [shape=Mrecord];\n\tedge [dir=\"back\"];\n"; + } +}; + +template <> +class GraphWriter<const clang::driver::dependencies::ModuleDepGraph *> + : public GraphWriterBase< + const clang::driver::dependencies::ModuleDepGraph *, + GraphWriter<const clang::driver::dependencies::ModuleDepGraph *>> { +public: + using GraphType = const clang::driver::dependencies::ModuleDepGraph *; + using Base = GraphWriterBase<GraphType, GraphWriter<GraphType>>; + + GraphWriter(raw_ostream &o, const GraphType &g, bool SN) : Base(o, g, SN) {} + + void writeNodes(); + +private: + using Base::DOTTraits; + using Base::GTraits; + using Base::NodeRef; + + void writeNodeDeclarations(ArrayRef<NodeRef> Nodes); + void writeNodeDeclaration(NodeRef Node); + void writeNodeRelations(ArrayRef<NodeRef> Nodes); + void writeNodeRelation(NodeRef Node); + + DenseMap<NodeRef, std::string> NodeIDLabels; +}; + +} // namespace llvm + +#endif diff --git a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt index 7c4f70b966c48..4d6375633c17a 100644 --- a/clang/lib/Driver/CMakeLists.txt +++ b/clang/lib/Driver/CMakeLists.txt @@ -17,6 +17,7 @@ endif() add_clang_library(clangDriver Action.cpp Compilation.cpp + DependencyScanner.cpp Distro.cpp Driver.cpp DriverOptions.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..3b2dc7af67bcb --- /dev/null +++ b/clang/lib/Driver/DependencyScanner.cpp @@ -0,0 +1,696 @@ +#include "clang/Driver/DependencyScanner.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/Tool.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/TargetParser/Host.h" +#include <atomic> + +using namespace clang; +using namespace llvm::opt; + +//===----------------------------------------------------------------------===// +// Dependency Scan Diagnostic Reporting Utilities +//===----------------------------------------------------------------------===// + +namespace { +/// Utility class to represent a CharSourceRange in a StandaloneDiagnostic. +struct CharSourceOffsetRange { + CharSourceOffsetRange(CharSourceRange Range, const SourceManager &SrcMgr, + const LangOptions &LangOpts); + + unsigned Begin = 0; + unsigned End = 0; + bool IsTokenRange = false; +}; + +/// Utility class to represent a FixItHint in a StandaloneDiagnostic. +struct StandaloneFixIt { + StandaloneFixIt(const SourceManager &SrcMgr, const LangOptions &LangOpts, + const FixItHint &FixIt); + + CharSourceOffsetRange RemoveRange; + CharSourceOffsetRange InsertFromRange; + std::string CodeToInsert; + bool BeforePreviousInsertions = false; +}; + +/// Represents a diagnostic in a form that can be retained until after its +/// corresponding source manager is destroyed. +/// +/// Source locations are stored as offsets into the file FilePath. +/// To report a StandaloneDiagnostic, it must first be converted back into a +/// StoredDiagnostic with a new source manager. +struct StandaloneDiagnostic { + StandaloneDiagnostic(const StoredDiagnostic &StoredDiag); + + LangOptions LangOpts; + SrcMgr::CharacteristicKind FileCharacteristic; + DiagnosticsEngine::Level Level; + unsigned ID = 0; + unsigned FileOffset = 0; + std::string FilePath; + std::string Message; + SmallVector<CharSourceOffsetRange> Ranges; + SmallVector<StandaloneFixIt> FixIts; +}; +} // anonymous namespace + +CharSourceOffsetRange::CharSourceOffsetRange(CharSourceRange Range, + const SourceManager &SrcMgr, + const LangOptions &LangOpts) + : IsTokenRange(Range.isTokenRange()) { + const auto FileRange = + Lexer::makeFileCharRange(std::move(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(std::move(FixIt.CodeToInsert)), + BeforePreviousInsertions(FixIt.BeforePreviousInsertions) {} + +StandaloneDiagnostic::StandaloneDiagnostic(const StoredDiagnostic &StoredDiag) + : Level(StoredDiag.getLevel()), ID(StoredDiag.getID()), + Message(StoredDiag.getMessage()), Ranges(), FixIts() { + // This is not an invalid diagnostic; invalid SourceLocations are used to + // represent diagnostics without a specific SourceLocation. + if (StoredDiag.getLocation().isInvalid()) + return; + + const auto &SrcMgr = StoredDiag.getLocation().getManager(); + FileCharacteristic = SrcMgr.getFileCharacteristic(StoredDiag.getLocation()); + + const auto FileLoc = SrcMgr.getFileLoc(StoredDiag.getLocation()); + StringRef Filename = SrcMgr.getFilename(FileLoc); + assert(!Filename.empty() && "diagnostic with location has no source file?"); + // If a custom CWD is set by the FileManager, make FilePath independent by + // converting it to an absolute path. + SmallString<256> AbsFilePath(Filename); + if (const auto &CWD = SrcMgr.getFileManager().getFileSystemOpts().WorkingDir; + !CWD.empty()) + llvm::sys::fs::make_absolute(CWD, AbsFilePath); + FilePath = AbsFilePath.str(); + FileOffset = SrcMgr.getFileOffset(FileLoc); + + 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); +} + +/// Converts a StandaloneDiagnostic into a StoredDiagnostic, associating it +/// with the provided FileManager and SourceManager. +static StoredDiagnostic +convertStandaloneDiagnostic(FileManager &FileMgr, SourceManager &SrcMgr, + StandaloneDiagnostic StandaloneDiag) { + const auto FileRef = FileMgr.getOptionalFileRef(StandaloneDiag.FilePath); + if (!FileRef) + return StoredDiagnostic(StandaloneDiag.Level, StandaloneDiag.ID, + StandaloneDiag.Message); + + const auto FileID = + SrcMgr.getOrCreateFileID(*FileRef, StandaloneDiag.FileCharacteristic); + 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 CharSourceOffsetRange &Range) { + return CharSourceRange(SourceRange(FileLoc.getLocWithOffset(Range.Begin), + FileLoc.getLocWithOffset(Range.End)), + Range.IsTokenRange); + }; + + SmallVector<clang::CharSourceRange> TranslatedRanges; + TranslatedRanges.reserve(StandaloneDiag.Ranges.size()); + llvm::transform(StandaloneDiag.Ranges, TranslatedRanges.begin(), + 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); + TranslatedFixIts.push_back(TranslatedFixIt); + } + + return StoredDiagnostic(StandaloneDiag.Level, StandaloneDiag.ID, + StandaloneDiag.Message, Loc, TranslatedRanges, + TranslatedFixIts); +} + +namespace { +/// Simple RAII helper to report StandaloneDiagnostics. +class StandaloneDiagReporter { +public: + StandaloneDiagReporter(DiagnosticsEngine &Diags, + llvm::raw_ostream &OS = llvm::errs()); + + StandaloneDiagReporter(const StandaloneDiagReporter &) = delete; + StandaloneDiagReporter &operator=(const StandaloneDiagReporter &) = delete; + + ~StandaloneDiagReporter(); + + void Report(StandaloneDiagnostic StandaloneDiag); + +private: + DiagnosticsEngine &Diags; + FileManager *FileMgr = nullptr; + SourceManager *SrcMgr = nullptr; + bool OwnsManagers = false; +}; +} // anonymous namespace + +StandaloneDiagReporter::StandaloneDiagReporter(DiagnosticsEngine &Diags, + llvm::raw_ostream &OS) + : Diags(Diags) { + // If Diags already has a source manager, use that one instead. + if (Diags.hasSourceManager()) { + OwnsManagers = false; + SrcMgr = &Diags.getSourceManager(); + FileMgr = &SrcMgr->getFileManager(); + assert(FileMgr != nullptr && + "DiagnosticEngine with invalid SourceManager!"); + } else { + OwnsManagers = true; + FileMgr = new FileManager({"."}); + SrcMgr = new SourceManager(Diags, *FileMgr); + } +} + +StandaloneDiagReporter::~StandaloneDiagReporter() { + if (OwnsManagers) { + delete SrcMgr; + delete FileMgr; + } +} + +void StandaloneDiagReporter::Report(StandaloneDiagnostic StandaloneDiag) { + const auto StoredDiag = + convertStandaloneDiagnostic(*FileMgr, *SrcMgr, std::move(StandaloneDiag)); + Diags.getClient()->BeginSourceFile(StandaloneDiag.LangOpts, nullptr); + Diags.Report(StoredDiag); + Diags.getClient()->EndSourceFile(); +} + +/// Reports all StandaloneDiagnostic instances collected over multiple scans. +static void reportAllScanDiagnostics( + DiagnosticsEngine &Diags, + ArrayRef<SmallVector<StandaloneDiagnostic, 0>> StandaloneDiagLists) { + StandaloneDiagReporter StandaloneDiagReporter(Diags); + for (const auto &StandaloneDiags : StandaloneDiagLists) + for (const auto &SD : StandaloneDiags) + StandaloneDiagReporter.Report(SD); +} + +namespace { +/// Collects every Diagnostic in a form that can be retained until after its +/// corresponding source manager is destroyed. +/// FIXME: Store the original LangOpts in the StoredDiagnostic too. +class StandaloneDiagCollector : public DiagnosticConsumer { +public: + void BeginSourceFile(const LangOptions &LangOpts, + const Preprocessor *PP = nullptr) override {} + + void EndSourceFile() override {} + + void HandleDiagnostic(DiagnosticsEngine::Level Level, + const Diagnostic &Info) override { + StoredDiagnostic StoredDiag(Level, Info); + StandaloneDiags.emplace_back(StoredDiag); + } + + SmallVector<StandaloneDiagnostic, 0> takeDiags() { + return std::move(StandaloneDiags); + } + +private: + SmallVector<StandaloneDiagnostic, 0> StandaloneDiags; +}; +} // anonymous namespace + +//===----------------------------------------------------------------------===// +// Dependency Scan Clang `-cc1` Command Line Generation +//===----------------------------------------------------------------------===// + +using CC1Command = std::vector<std::string>; + +/// Non-consuming version of llvm::opt::ArgList::AddAllArgsExcept. +/// Append all arguments onto the \c Output as strings, execpt those which match +/// any option defined in \c ExcludedIds. +static void addAllArgsExcept(const DerivedArgList &Args, + ArrayRef<llvm::opt::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 Arg *Arg : llvm::make_filter_range(Args, IsNotExcluded)) + Arg->render(Args, Output); +} + +/// Checks if a driver is a -cc1 command and if all its inputs are source files. +static bool isJobForDepScan(const driver::Command &Cmd) { + const bool IsCC1Cmd = StringRef(Cmd.getCreator().getName()) == "clang"; + auto IsSrcInput = [](const driver::InputInfo &II) -> bool { + return isSrcFile(II.getType()); + }; + return IsCC1Cmd && llvm::all_of(Cmd.getInputInfos(), IsSrcInput); +} + +/// Builds the command line to create the dummy driver from the calling drivers +/// command line. +static ArgStringList +buildCommandLineForDummyDriver(StringRef ClangProgramPath, + const DerivedArgList &Args) { + using namespace driver::options; + const llvm::SmallVector<llvm::opt::OptSpecifier, 1> ExcludedIds{ + OPT_ccc_print_phases}; + ArgStringList CommandLine{ClangProgramPath.data()}; + addAllArgsExcept(Args, ExcludedIds, CommandLine); + CommandLine.push_back("-fno-modules-driver"); + return CommandLine; +} + +/// Builds all -cc1 command line inputs for dependency scanning. +/// +/// Since the calling driver has not yet created the compilation jobs, +/// we reconstruct the driver and fully build the compilation to extract +/// the -cc1 command lines as inputs for the dependency scan. +static SmallVector<CC1Command> +buildCommandLinesFromDummyDriver(StringRef ClangProgramPath, + DiagnosticsEngine &Diags, + const DerivedArgList &Args) { + const auto DummyCommandLine = + buildCommandLineForDummyDriver(ClangProgramPath, Args); + + const auto DummyDriver = std::make_unique<driver::Driver>( + DummyCommandLine[0], llvm::sys::getDefaultTargetTriple(), Diags); + DummyDriver->setCheckInputsExist(false); + DummyDriver->setTitle("Driver for dependency scan '-cc1' input generation"); + std::unique_ptr<driver::Compilation> C( + DummyDriver->BuildCompilation(DummyCommandLine)); + if (!C) + return {}; + if (C->containsError()) { + llvm::reportFatalInternalError( + "failed to construct the compilation required to generate '-cc1' input " + "command lines for the dependency scan"); + } + + SmallVector<CC1Command> CC1CommandLines; + const auto JobsForDepScan = + llvm::make_filter_range(C->getJobs(), isJobForDepScan); + for (const driver::Command &Job : JobsForDepScan) { + auto CI = std::make_unique<CompilerInvocation>(); + CompilerInvocation::CreateFromArgs(*CI, Job.getArguments(), Diags); + std::vector<std::string> CommandLine{ClangProgramPath.str(), "-cc1"}; + CI->generateCC1CommandLine( + [&CommandLine](const Twine &Arg) { CommandLine.push_back(Arg.str()); }); + CC1CommandLines.push_back(std::move(CommandLine)); + } + return CC1CommandLines; +} + +//===----------------------------------------------------------------------===// +// Dependency Scan +//===----------------------------------------------------------------------===// + +namespace clang { +namespace driver { +namespace dependencies { + +char DependencyScanError::ID = 0; + +namespace { +using namespace tooling::dependencies; +/// A simple dependency action controller that only provides module lookup for +/// Clang modules. +class ModuleLookupActionController : public DependencyActionController { +public: + ModuleLookupActionController() { + driver::Driver::getDefaultModuleCachePath(ModulesCachePath); + } + + std::string lookupModuleOutput(const ModuleDeps &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) { + SmallString<256> ExplicitPCMPath(ModulesCachePath); + llvm::sys::path::append(ExplicitPCMPath, llvm::Twine(MID.ContextHash) + + MID.ModuleName + "-" + + MID.ContextHash + ".pcm"); + return std::string(ExplicitPCMPath); + } +}; +} // anonymous namespace + +/// Builds -cc1 command lines from the driver arguments, then performs a +/// dependency scan for each -cc1 command line using a work-stealing +/// concurrency. +llvm::Expected<SmallVector<TranslationUnitDeps, 0>> +scanModuleDependencies(llvm::StringRef ClangProgramPath, + DiagnosticsEngine &Diags, + const llvm::opt::DerivedArgList &Args) { + llvm::PrettyStackTraceString CrashInfo("Scanning module dependencies"); + using namespace tooling::dependencies; + + const auto CC1CommandLines = + buildCommandLinesFromDummyDriver(ClangProgramPath, Diags, Args); + + std::atomic<size_t> Index = 0; + auto GetNextInputIndex = [&]() -> std::optional<size_t> { + const auto CurIndex = Index.fetch_add(1, std::memory_order_relaxed); + if (CurIndex < CC1CommandLines.size()) + return CurIndex; + return std::nullopt; + }; + + DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, + ScanningOutputFormat::Full); + llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = + llvm::vfs::createPhysicalFileSystem(); + + SmallVector<TranslationUnitDeps, 0> ScanResults(CC1CommandLines.size()); + SmallVector<SmallVector<StandaloneDiagnostic, 0>> StandaloneDiagLists( + CC1CommandLines.size()); + std::atomic<bool> HasScanningError = false; + + auto ScanningTask = [&]() -> void { + DependencyScanningWorker Worker(Service, FS); + llvm::DenseSet<ModuleID> AlreadySeenModules; + constexpr auto CWD = "."; + + while (auto MaybeInputIndex = GetNextInputIndex()) { + const auto &CommandLine = CC1CommandLines[*MaybeInputIndex]; + ModuleLookupActionController ModuleLookupController; + StandaloneDiagCollector DiagsConsumer; + FullDependencyConsumer FullDepsConsumer(AlreadySeenModules); + + const bool Success = Worker.computeDependencies( + CWD, CommandLine, FullDepsConsumer, ModuleLookupController, + DiagsConsumer, std::nullopt); + if (!Success) + HasScanningError.store(true, std::memory_order_relaxed); + + ScanResults[*MaybeInputIndex] = + FullDepsConsumer.takeTranslationUnitDeps(); + StandaloneDiagLists[*MaybeInputIndex] = DiagsConsumer.takeDiags(); + } + }; + + if (CC1CommandLines.size() == 1) + ScanningTask(); + else { + llvm::DefaultThreadPool Pool; + for (unsigned I = 0; I < Pool.getMaxConcurrency(); ++I) + Pool.async(ScanningTask); + Pool.wait(); + } + + reportAllScanDiagnostics(Diags, StandaloneDiagLists); + if (HasScanningError) + return llvm::make_error<DependencyScanError>(); + return ScanResults; +} + +//===----------------------------------------------------------------------===// +// Module Dependency Graph +//===----------------------------------------------------------------------===// + +MDGNode::~MDGNode() = default; + +template <typename NodeTy, typename... Args> +NodeTy *ModuleDepGraph::MakeWithBumpAlloc(Args &&...args) { + return new (BumpPtrAlloc.Allocate(sizeof(NodeTy), alignof(NodeTy))) + NodeTy(std::forward<Args>(args)...); +} + +/// Helper class for constructing the ModuleDepGraph incrementally. +class ModuleDepGraphBuilder { +public: + ModuleDepGraphBuilder(StringRef ClangProgramPath) + : ClangProgramPath(ClangProgramPath) {} + + /// Adds the scan result of a single scan to the ModuleDepGraph. + void addScanResult(tooling::dependencies::TranslationUnitDeps TUDeps, + StringRef FileName); + + /// Takes and returns the constructured ModuleDepGraph. + ModuleDepGraph takeGraph(); + +private: + ModuleDepGraph MDG; + + StringRef ClangProgramPath; + + // Maps from module name to the corresponding node to avoid repeated graph + // walks to find a module. + llvm::StringMap<ClangModuleNode *> ClangModuleMap; + llvm::StringMap<CXXNamedModuleNode *> StandardCXXModuleMap; + + /// Creates a new NonModuleTUNode. + NonModuleTUNode * + createNonModuleTUNode(std::string FilePath, + std::vector<tooling::dependencies::Command> Commands); + + /// Finds an existing Clang module node or creates a new one. + /// If no existing module is found, an incomplete node witoutCommandLine is + /// created. + ClangModuleNode *getOrCreateClangModule(std::string ModuleName); + + /// Finds an existing Clang named module node or creates a new one. + /// If an an incomplete node with only the module name is found, this resolves + /// the CommandLine. + ClangModuleNode * + getOrCreateClangModule(std::string ModuleName, + std::vector<tooling::dependencies::Command> Commands); + + /// Finds an existing C++ named module node or creates a new one. + /// If no existing module is found, an incomplete node without InputFile or + /// CommandLine is created. + CXXNamedModuleNode *getOrCreateCXXModule(std::string ModuleName); + + /// Finds an existing standard C++ module node or creates a new one. + /// If an an incomplete node with only the module name is found, this resolves + /// the InputFile and CommandLine. + CXXNamedModuleNode *getOrCreateStandardCXXModule( + std::string ModuleName, StringRef InputFile, + std::vector<tooling::dependencies::Command> Commands); +}; + +NonModuleTUNode *ModuleDepGraphBuilder::createNonModuleTUNode( + std::string FilePath, + std::vector<tooling::dependencies::Command> Commands) { + auto *NewNode = MDG.MakeWithBumpAlloc<NonModuleTUNode>(std::move(FilePath)); + MDG.addNode(*NewNode); + return NewNode; +} + +ClangModuleNode * +ModuleDepGraphBuilder::getOrCreateClangModule(std::string ModuleName) { + auto It = ClangModuleMap.find(ModuleName); + if (It != ClangModuleMap.end()) + return It->getValue(); + + auto *NewNode = MDG.MakeWithBumpAlloc<ClangModuleNode>(std::move(ModuleName)); + MDG.addNode(*NewNode); + ClangModuleMap.try_emplace(NewNode->getModuleName(), NewNode); + return NewNode; +} + +ClangModuleNode *ModuleDepGraphBuilder::getOrCreateClangModule( + std::string ModuleName, + std::vector<tooling::dependencies::Command> Commands) { + auto *Node = getOrCreateClangModule(std::move(ModuleName)); + if (Node->getCommands().empty()) + Node->setCommands(std::move(Commands)); + return Node; +} + +CXXNamedModuleNode * +ModuleDepGraphBuilder::getOrCreateCXXModule(std::string ModuleName) { + auto It = StandardCXXModuleMap.find(ModuleName); + if (It != StandardCXXModuleMap.end()) + return It->getValue(); + + auto *NewNode = + MDG.MakeWithBumpAlloc<CXXNamedModuleNode>(std::move(ModuleName), ""); + MDG.addNode(*NewNode); + StandardCXXModuleMap.try_emplace(NewNode->getModuleName(), NewNode); + return NewNode; +} + +CXXNamedModuleNode *ModuleDepGraphBuilder::getOrCreateStandardCXXModule( + std::string ModuleName, StringRef InputFile, + std::vector<tooling::dependencies::Command> Commands) { + auto *Node = getOrCreateCXXModule(std::move(ModuleName)); + Node->setInputFile(InputFile); + Node->setCommands(std::move(Commands)); + return Node; +} + +void ModuleDepGraphBuilder::addScanResult( + tooling::dependencies::TranslationUnitDeps TUDeps, StringRef FileName) { + for (auto &MD : TUDeps.ModuleGraph) { + std::vector<std::string> BuildArgs = MD.getBuildArguments(); + std::vector<tooling::dependencies::Command> Cmds = { + {ClangProgramPath.str(), std::move(BuildArgs)}}; + auto *Node = + getOrCreateClangModule(std::move(MD.ID.ModuleName), std::move(Cmds)); + for (const auto &Dep : MD.ClangModuleDeps) { + auto *DepNode = getOrCreateClangModule(std::move(Dep.ModuleName)); + if (Node->hasEdgeTo(*DepNode)) + continue; + auto *Edge = MDG.MakeWithBumpAlloc<MDGEdge>(*DepNode); + MDG.connect(*Node, *DepNode, *Edge); + } + } + + MDGNode *CurNode = nullptr; + if (!TUDeps.ID.ModuleName.empty()) { + CurNode = getOrCreateStandardCXXModule( + std::move(TUDeps.ID.ModuleName), FileName, std::move(TUDeps.Commands)); + } else { + CurNode = createNonModuleTUNode(FileName.str(), std::move(TUDeps.Commands)); + } + + for (const auto &CXXNamedMDName : TUDeps.NamedModuleDeps) { + auto *DepNode = getOrCreateCXXModule(CXXNamedMDName); + auto *Edge = MDG.MakeWithBumpAlloc<MDGEdge>(*DepNode); + MDG.connect(*CurNode, *DepNode, *Edge); + } + + for (auto &MD : TUDeps.ClangModuleDeps) { + auto *DepNode = getOrCreateClangModule(MD.ModuleName); + auto *Edge = MDG.MakeWithBumpAlloc<MDGEdge>(*DepNode); + MDG.connect(*CurNode, *DepNode, *Edge); + } +} + +ModuleDepGraph ModuleDepGraphBuilder::takeGraph() { return std::move(MDG); } + +ModuleDepGraph +buildModuleDepGraph(SmallVectorImpl<TranslationUnitDeps> &&TUDepsList, + Driver::InputList Inputs, StringRef ClangProgramPath) { + ModuleDepGraphBuilder Builder(ClangProgramPath); + for (auto &&[TUDeps, Input] : llvm::zip(TUDepsList, Inputs)) + Builder.addScanResult(TUDeps, Input.second->getSpelling()); + return Builder.takeGraph(); +} + +} // namespace dependencies +} // namespace driver +} // namespace clang + +//===----------------------------------------------------------------------===// +// Module Dependency Graph: GraphTraits specialization +//===----------------------------------------------------------------------===// + +using namespace clang::driver::dependencies; + +namespace llvm { +void GraphWriter<const ModuleDepGraph *>::writeNodes() { + auto IsNodeVisible = [&](NodeRef N) { return !DTraits.isNodeHidden(N, G); }; + auto VisibleNodeRange = llvm::make_filter_range(nodes(G), IsNodeVisible); + SmallVector<NodeRef, 0> VisibleNodes(VisibleNodeRange); + writeNodeDeclarations(VisibleNodes); + writeNodeRelations(VisibleNodes); +} + +void GraphWriter<const ModuleDepGraph *>::writeNodeDeclarations( + ArrayRef<NodeRef> Nodes) { + for (const auto &Node : Nodes) + writeNodeDeclaration(Node); + O << "\n"; +} + +void GraphWriter<const ModuleDepGraph *>::writeNodeDeclaration(NodeRef Node) { + std::string NodeLabel; + switch (Node->getKind()) { + case MDGNode::NodeKind::ClangModule: { + const auto *ClangModule = static_cast<const ClangModuleNode *>(Node); + NodeLabel = "{ Type: Clang Module | Provides: \\\"" + + ClangModule->getModuleName().str() + "\\\" }"; + break; + } + case MDGNode::NodeKind::CXXNamedModule: { + const auto *CXXModuleNode = static_cast<const CXXNamedModuleNode *>(Node); + NodeLabel = "{ Type: C++ Named Module"; + if (!CXXModuleNode->getInputFile().empty()) { + NodeLabel += + " | Filename: \\\"" + CXXModuleNode->getInputFile().str() + "\\\""; + } else + NodeLabel += " | Filename: \\<unresolved\\>"; + NodeLabel += + " | Provides: \\\"" + CXXModuleNode->getModuleName().str() + "\\\" }"; + break; + } + case MDGNode::NodeKind::NonModuleTU: { + const auto *TUNode = static_cast<const NonModuleTUNode *>(Node); + NodeLabel = "{ Type: Default TU | Filename: \\\"" + + TUNode->getInputFile().str() + "\\\" }"; + break; + } + } + + const auto NodeIdentifierLabel = DTraits.getNodeIdentifierLabel(Node, G); + NodeIDLabels[Node] = NodeIdentifierLabel; + O << "\t\"" << DOT::EscapeString(NodeIdentifierLabel) << "\" [ label = \"" + << NodeLabel << "\" ];\n"; +} + +void GraphWriter<const ModuleDepGraph *>::writeNodeRelations( + ArrayRef<NodeRef> Nodes) { + for (const auto &Node : Nodes) + writeNodeRelation(Node); +} + +void GraphWriter<const ModuleDepGraph *>::writeNodeRelation(NodeRef Node) { + const auto SrcLabel = NodeIDLabels.lookup(Node); + for (const auto *NodeIT = GTraits::child_edge_begin(Node); + NodeIT != GTraits::child_edge_end(Node); ++NodeIT) { + const auto *Edge = *NodeIT; + const auto *TargetNode = GTraits::MDGGetTargetNode(Edge); + const auto DestLabel = NodeIDLabels.lookup(TargetNode); + if (SrcLabel.empty() || DestLabel.empty()) + continue; + O << "\t\"" << DOT::EscapeString(SrcLabel) << "\" -> \"" + << DOT::EscapeString(DestLabel) << "\";\n"; + } +} + +} // namespace llvm diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index 2abf2d4bb3eb0..f5cef171a4703 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" @@ -87,6 +88,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/FileUtilities.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/GraphWriter.h" #include "llvm/Support/MD5.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" @@ -4674,7 +4676,20 @@ 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::scanModuleDependencies(getClangProgramPath(), Diags, Args); + if (!ScanResults) { + llvm::consumeError(ScanResults.takeError()); + Diags.Report(diag::err_failed_dependency_scan); + return; + } + + const auto ModuleDepGraph = dependencies::buildModuleDepGraph( + std::move(*ScanResults), Inputs, getClangProgramPath()); + Diags.Report(diag::remark_module_dependency_graph); + if (!Diags.isLastDiagnosticIgnored()) + llvm::WriteGraph(llvm::errs(), &ModuleDepGraph); } /// Returns the canonical name for the offloading architecture when using a HIP diff --git a/clang/test/Driver/modules-driver-dependency-graph.cpp b/clang/test/Driver/modules-driver-dependency-graph.cpp new file mode 100644 index 0000000000000..26f93b24bc36b --- /dev/null +++ b/clang/test/Driver/modules-driver-dependency-graph.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 > %t/result 2>&1 +// RUN: cat %t/result | 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-NEXT: edge [dir="back"]; +// +// CHECK: "Clang Module 'transitive1'" [ label = "{ Type: Clang Module | Provides: \"transitive1\" }" ]; +// CHECK-NEXT: "Clang Module 'transitive2'" [ label = "{ Type: Clang Module | Provides: \"transitive2\" }" ]; +// CHECK-NEXT: "Clang Module 'direct1'" [ label = "{ Type: Clang Module | Provides: \"direct1\" }" ]; +// CHECK-NEXT: "Clang Module 'direct2'" [ label = "{ Type: Clang Module | Provides: \"direct2\" }" ]; +// CHECK-NEXT: "Clang Module 'root'" [ label = "{ Type: Clang Module | Provides: \"root\" }" ]; +// CHECK-NEXT: "Non-Module TU '[[PREFIX]]/main.cpp'" [ label = "{ Type: Default TU | Filename: \"[[PREFIX]]/main.cpp\" }" ]; +// CHECK-NEXT: "C++ Named Module 'B'" [ label = "{ Type: C++ Named Module | Filename: \"[[PREFIX]]/B.cpp\" | Provides: \"B\" }" ]; +// CHECK-NEXT: "C++ Named Module 'A'" [ label = "{ Type: C++ Named Module | Filename: \"[[PREFIX]]/A.cpp\" | Provides: \"A\" }" ]; +// CHECK-NEXT: "C++ Named Module 'A:B'" [ label = "{ Type: C++ Named Module | Filename: \"[[PREFIX]]/A-B.cpp\" | Provides: \"A:B\" }" ]; +// CHECK-NEXT: "C++ Named Module 'A:C'" [ label = "{ Type: C++ Named Module | Filename: \"[[PREFIX]]/A-C.cpp\" | Provides: \"A:C\" }" ]; +// +// CHECK: "Clang Module 'direct1'" -> "Clang Module 'transitive1'"; +// CHECK-NEXT: "Clang Module 'direct1'" -> "Clang Module 'transitive2'"; +// CHECK-NEXT: "Clang Module 'direct2'" -> "Clang Module 'transitive1'"; +// CHECK-NEXT: "Clang Module 'root'" -> "Clang Module 'direct1'"; +// CHECK-NEXT: "Clang Module 'root'" -> "Clang Module 'direct2'"; +// CHECK-NEXT: "Non-Module TU '[[PREFIX]]/main.cpp'" -> "C++ Named Module 'B'"; +// CHECK-NEXT: "Non-Module TU '[[PREFIX]]/main.cpp'" -> "Clang Module 'root'"; +// CHECK-NEXT: "C++ Named Module 'B'" -> "C++ Named Module 'A'"; +// CHECK-NEXT: "C++ Named Module 'B'" -> "Clang Module 'root'"; +// CHECK-NEXT: "C++ Named Module 'A'" -> "C++ Named Module 'A:B'"; +// CHECK-NEXT: "C++ Named Module 'A'" -> "C++ Named Module 'A:C'"; +// CHECK-NEXT: "C++ Named Module 'A:B'" -> "Clang Module 'direct1'"; +// 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; + _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits