[clang] [clang][modules-driver] Add initial support for driver-managed module builds (PR #156248)
Naveen Seth Hanig via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 31 08:27:50 PDT 2025
https://github.com/naveen-seth created https://github.com/llvm/llvm-project/pull/156248
With `-fmodules-driver` enabled, the Clang driver provides native support for:
- Named module imports defined in other source files on the command
line
- Standard library imports (`import std;` and `import std.compat;`)
- Clang modules discovered via module map files
Regular translation units can import both Clang modules and C++20 named modules.
Importing a Clang module into a C++20 named module interface unit, or vice versa, is not supported by this patch.
>From 9760106dbd916861d396b90cc30262a13a01c9c2 Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at outlook.com>
Date: Mon, 25 Aug 2025 02:36:42 +0200
Subject: [PATCH 1/2] [clang][modules-driver] Move logic to enable
-fmodules-driver (NFC)
This patch is part of a series to support driver-managed module builds
for C++ named modules and Clang modules.
Commit 9403c2d introduced the entry point for the driver-managed
module build logic and assumed that it would live in the BuildActions
phase of the driver.
That proved unnecessary: the logic can be fully implemented in the
BuildJobs phase.
This reverts changes to BuildActions in preparation for the upcoming
patches.
---
clang/include/clang/Driver/Driver.h | 25 -------
clang/lib/Driver/Driver.cpp | 107 ++++++++++++----------------
2 files changed, 47 insertions(+), 85 deletions(-)
diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h
index b9b187ada8add..ec574f5796117 100644
--- a/clang/include/clang/Driver/Driver.h
+++ b/clang/include/clang/Driver/Driver.h
@@ -512,9 +512,6 @@ 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.
@@ -799,27 +796,6 @@ 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.
///
@@ -827,7 +803,6 @@ class Driver {
/// 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/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index f110dbab3e5a5..d69012885ad4c 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -1473,6 +1473,33 @@ bool Driver::loadDefaultConfigFiles(llvm::cl::ExpansionContext &ExpCtx) {
return false;
}
+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;
+}
+
Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
llvm::PrettyStackTraceString CrashInfo("Compilation construction");
@@ -1836,6 +1863,26 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
else
BuildActions(*C, C->getArgs(), Inputs, C->getActions());
+ if (C->getArgs().hasFlag(options::OPT_fmodules_driver,
+ options::OPT_fno_modules_driver, false)) {
+ // TODO: When -fmodules-driver is no longer experimental, it should be
+ // enabled by default only if both conditions are met: (1) there are two or
+ // more C++ source inputs; and (2) at least one input uses C++20 named
+ // modules.
+ // The detection logic for this is kept here only for diagnostics until
+ // is enabled by default.
+ bool UsesCXXModules = hasCXXModuleInputType(Inputs);
+ if (!UsesCXXModules) {
+ const auto ErrOrScanResult = ScanInputsForCXX20ModulesUsage(Inputs);
+ if (!ErrOrScanResult) {
+ Diags.Report(diag::err_cannot_open_file)
+ << ErrOrScanResult.getError().message();
+ }
+ UsesCXXModules = *ErrOrScanResult;
+ }
+ Diags.Report(diag::remark_performing_driver_managed_module_build);
+ }
+
if (CCCPrintPhases) {
PrintActions(*C);
return C;
@@ -4320,33 +4367,6 @@ 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");
@@ -4358,33 +4378,6 @@ 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 || Args.hasArg(options::OPT_fmodules))
- 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) ||
@@ -4680,12 +4673,6 @@ void Driver::BuildDefaultActions(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);
-}
-
/// Returns the canonical name for the offloading architecture when using a HIP
/// or CUDA architecture.
static StringRef getCanonicalArchString(Compilation &C,
>From 810ce52cac5f62bd5cd2f1544eaec2e784563535 Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at outlook.com>
Date: Sun, 31 Aug 2025 17:22:21 +0200
Subject: [PATCH 2/2] [clang][modules-driver] Add initial support for
driver-managed module builds
With -fmodules-driver enabled, the Clang driver provides native
support for:
- Named module imports defined in other source files on the command
line
- Standard library imports (import std; and import std.compat;)
- Clang modules discovered via module map files
Regular translation units can import both Clang modules and C++20
named modules. Importing a Clang module into a C++20 named
module interface unit, or vice versa, is not supported by this patch.
---
.../clang/Basic/DiagnosticDriverKinds.td | 16 +
clang/include/clang/Driver/Driver.h | 11 +-
clang/include/clang/Driver/Job.h | 3 +
clang/include/clang/Driver/ModulesDriver.h | 52 +
clang/include/clang/Driver/Options.td | 6 +
clang/lib/Driver/CMakeLists.txt | 2 +
clang/lib/Driver/Driver.cpp | 84 +-
clang/lib/Driver/ModulesDriver.cpp | 1477 +++++++++++++++++
.../modules-driver-compile-both-kinds.cpp | 94 ++
.../modules-driver-dep-scan-diagnostics.cpp | 25 +
.../modules-driver-dep-scan-graphviz.cpp | 89 +
.../modules-driver-duplicate-named-module.cpp | 20 +
12 files changed, 1821 insertions(+), 58 deletions(-)
create mode 100644 clang/include/clang/Driver/ModulesDriver.h
create mode 100644 clang/lib/Driver/ModulesDriver.cpp
create mode 100644 clang/test/Driver/modules-driver-compile-both-kinds.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 b8c7c6e8d6909..596fd4a37d83f 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -587,6 +587,22 @@ def remark_found_cxx20_module_usage : Remark<
def remark_performing_driver_managed_module_build : Remark<
"performing driver managed module build">,
InGroup<ModulesDriver>;
+def remark_std_module_manifest_path : Remark<
+ "using std modules manifest: '%0'">, InGroup<ModulesDriver>;
+def err_failed_parse_modules_manifest_json: Error<
+ "failed to parse the std modules manifest">;
+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/Driver.h b/clang/include/clang/Driver/Driver.h
index ec574f5796117..ba0168385c69b 100644
--- a/clang/include/clang/Driver/Driver.h
+++ b/clang/include/clang/Driver/Driver.h
@@ -135,6 +135,10 @@ class Driver {
/// interpretation.
bool ModulesModeCXX20;
+ /// Set if the dirver should plan the compilation after scanning module
+ /// dependencies, using the scan results (set by -f(no-)modules-driver.)
+ bool DriverManagedModulesBuild;
+
/// LTO mode selected via -f(no-)?lto(=.*)? options.
LTOKind LTOMode;
@@ -796,13 +800,6 @@ class Driver {
/// compilation based on which -f(no-)?lto(=.*)? option occurs last.
void setLTOMode(const llvm::opt::ArgList &Args);
- /// 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/Job.h b/clang/include/clang/Driver/Job.h
index 561866197b780..c71ad8538e6c2 100644
--- a/clang/include/clang/Driver/Job.h
+++ b/clang/include/clang/Driver/Job.h
@@ -221,6 +221,8 @@ class Command {
const char *getExecutable() const { return Executable; }
+ llvm::opt::ArgStringList &getArguments() { return Arguments; }
+
const llvm::opt::ArgStringList &getArguments() const { return Arguments; }
const std::vector<InputInfo> &getInputInfos() const { return InputInfoList; }
@@ -277,6 +279,7 @@ class JobList {
/// Clear the job list.
void clear();
+ list_type &getJobs() { return Jobs; }
const list_type &getJobs() const { return Jobs; }
bool empty() const { return Jobs.empty(); }
diff --git a/clang/include/clang/Driver/ModulesDriver.h b/clang/include/clang/Driver/ModulesDriver.h
new file mode 100644
index 0000000000000..7720bddb8d4b0
--- /dev/null
+++ b/clang/include/clang/Driver/ModulesDriver.h
@@ -0,0 +1,52 @@
+//===- 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/Driver/Types.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/VirtualFileSystem.h"
+
+namespace clang {
+class DiagnosticsEngine;
+namespace driver {
+class Compilation;
+} // namespace driver
+} // namespace clang
+
+namespace clang::driver::modules {
+
+using InputTy = std::pair<types::ID, const llvm::opt::Arg *>;
+
+using InputList = llvm::SmallVector<InputTy, 16>;
+
+/// Checks whether the -fmodules-driver feature should be implicitly enabled.
+///
+/// When -fmodules-driver is no longer experimental, it should be enabled by
+/// default iff both conditions are met:
+/// (1) there are two or more C++ source inputs; and
+/// (2) at least one input uses C++20 named modules.
+bool shouldEnableModulesDriver(const InputList &Inputs,
+ llvm::vfs::FileSystem &VFS,
+ DiagnosticsEngine &Diags);
+
+/// Appends the std and std.compat module inputs.
+bool ensureNamedModuleStdLibraryInputs(Compilation &C, InputList &Inputs);
+
+bool performDriverModuleBuild(Compilation &C, DiagnosticsEngine &Diags);
+
+} // namespace clang::driver::modules
+
+#endif
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 82e8212bee12d..ed097130ac54f 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3302,6 +3302,12 @@ def fmodules_driver : Flag<["-"], "fmodules-driver">,
def fno_modules_driver : Flag<["-"], "fno-modules-driver">,
Group<f_Group>, Visibility<[ClangOption]>,
HelpText<"Disable support for driver managed module builds (experimental)">;
+def fimplicit_import_std : Flag<["-"], "fimplicit-import-std">,
+ Group<f_Group>, Visibility<[ClangOption]>,
+ HelpText<"Implicitly add the std module when discovered in driver managed module builds">;
+def fno_implicit_import_std : Flag<["-"], "fno-implicit-import-std">,
+ Group<f_Group>, Visibility<[ClangOption]>,
+ HelpText<"Don't implicitly add the std module when discovered in driver managed module builds">;
def experimental_modules_reduced_bmi : Flag<["-"], "fexperimental-modules-reduced-bmi">,
Group<f_Group>, Visibility<[ClangOption, CC1Option]>, Alias<fmodules_reduced_bmi>;
diff --git a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt
index 7c4f70b966c48..43f11fe623893 100644
--- a/clang/lib/Driver/CMakeLists.txt
+++ b/clang/lib/Driver/CMakeLists.txt
@@ -21,6 +21,7 @@ add_clang_library(clangDriver
Driver.cpp
DriverOptions.cpp
Job.cpp
+ ModulesDriver.cpp
Multilib.cpp
MultilibBuilder.cpp
OffloadBundler.cpp
@@ -98,6 +99,7 @@ add_clang_library(clangDriver
LINK_LIBS
clangBasic
+ clangDependencyScanning
clangLex
${system_libs}
)
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index d69012885ad4c..89ee1884393fa 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -60,6 +60,7 @@
#include "clang/Driver/Compilation.h"
#include "clang/Driver/InputInfo.h"
#include "clang/Driver/Job.h"
+#include "clang/Driver/ModulesDriver.h"
#include "clang/Driver/Options.h"
#include "clang/Driver/Phases.h"
#include "clang/Driver/SanitizerArgs.h"
@@ -1473,33 +1474,6 @@ bool Driver::loadDefaultConfigFiles(llvm::cl::ExpansionContext &ExpCtx) {
return false;
}
-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;
-}
-
Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
llvm::PrettyStackTraceString CrashInfo("Compilation construction");
@@ -1853,6 +1827,18 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
}
}
+ if (C->getArgs().hasFlag(options::OPT_fmodules_driver,
+ options::OPT_fno_modules_driver, false)) {
+ // The detection logic for this is kept here only for diagnostics until
+ // is enabled by default.
+ modules::shouldEnableModulesDriver(Inputs, getVFS(), Diags);
+ Diags.Report(diag::remark_performing_driver_managed_module_build);
+ if (C->getArgs().hasFlag(options::OPT_fimplicit_import_std,
+ options::OPT_fno_implicit_import_std, true)) {
+ modules::ensureNamedModuleStdLibraryInputs(*C, Inputs);
+ }
+ }
+
// Populate the tool chains for the offloading devices, if any.
CreateOffloadingDeviceToolChains(*C, Inputs);
@@ -1863,26 +1849,6 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
else
BuildActions(*C, C->getArgs(), Inputs, C->getActions());
- if (C->getArgs().hasFlag(options::OPT_fmodules_driver,
- options::OPT_fno_modules_driver, false)) {
- // TODO: When -fmodules-driver is no longer experimental, it should be
- // enabled by default only if both conditions are met: (1) there are two or
- // more C++ source inputs; and (2) at least one input uses C++20 named
- // modules.
- // The detection logic for this is kept here only for diagnostics until
- // is enabled by default.
- bool UsesCXXModules = hasCXXModuleInputType(Inputs);
- if (!UsesCXXModules) {
- const auto ErrOrScanResult = ScanInputsForCXX20ModulesUsage(Inputs);
- if (!ErrOrScanResult) {
- Diags.Report(diag::err_cannot_open_file)
- << ErrOrScanResult.getError().message();
- }
- UsesCXXModules = *ErrOrScanResult;
- }
- Diags.Report(diag::remark_performing_driver_managed_module_build);
- }
-
if (CCCPrintPhases) {
PrintActions(*C);
return C;
@@ -4236,10 +4202,20 @@ 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);
+ if (Args.hasArgNoClaim(options::OPT_fmodules_driver)) {
+ // HACK: This should be only added for the Standard library jobs, explicitly
+ // created by the modules driver.
+ MakeInputArg(Args, getOpts(),
+ Args.MakeArgString("-Wno-reserved-module-identifier"));
+ if (Args.hasArg(options::OPT_fmodules)) {
+ Args.eraseArg(options::OPT_fmodules);
+ Arg *Arg = Args.MakeSeparateArg(
+ nullptr, getOpts().getOption(options::OPT_fimplicit_modules),
+ Args.MakeArgString(("-fimplicit-modules")));
+ Arg->claim();
+ Args.append(Arg);
+ }
+ }
Arg *FinalPhaseArg;
phases::ID FinalPhase = getFinalPhase(Args, &FinalPhaseArg);
@@ -5427,6 +5403,12 @@ void Driver::BuildJobs(Compilation &C) const {
}
}
}
+ if (C.getArgs().hasFlag(options::OPT_fmodules_driver,
+ options::OPT_fno_modules_driver, false)) {
+ auto Success = modules::performDriverModuleBuild(C, C.getDriver().Diags);
+ if (!Success)
+ return;
+ }
}
namespace {
diff --git a/clang/lib/Driver/ModulesDriver.cpp b/clang/lib/Driver/ModulesDriver.cpp
new file mode 100644
index 0000000000000..e21d7fc55a168
--- /dev/null
+++ b/clang/lib/Driver/ModulesDriver.cpp
@@ -0,0 +1,1477 @@
+//===- 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/ModulesDriver.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticDriver.h"
+#include "clang/Driver/Compilation.h"
+#include "clang/Driver/Driver.h"
+#include "clang/Driver/InputInfo.h"
+#include "clang/Driver/Job.h"
+#include "clang/Driver/Tool.h"
+#include "clang/Driver/Types.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
+#include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h"
+#include "clang/Tooling/DependencyScanning/ModuleDepCollector.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/DepthFirstIterator.h"
+#include "llvm/ADT/DirectedGraph.h"
+#include "llvm/ADT/PostOrderIterator.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/TypeSwitch.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/Option.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/GraphWriter.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/ThreadPool.h"
+#include "llvm/TargetParser/Host.h"
+#include <atomic>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace clang;
+using namespace clang::driver;
+using namespace llvm;
+using namespace llvm::opt;
+
+namespace clang::tooling {
+namespace deps = dependencies;
+} // namespace clang::tooling
+
+using OwnedJobList = SmallVector<std::unique_ptr<Command>, 4>;
+
+//===----------------------------------------------------------------------===//
+// Check: Enable -fmodules-driver implicitly
+//===----------------------------------------------------------------------===//
+
+namespace clang::driver::modules {
+
+/// Returns true if any input is a `.cppm` file.
+static bool hasCXXModuleInputType(const 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);
+}
+
+/// 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
+/// failure to read the input source.
+static llvm::ErrorOr<bool>
+ScanInputsForCXX20ModulesUsage(const InputList &Inputs,
+ llvm::vfs::FileSystem &VFS,
+ DiagnosticsEngine &Diags) {
+ 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;
+}
+
+/// Checks if the -fmodules-driver feature should be implicitly enabled for this
+/// compilation.
+///
+/// The -fmodules-driver feature should be implicitly enabled iff (1) any input
+/// makes used of C++20 named modules; and (2) there are more than two source
+/// input files.
+///
+/// \returns true if the -fmodules-driver feature should be enabled, false
+/// otherwise.
+bool shouldEnableModulesDriver(const InputList &Inputs,
+ llvm::vfs::FileSystem &VFS,
+ DiagnosticsEngine &Diags) {
+ if (Inputs.size() < 2)
+ return false;
+
+ bool UsesCXXModules = hasCXXModuleInputType(Inputs);
+ if (UsesCXXModules)
+ return true;
+
+ const auto ErrOrScanResult =
+ ScanInputsForCXX20ModulesUsage(Inputs, VFS, Diags);
+ if (!ErrOrScanResult) {
+ Diags.Report(diag::err_cannot_open_file)
+ << ErrOrScanResult.getError().message();
+ }
+ return *ErrOrScanResult;
+}
+
+/// Builds the a C++ named module input for \c InputFile and adds it to \c Args.
+static void addCXXModuleInput(InputList &Inputs, DerivedArgList &Args,
+ const OptTable &Opts, StringRef InputFile) {
+ Arg *A = new Arg(Opts.getOption(options::OPT_INPUT), InputFile,
+ Args.getBaseArgs().MakeIndex(InputFile),
+ Args.getBaseArgs().MakeArgString(InputFile));
+ Args.AddSynthesizedArg(A);
+ A->claim();
+ Inputs.push_back(std::make_pair(types::TY_CXXModule, A));
+}
+
+/// Parses the std modules manifest and builds the inputs for the discovered
+/// std modules.
+///
+/// \returns true if the modules were added, false failure to read/parse the
+/// manifest (with diagnostics reported using the drivers DiagnosticEngine).
+bool ensureNamedModuleStdLibraryInputs(Compilation &C, InputList &Inputs) {
+ const auto &Driver = C.getDriver();
+ auto &Diags = Driver.getDiags();
+
+ const auto ManifestPath =
+ Driver.GetStdModuleManifestPath(C, C.getDefaultToolChain());
+ Diags.Report(diag::remark_std_module_manifest_path) << ManifestPath;
+ if (ManifestPath == "<NOT PRESENT>")
+ return false;
+
+ llvm::SmallString<256> ManifestDir(ManifestPath);
+ llvm::sys::path::remove_filename(ManifestDir);
+
+ auto MemBufOrErr = llvm::MemoryBuffer::getFile(ManifestPath);
+ if (!MemBufOrErr) {
+ Diags.Report(diag::err_cannot_open_file)
+ << MemBufOrErr.getError().message();
+ return false;
+ }
+ const auto MemBuf = std::move(*MemBufOrErr);
+
+ auto ParsedJsonOrErr = llvm::json::parse(MemBuf->getBuffer());
+ if (!ParsedJsonOrErr) {
+ Diags.Report(diag::err_failed_parse_modules_manifest_json);
+ llvm::consumeError(ParsedJsonOrErr.takeError());
+ return false;
+ }
+ const auto ParsedJson = std::move(*ParsedJsonOrErr);
+
+ const auto *ModulesInfoList = ParsedJson.getAsObject()->getArray("modules");
+ if (!ModulesInfoList)
+ return false;
+
+ const auto Opts = Driver.getOpts();
+ auto &Args = C.getArgs();
+ for (const auto &Entry : *ModulesInfoList) {
+ const auto *ModuleInfoObj = Entry.getAsObject();
+ if (!ModuleInfoObj)
+ return false;
+
+ auto IsStdLib = ModuleInfoObj->getBoolean("is-std-library");
+ if (!IsStdLib && !*IsStdLib)
+ continue;
+
+ if (auto SourcePath = ModuleInfoObj->getString("source-path")) {
+ SmallString<248> AbsSourcePath(ManifestDir);
+ llvm::sys::path::append(AbsSourcePath, *SourcePath);
+ addCXXModuleInput(Inputs, Args, Opts, AbsSourcePath);
+ }
+ }
+
+ return true;
+}
+
+} // namespace clang::driver::modules
+
+//===----------------------------------------------------------------------===//
+// Dependency Scan Diagnostic Reporting Utilities
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// Represents a CharSourceRange within a StandaloneDiagnostic.
+struct SourceOffsetRange {
+ SourceOffsetRange(CharSourceRange Range, const SourceManager &SrcMgr,
+ const LangOptions &LangOpts);
+ unsigned Begin = 0;
+ unsigned End = 0;
+ bool IsTokenRange = false;
+};
+
+/// Represents a FixItHint within 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 a combination of 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, 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 (!sys::path::is_absolute(Abs)) {
+ if (const auto &CWD =
+ SrcMgr.getFileManager().getFileSystemOpts().WorkingDir;
+ !CWD.empty())
+ 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, 0> TranslatedRanges;
+ TranslatedRanges.reserve(StandaloneDiag.Ranges.size());
+ transform(StandaloneDiag.Ranges, std::back_inserter(TranslatedRanges),
+ ConvertOffsetRange);
+
+ SmallVector<FixItHint, 0> 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 DiagnosticsEngine usually does not have a SourceManager at this
+/// point in 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 = makeIntrusiveRefCnt<FileManager>(std::move(Opts));
+ OwnedSrcMgr = 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();
+ }
+};
+
+/// Collects diagnostics in a form that can be retained until after their
+/// associated SourceManager 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);
+ DiagnosticConsumer::HandleDiagnostic(Level, Info);
+ }
+
+ 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
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// A simple dependency action controller that only provides module lookup for
+/// Clang modules.
+class ModuleLookupActionController
+ : public tooling::deps::DependencyActionController {
+public:
+ ModuleLookupActionController(StringRef TempDir) : TempDir(TempDir) {}
+
+ std::string
+ lookupModuleOutput(const tooling::deps::ModuleDeps &MD,
+ tooling::deps::ModuleOutputKind Kind) override {
+ if (Kind == tooling::deps::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> TempDir;
+
+ std::string constructPCMPath(const tooling::deps::ModuleID &ID) const {
+ SmallString<256> ExplicitPCMPath(TempDir);
+ llvm::sys::path::append(ExplicitPCMPath, Twine(ID.ModuleName) + "-" +
+ ID.ContextHash + ".pcm");
+ return std::string(ExplicitPCMPath);
+ }
+};
+} // namespace
+
+/// Returns the full -cc1 command line (incl. executable) for a driver command.
+static std::vector<std::string> buildCC1CommandLine(const Command &Cmd) {
+ const auto &CmdArgList = Cmd.getArguments();
+ std::vector<std::string> Out;
+ Out.reserve(CmdArgList.size() + 1);
+ Out.emplace_back(Cmd.getExecutable());
+ for (const auto &Arg : CmdArgList)
+ Out.emplace_back(Arg);
+ return Out;
+}
+
+/// Scans the full dependencies and Clang module graphs for all user inputs in
+/// \c ScanInputCmds.
+///
+/// The scan input commands are expected to be ordered as:
+/// [ user input jobs..., std module job, std.compat module job ]
+/// The user input jobs are always scanned and the \c std and \c std.compat
+/// jobs are scanned on demand if any user input imports them.
+///
+/// \returns a list of TranslationUnitDeps on succes or an empty list on
+/// failure.
+static SmallVector<tooling::deps::TranslationUnitDeps, 0>
+scanDependencies(const ArrayRef<std::unique_ptr<Command>> ScanInputCmds,
+ DiagnosticsEngine &Diags, StringRef TempDir) {
+ // The last 2 trailing jobs contain the std modules, always in order of first
+ // std and then std.compat.
+ // If the user does not import a standard library module, no scan is performed
+ // for it, and the corresponding job is later removed from the job list.
+ const auto InputCount = ScanInputCmds.size();
+ const auto UserInputCount = InputCount - 2;
+
+ llvm::DefaultThreadPool Pool;
+ llvm::ThreadPoolTaskGroup UserInputTaskGroup(Pool);
+ llvm::ThreadPoolTaskGroup StdModulesTaskGroup(Pool);
+
+ enum class SeenStdModulesState : uint8_t {
+ None = 0,
+ Std = 1,
+ StdAndStdCompat = 2
+ };
+ std::atomic<SeenStdModulesState> SeenStdModules = SeenStdModulesState::None;
+
+ // Helper to update the required std module imports after scanning a user
+ // input.
+ auto UpdateStdImportFlags =
+ [&](const tooling::deps::TranslationUnitDeps &Deps) {
+ if (SeenStdModules.load(std::memory_order_relaxed) ==
+ SeenStdModulesState::StdAndStdCompat)
+ return;
+
+ for (const auto &NamedDep : Deps.NamedModuleDeps) {
+ if (NamedDep == "std.compat") {
+ SeenStdModules.store(SeenStdModulesState::StdAndStdCompat,
+ std::memory_order_relaxed);
+ return;
+ }
+ if (NamedDep == "std") {
+ // If this fails, the state is already set to Std or
+ // StdAndStdCompat.
+ auto Expected = SeenStdModulesState::None;
+ (void)SeenStdModules.compare_exchange_strong(
+ Expected, SeenStdModulesState::Std, std::memory_order_relaxed,
+ std::memory_order_relaxed);
+ }
+ }
+ };
+
+ std::atomic<size_t> NextUserInputIdx = 0;
+ std::atomic<size_t> NextStdInputIdx = UserInputCount;
+
+ // Helper to get the next index to process for a worker.
+ // Workers in the StdModulesTaskGroup wait until all user inputs have finished
+ // scanning, and then scan only the required standard library modules.
+ auto GetNextInputIndex =
+ [&](bool IsStdModulesWorker) -> std::optional<size_t> {
+ const size_t CurUserInputIdx =
+ NextUserInputIdx.fetch_add(1, std::memory_order_relaxed);
+ if (CurUserInputIdx < UserInputCount)
+ return CurUserInputIdx;
+
+ if (IsStdModulesWorker) {
+ // Wait for all scans on user inputs to finish before reading the import
+ // state.
+ UserInputTaskGroup.wait();
+
+ // Because std.compat is always the last list item, and import std.compat
+ // implies import std, we can simply extend iteration based the required
+ // imports.
+ const size_t ExtraCount =
+ static_cast<size_t>(SeenStdModules.load(std::memory_order_relaxed));
+ const size_t CurStdInputIdx =
+ NextStdInputIdx.fetch_add(1, std::memory_order_relaxed);
+ if (CurStdInputIdx < UserInputCount + ExtraCount)
+ return CurStdInputIdx;
+ }
+
+ return std::nullopt;
+ };
+
+ const IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS =
+ llvm::vfs::createPhysicalFileSystem();
+ tooling::deps::DependencyScanningService Service(
+ tooling::deps::ScanningMode::DependencyDirectivesScan,
+ tooling::deps::ScanningOutputFormat::Full);
+
+ SmallVector<tooling::deps::TranslationUnitDeps, 0> TUDepsList(InputCount);
+ SmallVector<SmallVector<StandaloneDiagnostic, 0>, 0> DiagLists(InputCount);
+ std::atomic<bool> HasError = false;
+
+ auto RunScanningWorker = [&](bool IsStdModulesTask) -> void {
+ tooling::deps::DependencyScanningWorker Worker(Service, FS);
+ DenseSet<tooling::deps::ModuleID> AlreadySeen;
+
+ while (const auto MaybeIndex = GetNextInputIndex(IsStdModulesTask)) {
+ const auto Index = *MaybeIndex;
+ const auto &Cmd = *ScanInputCmds[Index];
+ const auto CommandLine = buildCC1CommandLine(Cmd);
+
+ ModuleLookupActionController LookupController(TempDir);
+ StandaloneDiagCollector DiagConsumer;
+ tooling::deps::FullDependencyConsumer DepsConsumer(AlreadySeen);
+
+ const bool Success =
+ Worker.computeDependencies(/*CWD*/ ".", CommandLine, DepsConsumer,
+ LookupController, DiagConsumer);
+ if (!Success)
+ HasError = true;
+
+ DiagLists[Index] = DiagConsumer.takeDiags();
+ TUDepsList[Index] = DepsConsumer.takeTranslationUnitDeps();
+
+ // Check if we additionally need to scan std.cppm or std.compat.cppm.
+ if (Index < UserInputCount)
+ UpdateStdImportFlags(TUDepsList[Index]);
+ }
+ };
+
+ // TODO: Improve the division between task groups. If we have a large amount
+ // of inputs, it would make more sense to have 2 StdModulesTaskGroup workers.
+ // TODO: For a single input, we should not spawn a thread at all.
+ if (UserInputCount > 3) {
+ const size_t Concurrency =
+ std::min(static_cast<size_t>(Pool.getMaxConcurrency()), UserInputCount);
+ if (Concurrency >= 3) {
+ for (size_t I = 0; I < Concurrency - 1; ++I)
+ Pool.async(UserInputTaskGroup, [&]() { RunScanningWorker(false); });
+ }
+ }
+ Pool.async(StdModulesTaskGroup, [&]() { RunScanningWorker(true); });
+ Pool.wait();
+
+ // Report diagnostics in the original source input order.
+ const StandaloneDiagReporter DiagReporter(Diags);
+ for (auto &StandaloneDiagList : DiagLists)
+ for (auto &StandaloneDiag : StandaloneDiagList)
+ DiagReporter.Report(std::move(StandaloneDiag));
+
+ if (HasError)
+ return {};
+
+ // Trim output to omit entries for std library modules which weren't
+ // imported.
+ const size_t UnusedStdModuleImports =
+ 2 - static_cast<size_t>(SeenStdModules.load(std::memory_order_relaxed));
+ TUDepsList.truncate(TUDepsList.size() - UnusedStdModuleImports);
+ return TUDepsList;
+}
+
+//===----------------------------------------------------------------------===//
+// Module Dependency Graph
+//===----------------------------------------------------------------------===//
+
+namespace {
+
+class MDGNode;
+class MDGEdge;
+using MDGNodeBase = DGNode<MDGNode, MDGEdge>;
+using MDGEdgeBase = DGEdge<MDGNode, MDGEdge>;
+using ModuleDepGraphBase = 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) {}
+ virtual ~MDGNode() = 0;
+
+ /// Returns this node's kind.
+ NodeKind getKind() const { return Kind; }
+
+private:
+ NodeKind Kind;
+};
+
+MDGNode::~MDGNode() {}
+
+/// 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 TranslationUnitBackedMDGNode : public MDGNode {
+protected:
+ explicit TranslationUnitBackedMDGNode(
+ const NodeKind Kind, tooling::dependencies::TranslationUnitDeps &TUDeps,
+ const size_t JobIndex)
+ : MDGNode(Kind), JobIndex(JobIndex),
+ NamedModuleDeps(std::move(TUDeps.NamedModuleDeps)),
+ ClangModuleDeps(std::move(TUDeps.ClangModuleDeps)) {
+ assert(!TUDeps.FileDeps.empty() &&
+ "TUDeps has to have a corresponding source file");
+ FileName = TUDeps.FileDeps.front();
+ assert(TUDeps.Commands.size() == 1 &&
+ "Generated multiple commands for a single -cc1 job?");
+ BuildArgs = std::move(TUDeps.Commands.front().Arguments);
+ }
+
+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;
+ }
+
+ size_t JobIndex;
+ std::string FileName;
+ std::vector<std::string> NamedModuleDeps;
+ std::vector<tooling::deps::ModuleID> ClangModuleDeps;
+ std::vector<std::string> BuildArgs;
+};
+
+/// Subclass of MDGNode representing a translation unit that provides a C++20
+/// named module.
+class CXXNamedModuleMDGNode final : public TranslationUnitBackedMDGNode {
+public:
+ CXXNamedModuleMDGNode(tooling::dependencies::TranslationUnitDeps &TUDeps,
+ const size_t JobIndex)
+ : TranslationUnitBackedMDGNode(NodeKind::NamedCXXModule, TUDeps,
+ JobIndex),
+ ModuleName(std::move(TUDeps.ID.ModuleName)) {}
+
+ /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc.
+ static bool classof(const MDGNode *N) {
+ return N->getKind() == MDGNode::NodeKind::NamedCXXModule;
+ }
+
+ std::string ModuleName;
+};
+
+/// Subclass of MDGNode representing a translation unit that does not provide
+/// any module.
+class NonModuleMDGNode final : public TranslationUnitBackedMDGNode {
+public:
+ NonModuleMDGNode(tooling::dependencies::TranslationUnitDeps &TUDeps,
+ const size_t JobIndex)
+ : TranslationUnitBackedMDGNode(NodeKind::NonModule, TUDeps, JobIndex) {}
+
+ /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc.
+ static bool classof(const MDGNode *N) {
+ return N->getKind() == MDGNode::NodeKind::NonModule;
+ }
+};
+
+/// Subclass of MDGNode representing a Clang module unit.
+class ClangModuleMDGNode final : public MDGNode {
+public:
+ ClangModuleMDGNode(tooling::dependencies::ModuleDeps &MD,
+ const size_t ParentJobIndex)
+ : MDGNode(NodeKind::ClangModule), ParentJobIndex(ParentJobIndex),
+ ID(std::move(MD.ID)), ClangModuleDeps(std::move(MD.ClangModuleDeps)),
+ BuildArgs(MD.getBuildArguments()) {}
+
+ /// Define classof to be able to use isa<>, cast<>, dyn_cast<>, etc.
+ static bool classof(const MDGNode *N) {
+ return N->getKind() == MDGNode::NodeKind::ClangModule;
+ }
+
+ size_t ParentJobIndex;
+ tooling::deps::ModuleID ID;
+ std::vector<tooling::deps::ModuleID> ClangModuleDeps;
+ std::vector<std::string> BuildArgs;
+};
+
+/// 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;
+};
+
+class ModuleDepGraphBuilder;
+
+/// A directed graph describing the discovered module dependency relations.
+///
+/// The graph owns all of its nodes and edges.
+/// The graph's root node is initialized on construction.
+class ModuleDepGraph : public ModuleDepGraphBase {
+public:
+ ModuleDepGraph() {
+ Root = new (Alloc.Allocate(sizeof(RootMDGNode), alignof(RootMDGNode)))
+ RootMDGNode();
+ }
+
+ MDGNode *getRoot() { return Root; }
+
+ const MDGNode *getRoot() const { return Root; }
+
+ // Gets the graph's allocator which should be used to allocate all of the
+ // Graph's nodes and edges.
+ BumpPtrAllocator &getAllocator() { return Alloc; }
+
+private:
+ friend class ModuleDepGraphBuilder;
+
+ BumpPtrAllocator Alloc;
+ RootMDGNode *Root = nullptr;
+};
+
+} // anonymous namespace
+
+//===----------------------------------------------------------------------===//
+// Module Dependency Graph: GraphTraits specializations
+//===----------------------------------------------------------------------===//
+
+namespace llvm {
+/// Non-const versions of the GraphTraits specializations for ModuleDepGraph.
+template <> struct GraphTraits<MDGNode *> {
+ using NodeRef = MDGNode *;
+
+ static NodeRef MDGGetTargetNode(MDGEdgeBase *E) {
+ return &E->getTargetNode();
+ }
+
+ using ChildIteratorType =
+ mapped_iterator<MDGNode::iterator, decltype(&MDGGetTargetNode)>;
+ using ChildEdgeIteratorType = 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<ModuleDepGraph *> : GraphTraits<MDGNode *> {
+ using GraphRef = ModuleDepGraph *;
+ using NodeRef = MDGNode *;
+
+ using nodes_iterator = ModuleDepGraph::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 GraphTraits specializations for ModuleDepGraph.
+template <> struct GraphTraits<const MDGNode *> {
+ using NodeRef = const MDGNode *;
+
+ static NodeRef MDGGetTargetNode(const MDGEdgeBase *E) {
+ return &E->getTargetNode();
+ }
+
+ using ChildIteratorType =
+ mapped_iterator<MDGNode::const_iterator, decltype(&MDGGetTargetNode)>;
+ using ChildEdgeIteratorType = 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 ModuleDepGraph *> : GraphTraits<const MDGNode *> {
+ using GraphRef = const ModuleDepGraph *;
+ using NodeRef = const MDGNode *;
+
+ using nodes_iterator = ModuleDepGraph::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
+
+//===----------------------------------------------------------------------===//
+// Module Dependency Graph: Graph Builder
+//===----------------------------------------------------------------------===//
+
+namespace {
+/// Construction helper for the module dependency graph.
+class ModuleDepGraphBuilder {
+public:
+ explicit ModuleDepGraphBuilder(DiagnosticsEngine &Diags) : Diags(Diags) {}
+
+ bool build(SmallVectorImpl<tooling::deps::TranslationUnitDeps> &&ScanResults);
+
+ ModuleDepGraph takeGraph() { return std::move(Graph); }
+
+private:
+ ModuleDepGraph Graph;
+ DiagnosticsEngine &Diags;
+
+ /// Lookup maps used for connecting the nodes.
+ DenseMap<tooling::deps::ModuleID, ClangModuleMDGNode *> ClangModuleNodeMap;
+ llvm::StringMap<CXXNamedModuleMDGNode *> CXXNamedModuleMap;
+
+ /// Allocation helper using the graph's allocator.
+ template <typename MDGComponent, typename... Args>
+ MDGComponent *makeWithGraphAlloc(Args &&...args) {
+ return new (
+ Graph.Alloc.Allocate(sizeof(MDGComponent), alignof(MDGComponent)))
+ MDGComponent(std::forward<Args>(args)...);
+ }
+
+ void addAllClangModuleNodes(
+ SmallVectorImpl<tooling::deps::TranslationUnitDeps> &ScanResults);
+ void addClangModuleGraph(tooling::deps::ModuleDepsGraph &ClangModuleGraph,
+ size_t ParentJob);
+ void addClangModuleNode(tooling::deps::ModuleDeps &MD, size_t ParentJob);
+ void connectClangModuleNodes();
+
+ bool addAllTUBackedNodes(
+ SmallVectorImpl<tooling::deps::TranslationUnitDeps> &ScanResults);
+ bool addCXXNamedModuleNode(tooling::deps::TranslationUnitDeps &&TUDeps,
+ size_t Job);
+ void addNonModuleNode(tooling::deps::TranslationUnitDeps &&TUDeps,
+ size_t Job);
+ void connectTUBackedNodes();
+
+ void addImportEdge(MDGNode &Imported, MDGNode &Importer);
+};
+} // anonymous namespace
+
+bool ModuleDepGraphBuilder::build(
+ SmallVectorImpl<tooling::deps::TranslationUnitDeps> &&ScanResults) {
+ addAllClangModuleNodes(ScanResults);
+ connectClangModuleNodes();
+ if (!addAllTUBackedNodes(ScanResults))
+ return false;
+ connectTUBackedNodes();
+ return true;
+}
+
+// Construct all Clang module nodes for this graph.
+void ModuleDepGraphBuilder::addAllClangModuleNodes(
+ SmallVectorImpl<tooling::deps::TranslationUnitDeps> &ScanResults) {
+ for (size_t I = 0, E = ScanResults.size(); I < E; ++I) {
+ addClangModuleGraph(ScanResults[I].ModuleGraph, I);
+ }
+}
+
+/// Adds a new Clang module node every not yet seen Clang module in \c
+/// MDGraph.
+void ModuleDepGraphBuilder::addClangModuleGraph(
+ tooling::deps::ModuleDepsGraph &ClangModuleGraph, size_t ParentJob) {
+ for (auto &MD : ClangModuleGraph) {
+ if (ClangModuleNodeMap.contains(MD.ID))
+ continue;
+ addClangModuleNode(MD, ParentJob);
+ }
+}
+
+void ModuleDepGraphBuilder::addClangModuleNode(tooling::deps::ModuleDeps &MD,
+ size_t ParentJob) {
+ auto *Node = makeWithGraphAlloc<ClangModuleMDGNode>(MD, ParentJob);
+ const auto [_, Inserted] = ClangModuleNodeMap.try_emplace(Node->ID, Node);
+ assert(Inserted && "Duplicate nodes for a single Clang module!");
+ Graph.addNode(*Node);
+}
+
+/// Interconnect all Clang module nodes.
+void ModuleDepGraphBuilder::connectClangModuleNodes() {
+ for (auto *Node : nodes(&Graph)) {
+ auto *Importer = cast<ClangModuleMDGNode>(Node);
+
+ if (Importer->ClangModuleDeps.empty()) {
+ // No imports: connect to root for reachability and continue.
+ addImportEdge(*Graph.getRoot(), *Importer);
+ continue;
+ }
+
+ for (const auto &ID : Importer->ClangModuleDeps) {
+ // The dependency scanning worker is expected to error if any required
+ // dependency is not found.
+ auto Found = ClangModuleNodeMap.find(ID);
+ assert(Found != ClangModuleNodeMap.end() &&
+ "Missing imported Clang module node");
+ if (Found == ClangModuleNodeMap.end())
+ continue;
+
+ MDGNode *Imported = Found->second;
+ addImportEdge(*Imported, *Importer);
+ }
+ }
+}
+
+// Construct all nodes for this graph, which represent the dependencies of a
+// source input.
+bool ModuleDepGraphBuilder::addAllTUBackedNodes(
+ SmallVectorImpl<tooling::deps::TranslationUnitDeps> &ScanResults) {
+ for (size_t I = 0, E = ScanResults.size(); I < E; ++I) {
+ auto &TUDeps = ScanResults[I];
+ if (TUDeps.ID.ModuleName.empty()) {
+ // Non-module TU
+ addNonModuleNode(std::move(TUDeps), I);
+ } else {
+ // C++ named module TU
+ if (!addCXXNamedModuleNode(std::move(TUDeps), I))
+ return false;
+ }
+ }
+ return true;
+}
+
+/// Adds a node representing the non-module translation unit described by \c
+/// TUDeps.
+void ModuleDepGraphBuilder::addNonModuleNode(
+ tooling::deps::TranslationUnitDeps &&TUDeps, size_t Job) {
+ auto *Node = makeWithGraphAlloc<NonModuleMDGNode>(TUDeps, Job);
+ Graph.addNode(*Node);
+}
+
+/// Adds a node representing the C++ named module unit described by \c
+/// TUDeps.
+///
+/// \returns false on error if a node for the same module interface already
+/// exists in the graph.
+bool ModuleDepGraphBuilder::addCXXNamedModuleNode(
+ tooling::deps::TranslationUnitDeps &&TUDeps, size_t Job) {
+ CXXNamedModuleMDGNode *Node =
+ makeWithGraphAlloc<CXXNamedModuleMDGNode>(TUDeps, Job);
+ StringRef ModuleName = Node->ModuleName;
+ const auto [It, Inserted] = CXXNamedModuleMap.try_emplace(ModuleName, Node);
+ if (!Inserted) {
+ StringRef ExistingFile = It->second->FileName;
+ StringRef ThisFile = Node->FileName;
+ Diags.Report(diag::err_mod_graph_named_module_redefinition)
+ << ModuleName << ExistingFile << ThisFile;
+ return false;
+ }
+
+ Graph.addNode(*Node);
+ return true;
+}
+
+// Construct all nodes for this graph, which represent the dependencies of a
+// source input.
+void ModuleDepGraphBuilder::connectTUBackedNodes() {
+ for (auto *Node : nodes(&Graph)) {
+ if (auto *Importer = dyn_cast<TranslationUnitBackedMDGNode>(Node)) {
+ if (Importer->NamedModuleDeps.empty() &&
+ Importer->ClangModuleDeps.empty()) {
+ addImportEdge(*Graph.getRoot(), *Importer);
+ continue;
+ }
+ for (const auto &Name : Importer->NamedModuleDeps)
+ if (auto *Imported = CXXNamedModuleMap.lookup(Name))
+ addImportEdge(*Imported, *Importer);
+ for (const auto &ID : Importer->ClangModuleDeps)
+ if (auto *Imported = ClangModuleNodeMap.lookup(ID))
+ addImportEdge(*Imported, *Importer);
+ }
+ }
+}
+
+/// Creates and adds an edge from \c Imported to \c Importer.
+void ModuleDepGraphBuilder::addImportEdge(MDGNode &Imported,
+ MDGNode &Importer) {
+ auto *Edge = makeWithGraphAlloc<MDGEdge>(Importer);
+ Imported.addEdge(*Edge);
+}
+
+/// Construct the module dependency graph for the given \c ScanResults.
+///
+/// \returns The constructed graph, or an std::nullopt on error, if
+/// \c ScanResults contains conflicting module definitions.
+static std::optional<ModuleDepGraph> buildModuleDepGraph(
+ SmallVectorImpl<tooling::deps::TranslationUnitDeps> &&ScanResults,
+ DiagnosticsEngine &Diags) {
+ ModuleDepGraphBuilder Builder(Diags);
+ if (!Builder.build(std::move(ScanResults)))
+ return std::nullopt;
+ return Builder.takeGraph();
+}
+
+//===----------------------------------------------------------------------===//
+// Module Dependency Graph: GraphViz Output
+//===----------------------------------------------------------------------===//
+
+namespace llvm {
+template <>
+struct DOTGraphTraits<const ModuleDepGraph *> : DefaultDOTGraphTraits {
+ explicit DOTGraphTraits(bool IsSimple = false)
+ : DefaultDOTGraphTraits(IsSimple) {}
+
+ static std::string getGraphName(const ModuleDepGraph *) {
+ return "Module Dependency Graph";
+ }
+
+ static std::string getGraphProperties(const ModuleDepGraph *) {
+ return "\tnode [shape=Mrecord];\n";
+ }
+
+ static bool isNodeHidden(const MDGNode *N, const ModuleDepGraph *) {
+ return isa<RootMDGNode>(N);
+ }
+
+ static std::string getNodeIdentifier(const MDGNode *N,
+ const ModuleDepGraph *) {
+ SmallString<128> Buf;
+ raw_svector_ostream OS(Buf);
+ TypeSwitch<const MDGNode *>(N)
+ .Case<ClangModuleMDGNode>([&](auto *N) { OS << N->ID.ModuleName; })
+ .Case<CXXNamedModuleMDGNode>([&](auto *N) { OS << N->ModuleName; })
+ .Case<NonModuleMDGNode>([&](auto *N) { OS << N->FileName; })
+ .Default([](auto *) { llvm_unreachable("Unhandled MDGNode kind!"); });
+ OS << " (" << getNodeKindStr(N->getKind()) << ")";
+
+ return std::string(OS.str());
+ }
+
+ static std::string getNodeLabel(const MDGNode *N, const 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;
+ };
+ TypeSwitch<const MDGNode *>(N)
+ .Case<const ClangModuleMDGNode>(
+ [&](auto *N) { PrintModuleName(OS, N->ID.ModuleName); })
+ .Case<const CXXNamedModuleMDGNode>([&](auto *N) {
+ PrintModuleName(OS, N->ModuleName);
+ OS << " \\| ";
+ PrintFilename(OS, N->FileName);
+ })
+ .Case<const NonModuleMDGNode>(
+ [&](auto *N) { PrintFilename(OS, N->FileName); })
+ .Default([](auto *) {
+ llvm::reportFatalInternalError("Unhandled MDGNode kind!");
+ });
+
+ return std::string(OS.str());
+ }
+
+private:
+ static StringRef getNodeKindStr(MDGNode::NodeKind Kind) {
+ using NodeKind = 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 ModuleDepGraph *>
+ : public GraphWriterBase<const ModuleDepGraph *,
+ GraphWriter<const ModuleDepGraph *>> {
+public:
+ using GraphType = const 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 ModuleDepGraph *>::writeNodes() {
+ auto IsNodeVisible = [&](NodeRef N) { return !DTraits.isNodeHidden(N, G); };
+ const auto VisibleNodeRange = make_filter_range(nodes(G), IsNodeVisible);
+ const SmallVector<NodeRef, 0> VisibleNodes(VisibleNodeRange);
+
+ writeNodeDefinitions(VisibleNodes);
+ writeNodeRelations(VisibleNodes);
+}
+
+void GraphWriter<const 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 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
+
+//===----------------------------------------------------------------------===//
+// Modules Driver
+//===----------------------------------------------------------------------===//
+
+/// Returns true if a driver command is a viable dependency scan input.
+/// We consider only clang -cc1 jobs whose inputs are all source inputs.
+static bool isJobForDepScan(const Command &Cmd) {
+ if (llvm::StringRef(Cmd.getCreator().getName()) != "clang")
+ return false;
+ auto IsSrcInput = [](const InputInfo &II) -> bool {
+ return types::isSrcFile(II.getType());
+ };
+ return llvm::all_of(Cmd.getInputInfos(), IsSrcInput);
+}
+
+/// Partition \c Cmds into dependency scan input jobs and regular jobs.
+static std::pair<OwnedJobList, OwnedJobList> partitionsCmds(JobList &&Cmds) {
+ OwnedJobList ScanInputCmds, DependentCmds;
+ for (auto &Job : Cmds.getJobs()) {
+ if (isJobForDepScan(*Job))
+ ScanInputCmds.push_back(std::move(Job));
+ else
+ DependentCmds.push_back(std::move(Job));
+ }
+ Cmds.getJobs().clear();
+ return {std::move(ScanInputCmds), std::move(DependentCmds)};
+}
+
+/// Modifies \c ScanInputCmds and \c DependentCmds to delete the jobs for std
+/// modules which were not imported.
+static void pruneUnusedStdModuleJobs(
+ SmallVectorImpl<std::unique_ptr<Command>> &ScanInputCmds,
+ SmallVectorImpl<std::unique_ptr<Command>> &DependentCmds,
+ SmallVector<tooling::deps::TranslationUnitDeps, 0> ScanOutputs) {
+ const size_t StdModulesToRemove = ScanInputCmds.size() - ScanOutputs.size();
+ if (StdModulesToRemove == 0)
+ return;
+
+ const size_t UnusedStdModulesFirst = ScanOutputs.size();
+ const size_t UnusedStdModulesEnd = ScanInputCmds.size();
+
+ // Remove the object files, created by Standard library module jobs which
+ // are to be deleted, from the final linker job.
+ llvm::StringSet<> LinkerInputsToRemove;
+ for (size_t I = UnusedStdModulesFirst; I < UnusedStdModulesEnd; ++I) {
+ for (StringRef Out : ScanInputCmds[I]->getOutputFilenames())
+ LinkerInputsToRemove.insert(Out);
+ }
+
+ if (!LinkerInputsToRemove.empty() && !DependentCmds.empty()) {
+ const auto &MaybeLinkJob = DependentCmds.back();
+ if (MaybeLinkJob->getSource().getKind() ==
+ Action::ActionClass::LinkJobClass) {
+ const auto &LinkJobArguments = MaybeLinkJob->getArguments();
+ ArgStringList NewArgList;
+ NewArgList.reserve(LinkJobArguments.size());
+ for (const char *Arg : LinkJobArguments) {
+ if (LinkerInputsToRemove.contains(StringRef(Arg)))
+ continue;
+ NewArgList.push_back(Arg);
+ }
+ MaybeLinkJob->replaceArguments(NewArgList);
+ }
+ }
+
+ // Delete the unused Standard library module jobs themselves.
+ ScanInputCmds.erase(
+ ScanInputCmds.begin() + static_cast<ptrdiff_t>(UnusedStdModulesFirst),
+ ScanInputCmds.begin() + static_cast<ptrdiff_t>(UnusedStdModulesEnd));
+}
+
+static SmallVector<MDGNode *, 0>
+getTopoSortedNodeList(ModuleDepGraph &ModuleGraph) {
+ SmallVector<MDGNode *, 0> PostOrder;
+ PostOrder.reserve(ModuleGraph.size());
+
+ for (auto *N : llvm::post_order(&ModuleGraph))
+ PostOrder.push_back(N);
+
+ SmallVector<MDGNode *, 0> TopoSortedGraph = PostOrder;
+ std::reverse(TopoSortedGraph.begin(), TopoSortedGraph.end());
+ // Drop the root node!
+ return SmallVector<MDGNode *, 0>(TopoSortedGraph.begin() + 1,
+ TopoSortedGraph.end());
+}
+
+static OwnedJobList installBuildArgsAndClangModuleJobs(
+ Compilation &C, SmallVectorImpl<MDGNode *> &NodesInBuildOrder,
+ OwnedJobList &ScanInputCmds) {
+ OwnedJobList Out;
+ Out.reserve(ScanInputCmds.size());
+
+ auto InstallArgStrList = [&](Command &Cmd, ArrayRef<std::string> Args) {
+ ArgStringList NewArgs;
+ NewArgs.reserve(Args.size());
+
+ auto &TCArgs = C.getArgsForToolChain(
+ &Cmd.getCreator().getToolChain(), Cmd.getSource().getOffloadingArch(),
+ Cmd.getSource().getOffloadingDeviceKind());
+ for (const auto &S : Args)
+ NewArgs.push_back(TCArgs.MakeArgString(S));
+
+ Cmd.replaceArguments(NewArgs);
+ };
+
+ for (size_t I = 0, E = NodesInBuildOrder.size(); I < E; ++I) {
+ auto *Node = NodesInBuildOrder[I];
+ TypeSwitch<MDGNode *>(Node)
+ .Case<ClangModuleMDGNode>([&](auto *N) {
+ const auto &ParentJob = ScanInputCmds[N->ParentJobIndex];
+ auto ClangModuleJob = std::make_unique<Command>(*ParentJob);
+ InstallArgStrList(*ClangModuleJob, N->BuildArgs);
+ N->ParentJobIndex = I;
+ Out.push_back(std::move(ClangModuleJob));
+ })
+ .Case<CXXNamedModuleMDGNode, NonModuleMDGNode>([&](auto *N) {
+ auto Job = std::move(ScanInputCmds[N->JobIndex]);
+ N->JobIndex = I;
+ InstallArgStrList(*Job, N->BuildArgs);
+ Out.push_back(std::move(Job));
+ })
+ .Default([](auto *) {
+ llvm::reportFatalInternalError("Unhandled MDGNode kind!");
+ });
+ }
+ return Out;
+}
+
+static llvm::opt::InputArgList
+parseInputArgList(llvm::opt::ArgStringList Argv) {
+ const auto &Opts = getDriverOptTable();
+ unsigned MissingArgIndex = 0, MissingArgCount = 0;
+
+ return Opts.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
+ llvm::opt::Visibility(options::CC1Option));
+}
+
+static void
+linkBuildArguments(Compilation &C, ArrayRef<MDGNode *> NodesInBuildOrder,
+ SmallVectorImpl<std::unique_ptr<Command>> &OrderedCC1Jobs,
+ SmallVectorImpl<std::unique_ptr<Command>> &JobListTail) {
+ auto constructPCMPath = [](Compilation &C, const StringRef &ModuleName) {
+ return C.getDriver().CreateTempFile(C, ModuleName, "pcm");
+ };
+ // Propagate -fmodule-file=<module-name>=<module-path>
+ for (auto *Node : NodesInBuildOrder) {
+ if (auto *TUNode = llvm::dyn_cast<CXXNamedModuleMDGNode>(Node)) {
+ StringRef FmoduleOutput;
+ auto &Job = OrderedCC1Jobs[TUNode->JobIndex];
+ auto &Args = Job->getArguments();
+ auto InputArgsList = parseInputArgList(Args);
+ FmoduleOutput = constructPCMPath(C, TUNode->ModuleName);
+ auto &TCArgs =
+ C.getArgsForToolChain(&Job->getCreator().getToolChain(),
+ Job->getSource().getOffloadingArch(),
+ Job->getSource().getOffloadingDeviceKind());
+ // Always create an -fmodule-output to take control of the output path.
+ Args.push_back(
+ TCArgs.MakeArgString(Twine("-fmodule-output=") + FmoduleOutput));
+ Args.push_back("-fmodules-reduced-bmi");
+ // Hack: Until we properly link the Jobs, hack to omit the warning for
+ // the std module.
+ Args.push_back("-Wno-reserved-module-identifier");
+
+ // Propogate to dependent -cc1 commands in the graph
+ for (auto *ChildNode : llvm::depth_first(Node)) {
+ auto *ChildTUNode = llvm::cast<TranslationUnitBackedMDGNode>(ChildNode);
+ if (ChildTUNode->JobIndex == TUNode->JobIndex)
+ continue;
+ auto &ChildJob = OrderedCC1Jobs[ChildTUNode->JobIndex];
+ auto &TCArgs = C.getArgsForToolChain(
+ &ChildJob->getCreator().getToolChain(),
+ ChildJob->getSource().getOffloadingArch(),
+ ChildJob->getSource().getOffloadingDeviceKind());
+ ChildJob->getArguments().push_back(
+ TCArgs.MakeArgString(Twine("-fmodule-file=") + TUNode->ModuleName +
+ "=" + FmoduleOutput));
+ }
+
+ // Propogate to dependent commands, which we not part of the dependency
+ // scan.
+ for (auto &TailCmd : JobListTail) {
+ if (StringRef(TailCmd->getCreator().getName()) != "clang")
+ continue;
+ auto II = TailCmd->getInputInfos();
+ if (II.empty())
+ continue;
+ if (II.front().isFilename() &&
+ II.front().getFilename() == TUNode->FileName) {
+ auto &TCArgs = C.getArgsForToolChain(
+ &TailCmd->getCreator().getToolChain(),
+ TailCmd->getSource().getOffloadingArch(),
+ TailCmd->getSource().getOffloadingDeviceKind());
+ TailCmd->getArguments().push_back(
+ TCArgs.MakeArgString(Twine("-fmodule-file=") +
+ TUNode->ModuleName + "=" + FmoduleOutput));
+ }
+ }
+ }
+ }
+}
+
+namespace clang::driver::modules {
+bool performDriverModuleBuild(Compilation &C, DiagnosticsEngine &Diags) {
+ auto [ScanInputCmds, JobListTail] = partitionsCmds(std::move(C.getJobs()));
+
+ // The directory containing all module artifacts.
+ const auto TempDir = C.getDriver().GetTemporaryDirectory("modules-driver");
+ C.addTempFile(C.getArgs().MakeArgString(TempDir));
+
+ auto ScanOutputs = scanDependencies(ScanInputCmds, Diags, TempDir);
+ if (ScanOutputs.empty()) {
+ Diags.Report(diag::err_failed_depdendency_scan);
+ return false;
+ }
+
+ pruneUnusedStdModuleJobs(ScanInputCmds, JobListTail, ScanOutputs);
+
+ auto DepGraph = buildModuleDepGraph(std::move(ScanOutputs), Diags);
+ if (!DepGraph) {
+ Diags.Report(diag::err_building_depdendency_graph);
+ return false;
+ }
+ Diags.Report(diag::remark_printing_module_graph);
+ if (!Diags.isLastDiagnosticIgnored())
+ WriteGraph<const ModuleDepGraph *>(llvm::errs(), &(*DepGraph));
+
+ auto NodesInBuildOrder = getTopoSortedNodeList(*DepGraph);
+
+ auto OrderedCC1Jobs =
+ installBuildArgsAndClangModuleJobs(C, NodesInBuildOrder, ScanInputCmds);
+
+ linkBuildArguments(C, NodesInBuildOrder, OrderedCC1Jobs, JobListTail);
+
+ // Reconstruct the job list.
+ C.getJobs().getJobs() = std::move(OrderedCC1Jobs);
+ for (auto &NonScanJob : JobListTail)
+ C.getJobs().getJobs().push_back(std::move(NonScanJob));
+ return true;
+}
+
+} // namespace clang::driver::modules
diff --git a/clang/test/Driver/modules-driver-compile-both-kinds.cpp b/clang/test/Driver/modules-driver-compile-both-kinds.cpp
new file mode 100644
index 0000000000000..465bb144de931
--- /dev/null
+++ b/clang/test/Driver/modules-driver-compile-both-kinds.cpp
@@ -0,0 +1,94 @@
+// Tests that the modules driver properly compiles both C++20 named modules
+// and Clang modules.
+// This does not test importing a Clang module into a C++20 named module
+// interface unit, or vice versa, is not yet supported.
+// TODO: Support imports between different module types.
+
+// RUN: split-file %s %t
+// RUN: %clang++ -std=c++23 -fmodules -fmodules-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
+
+//--- main.cpp
+#include "root.h"
+import A;
+import B;
+
+int main() {
+ sayHello();
+ helloWorld();
+ sayGoodbye();
+ sayGoodbyeTwice();
+}
+
+//--- A.cpp
+export module A;
+export import :B;
+import std;
+import :C;
+
+
+export void sayHello() {
+ sayHelloImpl();
+ std::println("!");
+}
+
+//--- A-B.cpp
+module;
+export module A:B;
+import std;
+import :C;
+
+export void helloWorld() {
+ sayHelloImpl();
+ std::print(" World!\n");
+}
+
+//--- A-C.cpp
+export module A:C;
+import std;
+
+void sayHelloImpl() {
+ std::print("Hello");
+}
+
+//--- B.cpp
+module;
+export module B;
+import A;
+
+export void sayHelloWorldTwice() {
+ helloWorld();
+ helloWorld();
+}
+
+//--- module.modulemap
+module root { header "root.h" export *}
+module direct1 { header "direct1.h" export *}
+module direct2 { header "direct2.h" export *}
+module transitive1 { header "transitive1.h" export *}
+module transitive2 { header "transitive2.h" export * }
+
+//--- root.h
+#include "direct1.h"
+#include "direct2.h"
+void sayGoodbyeTwice() {
+ std::println("Goodbye!");
+ std::println("Goodbye!");
+}
+
+//--- direct1.h
+#include "transitive1.h"
+#include "transitive2.h"
+
+//--- direct2.h
+#include "transitive1.h"
+
+//--- transitive1.h
+#include <print>
+void sayGoodbye() {
+ std::println("Goodbye!");
+}
+
+//--- transitive2.h
+// empty
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..e525da2843fff
--- /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: not %clang -### -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..71b5719927949
--- /dev/null
+++ b/clang/test/Driver/modules-driver-dep-scan-graphviz.cpp
@@ -0,0 +1,89 @@
+// 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)" -> "[[PREFIX]]/main.cpp (Non-module source)";
+// 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 A;
+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..15fe3894b9be8
--- /dev/null
+++ b/clang/test/Driver/modules-driver-duplicate-named-module.cpp
@@ -0,0 +1,20 @@
+// 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/A1.cpp %t/A2.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]]/A1.cpp' and '[[PREFIX]]/A2.cpp'
+/// CHECK: clang: error: failed to construct the module dependency graph
+
+//--- main.cpp
+import A;
+
+//--- A1.cpp
+export module A;
+
+//--- A2.cpp
+export module A;
More information about the cfe-commits
mailing list