Skip to content

A shared member's compile commands depend on the selected members when another member compiles a module of the same name, so -p and --workspace recompile it in turn #751

Description

@speak-agent

Summary

When two workspace members each compile a module with the same name, a shared member's compile commands depend on which members the invocation selects. mcpp build --workspace gives the shared member's translation units an explicit -fmodule-file=<name>=<build>/pcm.cache/<package>/<name>.pcm. mcpp build -p <member>, whose graph holds only one of the two providers, finds the module through -fprebuilt-module-path instead. Both invocations use the same build directory, so every switch between them recompiles the shared member and relinks what depends on it. Repeating one selection is a no-op.

Reproduction (mcpp 2026.9.30.2, Linux, llvm@22.1.8)

ws/
  mcpp.toml       [workspace] members = ["core", "app", "tool"]
  shared/m.ixx    export module m; export inline int answer() { return 42; }
  core/           lib; sources = ["core.cpp", "../shared/m.ixx"]; core.cpp imports m
  app/            bin; depends on core
  tool/           bin; sources = ["../shared/m.ixx"]; main.cpp imports m
mcpp build --workspace   # builds everything
mcpp build -p app        # Compiling core, Compiling app
mcpp build -p app        # nothing to do
mcpp build --workspace   # Compiling core, Compiling app
mcpp build -p app        # Compiling core, Compiling app

The build directory is the same for both invocations (target/x86_64-linux-gnu/16872967eb4d901b). In the workspace graph, build.ninja maps the module per provider: -fmodule-file=m=…/pcm.cache/core/m.pcm for core, …/pcm.cache/tool/m.pcm for tool. In the -p app graph, core's commands carry neither mapping.

Where it shows

GalTranslPP 3.1.3: its updater now compiles 3rdParty/3rdModule/boost.ixx, which the core library also compiles. In a mcpp build --workspace compile database, 80 of the core's 82 translation units differ from the ones -p GPPCLI plans; the only difference is -fmodule-file=boost=…/pcm.cache/gpp.core/boost.pcm. On a windows-2025 runner, mcpp run -p GPPCLI after mcpp build --workspace recompiled the core for 3m22s (Sunrisepeak/GalTranslPP run 36725347783). With 3.1.2, which compiled the module in one member, the same step took 5.8 s (run 36710506562).

Expected

A member's compile commands depend on the member and its own dependencies, not on unrelated members of the graph. Then -p shares what --workspace built, as e2e 851 states. Two ways would give this:

  • map a package's own modules to its own BMIs in every graph;
  • decide the per-provider mapping by what one program links, not by the whole graph.

Activity

  1. speak-agent commented on Sep 30, 2026

    @speak-agent
    MemberAuthor

    Second measurement, same commit on a warm cache (Sunrisepeak/GalTranslPP run 36741197697, attempt 2, mcpp 2026.9.30.2):

    I have not traced what the CLI pack does in those minutes. The difference from 3.1.2 is the second provider of boost, i.e. the recompile above.

  2. speak-agent commented on Oct 1, 2026

    @speak-agent
    MemberAuthor

    Fixed in 2026.10.1.2 (#754), following .agents/docs/2026-10-01-pack-drive-and-selection-independent-compile-design.md.

    The report's mechanism holds, with one correction to its expectation: features are unified over the selection by design (docs/06, e2e 851 C), so a feature that only one member asks of a shared member still recompiles it when the selection changes. The defect was a command line that changed while its meaning did not, and three facts about the whole graph produced one:

    1. The module census of 2026.9.30.2. A BMI moved below its provider's directory, and the importers were told so, only when the graph held two providers of its name. Every package's BMIs now lie below its own directory except the root's, as object files have since [bug] 对象路径按「父目录名+文件名」折叠,不同目录同名源文件冲突(multiple rules generate *.ddi) #233, and each unit reads one module map of the modules it reaches through its imports (-fmodule-mapper= on GCC, @<build dir>/modmap/<package>-<hash>.modmap on clang and MSVC).
    2. A file a member lists from outside its directory (../shared/m.cppm, the GalTranslPP shape) was owned by the workspace's virtual root, whose object census depended on how many members listed it. Such a unit now belongs to the member that declares it.
    3. -fPIC was a census of shared link units. A member that builds a shared library put it on every unit of the graph. Position independence now follows the target.

    Remedy 2 of the report alone would have left two BMIs at one flat path under --workspace; the placement had to stop depending on the graph first.

    Verification:

    • e2e 872: after --workspace, -p M compiles nothing for every member; emit build-database -p M and --workspace give every source the same argument list; the feature-union exception differs by exactly its define; a cache-served dependency module loads from its package's directory. It fails on 2026.10.1.1 and passes on 2026.10.1.2 under GCC 16 and clang 22.
    • GalTranslPP (the reported case): mcpp run -p GPPCLI after mcpp build --workspace took 3m22s and 4m30s on 2026.9.30.2. With mcpp built from 2026.10.1.2: a pack states its build, every drive takes the job count, and a unit's compile does not depend on the member selection (#751, #753) #754 it took 9 s and 7 s (Sunrisepeak/GalTranslPP runs 36804268525 and 36809872307), 10 s on the final head (36814777279), and 10 s with the released 2026.10.1.2 on PR 3 (36824209051). Both release packs together take 83 to 90 s.
    • The sandbox script .agents/docs/2026-10-01-pack-drive-and-selection-verify.sh, run in an xlings subos --sandbox with the CN mirror against the version installed from the index: sections 1 to 3 (this report's shape, two modules of one name, a shared-library member) pass on 2026.10.1.2, and fail on 2026.10.1.1 with build -p app compiled 3 units and compiled 2 units. The sandbox's proot intermittently fails ninja's reads of its own logs with Bad address on both versions (xlings#635); every section passed on 2026.10.1.2 in at least one of five runs, and every failure there was that error.
    • mcpp-index validated every member with 2026.10.1.2: 28 jobs succeeded and 1 was skipped (mcpplibs/mcpp-index run 36823747543; ci: validate every member with mcpp 2026.10.1.2; latest_mcpp -> 2026.10.1.2 mcpplibs/mcpp-index#497 moves latest_mcpp to it).
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