Skip to content

Commit 181cd53

Browse files
committed
feat: Clang multi-module support via static-deps path
- Restore is_gcc gate on dyndep (Clang lacks -fdeps-format=p1689r5; needs clang-scan-deps for P1689, future work) - Fix static-deps path: use implicit output (|) for BMI files so $out only contains the .o file; set $bmi_out for all compilers (GCC ignores it, Clang uses it for -fmodule-output) - Add E2E test 38_llvm_modules.sh: multi-module Clang project with export module + cross-module import, verifying pcm.cache/ layout
1 parent 83304e2 commit 181cd53

2 files changed

Lines changed: 101 additions & 2 deletions

File tree

src/build/ninja_backend.cppm

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,11 @@ bool is_c_source(const std::filesystem::path& src) {
126126
} // namespace
127127

128128
std::string emit_ninja_string(const BuildPlan& plan) {
129-
bool dyndep = dyndep_mode_enabled();
129+
// Clang uses clang-scan-deps (not -fdeps-format=p1689r5) for P1689
130+
// scanning, so dyndep is GCC-only for now. Clang falls through to the
131+
// static-deps path which uses BmiTraits for correct pcm.cache/ paths.
132+
bool dyndep = dyndep_mode_enabled()
133+
&& mcpp::toolchain::is_gcc(plan.toolchain);
130134
auto traits = mcpp::toolchain::bmi_traits(plan.toolchain);
131135
std::string out;
132136
auto append = [&](std::string s) { out += std::move(s); };
@@ -368,12 +372,18 @@ std::string emit_ninja_string(const BuildPlan& plan) {
368372

369373
std::string out_line = "build " + escape_ninja_path(cu.object);
370374
if (cu.providesModule) {
371-
out_line += " " + bmi_path(*cu.providesModule);
375+
// Use implicit output (|) so $out only contains the .o file.
376+
// GCC writes BMI implicitly; Clang uses -fmodule-output=$bmi_out.
377+
out_line += " | " + bmi_path(*cu.providesModule);
372378
}
373379
out_line += std::format(" : {} {}", rule, escape_ninja_path(cu.source));
374380
if (!implicit.empty())
375381
out_line += " |" + implicit;
376382
out_line += "\n";
383+
// Clang needs $bmi_out to emit -fmodule-output=$bmi_out
384+
if (cu.providesModule) {
385+
out_line += " bmi_out = " + bmi_path(*cu.providesModule) + "\n";
386+
}
377387
append(std::move(out_line));
378388
}
379389
append("\n");

tests/e2e/38_llvm_modules.sh

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env bash
2+
# 38_llvm_modules.sh — multi-module project with LLVM/Clang.
3+
#
4+
# Tests: module interface (.cppm) with `export module`, cross-module import,
5+
# dyndep pipeline, BMI path parameterization (pcm.cache/*.pcm), and
6+
# -fmodule-output / -fprebuilt-module-path flags.
7+
set -e
8+
9+
LLVM_ROOT="${HOME}/.mcpp/registry/data/xpkgs/xim-x-llvm/20.1.7"
10+
if [[ ! -x "$LLVM_ROOT/bin/clang++" ]]; then
11+
echo "SKIP: xlings llvm@20.1.7 is not installed"
12+
exit 0
13+
fi
14+
if [[ ! -f "$LLVM_ROOT/share/libc++/v1/std.cppm" ]]; then
15+
echo "SKIP: xlings llvm@20.1.7 has no libc++ std.cppm"
16+
exit 0
17+
fi
18+
19+
TMP=$(mktemp -d)
20+
trap "rm -rf $TMP" EXIT
21+
export MCPP_HOME="$TMP/mcpp-home"
22+
source "$(dirname "$0")/_inherit_toolchain.sh"
23+
24+
mkdir -p "$TMP/proj/src"
25+
cd "$TMP/proj"
26+
27+
cat > mcpp.toml <<'EOF'
28+
[package]
29+
name = "llvm_modules"
30+
version = "0.1.0"
31+
32+
[toolchain]
33+
linux = "llvm@20.1.7"
34+
EOF
35+
36+
# Module interface unit: exports greet()
37+
cat > src/greet.cppm <<'EOF'
38+
export module llvm_modules.greet;
39+
40+
import std;
41+
42+
export std::string greet(std::string_view name) {
43+
return std::format("Hello, {}!", name);
44+
}
45+
EOF
46+
47+
# Main imports the module
48+
cat > src/main.cpp <<'EOF'
49+
import std;
50+
import llvm_modules.greet;
51+
52+
int main() {
53+
std::println("{}", greet("clang"));
54+
return 0;
55+
}
56+
EOF
57+
58+
"$MCPP" build --no-cache > "$TMP/build.log" 2>&1 || {
59+
cat "$TMP/build.log"
60+
echo "FAIL: llvm multi-module build failed"
61+
exit 1
62+
}
63+
64+
binary=$(find target -type f -path '*/bin/llvm_modules' | head -1)
65+
[[ -n "$binary" && -x "$binary" ]] || {
66+
find target -maxdepth 5 -type f
67+
echo "FAIL: llvm_modules binary missing"
68+
exit 1
69+
}
70+
71+
out=$("$binary")
72+
[[ "$out" == "Hello, clang!" ]] || {
73+
echo "FAIL: wrong runtime output: $out"
74+
exit 1
75+
}
76+
77+
# Verify BMI went to pcm.cache/, not gcm.cache/
78+
pcm_dir=$(find target -type d -name 'pcm.cache' | head -1)
79+
[[ -n "$pcm_dir" ]] || {
80+
echo "FAIL: pcm.cache/ directory not found (Clang should use pcm.cache)"
81+
exit 1
82+
}
83+
gcm_dir=$(find target -type d -name 'gcm.cache' | head -1)
84+
[[ -z "$gcm_dir" ]] || {
85+
echo "FAIL: gcm.cache/ directory found (Clang should NOT use gcm.cache)"
86+
exit 1
87+
}
88+
89+
echo "OK"

0 commit comments

Comments
 (0)