Skip to content

Commit 8783350

Browse files
authored
feat: [resources] 与版本身份 —— 两处「模型比生态少一层」(#365, #363) (2026.8.7.1) (#369)
两个 issue 领域无关,失效形状同一条:生态已经产出的东西,mcpp 的模型表达不了,于是走到一条「不报错但结果是错的」路径上。 #365 Windows 资源:`[resources]` 一行写 icon 与版本信息,mcpp 生成并**跟踪**资源脚本;issue 结尾「llvm-rc 的 bug」那条归因已实测证伪(`VS_VERSION_INFO` 未定义会被当资源名字符串,类型仍是 RT_VERSION,所以每个打印类型的工具都说没问题,而 GetFileVersionInfo 查的是序号)。补齐 `role = "object"`:角色表原本缺「链接输入」这一格。 #363 版本身份:`resolve_semver` 现在返回索引的**字面键**而不是从数字重造的地址;预发布按 SemVer 排序且范围看不见它;数值段不再截断在第四段;别名条目不再是范围候选;真平局硬错。mcpp.lock 记录解析结果并覆盖传递依赖,且不再随命令抖动。 合入前做了一轮深度 review,四条实测缺陷 + 三处口径不一致在本 PR 内一并修掉,论证见设计文档 §F。
1 parent c9b561a commit 8783350

34 files changed

Lines changed: 3868 additions & 172 deletions

.agents/docs/2026-08-07-windows-resources-and-version-identity-design.md

Lines changed: 572 additions & 0 deletions
Large diffs are not rendered by default.

.github/workflows/cross-build-test.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,16 @@ jobs:
296296
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
297297
bash tests/e2e/102_mingw_cross_wine.sh
298298
299+
# mcpp#365. This is the only job with a MinGW cross toolchain, so it is
300+
# the only place the GNU half of resource compilation (windres -O coff,
301+
# because GNU ld cannot consume a .res) can run at all — the Linux e2e
302+
# shards skip it for want of the `mingw-cross` capability. Named
303+
# explicitly for the same reason 102 is.
304+
- name: "e2e: windows resources (windres / COFF)"
305+
run: |
306+
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
307+
bash tests/e2e/198_windows_resources_cross.sh
308+
299309
# ── windows → linux ───────────────────────────────────────────────────────
300310
# The mirror of mingw-cross-wine. Two jobs because a Windows runner cannot
301311
# execute the ELF it produces; the artefact is handed to a Linux job and

CHANGELOG.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,66 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [2026.8.7.1] — 2026-08-07
7+
8+
两处「模型比生态少一层」。设计与实测证据见 `.agents/docs/2026-08-07-windows-resources-and-version-identity-design.md`
9+
10+
### 新增
11+
12+
- **`[resources]`:exe 图标与版本信息现在是 `mcpp.toml` 里的一行(#365)。**
13+
14+
```toml
15+
[resources]
16+
icon = "assets/app.ico"
17+
```
18+
19+
`FILEVERSION` / `ProductName` / `FileDescription` / `CompanyName` / `LegalCopyright` 全部从 `[package]` 取默认值,资源脚本由 mcpp 生成。自写 `.rc``files = [...]`,mcpp 编译并**跟踪**它。
20+
21+
**只有 PE 目标消费这一节**;在 Linux/macOS 上它「不适用」——不是降级、不是带警告地跳过:没有消费者,构建逐字节不变,也不说话。所以**不需要(也不能)加 `cfg(windows)` 谓词**。节名不叫 `[windows]` 是因为「图标」作为概念不是 Windows 专有的,将来 macOS `.icns` 扩同一节而不是把这条轴按 OS 切三份。
22+
23+
**声明了却不存在的文件是硬错误**,这是对 issue 第 3 条请求的**有意偏离**:mcpp 里每个「声明过的输入」都是这个规则(`main = "…"` 必须匹配恰好一个文件、nasm 缺失是硬错误),而「缺失就跳过」会把这个 feature 要消灭的失效模式写成规定行为——一个没有图标、没有版本信息、且什么都没说的正式二进制。不要图标已经可表达:把那一行删掉。
24+
25+
**这条校验在每个目标上都跑**,「不适用」只停在*编译*那一步。路径存不存在是关于工作树的事实、不是关于目标的事实;按 PE 设门会让 Linux/macOS 的构建与 CI 完全看不见 `icon` 里的拼写错误,只有 Windows job 变红——正是这条硬错误要消灭的「太晚才知道」。
26+
27+
- **`role = "object"`:build.mcpp 的 action 现在能把产物接到链接输入上。** 角色表原本三格接在「编译输入 / 无 / 链接输出」上,缺的正是「链接输入」——一个构建图显然有的接线点。后果不是理论上的:预编译对象只能塞进 `[build].ldflags`,而那是链接命令里的一串字符、不是图里的文件,于是改了图标得到 `ninja: no work to do`
28+
29+
可选 `.target("name")` 指定接哪条边;**省略是推荐写法**,它接到本次构建产出的每个镜像——可执行、动态库**与测试二进制**。测试二进制在默认集合里不是顺手加的:它链接的是同一份库代码,排除掉会让 `mcpp build` 通过而 `mcpp test` 在这个 action 本来要提供的那个符号上报 `undefined symbol`;而改成显式点名也不成立——测试链接单元是从 `tests/*.cpp` **发现**出来的,名字不在 `mcpp.toml` 里,写了它的 build.mcpp 在普通 `mcpp build` 下会直接构建失败。**每一个**匹配不到链接单元的名字都是错误,包括写在一个匹配得上的名字旁边的那个(拼错的真实形状)。本次构建里没有任何镜像可接时报 degradation——这条边只能经由链接被达成,没有链接就意味着命令一次都不跑。
30+
31+
### 修复
32+
33+
- **解析出的版本现在是索引里的字面键,不是重新渲染的数字(#363)。** `resolve_semver` 一直把索引的字面版本键读到手里,然后 `return parsed[i].str()` —— 从解析出的数字重造一个地址。渲染器复现不了的东西就变成了不存在的地址:
34+
35+
| 上游键 | 旧行为 |
36+
|---|---|
37+
| `1.92.8-docking` | 截断成 `1.92.8`,与非 docking 那个**塌成同一个可比较版本**(两个不同 tarball) |
38+
| `25.0.4.7.1`(jdk-corretto,五段) | 截断成 `25.0.4.7` —— **索引里没有这个键** |
39+
| `pre-v0.0.5`(khistory,唯一的发布) | 静默跳过,然后报「no valid versions in index」——把责任推给一个发布得好好的包 |
40+
41+
现在字面键与序一起传递,`version_req` 只负责**排序**。连带修的:
42+
43+
- **预发布按 SemVer 排序**,且范围按 npm/Cargo 规则**看不见预发布**,除非约束自己在同一数值元组上带了预发布。`^1.92.8` 因此确定性地选 `1.92.8`,不再在两个 tarball 之间由一个看不见差别的序做取舍。同一条规则顺带修掉 `^1.2.3` 会漏进 `2.0.0-alpha`
44+
- **数值段不再截断在第四段**。真实索引里 `jdk-corretto` 发五段键;截断让 `25.0.4.7.1``25.0.4.7.2` 比较相等。
45+
- **别名条目(`{ ref = "…" }`)不再是范围候选**`jdk-temurin``["25.0.4"] = { ref = "25.0.4+7" }` 曾与它自己的目标构成一次「平局」。精确寻址不变。
46+
- **不可排序的键**(`b10069``latest``pre-v0.0.5`)成为一等公民的一类:只参与精确匹配,范围约束下报**指名的**错误并给出可粘贴的 pin 行。
47+
- **真平局硬错**。只差 build metadata 的两个键(`1.0.0+a` / `1.0.0+b`)是两个 tarball、两个 sha256,序说不出该要哪个;旧行为按描述符里的行序取第一个,意味着索引的一次排版调整会改变构建出来的东西。
48+
49+
- **mcpp.lock 记录解析结果,并覆盖传递依赖。** 它记的一直是**约束本身**(`version = "^1.92.8"`),而一个记录范围的 lock 不锁定任何东西;`Compiling compat.imgui v^1.92.8` 这行也一样。两者读的都是 `m->dependencies`(未解析的输入、且只有直接依赖),而解析结果 `ResolvedRecord` **早就覆盖整张图**——修法是把两个消费者都指过去,而不是补第三处回写。
50+
51+
lock 头部现在自己声明**它还不 pin 后续构建**(index 依赖仍每次从约束重新解析)。一个记着真实版本却不生效的文件,比一个明显记着范围的文件更容易被误当权威。
52+
53+
**dev-dependencies 不进 lock。** 解析结果覆盖整张图,而 `mcpp test` 解析 dev-deps、`mcpp build` 不解析——照单全收会让一个进 VCS 的文件取决于「上一条命令是什么」,build/test/build 写出三个不同的文件。判据:**lock 是 manifest 的函数,不是命令的函数**。头部注释也写了这一条。
54+
55+
- **`[resources]` 在 MSVC 下找不到 `rc.exe`** 工具链 PATH 覆盖按 `find_first_of(";:")` 切分,而 Windows 路径的**盘符冒号**就在下标 1——`C:\Windows Kits\…` 被切成 `C` 加一段「当前盘相对路径」,同盘时侥幸命中、跨盘必然找不到。而这条 PATH 遍历正是 msvc 下的**主路径**(`rc.exe` 属于 Windows SDK,从不在 `cl.exe` 旁边)。两个调用点各自推导同一条规则、且已经彼此不一致,现在收敛成一个 `split_env_list`
56+
57+
- **`.rc` 现在能穿过工程级 fast path。** `sources_newer_than` 只扫 `src/**/*` 的 C++ 扩展名,一次只改资源脚本的构建因此报 `Finished dev in 0.15s` —— 而 `.rc` 的 implicit input 集合来自扫描它、扫描发生在 prepare,所以往脚本里新加一行 `#include "ids.h"` 那个头文件永远不会被跟踪。与 `build.mcpp`、glob 输入(#359)是同一类:**mtime 扫描看不见,但改了它图就该长得不一样**。只扫 `files`——`icon``extra-inputs` 已经是 ninja 的 implicit input。
58+
59+
### 其他
60+
61+
- 版本号 2026.8.6.3 → **2026.8.7.1**
62+
- **行为变化(生态可见)**:`cc-connect` 这类「稳定版 + 预发布版」并存的包,`^1.3``1.3.3-beta.1` 改为解析到 `1.3.2`;`jdk-corretto`/`jdk-temurin` 这类带别名的包,范围解析改为选中真条目(`25.0.4.7.1` 而非别名 `25.0.4`),store 目录名随之变化。
63+
- **诊断口径**:`resources/versioninfo`(版本资源 Windows 读不到)与 `resources/no-image`(声明了却没有任何镜像可嵌)由 warning 改为 **degradation** —— 它们的 impact 正是本批要消灭的静默失效,`--strict` 必须看得见;`role = "object"` 无消费者新增 `action/no-target`,同口径。
64+
- README 的包索引链接由仓库地址改为 <https://mcpplibs.github.io/mcpp-index/>
65+
666
## [2026.8.5.4] — 2026-08-06
767

868
命令长度这一族缺陷的**第七次**,这次不再补洞。架构分析见 `.agents/docs/2026-08-06-command-length-architecture.md`

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
| [Documentation](docs/) · [Getting Started](docs/00-getting-started.md) · [mcpp.toml Guide](docs/05-mcpp-toml.md) · [Examples](docs/01-examples.md) · [Toolchains](docs/03-toolchains.md) |
1313
|:---:|
14-
| [Package index mcpp-index](https://github.com/mcpp-community/mcpp-index) · [Module libraries mcpplibs](https://github.com/mcpplibs) · [Community Forum](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) |
14+
| [Package index mcpp-index](https://mcpplibs.github.io/mcpp-index/) · [Module libraries mcpplibs](https://github.com/mcpplibs) · [Community Forum](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) |
1515
| [![ci-linux](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml) [![ci-macos](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml) [![ci-windows](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml) |
1616

1717
<p align="center">
@@ -360,7 +360,7 @@ Real projects built with mcpp — `import`-able C++23 modules and the toolchain
360360
| [imgui-m](https://github.com/mcpplibs/imgui-m) | Dear ImGui as a C++23 module package |
361361
| [cmdline](https://github.com/mcpplibs/cmdline) | Command-line parsing library / framework (mcpp uses it) |
362362

363-
More modular libraries → [mcpplibs](https://github.com/mcpplibs) · package index → [mcpp-index](https://github.com/mcpp-community/mcpp-index)
363+
More modular libraries → [mcpplibs](https://github.com/mcpplibs) · package index → [mcpp-index](https://mcpplibs.github.io/mcpp-index/)
364364

365365
## Contributing
366366

@@ -386,7 +386,7 @@ then follow the guide to help me submit a contribution to mcpp.
386386
## Community & Ecosystem
387387

388388
- [Community Forum](https://forum.d2learn.org/category/20) — chat group (QQ: 1067245099)
389-
- [mcpp-index](https://github.com/mcpp-community/mcpp-index) — default package index
389+
- [mcpp-index](https://mcpplibs.github.io/mcpp-index/) — default package index
390390
- [mcpplibs](https://github.com/mcpplibs) — collection of modular C++ libraries
391391

392392
### Acknowledgements

README.zh-CN.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
| [文档](docs/zh/) · [快速开始](docs/zh/00-getting-started.md) · [mcpp.toml 指南](docs/zh/05-mcpp-toml.md) · [示例项目](docs/zh/01-examples.md) · [工具链管理](docs/zh/03-toolchains.md) |
1313
|:---:|
14-
| [包索引 mcpp-index](https://github.com/mcpp-community/mcpp-index) · [模块化库 mcpplibs](https://github.com/mcpplibs) · [社区论坛](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) |
14+
| [包索引 mcpp-index](https://mcpplibs.github.io/mcpp-index/) · [模块化库 mcpplibs](https://github.com/mcpplibs) · [社区论坛](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) |
1515
| [![ci-linux](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml) [![ci-macos](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml) [![ci-windows](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml) |
1616

1717
<p align="center">
@@ -352,7 +352,7 @@ mcpp 的身份模型是两条正交轴:**工具链** = `family@version`(family
352352
| [imgui-m](https://github.com/mcpplibs/imgui-m) | Dear ImGui 的 C++23 模块封装包 |
353353
| [cmdline](https://github.com/mcpplibs/cmdline) | 命令行解析库/框架(mcpp 自身在用) |
354354

355-
更多模块化库 → [mcpplibs](https://github.com/mcpplibs) · 包索引 → [mcpp-index](https://github.com/mcpp-community/mcpp-index)
355+
更多模块化库 → [mcpplibs](https://github.com/mcpplibs) · 包索引 → [mcpp-index](https://mcpplibs.github.io/mcpp-index/)
356356

357357
## 参与贡献
358358

@@ -378,7 +378,7 @@ mcpp 的身份模型是两条正交轴:**工具链** = `family@version`(family
378378
## 社区 & 生态
379379

380380
- [社区论坛](https://forum.d2learn.org/category/20) — 交流群 (Q: 1067245099)
381-
- [mcpp-index](https://github.com/mcpp-community/mcpp-index) — 默认包索引
381+
- [mcpp-index](https://mcpplibs.github.io/mcpp-index/) — 默认包索引
382382
- [mcpplibs](https://github.com/mcpplibs) — 模块化 C++ 库集合
383383

384384
### 致谢

docs/05-mcpp-toml.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,105 @@ the other conditional dependency tables (§2.7.1). The **feature itself is
11511151
registered on every platform** — only what it pulls in is conditional — so
11521152
requesting it where no predicate matches is not an unknown-feature error.
11531153

1154+
### 2.15 `[resources]` — Metadata and Assets Embedded in the Artifact (2026.8.7.1+)
1155+
1156+
An exe icon and the version metadata Windows shows in a file's Properties dialog
1157+
are a path in `mcpp.toml`, nothing more:
1158+
1159+
```toml
1160+
[resources]
1161+
icon = "assets/app.ico"
1162+
```
1163+
1164+
That is the whole common case. `FILEVERSION`, `ProductName`, `FileDescription`,
1165+
`CompanyName` and `LegalCopyright` all default from `[package]`, and mcpp
1166+
generates the resource script for you.
1167+
1168+
| Key | Type | Meaning |
1169+
|---|---|---|
1170+
| `icon` | path | Embedded as the application icon (resource ordinal 1) |
1171+
| `files` | list of paths | Your own `.rc` scripts, compiled and **tracked** as build inputs |
1172+
| `extra-inputs` | list of paths | Inputs the `.rc` scanner could not see (see below) |
1173+
| `version-info` | bool | `false` opts out of the generated version resource |
1174+
| `[resources.version-info]` | table | `company`, `product`, `description`, `copyright`, `original-filename`, `internal-name` |
1175+
1176+
**Only PE targets *compile* this.** On Linux and macOS the section is
1177+
*inapplicable*: no resource units, no diagnostics, byte-identical build. You do
1178+
**not** need (and cannot use) a `cfg(windows)` predicate — write it once,
1179+
unconditionally.
1180+
1181+
**A declared file that does not exist fails the build — on every target.** A
1182+
resource is a build input like a source file; mcpp will not quietly ship a
1183+
binary without it. Validation is deliberately *not* PE-gated: whether a path
1184+
exists is a fact about your working tree, not about the target, so a typo in
1185+
`icon = "assets/app.ico"` is caught by your Linux or macOS build (and by their
1186+
CI jobs) instead of waiting for the Windows one. If you do not want an icon,
1187+
delete the line.
1188+
1189+
**Version fields.** `FILEVERSION` takes the four numeric segments of
1190+
`[package].version`, each of which must fit in 16 bits; the string fields keep
1191+
the version verbatim, so a form the numeric fields cannot hold (`1.0.0-rc1`)
1192+
still shows up in the Properties dialog.
1193+
1194+
#### Writing your own `.rc`
1195+
1196+
```toml
1197+
[resources]
1198+
files = ["res/app.rc"]
1199+
```
1200+
1201+
With `files` set, mcpp stops generating a version resource — you own the
1202+
resource ID space. Set `version-info = true` alongside it if you want both (and
1203+
mind the collision: there can be only one `RT_VERSION` at ordinal 1).
1204+
1205+
To start from the generated script instead of a blank file, copy it out of the
1206+
build directory (`target/<triple>/<fp>/res/<target>.mcpp.rc`) and list it in
1207+
`files`. The result is byte-identical, so moving from generated to hand-written
1208+
never changes what ships.
1209+
1210+
> **`VS_VERSION_INFO` needs `<windows.h>`.** In a hand-written script,
1211+
> `VS_VERSION_INFO VERSIONINFO` without `#include <windows.h>` files the version
1212+
> resource under a *string* name instead of ordinal 1. Every tool still reports
1213+
> `Type: VERSIONINFO`, but `GetFileVersionInfo` looks up the ordinal, so
1214+
> PowerShell's `FileVersionInfo` shows every field as empty. Either include
1215+
> `<windows.h>` or write `1 VERSIONINFO`. mcpp warns when it sees this shape;
1216+
> the script it generates uses the literal `1`.
1217+
1218+
#### Tracked inputs
1219+
1220+
mcpp reads the `.rc` for quoted `#include`s and for the files named by resource
1221+
statements (`ICON`, `RCDATA`, `MANIFEST`, …), and makes them build inputs, so
1222+
editing your icon relinks. Angled includes (`<windows.h>`) are the toolchain's
1223+
and are covered by the toolchain fingerprint instead.
1224+
1225+
A file name reached through a macro (`1 ICON APP_ICON`) is invisible to that
1226+
scan. mcpp names what it could not resolve and asks you to declare it:
1227+
1228+
```toml
1229+
extra-inputs = ["assets/app.ico"]
1230+
```
1231+
1232+
#### Anything else: `role = "object"`
1233+
1234+
For inputs that are not resource scripts — a blob embedded with `objcopy`, a
1235+
generated `.def`, a pre-built object — a build program can declare a build-graph
1236+
node whose outputs join the link:
1237+
1238+
```cpp
1239+
mcpp::action o;
1240+
o.id = "blob"; o.role = "object";
1241+
o.arg("./mkblob.sh").arg("blob.bin").arg("${mcpp.out_dir}/blob.o")
1242+
.input("blob.bin")
1243+
.output("${mcpp.out_dir}/blob.o")
1244+
.target("myapp") // omit: every image, test binaries included
1245+
.submit();
1246+
```
1247+
1248+
See [07 — build.mcpp](07-build-mcpp.md). Naming such a file in
1249+
`[build].ldflags` also "works", but ldflags is a flat string in the link
1250+
command: nothing tracks it, and editing the file gives you `ninja: no work to
1251+
do`.
1252+
11541253
## Appendix A. Schema Ownership Principle (admission criteria for new fields)
11551254

11561255
> **Closed syntax, open vocabulary**: whoever owns the parsing semantics defines the keys; whoever owns the domain knowledge defines the values.

0 commit comments

Comments
 (0)