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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions .agents/docs/2026-08-29-add-concurrentqueue-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# 收录 concurrentqueue 1.0.5(compat.concurrentqueue)

日期:2026-08-29 · 分支:`feat/add-concurrentqueue` · 状态:本地验证通过

## 1. 来源与形态判定

concurrentqueue 属于来源 (a):第三方上游库,上游不提供 mcpp 支持。

- 上游:<https://github.com/cameron314/concurrentqueue>(Cameron Desrochers;
即 `moodycamel::ConcurrentQueue`)。
- 最新版本:`git ls-remote --tags` 排序后最大的是 **`v1.0.5`**(本地工作副本 checkout 在
v1.0.5 之后 6 个 commit,未成 tag,不予采用;版本键用裸版本 `"1.0.5"`,下载 URL 保留
上游的 `refs/tags/v1.0.5` 拼写)。
- License:**双许可** —— 文件头声明 Simplified BSD(BSD-2-Clause)或 Boost 软件许可
(BSL-1.0)二选一,故 `licenses = {"BSD-2-Clause", "BSL-1.0"}`(compat.libarchive 已有
多值先例)。
- 源码布局:`concurrentqueue-1.0.5/` 包一层。库本体是 tarball **根**下三个头:
`concurrentqueue.h`(核心)、`blockingconcurrentqueue.h`(阻塞变体,依赖
`lightweightsemaphore.h`)、`internal/concurrentqueue_internal_debug.h`(调试头)。
另有 `c_api/`(2 个 `.cpp` + 1 个 `extern "C"` 头)、`tests/`、`benchmarks/`。

**形态 = B(header-only)**,叠加一个 source-gated 的 `c-api` feature。

## 2. 版本与下载源

`sha256 = 4d6368a27492d86011fde5ca0cf386dce7c49cd425aa3d9b063ca6ec373a6ef3`
(1567167 字节,连算两次一致)。GitHub 的 tag 归档字节稳定,可直接作 GLOBAL。

## 3. CN 镜像

本机**没有** `GITCODE_TOKEN`(gitcode 公开读 API 可用,但建仓/release 需要写权限)。
按 `docs/cn-mirror.md` 的回退方案:描述符采用**纯字符串 url**(只填上游 release),
lint 的 `check_mirror_urls.lua` 对纯字符串不施加镜像约束,CN 用户回退上游源,
镜像由维护者后续补充(`mcpp-res` 组织下尚无 `concurrentqueue` 仓)。

## 4. 实现决策

### 4.1 `include_dirs = {"*"}`:公开头就在 tarball 根

三个公开头与 LICENSE、CMakeLists 并列在归档顶层,没有 `include/` 一层可挑
(compat.gtl 的「只取 `*/include`」在这里无处安放),`*` 即上游 README 让你放进
include 路径的那个目录。anchor TU 照 compat.eigen / compat.plf-hive 先例,
给 mcpp 一个可构建的 lib 目标。

一个需要点破的名字重叠:根下的 `concurrentqueue.h`(C++ 主头)与
`c_api/concurrentqueue.h`(C API 头)**同名**。它不构成遮蔽危害,因为 include 路径上
只有归档根一个目录 —— `<concurrentqueue.h>` 永远命中前者,
`<c_api/concurrentqueue.h>` 是唯一能命中后者的拼法,两条路径互不混淆。

### 4.2 `c-api` feature:包里唯一可编译的可选组件

feature 准则(「该组件是否为额外的可编译源码」)在本包只有一个命中:`c_api/` 的两个
`.cpp`(把两个队列封装成 `moodycamel_cq_*` / `moodycamel_bcq_*` 平面 C 接口)。
默认关闭,请求后编进同一个 `concurrentqueue` lib 目标(compat.cjson 的 `utils` 先例)。

**其余一概不收**:`tests/`、`benchmarks/` 自带 `main()`,lib 目标的对象会急切进入消费者
链接,包里不能带 main(compat.libaio 同一推理);`unsupported` 一类纯头内容不存在 ——
这库没有纯头形式的可选项,因此没有「无从门控」的遗憾。

### 4.3 Windows 的 `MOODYCAMEL_STATIC`:`cxxflags` 与 feature `defines` 各管一半

`c_api/concurrentqueue.h` 自己挑 `MOODYCAMEL_EXPORT` 的拼法:未定义
`MOODYCAMEL_STATIC`/`DLL_EXPORT` 时按 **DLL 客户端**处理,全部函数声明成
`__declspec(dllimport)`。这在两边同时出错:

- 包自身的 `.cpp` 要**定义**这些函数 —— 定义一个 dllimport 函数是硬错误;
- 静态库消费者本要链接符号,却被声明成导入。

于是包级 `cxxflags = { "-DMOODYCAMEL_STATIC" }` 覆盖包自身 TU(c_api 两个 TU 全是
C++,`cflags` 到不了 —— compat.msdfgen 的教训),`c-api` feature 的
`defines = { "MOODYCAMEL_STATIC" }` 覆盖消费者 TU(Feature System v2 的接口 define)。
该宏只在 `#ifdef _WIN32` 内被读取,linux/macos 上处处定义等于无效操作,故不按平台拆分。
已核对本地产出的 `compile_commands.json`:四个 TU(两个 c_api 源、anchor、消费者测试)
都带上了 `-DMOODYCAMEL_STATIC`。

### 4.4 Linux 侧无需任何 `-D`/`-l`

`lightweightsemaphore.h` 在 Linux 走 POSIX `sem_t`(`<semaphore.h>`),不碰 futex
syscall,也就不触发 compat.libaio 踩过的 `__STRICT_ANSI__` 藏符号问题;
`_GNU_SOURCE` 在该头里只控制一个 monotonic-clock 优化分支,不开只是退化不报错。
`sem_*` 自 glibc 2.34 起在 libc 本体,本机验证未链 -lpthread 即通过。

## 5. 测试成员

- `tests/examples/concurrentqueue`(默认构建):单线程 FIFO、bulk 往返、ProducerToken、
move-only 元素、阻塞队列(空队列 `wait_dequeue_timed` 须**真的等满** 50ms 才返回
false —— 把「信号量提前 post」与「真等待」区分开的断言)、4 生产 × 4 消费的 MPMC
竞争,逐值核验「恰好收到一次」(丢失/重复/ABA 都会表现为计数 ≠1)。
- `tests/examples/concurrentqueue-c-api`(`features = ["c-api"]`):C API 的
create/enqueue/try_dequeue/size_approx/destroy 与 bcq 的 wait_dequeue 顺序性;
值以整数经 `void*` 往返,校验和必须分毫不差 —— 这正是「句柄类型装错队列」会暴露的地方。
- 依赖一律**限定拼写** `[dependencies.compat]`(eigen 成员的推理:裸名会按请求的命名空间
去公开远端索引解析,静默脱离本 checkout)。
- 两个成员都已登记进根 `mcpp.toml` 的 `[workspace].members`。

编写期自摆乌龙两处,恰好证明断言可失败:`wait_dequeue` 在 1.0.5 返回 `void`(只有
`_timed` 拼写报告成败);`enqueue_bulk` 返回 `bool`(只有 `try_dequeue_bulk` 返回数量)。

## 6. 验证结论

mcpp 用**本地已有**的 2026.8.27.1(CI 锁 2026.8.27.2,差一个 patch;宿主到
github.com 主站可达、release CDN 超时,CI 绿灯为最终裁决):

- `mcpp test -p concurrentqueue` → `test result ok`(冷启动,自干净 target/.mcpp);
- `mcpp test -p concurrentqueue-c-api` → `test result ok`(独立重编包,feature 生效);
- **负向验证**:引用 `moodycamel_cq_create` 的 TU 链接默认构建的包对象 →
`undefined reference to moodycamel_cq_create`,证明门控真实;默认构建的包对象目录里
也只有 `concurrentqueue_anchor.o`,无任何 `c_api/*.o`;
- 定位/查找:`include_dirs = {"*"}` 下 `<concurrentqueue.h>` 命中根主头,
`<c_api/concurrentqueue.h>` 命中 C API 头,同名不冲突。

## 7. 与 skill 流程的两处偏差

- **README 未改**:#277 之后描述符目录已从 README 移入 `docs/descriptor-examples.md`,
README 只留每形态一行的示例表(该表无需新增行)。本 PR 按 #277 后的现状,把条目写进
`docs/descriptor-examples.md` 与 `docs/zh/descriptor-examples.md` 的
「header-only(with features)」行。
- **mcpp 版本**:本地 2026.8.27.1 vs CI 2026.8.27.2,见第 6 节。
2 changes: 1 addition & 1 deletion docs/descriptor-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ in the [root README](../README.md#reference-examples).
| 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<unsigned char>` 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 (with `features`) | [`compat.eigen`](../pkgs/c/compat.eigen.lua) · [`compat.concurrentqueue`](../pkgs/c/compat.concurrentqueue.lua) (moodycamel's lock-free MPMC queue 1.0.5 — three public headers at the tarball root, so `*` plus an anchor TU is the package. The ONE compilable optional component, `c_api/`'s two `extern "C"` wrapper TUs, sits behind a `c-api` feature, and the Windows story is why the descriptor carries `cxxflags = {"-DMOODYCAMEL_STATIC"}` beside the feature's `defines`: the header defaults to `__declspec(dllimport)` unless told otherwise, which is wrong for both the package's own TUs (they DEFINE the functions — and `cflags` would never reach a `.cpp`) and a static-archive consumer; the macro is read only inside `#ifdef _WIN32`, so defining it everywhere is inert on linux/macos. Everything else upstream ships outside the library is either header-only (nothing to gate) or carries a `main()`/benchmarks, which a lib target cannot take — its objects enter the consumer's link eagerly) |
| 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) |
Expand Down
Loading
Loading