diff --git a/README.md b/README.md index 54b2b64..8a43185 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ 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) · [`compat.sqlite3`](pkgs/c/compat.sqlite3.lua) (plain C-source, no features: the single `sqlite3.c` amalgamation; 3.45.3, the final maintenance release of the most widely deployed 3.45.x line) · [`compat.libuv`](pkgs/c/compat.libuv.lua) (libuv 1.48.0 — the per-OS source sets transcribed from upstream's CMakeLists, because a `src/unix/*.c` glob would compile every OS's backend at once; linux/macos get explicit unix subsets, windows globs `src/win/*.c`) | · [`compat.xxhash`](pkgs/c/compat.xxhash.lua) (one TU, one header, no features at all — the interesting decision is what is NOT compiled: `xxh_x86dispatch.c` selects an AVX2/AVX512 path at RUNTIME and needs per-file `-mavx2` plus `XXH_X86DISPATCH` at every call site, so the package ships the flagless SSE2 baseline instead. Nor is the header-only `XXH_INLINE_ALL` mode chosen: it re-emits the implementation in every TU that hashes anything, which is the right trade only when there is exactly one such TU — something a package cannot know) -| C++-source compat, one depending on the other | [`compat.abseil`](pkgs/c/compat.abseil.lua) (151 TUs; a wildcard over `absl/**` trimmed by upstream's test/benchmark naming conventions) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua) (the libprotobuf runtime, 79 TUs transcribed from upstream's own `src/file_lists.cmake`; declares `compat.abseil` as a dependency because protobuf's public headers include `absl/…`, and its `gzip` feature defines `HAVE_ZLIB` and pulls `compat.zlib`, while `upb` adds protobuf's 64-TU C runtime out of the same tarball. It also exposes **`protoc`** as a `kind = "bin"` target, so a consumer writing `tools = ["protoc"]` gets the compiler built for its own machine out of the same package it links — making a generator/runtime version mismatch inexpressible) · [`compat.re2`](pkgs/c/compat.re2.lua) (22 TUs, upstream's own `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua) (redis++ 1.3.13 — the sync client, 17 TUs + `patterns/redlock.cpp`, depends on `compat.hiredis`; the one header CMake would generate, `hiredis_features.h`, is snapshotted via `generated_files`, and the async/TLS TUs are left out so the base build stays a two-package pair. An `async` feature adds the libuv-backed `AsyncRedis` interface (the 9 async TUs + `compat.libuv`; `event_loop.cpp` runs `uv_run` on a background thread, and `` arrives through compat.hiredis' wrapper headers). Two versions, one on each side of the source-structure watershed, share this ONE source list: 1.3.13 (modern 17-TU layout) and 1.3.3 (pre-`redis_uri.cpp`/`redlock` 15-TU layout) — the union works because 1.3.3's TUs are a strict subset, so exactly two globs match nothing there (a warning, not an error; same trick as compat.catch2)) | +| C++-source compat, one depending on the other | [`compat.abseil`](pkgs/c/compat.abseil.lua) (151 TUs; a wildcard over `absl/**` trimmed by upstream's test/benchmark naming conventions) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua) (the libprotobuf runtime, 79 TUs transcribed from upstream's own `src/file_lists.cmake`; declares `compat.abseil` as a dependency because protobuf's public headers include `absl/…`, and its `gzip` feature defines `HAVE_ZLIB` and pulls `compat.zlib`, while `upb` adds protobuf's 64-TU C runtime out of the same tarball. It also exposes **`protoc`** as a `kind = "bin"` target, so a consumer writing `tools = ["protoc"]` gets the compiler built for its own machine out of the same package it links — making a generator/runtime version mismatch inexpressible) · [`compat.re2`](pkgs/c/compat.re2.lua) (22 TUs, upstream's own `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua) (redis++ 1.3.13 — the sync client, 17 TUs + `patterns/redlock.cpp`, depends on `compat.hiredis`; the one header CMake would generate, `hiredis_features.h`, is snapshotted via `generated_files`, and the async/TLS TUs are left out so the base build stays a two-package pair. An `async` feature adds the libuv-backed `AsyncRedis` interface (the 9 async TUs + `compat.libuv`; `event_loop.cpp` runs `uv_run` on a background thread, and `` arrives through compat.hiredis' wrapper headers). Two versions, one on each side of the source-structure watershed, share this ONE source list: 1.3.13 (modern 17-TU layout) and 1.3.3 (pre-`redis_uri.cpp`/`redlock` 15-TU layout) — the union works because 1.3.3's TUs are a strict subset, so exactly two globs match nothing there (a warning, not an error; same trick as compat.catch2)) | · [`compat.sqlitecpp`](pkgs/c/compat.sqlitecpp.lua) (the RAII C++ wrapper over SQLite. Upstream vendors sqlite3 as a GIT SUBMODULE, so a source tarball simply does not contain it and the library cannot link — the dependency edge on `compat.sqlite3` replaces the submodule, and does it better: two consumers of SQLite in one link now share ONE amalgamation instead of each embedding a private copy with its own compile-time options. Its two CMake knobs are deliberately not set — `SQLITECPP_USE_ASSERT_ON_ERRORS` changes the error model from throwing to aborting, and `SQLITE_ENABLE_COLUMN_METADATA` has to agree with how SQLite ITSELF was built; both are the consumer's call, and the headers already guard them with `#ifdef`) +| C transport + the header-only C++ server on top of it | [`compat.usockets`](pkgs/c/compat.usockets.lua) · [`compat.uwebsockets`](pkgs/c/compat.uwebsockets.lua) (uSockets picks ONE event loop for all three platforms — libuv, via `compat.libuv` — because the alternative makes `us_loop_t` a different struct per platform for no gain; SSL and QUIC are left out so the base package's only dependency is that loop. The pair's real lesson is that `LIBUS_USE_LIBUV` / `LIBUS_NO_SSL` / `UWS_NO_ZLIB` are INTERFACE facts: `libusockets.h` changes the layout of `us_loop_t` under the first and gates its SSL declarations on the second, and uWS is header-only so its templates are instantiated in the CONSUMER's translation unit. An index descriptor's `cflags` reach only the package's own TUs, so every consumer must declare all three — a mismatch does not fail to build, it corrupts. The usockets test therefore writes to loop-attached extension memory from a timer callback and reads it back, which is exactly the assertion a layout disagreement breaks) | | C++-source compat, zero-dep client + optional components | [`compat.websocket`](pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs, so the **base build has zero external dependencies**: TLS off (the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and `IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op. Two optional features add on top: `server` (the four server TUs — `IXWebSocketServer`, `IXSocketServer`, `IXHttpServer`, `IXWebSocketProxyServer` — needing nothing external, and it **implies `zlib`** because upstream's server advertises permessage-deflate by default, which the transport negotiates regardless of the define) and `zlib` (deps `compat.zlib` and turns the codec into real per-message-deflate compression). The default-feature test brings its own minimal RFC 6455 echo server on loopback sockets (handshake, masking, fragmentation and close all exercised offline); a second member, `websocket-features`, runs a real `ix::WebSocketServer` and asserts the compression is observable on the wire — a 64 KiB repeated payload round-trips with `wireSize` = 80) | | Database client + the driver manager it needs, built from source | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua) (nanodbc 2.14.0, frozen upstream — one TU over the platform ODBC driver manager. Two fixes make the four-year-old source compile and RUN here: a force-included `char_traits` shim for libc++ (the standard's own customization point, guarded on `_LIBCPP_VERSION` so libstdc++/MSVC are untouched; and note `-include` reaches C++ TUs only through `cxxflags`, never `cflags`), and a per-platform answer to the manager itself — windows links the SDK's odbc32, macOS the OS's iODBC, while linux takes `compat.unixodbc` because mcpp's runtime closure rejects a NEEDED `libodbc.so.2` that only the host has. The test asserts the manager's own diagnostics surface through the wrapper — including nanodbc's frozen off-by-one that drops the last SQL-state character) · [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua) (unixODBC 2.3.14, Shape E over A — DM + odbcinst + ini/log/lst + libltdl compiled statically into one `odbc` target, exactly upstream's libodbc.a symbol set, so the consumer carries no `libodbc.so.2` NEEDED at all. The one non-obvious piece is libtool-free ltdl wiring: `-DLTDLOPEN=libltdlc` plus a generated `lt_libltdlc_LTX_preloaded_symbols` table (reconstructed from the libtool object's relocations) registers the dlopen loader. The frozen `config.h` merges ltdl's own configure output into the top-level one — ltdl sources never read the clashing identification macros, and the merge sidesteps a quoted `-DLT_CONFIG_H` that does not survive the pipeline. Verified against the libtool build of the same tarball: identical IM002 error path and identical `lt_dlopen` behaviour) | | C-source compat, an ISA tier turned off through the GENERATED config | [`compat.libwebp`](pkgs/c/compat.libwebp.lua) (117 TUs as five directory globs rather than a transcribed file list, and one real decision. libwebp's SSE4.1 gate is `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`, and `WEBP_MSC_SSE41` keys off `_MSC_VER` **alone** — which every MSVC-ABI compiler defines, including clang, while only cl.exe lets an intrinsic be used without a target flag. Under clang the SSE4.1 sources fail on `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`. Upstream answers with a PER-FILE `-msse4.1`, which no descriptor field expresses; adding it package-wide would let clang emit SSE4.1 in the BASELINE TUs too, past libwebp's own runtime dispatch — a SIGILL rather than a fallback. So the package uses the other half of upstream's own mechanism: `HAVE_CONFIG_H` plus a generated `src/webp/config.h` naming SSE2 and NEON and not SSE4.1, which turns `dec_sse41.c` and friends into upstream's `WEBP_DSP_INIT_STUB` and removes the matching `VP8DspInitSSE41()` call. `src/demux` and `src/mux` are separate upstream libraries with their own public headers and stay out until something asks for them) | diff --git a/README.zh-CN.md b/README.zh-CN.md index 89b5fc1..ef7b89a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -36,7 +36,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) · [`compat.hiredis`](pkgs/c/compat.hiredis.lua)(经典 1.2.0 —— 7 个 C TU;tarball 平铺头经 `generated_files` 补 `hiredis/` 前缀薄包装头,消费者可写 `#include `,与上游安装布局一致) · [`compat.sqlite3`](pkgs/c/compat.sqlite3.lua)(纯 C 源码、无 feature:单一 `sqlite3.c` amalgamation;3.45.3,部署最广的 3.45.x 线) · [`compat.libuv`](pkgs/c/compat.libuv.lua)(libuv 1.48.0 —— 逐 OS 源清单转录自上游 CMakeLists,因为 `src/unix/*.c` 通配会一次编进所有 OS 的后端;linux/macos 显式列 unix 子集,windows 用 `src/win/*.c` glob) |· [`compat.xxhash`](pkgs/c/compat.xxhash.lua)(单 TU、单头、无 feature —— 值得说的是**没有**编译什么:`xxh_x86dispatch.c` 在运行期选择 AVX2/AVX512 路径,需要 per-file `-mavx2` 并要求每个调用点定义 `XXH_X86DISPATCH`,故本包只出无需任何 flag 的 SSE2 基线。也没有选 header-only 的 `XXH_INLINE_ALL` 模式:它会在每个做哈希的 TU 里重新展开整份实现,那只有在「恰好只有一个这样的 TU」时才划算 —— 而这件事包本身无从知道) -| C++ 源码 compat(彼此依赖) | [`compat.abseil`](pkgs/c/compat.abseil.lua)(151 TU;对 `absl/**` 取通配后,按上游自身的 test/benchmark 命名约定裁剪) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua)(libprotobuf 运行时,79 TU 逐条转录自上游 `src/file_lists.cmake`;因 protobuf 公开头文件 include 了 `absl/…`,故显式依赖 `compat.abseil`;`gzip` feature 定义 `HAVE_ZLIB` 并拉入 `compat.zlib`,`upb` feature 则从同一个 tarball 里再编出 protobuf 的 64 TU C 运行时;还以 `kind = "bin"` target 暴露 **`protoc`**,消费者写 `tools = ["protoc"]` 即可从「自己链接的那个包」拿到为本机构建的编译器,使生成器与运行时的版本错配无法表达) · [`compat.re2`](pkgs/c/compat.re2.lua)(22 TU,取自上游自身的 `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua)(redis++ 1.3.13 —— 同步客户端,17 TU + `patterns/redlock.cpp`,依赖 `compat.hiredis`;CMake 唯一会生成的头 `hiredis_features.h` 用 `generated_files` 快照,async/TLS TU 不收,基座保持两包成对。`async` feature 补齐 libuv 版 `AsyncRedis` 接口(9 个 async TU + `compat.libuv`;`event_loop.cpp` 在后台线程跑 `uv_run`,`` 经 compat.hiredis 的包装头到达)。两个版本分处源码结构分水岭两侧,共享同一份源列表:1.3.13(现代 17-TU 布局)与 1.3.3(缺 `redis_uri.cpp`/`redlock` 的 15-TU 旧布局)—— 并集之所以成立,是因为 1.3.3 的 TU 是 1.3.13 的严格子集,恰好两个 glob 在 1.3.3 上零命中(仅警告,非错误;与 compat.catch2 同款手法)) | +| C++ 源码 compat(彼此依赖) | [`compat.abseil`](pkgs/c/compat.abseil.lua)(151 TU;对 `absl/**` 取通配后,按上游自身的 test/benchmark 命名约定裁剪) · [`compat.protobuf`](pkgs/c/compat.protobuf.lua)(libprotobuf 运行时,79 TU 逐条转录自上游 `src/file_lists.cmake`;因 protobuf 公开头文件 include 了 `absl/…`,故显式依赖 `compat.abseil`;`gzip` feature 定义 `HAVE_ZLIB` 并拉入 `compat.zlib`,`upb` feature 则从同一个 tarball 里再编出 protobuf 的 64 TU C 运行时;还以 `kind = "bin"` target 暴露 **`protoc`**,消费者写 `tools = ["protoc"]` 即可从「自己链接的那个包」拿到为本机构建的编译器,使生成器与运行时的版本错配无法表达) · [`compat.re2`](pkgs/c/compat.re2.lua)(22 TU,取自上游自身的 `RE2_SOURCES`) · [`compat.redis-plus-plus`](pkgs/c/compat.redis-plus-plus.lua)(redis++ 1.3.13 —— 同步客户端,17 TU + `patterns/redlock.cpp`,依赖 `compat.hiredis`;CMake 唯一会生成的头 `hiredis_features.h` 用 `generated_files` 快照,async/TLS TU 不收,基座保持两包成对。`async` feature 补齐 libuv 版 `AsyncRedis` 接口(9 个 async TU + `compat.libuv`;`event_loop.cpp` 在后台线程跑 `uv_run`,`` 经 compat.hiredis 的包装头到达)。两个版本分处源码结构分水岭两侧,共享同一份源列表:1.3.13(现代 17-TU 布局)与 1.3.3(缺 `redis_uri.cpp`/`redlock` 的 15-TU 旧布局)—— 并集之所以成立,是因为 1.3.3 的 TU 是 1.3.13 的严格子集,恰好两个 glob 在 1.3.3 上零命中(仅警告,非错误;与 compat.catch2 同款手法)) · [`compat.sqlitecpp`](pkgs/c/compat.sqlitecpp.lua)(SQLite 的 RAII C++ 封装。上游用 **git submodule** 引 sqlite3,源码 tarball 里根本没有它,库因此无法链接 —— 依赖边指向 `compat.sqlite3` 替代了那个 submodule,而且更好:同一次链接里的两个 SQLite 消费者从此共享**一份** amalgamation,而不是各自内嵌一份带各自编译选项的副本。它的两个 CMake 开关有意不设 —— `SQLITECPP_USE_ASSERT_ON_ERRORS` 把错误模型从抛异常改成中止进程,`SQLITE_ENABLE_COLUMN_METADATA` 必须与 SQLite **自身**的构建一致;两者都该由消费者决定,而头文件本来就用 `#ifdef` 守着) | +| C 传输层 + 其上的 header-only C++ 服务端 | [`compat.usockets`](pkgs/c/compat.usockets.lua) · [`compat.uwebsockets`](pkgs/c/compat.uwebsockets.lua)(uSockets 三平台统一选 libuv 一个事件循环(经 `compat.libuv`),因为按平台各选后端只会让 `us_loop_t` 每个平台一个形状而毫无收益;SSL 与 QUIC 不收,于是基础包的唯一依赖就是那个循环。这一对真正的教训是 `LIBUS_USE_LIBUV` / `LIBUS_NO_SSL` / `UWS_NO_ZLIB` 是**接口级**事实:`libusockets.h` 会因前者改变 `us_loop_t` 的布局、因后者门控 SSL 声明,而 uWS 是 header-only —— 它的模板是在**消费者**的 TU 里实例化的。描述符的 `cflags` 只作用于包自身的 TU,所以每个消费者都必须自己声明这三个;不一致不会构建失败,而是内存损坏。usockets 的测试因此从定时器回调里写loop 附属的扩展内存再读回来 —— 布局一旦不一致,正是这条断言会断) | | C++ 源码 compat(零依赖客户端 + 可选组件) | [`compat.websocket`](pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端,**基座零外部依赖**:TLS 关闭(OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),`IXWEBSOCKET_USE_ZLIB` 不定义(gzip codec 编译为 no-op)。两个可选 feature 在基座上叠加:`server`(4 个 server TU —— `IXWebSocketServer`/`IXSocketServer`/`IXHttpServer`/`IXWebSocketProxyServer`,零新增外部依赖,且 **implies `zlib`** —— 因为上游 server 默认就宣称 permessage-deflate,而 transport 的协商不受宏门控)与 `zlib`(依赖 `compat.zlib`,把 codec 变成真正的 permessage-deflate 压缩)。默认构建的测试自带基于 loopback 原始 socket 的最小 RFC 6455 echo server(握手/掩码/分片/关闭全部离线实测);第二个成员 `websocket-features` 跑真实的 `ix::WebSocketServer`,并断言压缩在线路上可观测 —— 64 KiB 重复载荷往返,`wireSize` = 80) | | 数据库客户端 + 源码构建的驱动管理器 | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua)(nanodbc 2.14.0,上游已冻结 —— 单 TU 封装平台 ODBC 驱动管理器。两处修复让这份四年前的源码在此可编译、可运行:一个 force-include 的 `char_traits` 补丁头(标准留给用户的定制点,以 `_LIBCPP_VERSION` 为界,不影响 libstdc++/MSVC;注意 `-include` 只能经 `cxxflags` 到达 C++ TU,`cflags` 够不着),以及对驱动管理器本身的分平台答案 —— windows 链 SDK 的 odbc32、macOS 链系统自带的 iODBC,linux 则依赖 `compat.unixodbc`,因为 mcpp 的运行时闭包检查不接受只有宿主才有的 `libodbc.so.2`。测试断言管理器自身的诊断能穿过封装层 —— 包括 nanodbc 已冻结的、会把 SQL state 末字符截掉的 off-by-one)· [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua)(unixODBC 2.3.14,E 叠 A 形态 —— DM + odbcinst + ini/log/lst + libltdl 静态编入单一 `odbc` 目标,与上游 libodbc.a 符号集一致,消费者不带任何 `libodbc.so.2` NEEDED。唯一非常规之处是无 libtool 的 ltdl 接线:`-DLTDLOPEN=libltdlc` 加一张生成的 `lt_libltdlc_LTX_preloaded_symbols` 表(从 libtool 目标文件的重定位记录还原)注册 dlopen loader。冻结的 `config.h` 把 ltdl 自己的 configure 输出合并进顶层(冲突宏 ltdl 源码并不读),绕开了无法在管道中幸存的带引号 `-DLT_CONFIG_H`。已与同 tarball 的 libtool 构建对比验证:IM002 错误路径与 `lt_dlopen` 行为完全一致) | | C 源码 compat(用**生成的 config** 关掉一档 ISA) | [`compat.libwebp`](pkgs/c/compat.libwebp.lua)(117 个 TU 用五条目录通配写完,外加一个真实的取舍。libwebp 的 SSE4.1 门是 `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`,而 `WEBP_MSC_SSE41` **只**看 `_MSC_VER` —— 每个 MSVC ABI 编译器都定义它,clang 也不例外,但只有 cl.exe 允许不带 target flag 使用任意 intrinsic。于是 clang 下 SSE4.1 那批源码报 `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`。上游用 **per-file** `-msse4.1` 解决,而描述符没有这个字段;整包加上去,clang 就会在**基线** TU 里也发 SSE4.1,绕过 libwebp 自己的运行期分发 —— 那是 SIGILL 而不是回退。所以本包用上游同一套机制的另一半:`HAVE_CONFIG_H` + 生成的 `src/webp/config.h`,只声明 SSE2 与 NEON、不声明 SSE4.1,`dec_sse41.c` 等随之变成上游的 `WEBP_DSP_INIT_STUB`,对应的 `VP8DspInitSSE41()` 调用点也一并消失。`src/demux` / `src/mux` 是上游各自独立、各带公开头文件的库,在有人需要之前不收) | diff --git a/mcpp.toml b/mcpp.toml index c8c6dd2..e04e737 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -76,9 +76,12 @@ members = [ "tests/examples/md4c", "tests/examples/spdlog-compiled", "tests/examples/sqlite3", + "tests/examples/sqlitecpp", "tests/examples/tinyhttps", + "tests/examples/usockets", "tests/examples/vulkan", "tests/examples/websocket", + "tests/examples/uwebsockets", "tests/examples/websocket-features", "tests/examples/tray", "tests/examples/xxhash", diff --git a/pkgs/c/compat.sqlitecpp.lua b/pkgs/c/compat.sqlitecpp.lua new file mode 100644 index 0000000..acb6867 --- /dev/null +++ b/pkgs/c/compat.sqlitecpp.lua @@ -0,0 +1,72 @@ +-- compat.sqlitecpp — SQLiteCpp, a thin RAII C++ wrapper over the SQLite C API. +-- A consumer writes `#include `. +-- +-- Shape A, with the one interesting bit being where SQLite comes from. +-- +-- Upstream vendors sqlite3 as a GIT SUBMODULE, and a source tarball carries no +-- submodules — so `sqlite3/sqlite3.c` simply is not in the archive and the +-- library would not link. The dependency edge on `compat.sqlite3` replaces it, +-- which is also the better arrangement: two consumers of SQLite in one link now +-- share ONE amalgamation rather than each embedding a private copy with its own +-- compile-time options. +-- +-- WHAT IS NOT COMPILED. `src/` holds exactly the wrapper's 11 TUs and nothing +-- else, so the glob needs no trimming. Upstream's `tests/`, `examples/` and the +-- `googletest/` submodule are elsewhere in the tree. +-- +-- COMPILE OPTIONS ARE DELIBERATELY ABSENT. SQLiteCpp's CMake offers +-- SQLITECPP_USE_ASSERT_ON_ERRORS (aborts the process on a misused API instead of +-- throwing) and SQLITE_ENABLE_COLUMN_METADATA (adds Column::getOriginName and +-- friends, and requires the SAME define when SQLite itself was built). Neither +-- is something a package should decide for its consumers: the first changes the +-- error model, the second has to agree with compat.sqlite3's build. The headers +-- guard both with #ifdef, so a consumer that wants either declares it — and for +-- the metadata one, must also build SQLite with it. +package = { + spec = "1", + namespace = "compat", + name = "sqlitecpp", + description = "SQLiteCpp: a smart and easy to use C++ SQLite3 wrapper", + licenses = {"MIT"}, + repo = "https://github.com/SRombauts/SQLiteCpp", + type = "package", + + xpm = { + linux = { + ["3.3.3"] = { + url = { + GLOBAL = "https://github.com/SRombauts/SQLiteCpp/archive/refs/tags/3.3.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/sqlitecpp/releases/download/3.3.3/sqlitecpp-3.3.3.tar.gz", + }, + sha256 = "33bd4372d83bc43117928ee842be64d05e7807f511b5195f85d30015cad9cac6", + }, + }, + macosx = { + ["3.3.3"] = { + url = { + GLOBAL = "https://github.com/SRombauts/SQLiteCpp/archive/refs/tags/3.3.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/sqlitecpp/releases/download/3.3.3/sqlitecpp-3.3.3.tar.gz", + }, + sha256 = "33bd4372d83bc43117928ee842be64d05e7807f511b5195f85d30015cad9cac6", + }, + }, + windows = { + ["3.3.3"] = { + url = { + GLOBAL = "https://github.com/SRombauts/SQLiteCpp/archive/refs/tags/3.3.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/sqlitecpp/releases/download/3.3.3/sqlitecpp-3.3.3.tar.gz", + }, + sha256 = "33bd4372d83bc43117928ee842be64d05e7807f511b5195f85d30015cad9cac6", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + include_dirs = { "*/include" }, + sources = { "*/src/*.cpp" }, + targets = { ["SQLiteCpp"] = { kind = "lib" } }, + deps = { ["compat.sqlite3"] = "3.45.3" }, + }, +} diff --git a/pkgs/c/compat.usockets.lua b/pkgs/c/compat.usockets.lua new file mode 100644 index 0000000..ddaad2f --- /dev/null +++ b/pkgs/c/compat.usockets.lua @@ -0,0 +1,90 @@ +-- compat.usockets — uSockets, the C eventing/networking layer under uWebSockets. +-- A consumer writes `#include `. +-- +-- Shape A, plus two defines that are INTERFACE facts rather than private ones. +-- +-- BACKEND: libuv, on every platform. uSockets 0.8.8 ships four event loops — +-- epoll/kqueue (POSIX only), GCD (macOS), libuv, and asio. Only libuv covers all +-- three platforms with ONE source list, and it is also the backend the ecosystem +-- has the most mileage on (it is what vcpkg's uwebsockets port selects). The +-- alternative — a per-platform backend — would make `us_loop_t` a different +-- struct per platform for no gain. +-- +-- SSL: OFF. crypto/openssl.c and crypto/sni_tree.cpp are not compiled and +-- compat.openssl is not depended on, so the base package has ZERO external +-- dependencies beyond libuv. A TLS feature belongs here eventually; it is a +-- feature rather than a default because terminating TLS in-process is a choice, +-- and paying for OpenSSL when you have not made it is not. +-- +-- ALSO OUT: quic.c (needs lsquic) and eventing/io_uring.c (Linux only, and a +-- second Linux backend would contradict the one-backend rule above). +-- +-- WHY THE DEFINES REACH CONSUMERS. `libusockets.h` — the PUBLIC header — changes +-- the layout of `us_loop_t` under LIBUS_USE_LIBUV and gates its SSL declarations +-- on LIBUS_NO_SSL. A consumer that does not see the same pair gets a different +-- struct than the library was compiled with, and the mismatch surfaces as +-- corruption at run time rather than an error at build time. An index +-- descriptor's `cflags` reach only this package's own TUs, so a CONSUMER must +-- declare them too — uWebSockets does, and so must anything using uSockets +-- directly. +package = { + spec = "1", + namespace = "compat", + name = "usockets", + description = "uSockets: eventing and networking layer under uWebSockets (libuv backend, no SSL)", + licenses = {"Apache-2.0"}, + repo = "https://github.com/uNetworking/uSockets", + type = "package", + + xpm = { + linux = { + ["0.8.8"] = { + url = { + GLOBAL = "https://github.com/uNetworking/uSockets/archive/refs/tags/v0.8.8.tar.gz", + CN = "https://gitcode.com/mcpp-res/usockets/releases/download/0.8.8/usockets-0.8.8.tar.gz", + }, + sha256 = "d14d2efe1df767dbebfb8d6f5b52aa952faf66b30c822fbe464debaa0c5c0b17", + }, + }, + macosx = { + ["0.8.8"] = { + url = { + GLOBAL = "https://github.com/uNetworking/uSockets/archive/refs/tags/v0.8.8.tar.gz", + CN = "https://gitcode.com/mcpp-res/usockets/releases/download/0.8.8/usockets-0.8.8.tar.gz", + }, + sha256 = "d14d2efe1df767dbebfb8d6f5b52aa952faf66b30c822fbe464debaa0c5c0b17", + }, + }, + windows = { + ["0.8.8"] = { + url = { + GLOBAL = "https://github.com/uNetworking/uSockets/archive/refs/tags/v0.8.8.tar.gz", + CN = "https://gitcode.com/mcpp-res/usockets/releases/download/0.8.8/usockets-0.8.8.tar.gz", + }, + sha256 = "d14d2efe1df767dbebfb8d6f5b52aa952faf66b30c822fbe464debaa0c5c0b17", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- src/ carries the public ; the internal headers are + -- included flat from within it. + include_dirs = { "*/src" }, + sources = { + "*/src/bsd.c", + "*/src/context.c", + "*/src/loop.c", + "*/src/socket.c", + "*/src/udp.c", + "*/src/eventing/libuv.c", + }, + cflags = { "-DLIBUS_USE_LIBUV", "-DLIBUS_NO_SSL" }, + cxxflags = { "-DLIBUS_USE_LIBUV", "-DLIBUS_NO_SSL" }, + targets = { ["uSockets"] = { kind = "lib" } }, + deps = { ["compat.libuv"] = "1.48.0" }, + windows = { ldflags = { "-lws2_32" } }, + }, +} diff --git a/pkgs/c/compat.uwebsockets.lua b/pkgs/c/compat.uwebsockets.lua new file mode 100644 index 0000000..05399a1 --- /dev/null +++ b/pkgs/c/compat.uwebsockets.lua @@ -0,0 +1,77 @@ +-- compat.uwebsockets — uWebSockets, a header-only C++ HTTP/WebSocket server. +-- A consumer writes `#include `. +-- +-- Shape B (header-only + anchor TU): the whole library is templates over +-- uSockets' C types, so the package is an include root, a trivial anchor so +-- there is a buildable lib target, and the dependency edge that actually +-- matters. +-- +-- THE DEFINES ARE THE POINT. uWS's templates instantiate over `us_loop_t` and +-- friends, so it has to see the SAME backend/SSL shape uSockets was compiled +-- with — LIBUS_USE_LIBUV and LIBUS_NO_SSL — or the struct layouts disagree +-- silently. UWS_NO_ZLIB additionally turns off per-message-deflate, which is +-- what keeps this package free of a zlib dependency; negotiation then simply +-- offers no compression extension, which is the conservative default. +-- +-- As with compat.usockets, an index descriptor's `cflags` reach only the +-- package's own TUs — and this package has exactly one, an anchor. So a +-- CONSUMER must declare all three itself. That is not a workaround: uWS is +-- header-only, so the consumer's TU is where it is actually compiled. +-- +-- INCLUDE LAYOUT. Upstream keeps its headers flat in `src/` and includes them as +-- . Distributions that install them under `uwebsockets/` (vcpkg does) +-- make consumers write ``; exposing `*/src` here is the +-- upstream spelling. +package = { + spec = "1", + namespace = "compat", + name = "uwebsockets", + description = "uWebSockets: header-only HTTP/WebSocket server (libuv backend, no SSL, no zlib)", + licenses = {"Apache-2.0"}, + repo = "https://github.com/uNetworking/uWebSockets", + type = "package", + + xpm = { + linux = { + ["20.79.0"] = { + url = { + GLOBAL = "https://github.com/uNetworking/uWebSockets/archive/refs/tags/v20.79.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/uwebsockets/releases/download/20.79.0/uwebsockets-20.79.0.tar.gz", + }, + sha256 = "d255491a19c26b3f1593c686d4c07d7d2cebe1ba68d42caad87c068cfed0bf84", + }, + }, + macosx = { + ["20.79.0"] = { + url = { + GLOBAL = "https://github.com/uNetworking/uWebSockets/archive/refs/tags/v20.79.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/uwebsockets/releases/download/20.79.0/uwebsockets-20.79.0.tar.gz", + }, + sha256 = "d255491a19c26b3f1593c686d4c07d7d2cebe1ba68d42caad87c068cfed0bf84", + }, + }, + windows = { + ["20.79.0"] = { + url = { + GLOBAL = "https://github.com/uNetworking/uWebSockets/archive/refs/tags/v20.79.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/uwebsockets/releases/download/20.79.0/uwebsockets-20.79.0.tar.gz", + }, + sha256 = "d255491a19c26b3f1593c686d4c07d7d2cebe1ba68d42caad87c068cfed0bf84", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/src" }, + generated_files = { + ["mcpp_generated/uws_anchor.c"] = "int mcpp_compat_uwebsockets_anchor(void) { return 0; }\n", + }, + sources = { "mcpp_generated/uws_anchor.c" }, + cxxflags = { "-DLIBUS_USE_LIBUV", "-DLIBUS_NO_SSL", "-DUWS_NO_ZLIB" }, + targets = { ["uWebSockets"] = { kind = "lib" } }, + deps = { ["compat.usockets"] = "0.8.8" }, + }, +} diff --git a/tests/examples/sqlitecpp/mcpp.toml b/tests/examples/sqlitecpp/mcpp.toml new file mode 100644 index 0000000..f0b71d5 --- /dev/null +++ b/tests/examples/sqlitecpp/mcpp.toml @@ -0,0 +1,9 @@ +# SQLiteCpp test project: consumes compat.sqlitecpp from this repo's own index. +# compat.sqlite3 arrives transitively — the wrapper depends on it because +# upstream's own build gets SQLite from a git submodule the tarball omits. +[package] +name = "sqlitecpp-tests" +version = "0.1.0" + +[dependencies.compat] +sqlitecpp = "3.3.3" diff --git a/tests/examples/sqlitecpp/tests/database.cpp b/tests/examples/sqlitecpp/tests/database.cpp new file mode 100644 index 0000000..f177fb9 --- /dev/null +++ b/tests/examples/sqlitecpp/tests/database.cpp @@ -0,0 +1,83 @@ +// Behavioral test: a real database session through the RAII wrapper. +// +// The load-bearing question for this package is whether the dependency edge on +// compat.sqlite3 actually replaces upstream's git submodule — and that is a LINK +// question, so every assertion below is chosen to reach sqlite3 symbols: the +// version string, prepared statements with bound parameters, a transaction that +// rolls back, and the exception path (which carries an sqlite3 error code). +#include + +#include +#include + +int main() { + // The wrapper reports the SQLite it was linked against, which is the + // cheapest proof that the dependency edge resolved to a real library. + const std::string version = SQLite::getLibVersion(); + assert(!version.empty() && version[0] == '3'); + + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + + db.exec("CREATE TABLE note (id INTEGER PRIMARY KEY, title TEXT NOT NULL, size INTEGER)"); + assert(db.tableExists("note")); + + // --- bound parameters through a prepared statement --- + { + SQLite::Statement insert(db, "INSERT INTO note (title, size) VALUES (?, ?)"); + for (const auto& [title, size] : {std::pair{"alpha", 10}, + {"beta", 20}, + {"gamma", 30}}) { + insert.bind(1, title); + insert.bind(2, size); + assert(insert.exec() == 1); + insert.reset(); + } + } + assert(db.execAndGet("SELECT COUNT(*) FROM note").getInt() == 3); + assert(db.getLastInsertRowid() == 3); + + // --- iteration, typed columns, and a named parameter --- + { + SQLite::Statement query(db, "SELECT id, title, size FROM note WHERE size >= :floor ORDER BY id"); + query.bind(":floor", 20); + int rows = 0; + int total = 0; + while (query.executeStep()) { + const int id = query.getColumn(0); + const std::string title = query.getColumn(1).getString(); + total += static_cast(query.getColumn(2).getInt()); + assert(id >= 2 && !title.empty()); + ++rows; + } + assert(rows == 2 && total == 50); + } + + // --- a transaction that is NOT committed must roll back on scope exit --- + { + SQLite::Transaction abandoned(db); + db.exec("DELETE FROM note"); + assert(db.execAndGet("SELECT COUNT(*) FROM note").getInt() == 0); + } + assert(db.execAndGet("SELECT COUNT(*) FROM note").getInt() == 3); + + // --- and one that is --- + { + SQLite::Transaction kept(db); + db.exec("UPDATE note SET size = size * 2"); + kept.commit(); + } + assert(db.execAndGet("SELECT SUM(size) FROM note").getInt() == 120); + + // --- the error path carries an sqlite3 error code, not just a message --- + bool threw = false; + try { + db.exec("INSERT INTO note (title) VALUES (NULL)"); // NOT NULL violation + } catch (const SQLite::Exception& e) { + threw = true; + assert(e.getErrorCode() != 0); + assert(std::string(e.what()).find("NOT NULL") != std::string::npos); + } + assert(threw); + + return 0; +} diff --git a/tests/examples/usockets/mcpp.toml b/tests/examples/usockets/mcpp.toml new file mode 100644 index 0000000..02b1540 --- /dev/null +++ b/tests/examples/usockets/mcpp.toml @@ -0,0 +1,14 @@ +# uSockets test project. compat.libuv arrives transitively as the event loop. +[package] +name = "usockets-tests" +version = "0.1.0" + +[dependencies.compat] +usockets = "0.8.8" + +[build] +# libusockets.h changes the layout of us_loop_t under LIBUS_USE_LIBUV and gates +# its SSL declarations on LIBUS_NO_SSL, so a consumer must be compiled with the +# SAME pair the library was — an index descriptor's cflags reach only the +# package's own TUs. Getting this wrong does not fail to build; it corrupts. +defines = ["LIBUS_USE_LIBUV", "LIBUS_NO_SSL"] diff --git a/tests/examples/usockets/tests/loop.cpp b/tests/examples/usockets/tests/loop.cpp new file mode 100644 index 0000000..a496a4b --- /dev/null +++ b/tests/examples/usockets/tests/loop.cpp @@ -0,0 +1,70 @@ +// Behavioral test: a real event loop iteration, driven by uSockets' own timer. +// +// The struct-layout hazard this package warns about is invisible to a build: +// `us_loop_t` has a different shape under LIBUS_USE_LIBUV, and a consumer that +// disagrees corrupts memory rather than failing to link. So the test allocates +// loop-attached extension memory, writes to it from a timer callback, and reads +// it back afterwards — if the layouts disagreed, that write lands somewhere else. +#include + +#include +#include + +namespace { + +struct LoopExt { + int ticks; + int magic; +}; + +struct TimerExt { + us_loop_t* loop; + int budget; +}; + +constexpr int kMagic = 0x5AFE; + +void on_wakeup(us_loop_t*) {} +void on_pre(us_loop_t*) {} +void on_post(us_loop_t*) {} + +void on_timer(us_timer_t* timer) { + auto* text = static_cast(us_timer_ext(timer)); + auto* lext = static_cast(us_loop_ext(text->loop)); + + assert(lext->magic == kMagic && "loop extension memory moved — layout mismatch"); + ++lext->ticks; + + if (--text->budget <= 0) { + us_timer_close(timer); + } +} + +} // namespace + +int main() { + us_loop_t* loop = us_create_loop(nullptr, on_wakeup, on_pre, on_post, + static_cast(sizeof(LoopExt))); + assert(loop != nullptr); + + auto* lext = static_cast(us_loop_ext(loop)); + std::memset(lext, 0, sizeof(LoopExt)); + lext->magic = kMagic; + + us_timer_t* timer = us_create_timer(loop, 0, static_cast(sizeof(TimerExt))); + assert(timer != nullptr); + auto* text = static_cast(us_timer_ext(timer)); + text->loop = loop; + text->budget = 3; + + // 1 ms, repeating: the loop must actually run and re-arm. + us_timer_set(timer, on_timer, 1, 1); + + us_loop_run(loop); // returns when nothing is left referencing the loop + + assert(lext->ticks == 3); + assert(lext->magic == kMagic); + + us_loop_free(loop); + return 0; +} diff --git a/tests/examples/uwebsockets/mcpp.toml b/tests/examples/uwebsockets/mcpp.toml new file mode 100644 index 0000000..5b97ba7 --- /dev/null +++ b/tests/examples/uwebsockets/mcpp.toml @@ -0,0 +1,14 @@ +# uWebSockets test project. compat.usockets and compat.libuv arrive transitively. +[package] +name = "uwebsockets-tests" +version = "0.1.0" + +[dependencies.compat] +uwebsockets = "20.79.0" + +[build] +# uWS is header-only: the consumer's TU is where it is actually compiled, so the +# consumer is where these have to be declared. LIBUS_* must match how uSockets +# was built (us_loop_t layout, SSL declarations); UWS_NO_ZLIB matches the +# package's zlib-free build. +defines = ["LIBUS_USE_LIBUV", "LIBUS_NO_SSL", "UWS_NO_ZLIB"] diff --git a/tests/examples/uwebsockets/tests/listen.cpp b/tests/examples/uwebsockets/tests/listen.cpp new file mode 100644 index 0000000..cfd3ed0 --- /dev/null +++ b/tests/examples/uwebsockets/tests/listen.cpp @@ -0,0 +1,48 @@ +// Behavioral test: instantiate the server templates and bind a real port. +// +// The risk in a header-only package layered on a C library is not "does the +// header exist" — it is whether the templates instantiate over the C types as +// the library was actually built, which only the CONSUMER's compile can answer. +// Registering routes instantiates HttpContext/HttpResponse/HttpRequest over +// us_socket_t, and `listen` walks all the way down to a bound socket. +#include + +#include +#include + +int main() { + bool bound = false; + int chosen_port = 0; + + uWS::App app; + + // Two route shapes and a websocket behavior: each instantiates a different + // corner of the template stack. + app.get("/ping", [](auto* res, auto* /*req*/) { + res->end("pong"); + }); + app.post("/echo/:id", [](auto* res, auto* req) { + res->end(std::string(req->getParameter(0))); + }); + app.ws("/ws", { + .open = [](auto* /*ws*/) {}, + .message = [](auto* ws, std::string_view msg, uWS::OpCode op) { ws->send(msg, op); }, + }); + + // Port 0 asks the OS for an ephemeral port, so the test cannot collide with + // anything else on the runner. + app.listen(0, [&](us_listen_socket_t* token) { + bound = (token != nullptr); + if (token) { + chosen_port = us_socket_local_port(0, reinterpret_cast(token)); + us_listen_socket_close(0, token); + } + }); + + // Returns once the listen socket is closed and nothing else holds the loop. + app.run(); + + assert(bound && "uWS::App failed to bind an ephemeral port"); + assert(chosen_port > 0 && "the OS assigned no port"); + return 0; +}