Repository navigation
artifacts dependency rejects duplicate C++ module names across independent executables #732
Description
Activity
- added 2 commits that reference this issue
on Sep 30, 2026 - added a commit that references this issue
on Sep 30, 2026 Fixed in mcpp 2026.9.30.2 (#745, released with #747). The analysis is section F7 of
.agents/docs/2026-09-30-build-wall-time-progress-count-and-hang-plan.md.The rule. A module name identifies one module within one program. GCC and clang name a module's entities and its initializer after the module, so a program cannot link two modules of one name (measured:
multiple definition of value@common()and ofinitializer for module common), and two programs may each have one. A build holds several programs, a package and the programs it ships throughartifactsor the members of a workspace, and mcpp required a name to be unique across the whole build.The change.
- An import is resolved in the importing package's closure: the package and every package it reaches through code and workspace-member edges, not through
artifacts,toolsor[build-dependencies]. One resolver answers which provider an import means, and every reader uses it. - Two providers of one name are refused only when one closure holds both, and the message names that package; one file reached as two packages is recognised by identity, not spelling (the
Updater/../3rdParty/...form of this report). - When two packages provide a name, their BMIs lie below their packages' directories and each compile that may import the name is bound to its provider: a module map for GCC,
-fmodule-file=for clang,/referencefor MSVC;mcpp dyndep --module-mapreads the same map. When every name has one provider nothing changes:build.ninjaandcompile_commands.jsonof the xlings workspace are byte-identical to 2026.9.30.1's.
Notes on the report:
- The reported layout,
boost.ixxcompiled bygpp.coreand bygpp.updater, now builds as it stands, since each program has oneboost. A package that providesboost, which both depend on, would compile it once. - The minimal example's name
commonis one of the top-level module names mcpp's naming rule refuses (core,util,common,std,detail,internal,base), independently of this issue; the tests useboost. - clangd finds a module by its name in the compilation database, so for a name two packages provide it may show the other program's module; the build is not affected (SPEC-005 R3.8a).
Criteria: e2e 847 under GCC 16 and clang 22 (an
artifactsupdater with its own module, an edit rebuilding only its own program, two workspace members, the reported layout, and the two refusals; it fails on 2026.9.30.1 with the error of this issue); e2e 848 under MSVC; unit tests of the resolver. Sandbox verification against the published release: sections 1, 2 and 3 pass against the published 2026.9.30.2 in an xlings sandbox (xlings subos use v9302 --sandbox, CN mirror for xlings and mcpp): an app and itsartifactsupdater each have their own moduleboost; two providers in one program are refused, naming the module; two workspace members each have their ownboost. The full script: 10 of 10 sections pass.- An import is resolved in the importing package's closure: the package and every package it reaches through code and workspace-member edges, not through
Problem
An executable requested through
artifactsis a separate program, but mcpp scans its C++ modules together with the consumer's modules and requires module names to be unique across both programs. This makes otherwise independent executables fail to configure when each has a module with the same name.Observed with mcpp 2026.9.28.2 while changing a GUI's updater dependency from
tools = ["Updater"]toartifacts = ["Updater"]. The GUI uses a core package that compilesboost.ixx, and the updater package also compiled that sameboost.ixxfor its own executable.mcpp build -p GPPGUI --profile fast-release --configure-onlyfailed with:Minimal example
app/mcpp.toml:updater/mcpp.tomlis the same except for the package and target names, and it has no dependency onapp:Both
common.ixxfiles can containexport module common; export int value() { return 1; }, and bothmain.cppfiles can containimport common; int main() { return value() - 1; }. The two module units belong to different executables and need not import each other. Runmcpp build --configure-onlyfromapp/.The minimal example is extracted from the observed project case; I have not run this reduced directory separately.
Expected behavior
artifactsshould build the updater for the consumer's target and profile, while allowing each independent executable to have its own module provider scope (or otherwise isolating the updater's build). The updater's object/BMI should not be linked into the consumer.Actual behavior / likely cause
scan_packages()collects units from every package and then callsresolve_graph()once.resolve_graph()keysproducerOfonly by the logical module name and rejects the second provider, without considering which executable owns it. This rejection is understandable for two providers within one executable's import closure, but it also applies across independent executables brought together by anartifactsedge.The project worked around the conflict by replacing the updater's single
import boost;with a Boost header include and removing its duplicate module source. That avoids this particular error but does not address the scope issue.Related: #711 introduced target-side executable artifacts.