Hi Ben,

Thank you for the detailed review. I should first clarify
something I failed to state clearly in my original post — the
prototype is not a build system wrapper. It simulates behavior I
believe belongs in the compiler driver itself.

The script exists only because I cannot easily modify a production
compiler. If the committee finds this direction worthwhile, integrating
it into the compiler would be the natural next step.

I've read P3286 — it addresses a different problem from what I'm proposing here. Thank you for the pointer.

1. Same flags for all files.

At the compiler level, a translation unit has one set of flags.
The consumer runs, say, `clang++ -std=c++20 -c main.cpp -lfoo`.
Those same flags go to `main.cpp` and to any interface source
extracted from the archive. This follows naturally once the
behavior is understood as compiler-internal.

2. Incremental compilation.

The compiler can cache the BMI locally, keyed on archive hash,
compiler version, and flags. If the interface source has not
changed, recompilation is skipped. This is the same caching
strategy compilers already use for precompiled headers and
module BMIs.

3. Separate compilation.

A compiler implementing this natively would invoke its own
compilation phase separately for each interface source — one
process per unit, just as it does for every other translation
unit. The prototype script already does this: it loops over
the interface members and calls the compiler independently for
each. The concern is addressed once the behavior is seen as
compiler-internal.

4. Assumption of a specific extension.

You are entirely right, and this is a real oversight on my
part. There is no standard extension for module interface
units, and any proposal must not hardcode one. I will fix the
prototype to be extension-agnostic. Thank you for catching
this.

5. Unconditionally compiling all interface units.

Once this is understood as a compiler step rather than a build
system step, the reasoning follows naturally. The compiler
does not maintain a dependency graph across translation units
— that is the build system's job. What the compiler can do,
within its own scope, is: encounter an archive, find interface
source members, and compile them. Whether a production
compiler compiles all interface members or only those actually
needed is an implementation detail. Neither path requires the
compiler to become a build system — it is simply processing
what it finds in the archive. And when well-designed, an
interface file contains only declarations — so the compilation
cost per file is low, comparable to parsing a header.

6. Requires a library file to carry interface sources.

I acknowledge this limitation. This proposal targets the
scenario where a library is distributed as a static archive
— the `-lfoo` use case. Header-only module libraries, where
no archive or shared library exists, are a separate problem
and a separate discussion. This proposal does not preclude
that use case from being addressed elsewhere.

7. Interface units needing flags of their own.

This is the most challenging point. I believe, however, that
it is not a distribution-mechanism problem — it is a module
interface design problem. A module interface that depends on
private include paths consumers cannot access is not truly
distributable, regardless of whether it ships as source, as a
BMI binary, or inside an archive. A well-designed module
interface should be self-contained — any internal #include
dependencies that consumers cannot access should not appear in
the interface at all. If the interface genuinely needs extra
headers or flags, the library author should ship them
alongside the interface — for example, as a metadata member
within the archive. But a distribution mechanism should not be
complicated to accommodate interfaces that are not designed
for distribution in the first place.

To summarize: this proposal targets a real and common use case —
linking a library with `-lfoo` and having the compiler resolve the
modules automatically. That scenario covers a large share of everyday
C++ library usage. It is not
intended to replace CMake, Meson, or Bazel. It is intended to make the
simple case simple — both for users, who get module resolution without
extra tooling, and for build system authors, who no longer need to
reinvent module scanning for the common case.

Thank you again for the thoughtful review. If this direction is worth
pursuing further, I would be happy to improve the prototype or
contribute to a paper — though I should note that I would likely need
a co-author familiar with the WG21 process.

Best regards,
moonlight


于 2026年6月12日 UTC 13:50:42,Ben Boeckel <ben.boeckel@kitware.com> 写道:
On Fri, Jun 12, 2026 at 11:38:56 +0000, moonlight via SG15 wrote:
I would like to share a small prototype that may be relevant to the
ongoing discussion on C++20 module distribution.

Currently, `-lfoo` has no standardized mechanism for module interface
discovery. This prototype demonstrates an alternative approach: the
compiler can automatically extract `.cppm` source files from `.a`
archives, compile the BMI locally, and proceed without any changes to
the existing build pipeline.

Repository:
<https://github.com/moonandlake123/autolink>

The key points:

- `.cppm` source files are packaged into `.a` archives alongside
object files at library build time.
- At consumption time, the compiler wrapper scans the archive,
extracts and compiles the module interface to BMI, then injects the
BMI path into the compiler command.
- No new file format, no BMI standardization, and no ABI changes are
required.
- Archives without `.cppm` files are passed through without
modification.

The prototype is verified on Clang 21 (Termux). GCC and MSVC
adaptations follow the same logic.

I welcome any feedback and would be grateful if this proves useful to
the work in SG15.

Have you seen this paper?

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3286r0.pdf

Issues I see at a glance with the approach/implementation:

- assumes all files in a target are compiled with the same flags
- incremental compilation seems hampered due to direct
source-to-artifact requiring a full recompile on any source change
- compiles all sources in a single shot instead of separate compilation
(required for module-providing sources anyways without the frontend
becoming a build system itself)
- implementation assumes `.cppm`; there are no standard extensions for
module interface units
- unconditionally compiles discovered module interface units without
considering whether they're actually necessary or not
- assumes consumed targets have libraries (static or shared); there is a
desire to have binary-less "header-only"-like module libraries where
module initializer symbols are declared "unnecessary" so that
libraries can be shipped as just-module-interfaces
- doesn't consider that module interface units might need flags of their
own (e.g., `-I` flags for includes in the interface that consumers do
not need)

I think this might be useful for the "class assignment" level of usage
and genuinely be useful there (except for the flags needed by the
dependency), but is not usable as-is for tools like CMake, Meson, Bazel,
etc. So as long as the goal is to support that level of usage, this
could be a viable direction (modulo, of course, the BMIs being
compilable with the consumer's flags).

Thanks,

--Ben