Skip to content

Implementation partition in a .cpp file is rebuilt on every build and its BMI is never written #790

Closed

Description

@yspbwx2010

An implementation partition (module M:part;) placed in a .cpp file is recompiled on every mcpp build, including builds where nothing changed. The plan declares a BMI for the unit, but the compile step for a .cpp file never writes one, so ninja always sees a missing output.

Minimal project (mcpp 2026.10.8.1, LLVM 23.1.3, --target x86_64-linux-gnu, Linux x86_64):

# mcpp.toml
[package]
name = "repro"
version = "0.1.0"

[toolchain]
default = "llvm@23.1.3"

[targets.repro]
kind = "bin"
main = "src/main.cpp"
// src/repro.cppm
export module repro;

export import :api;
// src/api.cppm
export module repro:api;

export auto answer() -> int;
// src/api_impl.cpp
module repro:api_impl;

import :api;

auto answer() -> int {
    return 42;
}
// src/main.cpp
import repro;
import std;

auto main() -> int {
    std::println("{}", answer());
}

Running mcpp build --target x86_64-linux-gnu --verbose three times: the first build compiles everything and the program prints 42. The second and third builds, with no edits in between, each run the same two steps again, [1/2] compiling src/api_impl.cpp and [2/2] linking.

The generated build.ninja has

build obj/api_impl.o | pcm.cache/repro-api_impl.pcm : cxx_object .../src/api_impl.cpp | obj/api_impl.cpp.ddi.dd

but the compile command is a plain -c .../src/api_impl.cpp -o obj/api_impl.o without -fmodule-output, and pcm.cache/ only ever contains repro.pcm, repro-api.pcm and std.pcm. Because repro-api_impl.pcm never appears, the edge is always dirty.

If any unit imports the partition, the build fails instead. Adding import :api_impl; to src/repro.cppm gives

src/repro.cppm:4:9: fatal error: module 'repro:api_impl' not found

Renaming the file to src/api_impl.cppm makes both cases work, so the problem is limited to the .cpp spelling. I expected mcpp either to build a .cpp file that declares a partition as a module unit and write its BMI, or to refuse it with a message that names the file, the same way an unknown extension is refused. Right now it is accepted silently and the cost only shows up as a rebuild on every run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions