Skip to content

Commit f082b27

Browse files
committed
feat(libaio): add compat.libaio 0.3.113, and move the descriptor catalog out of the README
libaio is the userspace wrapper over Linux's native AIO syscalls. Shape A (C-source compat): upstream's own `libaio_srcs` list from src/Makefile, twelve TUs, no configure step. Linux-only in the strong sense — `struct iocb` IS the kernel's, so there is no other platform section to write and consumers gate it with `[target.'cfg(linux)'.dependencies]`. Three things the descriptor had to answer: * Upstream installs exactly one header, `libaio.h`, but the tarball keeps it in `src/` next to the private ones — one of which is `syscall.h` and would shadow glibc's for every consumer TU. So `include_dirs` names a `generated_files` forwarder and nothing else; the package's own sources reach the real header through it while their quote-form includes still resolve next to the including `.c`, so no `-I` into `src/` is needed. * `-std=c11` sets __STRICT_ANSI__, which hides `syscall()` and `sigset_t` and makes even the public header fail to parse. `c_standard = "gnu11"` looks like the fix and is a trap: mcpp 2026.8.27.2 accepts the string and still emits `-std=c11`. `-D_GNU_SOURCE` in `cflags` is what works. * `io_getevents`/`io_cancel` have no ordinary definitions upstream — the short names come from `.symver … @@LIBAIO_0.4`. Verified to resolve for an executable under both ld.bfd and lld; building a `.so` straight out of these objects still needs upstream's libaio.map, exactly as with upstream's own libaio.a. No features: the only sources not compiled are a build-time assertion TU (restated as static_asserts in the test) and the harness, which carries a main() and so cannot be a feature at all. CN mirror published at gitcode mcpp-res/libaio, byte-identical to GLOBAL. Verified with the CI-pinned mcpp: `mcpp test -p libaio` green from cold, 12 objects actually compiled, and the assertions confirmed failable. Also, in the same pass: the "Reference examples" table had grown to ~22 KB of the 28 KB README and buried everything around it. The full catalog now lives in docs/descriptor-examples.md (+ zh), with libaio's own entry; the README keeps a six-row table of one starting point per shape and links out. README 28693 -> 6151 bytes. Fixes a stale pkgs/o/opencv.lua link along the way.
1 parent 7c3afc4 commit f082b27

13 files changed

Lines changed: 700 additions & 59 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# 收录 libaio 0.3.113(compat.libaio)
2+
3+
日期:2026-08-29 · PR 分支:`feat/add-libaio` · 状态:本地验证通过
4+
5+
## 1. 来源与形态判定
6+
7+
libaio 属于来源 (a):第三方上游库,上游不提供 mcpp 支持。
8+
9+
- 上游:<https://pagure.io/libaio>(Jeff Moyer 维护;GitHub 上只有零散的 fork,无官方镜像)。
10+
- 最新版本:`git ls-remote --tags` 排序后最大的 release tag 是 **`libaio-0.3.113`**
11+
注意 tag 命名有两代:老的是 `libaio.0-3-107.1` 这种点划混排形式,`sort -V` 会把它们排在
12+
`libaio-0.3.113` 之后 —— 只看 `tail` 会误判成 0.3.107。
13+
- License:LGPL-2.1-or-later(`COPYING` 是 LGPL 2.1;各源文件头写 "version 2 of the License,
14+
or (at your option) any later version")。
15+
- 源码布局:`libaio-0.3.113/` 包一层,库源码全在 `src/`(12 个 TU + 1 个公开头 + 若干私有头),
16+
另有 `harness/`(测试)与 `man/`。无 configure、无生成步骤、无 submodule、无符号链接。
17+
18+
**形态 = A(C 源码 compat)**,叠加一处「公开头从源码目录里择出来」的处理。
19+
20+
## 2. 版本与下载源
21+
22+
`sha256 = 2c44d1c5fd0d43752287c9ae1eb9c023f04ef848ea8d4aafa46e9aedb678200b`(49980 字节,连算两次一致)。
23+
24+
GLOBAL 用 `https://releases.pagure.org/libaio/libaio-0.3.113.tar.gz`,而**不是** pagure 的
25+
tag 归档 `https://pagure.io/libaio/archive/…`:后者当场返回 404,且即便可用,pagure 与 GitLab 同类,
26+
归档是即时生成的,sha 会漂移。releases.pagure.org 上的是固定发布文件。
27+
28+
## 3. CN 镜像
29+
30+
已在 gitcode 建 `mcpp-res/libaio`,seed 一个 README 后发 `0.3.113` release,上传与 GLOBAL **同一份**
31+
tarball:
32+
33+
https://gitcode.com/mcpp-res/libaio/releases/download/0.3.113/libaio-0.3.113.tar.gz
34+
35+
回拉校验 sha256 与 GLOBAL 逐字节一致,故描述符里 GLOBAL/CN 共用一个 `sha256`
36+
37+
## 4. 三个实现决策
38+
39+
### 4.1 只暴露一个公开头(`generated_files` 转发头)
40+
41+
上游 `make install` 只装 `libaio.h` 一个头,但 tarball 把它放在 `src/` 里,与私有头并列。若直接写
42+
`include_dirs = { "*/src" }`,消费者的 include 路径上就会多出 `syscall.h``aio_ring.h`
43+
`vsys_def.h``syscall-*.h`。其中 **`syscall.h` 会遮蔽 glibc 的同名头** —— 这是实打实的危害,不是洁癖。
44+
45+
做法沿用 compat.gmp 的先例:`generated_files` 写一个
46+
47+
libaio-0.3.113/mcpp/include/libaio.h → #include "../../src/libaio.h"
48+
49+
`include_dirs` 只指这一个目录。为什么这样够用:
50+
51+
- 包自身的 `.c``#include <libaio.h>`,命中转发头 → 命中真头文件;
52+
- 它们的 `#include "syscall.h"` / `"aio_ring.h"` 是引号形式,按「包含者所在目录优先」解析,
53+
就在 `src/` 里,**不需要任何指向 `src/``-I`**;
54+
- `compat-0_1.c` 用引号形式 `#include "libaio.h"`,同理命中 `src/` 里的真头。
55+
56+
已实测:12 个 TU 全部在只有 `-I .../mcpp/include` 的情况下零警告编过,且消费者侧
57+
`#include <syscall.h>` 拿到的确实是 glibc 的(`SYS_read == 0`)。
58+
59+
### 4.2 `c_standard = "gnu11"` 是个陷阱 —— 用 `-D_GNU_SOURCE`
60+
61+
`-std=c11` 会定义 `__STRICT_ANSI__`,glibc 随之关掉 `_DEFAULT_SOURCE`,于是:
62+
63+
- `<unistd.h>` 不再声明 `syscall()`(每个 TU 都经 `syscall.h``_body_io_syscall` 用到它);
64+
- `sigset_t` 不可见,连**公开头**都在 `io_pgetevents(…, sigset_t *sigmask)` 处解析失败。
65+
66+
上游没这个问题,因为它的 Makefile 用编译器默认的 gnu 模式。
67+
68+
第一版描述符写了 `c_standard = "gnu11"`,`mcpp xpkg parse` 通过,`mcpp test` 却报出与 c11 完全相同的
69+
两个错误。查产出的 `compile_commands.json`:**mcpp 2026.8.27.2 收下了 `gnu11` 这个字符串,仍然发
70+
`-std=c11`**,静默降级、无任何提示。
71+
72+
真正生效的写法是 `cflags = { "-D_GNU_SOURCE" }`。已实测 gcc 16.1.0 与 clang 22.1.8 下
73+
`-std=c11 -D_GNU_SOURCE -Wall` 12 个 TU 全部零警告。
74+
75+
> 附带影响:`pkgs/c/compat.freetype.lua` 也声明了 `c_standard = "gnu11"`,同样拿不到 gnu 模式。
76+
> 它目前 CI 是绿的(说明 freetype 不依赖 gnu 模式),因此本 PR 不动它,但这里记一笔。
77+
78+
顺带说明另外两处 GNU 扩展为何不用管:`syscall.h` 的具名可变参数宏
79+
`_body_io_syscall(sname, args...)`,以及 `raw_syscall.c` 在非 ia64 架构上整个文件只剩一个文件作用域
80+
`;` —— gcc/clang 只在 `-pedantic` 下才诊断这两者。
81+
82+
### 4.3 符号版本(symver)
83+
84+
`io_cancel.c` / `io_getevents.c` / `io_queue_wait.c` 的函数真名是 `io_getevents_0_4` 之类,短名靠
85+
`.symver … @@LIBAIO_0.4` 发布;`compat-0_1.c` 另加三个 `@LIBAIO_0.1` 的老 ABI 别名。
86+
87+
- **链可执行文件**:`@@`(默认版本)会同时定义基名,已在 ld.bfd 与 lld 上各实测通过,
88+
最终测试二进制里 `nm` 可见 `io_getevents_0_4``io_getevents@@LIBAIO_0.4` 同址。
89+
`kind = "lib"` 的对象并进消费者,走的正是这条路径。
90+
- **直接拿这些对象建 `.so`**:失败,`undefined version LIBAIO_0.4`,需要上游的
91+
`src/libaio.map` 版本脚本。这一点与上游自己的 `libaio.a` 完全相同,不是本描述符引入的。
92+
93+
**为什么不删 `compat-0_1.c`**:一开始考虑过删掉它以消除 `@LIBAIO_0.1`。实测证明没用 ——
94+
把它去掉后 `.so` 链接仍然因三个 `@@LIBAIO_0.4` 失败。既然删了不解决问题,又会让对象集与上游
95+
`libaio.a` 不一致,就保留。
96+
97+
## 5. feature 评估:无
98+
99+
判据是「是否存在额外的、可门控的**可编译源码**」。libaio 没有:
100+
101+
- `src/struct_offsets.c` 是构建期断言(其注释明说 "this code does not end up in the compiled object
102+
files"),上游也是与库分开编的 —— 不编,也不该做成 feature;
103+
- `harness/` 是测试套件,自带 `main()`。mcpp 的 lib 目标对象是**全量入链**的(非 archive 懒选),
104+
包里带 `main()` 必与消费者的 `main()` 冲突,所以它连做成 feature 的资格都没有。
105+
106+
`features` 整个不声明。
107+
108+
## 6. 测试成员 `tests/examples/libaio`
109+
110+
依赖按 `[target.'cfg(linux)'.dependencies.compat]` 门控,测试源码在非 Linux 上编成 no-op `main()`
111+
(compat.wil 的镜像写法)。断言全部是真实内核 AIO 行为,不 mock:
112+
113+
1. 复刻上游 `struct_offsets.c` 的三条 `static_assert`(本包不编那个 TU,把检查搬到这里);
114+
2. `io_prep_pwrite` 填出的 iocb 字段(opcode / fildes / buf / nbytes / offset);
115+
3. 写路径:提交 → 收事件 → `res == 512`,再用 `pread` 确认字节真的落盘;
116+
4. 读路径:在 offset 512 处异步读回并逐字节比对;
117+
5. 一次提交两个 iocb,用 `data` cookie 区分 —— `data` 是 padded 结构的首成员,
118+
PADDEDptr 选错在这里就会现形;
119+
6. 错误契约:对已关闭的 fd 提交,断言返回值是 `-EBADF``errno` 未被改动
120+
(libaio 不用 errno,这是它与周围 POSIX 调用相反的约定);
121+
7. `io_cancel` 对已完成请求返回负 errno —— 目的是让这个只以 `@@LIBAIO_0.4` 存在的符号真的被链接;
122+
8. `io_queue_init` / `io_set_callback` / `io_queue_run` / `io_queue_release` 的回调层。
123+
124+
## 7. 验证结论(mcpp 2026.8.27.2,与 CI 同版本)
125+
126+
- `mcpp xpkg parse pkgs/c/compat.libaio.lua``parse OK`,`sources 12``generated 312 bytes`
127+
- 冷跑(先删 `target/``.mcpp/`)`mcpp test -p libaio``test result ok. 1 passed; 0 failed`
128+
- **确认包真的被编译**:`obj/compat_libaio/libaio-0.3.113/src/` 下 12 个 `.o`,
129+
且最终二进制里能看到 `io_setup`/`io_submit`/`io_getevents@@LIBAIO_0.4` 等符号。
130+
- **确认断言可失败**:把一条 `memcmp` 断言反过来,`mcpp test``FAIL (exit 134)`;改回后重新冷跑仍绿。
131+
- 五个本地 lint(syntax / mirror-urls / package-name / platform-parity / duplicate-versions /
132+
cross-package-refs)全部通过。
133+
134+
## 8. 描述符解析踩到的一个坑
135+
136+
`generated_files` 的值**不能用 Lua 的 `..` 拼接**。mcpp 的描述符解析器不执行 Lua,只读字面量,
137+
遇到 `..` 会报 `malformed mcpp segment near key '<下一个 token>'` —— 报错位置指向别处,很容易误判。
138+
另外整张表写成一行也不行(`expected '=' in generated_files entry`),每个条目要各占一行。
139+
可用形态两种:单行字符串字面量,或 `[[ … ]]` 长字符串(本包与 compat.gmp 都用后者)。
140+
141+
## 9. 同 PR 内的 README 重构
142+
143+
`README.md` / `README.zh-CN.md` 的「参考示例」整张大表(占英文 README 约 22 KB 中的绝大部分)已移出到
144+
`docs/descriptor-examples.md``docs/zh/descriptor-examples.md`,README 只留每种常见形态一行、
145+
一句话说明的六行小表加一个链接。libaio 自己的条目写在新文档里。
146+
147+
顺带修掉表里一条早就失效的链接:`pkgs/o/opencv.lua``pkgs/o/opencv.opencv.lua`
148+
149+
README 体积:28693 → 6151 字节(中文 25591 → 5330)。

0 commit comments

Comments
 (0)