Skip to content

Commit e144221

Browse files
committed
fix(libserial): libc++ 下 std::result_of 已移除 —— install() 改写为 invoke_result_t;加 CN 镜像
SerialPort.h 的公开模板 call_with_retry 用了 C++20 移除的 std::result_of。 libstdc++ 仍保留,libc++ 没有,linux llvm 腿三个 TU 全部编译失败,且消费者 TU 同样会坏。shim 改不了头内模板,_LIBCPP_ENABLE_CXX20_REMOVED_TYPE_TRAITS 又必须先于每个 TU 的第一个 libc++ 头,故由 install() 把两处改写为等价的 std::invoke_result_t<Fn, Args...> 并补 <type_traits>,匹配次数不符即安装失败。 - url 改为 { GLOBAL, CN },CN 为 mcpp-res/libserial(逐字节一致) - 本地 gcc 16.1.0 / llvm 22.1.8 / llvm 23.1.3 均 1 passed
1 parent 6600af0 commit e144221

4 files changed

Lines changed: 94 additions & 8 deletions

File tree

‎.agents/docs/2026-10-03-add-libserial-plan.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,35 @@ stdlib 头),使 shim 成为编译期断言:去掉 shim 该 TU 就编不过。
109109
包含,shim 仍是无害的(重复包含 <cstdint> 与 include_next 都安全)。
110110
- `include_dirs` 用 `*/src`(上游安装根),该目录下只有 `libserial/` 一个子目录,不会
111111
遮蔽任何系统头。
112+
113+
## 10. 维护者追加(2026-10-11):llvm 腿失败与 CN 镜像
114+
115+
**现象**:PR CI 的 `workspace (linux llvm 0/4)` 失败(`red members name their issues` 随之失败,
116+
它要求红成员登记 issue),gcc / macOS / Windows 全绿。
117+
118+
**根因**:`src/libserial/SerialPort.h` 的公开模板 `call_with_retry` 写的是
119+
`typename std::result_of<Fn(Args...)>::type`。`std::result_of` 在 C++17 弃用、C++20 移除:
120+
libstdc++ 仍保留(仅弃用告警),libc++ 在 C++20 及以上直接删掉,于是三个 TU 全部报
121+
`no type named 'result_of' in namespace 'std'`。它在公开头里,消费者 TU 同样会坏。上游 master
122+
仍是这个写法。
123+
124+
**为什么不用别的办法**:
125+
- shim(`#include_next`)只能在头文件前后加东西,改不了头文件内部的模板;
126+
- `_LIBCPP_ENABLE_CXX20_REMOVED_TYPE_TRAITS`(libc++ 22/23 仍认)必须在每个 TU 的第一个
127+
libc++ 头之前定义;描述符的 `defines` 到不了消费者,shim 也无法保证顺序;
128+
- 降低语言标准同样到不了消费者 TU。
129+
130+
**做法**:`install()` 在解包树上把两处改写为等价的 `std::invoke_result_t<Fn, Args...>`
131+
(对它实际被调用的函数指针 + 退化实参,两者结果相同),并在 `#include <memory>` 后补
132+
`<type_traits>`;每处断言匹配次数(2 / 1),上游一变就让安装失败而不是悄悄跳过。
133+
随后按 `compat.ftxui` / `taskflow.taskflow` 的方式把包装层移进安装目录,`*/src` glob 不变。
134+
工作区 `.mcpp/` 不在 CI 缓存里,故无需 `revision`。
135+
136+
**CN 镜像**:建 `mcpp-res/libserial`,release `1.0.0` 上传与 GLOBAL 同一个 tarball,
137+
`curl` 回环 200 且逐字节一致;`url` 改为 `{ GLOBAL, CN }`。
138+
139+
**验证**:`mcpp test -p tests/examples/libserial` 在 gcc 16.1.0、llvm 22.1.8、llvm 23.1.3 下
140+
均 `1 passed`(llvm 用 `--cache off` 确认 compat.libserial 由 clang 重新编译)。
141+
142+
**另**:原分支合并 main 时把 CHANGELOG 中 `openkal-llvm-runtime 0.15.4` 与
143+
`openkal-linux 0.16.1 / openkal-musl 0.20.1` 两条误删,已按 main 恢复。

‎docs/descriptor-examples.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ in the [root README](../README.md#reference-examples).
1818
| 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 <hiredis/hiredis.h>` 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) · [`compat.quickjs-ng`](../pkgs/c/compat.quickjs-ng.lua) (QuickJS-ng 0.17.0 — upstream's four engine TUs and nothing else. `quickjs-libc.c`, the optional standard library that gives scripts files, the environment, timers and child processes, is NOT compiled, so an embedder gets an engine with no host access unless it adds its own. The tarball keeps `quickjs.h` beside private headers with generic names (`list.h`, `cutils.h`), so a `generated_files` forwarder is the only include directory, as in compat.libaio) |
1919
| C-source compat whose sources are chosen by ARCHITECTURE | [`compat.wamr`](../pkgs/c/compat.wamr.lua) (WAMR 2.4.5 — the descriptor schema varies `sources` and `cflags` per OS but has no per-architecture hook, and `archs` is package metadata rather than a selector. WAMR needs both: one of `BUILD_TARGET_X86_64`/`BUILD_TARGET_AARCH64` must be defined or `wasm_runtime_common.c` compiles its whole invoke-native section away and the link fails on `invokeNative`, and the implementation of that symbol is a hand-written assembly file per architecture. Both halves are handed to the preprocessor instead: a `generated_files` config header maps `__x86_64__`/`__aarch64__` to the matching `BUILD_TARGET_*` and reaches every TU through `-include`, and a generated `.S` — `.S`, because upstream's `arch/invokeNative_*.s` are lowercase and clang assembles those WITHOUT the preprocessor, so unlike libffi's `.S` files they cannot guard themselves — `#include`s the chosen one as text. Two further notes worth copying: `-std=gnu11` arrives through `cflags` because `c_standard = "c11"` defines `__STRICT_ANSI__`, under which the bare `asm` in `platform_internal.h` is not a keyword; and the POSIX files upstream drops when WASI is off are carried unconditionally rather than `!`-excluded and re-added by the `libc-wasi` feature, because an exclusion glob is global and wins over the feature's own entry for the same file) |
2020
| 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) |
21-
| C++-source compat, Linux-only POSIX API + a consumer-visible header fix | [`compat.libserial`](../pkgs/c/compat.libserial.lua) (LibSerial 1.0.0 — `SerialPort`/`SerialStream`, three TUs from upstream's `LIBSERIAL_SOURCES`, and the only `xpm` section is `linux`: the headers open with `<termios.h>` and every TU reaches `<linux/serial.h>`, so it is gated like compat.libaio. **A header that only compiles by accident gets a shim**: `src/libserial/SerialPortConstants.h` uses `uint8_t` but includes neither `<cstdint>` nor `<stdint.h>`, so a consumer TU that opens a libserial header first fails with an undeclared-type error; a `generated_files` shim placed ahead of `*/src` includes `<cstdint>` and `#include_next`s upstream's header (the compat.yaml-cpp / compat.cpptrace pattern). The test proves it by including the libserial header first of all and asserts the rest over a real openpty() pair) |
21+
| C++-source compat, Linux-only POSIX API + consumer-visible header fixes | [`compat.libserial`](../pkgs/c/compat.libserial.lua) (LibSerial 1.0.0 — `SerialPort`/`SerialStream`, three TUs from upstream's `LIBSERIAL_SOURCES`, and the only `xpm` section is `linux`: the headers open with `<termios.h>` and every TU reaches `<linux/serial.h>`, so it is gated like compat.libaio. **A header that only compiles by accident gets a shim**: `src/libserial/SerialPortConstants.h` uses `uint8_t` but includes neither `<cstdint>` nor `<stdint.h>`, so a consumer TU that opens a libserial header first fails with an undeclared-type error; a `generated_files` shim placed ahead of `*/src` includes `<cstdint>` and `#include_next`s upstream's header (the compat.yaml-cpp / compat.cpptrace pattern). The test proves it by including the libserial header first of all and asserts the rest over a real openpty() pair. **A header a shim cannot fix is patched in `install()`**: `SerialPort.h`'s public template `call_with_retry` is spelled with `std::result_of`, removed in C++20 — libstdc++ still ships it, libc++ does not, so on an llvm toolchain the library and every consumer TU fail to compile. A shim cannot reach a template inside the header, and libc++'s `_LIBCPP_ENABLE_CXX20_REMOVED_TYPE_TRAITS` only works when defined before every TU's first libc++ header, so the hook rewrites both spellings to the equivalent `std::invoke_result_t<Fn, Args...>` and asserts the match counts) |
2222
| 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 `<hiredis/adapters/libuv.h>` 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`)
2323
| Multi-component upstream flattened into one lib | [`compat.recastnavigation`](../pkgs/c/compat.recastnavigation.lua) (Recast Navigation 1.6.0 — upstream is five inter-dependent CMake libraries; the two every consumer uses are the base and the other three are `features`, all compiled into one lib. Their dependency edges have to be rebuilt by hand, which is why `debug-utils` carries `implies = { "tilecache" }`: upstream links DetourTileCache unconditionally, and without the implication a consumer asking only for debug drawing fails at link with missing `dtTileCache*` symbols. Upstream's install puts every header flat under `include/recastnavigation/` **and** keeps both that directory and its parent on the interface include path, so both `<Recast.h>` and `<recastnavigation/Recast.h>` are legal against a real install — 26 generated forwarding headers restore the second spelling for a source-tree build. `RECASTNAVIGATION_DT_POLYREF64` and `RECASTNAVIGATION_DT_VIRTUAL_QUERYFILTER` are deliberately NOT features: they change the ABI of types crossing the library boundary, and a feature's `defines` reach only the package's own TUs) |
2424
| 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) |

0 commit comments

Comments
 (0)