Repository navigation
Dependency module unreachable transitively: nested pcm.cache/<ns>.<pkg>/ but only the flat dir goes to -fprebuilt-module-path #816
Description
Summary
A dependency package's module cannot be reached transitively through the project's own module. The dependency's BMI lands in a nested directory pcm.cache/<ns>.<pkg>/, but only the flat pcm.cache is passed to -fprebuilt-module-path, and the explicit -fmodule-file=<name>=<nested path> mapping is attached only to TUs that import the dependency module directly. A TU that imports the project's own module (which imports the dependency's module) gets neither, so clang cannot resolve the transitive import:
error: failed to find module file for module 'X'
Build-blocking for any project whose own module interface imports a dependency package's module — which is the documented use case of module packages such as godotengine.godot-cpp-m (its README: "import godot_cpp; re-exports the whole public godot namespace").
Minimal reproduction (no Godot, 3 files)
odygrd.quill is used only because it is a small module package; the bug is not package-specific.
# mcpp.toml
[package]
name = "bmi-probe2"
version = "0.1.0"
[language]
standard = "c++23"
modules = true
import_std = false
[modules]
sources = ["src/**/*.cppm", "src/**/*.cpp"]
exports = ["mylib"]
[targets.bmi-probe2]
kind = "bin"
main = "src/main.cpp"
[dependencies.odygrd]
quill = "13.0.0"// src/mylib.cppm — the project's own module, importing a dependency's module
module;
#include <string>
export module mylib;
import quill;
export std::string greet() { return "hi"; }// src/helper.h — a plain header that imports the project's own module (the shape of
// any existing header pulled into a .cpp)
#pragma once
import mylib;
inline const char *who() { return "x"; }// src/main.cpp
#include "helper.h"
int main() { return who() ? 0 : 1; }$ mcpp build
Compiling bmi-probe2 v0.1.0 (.)
error: build failed in bmi-probe2 v0.1.0 (.)
failed: obj/main.o
In file included from /tmp/bmi-probe2/src/main.cpp:1:
/tmp/bmi-probe2/src/helper.h:2:1: error: failed to find module file for module 'quill'
Contrast that isolates the condition: the same dependency, imported directly, builds fine —
// src/main.cpp in a second probe, no own module at all
import quill;
int main() { return 0; }$ mcpp build
Compiling bmi-probe v0.1.0 (.)
Finished dev [unoptimized + debuginfo] in 14.84s # OK
So nested BMI directories are not broken per se; what breaks is the transitive path.
Evidence
Both builds place the dependency BMI in a nested directory, and both modmaps name it correctly:
# probe1 (direct import) — works
pcm.cache/odygrd.quill/quill.pcm
modmap: -fmodule-file=quill=…/pcm.cache/odygrd.quill/quill.pcm
unit_cxxflags = -D__MCPP_TARGET_LINUX__=1 @…/modmap/bmi-probe-c7c96848.modmap
^ modmap attached
# probe2 (transitive) — fails
pcm.cache/odygrd.quill/quill.pcm + pcm.cache/mylib.pcm
modmap: -fmodule-file=quill=…/pcm.cache/odygrd.quill/quill.pcm
unit_cxxflags = -D__MCPP_TARGET_LINUX__=1 # ← obj/main.o: NO modmap
unit_cxxflags = -D…=1 @…/modmap/bmi-probe2-….modmap # ← only the mylib TU has it
obj/main.cpp.ddi.dd correctly says it needs pcm.cache/mylib.pcm (flat, found), but loading that BMI requires quill, whose BMI is in the nested directory that -fprebuilt-module-path does not cover. The single global flag is:
-fprebuilt-module-path=/tmp/bmi-probe2/target/x86_64-linux-gnu/<fp>/pcm.cache
Compiling the failing TU by hand confirms both halves:
| invocation (same flags otherwise) | result |
|---|---|
flat -fprebuilt-module-path only (today) |
failed to find module file for module 'quill' |
+ -fprebuilt-module-path=…/pcm.cache/odygrd.quill |
compiles |
+ @modmap (-fmodule-file=quill=<nested path>) |
compiles |
So either of these closes it:
- put dependency BMIs in the flat
pcm.cache(or addpcm.cache/<ns>.<pkg>to-fprebuilt-module-path), or - attach the modmap to every TU, not only the ones that directly import the dependency module.
Impact
Build-blocking, and the workaround is not discoverable from the error message. It surfaced on a GDExtension project whose .cpp files include a header that does import mcpp_kaki.utils;, where mcpp_kaki.utils does import godot_cpp; — i.e. exactly the layering the module packages advertise. It is also why this looked Godot-specific at first: it needs a two-layer module graph, which most small projects do not have.
Current workaround in the affected project (a symlink per build, added automatically when the first mcpp build fails):
ln -s <ns>.<pkg>/<mod>.pcm pcm.cache/<mod>.pcm
Environment
- mcpp
2026.10.17.1(also reproduced on2026.10.8.1) - toolchain
llvm@22.1.8(clang 22.1.8, libc++ 22.1.8), C++23,modules = true - Linux x86_64 (Fedora), glibc payload 2.44
- Headers of the index packages at their current versions (
odygrd.quill13.0.0,godotengine.godot-cpp-m10.0.0-rc1 +compat.godot-cpp10.0.0-rc1)
Related but distinct: #797 (host-module vs [dependencies] disagree on a module library's export surface) — same family of "the two module paths answer the same question differently", different mechanism.
Activity