Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,615 changes: 1,615 additions & 0 deletions .agents/docs/2026-08-17-distribution-architecture-analysis-and-design.md

Large diffs are not rendered by default.

712 changes: 712 additions & 0 deletions .agents/docs/2026-08-17-library-distribution-design.md

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions .github/workflows/cross-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ name: cross-build-test
# qemu-aarch64 is the CI proxy for "does this cross artefact actually execute".
#
# ── NOT here ───────────────────────────────────────────────────────────────
# * The e2e scripts this job names explicitly (102, 198, 240, 248) are the
# ones the ordinary Linux shards SKIP for want of `mingw-cross`. They are
# listed in the job rather than left to run_all's cap gating precisely so
# they cannot end up skipping everywhere at once.
# * Same-arch builds (host arch == target arch) are NOT cross. The native musl
# static build `--target x86_64-linux-musl` (x86_64 host) is exercised by
# ci-linux.yml's "Toolchain: musl-gcc" step, and release.yml for the static
Expand Down Expand Up @@ -320,6 +324,23 @@ jobs:
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
bash tests/e2e/240_pack_pe_zip_cross.sh

# A LIBRARY package whose legs cross an OS boundary, for the same reason
# as the three above: this is the only job with a MinGW cross toolchain.
#
# It is not redundant with 245 (which covers the fat-package mechanism
# with gnu + musl and therefore runs on every ordinary Linux shard). The
# leg added here changes BINARY FORMAT, and it is the case that proves
# `lib/` has to be keyed by triple rather than by OS: MinGW and MSVC are
# both "windows" and write `libfoo.a` and `foo.lib` respectively.
#
# Without this step the test would carry `# requires: mingw-cross` and
# skip in every job that exists — verified on a developer's machine and
# nowhere else, while the suite reported green.
- name: "e2e: pack a library across an OS boundary (PE leg)"
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
bash tests/e2e/248_pack_library_fat_pe_leg.sh

# ── windows → linux ───────────────────────────────────────────────────────
# The mirror of mingw-cross-wine. Two jobs because a Windows runner cannot
# execute the ELF it produces; the artefact is handed to a Linux job and
Expand Down
84 changes: 84 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,92 @@

## [Unreleased]

### 新增

- **`mcpp pack <target>` 可以把一个库打成「接口 + 预编译二进制」的包(#433)。**

闭源库、离线环境、以及「构建农场已经编过一遍了」这三种场景,过去都只能自己
写脚本收集产物。现在:

```bash
mcpp pack mathkit # 静态库包
mcpp pack mathkit --target x86_64-linux-gnu \
--target aarch64-linux-gnu # 一个包,两条腿
```

**产出的是一个普通的 mcpp 包** —— 一份正常的 `mcpp.toml`,走 mcpp 早就有的
「载荷自带 manifest」通路。**新增 manifest 段 0 个、键 0 个**:打什么由
`[targets.<n>].kind` 决定(所以没有 `--lib`、没有 `--artifact`),发布哪些接口
由 `[lib]` 约定 + 模块图决定,公开头是 `[build].include_dirs` 全量,
每条腿的 ABI tag 与 digest 记在既有的 `[[runtime.artifacts]]` 上。
一个**老版本 mcpp 照样能构建**这种包 —— 它只是不执行下面那两道闸门。

一个包可以同时带**两种接口**:`include/`(文本,`#include`,不编译)与
`interface/`(模块,消费者编译它)。实测同一个包被「只 #include」/「只 import」/
「两者都用」三种方式消费,静态与动态两种形态,六格全过。

发布哪些 `.cppm` 是**算出来的** —— lib root 的模块闭包,不是「所有 `.m.o`」。
实现分区(`module M:secret;`)照样产 `.m.o`,按扩展名挑会**泄露闭源源码**;
同一个闭包反过来决定归档里要删哪些对象,按 `.m.o` 删则会删掉真代码、
三个平台全部链接失败。两条清单都会打印出来。

详见 `docs/12-binary-distribution.md`、`examples/05-lib-dist`、`examples/06-lib-consume`。

- **消费预编译包时的两道闸门。** 都是不检查就会静默出错的:

**接口与二进制是否仍然配对。** 这条闸门存在是因为另一种结果被实测过:把随包
接口里一个结构体的两个 `int` 成员互换 —— Itanium ABI 不 mangle 字段顺序 ——
消费者**编译过、链接过、运行过、打印出交换后的错数据**,任何工具都没有一句诊断。

**二进制是否为这套工具链所编。** 失配时诊断会**列出包里确实有哪些 tag** ——
一句「找不到」会让人去找一个就在自己硬盘上的包。

另外,在解开的分发包目录里直接 `mcpp build` 会被拒绝:那儿的 `interface/`
是声明,定义在旁边的归档里,构建会产出一个几乎空的库然后报告成功。

### 修复

- **实现分区(`module M:part;`)在 Windows 上构建不了,而根因在扫描器里。**

`module M:part;` 与 `module M;` 共用一个拼写,却是两种不同的声明,而扫描器把
它们当成了一种:前者被记成**「requires `M:part`、provides 空」** ——
一个文件 requires 自己的名字。于是图里**没有**从「import 分区的单元」到
「定义分区的单元」的边,构建顺序无约束:GCC 与 macOS clang 靠各自的依赖扫描
兜住了,**Windows clang 以 `failed to read compiled module` 失败**。

同一处还有第二半:`import :part;` 的解析读的是 `u.provides`,而实现单元
(`module M;`)没有 provides ⇒ 它里面的 `import :secret;` 停留在字面的
`:secret`,没有任何单元提供。两个平台都会刷的那条
`module 'M:part' imported but not provided in this build` 就是这两件事的
合并症状 —— **它读起来像一条提示,其实是病因**。

实现分区在此之前**mcpp 里任何地方都没有测试覆盖**,是库分发的 e2e 第一次
用到它才暴露出来。现在扫描器记 `provides = M:part` 并标 `providesInterface
= false`;`import :part;` 按 TU 自己所属的模块名解析。

- **`[target.'<三元组>'.build]` 在没有 `--target` 时从不命中。**

同一个语句的两种拼写互相矛盾:`cfg(linux)` 在原生构建上命中,
`[target.'x86_64-linux-gnu'.build]` 不命中。根因是 `matches()` 拿着原始的
`--target` 字符串(原生构建下是空的)短路返回 false,而同一文件的
`context_for()` 对 `cfg(...)` **回落到宿主三元组** —— 一个决定两处推导。
`manifest/types.cppm` 的注释从写下起承诺的就是回落那一种。

**形状是最坏的那种**:CI 传 `--target` 是绿的,开发者本机的 `mcpp build`
静默丢掉那一段,失败在链接期出现、点的是符号而不是谓词。

修法是**删掉第二个答题者**:解析后的三元组进 `cfgpred::Ctx`,`matches()`
只有一个来源。

- **`sources = []` 与不写 `sources` 逐字节等价。**

解析器在向量为空时一律填默认 glob,于是作者**没有任何写法**能表达
「什么都不要编」。二进制分发需要这个:一个纯头文件的包不编译任何东西,
而 `src/` 下任何遗留文件都会被扫进消费者的构建,并可能与预编译库里的符号
重复定义。改成记录**键是否出现**(`BuildConfig::sourcesDeclared`),
与 `XlingsConfig::subosDeclared` 同一个模式。


- **卸载后的清扫会波及**别的版本**,而那可能正在被另一个进程解压。**

`sweep_parked_payloads` 原来把整个 family 目录扫一遍,把**任何**没有文件的
Expand Down
10 changes: 9 additions & 1 deletion docs/02-pack-and-release.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
# 02 — Packaging for Release
# 02 — Packaging an Application for Release

> This page is about bundling a **program**. To ship a *library* as interface +
> prebuilt binaries, see [12 - Distributing a Prebuilt Library](12-binary-distribution.md).
>
> Which one `mcpp pack` does is decided by the target's `kind`, not by a flag:
> `mcpp pack <name>` packs `[targets.<name>]`, and a `bin` becomes a bundle
> while a `lib`/`shared` becomes a library package. With no name, mcpp picks
> the only packable target.

> A default dynamically linked binary produced by `mcpp build` has a loader and
> RUNPATH tied to the build sandbox. It is a development artifact, not a
Expand Down
17 changes: 17 additions & 0 deletions docs/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ the package/feature boundary, not on an individual target.

### 2.3 `[build]` — Build Configuration

> **`sources = []` is not the same as omitting `sources`.** An absent key
> selects the default glob; an explicitly empty list means *compile nothing*,
> which is what a header-only distribution package needs to say. Until
> mcpp 2026.8.17.2 the two were byte-identical, so there was no spelling for
> "nothing" and any file left under `src/` was swept in.

```toml
[build]
sources = ["src/**/*.cppm", "src/**/*.cpp"] # Source globs (default: src/**/*.{cppm,cpp,cc,c,S,s,asm})
Expand Down Expand Up @@ -1598,6 +1604,17 @@ do`.
- Package-level knobs all converge into features; for sugar keys (such as `backend=`)
to enter the core syntax, they must satisfy: ① domain-neutral (a cross-ecosystem
general pattern) ② 1:1 desugaring with zero new parsing semantics.
- **A key that duplicates an answer another section already gives is not admitted.**
Two places to state one fact is two places that can disagree, and the failure
is silent — whichever reader loses the race is simply wrong. Library packaging
([12](12-binary-distribution.md)) is the worked example: it added **zero**
manifest keys, because what to pack is `[targets.<n>].kind`, which interface
to publish is `[lib]` plus the module graph, which headers are public is
`[build].include_dirs`, and the per-artifact evidence is `[[runtime.artifacts]]`.
- A field that describes what a *generated* package IS (rather than what a build
should DO) belongs on `[[runtime.artifacts]]` — see §2.11. `provenance`
beginning with `mcpp-pack` is what marks a directory as one, and mcpp refuses
to `build` inside it.
- See `.agents/docs/2026-06-04-manifest-schema-ownership.md` for the full field-ownership
table and the finalized decisions.

Expand Down
Loading
Loading