diff --git a/.agents/docs/2026-08-29-add-libaio-plan.md b/.agents/docs/2026-08-29-add-libaio-plan.md new file mode 100644 index 00000000..d7483d42 --- /dev/null +++ b/.agents/docs/2026-08-29-add-libaio-plan.md @@ -0,0 +1,149 @@ +# 收录 libaio 0.3.113(compat.libaio) + +日期:2026-08-29 · PR 分支:`feat/add-libaio` · 状态:本地验证通过 + +## 1. 来源与形态判定 + +libaio 属于来源 (a):第三方上游库,上游不提供 mcpp 支持。 + +- 上游:(Jeff Moyer 维护;GitHub 上只有零散的 fork,无官方镜像)。 +- 最新版本:`git ls-remote --tags` 排序后最大的 release tag 是 **`libaio-0.3.113`**。 + 注意 tag 命名有两代:老的是 `libaio.0-3-107.1` 这种点划混排形式,`sort -V` 会把它们排在 + `libaio-0.3.113` 之后 —— 只看 `tail` 会误判成 0.3.107。 +- License:LGPL-2.1-or-later(`COPYING` 是 LGPL 2.1;各源文件头写 "version 2 of the License, + or (at your option) any later version")。 +- 源码布局:`libaio-0.3.113/` 包一层,库源码全在 `src/`(12 个 TU + 1 个公开头 + 若干私有头), + 另有 `harness/`(测试)与 `man/`。无 configure、无生成步骤、无 submodule、无符号链接。 + +**形态 = A(C 源码 compat)**,叠加一处「公开头从源码目录里择出来」的处理。 + +## 2. 版本与下载源 + +`sha256 = 2c44d1c5fd0d43752287c9ae1eb9c023f04ef848ea8d4aafa46e9aedb678200b`(49980 字节,连算两次一致)。 + +GLOBAL 用 `https://releases.pagure.org/libaio/libaio-0.3.113.tar.gz`,而**不是** pagure 的 +tag 归档 `https://pagure.io/libaio/archive/…`:后者当场返回 404,且即便可用,pagure 与 GitLab 同类, +归档是即时生成的,sha 会漂移。releases.pagure.org 上的是固定发布文件。 + +## 3. CN 镜像 + +已在 gitcode 建 `mcpp-res/libaio`,seed 一个 README 后发 `0.3.113` release,上传与 GLOBAL **同一份** +tarball: + + https://gitcode.com/mcpp-res/libaio/releases/download/0.3.113/libaio-0.3.113.tar.gz + +回拉校验 sha256 与 GLOBAL 逐字节一致,故描述符里 GLOBAL/CN 共用一个 `sha256`。 + +## 4. 三个实现决策 + +### 4.1 只暴露一个公开头(`generated_files` 转发头) + +上游 `make install` 只装 `libaio.h` 一个头,但 tarball 把它放在 `src/` 里,与私有头并列。若直接写 +`include_dirs = { "*/src" }`,消费者的 include 路径上就会多出 `syscall.h`、`aio_ring.h`、 +`vsys_def.h`、`syscall-*.h`。其中 **`syscall.h` 会遮蔽 glibc 的同名头** —— 这是实打实的危害,不是洁癖。 + +做法沿用 compat.gmp 的先例:`generated_files` 写一个 + + libaio-0.3.113/mcpp/include/libaio.h → #include "../../src/libaio.h" + +`include_dirs` 只指这一个目录。为什么这样够用: + +- 包自身的 `.c` 写 `#include `,命中转发头 → 命中真头文件; +- 它们的 `#include "syscall.h"` / `"aio_ring.h"` 是引号形式,按「包含者所在目录优先」解析, + 就在 `src/` 里,**不需要任何指向 `src/` 的 `-I`**; +- `compat-0_1.c` 用引号形式 `#include "libaio.h"`,同理命中 `src/` 里的真头。 + +已实测:12 个 TU 全部在只有 `-I .../mcpp/include` 的情况下零警告编过,且消费者侧 +`#include ` 拿到的确实是 glibc 的(`SYS_read == 0`)。 + +### 4.2 `c_standard = "gnu11"` 是个陷阱 —— 用 `-D_GNU_SOURCE` + +`-std=c11` 会定义 `__STRICT_ANSI__`,glibc 随之关掉 `_DEFAULT_SOURCE`,于是: + +- `` 不再声明 `syscall()`(每个 TU 都经 `syscall.h` 的 `_body_io_syscall` 用到它); +- `sigset_t` 不可见,连**公开头**都在 `io_pgetevents(…, sigset_t *sigmask)` 处解析失败。 + +上游没这个问题,因为它的 Makefile 用编译器默认的 gnu 模式。 + +第一版描述符写了 `c_standard = "gnu11"`,`mcpp xpkg parse` 通过,`mcpp test` 却报出与 c11 完全相同的 +两个错误。查产出的 `compile_commands.json`:**mcpp 2026.8.27.2 收下了 `gnu11` 这个字符串,仍然发 +`-std=c11`**,静默降级、无任何提示。 + +真正生效的写法是 `cflags = { "-D_GNU_SOURCE" }`。已实测 gcc 16.1.0 与 clang 22.1.8 下 +`-std=c11 -D_GNU_SOURCE -Wall` 12 个 TU 全部零警告。 + +> 附带影响:`pkgs/c/compat.freetype.lua` 也声明了 `c_standard = "gnu11"`,同样拿不到 gnu 模式。 +> 它目前 CI 是绿的(说明 freetype 不依赖 gnu 模式),因此本 PR 不动它,但这里记一笔。 + +顺带说明另外两处 GNU 扩展为何不用管:`syscall.h` 的具名可变参数宏 +`_body_io_syscall(sname, args...)`,以及 `raw_syscall.c` 在非 ia64 架构上整个文件只剩一个文件作用域 +的 `;` —— gcc/clang 只在 `-pedantic` 下才诊断这两者。 + +### 4.3 符号版本(symver) + +`io_cancel.c` / `io_getevents.c` / `io_queue_wait.c` 的函数真名是 `io_getevents_0_4` 之类,短名靠 +`.symver … @@LIBAIO_0.4` 发布;`compat-0_1.c` 另加三个 `@LIBAIO_0.1` 的老 ABI 别名。 + +- **链可执行文件**:`@@`(默认版本)会同时定义基名,已在 ld.bfd 与 lld 上各实测通过, + 最终测试二进制里 `nm` 可见 `io_getevents_0_4` 与 `io_getevents@@LIBAIO_0.4` 同址。 + `kind = "lib"` 的对象并进消费者,走的正是这条路径。 +- **直接拿这些对象建 `.so`**:失败,`undefined version LIBAIO_0.4`,需要上游的 + `src/libaio.map` 版本脚本。这一点与上游自己的 `libaio.a` 完全相同,不是本描述符引入的。 + +**为什么不删 `compat-0_1.c`**:一开始考虑过删掉它以消除 `@LIBAIO_0.1`。实测证明没用 —— +把它去掉后 `.so` 链接仍然因三个 `@@LIBAIO_0.4` 失败。既然删了不解决问题,又会让对象集与上游 +`libaio.a` 不一致,就保留。 + +## 5. feature 评估:无 + +判据是「是否存在额外的、可门控的**可编译源码**」。libaio 没有: + +- `src/struct_offsets.c` 是构建期断言(其注释明说 "this code does not end up in the compiled object + files"),上游也是与库分开编的 —— 不编,也不该做成 feature; +- `harness/` 是测试套件,自带 `main()`。mcpp 的 lib 目标对象是**全量入链**的(非 archive 懒选), + 包里带 `main()` 必与消费者的 `main()` 冲突,所以它连做成 feature 的资格都没有。 + +故 `features` 整个不声明。 + +## 6. 测试成员 `tests/examples/libaio` + +依赖按 `[target.'cfg(linux)'.dependencies.compat]` 门控,测试源码在非 Linux 上编成 no-op `main()` +(compat.wil 的镜像写法)。断言全部是真实内核 AIO 行为,不 mock: + +1. 复刻上游 `struct_offsets.c` 的三条 `static_assert`(本包不编那个 TU,把检查搬到这里); +2. `io_prep_pwrite` 填出的 iocb 字段(opcode / fildes / buf / nbytes / offset); +3. 写路径:提交 → 收事件 → `res == 512`,再用 `pread` 确认字节真的落盘; +4. 读路径:在 offset 512 处异步读回并逐字节比对; +5. 一次提交两个 iocb,用 `data` cookie 区分 —— `data` 是 padded 结构的首成员, + PADDEDptr 选错在这里就会现形; +6. 错误契约:对已关闭的 fd 提交,断言返回值是 `-EBADF` 且 `errno` 未被改动 + (libaio 不用 errno,这是它与周围 POSIX 调用相反的约定); +7. `io_cancel` 对已完成请求返回负 errno —— 目的是让这个只以 `@@LIBAIO_0.4` 存在的符号真的被链接; +8. `io_queue_init` / `io_set_callback` / `io_queue_run` / `io_queue_release` 的回调层。 + +## 7. 验证结论(mcpp 2026.8.27.2,与 CI 同版本) + +- `mcpp xpkg parse pkgs/c/compat.libaio.lua` → `parse OK`,`sources 12`、`generated 312 bytes`。 +- 冷跑(先删 `target/` 与 `.mcpp/`)`mcpp test -p libaio` → `test result ok. 1 passed; 0 failed`。 +- **确认包真的被编译**:`obj/compat_libaio/libaio-0.3.113/src/` 下 12 个 `.o`, + 且最终二进制里能看到 `io_setup`/`io_submit`/`io_getevents@@LIBAIO_0.4` 等符号。 +- **确认断言可失败**:把一条 `memcmp` 断言反过来,`mcpp test` 报 `FAIL (exit 134)`;改回后重新冷跑仍绿。 +- 五个本地 lint(syntax / mirror-urls / package-name / platform-parity / duplicate-versions / + cross-package-refs)全部通过。 + +## 8. 描述符解析踩到的一个坑 + +`generated_files` 的值**不能用 Lua 的 `..` 拼接**。mcpp 的描述符解析器不执行 Lua,只读字面量, +遇到 `..` 会报 `malformed mcpp segment near key '<下一个 token>'` —— 报错位置指向别处,很容易误判。 +另外整张表写成一行也不行(`expected '=' in generated_files entry`),每个条目要各占一行。 +可用形态两种:单行字符串字面量,或 `[[ … ]]` 长字符串(本包与 compat.gmp 都用后者)。 + +## 9. 同 PR 内的 README 重构 + +`README.md` / `README.zh-CN.md` 的「参考示例」整张大表(占英文 README 约 22 KB 中的绝大部分)已移出到 +`docs/descriptor-examples.md` 与 `docs/zh/descriptor-examples.md`,README 只留每种常见形态一行、 +一句话说明的六行小表加一个链接。libaio 自己的条目写在新文档里。 + +顺带修掉表里一条早就失效的链接:`pkgs/o/opencv.lua` → `pkgs/o/opencv.opencv.lua`。 + +README 体积:28693 → 6151 字节(中文 25591 → 5330)。 diff --git a/README.md b/README.md index 00af0420..e2369ee1 100644 --- a/README.md +++ b/README.md @@ -33,35 +33,21 @@ Two kinds of packages live here: build information. These come in several shapes — header-only, plain C sources, C++23 module wrapper — with optional components gated behind `features` and a GitCode CN mirror configured. -### Reference examples (`.lua` descriptors) +### Reference examples -| Shape | Examples | -|------|------| -| Native module library (Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](pkgs/f/ffmpeg.lua) (module layer; sources compiled directly through `compat.ffmpeg`) · [`opencv`](pkgs/o/opencv.lua) (single repository: the module layer and the full OpenCV 5 source build both live in the package, and only this descriptor stays on the index side) · [`mcpplibs.grpc`](pkgs/g/grpc.lua) (gRPC 1.83.0 — the one library here that CANNOT be a compat descriptor: upstream publishes no self-contained source artifact, its tag archive carrying abseil/protobuf/re2/boringssl/zlib as empty submodule placeholders, so [grpc-m](https://github.com/mcpplibs/grpc-m)'s release tarball IS that artifact. It vendors only gRPC's own source and takes the five dependencies from this index, so a consumer that also uses protobuf links one copy rather than two) | -| C-source compat (with `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) · [`compat.hiredis`](pkgs/c/compat.hiredis.lua) (the classic 1.2.0 — a 7-TU C build whose flat tarball headers get `hiredis/`-prefixed wrapper headers via `generated_files`, so consumers write `#include ` exactly like upstream's install layout) · [`compat.sqlite3`](pkgs/c/compat.sqlite3.lua) (plain C-source, no features: the single `sqlite3.c` amalgamation; 3.45.3, the final maintenance release of the most widely deployed 3.45.x line) · [`compat.libuv`](pkgs/c/compat.libuv.lua) (libuv 1.48.0 — the per-OS source sets transcribed from upstream's CMakeLists, because a `src/unix/*.c` glob would compile every OS's backend at once; linux/macos get explicit unix subsets, windows globs `src/win/*.c`) | · [`compat.xxhash`](pkgs/c/compat.xxhash.lua) (one TU, one header, no features at all — the interesting decision is what is NOT compiled: `xxh_x86dispatch.c` selects an AVX2/AVX512 path at RUNTIME and needs per-file `-mavx2` plus `XXH_X86DISPATCH` at every call site, so the package ships the flagless SSE2 baseline instead. Nor is the header-only `XXH_INLINE_ALL` mode chosen: it re-emits the implementation in every TU that hashes anything, which is the right trade only when there is exactly one such TU — something a package cannot know) -| C++-source compat, one depending on the other | [`compat.abseil`](pkgs/c/compat.abseil.lua) (151 TUs; a wildcard over `absl/**` trimmed by upstream's test/benchmark naming conventions) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua) (the libprotobuf runtime, 79 TUs transcribed from upstream's own `src/file_lists.cmake`; declares `compat.abseil` as a dependency because protobuf's public headers include `absl/…`, and its `gzip` feature defines `HAVE_ZLIB` and pulls `compat.zlib`, while `upb` adds protobuf's 64-TU C runtime out of the same tarball. It also exposes **`protoc`** as a `kind = "bin"` target, so a consumer writing `tools = ["protoc"]` gets the compiler built for its own machine out of the same package it links — making a generator/runtime version mismatch inexpressible) · [`compat.re2`](pkgs/c/compat.re2.lua) (22 TUs, upstream's own `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua) (redis++ 1.3.13 — the sync client, 17 TUs + `patterns/redlock.cpp`, depends on `compat.hiredis`; the one header CMake would generate, `hiredis_features.h`, is snapshotted via `generated_files`, and the async/TLS TUs are left out so the base build stays a two-package pair. An `async` feature adds the libuv-backed `AsyncRedis` interface (the 9 async TUs + `compat.libuv`; `event_loop.cpp` runs `uv_run` on a background thread, and `` arrives through compat.hiredis' wrapper headers). Two versions, one on each side of the source-structure watershed, share this ONE source list: 1.3.13 (modern 17-TU layout) and 1.3.3 (pre-`redis_uri.cpp`/`redlock` 15-TU layout) — the union works because 1.3.3's TUs are a strict subset, so exactly two globs match nothing there (a warning, not an error; same trick as compat.catch2)) | · [`compat.sqlitecpp`](pkgs/c/compat.sqlitecpp.lua) (the RAII C++ wrapper over SQLite. Upstream vendors sqlite3 as a GIT SUBMODULE, so a source tarball simply does not contain it and the library cannot link — the dependency edge on `compat.sqlite3` replaces the submodule, and does it better: two consumers of SQLite in one link now share ONE amalgamation instead of each embedding a private copy with its own compile-time options. Its two CMake knobs are deliberately not set — `SQLITECPP_USE_ASSERT_ON_ERRORS` changes the error model from throwing to aborting, and `SQLITE_ENABLE_COLUMN_METADATA` has to agree with how SQLite ITSELF was built; both are the consumer's call, and the headers already guard them with `#ifdef`) -| C transport + the header-only C++ server on top of it | [`compat.usockets`](pkgs/c/compat.usockets.lua) · [`compat.uwebsockets`](pkgs/c/compat.uwebsockets.lua) (uSockets picks ONE event loop for all three platforms — libuv, via `compat.libuv` — because the alternative makes `us_loop_t` a different struct per platform for no gain; SSL and QUIC are left out so the base package's only dependency is that loop. The pair's real lesson is that `LIBUS_USE_LIBUV` / `LIBUS_NO_SSL` / `UWS_NO_ZLIB` are INTERFACE facts: `libusockets.h` changes the layout of `us_loop_t` under the first and gates its SSL declarations on the second, and uWS is header-only so its templates are instantiated in the CONSUMER's translation unit. An index descriptor's `cflags` reach only the package's own TUs, so every consumer must declare all three — a mismatch does not fail to build, it corrupts. The usockets test therefore writes to loop-attached extension memory from a timer callback and reads it back, which is exactly the assertion a layout disagreement breaks) | -| C++-source compat, zero-dep client + optional components | [`compat.websocket`](pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs, so the **base build has zero external dependencies**: TLS off (the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and `IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op. Two optional features add on top: `server` (the four server TUs — `IXWebSocketServer`, `IXSocketServer`, `IXHttpServer`, `IXWebSocketProxyServer` — needing nothing external, and it **implies `zlib`** because upstream's server advertises permessage-deflate by default, which the transport negotiates regardless of the define) and `zlib` (deps `compat.zlib` and turns the codec into real per-message-deflate compression). The default-feature test brings its own minimal RFC 6455 echo server on loopback sockets (handshake, masking, fragmentation and close all exercised offline); a second member, `websocket-features`, runs a real `ix::WebSocketServer` and asserts the compression is observable on the wire — a 64 KiB repeated payload round-trips with `wireSize` = 80) | -| Database client + the driver manager it needs, built from source | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua) (nanodbc 2.14.0, frozen upstream — one TU over the platform ODBC driver manager. Two fixes make the four-year-old source compile and RUN here: a force-included `char_traits` shim for libc++ (the standard's own customization point, guarded on `_LIBCPP_VERSION` so libstdc++/MSVC are untouched; and note `-include` reaches C++ TUs only through `cxxflags`, never `cflags`), and a per-platform answer to the manager itself — windows links the SDK's odbc32, macOS the OS's iODBC, while linux takes `compat.unixodbc` because mcpp's runtime closure rejects a NEEDED `libodbc.so.2` that only the host has. The test asserts the manager's own diagnostics surface through the wrapper — including nanodbc's frozen off-by-one that drops the last SQL-state character) · [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua) (unixODBC 2.3.14, Shape E over A — DM + odbcinst + ini/log/lst + libltdl compiled statically into one `odbc` target, exactly upstream's libodbc.a symbol set, so the consumer carries no `libodbc.so.2` NEEDED at all. The one non-obvious piece is libtool-free ltdl wiring: `-DLTDLOPEN=libltdlc` plus a generated `lt_libltdlc_LTX_preloaded_symbols` table (reconstructed from the libtool object's relocations) registers the dlopen loader. The frozen `config.h` merges ltdl's own configure output into the top-level one — ltdl sources never read the clashing identification macros, and the merge sidesteps a quoted `-DLT_CONFIG_H` that does not survive the pipeline. Verified against the libtool build of the same tarball: identical IM002 error path and identical `lt_dlopen` behaviour) | -| C-source compat, an ISA tier turned off through the GENERATED config | [`compat.libwebp`](pkgs/c/compat.libwebp.lua) (117 TUs as five directory globs rather than a transcribed file list, and one real decision. libwebp's SSE4.1 gate is `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`, and `WEBP_MSC_SSE41` keys off `_MSC_VER` **alone** — which every MSVC-ABI compiler defines, including clang, while only cl.exe lets an intrinsic be used without a target flag. Under clang the SSE4.1 sources fail on `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`. Upstream answers with a PER-FILE `-msse4.1`, which no descriptor field expresses; adding it package-wide would let clang emit SSE4.1 in the BASELINE TUs too, past libwebp's own runtime dispatch — a SIGILL rather than a fallback. So the package uses the other half of upstream's own mechanism: `HAVE_CONFIG_H` plus a generated `src/webp/config.h` naming SSE2 and NEON and not SSE4.1, which turns `dec_sse41.c` and friends into upstream's `WEBP_DSP_INIT_STUB` and removes the matching `VP8DspInitSSE41()` call. `src/demux` and `src/mux` are separate upstream libraries with their own public headers and stay out until something asks for them) | -| header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | -| header-only, nothing to gate | [`compat.CLI11`](pkgs/c/compat.CLI11.lua) (a command line parser whose every definition is `CLI11_INLINE`, so the package is `*/include` plus an anchor TU. Upstream's two extras stay out: `src/Precompile.cpp` only means anything when `CLI11_COMPILE` also reaches the CONSUMER's translation units — an interface define, not a sources-only gate — and `src/modules/CLI11.cppm` is a module layer, which is a package shape of its own rather than a feature of the compat package) · [`compat.gtl`](pkgs/c/compat.gtl.lua) (Greg's Template Library — the Swiss-table `flat_hash_map` family plus btrees and a bit_vector. `*/include` exactly, not the tarball root: `tests/` and `examples/` carry headers of their own, and naming `include/` is what upstream's INTERFACE target exposes, so a consumer cannot accidentally resolve into test code) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua) (the reference implementation of the proposed `std::hive`; the whole library is one file at the tarball root, so `*` plus an anchor TU is the entire package. Untagged upstream, so the version is a DATE over a commit archive — the compat.khrplatform precedent) · [`compat.wil`](pkgs/c/compat.wil.lua) (the Windows Implementation Library — RAII over Win32 handles, COM pointers and HRESULT. Windows-ONLY in an unusual sense: not a portable library with a Windows backend, but a library ABOUT Win32, so there are no other platform sections to declare and consumers gate the dependency with `[target.'cfg(windows)'.dependencies]` — compat.x11 and the gui-stack member in the other direction. Nothing is pre-configured: WIL's knobs (`WIL_ENABLE_EXCEPTIONS`, `RESULT_DIAGNOSTICS_LEVEL`, `WIL_USE_STL`) are macros the CONSUMER defines before including, and a header-only package has no compiled artifact for such a choice to be baked into anyway — pre-setting one would pick an error model on its consumers' behalf) | -| single-header library + a GENERATED implementation TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua) (two stb-style headers where the implementation hides behind `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION`. Upstream ships no `.c` — its examples define the macros inline — so the package generates one that instantiates BOTH halves once. That is what turns a header drop into something linkable, and it moves the duplicate-symbol hazard from every consumer to a single place: consumers must NOT define those macros again, and the test links `nsvgParse` and `nsvgRasterize` together precisely so a package that instantiated only one half fails here rather than downstream) · [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua) (VMA 3.4.0, same shape but the generated TU also has to make a POLICY choice. VMA defaults to `VMA_STATIC_VULKAN_FUNCTIONS 1`, which references `vkBindBufferMemory2` and seven siblings by name — eight undefined symbols against a headers-only dep. Pulling `compat.vulkan` to satisfy them would force a Vulkan loader on every consumer of a memory allocator and fight anyone dispatching through volk, so the generated TU selects the dynamic path instead and VMA resolves everything through `VmaVulkanFunctions`. Note the implementation is C++ despite the C-shaped API, so the generated file is `.cpp`) | -| Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | -| Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua) (91 TUs; the release tarball already ships `ares_build.h` and a Windows config, so only `ares_config.h` is snapshotted per OS) · [`compat.msdfgen`](pkgs/c/compat.msdfgen.lua) (msdfgen 1.13 — the config is not optional here: `core/base.h` opens with `#include `, so without generating it nothing compiles, not even `core/`. Generating it rather than passing `-D` flags is also what makes the library and its consumers agree BY CONSTRUCTION — `base.h` is reached from every public header, so the file is the single place that says which of SVG/PNG/Skia exist. Of the four `ext/` units only `import-font.cpp` is built; the other three each need a library this index does not carry, and their declarations disappear through the same generated config. `MSDFGEN_USE_CPP11` is left off on purpose: it adds move constructors to `Bitmap`, so it changes the layout of a type that crosses the library boundary, and a package cannot guarantee every consumer defines it identically) | -| Upstream amalgamation (one TU is the whole library) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua) (HarfBuzz 14.3.0 — upstream builds with meson, and reproducing that here would mean tracking ~137 `.cc` files plus a generated config. `src/harfbuzz.cc` is upstream's own supported "compile one file" path, so `sources` is a single line that cannot drift out of sync with a release. The amalgamation also `#include`s the CoreText/DirectWrite/GDI/GLib/Graphite2 backends, each behind its own `HAVE_*` gate, so naming only `HAVE_FREETYPE` selects the FreeType bridge and compiles the rest to nothing. `HB_NO_MT` is deliberately NOT set: it removes HarfBuzz's atomics, which is only sound under a single-threading promise a shared package cannot make for its consumers) · [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua) (mimalloc 3.4.5 — the opposite lesson: it also ships an amalgamation (`src/static.c`), and using it would be wrong. A `src/*.c` glob is wrong three ways, each a LINK error rather than a compile error — `static.c` duplicates every symbol, and `free.c`/`alloc-override.c` are `#include`d by `alloc.c` rather than being TUs — so the source list is upstream's own `mi_sources`. `MI_MALLOC_OVERRIDE` stays off: a dependency silently taking over the process allocator is not a package's call) · [`compat.miniaudio`](pkgs/c/compat.miniaudio.lua) (miniaudio 0.11.25 — `miniaudio.c` is upstream's own two-line `MINIAUDIO_IMPLEMENTATION` driver and its CMake library target, so `sources` is one line that tracks the release. The Linux link line is `-ldl -lpthread -lm` and deliberately NOT `-lasound`/`-lpulse`: miniaudio `dlopen`s its backends, so the package builds on a machine that has neither) · [`compat.spirv-reflect`](pkgs/c/compat.spirv-reflect.lua) (Khronos' SPIR-V reflection library; `spirv_reflect.c` is exactly upstream's `spirv-reflect-static` target. Both `*` and `*/include` are exposed so the default `"./include/spirv/unified1/spirv.h"` and the `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` spelling resolve to the SAME bundled grammar header — a consumer that defines that macro cannot silently get a different SPIR-V revision than this `.c` was written against. Versioned by SDK line to stay in step with compat.vulkan-headers) | · [`compat.reflectcpp`](pkgs/c/compat.reflectcpp.lua) (reflect-cpp 0.25.0 — upstream ships one umbrella TU per BACKEND, and only two of them are compiled. The other nine (avro / bson / capnproto / cbor / flexbuf / msgpack / toml / xml / yaml) each `#include` a third-party library's headers, so compiling them would turn a dependency-free package into one with nine dependencies; they belong behind features with their own `deps`. The yyjson question resolves the other way: `rfl/json/*.hpp` probes `__has_include()` and falls back to its own `include/rfl/thirdparty/yyjson.h`, so exposing only `include/` keeps the VENDORED copy in play and the package cannot disagree with `compat.yyjson` about a version. `include/rfl/thirdparty` is a second include root only because `src/yyjson.c` includes `"yyjson.h"` flat) -| Upstream codegen frozen into the mirror archive | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua) (two versions: `4.5.0` = the `godot-4.5-stable` bindings, `10.0.0-rc1` = godot-cpp's own 10.x line, whose bindings target Godot 4.6. The ~1000 GDExtension classes under `gen/` exist in no upstream tag archive — upstream's `binding_generator.py` emits them at build time. Running it once offline and publishing upstream's tree byte-for-byte **plus** `gen/` keeps Python off the consumer side entirely; `tools/godot-cpp/repack.sh` reproduces the archive deterministically and refuses to publish if any upstream file differs) | -| Header package filling a gap in the index | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua) (libglvnd's `GL/glx.h`, absent from the Khronos registry and required by SDL's X11 backend) | -| C++ application framework compat (dependencies reuse packages already in the index) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua) (upstream's `3rd/` ships 8 vendored dependencies; none of them is compiled here — all are redirected to the same-version `compat.*` packages in this index) | -| Mutually exclusive backends (one of several inside one package) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua): `vulkan` / `sdl2` each **replace** the default OpenGL / GLFW, and the default backend is expressed by *naming no feature at all* — there is no `opengl`/`glfw` feature. A `default` feature cannot express exclusivity: its own `defines`/`sources`/`deps` have no effect whatsoever, while its `implies` always applies and cannot be overridden by a named feature (which is, conversely, exactly the solution for the "always-on interface define" row below). The workable answer is to read the `-DMCPP_FEATURE_` mcpp passes anyway and decide up front in a force-included header. Note also that `cflags` only reaches C TUs — C++ needs `cxxflags`, so a backend define written only into `cflags` never reaches any `.cpp` | -| Host runtime adaptation (drivers are not vendored) | [`compat.glx-runtime`](pkgs/c/compat.glx-runtime.lua) · [`compat.vulkan-runtime`](pkgs/c/compat.vulkan-runtime.lua) (mcpp binaries run against a bundled glibc, so a bare-soname `dlopen` never reaches the host drivers; a symlink farm plus `runtime.library_dirs` bridges that. Note the farm holds only versioned sonames — `library_dirs` also joins the link line) | -| Always-on interface define | `CURL_STATICLIB` in [`compat.curl`](pkgs/c/compat.curl.lua): `cflags` is always on but package-private, while a feature's `defines` reaches consumers yet has to be named — `default = { implies = … }` applies unconditionally and happens to give both | -| Multiple majors in one package (shape switches with the version) | [`compat.catch2`](pkgs/c/compat.catch2.lua) (3.x compiles `src/catch2/` into a static library; 2.x goes header-only through `single_include/`) | -| External build system (`install()` builds from source) | [`compat.openblas`](pkgs/c/compat.openblas.lua) (Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua) (Perl Configure + Make, static libssl/libcrypto) | -| Whole-source direct build (config snapshot + source list, no external build system) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua) (2281 TUs including NASM assembly, declared through 28 directory globs) | -| Build-time generator output vendored into the descriptor | [`compat.gmp`](pkgs/c/compat.gmp.lua) (516 TUs, all three platforms. GMP's build COMPILES AND RUNS seven table generators and substitutes `gmp.h` from `gmp-h.in` — all of it a pure function of limb=64/nail=0, so the outputs are produced once by upstream's own generators and shipped in `generated_files` (~270 KB, of which `trialdivtab.h` is 109 KB). That is what removes the `install()` hook, autotools, and the host compiler its probes needed — and with them the reason windows was deferred, since GMP's generic C only ever needed a GCC-compatible compiler. `generated_files` also carries a one-line forwarding header per source directory, so the package compiles with **no `-I` at all** and `include_dirs` exposes `gmp.h` + `gmpxx.h` rather than GMP's private headers. Verified against a `--disable-assembly` autotools build of the same tarball: identical 598-symbol export set, and GMP's own `make check` passes 177/178 against it) | -| Module layer over a compat source build (external Form-A repo) | [`godotengine.godot-cpp-m`](pkgs/g/godotengine.godot-cpp-m.lua) (two versions tracking upstream: `10.0.0-rc1` = Godot 4.6, `4.5.0` = Godot 4.5. `import godot_cpp;` re-exports the whole `godot` namespace, ~1800 names GENERATED from the headers rather than curated; the 1022-TU build stays in `compat.godot-cpp`, so the index carries only this descriptor. Macros — `GDCLASS`, `GDREGISTER_CLASS`, `memnew`, `ERR_*` — are the one thing a named module cannot export, so the package ships a side header to include next to the import. It also ships a generated `hashfuncs.hpp` shim — upstream's header minus `static` on two functions whose bodies declare an unnamed union — without which GCC refuses the module interface outright, a hard error no `-W` flag reaches) | -| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua) (upstream's own `include/boost/ut.cppm` reproduced verbatim but for one `__argc`/`__argv` shim that Clang-on-MSVC needs; namespace `boost-ext` since it is NOT an official Boost library) | +A few descriptors worth opening first, one per common shape: + +| Shape | Example | What it shows | +|------|------|------| +| Native module library (Form A) | [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) | Upstream carries its own `mcpp.toml`, so the descriptor is metadata plus a download address | +| C-source compat | [`compat.cjson`](pkgs/c/compat.cjson.lua) | One `.c` compiled into a lib; the optional extension sits behind a `features` gate | +| Header-only | [`compat.gtl`](pkgs/c/compat.gtl.lua) | Nothing to compile — `include_dirs` and an anchor TU | +| Whole-source build + generated config | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) | The config header configure would have produced is snapshotted into `generated_files` | +| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) | A generated `.cppm` turns a header-only library into `import` | +| External build system | [`compat.openssl`](pkgs/c/compat.openssl.lua) | An `install()` hook drives upstream's own Perl Configure + Make | + +The full catalog — every shape this index has needed, and the reasoning behind each descriptor including what it +deliberately leaves out — is in **[Descriptor examples by shape](docs/descriptor-examples.md)**. ### Adding a package @@ -81,6 +67,8 @@ Detailed documentation lives in [`docs/`](docs/), written for humans and agents - [Library shapes and descriptor templates](docs/package-types.md): descriptor templates and samples for each shape, plus how to write the minimal project. +- [Descriptor examples by shape](docs/descriptor-examples.md): the full catalog of what is already in the index, and + why each descriptor is written the way it is. - [The CN mirror loop](docs/cn-mirror.md): `gtc` and gitcode operations, plus the fallback when you have no `mcpp-res` access. - [Repository layout, schema and CI](docs/repository-and-schema.md): field cheat-sheet, selective-run mechanics and diff --git a/README.zh-CN.md b/README.zh-CN.md index 5a369f90..34117e6e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -30,35 +30,21 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 - **第三方 C/C++ 库(`compat`)**:其上游不提供 mcpp 支持,描述文件(Form B)内联构建信息。该类库存在 header-only、纯 C 源码、C++23 module wrapper 等形态,可选组件经 `features` 门控,并配备 GitCode CN 镜像。 -### 参考示例(`.lua` 描述符) +### 参考示例 -| 形态 | 示例 | -|------|------| -| 原生模块库(Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](pkgs/f/ffmpeg.lua)(模块层,源码经 `compat.ffmpeg` 直编) · [`opencv`](pkgs/o/opencv.lua)(单仓库:模块层与 OpenCV 5 全源码构建同在包内,索引侧只留本描述符) · [`mcpplibs.grpc`](pkgs/g/grpc.lua)(gRPC 1.83.0 —— 本索引里唯一**无法**做成 compat 描述符的库:上游不发布任何自包含源码产物,其 tag 归档里 abseil/protobuf/re2/boringssl/zlib 全是空 submodule 占位,因此 [grpc-m](https://github.com/mcpplibs/grpc-m) 的 release tarball 才是那个产物。它只 vendor gRPC 自己的源码,五个依赖全取自本索引,故同时直接使用 protobuf 的消费者链进去的是同一份而非两份)| -| C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) · [`compat.hiredis`](pkgs/c/compat.hiredis.lua)(经典 1.2.0 —— 7 个 C TU;tarball 平铺头经 `generated_files` 补 `hiredis/` 前缀薄包装头,消费者可写 `#include `,与上游安装布局一致) · [`compat.sqlite3`](pkgs/c/compat.sqlite3.lua)(纯 C 源码、无 feature:单一 `sqlite3.c` amalgamation;3.45.3,部署最广的 3.45.x 线) · [`compat.libuv`](pkgs/c/compat.libuv.lua)(libuv 1.48.0 —— 逐 OS 源清单转录自上游 CMakeLists,因为 `src/unix/*.c` 通配会一次编进所有 OS 的后端;linux/macos 显式列 unix 子集,windows 用 `src/win/*.c` glob) |· [`compat.xxhash`](pkgs/c/compat.xxhash.lua)(单 TU、单头、无 feature —— 值得说的是**没有**编译什么:`xxh_x86dispatch.c` 在运行期选择 AVX2/AVX512 路径,需要 per-file `-mavx2` 并要求每个调用点定义 `XXH_X86DISPATCH`,故本包只出无需任何 flag 的 SSE2 基线。也没有选 header-only 的 `XXH_INLINE_ALL` 模式:它会在每个做哈希的 TU 里重新展开整份实现,那只有在「恰好只有一个这样的 TU」时才划算 —— 而这件事包本身无从知道) -| C++ 源码 compat(彼此依赖) | [`compat.abseil`](pkgs/c/compat.abseil.lua)(151 TU;对 `absl/**` 取通配后,按上游自身的 test/benchmark 命名约定裁剪) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua)(libprotobuf 运行时,79 TU 逐条转录自上游 `src/file_lists.cmake`;因 protobuf 公开头文件 include 了 `absl/…`,故显式依赖 `compat.abseil`;`gzip` feature 定义 `HAVE_ZLIB` 并拉入 `compat.zlib`,`upb` feature 则从同一个 tarball 里再编出 protobuf 的 64 TU C 运行时;还以 `kind = "bin"` target 暴露 **`protoc`**,消费者写 `tools = ["protoc"]` 即可从「自己链接的那个包」拿到为本机构建的编译器,使生成器与运行时的版本错配无法表达) · [`compat.re2`](pkgs/c/compat.re2.lua)(22 TU,取自上游自身的 `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua)(redis++ 1.3.13 —— 同步客户端,17 TU + `patterns/redlock.cpp`,依赖 `compat.hiredis`;CMake 唯一会生成的头 `hiredis_features.h` 用 `generated_files` 快照,async/TLS TU 不收,基座保持两包成对。`async` feature 补齐 libuv 版 `AsyncRedis` 接口(9 个 async TU + `compat.libuv`;`event_loop.cpp` 在后台线程跑 `uv_run`,`` 经 compat.hiredis 的包装头到达)。两个版本分处源码结构分水岭两侧,共享同一份源列表:1.3.13(现代 17-TU 布局)与 1.3.3(缺 `redis_uri.cpp`/`redlock` 的 15-TU 旧布局)—— 并集之所以成立,是因为 1.3.3 的 TU 是 1.3.13 的严格子集,恰好两个 glob 在 1.3.3 上零命中(仅警告,非错误;与 compat.catch2 同款手法)) · [`compat.sqlitecpp`](pkgs/c/compat.sqlitecpp.lua)(SQLite 的 RAII C++ 封装。上游用 **git submodule** 引 sqlite3,源码 tarball 里根本没有它,库因此无法链接 —— 依赖边指向 `compat.sqlite3` 替代了那个 submodule,而且更好:同一次链接里的两个 SQLite 消费者从此共享**一份** amalgamation,而不是各自内嵌一份带各自编译选项的副本。它的两个 CMake 开关有意不设 —— `SQLITECPP_USE_ASSERT_ON_ERRORS` 把错误模型从抛异常改成中止进程,`SQLITE_ENABLE_COLUMN_METADATA` 必须与 SQLite **自身**的构建一致;两者都该由消费者决定,而头文件本来就用 `#ifdef` 守着) | -| C 传输层 + 其上的 header-only C++ 服务端 | [`compat.usockets`](pkgs/c/compat.usockets.lua) · [`compat.uwebsockets`](pkgs/c/compat.uwebsockets.lua)(uSockets 三平台统一选 libuv 一个事件循环(经 `compat.libuv`),因为按平台各选后端只会让 `us_loop_t` 每个平台一个形状而毫无收益;SSL 与 QUIC 不收,于是基础包的唯一依赖就是那个循环。这一对真正的教训是 `LIBUS_USE_LIBUV` / `LIBUS_NO_SSL` / `UWS_NO_ZLIB` 是**接口级**事实:`libusockets.h` 会因前者改变 `us_loop_t` 的布局、因后者门控 SSL 声明,而 uWS 是 header-only —— 它的模板是在**消费者**的 TU 里实例化的。描述符的 `cflags` 只作用于包自身的 TU,所以每个消费者都必须自己声明这三个;不一致不会构建失败,而是内存损坏。usockets 的测试因此从定时器回调里写loop 附属的扩展内存再读回来 —— 布局一旦不一致,正是这条断言会断) | -| C++ 源码 compat(零依赖客户端 + 可选组件) | [`compat.websocket`](pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端,**基座零外部依赖**:TLS 关闭(OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),`IXWEBSOCKET_USE_ZLIB` 不定义(gzip codec 编译为 no-op)。两个可选 feature 在基座上叠加:`server`(4 个 server TU —— `IXWebSocketServer`/`IXSocketServer`/`IXHttpServer`/`IXWebSocketProxyServer`,零新增外部依赖,且 **implies `zlib`** —— 因为上游 server 默认就宣称 permessage-deflate,而 transport 的协商不受宏门控)与 `zlib`(依赖 `compat.zlib`,把 codec 变成真正的 permessage-deflate 压缩)。默认构建的测试自带基于 loopback 原始 socket 的最小 RFC 6455 echo server(握手/掩码/分片/关闭全部离线实测);第二个成员 `websocket-features` 跑真实的 `ix::WebSocketServer`,并断言压缩在线路上可观测 —— 64 KiB 重复载荷往返,`wireSize` = 80) | -| 数据库客户端 + 源码构建的驱动管理器 | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua)(nanodbc 2.14.0,上游已冻结 —— 单 TU 封装平台 ODBC 驱动管理器。两处修复让这份四年前的源码在此可编译、可运行:一个 force-include 的 `char_traits` 补丁头(标准留给用户的定制点,以 `_LIBCPP_VERSION` 为界,不影响 libstdc++/MSVC;注意 `-include` 只能经 `cxxflags` 到达 C++ TU,`cflags` 够不着),以及对驱动管理器本身的分平台答案 —— windows 链 SDK 的 odbc32、macOS 链系统自带的 iODBC,linux 则依赖 `compat.unixodbc`,因为 mcpp 的运行时闭包检查不接受只有宿主才有的 `libodbc.so.2`。测试断言管理器自身的诊断能穿过封装层 —— 包括 nanodbc 已冻结的、会把 SQL state 末字符截掉的 off-by-one)· [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua)(unixODBC 2.3.14,E 叠 A 形态 —— DM + odbcinst + ini/log/lst + libltdl 静态编入单一 `odbc` 目标,与上游 libodbc.a 符号集一致,消费者不带任何 `libodbc.so.2` NEEDED。唯一非常规之处是无 libtool 的 ltdl 接线:`-DLTDLOPEN=libltdlc` 加一张生成的 `lt_libltdlc_LTX_preloaded_symbols` 表(从 libtool 目标文件的重定位记录还原)注册 dlopen loader。冻结的 `config.h` 把 ltdl 自己的 configure 输出合并进顶层(冲突宏 ltdl 源码并不读),绕开了无法在管道中幸存的带引号 `-DLT_CONFIG_H`。已与同 tarball 的 libtool 构建对比验证:IM002 错误路径与 `lt_dlopen` 行为完全一致) | -| C 源码 compat(用**生成的 config** 关掉一档 ISA) | [`compat.libwebp`](pkgs/c/compat.libwebp.lua)(117 个 TU 用五条目录通配写完,外加一个真实的取舍。libwebp 的 SSE4.1 门是 `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`,而 `WEBP_MSC_SSE41` **只**看 `_MSC_VER` —— 每个 MSVC ABI 编译器都定义它,clang 也不例外,但只有 cl.exe 允许不带 target flag 使用任意 intrinsic。于是 clang 下 SSE4.1 那批源码报 `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`。上游用 **per-file** `-msse4.1` 解决,而描述符没有这个字段;整包加上去,clang 就会在**基线** TU 里也发 SSE4.1,绕过 libwebp 自己的运行期分发 —— 那是 SIGILL 而不是回退。所以本包用上游同一套机制的另一半:`HAVE_CONFIG_H` + 生成的 `src/webp/config.h`,只声明 SSE2 与 NEON、不声明 SSE4.1,`dec_sse41.c` 等随之变成上游的 `WEBP_DSP_INIT_STUB`,对应的 `VP8DspInitSSE41()` 调用点也一并消失。`src/demux` / `src/mux` 是上游各自独立、各带公开头文件的库,在有人需要之前不收) | -| header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | -| header-only(无可门控组件) | [`compat.CLI11`](pkgs/c/compat.CLI11.lua)(命令行解析器,全部定义都是 `CLI11_INLINE`,故整包就是 `*/include` 加一个 anchor TU。上游两个额外件都不收:`src/Precompile.cpp` 只有在 `CLI11_COMPILE` 同时到达**消费者** TU 时才有意义 —— 那是 interface define,不是 sources 门控;`src/modules/CLI11.cppm` 属于模块层,是另一种包形态,而非 compat 包的 feature) · [`compat.gtl`](pkgs/c/compat.gtl.lua)(Greg's Template Library —— Swiss-table 的 `flat_hash_map` 家族,外加 btree 与 bit_vector。只取 `*/include` 而非 tarball 根:`tests/` 与 `examples/` 各自带头文件,而 `include/` 正是上游 INTERFACE target 暴露的范围,消费者不会误解析到测试代码) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua)(提案中 `std::hive` 的参考实现;整库就是 tarball 根下一个文件,故全包即 `*` 加一个 anchor TU。上游不打 tag,于是版本用 commit 归档上的日期 —— 沿用 compat.khrplatform 的先例) · [`compat.wil`](pkgs/c/compat.wil.lua)(Windows Implementation Library —— Win32 句柄、COM 指针与 HRESULT 的 RAII 封装。它的「仅 Windows」性质与众不同:不是带 Windows 后端的可移植库,而是一个**关于** Win32 的库,因此没有别的平台段可声明,消费者用 `[target.'cfg(windows)'.dependencies]` 门控 —— 与 compat.x11 及 gui-stack 成员正好互为镜像。什么都不预设:WIL 的开关(`WIL_ENABLE_EXCEPTIONS`、`RESULT_DIAGNOSTICS_LEVEL`、`WIL_USE_STL`)都是**消费者**在 include 之前定义的宏,而 header-only 包根本没有可以把这个选择烤进去的产物 ——预设任何一个都等于替消费者选定了错误模型) | -| 单头库 + **生成**实现 TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua)(两个 stb 风格头文件,实现藏在 `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION` 之后。上游不提供 `.c` —— 其示例是就地 define 宏 —— 故本包生成一个,把两半各实例化一次。这才让「一堆头文件」变成可链接的包,并把重复符号的风险从每个消费者收敛到唯一一处:消费者不得再次定义这两个宏。测试刻意同时链接 `nsvgParse` 与 `nsvgRasterize`,使「只实例化了一半」的包在此处就失败,而不是流到下游)· [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua)(VMA 3.4.0,同一形态,但生成的 TU 还得做一个**策略**选择。VMA 默认 `VMA_STATIC_VULKAN_FUNCTIONS 1`,会按名字引用 `vkBindBufferMemory2` 等八个符号 —— 对「只依赖头文件」而言就是八个未定义引用。为此拉入 `compat.vulkan` 是错的:那会逼所有用内存分配器的消费者都链上 Vulkan loader,并与通过 volk 自行分发的项目冲突。故生成的 TU 改走动态路径,VMA 一律经 `VmaVulkanFunctions` 解析。注意其 API 形如 C 但实现是 C++,故生成文件为 `.cpp`)| -| 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | -| 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua)(91 TU;release tarball 已自带 `ares_build.h` 与 Windows 配置,故只需按 OS 冻结 `ares_config.h`) · [`compat.msdfgen`](pkgs/c/compat.msdfgen.lua)(msdfgen 1.13 —— 这里的 config 不是可选项:`core/base.h` 开头就是 `#include `,不生成它连 `core/` 都编不了。选择生成它而非传 `-D`,还使库与消费者**天然一致** —— `base.h` 被每个公开头间接包含,于是该文件成为「SVG/PNG/Skia 哪些存在」的唯一出处。`ext/` 四个单元只编 `import-font.cpp`,其余三个各需一个本索引没有的库,其声明经由同一份生成 config 一并消失。`MSDFGEN_USE_CPP11` 刻意不开:它给 `Bitmap` 增加移动构造,即改变了跨库边界类型的布局,而包无法保证每个消费者都同样定义它) | -| 上游 amalgamation(单 TU 即整库) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua)(HarfBuzz 14.3.0 —— 上游用 meson,在此复刻意味着跟踪 ~137 个 `.cc` 加一份生成的 config。`src/harfbuzz.cc` 正是上游自己支持的「只编一个文件」路径,于是 `sources` 只有一行,且不会与 release 脱节。该 amalgamation 同时 `#include` 了 CoreText/DirectWrite/GDI/GLib/Graphite2 各后端,每个都有自己的 `HAVE_*` 门,因此仅声明 `HAVE_FREETYPE` 即可精确选中 FreeType 桥接,其余编译为空。`HB_NO_MT` 刻意不设:它会去掉 HarfBuzz 的原子操作与锁,仅在消费者保证单线程时才成立,而共享包无法替消费者作此承诺)· [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua)(mimalloc 3.4.5 —— 反向的教训:它同样带 amalgamation(`src/static.c`),但用它是错的。`src/*.c` 通配会在三处出错,且每一处都是**链接期**而非编译期报错 —— `static.c` 会让每个符号重复,`free.c` / `alloc-override.c` 则是被 `alloc.c` `#include` 的、并非独立 TU —— 故源列表取上游自己的 `mi_sources`。`MI_MALLOC_OVERRIDE` 保持关闭:让一个依赖悄悄接管进程分配器,不该由包来决定) · [`compat.miniaudio`](pkgs/c/compat.miniaudio.lua)(miniaudio 0.11.25 —— `miniaudio.c` 就是上游自己那两行 `MINIAUDIO_IMPLEMENTATION` 驱动文件,也是其 CMake 库目标,故 `sources` 只有一行且随 release 走。Linux 链接行是 `-ldl -lpthread -lm`,刻意不含 `-lasound`/`-lpulse`:miniaudio 用 `dlopen` 加载后端,因此在两者都没有的机器上依然能构建)· [`compat.spirv-reflect`](pkgs/c/compat.spirv-reflect.lua)(Khronos 的 SPIR-V 反射库;`spirv_reflect.c` 恰是上游 `spirv-reflect-static` 目标。同时暴露 `*` 与 `*/include`,使默认的 `"./include/spirv/unified1/spirv.h"` 与 `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` 两种写法解析到**同一份**内置语法头 —— 定义了该宏的消费者不会悄悄拿到与这份 `.c` 不匹配的 SPIR-V 修订。版本按 SDK 线号,与 compat.vulkan-headers 保持同步) · [`compat.reflectcpp`](pkgs/c/compat.reflectcpp.lua)(reflect-cpp 0.25.0 —— 上游按**后端**各出一个伞形 TU,这里只编两个。另外九个(avro / bson / capnproto / cbor / flexbuf / msgpack / toml / xml / yaml)各自 `#include` 一个第三方库的头文件,编了就把一个零依赖包变成九依赖包;它们该以 feature 形态各带 `deps` 单独进来。yyjson 的取舍反过来:`rfl/json/*.hpp` 用 `__has_include()` 探测,探不到就回落到自带的 `include/rfl/thirdparty/yyjson.h`,所以只暴露 `include/` 就保住了**内置副本**,本包也就不可能和 `compat.yyjson` 在版本上打架。`include/rfl/thirdparty` 之所以要作为第二个 include root,只是因为 `src/yyjson.c` 是平铺 include `"yyjson.h"` 的) | -| 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua)(两个版本:`4.5.0` 是 `godot-4.5-stable` 的绑定,`10.0.0-rc1` 是 godot-cpp 自己的 10.x 线、对应 Godot 4.6。`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) | -| 补索引空缺的头文件包 | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) | -| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) | -| 互斥后端(同包多后端二选一) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua):`vulkan` / `sdl2` 各自**替换**默认的 OpenGL / GLFW,默认后端由"不点名任何 feature"表达,并不存在 `opengl`/`glfw` feature。`default` feature 表达不了互斥 —— 它自带的 `defines`/`sources`/`deps` 完全不生效,而 `implies` 又恒生效、无法被点名的 feature 覆盖(后者反而正好是本表『恒开的 interface define』一行的解法)。可行解是读 mcpp 本就会传的 `-DMCPP_FEATURE_`,在强制包含头里做前置判定。另注意 `cflags` 只作用于 C TU,C++ 需 `cxxflags` —— 只写进 `cflags` 的后端 define 到不了任何 `.cpp` | -| 宿主运行时适配(不 vendor 驱动) | [`compat.glx-runtime`](pkgs/c/compat.glx-runtime.lua) · [`compat.vulkan-runtime`](pkgs/c/compat.vulkan-runtime.lua)(mcpp 产物跑在自带 glibc 下,裸 soname 的 `dlopen` 够不到宿主驱动;用符号链接农场 + `runtime.library_dirs` 打通。注意 farm 只放带版本号的 soname —— `library_dirs` 同时进链接行) | -| 恒开的 interface define | [`compat.curl`](pkgs/c/compat.curl.lua) 的 `CURL_STATICLIB`:`cflags` 恒开但包私有,feature `defines` 可达消费端但需点名 —— `default = { implies = … }` 无条件生效,恰好两者兼得 | -| 单包多 major(形态随版本切换) | [`compat.catch2`](pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) | -| 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) | -| 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) | -| 构建期生成器产物内联进描述符 | [`compat.gmp`](pkgs/c/compat.gmp.lua)(516 TU,三平台齐全。GMP 的构建要**编译并运行**七个表生成器,还要把 `gmp-h.in` substitute 成 `gmp.h` —— 这些都只是 limb=64/nail=0 的纯函数,故用上游自己的生成器跑一次,产物写进 `generated_files`(约 270 KB,其中 `trialdivtab.h` 占 109 KB)。这一步换掉的是 `install()` 钩子、autotools 以及其探针所需的宿主编译器,连带解掉 windows 推迟的理由 —— GMP 的通用 C 内核从来只要一个 GCC 兼容编译器。`generated_files` 里还按源码目录各放一个一行转发头,于是整包编译**不需要任何 `-I`**,`include_dirs` 只暴露 `gmp.h` + `gmpxx.h`,而不是 GMP 的私有头。与同一 tarball 的 `--disable-assembly` autotools 构建对拍:导出符号 598 个完全一致,上游自带 `make check` 对本产物 177/178 通过) | -| 模块层叠在 compat 源码构建之上(外部 Form-A 仓) | [`godotengine.godot-cpp-m`](pkgs/g/godotengine.godot-cpp-m.lua)(两个版本与上游对齐:`10.0.0-rc1` 对应 Godot 4.6,`4.5.0` 对应 Godot 4.5。`import godot_cpp;` 重导出整个 `godot` 命名空间,约 1800 个名字由头文件**生成**而非手工罗列;1022 个 TU 的构建留在 `compat.godot-cpp`,索引侧只留这一个描述符。宏 —— `GDCLASS`、`GDREGISTER_CLASS`、`memnew`、`ERR_*` —— 是具名模块唯一带不走的东西,故包内附一个与 import 并排包含的侧头文件。另外还带一份生成的 `hashfuncs.hpp` 遮蔽头 —— 上游那个头去掉两个函数的 `static`(它们体内声明了匿名 union)—— 否则 GCC 直接拒绝该模块接口,且是任何 `-W` 开关都够不到的硬错误) | -| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](pkgs/b/boost-ext.ut.lua)(逐字复用上游自带的 `include/boost/ut.cppm`,仅加一处 Clang-on-MSVC 需要的 `__argc`/`__argv` shim;命名空间取 `boost-ext`,因其并非 boost 官方库) | +按常见形态各挑一个,建议先看这几个描述符: + +| 形态 | 示例 | 看点 | +|------|------|------| +| 原生模块库(Form A) | [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) | 上游自带 `mcpp.toml`,描述符只有元数据与下载地址 | +| C 源码 compat | [`compat.cjson`](pkgs/c/compat.cjson.lua) | 单个 `.c` 编成库;可选扩展由 `features` 门控 | +| header-only | [`compat.gtl`](pkgs/c/compat.gtl.lua) | 没有可编译内容 —— `include_dirs` 加一个 anchor TU | +| 全源码直编 + 生成 config | [`compat.c-ares`](pkgs/c/compat.c-ares.lua) | 把 configure 本该生成的 config 头快照进 `generated_files` | +| C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) | 一份生成的 `.cppm` 把 header-only 库变成 `import` 即用 | +| 外部构建系统 | [`compat.openssl`](pkgs/c/compat.openssl.lua) | `install()` 钩子驱动上游自己的 Perl Configure + Make | + +完整目录 —— 本索引遇到过的全部形态,以及每个描述符背后的取舍(包括它刻意不做什么)—— +见 **[描述符示例总览(按形态)](docs/zh/descriptor-examples.md)**。 ### 新增一个包 @@ -75,6 +61,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 细节文档位于 [`docs/zh/`](docs/zh/),供人工与 agent 共同使用(英文版位于 [`docs/`](docs/)): - [库形态与描述符模板](docs/zh/package-types.md):各类形态的描述符模板与样例,以及最小工程的写法。 +- [描述符示例总览(按形态)](docs/zh/descriptor-examples.md):索引里已有内容的完整目录,以及每个描述符为何这样写。 - [CN 镜像闭环](docs/zh/cn-mirror.md):`gtc` 与 gitcode 操作,以及无 `mcpp-res` 权限时的回退方案。 - [仓库结构与 schema 与 CI](docs/zh/repository-and-schema.md):字段速查、选跑机制与本地 lint。 - 字段的**权威判定**是 `mcpp xpkg parse`(CI 用的就是它:未知的 mcpp 段字段直接失败,而不是被静默忽略); diff --git a/docs/README.md b/docs/README.md index 75ed80c8..0c73eee1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,6 +10,7 @@ behind each step. | Document | Contents | |------|------| | [package-types.md](package-types.md) | Descriptor templates and samples for the four library shapes (C-source compat, header-only, C++23 module wrapper, external Form-A module repo) | +| [descriptor-examples.md](descriptor-examples.md) | The complete catalog of descriptors in this index, grouped by shape — what each package compiles, and what it deliberately leaves out | | [cn-mirror.md](cn-mirror.md) | The GitCode `mcpp-res` CN mirror loop (`gtc` tooling, closed-loop verification, caveats), including the fallback when you lack write access to `mcpp-res` (plain-string upstream url) | | [repository-and-schema.md](repository-and-schema.md) | Repository layout, descriptor schema cheat-sheet, `validate.yml` CI behavior, reproducing lint locally, case index | diff --git a/docs/descriptor-examples.md b/docs/descriptor-examples.md new file mode 100644 index 00000000..76f07d9c --- /dev/null +++ b/docs/descriptor-examples.md @@ -0,0 +1,40 @@ +# Descriptor examples by shape + +**English** | [简体中文](zh/descriptor-examples.md) + +The complete catalog of `.lua` descriptors in this index, grouped by the shape of the problem each one solves. It is +meant to be read the way you would read a case book: find the row whose situation matches yours, open that descriptor, +and reuse its reasoning. Every entry records not just what the package compiles but **what it deliberately does not**, +which is usually the part worth copying. + +For the templates behind each shape see [package-types.md](package-types.md); the short list of starting points lives +in the [root README](../README.md#reference-examples). + +| Shape | Examples | +|------|------| +| Native module library (Form A) | [`mcpplibs.xpkg`](../pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](../pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](../pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](../pkgs/f/ffmpeg.lua) (module layer; sources compiled directly through `compat.ffmpeg`) · [`opencv`](../pkgs/o/opencv.opencv.lua) (single repository: the module layer and the full OpenCV 5 source build both live in the package, and only this descriptor stays on the index side) · [`mcpplibs.grpc`](../pkgs/g/grpc.lua) (gRPC 1.83.0 — the one library here that CANNOT be a compat descriptor: upstream publishes no self-contained source artifact, its tag archive carrying abseil/protobuf/re2/boringssl/zlib as empty submodule placeholders, so [grpc-m](https://github.com/mcpplibs/grpc-m)'s release tarball IS that artifact. It vendors only gRPC's own source and takes the five dependencies from this index, so a consumer that also uses protobuf links one copy rather than two) | +| C-source compat (with `features`) | [`compat.cjson`](../pkgs/c/compat.cjson.lua) · [`compat.zlib`](../pkgs/c/compat.zlib.lua) · [`compat.hiredis`](../pkgs/c/compat.hiredis.lua) (the classic 1.2.0 — a 7-TU C build whose flat tarball headers get `hiredis/`-prefixed wrapper headers via `generated_files`, so consumers write `#include ` exactly like upstream's install layout) · [`compat.sqlite3`](../pkgs/c/compat.sqlite3.lua) (plain C-source, no features: the single `sqlite3.c` amalgamation; 3.45.3, the final maintenance release of the most widely deployed 3.45.x line) · [`compat.libuv`](../pkgs/c/compat.libuv.lua) (libuv 1.48.0 — the per-OS source sets transcribed from upstream's CMakeLists, because a `src/unix/*.c` glob would compile every OS's backend at once; linux/macos get explicit unix subsets, windows globs `src/win/*.c`) | · [`compat.xxhash`](../pkgs/c/compat.xxhash.lua) (one TU, one header, no features at all — the interesting decision is what is NOT compiled: `xxh_x86dispatch.c` selects an AVX2/AVX512 path at RUNTIME and needs per-file `-mavx2` plus `XXH_X86DISPATCH` at every call site, so the package ships the flagless SSE2 baseline instead. Nor is the header-only `XXH_INLINE_ALL` mode chosen: it re-emits the implementation in every TU that hashes anything, which is the right trade only when there is exactly one such TU — something a package cannot know) +| C-source compat where the library IS a kernel ABI | [`compat.libaio`](../pkgs/c/compat.libaio.lua) (libaio 0.3.113 — twelve syscall-wrapper TUs, and the only `xpm` section is `linux`, because there is no port to declare: `struct iocb` is the kernel's and every TU is `syscall(__NR_io_*, …)`. Consumers gate it with `[target.'cfg(linux)'.dependencies]`, the mirror image of compat.wil. Three things it teaches. **One public header out of a source dir**: upstream installs exactly one, `libaio.h`, but the tarball keeps it in `src/` beside the private headers — one of which is named `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 `#include "syscall.h"` still resolves next to the including `.c`, so no `-I` into `src/` is needed at all. **A `c_standard` that is a trap**: `-std=c11` sets `__STRICT_ANSI__`, which hides `syscall()` and `sigset_t`, and the public header then fails to parse at `io_pgetevents`; declaring `c_standard = "gnu11"` LOOKS like the fix but mcpp 2026.8.27.2 accepts the string and still emits `-std=c11` (visible in the emitted `compile_commands.json`), so `-D_GNU_SOURCE` in `cflags` is the spelling that takes effect. **Symbol versioning in a static package**: `io_getevents` and `io_cancel` have no ordinary definitions upstream — the functions are `io_getevents_0_4` etc. publishing short names through `.symver … @@LIBAIO_0.4` — which resolves for an executable under both ld.bfd and lld, but not when a consumer builds a `.so` straight out of these objects; that needs upstream's `src/libaio.map`, exactly as upstream's own `libaio.a` does) | +| C++-source compat, one depending on the other | [`compat.abseil`](../pkgs/c/compat.abseil.lua) (151 TUs; a wildcard over `absl/**` trimmed by upstream's test/benchmark naming conventions) · [`compat.protobuf`](../pkgs/c/compat.protobuf.lua) (the libprotobuf runtime, 79 TUs transcribed from upstream's own `src/file_lists.cmake`; declares `compat.abseil` as a dependency because protobuf's public headers include `absl/…`, and its `gzip` feature defines `HAVE_ZLIB` and pulls `compat.zlib`, while `upb` adds protobuf's 64-TU C runtime out of the same tarball. It also exposes **`protoc`** as a `kind = "bin"` target, so a consumer writing `tools = ["protoc"]` gets the compiler built for its own machine out of the same package it links — making a generator/runtime version mismatch inexpressible) · [`compat.re2`](../pkgs/c/compat.re2.lua) (22 TUs, upstream's own `RE2_SOURCES`) · [`compat.redis-plus-plus`](../pkgs/c/compat.redis-plus-plus.lua) (redis++ 1.3.13 — the sync client, 17 TUs + `patterns/redlock.cpp`, depends on `compat.hiredis`; the one header CMake would generate, `hiredis_features.h`, is snapshotted via `generated_files`, and the async/TLS TUs are left out so the base build stays a two-package pair. An `async` feature adds the libuv-backed `AsyncRedis` interface (the 9 async TUs + `compat.libuv`; `event_loop.cpp` runs `uv_run` on a background thread, and `` arrives through compat.hiredis' wrapper headers). Two versions, one on each side of the source-structure watershed, share this ONE source list: 1.3.13 (modern 17-TU layout) and 1.3.3 (pre-`redis_uri.cpp`/`redlock` 15-TU layout) — the union works because 1.3.3's TUs are a strict subset, so exactly two globs match nothing there (a warning, not an error; same trick as compat.catch2)) | · [`compat.sqlitecpp`](../pkgs/c/compat.sqlitecpp.lua) (the RAII C++ wrapper over SQLite. Upstream vendors sqlite3 as a GIT SUBMODULE, so a source tarball simply does not contain it and the library cannot link — the dependency edge on `compat.sqlite3` replaces the submodule, and does it better: two consumers of SQLite in one link now share ONE amalgamation instead of each embedding a private copy with its own compile-time options. Its two CMake knobs are deliberately not set — `SQLITECPP_USE_ASSERT_ON_ERRORS` changes the error model from throwing to aborting, and `SQLITE_ENABLE_COLUMN_METADATA` has to agree with how SQLite ITSELF was built; both are the consumer's call, and the headers already guard them with `#ifdef`) +| C transport + the header-only C++ server on top of it | [`compat.usockets`](../pkgs/c/compat.usockets.lua) · [`compat.uwebsockets`](../pkgs/c/compat.uwebsockets.lua) (uSockets picks ONE event loop for all three platforms — libuv, via `compat.libuv` — because the alternative makes `us_loop_t` a different struct per platform for no gain; SSL and QUIC are left out so the base package's only dependency is that loop. The pair's real lesson is that `LIBUS_USE_LIBUV` / `LIBUS_NO_SSL` / `UWS_NO_ZLIB` are INTERFACE facts: `libusockets.h` changes the layout of `us_loop_t` under the first and gates its SSL declarations on the second, and uWS is header-only so its templates are instantiated in the CONSUMER's translation unit. An index descriptor's `cflags` reach only the package's own TUs, so every consumer must declare all three — a mismatch does not fail to build, it corrupts. The usockets test therefore writes to loop-attached extension memory from a timer callback and reads it back, which is exactly the assertion a layout disagreement breaks) | +| C++-source compat, zero-dep client + optional components | [`compat.websocket`](../pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs, so the **base build has zero external dependencies**: TLS off (the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and `IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op. Two optional features add on top: `server` (the four server TUs — `IXWebSocketServer`, `IXSocketServer`, `IXHttpServer`, `IXWebSocketProxyServer` — needing nothing external, and it **implies `zlib`** because upstream's server advertises permessage-deflate by default, which the transport negotiates regardless of the define) and `zlib` (deps `compat.zlib` and turns the codec into real per-message-deflate compression). The default-feature test brings its own minimal RFC 6455 echo server on loopback sockets (handshake, masking, fragmentation and close all exercised offline); a second member, `websocket-features`, runs a real `ix::WebSocketServer` and asserts the compression is observable on the wire — a 64 KiB repeated payload round-trips with `wireSize` = 80) | +| Database client + the driver manager it needs, built from source | [`compat.nanodbc`](../pkgs/c/compat.nanodbc.lua) (nanodbc 2.14.0, frozen upstream — one TU over the platform ODBC driver manager. Two fixes make the four-year-old source compile and RUN here: a force-included `char_traits` shim for libc++ (the standard's own customization point, guarded on `_LIBCPP_VERSION` so libstdc++/MSVC are untouched; and note `-include` reaches C++ TUs only through `cxxflags`, never `cflags`), and a per-platform answer to the manager itself — windows links the SDK's odbc32, macOS the OS's iODBC, while linux takes `compat.unixodbc` because mcpp's runtime closure rejects a NEEDED `libodbc.so.2` that only the host has. The test asserts the manager's own diagnostics surface through the wrapper — including nanodbc's frozen off-by-one that drops the last SQL-state character) · [`compat.unixodbc`](../pkgs/c/compat.unixodbc.lua) (unixODBC 2.3.14, Shape E over A — DM + odbcinst + ini/log/lst + libltdl compiled statically into one `odbc` target, exactly upstream's libodbc.a symbol set, so the consumer carries no `libodbc.so.2` NEEDED at all. The one non-obvious piece is libtool-free ltdl wiring: `-DLTDLOPEN=libltdlc` plus a generated `lt_libltdlc_LTX_preloaded_symbols` table (reconstructed from the libtool object's relocations) registers the dlopen loader. The frozen `config.h` merges ltdl's own configure output into the top-level one — ltdl sources never read the clashing identification macros, and the merge sidesteps a quoted `-DLT_CONFIG_H` that does not survive the pipeline. Verified against the libtool build of the same tarball: identical IM002 error path and identical `lt_dlopen` behaviour) | +| C-source compat, an ISA tier turned off through the GENERATED config | [`compat.libwebp`](../pkgs/c/compat.libwebp.lua) (117 TUs as five directory globs rather than a transcribed file list, and one real decision. libwebp's SSE4.1 gate is `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`, and `WEBP_MSC_SSE41` keys off `_MSC_VER` **alone** — which every MSVC-ABI compiler defines, including clang, while only cl.exe lets an intrinsic be used without a target flag. Under clang the SSE4.1 sources fail on `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`. Upstream answers with a PER-FILE `-msse4.1`, which no descriptor field expresses; adding it package-wide would let clang emit SSE4.1 in the BASELINE TUs too, past libwebp's own runtime dispatch — a SIGILL rather than a fallback. So the package uses the other half of upstream's own mechanism: `HAVE_CONFIG_H` plus a generated `src/webp/config.h` naming SSE2 and NEON and not SSE4.1, which turns `dec_sse41.c` and friends into upstream's `WEBP_DSP_INIT_STUB` and removes the matching `VP8DspInitSSE41()` call. `src/demux` and `src/mux` are separate upstream libraries with their own public headers and stay out until something asks for them) | +| header-only (with `features`) | [`compat.eigen`](../pkgs/c/compat.eigen.lua) | +| header-only, nothing to gate | [`compat.CLI11`](../pkgs/c/compat.CLI11.lua) (a command line parser whose every definition is `CLI11_INLINE`, so the package is `*/include` plus an anchor TU. Upstream's two extras stay out: `src/Precompile.cpp` only means anything when `CLI11_COMPILE` also reaches the CONSUMER's translation units — an interface define, not a sources-only gate — and `src/modules/CLI11.cppm` is a module layer, which is a package shape of its own rather than a feature of the compat package) · [`compat.gtl`](../pkgs/c/compat.gtl.lua) (Greg's Template Library — the Swiss-table `flat_hash_map` family plus btrees and a bit_vector. `*/include` exactly, not the tarball root: `tests/` and `examples/` carry headers of their own, and naming `include/` is what upstream's INTERFACE target exposes, so a consumer cannot accidentally resolve into test code) · [`compat.plf-hive`](../pkgs/c/compat.plf-hive.lua) (the reference implementation of the proposed `std::hive`; the whole library is one file at the tarball root, so `*` plus an anchor TU is the entire package. Untagged upstream, so the version is a DATE over a commit archive — the compat.khrplatform precedent) · [`compat.wil`](../pkgs/c/compat.wil.lua) (the Windows Implementation Library — RAII over Win32 handles, COM pointers and HRESULT. Windows-ONLY in an unusual sense: not a portable library with a Windows backend, but a library ABOUT Win32, so there are no other platform sections to declare and consumers gate the dependency with `[target.'cfg(windows)'.dependencies]` — compat.x11 and the gui-stack member in the other direction. Nothing is pre-configured: WIL's knobs (`WIL_ENABLE_EXCEPTIONS`, `RESULT_DIAGNOSTICS_LEVEL`, `WIL_USE_STL`) are macros the CONSUMER defines before including, and a header-only package has no compiled artifact for such a choice to be baked into anyway — pre-setting one would pick an error model on its consumers' behalf) | +| single-header library + a GENERATED implementation TU | [`compat.nanosvg`](../pkgs/c/compat.nanosvg.lua) (two stb-style headers where the implementation hides behind `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION`. Upstream ships no `.c` — its examples define the macros inline — so the package generates one that instantiates BOTH halves once. That is what turns a header drop into something linkable, and it moves the duplicate-symbol hazard from every consumer to a single place: consumers must NOT define those macros again, and the test links `nsvgParse` and `nsvgRasterize` together precisely so a package that instantiated only one half fails here rather than downstream) · [`compat.vulkan-memory-allocator`](../pkgs/c/compat.vulkan-memory-allocator.lua) (VMA 3.4.0, same shape but the generated TU also has to make a POLICY choice. VMA defaults to `VMA_STATIC_VULKAN_FUNCTIONS 1`, which references `vkBindBufferMemory2` and seven siblings by name — eight undefined symbols against a headers-only dep. Pulling `compat.vulkan` to satisfy them would force a Vulkan loader on every consumer of a memory allocator and fight anyone dispatching through volk, so the generated TU selects the dynamic path instead and VMA resolves everything through `VmaVulkanFunctions`. Note the implementation is C++ despite the C-shaped API, so the generated file is `.cpp`) | +| Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](../pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](../pkgs/c/compat.vulkan-headers.lua) | +| Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](../pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](../pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) · [`compat.c-ares`](../pkgs/c/compat.c-ares.lua) (91 TUs; the release tarball already ships `ares_build.h` and a Windows config, so only `ares_config.h` is snapshotted per OS) · [`compat.msdfgen`](../pkgs/c/compat.msdfgen.lua) (msdfgen 1.13 — the config is not optional here: `core/base.h` opens with `#include `, so without generating it nothing compiles, not even `core/`. Generating it rather than passing `-D` flags is also what makes the library and its consumers agree BY CONSTRUCTION — `base.h` is reached from every public header, so the file is the single place that says which of SVG/PNG/Skia exist. Of the four `ext/` units only `import-font.cpp` is built; the other three each need a library this index does not carry, and their declarations disappear through the same generated config. `MSDFGEN_USE_CPP11` is left off on purpose: it adds move constructors to `Bitmap`, so it changes the layout of a type that crosses the library boundary, and a package cannot guarantee every consumer defines it identically) | +| Upstream amalgamation (one TU is the whole library) | [`compat.harfbuzz`](../pkgs/c/compat.harfbuzz.lua) (HarfBuzz 14.3.0 — upstream builds with meson, and reproducing that here would mean tracking ~137 `.cc` files plus a generated config. `src/harfbuzz.cc` is upstream's own supported "compile one file" path, so `sources` is a single line that cannot drift out of sync with a release. The amalgamation also `#include`s the CoreText/DirectWrite/GDI/GLib/Graphite2 backends, each behind its own `HAVE_*` gate, so naming only `HAVE_FREETYPE` selects the FreeType bridge and compiles the rest to nothing. `HB_NO_MT` is deliberately NOT set: it removes HarfBuzz's atomics, which is only sound under a single-threading promise a shared package cannot make for its consumers) · [`compat.mimalloc`](../pkgs/c/compat.mimalloc.lua) (mimalloc 3.4.5 — the opposite lesson: it also ships an amalgamation (`src/static.c`), and using it would be wrong. A `src/*.c` glob is wrong three ways, each a LINK error rather than a compile error — `static.c` duplicates every symbol, and `free.c`/`alloc-override.c` are `#include`d by `alloc.c` rather than being TUs — so the source list is upstream's own `mi_sources`. `MI_MALLOC_OVERRIDE` stays off: a dependency silently taking over the process allocator is not a package's call) · [`compat.miniaudio`](../pkgs/c/compat.miniaudio.lua) (miniaudio 0.11.25 — `miniaudio.c` is upstream's own two-line `MINIAUDIO_IMPLEMENTATION` driver and its CMake library target, so `sources` is one line that tracks the release. The Linux link line is `-ldl -lpthread -lm` and deliberately NOT `-lasound`/`-lpulse`: miniaudio `dlopen`s its backends, so the package builds on a machine that has neither) · [`compat.spirv-reflect`](../pkgs/c/compat.spirv-reflect.lua) (Khronos' SPIR-V reflection library; `spirv_reflect.c` is exactly upstream's `spirv-reflect-static` target. Both `*` and `*/include` are exposed so the default `"./include/spirv/unified1/spirv.h"` and the `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` spelling resolve to the SAME bundled grammar header — a consumer that defines that macro cannot silently get a different SPIR-V revision than this `.c` was written against. Versioned by SDK line to stay in step with compat.vulkan-headers) | · [`compat.reflectcpp`](../pkgs/c/compat.reflectcpp.lua) (reflect-cpp 0.25.0 — upstream ships one umbrella TU per BACKEND, and only two of them are compiled. The other nine (avro / bson / capnproto / cbor / flexbuf / msgpack / toml / xml / yaml) each `#include` a third-party library's headers, so compiling them would turn a dependency-free package into one with nine dependencies; they belong behind features with their own `deps`. The yyjson question resolves the other way: `rfl/json/*.hpp` probes `__has_include()` and falls back to its own `include/rfl/thirdparty/yyjson.h`, so exposing only `include/` keeps the VENDORED copy in play and the package cannot disagree with `compat.yyjson` about a version. `include/rfl/thirdparty` is a second include root only because `src/yyjson.c` includes `"yyjson.h"` flat) +| Upstream codegen frozen into the mirror archive | [`compat.godot-cpp`](../pkgs/c/compat.godot-cpp.lua) (two versions: `4.5.0` = the `godot-4.5-stable` bindings, `10.0.0-rc1` = godot-cpp's own 10.x line, whose bindings target Godot 4.6. The ~1000 GDExtension classes under `gen/` exist in no upstream tag archive — upstream's `binding_generator.py` emits them at build time. Running it once offline and publishing upstream's tree byte-for-byte **plus** `gen/` keeps Python off the consumer side entirely; `tools/godot-cpp/repack.sh` reproduces the archive deterministically and refuses to publish if any upstream file differs) | +| Header package filling a gap in the index | [`compat.glx-headers`](../pkgs/c/compat.glx-headers.lua) (libglvnd's `GL/glx.h`, absent from the Khronos registry and required by SDL's X11 backend) | +| C++ application framework compat (dependencies reuse packages already in the index) | [`compat.eui-neo`](../pkgs/e/compat.eui-neo.lua) (upstream's `3rd/` ships 8 vendored dependencies; none of them is compiled here — all are redirected to the same-version `compat.*` packages in this index) | +| Mutually exclusive backends (one of several inside one package) | [`compat.eui-neo`](../pkgs/e/compat.eui-neo.lua): `vulkan` / `sdl2` each **replace** the default OpenGL / GLFW, and the default backend is expressed by *naming no feature at all* — there is no `opengl`/`glfw` feature. A `default` feature cannot express exclusivity: its own `defines`/`sources`/`deps` have no effect whatsoever, while its `implies` always applies and cannot be overridden by a named feature (which is, conversely, exactly the solution for the "always-on interface define" row below). The workable answer is to read the `-DMCPP_FEATURE_` mcpp passes anyway and decide up front in a force-included header. Note also that `cflags` only reaches C TUs — C++ needs `cxxflags`, so a backend define written only into `cflags` never reaches any `.cpp` | +| Host runtime adaptation (drivers are not vendored) | [`compat.glx-runtime`](../pkgs/c/compat.glx-runtime.lua) · [`compat.vulkan-runtime`](../pkgs/c/compat.vulkan-runtime.lua) (mcpp binaries run against a bundled glibc, so a bare-soname `dlopen` never reaches the host drivers; a symlink farm plus `runtime.library_dirs` bridges that. Note the farm holds only versioned sonames — `library_dirs` also joins the link line) | +| Always-on interface define | `CURL_STATICLIB` in [`compat.curl`](../pkgs/c/compat.curl.lua): `cflags` is always on but package-private, while a feature's `defines` reaches consumers yet has to be named — `default = { implies = … }` applies unconditionally and happens to give both | +| Multiple majors in one package (shape switches with the version) | [`compat.catch2`](../pkgs/c/compat.catch2.lua) (3.x compiles `src/catch2/` into a static library; 2.x goes header-only through `single_include/`) | +| External build system (`install()` builds from source) | [`compat.openblas`](../pkgs/c/compat.openblas.lua) (Make) · [`compat.openssl`](../pkgs/c/compat.openssl.lua) (Perl Configure + Make, static libssl/libcrypto) | +| Whole-source direct build (config snapshot + source list, no external build system) | [`compat.ffmpeg`](../pkgs/c/compat.ffmpeg.lua) (2281 TUs including NASM assembly, declared through 28 directory globs) | +| Build-time generator output vendored into the descriptor | [`compat.gmp`](../pkgs/c/compat.gmp.lua) (516 TUs, all three platforms. GMP's build COMPILES AND RUNS seven table generators and substitutes `gmp.h` from `gmp-h.in` — all of it a pure function of limb=64/nail=0, so the outputs are produced once by upstream's own generators and shipped in `generated_files` (~270 KB, of which `trialdivtab.h` is 109 KB). That is what removes the `install()` hook, autotools, and the host compiler its probes needed — and with them the reason windows was deferred, since GMP's generic C only ever needed a GCC-compatible compiler. `generated_files` also carries a one-line forwarding header per source directory, so the package compiles with **no `-I` at all** and `include_dirs` exposes `gmp.h` + `gmpxx.h` rather than GMP's private headers. Verified against a `--disable-assembly` autotools build of the same tarball: identical 598-symbol export set, and GMP's own `make check` passes 177/178 against it) | +| Module layer over a compat source build (external Form-A repo) | [`godotengine.godot-cpp-m`](../pkgs/g/godotengine.godot-cpp-m.lua) (two versions tracking upstream: `10.0.0-rc1` = Godot 4.6, `4.5.0` = Godot 4.5. `import godot_cpp;` re-exports the whole `godot` namespace, ~1800 names GENERATED from the headers rather than curated; the 1022-TU build stays in `compat.godot-cpp`, so the index carries only this descriptor. Macros — `GDCLASS`, `GDREGISTER_CLASS`, `memnew`, `ERR_*` — are the one thing a named module cannot export, so the package ships a side header to include next to the import. It also ships a generated `hashfuncs.hpp` shim — upstream's header minus `static` on two functions whose bodies declare an unnamed union — without which GCC refuses the module interface outright, a hard error no `-W` flag reaches) | +| C++23 module wrapper | [`nlohmann.json`](../pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](../pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](../pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](../pkgs/b/boost-ext.ut.lua) (upstream's own `include/boost/ut.cppm` reproduced verbatim but for one `__argc`/`__argv` shim that Clang-on-MSVC needs; namespace `boost-ext` since it is NOT an official Boost library) | diff --git a/docs/package-types.md b/docs/package-types.md index 3930a093..817afc97 100644 --- a/docs/package-types.md +++ b/docs/package-types.md @@ -20,8 +20,7 @@ combined as needed. | **G. Host runtime adaptation** | things that cannot be vendored, such as drivers — only a symlink farm plus metadata | `pkgs/c/compat.glx-runtime.lua`, `compat.vulkan-runtime.lua` | `runtime.library_dirs` / `capabilities` | | **H. Host tool provider** | the upstream tarball also holds a **code generator** consumers run at build time | `pkgs/c/compat.protobuf.lua` (`protoc`) | a `targets` entry with `kind = "bin"` + `main`, plus `required_features` | -For the complete sample index, see the -[Reference examples table in the root README](../README.md#reference-examples-lua-descriptors). +For the complete sample index, see [Descriptor examples by shape](descriptor-examples.md). Shapes A, B and C share this skeleton (the `package` header and `xpm`): diff --git a/docs/zh/README.md b/docs/zh/README.md index 3bc13324..da06fdd3 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -8,6 +8,7 @@ | 文档 | 内容 | |------|------| | [package-types.md](package-types.md) | 四种库形态(C 源码 compat、header-only、C++23 module wrapper、外部 Form-A 模块仓)的描述符模板与样例 | +| [descriptor-examples.md](descriptor-examples.md) | 本索引全部描述符的完整目录,按形态分组 —— 每个包编了什么,以及刻意没编什么 | | [cn-mirror.md](cn-mirror.md) | GitCode `mcpp-res` CN 镜像闭环(`gtc` 工具、闭环校验、注意事项),含无 `mcpp-res` 写权限时的回退方案(plain-string 上游 url) | | [repository-and-schema.md](repository-and-schema.md) | 仓库布局、描述符 schema 速查、`validate.yml` CI 行为、本地 lint 复现、案例索引 | diff --git a/docs/zh/descriptor-examples.md b/docs/zh/descriptor-examples.md new file mode 100644 index 00000000..aedf1438 --- /dev/null +++ b/docs/zh/descriptor-examples.md @@ -0,0 +1,38 @@ +# 描述符示例总览(按形态) + +[English](../descriptor-examples.md) | **简体中文** + +本索引全部 `.lua` 描述符的完整目录,按「各自解决的问题形态」分组。它的读法接近判例集:先找到与你处境相符的那一 +行,再打开对应描述符复用其推理过程。每条记录的不只是这个包编了什么,更包括它**刻意没有编什么** —— 后者通常才是 +值得照搬的部分。 + +各形态的模板见 [package-types.md](package-types.md);精简版的入门示例见[根 README](../../README.zh-CN.md#参考示例)。 + +| 形态 | 示例 | +|------|------| +| 原生模块库(Form A) | [`mcpplibs.xpkg`](../../pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](../../pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](../../pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](../../pkgs/f/ffmpeg.lua)(模块层,源码经 `compat.ffmpeg` 直编) · [`opencv`](../../pkgs/o/opencv.opencv.lua)(单仓库:模块层与 OpenCV 5 全源码构建同在包内,索引侧只留本描述符) · [`mcpplibs.grpc`](../../pkgs/g/grpc.lua)(gRPC 1.83.0 —— 本索引里唯一**无法**做成 compat 描述符的库:上游不发布任何自包含源码产物,其 tag 归档里 abseil/protobuf/re2/boringssl/zlib 全是空 submodule 占位,因此 [grpc-m](https://github.com/mcpplibs/grpc-m) 的 release tarball 才是那个产物。它只 vendor gRPC 自己的源码,五个依赖全取自本索引,故同时直接使用 protobuf 的消费者链进去的是同一份而非两份)| +| C 源码 compat(含 `features`) | [`compat.cjson`](../../pkgs/c/compat.cjson.lua) · [`compat.zlib`](../../pkgs/c/compat.zlib.lua) · [`compat.hiredis`](../../pkgs/c/compat.hiredis.lua)(经典 1.2.0 —— 7 个 C TU;tarball 平铺头经 `generated_files` 补 `hiredis/` 前缀薄包装头,消费者可写 `#include `,与上游安装布局一致) · [`compat.sqlite3`](../../pkgs/c/compat.sqlite3.lua)(纯 C 源码、无 feature:单一 `sqlite3.c` amalgamation;3.45.3,部署最广的 3.45.x 线) · [`compat.libuv`](../../pkgs/c/compat.libuv.lua)(libuv 1.48.0 —— 逐 OS 源清单转录自上游 CMakeLists,因为 `src/unix/*.c` 通配会一次编进所有 OS 的后端;linux/macos 显式列 unix 子集,windows 用 `src/win/*.c` glob) |· [`compat.xxhash`](../../pkgs/c/compat.xxhash.lua)(单 TU、单头、无 feature —— 值得说的是**没有**编译什么:`xxh_x86dispatch.c` 在运行期选择 AVX2/AVX512 路径,需要 per-file `-mavx2` 并要求每个调用点定义 `XXH_X86DISPATCH`,故本包只出无需任何 flag 的 SSE2 基线。也没有选 header-only 的 `XXH_INLINE_ALL` 模式:它会在每个做哈希的 TU 里重新展开整份实现,那只有在「恰好只有一个这样的 TU」时才划算 —— 而这件事包本身无从知道) +| C 源码 compat(库本身就是内核 ABI) | [`compat.libaio`](../../pkgs/c/compat.libaio.lua)(libaio 0.3.113 —— 12 个系统调用封装 TU,`xpm` 只有 `linux` 一段,因为根本不存在「移植」可声明:`struct iocb` 就是内核的结构体,每个 TU 都是 `syscall(__NR_io_*, …)`。消费者用 `[target.'cfg(linux)'.dependencies]` 门控,与 compat.wil 互为镜像。它给出三条经验。**把唯一的公开头从源码目录里择出来**:上游只安装 `libaio.h` 一个头,但 tarball 把它放在 `src/` 里、与私有头并列 —— 其中一个恰好叫 `syscall.h`,一旦上了 include 路径就会**遮蔽** glibc 的同名头。故 `include_dirs` 只指向一个 `generated_files` 转发头;包自身的源码经它拿到真头文件,而它们引号形式的 `#include "syscall.h"` 仍按「包含者所在目录优先」解析,于是整包**不需要任何指向 `src/` 的 `-I`**。**一个会骗人的 `c_standard`**:`-std=c11` 会定义 `__STRICT_ANSI__`,从而藏掉 `syscall()` 与 `sigset_t`,连公开头都会在 `io_pgetevents` 处解析失败;写 `c_standard = "gnu11"` **看起来**是解法,但 mcpp 2026.8.27.2 接受这个字符串却依然发 `-std=c11`(在产出的 `compile_commands.json` 里可见),真正生效的写法是 `cflags` 里的 `-D_GNU_SOURCE`。**静态包里的符号版本**:`io_getevents` / `io_cancel` 在上游并没有普通定义 —— 函数名是 `io_getevents_0_4` 之类,短名经 `.symver … @@LIBAIO_0.4` 发布 —— 链接**可执行文件**时 ld.bfd 与 lld 都能解析,但消费者若直接拿这些对象去构建 `.so` 就不行,那需要上游的 `src/libaio.map`,与上游自己的 `libaio.a` 完全同理) | +| C++ 源码 compat(彼此依赖) | [`compat.abseil`](../../pkgs/c/compat.abseil.lua)(151 TU;对 `absl/**` 取通配后,按上游自身的 test/benchmark 命名约定裁剪) · [`compat.protobuf`](../../pkgs/c/compat.protobuf.lua)(libprotobuf 运行时,79 TU 逐条转录自上游 `src/file_lists.cmake`;因 protobuf 公开头文件 include 了 `absl/…`,故显式依赖 `compat.abseil`;`gzip` feature 定义 `HAVE_ZLIB` 并拉入 `compat.zlib`,`upb` feature 则从同一个 tarball 里再编出 protobuf 的 64 TU C 运行时;还以 `kind = "bin"` target 暴露 **`protoc`**,消费者写 `tools = ["protoc"]` 即可从「自己链接的那个包」拿到为本机构建的编译器,使生成器与运行时的版本错配无法表达) · [`compat.re2`](../../pkgs/c/compat.re2.lua)(22 TU,取自上游自身的 `RE2_SOURCES`) · [`compat.redis-plus-plus`](../../pkgs/c/compat.redis-plus-plus.lua)(redis++ 1.3.13 —— 同步客户端,17 TU + `patterns/redlock.cpp`,依赖 `compat.hiredis`;CMake 唯一会生成的头 `hiredis_features.h` 用 `generated_files` 快照,async/TLS TU 不收,基座保持两包成对。`async` feature 补齐 libuv 版 `AsyncRedis` 接口(9 个 async TU + `compat.libuv`;`event_loop.cpp` 在后台线程跑 `uv_run`,`` 经 compat.hiredis 的包装头到达)。两个版本分处源码结构分水岭两侧,共享同一份源列表:1.3.13(现代 17-TU 布局)与 1.3.3(缺 `redis_uri.cpp`/`redlock` 的 15-TU 旧布局)—— 并集之所以成立,是因为 1.3.3 的 TU 是 1.3.13 的严格子集,恰好两个 glob 在 1.3.3 上零命中(仅警告,非错误;与 compat.catch2 同款手法)) · [`compat.sqlitecpp`](../../pkgs/c/compat.sqlitecpp.lua)(SQLite 的 RAII C++ 封装。上游用 **git submodule** 引 sqlite3,源码 tarball 里根本没有它,库因此无法链接 —— 依赖边指向 `compat.sqlite3` 替代了那个 submodule,而且更好:同一次链接里的两个 SQLite 消费者从此共享**一份** amalgamation,而不是各自内嵌一份带各自编译选项的副本。它的两个 CMake 开关有意不设 —— `SQLITECPP_USE_ASSERT_ON_ERRORS` 把错误模型从抛异常改成中止进程,`SQLITE_ENABLE_COLUMN_METADATA` 必须与 SQLite **自身**的构建一致;两者都该由消费者决定,而头文件本来就用 `#ifdef` 守着) | +| C 传输层 + 其上的 header-only C++ 服务端 | [`compat.usockets`](../../pkgs/c/compat.usockets.lua) · [`compat.uwebsockets`](../../pkgs/c/compat.uwebsockets.lua)(uSockets 三平台统一选 libuv 一个事件循环(经 `compat.libuv`),因为按平台各选后端只会让 `us_loop_t` 每个平台一个形状而毫无收益;SSL 与 QUIC 不收,于是基础包的唯一依赖就是那个循环。这一对真正的教训是 `LIBUS_USE_LIBUV` / `LIBUS_NO_SSL` / `UWS_NO_ZLIB` 是**接口级**事实:`libusockets.h` 会因前者改变 `us_loop_t` 的布局、因后者门控 SSL 声明,而 uWS 是 header-only —— 它的模板是在**消费者**的 TU 里实例化的。描述符的 `cflags` 只作用于包自身的 TU,所以每个消费者都必须自己声明这三个;不一致不会构建失败,而是内存损坏。usockets 的测试因此从定时器回调里写loop 附属的扩展内存再读回来 —— 布局一旦不一致,正是这条断言会断) | +| C++ 源码 compat(零依赖客户端 + 可选组件) | [`compat.websocket`](../../pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端,**基座零外部依赖**:TLS 关闭(OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),`IXWEBSOCKET_USE_ZLIB` 不定义(gzip codec 编译为 no-op)。两个可选 feature 在基座上叠加:`server`(4 个 server TU —— `IXWebSocketServer`/`IXSocketServer`/`IXHttpServer`/`IXWebSocketProxyServer`,零新增外部依赖,且 **implies `zlib`** —— 因为上游 server 默认就宣称 permessage-deflate,而 transport 的协商不受宏门控)与 `zlib`(依赖 `compat.zlib`,把 codec 变成真正的 permessage-deflate 压缩)。默认构建的测试自带基于 loopback 原始 socket 的最小 RFC 6455 echo server(握手/掩码/分片/关闭全部离线实测);第二个成员 `websocket-features` 跑真实的 `ix::WebSocketServer`,并断言压缩在线路上可观测 —— 64 KiB 重复载荷往返,`wireSize` = 80) | +| 数据库客户端 + 源码构建的驱动管理器 | [`compat.nanodbc`](../../pkgs/c/compat.nanodbc.lua)(nanodbc 2.14.0,上游已冻结 —— 单 TU 封装平台 ODBC 驱动管理器。两处修复让这份四年前的源码在此可编译、可运行:一个 force-include 的 `char_traits` 补丁头(标准留给用户的定制点,以 `_LIBCPP_VERSION` 为界,不影响 libstdc++/MSVC;注意 `-include` 只能经 `cxxflags` 到达 C++ TU,`cflags` 够不着),以及对驱动管理器本身的分平台答案 —— windows 链 SDK 的 odbc32、macOS 链系统自带的 iODBC,linux 则依赖 `compat.unixodbc`,因为 mcpp 的运行时闭包检查不接受只有宿主才有的 `libodbc.so.2`。测试断言管理器自身的诊断能穿过封装层 —— 包括 nanodbc 已冻结的、会把 SQL state 末字符截掉的 off-by-one)· [`compat.unixodbc`](../../pkgs/c/compat.unixodbc.lua)(unixODBC 2.3.14,E 叠 A 形态 —— DM + odbcinst + ini/log/lst + libltdl 静态编入单一 `odbc` 目标,与上游 libodbc.a 符号集一致,消费者不带任何 `libodbc.so.2` NEEDED。唯一非常规之处是无 libtool 的 ltdl 接线:`-DLTDLOPEN=libltdlc` 加一张生成的 `lt_libltdlc_LTX_preloaded_symbols` 表(从 libtool 目标文件的重定位记录还原)注册 dlopen loader。冻结的 `config.h` 把 ltdl 自己的 configure 输出合并进顶层(冲突宏 ltdl 源码并不读),绕开了无法在管道中幸存的带引号 `-DLT_CONFIG_H`。已与同 tarball 的 libtool 构建对比验证:IM002 错误路径与 `lt_dlopen` 行为完全一致) | +| C 源码 compat(用**生成的 config** 关掉一档 ISA) | [`compat.libwebp`](../../pkgs/c/compat.libwebp.lua)(117 个 TU 用五条目录通配写完,外加一个真实的取舍。libwebp 的 SSE4.1 门是 `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`,而 `WEBP_MSC_SSE41` **只**看 `_MSC_VER` —— 每个 MSVC ABI 编译器都定义它,clang 也不例外,但只有 cl.exe 允许不带 target flag 使用任意 intrinsic。于是 clang 下 SSE4.1 那批源码报 `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`。上游用 **per-file** `-msse4.1` 解决,而描述符没有这个字段;整包加上去,clang 就会在**基线** TU 里也发 SSE4.1,绕过 libwebp 自己的运行期分发 —— 那是 SIGILL 而不是回退。所以本包用上游同一套机制的另一半:`HAVE_CONFIG_H` + 生成的 `src/webp/config.h`,只声明 SSE2 与 NEON、不声明 SSE4.1,`dec_sse41.c` 等随之变成上游的 `WEBP_DSP_INIT_STUB`,对应的 `VP8DspInitSSE41()` 调用点也一并消失。`src/demux` / `src/mux` 是上游各自独立、各带公开头文件的库,在有人需要之前不收) | +| header-only(含 `features`) | [`compat.eigen`](../../pkgs/c/compat.eigen.lua) | +| header-only(无可门控组件) | [`compat.CLI11`](../../pkgs/c/compat.CLI11.lua)(命令行解析器,全部定义都是 `CLI11_INLINE`,故整包就是 `*/include` 加一个 anchor TU。上游两个额外件都不收:`src/Precompile.cpp` 只有在 `CLI11_COMPILE` 同时到达**消费者** TU 时才有意义 —— 那是 interface define,不是 sources 门控;`src/modules/CLI11.cppm` 属于模块层,是另一种包形态,而非 compat 包的 feature) · [`compat.gtl`](../../pkgs/c/compat.gtl.lua)(Greg's Template Library —— Swiss-table 的 `flat_hash_map` 家族,外加 btree 与 bit_vector。只取 `*/include` 而非 tarball 根:`tests/` 与 `examples/` 各自带头文件,而 `include/` 正是上游 INTERFACE target 暴露的范围,消费者不会误解析到测试代码) · [`compat.plf-hive`](../../pkgs/c/compat.plf-hive.lua)(提案中 `std::hive` 的参考实现;整库就是 tarball 根下一个文件,故全包即 `*` 加一个 anchor TU。上游不打 tag,于是版本用 commit 归档上的日期 —— 沿用 compat.khrplatform 的先例) · [`compat.wil`](../../pkgs/c/compat.wil.lua)(Windows Implementation Library —— Win32 句柄、COM 指针与 HRESULT 的 RAII 封装。它的「仅 Windows」性质与众不同:不是带 Windows 后端的可移植库,而是一个**关于** Win32 的库,因此没有别的平台段可声明,消费者用 `[target.'cfg(windows)'.dependencies]` 门控 —— 与 compat.x11 及 gui-stack 成员正好互为镜像。什么都不预设:WIL 的开关(`WIL_ENABLE_EXCEPTIONS`、`RESULT_DIAGNOSTICS_LEVEL`、`WIL_USE_STL`)都是**消费者**在 include 之前定义的宏,而 header-only 包根本没有可以把这个选择烤进去的产物 ——预设任何一个都等于替消费者选定了错误模型) | +| 单头库 + **生成**实现 TU | [`compat.nanosvg`](../../pkgs/c/compat.nanosvg.lua)(两个 stb 风格头文件,实现藏在 `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION` 之后。上游不提供 `.c` —— 其示例是就地 define 宏 —— 故本包生成一个,把两半各实例化一次。这才让「一堆头文件」变成可链接的包,并把重复符号的风险从每个消费者收敛到唯一一处:消费者不得再次定义这两个宏。测试刻意同时链接 `nsvgParse` 与 `nsvgRasterize`,使「只实例化了一半」的包在此处就失败,而不是流到下游)· [`compat.vulkan-memory-allocator`](../../pkgs/c/compat.vulkan-memory-allocator.lua)(VMA 3.4.0,同一形态,但生成的 TU 还得做一个**策略**选择。VMA 默认 `VMA_STATIC_VULKAN_FUNCTIONS 1`,会按名字引用 `vkBindBufferMemory2` 等八个符号 —— 对「只依赖头文件」而言就是八个未定义引用。为此拉入 `compat.vulkan` 是错的:那会逼所有用内存分配器的消费者都链上 Vulkan loader,并与通过 volk 自行分发的项目冲突。故生成的 TU 改走动态路径,VMA 一律经 `VmaVulkanFunctions` 解析。注意其 API 形如 C 但实现是 C++,故生成文件为 `.cpp`)| +| 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](../../pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](../../pkgs/c/compat.vulkan-headers.lua) | +| 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](../../pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](../../pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) · [`compat.c-ares`](../../pkgs/c/compat.c-ares.lua)(91 TU;release tarball 已自带 `ares_build.h` 与 Windows 配置,故只需按 OS 冻结 `ares_config.h`) · [`compat.msdfgen`](../../pkgs/c/compat.msdfgen.lua)(msdfgen 1.13 —— 这里的 config 不是可选项:`core/base.h` 开头就是 `#include `,不生成它连 `core/` 都编不了。选择生成它而非传 `-D`,还使库与消费者**天然一致** —— `base.h` 被每个公开头间接包含,于是该文件成为「SVG/PNG/Skia 哪些存在」的唯一出处。`ext/` 四个单元只编 `import-font.cpp`,其余三个各需一个本索引没有的库,其声明经由同一份生成 config 一并消失。`MSDFGEN_USE_CPP11` 刻意不开:它给 `Bitmap` 增加移动构造,即改变了跨库边界类型的布局,而包无法保证每个消费者都同样定义它) | +| 上游 amalgamation(单 TU 即整库) | [`compat.harfbuzz`](../../pkgs/c/compat.harfbuzz.lua)(HarfBuzz 14.3.0 —— 上游用 meson,在此复刻意味着跟踪 ~137 个 `.cc` 加一份生成的 config。`src/harfbuzz.cc` 正是上游自己支持的「只编一个文件」路径,于是 `sources` 只有一行,且不会与 release 脱节。该 amalgamation 同时 `#include` 了 CoreText/DirectWrite/GDI/GLib/Graphite2 各后端,每个都有自己的 `HAVE_*` 门,因此仅声明 `HAVE_FREETYPE` 即可精确选中 FreeType 桥接,其余编译为空。`HB_NO_MT` 刻意不设:它会去掉 HarfBuzz 的原子操作与锁,仅在消费者保证单线程时才成立,而共享包无法替消费者作此承诺)· [`compat.mimalloc`](../../pkgs/c/compat.mimalloc.lua)(mimalloc 3.4.5 —— 反向的教训:它同样带 amalgamation(`src/static.c`),但用它是错的。`src/*.c` 通配会在三处出错,且每一处都是**链接期**而非编译期报错 —— `static.c` 会让每个符号重复,`free.c` / `alloc-override.c` 则是被 `alloc.c` `#include` 的、并非独立 TU —— 故源列表取上游自己的 `mi_sources`。`MI_MALLOC_OVERRIDE` 保持关闭:让一个依赖悄悄接管进程分配器,不该由包来决定) · [`compat.miniaudio`](../../pkgs/c/compat.miniaudio.lua)(miniaudio 0.11.25 —— `miniaudio.c` 就是上游自己那两行 `MINIAUDIO_IMPLEMENTATION` 驱动文件,也是其 CMake 库目标,故 `sources` 只有一行且随 release 走。Linux 链接行是 `-ldl -lpthread -lm`,刻意不含 `-lasound`/`-lpulse`:miniaudio 用 `dlopen` 加载后端,因此在两者都没有的机器上依然能构建)· [`compat.spirv-reflect`](../../pkgs/c/compat.spirv-reflect.lua)(Khronos 的 SPIR-V 反射库;`spirv_reflect.c` 恰是上游 `spirv-reflect-static` 目标。同时暴露 `*` 与 `*/include`,使默认的 `"./include/spirv/unified1/spirv.h"` 与 `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` 两种写法解析到**同一份**内置语法头 —— 定义了该宏的消费者不会悄悄拿到与这份 `.c` 不匹配的 SPIR-V 修订。版本按 SDK 线号,与 compat.vulkan-headers 保持同步) · [`compat.reflectcpp`](../../pkgs/c/compat.reflectcpp.lua)(reflect-cpp 0.25.0 —— 上游按**后端**各出一个伞形 TU,这里只编两个。另外九个(avro / bson / capnproto / cbor / flexbuf / msgpack / toml / xml / yaml)各自 `#include` 一个第三方库的头文件,编了就把一个零依赖包变成九依赖包;它们该以 feature 形态各带 `deps` 单独进来。yyjson 的取舍反过来:`rfl/json/*.hpp` 用 `__has_include()` 探测,探不到就回落到自带的 `include/rfl/thirdparty/yyjson.h`,所以只暴露 `include/` 就保住了**内置副本**,本包也就不可能和 `compat.yyjson` 在版本上打架。`include/rfl/thirdparty` 之所以要作为第二个 include root,只是因为 `src/yyjson.c` 是平铺 include `"yyjson.h"` 的) | +| 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](../../pkgs/c/compat.godot-cpp.lua)(两个版本:`4.5.0` 是 `godot-4.5-stable` 的绑定,`10.0.0-rc1` 是 godot-cpp 自己的 10.x 线、对应 Godot 4.6。`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) | +| 补索引空缺的头文件包 | [`compat.glx-headers`](../../pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) | +| C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](../../pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) | +| 互斥后端(同包多后端二选一) | [`compat.eui-neo`](../../pkgs/e/compat.eui-neo.lua):`vulkan` / `sdl2` 各自**替换**默认的 OpenGL / GLFW,默认后端由"不点名任何 feature"表达,并不存在 `opengl`/`glfw` feature。`default` feature 表达不了互斥 —— 它自带的 `defines`/`sources`/`deps` 完全不生效,而 `implies` 又恒生效、无法被点名的 feature 覆盖(后者反而正好是本表『恒开的 interface define』一行的解法)。可行解是读 mcpp 本就会传的 `-DMCPP_FEATURE_`,在强制包含头里做前置判定。另注意 `cflags` 只作用于 C TU,C++ 需 `cxxflags` —— 只写进 `cflags` 的后端 define 到不了任何 `.cpp` | +| 宿主运行时适配(不 vendor 驱动) | [`compat.glx-runtime`](../../pkgs/c/compat.glx-runtime.lua) · [`compat.vulkan-runtime`](../../pkgs/c/compat.vulkan-runtime.lua)(mcpp 产物跑在自带 glibc 下,裸 soname 的 `dlopen` 够不到宿主驱动;用符号链接农场 + `runtime.library_dirs` 打通。注意 farm 只放带版本号的 soname —— `library_dirs` 同时进链接行) | +| 恒开的 interface define | [`compat.curl`](../../pkgs/c/compat.curl.lua) 的 `CURL_STATICLIB`:`cflags` 恒开但包私有,feature `defines` 可达消费端但需点名 —— `default = { implies = … }` 无条件生效,恰好两者兼得 | +| 单包多 major(形态随版本切换) | [`compat.catch2`](../../pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) | +| 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](../../pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](../../pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) | +| 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](../../pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) | +| 构建期生成器产物内联进描述符 | [`compat.gmp`](../../pkgs/c/compat.gmp.lua)(516 TU,三平台齐全。GMP 的构建要**编译并运行**七个表生成器,还要把 `gmp-h.in` substitute 成 `gmp.h` —— 这些都只是 limb=64/nail=0 的纯函数,故用上游自己的生成器跑一次,产物写进 `generated_files`(约 270 KB,其中 `trialdivtab.h` 占 109 KB)。这一步换掉的是 `install()` 钩子、autotools 以及其探针所需的宿主编译器,连带解掉 windows 推迟的理由 —— GMP 的通用 C 内核从来只要一个 GCC 兼容编译器。`generated_files` 里还按源码目录各放一个一行转发头,于是整包编译**不需要任何 `-I`**,`include_dirs` 只暴露 `gmp.h` + `gmpxx.h`,而不是 GMP 的私有头。与同一 tarball 的 `--disable-assembly` autotools 构建对拍:导出符号 598 个完全一致,上游自带 `make check` 对本产物 177/178 通过) | +| 模块层叠在 compat 源码构建之上(外部 Form-A 仓) | [`godotengine.godot-cpp-m`](../../pkgs/g/godotengine.godot-cpp-m.lua)(两个版本与上游对齐:`10.0.0-rc1` 对应 Godot 4.6,`4.5.0` 对应 Godot 4.5。`import godot_cpp;` 重导出整个 `godot` 命名空间,约 1800 个名字由头文件**生成**而非手工罗列;1022 个 TU 的构建留在 `compat.godot-cpp`,索引侧只留这一个描述符。宏 —— `GDCLASS`、`GDREGISTER_CLASS`、`memnew`、`ERR_*` —— 是具名模块唯一带不走的东西,故包内附一个与 import 并排包含的侧头文件。另外还带一份生成的 `hashfuncs.hpp` 遮蔽头 —— 上游那个头去掉两个函数的 `static`(它们体内声明了匿名 union)—— 否则 GCC 直接拒绝该模块接口,且是任何 `-W` 开关都够不到的硬错误) | +| C++23 module wrapper | [`nlohmann.json`](../../pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](../../pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](../../pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](../../pkgs/b/boost-ext.ut.lua)(逐字复用上游自带的 `include/boost/ut.cppm`,仅加一处 Clang-on-MSVC 需要的 `__argc`/`__argv` shim;命名空间取 `boost-ext`,因其并非 boost 官方库) | diff --git a/docs/zh/package-types.md b/docs/zh/package-types.md index becdcd33..1aa0f80d 100644 --- a/docs/zh/package-types.md +++ b/docs/zh/package-types.md @@ -18,7 +18,7 @@ A–D 是四种**基础**形态,先按它们判定;E–G 是在基础形态之 | **G. 宿主运行时适配** | 驱动之类无法 vendor 的东西,只做符号链接农场 + 元数据 | `pkgs/c/compat.glx-runtime.lua`、`compat.vulkan-runtime.lua` | `runtime.library_dirs` / `capabilities` | | **H. 宿主工具提供方** | 上游 tarball 里除了库,还带着消费者在构建期要跑的**代码生成器** | `pkgs/c/compat.protobuf.lua`(`protoc`) | `targets` 里一条 `kind = "bin"` + `main`,配 `required_features` | -完整的样例索引见[根 README 的「参考示例」表](../../README.zh-CN.md#参考示例lua-描述符)。 +完整的样例索引见[描述符示例总览(按形态)](descriptor-examples.md)。 A、B、C 三类共用的骨架(`package` 头与 `xpm`)如下: diff --git a/mcpp.toml b/mcpp.toml index 19fd018f..7d48a991 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -76,6 +76,7 @@ members = [ "tests/examples/spdlog", "tests/examples/freetype", "tests/examples/glad", + "tests/examples/libaio", "tests/examples/libpng", "tests/examples/libwebp", "tests/examples/llamacpp", diff --git a/pkgs/c/compat.libaio.lua b/pkgs/c/compat.libaio.lua new file mode 100644 index 00000000..a8d99775 --- /dev/null +++ b/pkgs/c/compat.libaio.lua @@ -0,0 +1,154 @@ +-- compat.libaio — libaio 0.3.113, the userspace wrapper over Linux's native +-- asynchronous I/O syscalls (io_setup / io_submit / io_getevents / io_cancel / +-- io_destroy, plus the io_prep_* helpers and the io_queue_* convenience layer). +-- +-- Shape A (C-source compat), same as compat.cjson / compat.hiredis: upstream's +-- own `libaio_srcs` list from src/Makefile compiled into one lib target. There +-- is no configure step and nothing generated — twelve small TUs and one public +-- header. +-- +-- LINUX ONLY, and not in the "portable library with a Linux backend" sense: +-- libaio IS the Linux AIO ABI. Every TU is `syscall(__NR_io_*, …)`, and the +-- header's `struct iocb` is the kernel's. There is no macOS or Windows section +-- to write, so consumers gate the dependency with +-- `[target.'cfg(linux)'.dependencies]` — compat.wil does the same in the other +-- direction. (Single-platform `xpm` is why the platform-version-parity lint +-- stays quiet here: it only compares platforms that both carry versions.) +-- +-- _GNU_SOURCE IS LOAD-BEARING, and not for a GNU extension in the sources. +-- `-std=c11` defines __STRICT_ANSI__, which turns _DEFAULT_SOURCE off, and +-- without it glibc hides two things libaio needs: stops declaring +-- `syscall()` (which every TU here reaches through `syscall.h`'s +-- `_body_io_syscall` macro), and `sigset_t` never arrives, so even the PUBLIC +-- header fails to parse at `io_pgetevents(…, sigset_t *sigmask)`. Upstream +-- never hits this because its Makefile compiles in the compiler's default gnu +-- mode. Declaring `c_standard = "gnu11"` looks like the tidier fix and is a +-- trap: mcpp 2026.8.27.2 accepts the string and still emits `-std=c11` +-- (verified in the emitted compile_commands.json), so the build fails exactly +-- as if nothing had been declared. The define is the spelling that actually +-- takes effect. +-- +-- Two further GNU-isms ride along and are fine under plain `-std=c11` because +-- gcc/clang only diagnose them under -pedantic: `syscall.h`'s named-variadic +-- macro `_body_io_syscall(sname, args...)`, and the bare `;` at file scope that +-- is all raw_syscall.c contains on every arch but ia64. +-- +-- INCLUDE LAYOUT. Upstream installs exactly one header, `libaio.h`, but in the +-- tarball it sits in `src/` next to the private ones — and one of those is +-- named `syscall.h`, which is also a glibc header. Putting `*/src` on the +-- include path would therefore SHADOW for every consumer TU. So the +-- package ships a one-line forwarding header through `generated_files` and +-- exposes only the directory holding it (the compat.gmp pattern), which is also +-- what upstream's install layout promises: `libaio.h` and nothing else. The +-- package's own TUs reach the real header through the same forwarder, and their +-- `#include "syscall.h"` / `"aio_ring.h"` resolve next to the including .c — +-- quote-form searches the includer's own directory first — so no -I into `src/` +-- is needed at all. +-- +-- WHAT IS NOT COMPILED. `src/struct_offsets.c` is upstream's build-time +-- assertion that the `iocb.u` union members line up; its own comment says "this +-- code does not end up in the compiled object files", and upstream's Makefile +-- compiles it separately from the library. `harness/` is the test suite — it +-- carries its own `main()`, and a mcpp lib target's objects all enter the +-- consumer's link eagerly, so a main() shipped in a package collides with the +-- consumer's own. Neither belongs behind a feature; there are no optional +-- COMPILABLE components here, so this package declares no `features` at all. +-- +-- SYMBOL VERSIONING is the one thing worth knowing before consuming this. +-- io_cancel.c / io_getevents.c / io_queue_wait.c define their functions under +-- versioned names (`io_getevents_0_4`) and publish the plain name through +-- `.symver … @@LIBAIO_0.4`; compat-0_1.c adds the three `@LIBAIO_0.1` aliases +-- for the pre-0.3 ABI. For an EXECUTABLE that is transparent — the default +-- (`@@`) version defines the base symbol, verified here against both ld.bfd and +-- lld — which is the case a `kind = "lib"` package is for. Building a SHARED +-- library straight out of these objects needs upstream's `src/libaio.map` +-- version script, or the link fails with `undefined version LIBAIO_0.4`; +-- that is a property of upstream's own libaio.a, not something this descriptor +-- introduces, and it is why compat-0_1.c stays in rather than being dropped: +-- removing it would not lift the restriction (the `@@` symvers alone are enough +-- to trigger it) and would make the object set differ from upstream's for no +-- gain. +-- +-- VERSION. 0.3.113 is upstream's latest release (pagure tag `libaio-0.3.113`). +-- The GLOBAL url is the release tarball from releases.pagure.org rather than a +-- git-archive of the tag: pagure regenerates archives, so their sha256 drifts, +-- while the release file is fixed bytes (hashed twice here, identical). +package = { + spec = "1", + namespace = "compat", + name = "libaio", + description = "Linux-native asynchronous I/O access library (libaio)", + licenses = {"LGPL-2.1-or-later"}, + repo = "https://pagure.io/libaio", + type = "package", + + xpm = { + linux = { + ["0.3.113"] = { + url = { + GLOBAL = "https://releases.pagure.org/libaio/libaio-0.3.113.tar.gz", + CN = "https://gitcode.com/mcpp-res/libaio/releases/download/0.3.113/libaio-0.3.113.tar.gz", + }, + sha256 = "2c44d1c5fd0d43752287c9ae1eb9c023f04ef848ea8d4aafa46e9aedb678200b", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + + -- Only the forwarding header below, so `src/`'s private headers — + -- above all its `syscall.h` — never reach a consumer's include path. + include_dirs = { "libaio-0.3.113/mcpp/include" }, + + -- _GNU_SOURCE: see the header comment — without it `syscall()` and + -- `sigset_t` are hidden under -std=c11 and nothing compiles. + -- -fPIC: matches upstream (its CFLAGS carry it for libaio.a as well as + -- the .so) and is what lets these objects link into a shared consumer. + cflags = { "-D_GNU_SOURCE", "-fPIC" }, + + -- Upstream src/Makefile's `libaio_srcs`, verbatim and in its order, + -- including its section comments. + sources = { + -- libaio provided functions + "libaio-0.3.113/src/io_queue_init.c", + "libaio-0.3.113/src/io_queue_release.c", + "libaio-0.3.113/src/io_queue_wait.c", + "libaio-0.3.113/src/io_queue_run.c", + -- real syscalls + "libaio-0.3.113/src/io_getevents.c", + "libaio-0.3.113/src/io_submit.c", + "libaio-0.3.113/src/io_cancel.c", + "libaio-0.3.113/src/io_setup.c", + "libaio-0.3.113/src/io_destroy.c", + "libaio-0.3.113/src/io_pgetevents.c", + -- internal functions + "libaio-0.3.113/src/raw_syscall.c", + -- old symbols + "libaio-0.3.113/src/compat-0_1.c", + }, + + -- `libaio.a` / `-laio`, the spelling every consumer already knows. + targets = { ["aio"] = { kind = "lib" } }, + deps = {}, + + -- The whole public surface of the package. Quote-form, so it resolves + -- against this file's own directory and lands on upstream's real + -- header two levels up — the angle-bracket trick compat.hiredis uses + -- would need `src/` on the include path, which is exactly what this + -- header exists to avoid. + generated_files = { + ["libaio-0.3.113/mcpp/include/libaio.h"] = +[[ +#pragma once +/* compat.libaio: upstream installs libaio.h alone, but the release tarball + keeps it in src/ beside the private headers -- one of which is named + syscall.h and would shadow glibc's for every consumer TU. This forwarder is + the only thing on the include path. */ +#include "../../src/libaio.h" +]], + }, + }, +} diff --git a/tests/examples/libaio/mcpp.toml b/tests/examples/libaio/mcpp.toml new file mode 100644 index 00000000..76fd8fda --- /dev/null +++ b/tests/examples/libaio/mcpp.toml @@ -0,0 +1,16 @@ +# libaio test project: drive real kernel AIO through the wrapper. +# +# libaio is not a portable library with a Linux backend -- it IS the Linux AIO +# ABI -- so the descriptor has a `linux` section and nothing else, the +# dependency is gated, and the test compiles to a no-op main() elsewhere. Same +# shape as tests/examples/wil in the other direction. +# +# Nothing here is mocked: the assertions submit iocbs to the kernel and check +# the completion events, because a package that links but mis-declares +# `struct iocb` would pass a "does it link" test and corrupt every submission. +[package] +name = "libaio-tests" +version = "0.1.0" + +[target.'cfg(linux)'.dependencies.compat] +libaio = "0.3.113" diff --git a/tests/examples/libaio/tests/aio.cpp b/tests/examples/libaio/tests/aio.cpp new file mode 100644 index 00000000..bc59f894 --- /dev/null +++ b/tests/examples/libaio/tests/aio.cpp @@ -0,0 +1,267 @@ +// Behavioral test: real kernel AIO submissions, not a link check. +// +// Three things can go wrong with this package and none of them is a missing +// symbol. +// +// 1. `struct iocb` is the KERNEL's structure, assembled by libaio.h out of +// PADDED/PADDEDptr/PADDEDul macros chosen by endianness and word size. Get +// that selection wrong and every field lands at the wrong offset — the +// library still links, io_submit still returns 1, and the kernel writes +// the wrong bytes to the wrong place. So the assertions read back what +// actually hit the file. +// +// 2. The plain names `io_getevents` and `io_cancel` do not exist as ordinary +// definitions upstream: the functions are called `io_getevents_0_4` / +// `io_cancel_0_4` and publish the short names through +// `.symver … @@LIBAIO_0.4`. Whether that survives into a consumer's link +// is a linker behavior, so both are called here on purpose. +// +// 3. libaio does NOT use errno. Every wrapper returns `-errno` directly and +// restores the caller's errno, which is the opposite of the convention +// the surrounding POSIX calls follow. The error-path assertion pins that +// down rather than assuming it. +// +// The static_asserts below are upstream's own src/struct_offsets.c, which this +// package deliberately does not compile (it is a build-time check that emits no +// library code). Restating them here keeps the check and moves it to where it +// is actually observable. + +#ifdef __linux__ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ── upstream src/struct_offsets.c, verbatim in intent ──────────────────── +// The iocb.u union overlays io_iocb_common / io_iocb_vector / io_iocb_sockaddr, +// and the kernel reads whichever the opcode implies. If the padding macros +// picked the wrong variant these three stop agreeing. +static_assert(offsetof(struct iocb, u.v.nr) == offsetof(struct iocb, u.c.nbytes), + "iocb.u.v.nr does not match the offset of iocb.u.c.nbytes"); +static_assert(offsetof(struct iocb, u.v.offset) == offsetof(struct iocb, u.c.offset), + "iocb.u.v.offset does not match the offset of iocb.u.c.offset"); +static_assert(offsetof(struct iocb, u.saddr.len) == offsetof(struct iocb, u.c.nbytes), + "iocb.u.saddr.len does not match the offset of iocb.u.c.nbytes"); + +namespace { + +constexpr std::size_t kBlock = 512; + +// Reap exactly `want` completions, or give up. 10s is far beyond what buffered +// AIO on a temp file needs; it is a deadlock guard, not a timing assumption. +int reap(io_context_t ctx, io_event* events, long want) { + timespec timeout{10, 0}; + return io_getevents(ctx, want, want, events, &timeout); +} + +struct TempFile { + char path[64] = {}; + int fd = -1; + + TempFile() { + const char* dir = std::getenv("TMPDIR"); + std::snprintf(path, sizeof path, "%s/mcpp_libaio_XXXXXX", + (dir && *dir) ? dir : "/tmp"); + fd = ::mkstemp(path); + } + ~TempFile() { + if (fd >= 0) ::close(fd); + if (path[0]) ::unlink(path); + } +}; + +// Set by the io_queue_run callback below. +struct CallbackRecord { + int calls = 0; + iocb* obj = nullptr; + long res = -1; +}; +CallbackRecord g_callback; + +void on_complete(io_context_t, iocb* cb, long res, long /*res2*/) { + g_callback.calls += 1; + g_callback.obj = cb; + g_callback.res = res; +} + +} // namespace + +int main() { + TempFile file; + assert(file.fd >= 0 && "could not create the temp file the test writes through"); + + io_context_t ctx = nullptr; + const int setup = io_setup(8, &ctx); + assert(setup == 0 && "io_setup failed; libaio returns -errno, not -1"); + assert(ctx != nullptr); + + // ── io_prep_pwrite fills the iocb the kernel will read ─────────────── + // Checked before submitting: if these are wrong the submission below can + // still "succeed" while writing something else entirely. + unsigned char out[kBlock]; + for (std::size_t i = 0; i < kBlock; ++i) out[i] = static_cast(i & 0xFF); + + iocb write_cb; + io_prep_pwrite(&write_cb, file.fd, out, kBlock, 0); + assert(write_cb.aio_lio_opcode == IO_CMD_PWRITE); + assert(write_cb.aio_fildes == file.fd); + assert(write_cb.u.c.buf == out); + assert(write_cb.u.c.nbytes == kBlock); + assert(write_cb.u.c.offset == 0); + + // ── the write actually reaches the file ────────────────────────────── + { + iocb* batch[1] = {&write_cb}; + assert(io_submit(ctx, 1, batch) == 1); + + io_event event{}; + assert(reap(ctx, &event, 1) == 1); + assert(event.obj == &write_cb); + assert(static_cast(event.res) == static_cast(kBlock)); + + unsigned char verify[kBlock] = {}; + assert(::pread(file.fd, verify, kBlock, 0) == static_cast(kBlock)); + assert(std::memcmp(verify, out, kBlock) == 0 && + "the async write did not land the bytes it reported writing"); + + // io_cancel on a request the kernel already completed must fail, and + // must fail libaio's way: a negative errno as the return value. + // Calling it at all is the point — it is one of the three functions + // that only exist under a @@LIBAIO_0.4 symbol version. + io_event discarded{}; + assert(io_cancel(ctx, &write_cb, &discarded) < 0); + } + + // ── the read path, at a non-zero offset ────────────────────────────── + { + unsigned char tail[kBlock]; + for (std::size_t i = 0; i < kBlock; ++i) tail[i] = static_cast(0xA5 ^ i); + + iocb append; + io_prep_pwrite(&append, file.fd, tail, kBlock, kBlock); + iocb* batch[1] = {&append}; + assert(io_submit(ctx, 1, batch) == 1); + io_event event{}; + assert(reap(ctx, &event, 1) == 1); + assert(static_cast(event.res) == static_cast(kBlock)); + + unsigned char in[kBlock] = {}; + iocb read_cb; + io_prep_pread(&read_cb, file.fd, in, kBlock, kBlock); + assert(read_cb.aio_lio_opcode == IO_CMD_PREAD); + assert(read_cb.u.c.offset == static_cast(kBlock)); + + iocb* rbatch[1] = {&read_cb}; + assert(io_submit(ctx, 1, rbatch) == 1); + assert(reap(ctx, &event, 1) == 1); + assert(static_cast(event.res) == static_cast(kBlock)); + assert(std::memcmp(in, tail, kBlock) == 0 && + "the async read at offset 512 returned the wrong block"); + } + + // ── a batch of two, told apart by iocb.data ────────────────────────── + // `data` is the first member of the padded struct, so a wrong PADDEDptr + // choice shows up here as a cookie that does not come back. + { + unsigned char a[kBlock], b[kBlock]; + std::memset(a, 0x11, sizeof a); + std::memset(b, 0x22, sizeof b); + + iocb first, second; + io_prep_pwrite(&first, file.fd, a, kBlock, 2 * kBlock); + io_prep_pwrite(&second, file.fd, b, kBlock, 3 * kBlock); + first.data = &first; + second.data = &second; + + iocb* batch[2] = {&first, &second}; + assert(io_submit(ctx, 2, batch) == 2); + + io_event events[2]{}; + assert(reap(ctx, events, 2) == 2); + + bool saw_first = false, saw_second = false; + for (const io_event& e : events) { + assert(static_cast(e.res) == static_cast(kBlock)); + if (e.data == &first) saw_first = true; + if (e.data == &second) saw_second = true; + } + assert(saw_first && saw_second && "iocb.data cookies did not round-trip"); + + unsigned char verify[kBlock] = {}; + assert(::pread(file.fd, verify, kBlock, 3 * kBlock) == static_cast(kBlock)); + assert(verify[0] == 0x22 && verify[kBlock - 1] == 0x22); + } + + // ── the error contract: -errno returned, caller's errno untouched ──── + { + unsigned char scratch[kBlock] = {}; + iocb bad; + io_prep_pwrite(&bad, -1, scratch, kBlock, 0); + iocb* batch[1] = {&bad}; + + errno = 0; + const int rc = io_submit(ctx, 1, batch); + assert(rc < 0 && "io_submit on a closed fd must fail"); + assert(rc == -EBADF && "libaio reports -errno as the return value"); + assert(errno == 0 && "libaio must not clobber the caller's errno"); + } + + assert(io_destroy(ctx) == 0); + + // ── the io_queue_* convenience layer and its callback ──────────────── + // A separate context, because io_queue_init owns setup/teardown. This is + // the only path that reads iocb.data as a FUNCTION POINTER, so it is the + // one place a callback that never fires is a real failure rather than a + // slow completion. + { + io_context_t qctx = nullptr; + assert(io_queue_init(8, &qctx) == 0); + + unsigned char payload[kBlock]; + std::memset(payload, 0x5A, sizeof payload); + + iocb cb; + io_prep_pwrite(&cb, file.fd, payload, kBlock, 4 * kBlock); + io_set_callback(&cb, on_complete); + assert(cb.data == reinterpret_cast(&on_complete)); + + iocb* batch[1] = {&cb}; + assert(io_submit(qctx, 1, batch) == 1); + + // io_queue_run polls with a zero timeout and returns 0 when the ring is + // empty, so it is spun until the completion shows up. + for (int i = 0; i < 1000 && g_callback.calls == 0; ++i) { + assert(io_queue_run(qctx) >= 0); + if (g_callback.calls == 0) { + timespec nap{0, 1'000'000}; // 1ms; 1000 tries = 1s ceiling + ::nanosleep(&nap, nullptr); + } + } + + assert(g_callback.calls == 1 && "io_queue_run never dispatched the callback"); + assert(g_callback.obj == &cb); + assert(g_callback.res == static_cast(kBlock)); + + unsigned char verify[kBlock] = {}; + assert(::pread(file.fd, verify, kBlock, 4 * kBlock) == static_cast(kBlock)); + assert(verify[0] == 0x5A && verify[kBlock - 1] == 0x5A); + + assert(io_queue_release(qctx) == 0); + } + + return 0; +} + +#else + +int main() { return 0; } // libaio is the Linux AIO ABI; nothing to assert here. + +#endif