From b5f2572140dc99af56b86da69b3c819fa200bb7b Mon Sep 17 00:00:00 2001 From: wellwei <96378453+wellwei@users.noreply.github.com> Date: Sun, 9 Aug 2026 01:53:08 +0800 Subject: [PATCH 1/4] feat(pkg): add compat.hiredis@1.2.0 and compat.redis-plus-plus@1.3.13 --- .../2026-08-09-add-redis-plus-plus-plan.md | 84 +++++++++ README.md | 4 +- README.zh-CN.md | 4 +- mcpp.toml | 1 + pkgs/c/compat.hiredis.lua | 99 ++++++++++ pkgs/r/compat.redis-plus-plus.lua | 111 +++++++++++ tests/examples/redis-plus-plus/mcpp.toml | 17 ++ .../redis-plus-plus/tests/redis_test.cpp | 178 ++++++++++++++++++ 8 files changed, 494 insertions(+), 4 deletions(-) create mode 100644 .agents/docs/2026-08-09-add-redis-plus-plus-plan.md create mode 100644 pkgs/c/compat.hiredis.lua create mode 100644 pkgs/r/compat.redis-plus-plus.lua create mode 100644 tests/examples/redis-plus-plus/mcpp.toml create mode 100644 tests/examples/redis-plus-plus/tests/redis_test.cpp diff --git a/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md b/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md new file mode 100644 index 0000000..60c5d70 --- /dev/null +++ b/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md @@ -0,0 +1,84 @@ +# 新增 redis-plus-plus + hiredis 收录(compat 源码形态,双包) + +**日期**: 2026-08-09 +**本仓**: `mcpplibs/mcpp-index` +**目标**: +1. 收录 [`sewenew/redis-plus-plus`](https://github.com/sewenew/redis-plus-plus) **1.3.13** —— C++ Redis 客户端(同步 API), + 文件 `pkgs/r/compat.redis-plus-plus.lua`。 +2. 收录其依赖 [`redis/hiredis`](https://github.com/redis/hiredis) **1.2.0** —— C 客户端,文件 `pkgs/c/compat.hiredis.lua`。 +3. 形态:两者均为 **Form A 源码 compat**;redis-plus-plus 额外带一个 Shape-E 特征 + (`hiredis_features.h` 用 `generated_files` 快照)。 +4. `tests/examples/redis-plus-plus/` 最小工程:离线迷你 RESP server 断言 PING/SET 全链路。 + +版本选型原则:**不追最新,选经典、泛用、下游依赖面最广的稳定版**。后续再按 xpm 追加多版本。 + +--- + +## 1. 版本选型(经典优先) + +| 包 | 选定 | 依据 | +|---|---|---| +| hiredis | **1.2.0**(2023-06) | Debian 12 / Ubuntu 24.04 稳定版即 1.2.0;vcpkg 长期默认;conan 收录;含 `redisEnableKeepAliveWithInterval` | +| redis-plus-plus | **1.3.13**(2024-10) | 2025 快速迭代前的长期稳定版,vcpkg/conan 长期默认;API 与最新 1.3.15 一致;源码结构与 1.3.15 **逐字节一致**(`.cpp` 集合 diff 为空) | + +SHA-256(均两次独立下载一致,无归档漂移): +- hiredis 1.2.0:`82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c` +- redis-plus-plus 1.3.13:`678a61898ed72f0c692102c7ce103a1bcae1e6ff85a4ad03e6002c1ba8fe1e08` + +## 2. 形态判定 + +- **hiredis → Form A(C 源码 compat)**:7 个 `.c`(上游 CMake `hiredis_sources` 原样)、头平铺、C99、无生成文件。 + 唯一特殊点:上游安装布局是 `/hiredis/hiredis.h`,tarball 却是平铺 → 用 `generated_files` 写两个 + **薄包装头**(`mcpp_generated/include/hiredis/hiredis.h` = `#include `),先例:compat.opengl 的 + `GL/gl.h`、compat.glx-headers 的 `X11/Xpoll.h`。必须是**尖括号**形式(跳过包装头自身目录,落到真实头); + `"..."` 形式会在包装目录里自包含。 +- **redis-plus-plus → Form A(C++ 源码 compat)+ Shape-E 特征**:17 个同步 TU(上游 CMake 核心清单 + + `patterns/redlock.cpp`);`async_*.cpp`/`event_loop.cpp`/`tls/…` 不编。CMake 唯一会生成的头 + `hiredis_features.h`(从 `.h.in` configure_file 产出,`connection.h` 会包含)用 `generated_files` 快照: + ```lua + ["mcpp_generated/sw/redis++/hiredis_features.h"] = "#define REDIS_PLUS_PLUS_HAS_redisEnableKeepAliveWithInterval\n" + ``` + 既非 header-only(需编译),也无 `.cppm`(非 Form C),上游无 mcpp 描述符(非 Form D)。 + +include 布局(镜像上游 target_include_directories): +- hiredis:`{ "*", "mcpp_generated/include" }` +- redis-plus-plus:`{ "*/src", "*/src/sw/redis++/cxx17", "*/src/sw/redis++/no_tls", "mcpp_generated" }` + +## 3. 多版本友好性 + +- hiredis 全部 1.x(1.2.0/1.3.0/1.4.1 已核实)源列表与布局一致 → 加版本 = xpm 加一行,`mcpp` 块不动。 +- redis-plus-plus **1.3.6+** 源列表与结构一致(1.3.13 vs 1.3.15 diff 为空)→ 加 1.3.14/1.3.15 = xpm 加一行。 + **1.3.6 之前是分水岭**(1.3.3 缺 `redis_uri.cpp`/`shards.cpp`/`patterns/redlock.cpp`,且无 + `hiredis_features.h`)→ 需要独立源列表,暂不支持。 + +## 4. feature 评估 + +- **tls**:需 hiredis_ssl(`ssl.c`)+ OpenSSL;`compat.openssl` 仅 linux/macos,且 openssl 是 install()-驱动。 + v1 不做,留后续(可做成 hiredis `ssl` feature + redis-plus-plus `tls` feature)。 +- **async**:需 libuv,索引中无此包,需先加 `compat.libuv`。v1 不做。 +- **coro**:依赖 async。v1 不做。 +- 负向口径:不启用时 async/tls 符号应缺失(链接期 undefined reference)。 + +## 5. CN 镜像 + +本机无 `gtc`、无 `mcpp-res` 写权限 → 按 docs/cn-mirror.md 回退,**url 用纯字符串上游 GitHub release** +(先例:compat.spdlog、tensorvia-cpu)。后续由维护者补 `{ GLOBAL, CN }` 表(sha 不变)。 + +## 6. 验证结论(已实测) + +- 本地 `mcpp test -p redis-plus-plus`(2026.8.8.4 与 CI 钉版 2026.8.8.2 各跑一遍,后者冷沙箱): + `test result ok. 1 passed; 0 failed`,测试输出 `OK: PING -> PONG, SET -> OK`。 +- 独立 clang++ 冒烟(-std=c++17 与 c++23):17/17 TU 编译通过;静态链接 hiredis 后, + 离线 ping 连接被拒场景抛 `sw::redis::IoError`(完整覆盖 URI 解析 → hiredis 连接 → 错误映射)。 +- lint 全绿:`mcpp xpkg parse`(本地与钉版)、`check_mirror_urls.lua`、`check_package_name.lua`、 + `check_cross_package_refs.lua`、`check_platform_version_parity.lua`。 + +## 7. 注意事项 / 风险 + +- Windows 腿本机无法实证:`-DNOMINMAX`(redis-plus-plus,上游 CMake 同款)、 + `-D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN -lws2_32 -lcrypt32`(hiredis,上游 CMake 同款)按先例写入, + 待 CI 三平台验证;测试用与 websocket 成员相同的跨平台 socket 抽象。 +- 测试自建迷你 RESP server(PING→+PONG、SET→+OK),无需 redis-server 进程、无网络依赖; + 带 5s 接收超时防 CI 挂起。 +- 纯字符串 url 的 GLOBAL 拉取在 CI 冷缓存下需要 GitHub 可达(与 spdlog 等成员相同条件)。 +- 后续加多版本时,只动 xpm 与消费者 member pin,`mcpp` 块与测试断言保持不变(1.3.6+ 线)。 diff --git a/README.md b/README.md index e4f0081..6780d99 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ Two kinds of packages live here: | 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) | -| 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`) | +| 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) | +| 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/r/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) | | 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) | | 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) | diff --git a/README.zh-CN.md b/README.zh-CN.md index 3a93444..37cbe08 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -35,8 +35,8 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 形态 | 示例 | |------|------| | 原生模块库(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) | -| 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`) | +| 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 `,与上游安装布局一致) | +| 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/r/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 不收,基座保持两包成对) | | 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) | | 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) | diff --git a/mcpp.toml b/mcpp.toml index 4bfe121..06d018b 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -57,6 +57,7 @@ members = [ "tests/examples/opencv-module-dnn", "tests/examples/opencv-module-unifont", "tests/examples/re2", + "tests/examples/redis-plus-plus", "tests/examples/sdl2", "tests/examples/spdlog", "tests/examples/freetype", diff --git a/pkgs/c/compat.hiredis.lua b/pkgs/c/compat.hiredis.lua new file mode 100644 index 0000000..0620ea3 --- /dev/null +++ b/pkgs/c/compat.hiredis.lua @@ -0,0 +1,99 @@ +-- compat.hiredis — hiredis 1.2.0, minimal C client for Redis. +-- +-- Shape A (C-source compat), same as compat.cjson / compat.zlib: the seven +-- .c files upstream's own CMake compiles into libhiredis, listed verbatim. +-- ssl.c (hiredis_ssl, needs OpenSSL) and test.c stay out. +-- +-- INCLUDE LAYOUT. Upstream's INSTALL layout is `/hiredis/hiredis.h` +-- and redis-plus-plus's sources spell `#include `, but the +-- release tarball keeps every header FLAT at the root. mcpp include_dirs are +-- plain globs with no rename, so this package ships two thin wrapper headers +-- through generated_files that re-include the real flat headers with the +-- ANGLE-bracket form (which skips the wrapper's own directory): +-- mcpp_generated/include/hiredis/hiredis.h -> #include +-- mcpp_generated/include/hiredis/async.h -> #include +-- Same trick as compat.opengl's mcpp_generated/include/GL/gl.h and +-- compat.glx-headers' mcpp_generated/include/X11/Xpoll.h. The real headers' +-- internal `#include "read.h"` etc. are relative, so they resolve next to the +-- real file inside the wrap dir. +-- +-- VERSION. 1.2.0 is the classic, widely-shipped release (Debian 12 / Ubuntu +-- 24.04 stable carry 1.2.0; it was vcpkg's long-standing default). Every +-- hiredis 1.x release shares this exact source list and layout, so adding +-- 1.3.x/1.4.x later is just one more xpm row — the mcpp block never changes. +-- +-- No CN mirror yet: `url` is a plain string (upstream GitHub release only), +-- the documented fallback when there is no mcpp-res write access +-- (docs/cn-mirror.md; precedent: compat.spdlog). +package = { + spec = "1", + namespace = "compat", + name = "hiredis", + description = "Minimalistic C client for Redis (static, upstream 7-TU source build)", + licenses = {"BSD-3-Clause"}, + repo = "https://github.com/redis/hiredis", + type = "package", + + xpm = { + linux = { + ["1.2.0"] = { + url = "https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz", + sha256 = "82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c", + }, + }, + macosx = { + ["1.2.0"] = { + url = "https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz", + sha256 = "82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c", + }, + }, + windows = { + ["1.2.0"] = { + url = "https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz", + sha256 = "82ad632d31ee05da13b537c124f819eb88e18851d9cb0c30ae0552084811588c", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c99", -- hiredis requires C99 + + -- `*` is the wrap-dir root (where the flat headers live; consumers and + -- the wrappers below resolve `` through it), and + -- `mcpp_generated/include` carries the `hiredis/`-prefixed wrappers. + include_dirs = { "*", "mcpp_generated/include" }, + + -- Upstream CMakeLists' `hiredis_sources`, verbatim — identical across + -- every 1.x release. + sources = { + "*/alloc.c", + "*/async.c", + "*/hiredis.c", + "*/net.c", + "*/read.c", + "*/sds.c", + "*/sockcompat.c", + }, + + targets = { ["hiredis"] = { kind = "lib" } }, + deps = {}, + + -- Thin `hiredis/`-prefixed wrappers over the flat tarball headers, so + -- consumers write `#include ` exactly like + -- upstream's installed layout. Angle brackets are deliberate: they + -- skip the wrapper's own directory and land on the real headers via + -- the `*` include dir (the flat `"..."` form would self-include). + generated_files = { + ["mcpp_generated/include/hiredis/hiredis.h"] = "#pragma once\n#include \n", + ["mcpp_generated/include/hiredis/async.h"] = "#pragma once\n#include \n", + }, + + windows = { + -- Upstream CMake adds these on WIN32 (same pattern as compat.c-ares). + cflags = { "-D_CRT_SECURE_NO_WARNINGS", "-DWIN32_LEAN_AND_MEAN" }, + ldflags = { "-lws2_32", "-lcrypt32" }, -- propagated to consumers + }, + }, +} diff --git a/pkgs/r/compat.redis-plus-plus.lua b/pkgs/r/compat.redis-plus-plus.lua new file mode 100644 index 0000000..5bc3afc --- /dev/null +++ b/pkgs/r/compat.redis-plus-plus.lua @@ -0,0 +1,111 @@ +-- compat.redis-plus-plus — redis-plus-plus 1.3.13, C++ client for Redis. +-- +-- Shape A (C++-source compat) with one small Shape-E trait: the single header +-- upstream's CMake would generate (hiredis_features.h) is snapshotted through +-- generated_files — exactly like compat.curl's curl_config.h. The base build +-- is the SYNC client only (17 TUs from upstream CMake's +-- `REDIS_PLUS_PLUS_SOURCES` plus patterns/redlock.cpp); async (libuv) and TLS +-- (OpenSSL) TUs are not compiled, so this package has exactly one dependency: +-- compat.hiredis. +-- +-- INCLUDE LAYOUT mirrors upstream's own target_include_directories: +-- */src -> `#include ` +-- */src/sw/redis++/cxx17 -> `sw/redis++/cxx_utils.h` (the C++17 variant, +-- selected because mcpp compiles C++23) +-- */src/sw/redis++/no_tls -> `sw/redis++/tls.h` (upstream's no-op TLS stub) +-- mcpp_generated -> the generated hiredis_features.h +-- The compat.hiredis dependency's include dirs propagate, so +-- `` resolves through its `hiredis/` wrapper headers. +-- +-- generated_files snapshot: connection.h includes +-- `sw/redis++/hiredis_features.h`, which upstream produces via +-- configure_file(hiredis_features.h.in). hiredis 1.2.0 HAS +-- redisEnableKeepAliveWithInterval, so the define is on. +-- +-- VERSION. 1.3.13 is the classic stable release (2024-10), the long-standing +-- vcpkg/conan default before the 2025 releases. 1.3.6+ share this exact source +-- list and structure (diffed against 1.3.15: identical .cpp set), so adding +-- 1.3.14/1.3.15 later is just one more xpm row. Versions before 1.3.6 differ +-- (no redis_uri.cpp / shards.cpp / redlock.cpp, no hiredis_features.h) and +-- would need their own source list. +-- +-- No CN mirror yet: plain-string upstream URL (see compat.hiredis). +package = { + spec = "1", + namespace = "compat", + name = "redis-plus-plus", + description = "C++ client for Redis (sync API; built from source, depends on compat.hiredis)", + licenses = {"Apache-2.0"}, + repo = "https://github.com/sewenew/redis-plus-plus", + type = "package", + + xpm = { + linux = { + ["1.3.13"] = { + url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.13.tar.gz", + sha256 = "678a61898ed72f0c692102c7ce103a1bcae1e6ff85a4ad03e6002c1ba8fe1e08", + }, + }, + macosx = { + ["1.3.13"] = { + url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.13.tar.gz", + sha256 = "678a61898ed72f0c692102c7ce103a1bcae1e6ff85a4ad03e6002c1ba8fe1e08", + }, + }, + windows = { + ["1.3.13"] = { + url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.13.tar.gz", + sha256 = "678a61898ed72f0c692102c7ce103a1bcae1e6ff85a4ad03e6002c1ba8fe1e08", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + + include_dirs = { + "*/src", + "*/src/sw/redis++/cxx17", + "*/src/sw/redis++/no_tls", + "mcpp_generated", + }, + + -- Upstream CMake `REDIS_PLUS_PLUS_SOURCES` (sync core) + + -- patterns/redlock.cpp. async_*.cpp / event_loop.cpp / + -- tls/sw/redis++/tls.cpp are intentionally absent — they need + -- libuv / OpenSSL, which this base build does not pull in. + sources = { + "*/src/sw/redis++/command.cpp", + "*/src/sw/redis++/command_options.cpp", + "*/src/sw/redis++/connection.cpp", + "*/src/sw/redis++/connection_pool.cpp", + "*/src/sw/redis++/crc16.cpp", + "*/src/sw/redis++/errors.cpp", + "*/src/sw/redis++/pipeline.cpp", + "*/src/sw/redis++/redis.cpp", + "*/src/sw/redis++/redis_cluster.cpp", + "*/src/sw/redis++/redis_uri.cpp", + "*/src/sw/redis++/reply.cpp", + "*/src/sw/redis++/sentinel.cpp", + "*/src/sw/redis++/shards.cpp", + "*/src/sw/redis++/shards_pool.cpp", + "*/src/sw/redis++/subscriber.cpp", + "*/src/sw/redis++/transaction.cpp", + "*/src/sw/redis++/patterns/redlock.cpp", + }, + + targets = { ["redis_plus_plus"] = { kind = "lib" } }, + deps = { ["compat.hiredis"] = "1.2.0" }, + + generated_files = { + ["mcpp_generated/sw/redis++/hiredis_features.h"] = "#define REDIS_PLUS_PLUS_HAS_redisEnableKeepAliveWithInterval\n", + }, + + windows = { + -- Upstream CMake adds NOMINMAX to its static-lib target on WIN32; + -- ws2_32 arrives through compat.hiredis' propagated ldflags. + cxxflags = { "-DNOMINMAX" }, + }, + }, +} diff --git a/tests/examples/redis-plus-plus/mcpp.toml b/tests/examples/redis-plus-plus/mcpp.toml new file mode 100644 index 0000000..7554824 --- /dev/null +++ b/tests/examples/redis-plus-plus/mcpp.toml @@ -0,0 +1,17 @@ +# redis-plus-plus test project: consumes compat.redis-plus-plus (and through it +# compat.hiredis) and asserts behavior under `mcpp test`. Part of the +# mcpp-index self-referential workspace — the workspace-root `[indices]` +# redirect points at this repo, so the dependency resolves to the checked-in +# recipe (pkgs/r/compat.redis-plus-plus.lua). +# +# The test is entirely OFFLINE and self-contained: it runs a minimal RESP +# PING/SET server inside the test process (raw loopback sockets, same pattern +# as the websocket member) and drives the sw::redis::Redis sync client against +# it — a full protocol round trip with no redis-server binary and no network +# access. +[package] +name = "redis-plus-plus-tests" +version = "0.1.0" + +[dependencies.compat] +redis-plus-plus = "1.3.13" diff --git a/tests/examples/redis-plus-plus/tests/redis_test.cpp b/tests/examples/redis-plus-plus/tests/redis_test.cpp new file mode 100644 index 0000000..5683691 --- /dev/null +++ b/tests/examples/redis-plus-plus/tests/redis_test.cpp @@ -0,0 +1,178 @@ +// redis_test.cpp — offline behavioral test for compat.redis-plus-plus. +// +// No redis-server binary and no network access: this test runs a minimal RESP +// server inside the process (raw loopback sockets, same platform abstraction +// as the websocket member) and drives the sw::redis::Redis sync client against +// it. That exercises the full stack the package must get right: +// * both static libs actually link (redis++ + hiredis), +// * hiredis connects and speaks the RESP protocol (PING -> +PONG), +// * redis++ formats commands (SET -> +OK) and parses replies back to C++. +#include + +#include +#include +#include +#include +#include +#include +#include + +// ── Platform socket abstraction (same as tests/examples/websocket) ───────── + +#ifdef _WIN32 +#include +#include +using SockType = SOCKET; +constexpr SockType kInvalidSocket = INVALID_SOCKET; +using AddrLenType = int; +#else +#include +#include +#include +#include +#include +using SockType = int; +constexpr SockType kInvalidSocket = -1; +using AddrLenType = socklen_t; +#endif + +void init_sockets() +{ +#ifdef _WIN32 + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); +#endif +} + +void close_socket(SockType s) +{ +#ifdef _WIN32 + closesocket(s); +#else + ::close(s); +#endif +} + +// Safety net: any read that stalls this long makes the server thread return so +// the test fails cleanly instead of hanging the process (and, on CI, a runner). +void set_recv_timeout(SockType s, int ms) +{ +#ifdef _WIN32 + DWORD t = static_cast(ms); + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&t), sizeof(t)); +#else + timeval tv{}; + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms % 1000) * 1000; + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +#endif +} + +// ── Minimal RESP server: answers PING with +PONG, SET with +OK ───────────── + +class RespServer +{ +public: + ~RespServer() + { + if (client != kInvalidSocket) close_socket(client); + if (listener != kInvalidSocket) close_socket(listener); + if (thread.joinable()) thread.join(); + } + + bool start() + { + init_sockets(); + listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener == kInvalidSocket) return false; + int one = 1; +#ifdef _WIN32 + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&one), sizeof(one)); +#else + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +#endif + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; // ephemeral + if (bind(listener, reinterpret_cast(&addr), sizeof(addr)) != 0) return false; + AddrLenType alen = sizeof(addr); + getsockname(listener, reinterpret_cast(&addr), &alen); + port = ntohs(addr.sin_port); + if (listen(listener, 1) != 0) return false; + thread = std::thread([this] { run(); }); + return true; + } + + int port = 0; + +private: + void run() + { + SockType s = accept(listener, nullptr, nullptr); + if (s == kInvalidSocket) return; + client = s; + set_recv_timeout(client, 5000); + + char buf[1024]; + int served = 0; + // The client keeps the connection open for the whole test; serve a + // handful of commands, then stop so the client's next read fails + // cleanly instead of hanging. + while (served < 8) { + const int n = static_cast(recv(client, buf, sizeof(buf) - 1, 0)); + if (n <= 0) break; + const std::string_view req(buf, static_cast(n)); + if (req.find("PING") != std::string_view::npos) { + static constexpr char kPong[] = "+PONG\r\n"; + send(client, kPong, static_cast(sizeof(kPong) - 1), 0); + ++served; + } else if (req.find("SET") != std::string_view::npos) { + static constexpr char kOk[] = "+OK\r\n"; + send(client, kOk, static_cast(sizeof(kOk) - 1), 0); + ++served; + } + } + } + + SockType listener = kInvalidSocket; + SockType client = kInvalidSocket; + std::thread thread; +}; + +int main() +{ + RespServer server; + if (!server.start()) { + std::cerr << "failed to start RESP server\n"; + return 1; + } + + sw::redis::ConnectionOptions opts; + opts.host = "127.0.0.1"; + opts.port = server.port; + opts.connect_timeout = std::chrono::milliseconds(3000); + opts.socket_timeout = std::chrono::milliseconds(3000); + + try { + sw::redis::Redis redis(opts); + + const std::string pong = redis.ping(); + if (pong != "PONG") { + std::cerr << "unexpected PING reply: '" << pong << "'\n"; + return 2; + } + + if (!redis.set("mcpp", "redis-plus-plus")) { + std::cerr << "SET did not return OK\n"; + return 3; + } + + std::cout << "OK: PING -> " << pong << ", SET -> OK\n"; + return 0; + } catch (const sw::redis::Error &e) { + std::cerr << "redis error: " << e.what() << "\n"; + return 4; + } +} From 9252d1334d2452e5436648c3b5d45ef73b8d9e42 Mon Sep 17 00:00:00 2001 From: wellwei <96378453+wellwei@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:00:18 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(pkg):=20redis-plus-plus@1.3.3=20?= =?UTF-8?q?=E2=80=94=20pre-watershed=20structure=20via=20source-list=20uni?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One descriptor now serves both sides of the 1.3.6 source-structure watershed: 1.3.13 (17 sync TUs) and 1.3.3 (15 TUs, no redis_uri.cpp/redlock.cpp, no hiredis_features.h). The union works because 1.3.3's TUs are a strict subset of 1.3.13's, so exactly two source globs match nothing there (a warning, not an error — same premise as compat.catch2's disjoint union). New member tests/examples/redis-plus-plus-v133 pins 1.3.3; both members pass the same offline RESP round-trip test under mcpp 2026.8.8.2. --- .../2026-08-09-add-redis-plus-plus-plan.md | 19 +- README.md | 2 +- README.zh-CN.md | 2 +- mcpp.toml | 1 + pkgs/r/compat.redis-plus-plus.lua | 35 +++- tests/examples/redis-plus-plus-v133/mcpp.toml | 15 ++ .../redis-plus-plus-v133/tests/redis_test.cpp | 178 ++++++++++++++++++ 7 files changed, 238 insertions(+), 14 deletions(-) create mode 100644 tests/examples/redis-plus-plus-v133/mcpp.toml create mode 100644 tests/examples/redis-plus-plus-v133/tests/redis_test.cpp diff --git a/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md b/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md index 60c5d70..606ccc6 100644 --- a/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md +++ b/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md @@ -44,12 +44,16 @@ include 布局(镜像上游 target_include_directories): - hiredis:`{ "*", "mcpp_generated/include" }` - redis-plus-plus:`{ "*/src", "*/src/sw/redis++/cxx17", "*/src/sw/redis++/no_tls", "mcpp_generated" }` -## 3. 多版本友好性 +## 3. 多版本友好性(已跨分水岭落地) - hiredis 全部 1.x(1.2.0/1.3.0/1.4.1 已核实)源列表与布局一致 → 加版本 = xpm 加一行,`mcpp` 块不动。 - redis-plus-plus **1.3.6+** 源列表与结构一致(1.3.13 vs 1.3.15 diff 为空)→ 加 1.3.14/1.3.15 = xpm 加一行。 - **1.3.6 之前是分水岭**(1.3.3 缺 `redis_uri.cpp`/`shards.cpp`/`patterns/redlock.cpp`,且无 - `hiredis_features.h`)→ 需要独立源列表,暂不支持。 +- **1.3.6 之前是分水岭,现已用「并集源列表」支持**:1.3.3 的同步核心是 15 个 TU(有 `shards.cpp`,缺 + `redis_uri.cpp` 与 `patterns/redlock.cpp`),且无 `hiredis_features.h`(生成的快照头只是不被包含)、 + `no_tls/tls.h` 是平铺的(`#include "tls.h"`,经 `*/src/sw/redis++/no_tls` include dir 命中)。因为 1.3.3 的 + TU 是 1.3.13 17-TU 列表的**严格子集**,同一份 `sources` 对两个版本都成立:1.3.3 上恰好两个 glob 零命中 + (警告而非错误)—— 与 compat.catch2 的「不相交并集」同款前提,这里更简单。复核新版本时须重查该子集关系 + (长期解:per-version build blocks,mcpp-community/mcpp#290)。 ## 4. feature 评估 @@ -66,8 +70,10 @@ include 布局(镜像上游 target_include_directories): ## 6. 验证结论(已实测) -- 本地 `mcpp test -p redis-plus-plus`(2026.8.8.4 与 CI 钉版 2026.8.8.2 各跑一遍,后者冷沙箱): - `test result ok. 1 passed; 0 failed`,测试输出 `OK: PING -> PONG, SET -> OK`。 +- 本地 `mcpp test -p redis-plus-plus`(1.3.13)与 `mcpp test -p redis-plus-plus-v133`(1.3.3) + (2026.8.8.4 与 CI 钉版 2026.8.8.2 各跑一遍,后者冷沙箱): + 均为 `test result ok. 1 passed; 0 failed`,测试输出 `OK: PING -> PONG, SET -> OK` —— 同一描述符、同一测试, + 两个分水岭两侧的版本都构建、链接、运行通过。 - 独立 clang++ 冒烟(-std=c++17 与 c++23):17/17 TU 编译通过;静态链接 hiredis 后, 离线 ping 连接被拒场景抛 `sw::redis::IoError`(完整覆盖 URI 解析 → hiredis 连接 → 错误映射)。 - lint 全绿:`mcpp xpkg parse`(本地与钉版)、`check_mirror_urls.lua`、`check_package_name.lua`、 @@ -81,4 +87,5 @@ include 布局(镜像上游 target_include_directories): - 测试自建迷你 RESP server(PING→+PONG、SET→+OK),无需 redis-server 进程、无网络依赖; 带 5s 接收超时防 CI 挂起。 - 纯字符串 url 的 GLOBAL 拉取在 CI 冷缓存下需要 GitHub 可达(与 spdlog 等成员相同条件)。 -- 后续加多版本时,只动 xpm 与消费者 member pin,`mcpp` 块与测试断言保持不变(1.3.6+ 线)。 +- 加 1.3.3 后,消费者侧有两个成员:`redis-plus-plus`(钉 1.3.13)与 `redis-plus-plus-v133`(钉 1.3.3), + 各自覆盖分水岭一侧;1.3.3 构建会产生两个零命中 glob 警告(预期,catch2 v2 同款)。 diff --git a/README.md b/README.md index 6780d99..a34db97 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Two kinds of packages live here: |------|------| | 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) | -| 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/r/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) | +| 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/r/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. 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)) | | 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) | | 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) | diff --git a/README.zh-CN.md b/README.zh-CN.md index 37cbe08..b5ce554 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -36,7 +36,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 |------|------| | 原生模块库(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 `,与上游安装布局一致) | -| 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/r/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 不收,基座保持两包成对) | +| 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/r/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 不收,基座保持两包成对。两个版本分处源码结构分水岭两侧,共享同一份源列表: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 同款手法)) | | 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) | | 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) | diff --git a/mcpp.toml b/mcpp.toml index 06d018b..054476d 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -58,6 +58,7 @@ members = [ "tests/examples/opencv-module-unifont", "tests/examples/re2", "tests/examples/redis-plus-plus", + "tests/examples/redis-plus-plus-v133", "tests/examples/sdl2", "tests/examples/spdlog", "tests/examples/freetype", diff --git a/pkgs/r/compat.redis-plus-plus.lua b/pkgs/r/compat.redis-plus-plus.lua index 5bc3afc..cc911dc 100644 --- a/pkgs/r/compat.redis-plus-plus.lua +++ b/pkgs/r/compat.redis-plus-plus.lua @@ -22,12 +22,23 @@ -- configure_file(hiredis_features.h.in). hiredis 1.2.0 HAS -- redisEnableKeepAliveWithInterval, so the define is on. -- --- VERSION. 1.3.13 is the classic stable release (2024-10), the long-standing --- vcpkg/conan default before the 2025 releases. 1.3.6+ share this exact source --- list and structure (diffed against 1.3.15: identical .cpp set), so adding --- 1.3.14/1.3.15 later is just one more xpm row. Versions before 1.3.6 differ --- (no redis_uri.cpp / shards.cpp / redlock.cpp, no hiredis_features.h) and --- would need their own source list. +-- VERSION / MULTI-VERSION UNION. Two versions today, one on each side of the +-- source-structure watershed: +-- * 1.3.13 (2024-10) — the classic stable release, long-standing vcpkg/conan +-- default before the 2025 releases; 1.3.6+ all share this exact source list +-- (diffed against 1.3.15: identical .cpp set), so 1.3.14/1.3.15 later are +-- just new xpm rows. +-- * 1.3.3 (2021-12) — the pre-watershed structure: 15 sync TUs (it lacks +-- redis_uri.cpp and patterns/redlock.cpp, both added in 1.3.6+), no +-- hiredis_features.h (connection.h never includes it, so the generated +-- header is simply unused), and its no_tls stub is FLAT (no_tls/tls.h, +-- included as "tls.h") instead of namespaced. +-- One mcpp block serves both because the two layouts are a SUBSET/SUPERSET +-- pair (same trick as compat.catch2's disjoint union, but simpler): the source +-- list below is 1.3.13's 17 TUs, and for 1.3.3 exactly two globs match nothing +-- (redis_uri.cpp, patterns/redlock.cpp) — a zero-hit glob is a warning, not an +-- error. Re-check the table when adding a version (mcpp-community/mcpp#290 is +-- the long-term per-version build blocks that removes this assumption). -- -- No CN mirror yet: plain-string upstream URL (see compat.hiredis). package = { @@ -41,18 +52,30 @@ package = { xpm = { linux = { + ["1.3.3"] = { + url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.3.tar.gz", + sha256 = "23689059b7ba50fcd8fa673c5dbb7bdb010b896b14abad5aa80a5ec48eb64f26", + }, ["1.3.13"] = { url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.13.tar.gz", sha256 = "678a61898ed72f0c692102c7ce103a1bcae1e6ff85a4ad03e6002c1ba8fe1e08", }, }, macosx = { + ["1.3.3"] = { + url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.3.tar.gz", + sha256 = "23689059b7ba50fcd8fa673c5dbb7bdb010b896b14abad5aa80a5ec48eb64f26", + }, ["1.3.13"] = { url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.13.tar.gz", sha256 = "678a61898ed72f0c692102c7ce103a1bcae1e6ff85a4ad03e6002c1ba8fe1e08", }, }, windows = { + ["1.3.3"] = { + url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.3.tar.gz", + sha256 = "23689059b7ba50fcd8fa673c5dbb7bdb010b896b14abad5aa80a5ec48eb64f26", + }, ["1.3.13"] = { url = "https://github.com/sewenew/redis-plus-plus/archive/refs/tags/1.3.13.tar.gz", sha256 = "678a61898ed72f0c692102c7ce103a1bcae1e6ff85a4ad03e6002c1ba8fe1e08", diff --git a/tests/examples/redis-plus-plus-v133/mcpp.toml b/tests/examples/redis-plus-plus-v133/mcpp.toml new file mode 100644 index 0000000..d2f1f2a --- /dev/null +++ b/tests/examples/redis-plus-plus-v133/mcpp.toml @@ -0,0 +1,15 @@ +# redis-plus-plus v1.3.3 test project — the PRE-watershed structure: 15 sync +# TUs (no redis_uri.cpp / patterns/redlock.cpp, no hiredis_features.h, flat +# no_tls/tls.h). This is the member that proves the single-package union holds: +# at 1.3.3 exactly two source globs match nothing (a warning, not an error) and +# the build must still link and pass the same offline RESP assertions as the +# 1.3.13 member (tests/examples/redis-plus-plus). +# +# The `compat` index redirect is inherited from the workspace root. +# Part of the mcpp-index self-referential workspace. +[package] +name = "redis-plus-plus-v133-tests" +version = "0.1.0" + +[dependencies.compat] +redis-plus-plus = "1.3.3" diff --git a/tests/examples/redis-plus-plus-v133/tests/redis_test.cpp b/tests/examples/redis-plus-plus-v133/tests/redis_test.cpp new file mode 100644 index 0000000..5683691 --- /dev/null +++ b/tests/examples/redis-plus-plus-v133/tests/redis_test.cpp @@ -0,0 +1,178 @@ +// redis_test.cpp — offline behavioral test for compat.redis-plus-plus. +// +// No redis-server binary and no network access: this test runs a minimal RESP +// server inside the process (raw loopback sockets, same platform abstraction +// as the websocket member) and drives the sw::redis::Redis sync client against +// it. That exercises the full stack the package must get right: +// * both static libs actually link (redis++ + hiredis), +// * hiredis connects and speaks the RESP protocol (PING -> +PONG), +// * redis++ formats commands (SET -> +OK) and parses replies back to C++. +#include + +#include +#include +#include +#include +#include +#include +#include + +// ── Platform socket abstraction (same as tests/examples/websocket) ───────── + +#ifdef _WIN32 +#include +#include +using SockType = SOCKET; +constexpr SockType kInvalidSocket = INVALID_SOCKET; +using AddrLenType = int; +#else +#include +#include +#include +#include +#include +using SockType = int; +constexpr SockType kInvalidSocket = -1; +using AddrLenType = socklen_t; +#endif + +void init_sockets() +{ +#ifdef _WIN32 + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); +#endif +} + +void close_socket(SockType s) +{ +#ifdef _WIN32 + closesocket(s); +#else + ::close(s); +#endif +} + +// Safety net: any read that stalls this long makes the server thread return so +// the test fails cleanly instead of hanging the process (and, on CI, a runner). +void set_recv_timeout(SockType s, int ms) +{ +#ifdef _WIN32 + DWORD t = static_cast(ms); + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&t), sizeof(t)); +#else + timeval tv{}; + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms % 1000) * 1000; + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +#endif +} + +// ── Minimal RESP server: answers PING with +PONG, SET with +OK ───────────── + +class RespServer +{ +public: + ~RespServer() + { + if (client != kInvalidSocket) close_socket(client); + if (listener != kInvalidSocket) close_socket(listener); + if (thread.joinable()) thread.join(); + } + + bool start() + { + init_sockets(); + listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener == kInvalidSocket) return false; + int one = 1; +#ifdef _WIN32 + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&one), sizeof(one)); +#else + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +#endif + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; // ephemeral + if (bind(listener, reinterpret_cast(&addr), sizeof(addr)) != 0) return false; + AddrLenType alen = sizeof(addr); + getsockname(listener, reinterpret_cast(&addr), &alen); + port = ntohs(addr.sin_port); + if (listen(listener, 1) != 0) return false; + thread = std::thread([this] { run(); }); + return true; + } + + int port = 0; + +private: + void run() + { + SockType s = accept(listener, nullptr, nullptr); + if (s == kInvalidSocket) return; + client = s; + set_recv_timeout(client, 5000); + + char buf[1024]; + int served = 0; + // The client keeps the connection open for the whole test; serve a + // handful of commands, then stop so the client's next read fails + // cleanly instead of hanging. + while (served < 8) { + const int n = static_cast(recv(client, buf, sizeof(buf) - 1, 0)); + if (n <= 0) break; + const std::string_view req(buf, static_cast(n)); + if (req.find("PING") != std::string_view::npos) { + static constexpr char kPong[] = "+PONG\r\n"; + send(client, kPong, static_cast(sizeof(kPong) - 1), 0); + ++served; + } else if (req.find("SET") != std::string_view::npos) { + static constexpr char kOk[] = "+OK\r\n"; + send(client, kOk, static_cast(sizeof(kOk) - 1), 0); + ++served; + } + } + } + + SockType listener = kInvalidSocket; + SockType client = kInvalidSocket; + std::thread thread; +}; + +int main() +{ + RespServer server; + if (!server.start()) { + std::cerr << "failed to start RESP server\n"; + return 1; + } + + sw::redis::ConnectionOptions opts; + opts.host = "127.0.0.1"; + opts.port = server.port; + opts.connect_timeout = std::chrono::milliseconds(3000); + opts.socket_timeout = std::chrono::milliseconds(3000); + + try { + sw::redis::Redis redis(opts); + + const std::string pong = redis.ping(); + if (pong != "PONG") { + std::cerr << "unexpected PING reply: '" << pong << "'\n"; + return 2; + } + + if (!redis.set("mcpp", "redis-plus-plus")) { + std::cerr << "SET did not return OK\n"; + return 3; + } + + std::cout << "OK: PING -> " << pong << ", SET -> OK\n"; + return 0; + } catch (const sw::redis::Error &e) { + std::cerr << "redis error: " << e.what() << "\n"; + return 4; + } +} From 2191099f0d92d3035dbb5ac366a835af753a9198 Mon Sep 17 00:00:00 2001 From: wellwei <96378453+wellwei@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:10:13 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(pkg):=20force-include=20=20for?= =?UTF-8?q?=20redis-plus-plus@1.3.3=20=E2=80=94=20'uint16=5Ft'=20under=20v?= =?UTF-8?q?endored=20gcc/libstdc++?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.3.3's utils.h declares uint16_t crc16(...) without including (upstream added it in 1.3.6+). clang's libc++ pulls it in transitively, but the linux-default (gcc, vendored libstdc++) CI leg does not: 'uint16_t' does not name a type. Package cxxflags -include cstdint fixes the package build on every leg (harmless for 1.3.13, which includes it itself), and the v133 member's [build] cxxflags covers its test TU (which includes first). Caught by CI: workspace linux default 0/1. --- .agents/docs/2026-08-09-add-redis-plus-plus-plan.md | 4 ++++ pkgs/r/compat.redis-plus-plus.lua | 9 +++++++++ tests/examples/redis-plus-plus-v133/mcpp.toml | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md b/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md index 606ccc6..b6e32a5 100644 --- a/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md +++ b/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md @@ -89,3 +89,7 @@ include 布局(镜像上游 target_include_directories): - 纯字符串 url 的 GLOBAL 拉取在 CI 冷缓存下需要 GitHub 可达(与 spdlog 等成员相同条件)。 - 加 1.3.3 后,消费者侧有两个成员:`redis-plus-plus`(钉 1.3.13)与 `redis-plus-plus-v133`(钉 1.3.3), 各自覆盖分水岭一侧;1.3.3 构建会产生两个零命中 glob 警告(预期,catch2 v2 同款)。 +- **CI 修复(PR #188 实测)**:linux-default(gcc/vendored libstdc++)腿报 + `'uint16_t' does not name a type` —— 1.3.3 的 `utils.h` 用 `uint16_t` 却未包含 `` + (1.3.6+ 才补)。包级 `cxxflags = { "-include", "cstdint" }` 修复包自身,v133 成员 + `[build] cxxflags` 修复测试 TU(先包含 redis++.h);对 1.3.13 无害。 diff --git a/pkgs/r/compat.redis-plus-plus.lua b/pkgs/r/compat.redis-plus-plus.lua index cc911dc..8b67f20 100644 --- a/pkgs/r/compat.redis-plus-plus.lua +++ b/pkgs/r/compat.redis-plus-plus.lua @@ -125,6 +125,15 @@ package = { ["mcpp_generated/sw/redis++/hiredis_features.h"] = "#define REDIS_PLUS_PLUS_HAS_redisEnableKeepAliveWithInterval\n", }, + -- 1.3.3's utils.h declares `uint16_t crc16(...)` WITHOUT including + -- (upstream added the include in 1.3.6+). clang's libc++ + -- pulls it in transitively, but the vendored gcc/libstdc++ sysroot + -- does not, so the 1.3.3 build fails with "'uint16_t' does not name a + -- type" on the linux-default (gcc) CI leg. -include cstdint forces the + -- header into every C++ TU; harmless for 1.3.13 (which includes it + -- itself). Recipe repair with CI regression evidence (PR #188). + cxxflags = { "-include", "cstdint" }, + windows = { -- Upstream CMake adds NOMINMAX to its static-lib target on WIN32; -- ws2_32 arrives through compat.hiredis' propagated ldflags. diff --git a/tests/examples/redis-plus-plus-v133/mcpp.toml b/tests/examples/redis-plus-plus-v133/mcpp.toml index d2f1f2a..40940f8 100644 --- a/tests/examples/redis-plus-plus-v133/mcpp.toml +++ b/tests/examples/redis-plus-plus-v133/mcpp.toml @@ -13,3 +13,9 @@ version = "0.1.0" [dependencies.compat] redis-plus-plus = "1.3.3" + +# 1.3.3's utils.h uses uint16_t without including (fixed upstream in +# 1.3.6+); under the vendored gcc/libstdc++ sysroot the test TU (which includes +# first) needs the same force-include the package uses. +[build] +cxxflags = ["-include", "cstdint"] From 48e74cb7bf7b16f3d64bd21dc2b3caf0212aa39b Mon Sep 17 00:00:00 2001 From: wellwei <96378453+wellwei@users.noreply.github.com> Date: Sun, 9 Aug 2026 02:18:59 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(pkg):=20move=20compat.redis-plus-plus?= =?UTF-8?q?=20to=20pkgs/c/=20=E2=80=94=20=20is=20the=20full-name=20init?= =?UTF-8?q?ial,=20not=20the=20short=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repository rule is pkgs//.lua with = initial of the FULL package name (compat.* → c, docs/repository-and-schema.md). compat.redis-plus-plus was wrongly placed in pkgs/r/ (short-name initial); the compat namespace belongs in pkgs/c/ next to compat.hiredis. Resolution is path-independent, so this is purely the directory convention — README links, the member comment and the design doc updated in lock-step. --- .agents/docs/2026-08-09-add-redis-plus-plus-plan.md | 2 +- README.md | 2 +- README.zh-CN.md | 2 +- pkgs/{r => c}/compat.redis-plus-plus.lua | 0 tests/examples/redis-plus-plus/mcpp.toml | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename pkgs/{r => c}/compat.redis-plus-plus.lua (100%) diff --git a/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md b/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md index b6e32a5..ca8fcd9 100644 --- a/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md +++ b/.agents/docs/2026-08-09-add-redis-plus-plus-plan.md @@ -4,7 +4,7 @@ **本仓**: `mcpplibs/mcpp-index` **目标**: 1. 收录 [`sewenew/redis-plus-plus`](https://github.com/sewenew/redis-plus-plus) **1.3.13** —— C++ Redis 客户端(同步 API), - 文件 `pkgs/r/compat.redis-plus-plus.lua`。 + 文件 `pkgs/c/compat.redis-plus-plus.lua`。 2. 收录其依赖 [`redis/hiredis`](https://github.com/redis/hiredis) **1.2.0** —— C 客户端,文件 `pkgs/c/compat.hiredis.lua`。 3. 形态:两者均为 **Form A 源码 compat**;redis-plus-plus 额外带一个 Shape-E 特征 (`hiredis_features.h` 用 `generated_files` 快照)。 diff --git a/README.md b/README.md index a34db97..d4bb706 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Two kinds of packages live here: |------|------| | 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) | -| 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/r/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. 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)) | +| 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. 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)) | | 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) | | 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) | diff --git a/README.zh-CN.md b/README.zh-CN.md index b5ce554..2ac39c0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -36,7 +36,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 |------|------| | 原生模块库(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 `,与上游安装布局一致) | -| 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/r/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 不收,基座保持两包成对。两个版本分处源码结构分水岭两侧,共享同一份源列表: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 同款手法)) | +| 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 不收,基座保持两包成对。两个版本分处源码结构分水岭两侧,共享同一份源列表: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 同款手法)) | | 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) | | 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) | diff --git a/pkgs/r/compat.redis-plus-plus.lua b/pkgs/c/compat.redis-plus-plus.lua similarity index 100% rename from pkgs/r/compat.redis-plus-plus.lua rename to pkgs/c/compat.redis-plus-plus.lua diff --git a/tests/examples/redis-plus-plus/mcpp.toml b/tests/examples/redis-plus-plus/mcpp.toml index 7554824..e32f77a 100644 --- a/tests/examples/redis-plus-plus/mcpp.toml +++ b/tests/examples/redis-plus-plus/mcpp.toml @@ -2,7 +2,7 @@ # compat.hiredis) and asserts behavior under `mcpp test`. Part of the # mcpp-index self-referential workspace — the workspace-root `[indices]` # redirect points at this repo, so the dependency resolves to the checked-in -# recipe (pkgs/r/compat.redis-plus-plus.lua). +# recipe (pkgs/c/compat.redis-plus-plus.lua). # # The test is entirely OFFLINE and self-contained: it runs a minimal RESP # PING/SET server inside the test process (raw loopback sockets, same pattern