[clang] [clang][ModulesDriver] Add support for Clang modules to -fmodules-driver (PR #187606)
Naveen Seth Hanig via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 2 01:42:54 PDT 2026
https://github.com/naveen-seth updated https://github.com/llvm/llvm-project/pull/187606
>From c48ed203a98e7aa881d35306fac3b7715f570306 Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at outlook.com>
Date: Thu, 19 Mar 2026 23:35:41 +0100
Subject: [PATCH 1/3] [clang][ModulesDriver] Add support for Clang modules to
-fmodules-driver
This PR adds basic support for Clang modules to the -fmodules-driver
feature, enabling compilations with Clang modules managed natively by
the Clang driver.
Caching is not included in this PR.
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/lib/Driver/ModulesDriver.cpp | 14 ++--
.../modules-driver-clang-modules-only.cpp | 75 +++++++++++++++++++
.../modules-driver-manifest-input-args.cpp | 14 +---
3 files changed, 88 insertions(+), 15 deletions(-)
create mode 100644 clang/test/Driver/modules-driver-clang-modules-only.cpp
diff --git a/clang/lib/Driver/ModulesDriver.cpp b/clang/lib/Driver/ModulesDriver.cpp
index 8740f9615d304..166c7749245c6 100644
--- a/clang/lib/Driver/ModulesDriver.cpp
+++ b/clang/lib/Driver/ModulesDriver.cpp
@@ -25,6 +25,7 @@
#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/SmallVectorExtras.h"
#include "llvm/ADT/TypeSwitch.h"
@@ -1528,9 +1529,12 @@ void driver::modules::runModulesDriver(
// TODO: Install all updated command-lines produced by the dependency scan.
// TODO: Fix-up command-lines for named module imports.
- // TODO: Sort the graph topologically before adding jobs back to the
- // Compilation being built.
- for (auto *N : Graph)
- if (auto *JN = dyn_cast<JobNode>(N))
- C.addCommand(std::move(JN->Job));
+ llvm::ReversePostOrderTraversal<CompilationGraph *> TopologicallySortedNodes(
+ &Graph);
+ assert(isa<RootNode>(*TopologicallySortedNodes.begin()) &&
+ "First node in topological order must be the root!");
+ auto TopologicallySortedJobNodes = llvm::map_range(
+ llvm::drop_begin(TopologicallySortedNodes), llvm::CastTo<JobNode>);
+ for (auto *JN : TopologicallySortedJobNodes)
+ C.addCommand(std::move(JN->Job));
}
diff --git a/clang/test/Driver/modules-driver-clang-modules-only.cpp b/clang/test/Driver/modules-driver-clang-modules-only.cpp
new file mode 100644
index 0000000000000..e99ce7883afba
--- /dev/null
+++ b/clang/test/Driver/modules-driver-clang-modules-only.cpp
@@ -0,0 +1,75 @@
+// Checks that -fmodules-driver correctly handles compilations using Clang modules.
+
+// RUN: split-file %s %t
+// RUN: rm -rf %t/modules-cache
+// RUN: %clang -std=c++23 \
+// RUN: -fmodules-driver -Rmodules-driver \
+// RUN: -fmodules -Rmodule-import \
+// RUN: -fmodule-map-file=%t/module.modulemap \
+// RUN: -fmodules-cache-path=%t/modules-cache \
+// RUN: -fsyntax-only %t/main.cpp 2>&1 \
+// RUN: | sed 's:\\\\\?:/:g' \
+// RUN: | FileCheck -DPREFIX=%/t %s
+
+// The scan itself will also produce [-Rmodule-import] remarks.
+// Let's skip past them, we only care about the final -cc1 commands.
+// CHECK: clang: remark: printing module dependency graph [-Rmodules-driver]
+// CHECK-NEXT: digraph "Module Dependency Graph" {
+// CHECK: }
+
+// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'root' from
+// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'direct1' into 'root'
+// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'transitive1' into 'direct1'
+// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'transitive2' into 'direct1'
+// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'direct2' into 'root'
+// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'transitive2' into 'direct2'
+
+// (Because of missing include guards, this example would also run into
+// redefinition errors when compiling without modules.)
+
+/--- module.modulemap
+module root { header "root.h"}
+module transitive1 { header "transitive1.h" }
+module transitive2 { header "transitive2.h" }
+module direct1 { header "direct1.h" }
+module direct2 { header "direct2.h" }
+
+//--- root.h
+#include "direct1.h"
+#include "direct2.h"
+int fromRoot() {
+ return fromDirect1() + fromDirect2();
+}
+
+//--- direct1.h
+#include "transitive1.h"
+#include "transitive2.h"
+
+int fromDirect1() {
+ return fromTransitive1() + fromTransitive2();
+}
+
+//--- direct2.h
+#include "transitive2.h"
+
+int fromDirect2() {
+ return fromTransitive2() + 2;
+}
+
+//--- transitive1.h
+int fromTransitive1() {
+ return 20;
+}
+
+//--- transitive2.h
+
+int fromTransitive2() {
+ return 10;
+}
+
+//--- main.cpp
+#include "root.h"
+
+int main() {
+ fromRoot();
+}
diff --git a/clang/test/Driver/modules-driver-manifest-input-args.cpp b/clang/test/Driver/modules-driver-manifest-input-args.cpp
index 99765a1943faf..d1c2938a73fb5 100644
--- a/clang/test/Driver/modules-driver-manifest-input-args.cpp
+++ b/clang/test/Driver/modules-driver-manifest-input-args.cpp
@@ -26,17 +26,11 @@
// RUN: %t/main.cpp \
// RUN: -### 2>&1 \
// RUN: | sed 's:\\\\\?:/:g' \
-// RUN: | FileCheck %s -check-prefix=MAIN-CC1 -check-prefix=STDLIB-MOD-CC1 -DPREFIX=%/t
+// RUN: | FileCheck %s -DPREFIX=%/t
-// MAIN-CC1: "-cc1"
-// MAIN-CC1-SAME: "main.cpp"
-// MAIN-CC1-NOT: "-Wno-reserved-module-identifier"
-// MAIN-CC1-NOT: "-internal-isystem" "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/"
-
-// STDLIB-MOD-CC1: "-cc1"
-// STDLIB-MOD-CC1-SAME: "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/std.cppm"
-// STDLIB-MOD-CC1-SAME: "-Wno-reserved-module-identifier"
-// STDLIB-MOD-CC1-SAME: "-internal-isystem" "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/"
+// CHECK: "-cc1" {{.*}} "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/std.cppm"
+// CHECK-SAME: "-Wno-reserved-module-identifier"
+// CHECK-SAME: "-internal-isystem" "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/"
//--- main.cpp
import std;
>From 9f1ab126d79fc45b1bb4bbdfc12b1c5dfaa387b5 Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at outlook.com>
Date: Thu, 2 Apr 2026 03:15:34 +0200
Subject: [PATCH 2/3] Install scan command-lines for all jobs
---
clang/lib/Driver/ModulesDriver.cpp | 49 ++++++++---
.../modules-driver-clang-modules-only.cpp | 86 +++++++++++++++----
.../modules-driver-manifest-input-args.cpp | 10 ++-
3 files changed, 113 insertions(+), 32 deletions(-)
diff --git a/clang/lib/Driver/ModulesDriver.cpp b/clang/lib/Driver/ModulesDriver.cpp
index 7213bc9bde613..de42f4d1daa83 100644
--- a/clang/lib/Driver/ModulesDriver.cpp
+++ b/clang/lib/Driver/ModulesDriver.cpp
@@ -30,6 +30,7 @@
#include "llvm/ADT/SmallVectorExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/ADT/iterator_range.h"
+#include "llvm/Option/ArgList.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/GraphWriter.h"
#include "llvm/Support/JSON.h"
@@ -1256,13 +1257,12 @@ createClangModulePrecompileJob(Compilation &C, const Command &ImportingJob,
/// non-null.
static void createClangModuleJobsAndNodes(
CompilationGraph &Graph, Compilation &C,
- ArrayRef<std::unique_ptr<Command>> Jobs, ArrayRef<size_t> ScannedJobIndices,
+ ArrayRef<std::unique_ptr<Command>> ImportingJobs,
SmallVectorImpl<deps::ModuleDepsGraph> &&ModuleDepGraphsForScannedJobs) {
llvm::DenseSet<deps::ModuleID> AlreadySeen;
- for (auto &&[ScanIndex, ModuleDepsGraph] :
- llvm::enumerate(ModuleDepGraphsForScannedJobs)) {
- const auto &ImportingJob = *Jobs[ScannedJobIndices[ScanIndex]];
-
+ for (auto &&[ImportingJob, ModuleDepsGraph] :
+ llvm::zip_equal(llvm::make_pointee_range(ImportingJobs),
+ ModuleDepGraphsForScannedJobs)) {
for (auto &MD : ModuleDepsGraph) {
const auto Inserted = AlreadySeen.insert(MD.ID).second;
if (!Inserted)
@@ -1275,6 +1275,31 @@ static void createClangModuleJobsAndNodes(
}
}
+/// Installs the command lines produced by the dependency scan into
+/// \p ScannedJobs.
+static void
+installScanCommandLines(Compilation &C,
+ MutableArrayRef<std::unique_ptr<Command>> ScannedJobs,
+ ArrayRef<InputDependencies> InputDepsForScannedJobs) {
+ for (auto &&[Job, InputDeps] : llvm::zip_equal(
+ llvm::make_pointee_range(ScannedJobs), InputDepsForScannedJobs)) {
+ auto BuildArgs = InputDeps.BuildArgs;
+ ArgStringList JobArgs;
+ JobArgs.reserve(BuildArgs.size());
+
+ const auto &SourceAction = Job.getSource();
+ auto &TC = Job.getCreator().getToolChain();
+ auto &TCArgs =
+ C.getArgsForToolChain(&TC, SourceAction.getOffloadingArch(),
+ SourceAction.getOffloadingDeviceKind());
+
+ for (const auto &Arg : BuildArgs)
+ JobArgs.push_back(TCArgs.MakeArgString(Arg));
+
+ Job.replaceArguments(std::move(JobArgs));
+ }
+}
+
/// Creates nodes for all jobs which were scanned for dependencies.
///
/// The updated command lines produced by the dependency scan are installed at a
@@ -1507,16 +1532,17 @@ void driver::modules::runModulesDriver(
Graph, takeJobsAtIndices(Jobs, ScanResult.NonScannableJobIndices));
auto UnusedStdlibModuleJobNodes = createNodesForUnusedStdlibModuleJobs(
Graph, takeJobsAtIndices(Jobs, ScanResult.UnusedStdlibModuleJobIndices));
+
+ auto ScannedJobs = takeJobsAtIndices(Jobs, ScanResult.ScannedJobIndices);
createClangModuleJobsAndNodes(
- Graph, C, Jobs, ScanResult.ScannedJobIndices,
+ Graph, C, /*ImportingJobs*/ ScannedJobs,
std::move(ScanResult.ModuleDepGraphsForScannedJobs));
- createNodesForScannedJobs(
- Graph, takeJobsAtIndices(Jobs, ScanResult.ScannedJobIndices),
- std::move(ScanResult.InputDepsForScannedJobs));
- createRegularEdges(Graph);
+ installScanCommandLines(C, ScannedJobs, ScanResult.InputDepsForScannedJobs);
+ createNodesForScannedJobs(Graph, std::move(ScannedJobs),
+ std::move(ScanResult.InputDepsForScannedJobs));
+ createRegularEdges(Graph);
pruneUnusedStdlibModuleJobs(Graph, UnusedStdlibModuleJobNodes);
-
if (!createModuleDependencyEdges(Graph, Diags))
return;
createAndConnectRoot(Graph);
@@ -1525,7 +1551,6 @@ void driver::modules::runModulesDriver(
if (!Diags.isLastDiagnosticIgnored())
llvm::WriteGraph<const CompilationGraph *>(llvm::errs(), &Graph);
- // TODO: Install all updated command-lines produced by the dependency scan.
// TODO: Fix-up command-lines for named module imports.
llvm::ReversePostOrderTraversal<CompilationGraph *> TopologicallySortedNodes(
diff --git a/clang/test/Driver/modules-driver-clang-modules-only.cpp b/clang/test/Driver/modules-driver-clang-modules-only.cpp
index e99ce7883afba..37a803f9b6fc0 100644
--- a/clang/test/Driver/modules-driver-clang-modules-only.cpp
+++ b/clang/test/Driver/modules-driver-clang-modules-only.cpp
@@ -1,28 +1,81 @@
// Checks that -fmodules-driver correctly handles compilations using Clang modules.
// RUN: split-file %s %t
+// RUN: rm -rf %t/modules-cache
+
+// RUN: %clang -std=c++23 \
+// RUN: -fmodules-driver -Rmodules-driver \
+// RUN: -fmodules -Rmodule-import \
+// RUN: -fmodule-map-file=%t/module.modulemap \
+// RUN: -fmodules-cache-path=%t/modules-cache \
+// RUN: -fsyntax-only %t/main.cpp 2>&1 \
+// RUN: | sed 's:\\\\\?:/:g' \
+// RUN: | FileCheck -DPREFIX=%/t --check-prefix=CHECK-REMARKS %s
+
// RUN: rm -rf %t/modules-cache
// RUN: %clang -std=c++23 \
-// RUN: -fmodules-driver -Rmodules-driver \
-// RUN: -fmodules -Rmodule-import \
-// RUN: -fmodule-map-file=%t/module.modulemap \
-// RUN: -fmodules-cache-path=%t/modules-cache \
-// RUN: -fsyntax-only %t/main.cpp 2>&1 \
-// RUN: | sed 's:\\\\\?:/:g' \
-// RUN: | FileCheck -DPREFIX=%/t %s
+// RUN: -fmodules-driver \
+// RUN: -fmodules \
+// RUN: -fmodule-map-file=%t/module.modulemap \
+// RUN: -fmodules-cache-path=%t/modules-cache \
+// RUN: -fsyntax-only %t/main.cpp \
+// RUN: -### 2>&1 \
+// RUN: | sed 's:\\\\\?:/:g' \
+// RUN: | FileCheck --check-prefix=CHECK-CC1 %s
// The scan itself will also produce [-Rmodule-import] remarks.
// Let's skip past them, we only care about the final -cc1 commands.
-// CHECK: clang: remark: printing module dependency graph [-Rmodules-driver]
-// CHECK-NEXT: digraph "Module Dependency Graph" {
-// CHECK: }
+// CHECK-REMARKS: clang: remark: printing module dependency graph [-Rmodules-driver]
+// CHECK-REMARKS-NEXT: digraph "Module Dependency Graph" {
+// CHECK-REMARKS: }
+
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:2: remark: importing module 'root' from
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:2: remark: importing module 'direct1' into 'root'
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:2: remark: importing module 'transitive1' into 'direct1'
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:2: remark: importing module 'transitive2' into 'direct1'
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:2: remark: importing module 'direct2' into 'root'
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:2: remark: importing module 'transitive2' into 'direct2'
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "-o" "[[TRANSITIVE2PCM:[^"]+]]"
+// CHECK-CC1-SAME: "-emit-module"
+// CHECK-CC1-SAME: "-fmodule-name=transitive2"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
-// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'root' from
-// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'direct1' into 'root'
-// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'transitive1' into 'direct1'
-// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'transitive2' into 'direct1'
-// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'direct2' into 'root'
-// CHECK: [[PREFIX]]/main.cpp:1:2: remark: importing module 'transitive2' into 'direct2'
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "-o" "[[DIRECT2PCM:[^"]+]]"
+// CHECK-CC1-SAME: "-emit-module"
+// CHECK-CC1-SAME: "-fmodule-file=transitive2=[[TRANSITIVE2PCM]]"
+// CHECK-CC1-SAME: "-fmodule-name=direct2"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "-o" "[[TRANSITIVE1PCM:[^"]+]]"
+// CHECK-CC1-SAME: "-emit-module"
+// CHECK-CC1-SAME: "-fmodule-name=transitive1"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "-o" "[[DIRECT1PCM:[^"]+]]"
+// CHECK-CC1-SAME: "-emit-module"
+// CHECK-CC1-SAME: "-fmodule-file=transitive1=[[TRANSITIVE1PCM]]"
+// CHECK-CC1-SAME: "-fmodule-file=transitive2=[[TRANSITIVE2PCM]]"
+// CHECK-CC1-SAME: "-fmodule-name=direct1"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "-o" "[[ROOTPCM:[^"]+]]"
+// CHECK-CC1-SAME: "-emit-module"
+// CHECK-CC1-SAME: "-fmodule-file=direct1=[[DIRECT1PCM]]"
+// CHECK-CC1-SAME: "-fmodule-file=direct2=[[DIRECT2PCM]]"
+// CHECK-CC1-SAME: "-fmodule-name=root"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "-fsyntax-only"
+// CHECK-CC1-SAME: "{{.*}}/main.cpp"
+// CHECK-CC1-SAME: "-fmodule-file=root=[[ROOTPCM]]"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
// (Because of missing include guards, this example would also run into
// redefinition errors when compiling without modules.)
@@ -62,7 +115,6 @@ int fromTransitive1() {
}
//--- transitive2.h
-
int fromTransitive2() {
return 10;
}
diff --git a/clang/test/Driver/modules-driver-manifest-input-args.cpp b/clang/test/Driver/modules-driver-manifest-input-args.cpp
index d1c2938a73fb5..726fe03e27840 100644
--- a/clang/test/Driver/modules-driver-manifest-input-args.cpp
+++ b/clang/test/Driver/modules-driver-manifest-input-args.cpp
@@ -28,9 +28,13 @@
// RUN: | sed 's:\\\\\?:/:g' \
// RUN: | FileCheck %s -DPREFIX=%/t
-// CHECK: "-cc1" {{.*}} "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/std.cppm"
-// CHECK-SAME: "-Wno-reserved-module-identifier"
-// CHECK-SAME: "-internal-isystem" "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/"
+// CHECK: "-cc1" {{.*}} "-Wno-reserved-module-identifier" {{.*}} "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/std.cppm" {{.*}} "-internal-isystem" "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/"
+
+// The adjustments should only be made for inputs from the Standard Library module manifest:
+// Check that the -cc1 command line does not contain those adjustments!
+// CHECK: "-cc1" {{.*}}main
+// CHECK-NOT: "-Wno-reserved-module-identifier"
+// CHECK-NOT: "-internal-isystem" "[[PREFIX]]/Inputs/usr/lib/x86_64-linux-gnu/../share/libc++/v1/"
//--- main.cpp
import std;
>From cd66e7b90990f3831d4a50c0098b59186342a22e Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at outlook.com>
Date: Thu, 2 Apr 2026 10:41:21 +0200
Subject: [PATCH 3/3] Avoid copy + const correctness
---
clang/lib/Driver/ModulesDriver.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Driver/ModulesDriver.cpp b/clang/lib/Driver/ModulesDriver.cpp
index de42f4d1daa83..6dd5624fc2ae2 100644
--- a/clang/lib/Driver/ModulesDriver.cpp
+++ b/clang/lib/Driver/ModulesDriver.cpp
@@ -1283,12 +1283,12 @@ installScanCommandLines(Compilation &C,
ArrayRef<InputDependencies> InputDepsForScannedJobs) {
for (auto &&[Job, InputDeps] : llvm::zip_equal(
llvm::make_pointee_range(ScannedJobs), InputDepsForScannedJobs)) {
- auto BuildArgs = InputDeps.BuildArgs;
+ const auto &BuildArgs = InputDeps.BuildArgs;
ArgStringList JobArgs;
JobArgs.reserve(BuildArgs.size());
const auto &SourceAction = Job.getSource();
- auto &TC = Job.getCreator().getToolChain();
+ const auto &TC = Job.getCreator().getToolChain();
auto &TCArgs =
C.getArgsForToolChain(&TC, SourceAction.getOffloadingArch(),
SourceAction.getOffloadingDeviceKind());
More information about the cfe-commits
mailing list