Skip to content

Commit e6de2b6

Browse files
committed
fix: emit absolute -fprebuilt-module-path so clangd resolves modules
src/build/flags.cppm was emitting a bare `-fprebuilt-module-path=pcm.cache` (or `=gcm.cache`). This works for `mcpp build` because ninja runs commands with cwd = `<projectRoot>/target/<triple>/<fp>/` so the relative path resolves correctly. But the same flag is captured verbatim into `compile_commands.json`, whose `directory` field is `plan.projectRoot`. Per CDB spec, clangd does `cd <directory>` before resolving the args, so it looks for `<projectRoot>/pcm.cache` — which doesn't exist. Effect: `import` resolution silently fails in clangd ("module 'X' not found"), even when `mcpp build` succeeds. The other `-fmodule-file=` flags in the same block were already absolute (escape_path-wrapped); this one was an oversight. Fix: format the flag with `escape_path(plan.outputDir / traits.bmiDir)`. Single line. ninja still works (absolute paths are cwd-independent), and clangd now finds the BMI cache. Regression test: tests/e2e/47_cdb_prebuilt_module_path_abs.sh creates a fresh project, runs `mcpp build`, parses `compile_commands.json`, asserts every `-fprebuilt-module-path=X`: - is absolute (starts with `/` on POSIX or `<drive>:` on Windows) - ends in `/pcm.cache` or `/gcm.cache` - points to an existing directory Pass-through for GCC-only flows that don't emit the flag at all.
1 parent 7f8d3e4 commit e6de2b6

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

src/build/flags.cppm

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,17 @@ CompileFlags compute_flags(const BuildPlan& plan) {
212212
auto traits = mcpp::toolchain::bmi_traits(plan.toolchain);
213213
std::string prebuilt_module_flag;
214214
if (traits.needsPrebuiltModulePath) {
215-
prebuilt_module_flag = std::format(" -fprebuilt-module-path={}", traits.bmiDir);
215+
// Absolute path: a bare `pcm.cache` / `gcm.cache` works at ninja
216+
// time because ninja runs commands with cwd = outputDir, but the
217+
// same flag ends up verbatim in `compile_commands.json` whose
218+
// `directory` field is the project root. clangd does `cd directory`
219+
// before resolving the flag, so a bare relative path points at
220+
// `<projectRoot>/pcm.cache` (which doesn't exist) and `import`
221+
// resolution fails with `module 'X' not found`. The other
222+
// `-fmodule-file=` flags in this block are already escape_path'd
223+
// (absolute) for the same reason — this one was a leftover.
224+
prebuilt_module_flag = std::format(" -fprebuilt-module-path={}",
225+
escape_path(plan.outputDir / traits.bmiDir));
216226
}
217227
f.cxx = std::format("-std=c++23{}{}{}{}{}{}{}{}{}{}", module_flag, std_module_flag,
218228
std_compat_module_flag, prebuilt_module_flag,
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env bash
2+
# requires:
3+
# 47_cdb_prebuilt_module_path_abs.sh — `-fprebuilt-module-path` in
4+
# compile_commands.json must be an ABSOLUTE path, not a bare `pcm.cache` /
5+
# `gcm.cache`. Reason: CDB `directory` is the project root, but ninja runs
6+
# from the outputDir; a bare relative path works at build time only and
7+
# silently breaks clangd's module resolution ("module 'X' not found").
8+
set -e
9+
10+
TMP=$(mktemp -d)
11+
trap "rm -rf $TMP" EXIT
12+
13+
cd "$TMP"
14+
"$MCPP" new app > /dev/null
15+
cd app
16+
"$MCPP" build > /dev/null
17+
18+
cdb=compile_commands.json
19+
[[ -f "$cdb" ]] || { echo "FAIL: no $cdb generated"; exit 1; }
20+
21+
# Extract every -fprebuilt-module-path=<value> token.
22+
# (No jq dependency — grep is enough and portable on macOS / git-bash.)
23+
vals=$(grep -oE '\-fprebuilt-module-path=[^"]+' "$cdb" | sed 's/^-fprebuilt-module-path=//')
24+
if [[ -z "$vals" ]]; then
25+
# No -fprebuilt-module-path emitted = GCC toolchain that uses -fmodules
26+
# only (gcm.cache). bmi_traits sets needsPrebuiltModulePath=false for
27+
# the GCC path. Nothing to assert — pass.
28+
echo "OK (no prebuilt-module-path flag — GCC toolchain)"
29+
exit 0
30+
fi
31+
32+
# Every value must be absolute AND point at the actual build cache dir.
33+
fail=0
34+
while read -r v; do
35+
[[ -z "$v" ]] && continue
36+
echo " checking: $v"
37+
38+
# Absolute path test: leading '/' on POSIX or '<letter>:' on Windows.
39+
if [[ "$v" =~ ^/ || "$v" =~ ^[A-Za-z]: ]]; then
40+
:
41+
else
42+
echo "FAIL: -fprebuilt-module-path value is relative: '$v'"
43+
echo " this breaks clangd because CDB 'directory' = project root,"
44+
echo " but the BMI cache lives under target/<triple>/<fp>/."
45+
fail=1
46+
fi
47+
48+
# Must end with pcm.cache or gcm.cache (sanity).
49+
case "$v" in
50+
*/pcm.cache|*/gcm.cache) ;;
51+
*) echo "FAIL: -fprebuilt-module-path value does not end in pcm.cache/gcm.cache: '$v'"
52+
fail=1 ;;
53+
esac
54+
done <<< "$vals"
55+
56+
[[ $fail -eq 0 ]] || exit 1
57+
58+
# Stronger: clangd would `cd` into CDB's directory then resolve. Verify the
59+
# value, taken at face value (already absolute), points at a real dir.
60+
first=$(echo "$vals" | head -1)
61+
if [[ ! -d "$first" ]]; then
62+
echo "FAIL: -fprebuilt-module-path resolves to a non-existent dir: $first"
63+
echo " (build succeeded, so the BMI dir must exist somewhere — this"
64+
echo " means the flag points to the wrong place even with abs path.)"
65+
exit 1
66+
fi
67+
68+
echo "OK"

0 commit comments

Comments
 (0)