diff --git a/README.md b/README.md index 00c8eef..54b2b64 100644 --- a/README.md +++ b/README.md @@ -38,16 +38,17 @@ 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) · [`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`) | +| 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, zero-dep client + optional components | [`compat.websocket`](pkgs/c/compat.websocket.lua) (IXWebSocket 12.0.1 — a pure RFC 6455 client compiled from upstream's `IXWEBSOCKET_SOURCES` minus the four server TUs, so the **base build has zero external dependencies**: TLS off (the OpenSSL/MbedTLS/AppleSSL TUs aren't built) and `IXWEBSOCKET_USE_ZLIB` unset, so the gzip codec compiles to a no-op. Two optional features add on top: `server` (the four server TUs — `IXWebSocketServer`, `IXSocketServer`, `IXHttpServer`, `IXWebSocketProxyServer` — needing nothing external, and it **implies `zlib`** because upstream's server advertises permessage-deflate by default, which the transport negotiates regardless of the define) and `zlib` (deps `compat.zlib` and turns the codec into real per-message-deflate compression). The default-feature test brings its own minimal RFC 6455 echo server on loopback sockets (handshake, masking, fragmentation and close all exercised offline); a second member, `websocket-features`, runs a real `ix::WebSocketServer` and asserts the compression is observable on the wire — a 64 KiB repeated payload round-trips with `wireSize` = 80) | | Database client + the driver manager it needs, built from source | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua) (nanodbc 2.14.0, frozen upstream — one TU over the platform ODBC driver manager. Two fixes make the four-year-old source compile and RUN here: a force-included `char_traits` shim for libc++ (the standard's own customization point, guarded on `_LIBCPP_VERSION` so libstdc++/MSVC are untouched; and note `-include` reaches C++ TUs only through `cxxflags`, never `cflags`), and a per-platform answer to the manager itself — windows links the SDK's odbc32, macOS the OS's iODBC, while linux takes `compat.unixodbc` because mcpp's runtime closure rejects a NEEDED `libodbc.so.2` that only the host has. The test asserts the manager's own diagnostics surface through the wrapper — including nanodbc's frozen off-by-one that drops the last SQL-state character) · [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua) (unixODBC 2.3.14, Shape E over A — DM + odbcinst + ini/log/lst + libltdl compiled statically into one `odbc` target, exactly upstream's libodbc.a symbol set, so the consumer carries no `libodbc.so.2` NEEDED at all. The one non-obvious piece is libtool-free ltdl wiring: `-DLTDLOPEN=libltdlc` plus a generated `lt_libltdlc_LTX_preloaded_symbols` table (reconstructed from the libtool object's relocations) registers the dlopen loader. The frozen `config.h` merges ltdl's own configure output into the top-level one — ltdl sources never read the clashing identification macros, and the merge sidesteps a quoted `-DLT_CONFIG_H` that does not survive the pipeline. Verified against the libtool build of the same tarball: identical IM002 error path and identical `lt_dlopen` behaviour) | +| C-source compat, an ISA tier turned off through the GENERATED config | [`compat.libwebp`](pkgs/c/compat.libwebp.lua) (117 TUs as five directory globs rather than a transcribed file list, and one real decision. libwebp's SSE4.1 gate is `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`, and `WEBP_MSC_SSE41` keys off `_MSC_VER` **alone** — which every MSVC-ABI compiler defines, including clang, while only cl.exe lets an intrinsic be used without a target flag. Under clang the SSE4.1 sources fail on `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`. Upstream answers with a PER-FILE `-msse4.1`, which no descriptor field expresses; adding it package-wide would let clang emit SSE4.1 in the BASELINE TUs too, past libwebp's own runtime dispatch — a SIGILL rather than a fallback. So the package uses the other half of upstream's own mechanism: `HAVE_CONFIG_H` plus a generated `src/webp/config.h` naming SSE2 and NEON and not SSE4.1, which turns `dec_sse41.c` and friends into upstream's `WEBP_DSP_INIT_STUB` and removes the matching `VP8DspInitSSE41()` call. `src/demux` and `src/mux` are separate upstream libraries with their own public headers and stay out until something asks for them) | | header-only (with `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | | header-only, nothing to gate | [`compat.CLI11`](pkgs/c/compat.CLI11.lua) (a command line parser whose every definition is `CLI11_INLINE`, so the package is `*/include` plus an anchor TU. Upstream's two extras stay out: `src/Precompile.cpp` only means anything when `CLI11_COMPILE` also reaches the CONSUMER's translation units — an interface define, not a sources-only gate — and `src/modules/CLI11.cppm` is a module layer, which is a package shape of its own rather than a feature of the compat package) · [`compat.gtl`](pkgs/c/compat.gtl.lua) (Greg's Template Library — the Swiss-table `flat_hash_map` family plus btrees and a bit_vector. `*/include` exactly, not the tarball root: `tests/` and `examples/` carry headers of their own, and naming `include/` is what upstream's INTERFACE target exposes, so a consumer cannot accidentally resolve into test code) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua) (the reference implementation of the proposed `std::hive`; the whole library is one file at the tarball root, so `*` plus an anchor TU is the entire package. Untagged upstream, so the version is a DATE over a commit archive — the compat.khrplatform precedent) | | single-header library + a GENERATED implementation TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua) (two stb-style headers where the implementation hides behind `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION`. Upstream ships no `.c` — its examples define the macros inline — so the package generates one that instantiates BOTH halves once. That is what turns a header drop into something linkable, and it moves the duplicate-symbol hazard from every consumer to a single place: consumers must NOT define those macros again, and the test links `nsvgParse` and `nsvgRasterize` together precisely so a package that instantiated only one half fails here rather than downstream) · [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua) (VMA 3.4.0, same shape but the generated TU also has to make a POLICY choice. VMA defaults to `VMA_STATIC_VULKAN_FUNCTIONS 1`, which references `vkBindBufferMemory2` and seven siblings by name — eight undefined symbols against a headers-only dep. Pulling `compat.vulkan` to satisfy them would force a Vulkan loader on every consumer of a memory allocator and fight anyone dispatching through volk, so the generated TU selects the dynamic path instead and VMA resolves everything through `VmaVulkanFunctions`. Note the implementation is C++ despite the C-shaped API, so the generated file is `.cpp`) | | Runtime loader compat (pure sources, sidestepping upstream codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua) (the Khronos loader: `loader/generated/` is checked in, and the assembly path degrades to plain C through `UNKNOWN_FUNCTIONS_SUPPORTED`, so no CMake/Python/assembler is needed; windows deferred) · [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | | Whole-source direct build + generated config (only where a platform lacks one) | [`compat.curl`](pkgs/c/compat.curl.lua) (win32 uses upstream's checked-in config, unix generates one) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua) (win/mac use upstream's checked-in config; linux generates one and enables X11 by hand) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua) (91 TUs; the release tarball already ships `ares_build.h` and a Windows config, so only `ares_config.h` is snapshotted per OS) · [`compat.msdfgen`](pkgs/c/compat.msdfgen.lua) (msdfgen 1.13 — the config is not optional here: `core/base.h` opens with `#include `, so without generating it nothing compiles, not even `core/`. Generating it rather than passing `-D` flags is also what makes the library and its consumers agree BY CONSTRUCTION — `base.h` is reached from every public header, so the file is the single place that says which of SVG/PNG/Skia exist. Of the four `ext/` units only `import-font.cpp` is built; the other three each need a library this index does not carry, and their declarations disappear through the same generated config. `MSDFGEN_USE_CPP11` is left off on purpose: it adds move constructors to `Bitmap`, so it changes the layout of a type that crosses the library boundary, and a package cannot guarantee every consumer defines it identically) | -| Upstream amalgamation (one TU is the whole library) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua) (HarfBuzz 14.3.0 — upstream builds with meson, and reproducing that here would mean tracking ~137 `.cc` files plus a generated config. `src/harfbuzz.cc` is upstream's own supported "compile one file" path, so `sources` is a single line that cannot drift out of sync with a release. The amalgamation also `#include`s the CoreText/DirectWrite/GDI/GLib/Graphite2 backends, each behind its own `HAVE_*` gate, so naming only `HAVE_FREETYPE` selects the FreeType bridge and compiles the rest to nothing. `HB_NO_MT` is deliberately NOT set: it removes HarfBuzz's atomics, which is only sound under a single-threading promise a shared package cannot make for its consumers) · [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua) (mimalloc 3.4.5 — the opposite lesson: it also ships an amalgamation (`src/static.c`), and using it would be wrong. A `src/*.c` glob is wrong three ways, each a LINK error rather than a compile error — `static.c` duplicates every symbol, and `free.c`/`alloc-override.c` are `#include`d by `alloc.c` rather than being TUs — so the source list is upstream's own `mi_sources`. `MI_MALLOC_OVERRIDE` stays off: a dependency silently taking over the process allocator is not a package's call) · [`compat.miniaudio`](pkgs/c/compat.miniaudio.lua) (miniaudio 0.11.25 — `miniaudio.c` is upstream's own two-line `MINIAUDIO_IMPLEMENTATION` driver and its CMake library target, so `sources` is one line that tracks the release. The Linux link line is `-ldl -lpthread -lm` and deliberately NOT `-lasound`/`-lpulse`: miniaudio `dlopen`s its backends, so the package builds on a machine that has neither) · [`compat.spirv-reflect`](pkgs/c/compat.spirv-reflect.lua) (Khronos' SPIR-V reflection library; `spirv_reflect.c` is exactly upstream's `spirv-reflect-static` target. Both `*` and `*/include` are exposed so the default `"./include/spirv/unified1/spirv.h"` and the `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` spelling resolve to the SAME bundled grammar header — a consumer that defines that macro cannot silently get a different SPIR-V revision than this `.c` was written against. Versioned by SDK line to stay in step with compat.vulkan-headers) | +| Upstream amalgamation (one TU is the whole library) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua) (HarfBuzz 14.3.0 — upstream builds with meson, and reproducing that here would mean tracking ~137 `.cc` files plus a generated config. `src/harfbuzz.cc` is upstream's own supported "compile one file" path, so `sources` is a single line that cannot drift out of sync with a release. The amalgamation also `#include`s the CoreText/DirectWrite/GDI/GLib/Graphite2 backends, each behind its own `HAVE_*` gate, so naming only `HAVE_FREETYPE` selects the FreeType bridge and compiles the rest to nothing. `HB_NO_MT` is deliberately NOT set: it removes HarfBuzz's atomics, which is only sound under a single-threading promise a shared package cannot make for its consumers) · [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua) (mimalloc 3.4.5 — the opposite lesson: it also ships an amalgamation (`src/static.c`), and using it would be wrong. A `src/*.c` glob is wrong three ways, each a LINK error rather than a compile error — `static.c` duplicates every symbol, and `free.c`/`alloc-override.c` are `#include`d by `alloc.c` rather than being TUs — so the source list is upstream's own `mi_sources`. `MI_MALLOC_OVERRIDE` stays off: a dependency silently taking over the process allocator is not a package's call) · [`compat.miniaudio`](pkgs/c/compat.miniaudio.lua) (miniaudio 0.11.25 — `miniaudio.c` is upstream's own two-line `MINIAUDIO_IMPLEMENTATION` driver and its CMake library target, so `sources` is one line that tracks the release. The Linux link line is `-ldl -lpthread -lm` and deliberately NOT `-lasound`/`-lpulse`: miniaudio `dlopen`s its backends, so the package builds on a machine that has neither) · [`compat.spirv-reflect`](pkgs/c/compat.spirv-reflect.lua) (Khronos' SPIR-V reflection library; `spirv_reflect.c` is exactly upstream's `spirv-reflect-static` target. Both `*` and `*/include` are exposed so the default `"./include/spirv/unified1/spirv.h"` and the `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` spelling resolve to the SAME bundled grammar header — a consumer that defines that macro cannot silently get a different SPIR-V revision than this `.c` was written against. Versioned by SDK line to stay in step with compat.vulkan-headers) | · [`compat.reflectcpp`](pkgs/c/compat.reflectcpp.lua) (reflect-cpp 0.25.0 — upstream ships one umbrella TU per BACKEND, and only two of them are compiled. The other nine (avro / bson / capnproto / cbor / flexbuf / msgpack / toml / xml / yaml) each `#include` a third-party library's headers, so compiling them would turn a dependency-free package into one with nine dependencies; they belong behind features with their own `deps`. The yyjson question resolves the other way: `rfl/json/*.hpp` probes `__has_include()` and falls back to its own `include/rfl/thirdparty/yyjson.h`, so exposing only `include/` keeps the VENDORED copy in play and the package cannot disagree with `compat.yyjson` about a version. `include/rfl/thirdparty` is a second include root only because `src/yyjson.c` includes `"yyjson.h"` flat) | Upstream codegen frozen into the mirror archive | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua) (two versions: `4.5.0` = the `godot-4.5-stable` bindings, `10.0.0-rc1` = godot-cpp's own 10.x line, whose bindings target Godot 4.6. The ~1000 GDExtension classes under `gen/` exist in no upstream tag archive — upstream's `binding_generator.py` emits them at build time. Running it once offline and publishing upstream's tree byte-for-byte **plus** `gen/` keeps Python off the consumer side entirely; `tools/godot-cpp/repack.sh` reproduces the archive deterministically and refuses to publish if any upstream file differs) | | Header package filling a gap in the index | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua) (libglvnd's `GL/glx.h`, absent from the Khronos registry and required by SDL's X11 backend) | | C++ application framework compat (dependencies reuse packages already in the index) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua) (upstream's `3rd/` ships 8 vendored dependencies; none of them is compiled here — all are redirected to the same-version `compat.*` packages in this index) | diff --git a/README.zh-CN.md b/README.zh-CN.md index a6fce19..89b5fc1 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -35,16 +35,17 @@ 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) | +| 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.websocket`](pkgs/c/compat.websocket.lua)(IXWebSocket 12.0.1 —— 从上游 `IXWEBSOCKET_SOURCES` 剔掉 4 个 server TU 后直编的纯 RFC 6455 客户端,**基座零外部依赖**:TLS 关闭(OpenSSL/MbedTLS/AppleSSL 三组 TU 均不编),`IXWEBSOCKET_USE_ZLIB` 不定义(gzip codec 编译为 no-op)。两个可选 feature 在基座上叠加:`server`(4 个 server TU —— `IXWebSocketServer`/`IXSocketServer`/`IXHttpServer`/`IXWebSocketProxyServer`,零新增外部依赖,且 **implies `zlib`** —— 因为上游 server 默认就宣称 permessage-deflate,而 transport 的协商不受宏门控)与 `zlib`(依赖 `compat.zlib`,把 codec 变成真正的 permessage-deflate 压缩)。默认构建的测试自带基于 loopback 原始 socket 的最小 RFC 6455 echo server(握手/掩码/分片/关闭全部离线实测);第二个成员 `websocket-features` 跑真实的 `ix::WebSocketServer`,并断言压缩在线路上可观测 —— 64 KiB 重复载荷往返,`wireSize` = 80) | | 数据库客户端 + 源码构建的驱动管理器 | [`compat.nanodbc`](pkgs/c/compat.nanodbc.lua)(nanodbc 2.14.0,上游已冻结 —— 单 TU 封装平台 ODBC 驱动管理器。两处修复让这份四年前的源码在此可编译、可运行:一个 force-include 的 `char_traits` 补丁头(标准留给用户的定制点,以 `_LIBCPP_VERSION` 为界,不影响 libstdc++/MSVC;注意 `-include` 只能经 `cxxflags` 到达 C++ TU,`cflags` 够不着),以及对驱动管理器本身的分平台答案 —— windows 链 SDK 的 odbc32、macOS 链系统自带的 iODBC,linux 则依赖 `compat.unixodbc`,因为 mcpp 的运行时闭包检查不接受只有宿主才有的 `libodbc.so.2`。测试断言管理器自身的诊断能穿过封装层 —— 包括 nanodbc 已冻结的、会把 SQL state 末字符截掉的 off-by-one)· [`compat.unixodbc`](pkgs/c/compat.unixodbc.lua)(unixODBC 2.3.14,E 叠 A 形态 —— DM + odbcinst + ini/log/lst + libltdl 静态编入单一 `odbc` 目标,与上游 libodbc.a 符号集一致,消费者不带任何 `libodbc.so.2` NEEDED。唯一非常规之处是无 libtool 的 ltdl 接线:`-DLTDLOPEN=libltdlc` 加一张生成的 `lt_libltdlc_LTX_preloaded_symbols` 表(从 libtool 目标文件的重定位记录还原)注册 dlopen loader。冻结的 `config.h` 把 ltdl 自己的 configure 输出合并进顶层(冲突宏 ltdl 源码并不读),绕开了无法在管道中幸存的带引号 `-DLT_CONFIG_H`。已与同 tarball 的 libtool 构建对比验证:IM002 错误路径与 `lt_dlopen` 行为完全一致) | +| C 源码 compat(用**生成的 config** 关掉一档 ISA) | [`compat.libwebp`](pkgs/c/compat.libwebp.lua)(117 个 TU 用五条目录通配写完,外加一个真实的取舍。libwebp 的 SSE4.1 门是 `(__SSE4_1__ || WEBP_MSC_SSE41) && (!HAVE_CONFIG_H || WEBP_HAVE_SSE41)`,而 `WEBP_MSC_SSE41` **只**看 `_MSC_VER` —— 每个 MSVC ABI 编译器都定义它,clang 也不例外,但只有 cl.exe 允许不带 target flag 使用任意 intrinsic。于是 clang 下 SSE4.1 那批源码报 `always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3'`。上游用 **per-file** `-msse4.1` 解决,而描述符没有这个字段;整包加上去,clang 就会在**基线** TU 里也发 SSE4.1,绕过 libwebp 自己的运行期分发 —— 那是 SIGILL 而不是回退。所以本包用上游同一套机制的另一半:`HAVE_CONFIG_H` + 生成的 `src/webp/config.h`,只声明 SSE2 与 NEON、不声明 SSE4.1,`dec_sse41.c` 等随之变成上游的 `WEBP_DSP_INIT_STUB`,对应的 `VP8DspInitSSE41()` 调用点也一并消失。`src/demux` / `src/mux` 是上游各自独立、各带公开头文件的库,在有人需要之前不收) | | header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | | header-only(无可门控组件) | [`compat.CLI11`](pkgs/c/compat.CLI11.lua)(命令行解析器,全部定义都是 `CLI11_INLINE`,故整包就是 `*/include` 加一个 anchor TU。上游两个额外件都不收:`src/Precompile.cpp` 只有在 `CLI11_COMPILE` 同时到达**消费者** TU 时才有意义 —— 那是 interface define,不是 sources 门控;`src/modules/CLI11.cppm` 属于模块层,是另一种包形态,而非 compat 包的 feature) · [`compat.gtl`](pkgs/c/compat.gtl.lua)(Greg's Template Library —— Swiss-table 的 `flat_hash_map` 家族,外加 btree 与 bit_vector。只取 `*/include` 而非 tarball 根:`tests/` 与 `examples/` 各自带头文件,而 `include/` 正是上游 INTERFACE target 暴露的范围,消费者不会误解析到测试代码) · [`compat.plf-hive`](pkgs/c/compat.plf-hive.lua)(提案中 `std::hive` 的参考实现;整库就是 tarball 根下一个文件,故全包即 `*` 加一个 anchor TU。上游不打 tag,于是版本用 commit 归档上的日期 —— 沿用 compat.khrplatform 的先例) | | 单头库 + **生成**实现 TU | [`compat.nanosvg`](pkgs/c/compat.nanosvg.lua)(两个 stb 风格头文件,实现藏在 `NANOSVG_IMPLEMENTATION` / `NANOSVGRAST_IMPLEMENTATION` 之后。上游不提供 `.c` —— 其示例是就地 define 宏 —— 故本包生成一个,把两半各实例化一次。这才让「一堆头文件」变成可链接的包,并把重复符号的风险从每个消费者收敛到唯一一处:消费者不得再次定义这两个宏。测试刻意同时链接 `nsvgParse` 与 `nsvgRasterize`,使「只实例化了一半」的包在此处就失败,而不是流到下游)· [`compat.vulkan-memory-allocator`](pkgs/c/compat.vulkan-memory-allocator.lua)(VMA 3.4.0,同一形态,但生成的 TU 还得做一个**策略**选择。VMA 默认 `VMA_STATIC_VULKAN_FUNCTIONS 1`,会按名字引用 `vkBindBufferMemory2` 等八个符号 —— 对「只依赖头文件」而言就是八个未定义引用。为此拉入 `compat.vulkan` 是错的:那会逼所有用内存分配器的消费者都链上 Vulkan loader,并与通过 volk 自行分发的项目冲突。故生成的 TU 改走动态路径,VMA 一律经 `VmaVulkanFunctions` 解析。注意其 API 形如 C 但实现是 C++,故生成文件为 `.cpp`)| | 运行时 loader compat(纯源码,绕开上游 codegen/asm) | [`compat.vulkan`](pkgs/c/compat.vulkan.lua)(Khronos loader:`loader/generated/` 已签入,汇编路径经 `UNKNOWN_FUNCTIONS_SUPPORTED` 降级为纯 C,故无需 CMake/Python/汇编器;windows 延后)· [`compat.vulkan-headers`](pkgs/c/compat.vulkan-headers.lua) | | 全源码直编 + 生成 config(仅缺口平台) | [`compat.curl`](pkgs/c/compat.curl.lua)(win32 用上游签入 config,unix 生成) · [`compat.sdl2`](pkgs/c/compat.sdl2.lua)(win/mac 用上游签入 config,linux 生成 + 手工开 X11) · [`compat.c-ares`](pkgs/c/compat.c-ares.lua)(91 TU;release tarball 已自带 `ares_build.h` 与 Windows 配置,故只需按 OS 冻结 `ares_config.h`) · [`compat.msdfgen`](pkgs/c/compat.msdfgen.lua)(msdfgen 1.13 —— 这里的 config 不是可选项:`core/base.h` 开头就是 `#include `,不生成它连 `core/` 都编不了。选择生成它而非传 `-D`,还使库与消费者**天然一致** —— `base.h` 被每个公开头间接包含,于是该文件成为「SVG/PNG/Skia 哪些存在」的唯一出处。`ext/` 四个单元只编 `import-font.cpp`,其余三个各需一个本索引没有的库,其声明经由同一份生成 config 一并消失。`MSDFGEN_USE_CPP11` 刻意不开:它给 `Bitmap` 增加移动构造,即改变了跨库边界类型的布局,而包无法保证每个消费者都同样定义它) | -| 上游 amalgamation(单 TU 即整库) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua)(HarfBuzz 14.3.0 —— 上游用 meson,在此复刻意味着跟踪 ~137 个 `.cc` 加一份生成的 config。`src/harfbuzz.cc` 正是上游自己支持的「只编一个文件」路径,于是 `sources` 只有一行,且不会与 release 脱节。该 amalgamation 同时 `#include` 了 CoreText/DirectWrite/GDI/GLib/Graphite2 各后端,每个都有自己的 `HAVE_*` 门,因此仅声明 `HAVE_FREETYPE` 即可精确选中 FreeType 桥接,其余编译为空。`HB_NO_MT` 刻意不设:它会去掉 HarfBuzz 的原子操作与锁,仅在消费者保证单线程时才成立,而共享包无法替消费者作此承诺)· [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua)(mimalloc 3.4.5 —— 反向的教训:它同样带 amalgamation(`src/static.c`),但用它是错的。`src/*.c` 通配会在三处出错,且每一处都是**链接期**而非编译期报错 —— `static.c` 会让每个符号重复,`free.c` / `alloc-override.c` 则是被 `alloc.c` `#include` 的、并非独立 TU —— 故源列表取上游自己的 `mi_sources`。`MI_MALLOC_OVERRIDE` 保持关闭:让一个依赖悄悄接管进程分配器,不该由包来决定) · [`compat.miniaudio`](pkgs/c/compat.miniaudio.lua)(miniaudio 0.11.25 —— `miniaudio.c` 就是上游自己那两行 `MINIAUDIO_IMPLEMENTATION` 驱动文件,也是其 CMake 库目标,故 `sources` 只有一行且随 release 走。Linux 链接行是 `-ldl -lpthread -lm`,刻意不含 `-lasound`/`-lpulse`:miniaudio 用 `dlopen` 加载后端,因此在两者都没有的机器上依然能构建)· [`compat.spirv-reflect`](pkgs/c/compat.spirv-reflect.lua)(Khronos 的 SPIR-V 反射库;`spirv_reflect.c` 恰是上游 `spirv-reflect-static` 目标。同时暴露 `*` 与 `*/include`,使默认的 `"./include/spirv/unified1/spirv.h"` 与 `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` 两种写法解析到**同一份**内置语法头 —— 定义了该宏的消费者不会悄悄拿到与这份 `.c` 不匹配的 SPIR-V 修订。版本按 SDK 线号,与 compat.vulkan-headers 保持同步) | +| 上游 amalgamation(单 TU 即整库) | [`compat.harfbuzz`](pkgs/c/compat.harfbuzz.lua)(HarfBuzz 14.3.0 —— 上游用 meson,在此复刻意味着跟踪 ~137 个 `.cc` 加一份生成的 config。`src/harfbuzz.cc` 正是上游自己支持的「只编一个文件」路径,于是 `sources` 只有一行,且不会与 release 脱节。该 amalgamation 同时 `#include` 了 CoreText/DirectWrite/GDI/GLib/Graphite2 各后端,每个都有自己的 `HAVE_*` 门,因此仅声明 `HAVE_FREETYPE` 即可精确选中 FreeType 桥接,其余编译为空。`HB_NO_MT` 刻意不设:它会去掉 HarfBuzz 的原子操作与锁,仅在消费者保证单线程时才成立,而共享包无法替消费者作此承诺)· [`compat.mimalloc`](pkgs/c/compat.mimalloc.lua)(mimalloc 3.4.5 —— 反向的教训:它同样带 amalgamation(`src/static.c`),但用它是错的。`src/*.c` 通配会在三处出错,且每一处都是**链接期**而非编译期报错 —— `static.c` 会让每个符号重复,`free.c` / `alloc-override.c` 则是被 `alloc.c` `#include` 的、并非独立 TU —— 故源列表取上游自己的 `mi_sources`。`MI_MALLOC_OVERRIDE` 保持关闭:让一个依赖悄悄接管进程分配器,不该由包来决定) · [`compat.miniaudio`](pkgs/c/compat.miniaudio.lua)(miniaudio 0.11.25 —— `miniaudio.c` 就是上游自己那两行 `MINIAUDIO_IMPLEMENTATION` 驱动文件,也是其 CMake 库目标,故 `sources` 只有一行且随 release 走。Linux 链接行是 `-ldl -lpthread -lm`,刻意不含 `-lasound`/`-lpulse`:miniaudio 用 `dlopen` 加载后端,因此在两者都没有的机器上依然能构建)· [`compat.spirv-reflect`](pkgs/c/compat.spirv-reflect.lua)(Khronos 的 SPIR-V 反射库;`spirv_reflect.c` 恰是上游 `spirv-reflect-static` 目标。同时暴露 `*` 与 `*/include`,使默认的 `"./include/spirv/unified1/spirv.h"` 与 `SPIRV_REFLECT_USE_SYSTEM_SPIRV_H` 两种写法解析到**同一份**内置语法头 —— 定义了该宏的消费者不会悄悄拿到与这份 `.c` 不匹配的 SPIR-V 修订。版本按 SDK 线号,与 compat.vulkan-headers 保持同步) · [`compat.reflectcpp`](pkgs/c/compat.reflectcpp.lua)(reflect-cpp 0.25.0 —— 上游按**后端**各出一个伞形 TU,这里只编两个。另外九个(avro / bson / capnproto / cbor / flexbuf / msgpack / toml / xml / yaml)各自 `#include` 一个第三方库的头文件,编了就把一个零依赖包变成九依赖包;它们该以 feature 形态各带 `deps` 单独进来。yyjson 的取舍反过来:`rfl/json/*.hpp` 用 `__has_include()` 探测,探不到就回落到自带的 `include/rfl/thirdparty/yyjson.h`,所以只暴露 `include/` 就保住了**内置副本**,本包也就不可能和 `compat.yyjson` 在版本上打架。`include/rfl/thirdparty` 之所以要作为第二个 include root,只是因为 `src/yyjson.c` 是平铺 include `"yyjson.h"` 的) | | 上游 codegen 前置冻结进镜像归档 | [`compat.godot-cpp`](pkgs/c/compat.godot-cpp.lua)(两个版本:`4.5.0` 是 `godot-4.5-stable` 的绑定,`10.0.0-rc1` 是 godot-cpp 自己的 10.x 线、对应 Godot 4.6。`gen/` 下约 1000 个 GDExtension 类不在任何上游 tag 归档里,由上游 `binding_generator.py` 在构建时生成。改为离线跑一次,把上游源码树逐字节原样 **加上** `gen/` 一起发布,消费侧就完全不需要 Python;`tools/godot-cpp/repack.sh` 可确定性复现该归档,且上游文件一旦有出入即拒绝打包) | | 补索引空缺的头文件包 | [`compat.glx-headers`](pkgs/c/compat.glx-headers.lua)(libglvnd 的 `GL/glx.h`,Khronos registry 不含,SDL 的 X11 后端必需) | | C++ 应用框架 compat(依赖复用索引内既有包) | [`compat.eui-neo`](pkgs/e/compat.eui-neo.lua)(上游 `3rd/` 自带 8 个 vendored 依赖,此处一个不编,全部改指索引内同版本 `compat.*`) | diff --git a/mcpp.toml b/mcpp.toml index eab4d02..c8c6dd2 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -59,6 +59,7 @@ members = [ "tests/examples/opencv-module", "tests/examples/opencv-module-dnn", "tests/examples/opencv-module-unifont", + "tests/examples/reflectcpp", "tests/examples/re2", "tests/examples/redis-plus-plus", "tests/examples/redis-plus-plus-v133", @@ -68,6 +69,7 @@ members = [ "tests/examples/freetype", "tests/examples/glad", "tests/examples/libpng", + "tests/examples/libwebp", "tests/examples/llamacpp", "tests/examples/llamacpp-metal", "tests/examples/llmapi", @@ -79,6 +81,7 @@ members = [ "tests/examples/websocket", "tests/examples/websocket-features", "tests/examples/tray", + "tests/examples/xxhash", "tests/examples/yyjson", "tests/examples/magic_enum", "tests/examples/mimalloc", diff --git a/pkgs/c/compat.libwebp.lua b/pkgs/c/compat.libwebp.lua new file mode 100644 index 0000000..44cfc25 --- /dev/null +++ b/pkgs/c/compat.libwebp.lua @@ -0,0 +1,155 @@ +-- compat.libwebp — the WebP image codec, encode + decode, built from upstream +-- source rather than linked against a host libwebp. +-- +-- Shape A (C-source compat): a consumer writes `#include ` / +-- `` and links one lib. +-- +-- WHAT IS IN THE LIB. dec + enc + dsp + utils + sharpyuv, as five directory +-- globs. sharpyuv is inside the SAME archive rather than a package of its own +-- because the encoder's picture_csp_enc.c calls into it directly — upstream's +-- CMake builds it as a separate target only to reuse it elsewhere, and there is +-- nothing else here to reuse it. +-- +-- WHAT IS NOT. `src/demux` and `src/mux` (animation and metadata containers: +-- WebPDemux* / WebPMux*) are separate upstream libraries with their own public +-- headers; they belong in a feature, and nothing asks for them yet. +-- `examples/`, `extras/`, `imageio/` and `swig/` are tools and bindings. +-- +-- SIMD, and the one thing this package has to decide. Every +-- `src/dsp/*_sse2.c`, `*_sse41.c`, `*_neon.c`, `*_mips*.c`, `*_msa.c` variant is +-- compiled unconditionally; which of them produce CODE is decided by +-- `src/dsp/cpu.h`, and the ones that do not fall back to a `WEBP_DSP_INIT_STUB` +-- so the link stays complete either way. That is what makes the directory glob +-- safe and why there are no per-file flags here. +-- +-- SSE4.1 IS TURNED OFF, on purpose. Its gate is +-- +-- #if (defined(__SSE4_1__) || defined(WEBP_MSC_SSE41)) && \ +-- (!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_SSE41)) +-- +-- and `WEBP_MSC_SSE41` keys off `_MSC_VER` alone. Every MSVC-ABI compiler +-- defines that — including clang — but only cl.exe actually lets any intrinsic +-- be used without a target flag. Under clang the SSE4.1 sources then fail with +-- +-- always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3', +-- but would be inlined into function 'VP8L32bToPlanar_SSE41' that is +-- compiled without support for 'ssse3' +-- +-- Upstream's CMake answers this with a PER-FILE `-msse4.1`, which mcpp has no +-- field for. Adding it package-wide instead would let clang emit SSE4.1 in the +-- BASELINE translation units too, past libwebp's own runtime CPU dispatch — an +-- artifact that SIGILLs on a pre-2008 CPU rather than falling back. So this +-- package takes the other half of upstream's mechanism: `HAVE_CONFIG_H` plus a +-- generated `src/webp/config.h` that names SSE2 and NEON and NOT SSE4.1. +-- x86-64's baseline SSE2 and aarch64's baseline NEON both need no flag and stay +-- on; SSE4.1's `VP8DspInitSSE41` becomes the stub and its call site disappears +-- with `WEBP_HAVE_SSE41`. +-- +-- INCLUDE ROOTS, both of them. The library's own sources include flat from the +-- archive root (`#include "src/dsp/dsp.h"`, `"sharpyuv/sharpyuv.h"`), while +-- consumers include `` — which lives at `src/webp/decode.h`. Both +-- roots therefore travel to dependents. +package = { + spec = "1", + namespace = "compat", + name = "libwebp", + description = "WebP image codec: encode + decode + sharpyuv, built from upstream sources", + licenses = {"BSD-3-Clause"}, + repo = "https://github.com/webmproject/libwebp", + type = "package", + + xpm = { + linux = { + ["1.5.0"] = { + url = { + GLOBAL = "https://github.com/webmproject/libwebp/archive/refs/tags/v1.5.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/libwebp/releases/download/1.5.0/libwebp-1.5.0.tar.gz", + }, + sha256 = "668c9aba45565e24c27e17f7aaf7060a399f7f31dba6c97a044e1feacb930f37", + }, + }, + macosx = { + ["1.5.0"] = { + url = { + GLOBAL = "https://github.com/webmproject/libwebp/archive/refs/tags/v1.5.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/libwebp/releases/download/1.5.0/libwebp-1.5.0.tar.gz", + }, + sha256 = "668c9aba45565e24c27e17f7aaf7060a399f7f31dba6c97a044e1feacb930f37", + }, + }, + windows = { + ["1.5.0"] = { + url = { + GLOBAL = "https://github.com/webmproject/libwebp/archive/refs/tags/v1.5.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/libwebp/releases/download/1.5.0/libwebp-1.5.0.tar.gz", + }, + sha256 = "668c9aba45565e24c27e17f7aaf7060a399f7f31dba6c97a044e1feacb930f37", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*", "*/src", "mcpp_generated" }, + -- The header libwebp's autotools build generates and its tarball does + -- not ship. Sources reach it as `#include "src/webp/config.h"`, so it + -- has to sit under a root that spells that path. + -- + -- Defining HAVE_CONFIG_H flips EVERY `(!defined(HAVE_CONFIG_H) || + -- defined(WEBP_HAVE_x))` gate in src/dsp/cpu.h from "on unless denied" + -- to "off unless allowed", so this file is the allow-list — which is + -- exactly the control upstream's configure script exercises. + generated_files = { + ["mcpp_generated/src/webp/config.h"] = [==[ +#ifndef MCPP_COMPAT_LIBWEBP_CONFIG_H +#define MCPP_COMPAT_LIBWEBP_CONFIG_H + +/* The SIMD families this package compiles. Both are the BASELINE of their + architecture and need no target flag: SSE2 on x86-64, NEON on aarch64. Each + is still guarded by the compiler's own macro first, so naming both here is + safe on either architecture. */ +#define WEBP_HAVE_SSE2 +#define WEBP_HAVE_NEON + +/* WEBP_HAVE_SSE41 is deliberately absent — see the descriptor header. Leaving + it out turns dec_sse41.c and friends into WEBP_DSP_INIT_STUB and removes the + `if (VP8GetCPUInfo(kSSE4_1)) VP8DspInitSSE41();` call, so the library still + links and still dispatches, one tier lower. */ + +/* MIPS/MSA are likewise absent: nothing here targets them, and under + HAVE_CONFIG_H silence means off. */ + +#if defined(__GNUC__) || defined(__clang__) +#define HAVE_BUILTIN_BSWAP16 +#define HAVE_BUILTIN_BSWAP32 +#define HAVE_BUILTIN_BSWAP64 +#endif + +#endif /* MCPP_COMPAT_LIBWEBP_CONFIG_H */ +]==], + }, + cflags = { "-DHAVE_CONFIG_H" }, + -- 117 translation units, as five directory globs. Each directory holds + -- only library code; the tools live in examples/ extras/ imageio/ swig/ + -- and the container libraries in src/demux src/mux, none of which are + -- matched here. + sources = { + "*/src/dec/*.c", + "*/src/enc/*.c", + "*/src/dsp/*.c", + "*/src/utils/*.c", + "*/sharpyuv/*.c", + }, + targets = { ["webp"] = { kind = "lib" } }, + deps = { }, + + -- libm for the dsp math, pthread for utils/thread_utils.c's worker pool. + -- Both are sysroot components, not third-party host libraries. Windows + -- needs neither: the CRT carries the math and thread_utils uses the Win32 + -- thread API directly. + linux = { ldflags = { "-lpthread", "-lm" } }, + macosx = { ldflags = { "-lpthread", "-lm" } }, + }, +} diff --git a/pkgs/c/compat.reflectcpp.lua b/pkgs/c/compat.reflectcpp.lua new file mode 100644 index 0000000..0288155 --- /dev/null +++ b/pkgs/c/compat.reflectcpp.lua @@ -0,0 +1,81 @@ +-- compat.reflectcpp — reflect-cpp, compile-time reflection for C++ structs plus +-- the serialization formats built on it. A consumer writes `#include ` +-- and `#include `. +-- +-- Shape A with an umbrella TU. Upstream ships ONE umbrella source per backend +-- (`src/reflectcpp.cpp` pulls the five core `rfl/*.cpp`, `src/reflectcpp_json.cpp` +-- pulls `rfl/json/{Writer,to_schema}.cpp`), which is what its own CMake compiles. +-- +-- ONLY core + JSON are compiled here. The avro / bson / capnproto / cbor / +-- flexbuf / msgpack / toml / xml / yaml umbrellas each `#include` a third-party +-- library's headers — avro-c, libbson, capnproto, tinyxml2, yaml-cpp, … — so +-- compiling them would turn an otherwise dependency-free package into one with +-- nine external dependencies. They belong behind features, each with its own +-- `deps`, once there is a descriptor for the library each needs. +-- +-- yyjson comes from INSIDE reflect-cpp, and that is deliberate. `rfl/json/*.hpp` +-- probes `__has_include()`: if an external yyjson is on the include +-- path it uses that one, otherwise it falls back to +-- `include/rfl/thirdparty/yyjson.h`. Exposing only `include/` keeps the vendored +-- copy in play, so this package does NOT depend on compat.yyjson and cannot +-- disagree with it about yyjson's version. `src/yyjson.c` — the vendored +-- implementation — includes `"yyjson.h"` flat, which is why +-- `include/rfl/thirdparty` is a second include root: it is where that header is. +-- +-- A consumer that wants the *external* yyjson simply depends on compat.yyjson +-- too; its include root then wins the `__has_include` probe. Nothing here has +-- to change for that, but the two copies must then agree on +-- `yyjson_api_inline`, so it is not the default. +package = { + spec = "1", + namespace = "compat", + name = "reflectcpp", + description = "Compile-time reflection and serialization for C++ structs (core + JSON)", + licenses = {"MIT"}, + repo = "https://github.com/getml/reflect-cpp", + type = "package", + + xpm = { + linux = { + ["0.25.0"] = { + url = { + GLOBAL = "https://github.com/getml/reflect-cpp/archive/refs/tags/v0.25.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/reflectcpp/releases/download/0.25.0/reflectcpp-0.25.0.tar.gz", + }, + sha256 = "de74d3793fd3dde9105ebe0f40bffb28df7009d59e0714389e4d29fcb46a1a3f", + }, + }, + macosx = { + ["0.25.0"] = { + url = { + GLOBAL = "https://github.com/getml/reflect-cpp/archive/refs/tags/v0.25.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/reflectcpp/releases/download/0.25.0/reflectcpp-0.25.0.tar.gz", + }, + sha256 = "de74d3793fd3dde9105ebe0f40bffb28df7009d59e0714389e4d29fcb46a1a3f", + }, + }, + windows = { + ["0.25.0"] = { + url = { + GLOBAL = "https://github.com/getml/reflect-cpp/archive/refs/tags/v0.25.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/reflectcpp/releases/download/0.25.0/reflectcpp-0.25.0.tar.gz", + }, + sha256 = "de74d3793fd3dde9105ebe0f40bffb28df7009d59e0714389e4d29fcb46a1a3f", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/include", "*/include/rfl/thirdparty" }, + sources = { + "*/src/reflectcpp.cpp", + "*/src/reflectcpp_json.cpp", + "*/src/yyjson.c", + }, + targets = { ["reflectcpp"] = { kind = "lib" } }, + deps = { }, + }, +} diff --git a/pkgs/c/compat.xxhash.lua b/pkgs/c/compat.xxhash.lua new file mode 100644 index 0000000..f775fbf --- /dev/null +++ b/pkgs/c/compat.xxhash.lua @@ -0,0 +1,67 @@ +-- compat.xxhash — xxHash, an extremely fast non-cryptographic hash. +-- +-- Shape A (C-source compat): one translation unit, one public header, no +-- configuration. A consumer writes `#include ` and links the lib. +-- +-- COMPILED, not XXH_INLINE_ALL. Upstream offers a header-only mode by defining +-- XXH_INLINE_ALL before the include, which re-emits the whole implementation in +-- every translation unit that hashes anything. That is the right trade only when +-- exactly one TU uses it; a package cannot know that, and the compiled form is +-- what every distribution ships. A consumer that does want the inline mode still +-- can — the header honours the macro regardless of this lib being linked. +-- +-- The xxh_x86dispatch.c dispatcher is deliberately NOT compiled: it exists to +-- select an AVX2/AVX512 path at runtime and requires per-file -mavx2 flags plus +-- XXH_X86DISPATCH at every call site. Without it the SSE2 baseline path is used, +-- which needs no flags and no CPU detection. +package = { + spec = "1", + namespace = "compat", + name = "xxhash", + description = "Extremely fast non-cryptographic hash algorithm (XXH32/XXH64/XXH3)", + licenses = {"BSD-2-Clause"}, + repo = "https://github.com/Cyan4973/xxHash", + type = "package", + + xpm = { + linux = { + ["0.8.3"] = { + url = { + GLOBAL = "https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/xxhash/releases/download/0.8.3/xxhash-0.8.3.tar.gz", + }, + sha256 = "aae608dfe8213dfd05d909a57718ef82f30722c392344583d3f39050c7f29a80", + }, + }, + macosx = { + ["0.8.3"] = { + url = { + GLOBAL = "https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/xxhash/releases/download/0.8.3/xxhash-0.8.3.tar.gz", + }, + sha256 = "aae608dfe8213dfd05d909a57718ef82f30722c392344583d3f39050c7f29a80", + }, + }, + windows = { + ["0.8.3"] = { + url = { + GLOBAL = "https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/xxhash/releases/download/0.8.3/xxhash-0.8.3.tar.gz", + }, + sha256 = "aae608dfe8213dfd05d909a57718ef82f30722c392344583d3f39050c7f29a80", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- Tarball root: xxhash.h, xxh3.h and xxhash.c all sit there, so a + -- consumer's `#include ` resolves against `*/`. + include_dirs = { "*" }, + sources = { "*/xxhash.c" }, + targets = { ["xxhash"] = { kind = "lib" } }, + deps = { }, + }, +} diff --git a/tests/examples/libwebp/mcpp.toml b/tests/examples/libwebp/mcpp.toml new file mode 100644 index 0000000..d87fa1c --- /dev/null +++ b/tests/examples/libwebp/mcpp.toml @@ -0,0 +1,8 @@ +# libwebp test project: consumes compat.libwebp from this repo's own index and +# asserts a real encode -> decode round trip, not just that the symbols link. +[package] +name = "libwebp-tests" +version = "0.1.0" + +[dependencies.compat] +libwebp = "1.5.0" diff --git a/tests/examples/libwebp/tests/roundtrip.cpp b/tests/examples/libwebp/tests/roundtrip.cpp new file mode 100644 index 0000000..bc0e539 --- /dev/null +++ b/tests/examples/libwebp/tests/roundtrip.cpp @@ -0,0 +1,82 @@ +// Behavioral test: encode a synthetic image and decode it back. +// +// Linking is not the interesting question for this package — the source list is +// five directory globs including every SIMD variant, and a variant that failed +// to self-gate would still LINK while producing wrong pixels. So the assertions +// are on the pixels: lossless must round-trip exactly, lossy must stay close, +// and the encoder must actually respond to its quality knob. +#include +#include + +#include +#include +#include +#include + +namespace { + +constexpr int kW = 64; +constexpr int kH = 48; + +// A gradient with a hard edge: smooth enough for the lossy path to do well, +// structured enough that a broken transform shows up as a large delta. +std::vector make_rgba() { + std::vector px(static_cast(kW) * kH * 4); + for (int y = 0; y < kH; ++y) { + for (int x = 0; x < kW; ++x) { + uint8_t* p = &px[(static_cast(y) * kW + x) * 4]; + p[0] = static_cast(x * 4); + p[1] = static_cast(y * 5); + p[2] = static_cast(x < kW / 2 ? 30 : 220); + p[3] = 255; + } + } + return px; +} + +} // namespace + +int main() { + const std::vector src = make_rgba(); + const int stride = kW * 4; + + // --- the header round-trips the dimensions --- + uint8_t* lossless = nullptr; + const std::size_t lossless_size = + WebPEncodeLosslessRGBA(src.data(), kW, kH, stride, &lossless); + assert(lossless_size > 0 && lossless != nullptr); + + int w = 0, h = 0; + assert(WebPGetInfo(lossless, lossless_size, &w, &h) == 1); + assert(w == kW && h == kH); + + // --- lossless must be EXACT --- + uint8_t* decoded = WebPDecodeRGBA(lossless, lossless_size, &w, &h); + assert(decoded != nullptr && w == kW && h == kH); + for (std::size_t i = 0; i < src.size(); ++i) assert(decoded[i] == src[i]); + WebPFree(decoded); + + // --- lossy must be close, and quality must have an effect --- + uint8_t* q90 = nullptr; + const std::size_t q90_size = WebPEncodeRGBA(src.data(), kW, kH, stride, 90.0f, &q90); + assert(q90_size > 0 && q90 != nullptr); + + uint8_t* q10 = nullptr; + const std::size_t q10_size = WebPEncodeRGBA(src.data(), kW, kH, stride, 10.0f, &q10); + assert(q10_size > 0 && q10 != nullptr); + assert(q10_size < q90_size); // a lower quality must produce a smaller file + + decoded = WebPDecodeRGBA(q90, q90_size, &w, &h); + assert(decoded != nullptr && w == kW && h == kH); + long total = 0; + for (std::size_t i = 0; i < src.size(); ++i) + total += std::abs(static_cast(decoded[i]) - static_cast(src[i])); + const double mean_abs_error = static_cast(total) / static_cast(src.size()); + assert(mean_abs_error < 8.0); // quality 90 on a smooth image + WebPFree(decoded); + + WebPFree(lossless); + WebPFree(q90); + WebPFree(q10); + return 0; +} diff --git a/tests/examples/reflectcpp/mcpp.toml b/tests/examples/reflectcpp/mcpp.toml new file mode 100644 index 0000000..a688d7f --- /dev/null +++ b/tests/examples/reflectcpp/mcpp.toml @@ -0,0 +1,8 @@ +# reflect-cpp test project: consumes compat.reflectcpp from this repo's own index +# and asserts struct <-> JSON round trips through the bundled yyjson. +[package] +name = "reflectcpp-tests" +version = "0.1.0" + +[dependencies.compat] +reflectcpp = "0.25.0" diff --git a/tests/examples/reflectcpp/tests/json_roundtrip.cpp b/tests/examples/reflectcpp/tests/json_roundtrip.cpp new file mode 100644 index 0000000..35cac5b --- /dev/null +++ b/tests/examples/reflectcpp/tests/json_roundtrip.cpp @@ -0,0 +1,73 @@ +// Behavioral test: struct -> JSON -> struct, through the JSON backend. +// +// The package compiles two umbrella TUs plus the vendored yyjson. All three have +// to be present and agree: leave out src/yyjson.c and this fails to LINK; get the +// include roots wrong and rfl/json/save.hpp picks a different yyjson than the one +// compiled, which fails at runtime rather than at build time. So the assertions +// go all the way to parsed values. +#include +#include + +#include +#include +#include +#include + +struct Point { + int x; + int y; +}; + +struct Document { + std::string name; + int version; + bool enabled; + std::vector points; + std::optional note; +}; + +int main() { + const Document doc{ + .name = "mcpp", + .version = 23, + .enabled = true, + .points = {{1, 2}, {-3, 4}}, + .note = std::nullopt, + }; + + // --- round trip --- + const std::string json = rfl::json::write(doc); + assert(!json.empty()); + + const auto parsed = rfl::json::read(json); + assert(parsed && "a document this library just wrote must parse"); + const Document& back = parsed.value(); + assert(back.name == doc.name); + assert(back.version == doc.version); + assert(back.enabled == doc.enabled); + assert(back.points.size() == 2); + assert(back.points[0].x == 1 && back.points[0].y == 2); + assert(back.points[1].x == -3 && back.points[1].y == 4); + assert(!back.note.has_value()); + + // --- the field-name rename processor, which is why callers reach for rfl --- + const std::string camel = rfl::json::write(doc); + const auto camel_back = rfl::json::read(camel); + assert(camel_back && camel_back.value().version == doc.version); + + // --- a malformed document must fail, not throw or crash --- + const auto bad = rfl::json::read(R"({"name": 5})"); + assert(!bad && "a type mismatch must surface as an error, not a value"); + + // --- to_generic / from_generic, the path an RPC layer uses when the + // concrete type is only known per method --- + const auto generic = rfl::to_generic(doc); + const auto from_generic = rfl::from_generic(generic); + assert(from_generic && from_generic.value().name == doc.name); + + // --- the JSON Schema generator --- + const std::string schema = rfl::json::to_schema(); + assert(schema.find("version") != std::string::npos); + + return 0; +} diff --git a/tests/examples/xxhash/mcpp.toml b/tests/examples/xxhash/mcpp.toml new file mode 100644 index 0000000..b06de7d --- /dev/null +++ b/tests/examples/xxhash/mcpp.toml @@ -0,0 +1,8 @@ +# xxHash test project: consumes compat.xxhash from this repo's own index and +# asserts the hash values themselves, not just that the symbols link. +[package] +name = "xxhash-tests" +version = "0.1.0" + +[dependencies.compat] +xxhash = "0.8.3" diff --git a/tests/examples/xxhash/tests/digest.cpp b/tests/examples/xxhash/tests/digest.cpp new file mode 100644 index 0000000..f6c7abb --- /dev/null +++ b/tests/examples/xxhash/tests/digest.cpp @@ -0,0 +1,174 @@ +// Behavioral test: the digests themselves, against upstream's own vectors. +// +// A hash package that links but computes a different value is worse than one +// that does not link at all — every caller downstream is content-addressing +// something. So the assertions here are known-answer tests taken verbatim from +// xxHash's `tests/sanity_test_vectors.h`, together with the buffer generator +// from `tests/sanity_test.c` that those vectors were computed over. Nothing is +// derived from running the code under test. +// +// The lengths cover every size branch XXH3 switches on (0, 1-3, 4-8, 9-16, +// 17-128, 129-240, >240) plus the multi-block path at 2048 and 4160, which is +// where a mis-selected SIMD variant or a byte-order slip would show up. +#include + +#include +#include +#include +#include + +namespace { + +// tests/sanity_test.c: fillTestBuffer(). "Its values must not be changed." +constexpr std::uint32_t kPrime32 = 2654435761U; +constexpr std::uint64_t kPrime64 = 11400714785074694797ULL; + +std::vector make_sanity_buffer(std::size_t len) { + std::vector buf(len); + std::uint64_t gen = kPrime32; + for (std::size_t i = 0; i < len; ++i) { + buf[i] = static_cast(gen >> 56); + gen *= kPrime64; + } + return buf; +} + +struct Case32 { std::uint32_t len; std::uint32_t seed; std::uint32_t expected; }; +struct Case64 { std::uint32_t len; std::uint64_t seed; std::uint64_t expected; }; +struct Case128 { std::uint32_t len; std::uint64_t lo; std::uint64_t hi; }; + +// XSUM_XXH32_testdata, seed 0. +constexpr Case32 kXXH32[] = { + { 0, 0x00000000U, 0x02CC5D05U }, { 1, 0x00000000U, 0xCF65B03EU }, + { 2, 0x00000000U, 0x1151BEE4U }, { 3, 0x00000000U, 0xC23884F5U }, + { 4, 0x00000000U, 0xA9DE7CE9U }, { 8, 0x00000000U, 0xA3F6F44BU }, + { 9, 0x00000000U, 0xFFB82A24U }, { 16, 0x00000000U, 0x93BA3759U }, + { 17, 0x00000000U, 0x89FDC23EU }, { 64, 0x00000000U, 0x02E95DBBU }, + { 128, 0x00000000U, 0x0FD07B71U }, { 129, 0x00000000U, 0x68C9EC37U }, + { 222, 0x00000000U, 0x5BD11DBDU }, { 240, 0x00000000U, 0xFA6B6557U }, + { 241, 0x00000000U, 0xE5F7C54DU }, { 403, 0x00000000U, 0x6675FF5AU }, + { 512, 0x00000000U, 0xD485C30AU }, { 2048, 0x00000000U, 0x7C535464U }, + { 4160, 0x00000000U, 0xDB0D9141U }, +}; + +// XSUM_XXH64_testdata, seed 0. +constexpr Case64 kXXH64[] = { + { 0, 0ULL, 0xEF46DB3751D8E999ULL }, { 1, 0ULL, 0xE934A84ADB052768ULL }, + { 2, 0ULL, 0x5D48CD60A77E23FFULL }, { 3, 0ULL, 0xFF7E1959CB50794AULL }, + { 4, 0ULL, 0x9136A0DCA57457EEULL }, { 8, 0ULL, 0xCDBCF538E71D1348ULL }, + { 9, 0ULL, 0x554B1AE991EDA6B6ULL }, { 16, 0ULL, 0x98C90B57FDFCB55CULL }, + { 17, 0ULL, 0x0D39A2D051A30C2CULL }, { 64, 0ULL, 0xEF558F8ACAC2B5CDULL }, + { 128, 0ULL, 0x90CA021457D96DC5ULL }, { 129, 0ULL, 0x41C280132D697ABAULL }, + { 222, 0ULL, 0xB641AE8CB691C174ULL }, { 240, 0ULL, 0xB81838D483BAEE53ULL }, + { 241, 0ULL, 0x95D76C8B4D8FC4D6ULL }, { 403, 0ULL, 0xD99858FEE82283DFULL }, + { 512, 0ULL, 0x4358D2FDD62B58A7ULL }, { 2048, 0ULL, 0x5940F2752BC04387ULL }, + { 4160, 0ULL, 0xEEE6A4E2AC952A5EULL }, +}; + +// XSUM_XXH3_testdata, seed 0 and seed PRIME64 — the seeded path is a different +// branch in XXH3, not a reseeding of the same one. +constexpr Case64 kXXH3[] = { + { 0, 0ULL, 0x2D06800538D394C2ULL }, { 1, 0ULL, 0xC44BDFF4074EECDBULL }, + { 2, 0ULL, 0x7A9978044CB8A8BBULL }, { 3, 0ULL, 0x54247382A8D6B94DULL }, + { 4, 0ULL, 0xE5DC74BC51848A51ULL }, { 8, 0ULL, 0x24CCC9ACAA9F65E4ULL }, + { 9, 0ULL, 0x14D5001C15DD3F2BULL }, { 16, 0ULL, 0x981B17D36C7498C9ULL }, + { 17, 0ULL, 0x796F5ACD3A60F862ULL }, { 64, 0ULL, 0x9CB48487720EC49DULL }, + { 128, 0ULL, 0xFCFF24126754D861ULL }, { 129, 0ULL, 0x98F1B0A679A2CA29ULL }, + { 222, 0ULL, 0xB9163B558664D356ULL }, { 240, 0ULL, 0x81C3C2B67F568CCFULL }, + { 241, 0ULL, 0xC5A639ECD2030E5EULL }, { 403, 0ULL, 0xCDEB804D65C6DEA4ULL }, + { 512, 0ULL, 0x617E49599013CB6BULL }, { 2048, 0ULL, 0xDD59E2C3A5F038E0ULL }, + { 4160, 0ULL, 0x4F323B15321E94E1ULL }, +}; + +constexpr Case64 kXXH3Seeded[] = { + { 0, 0x9E3779B185EBCA8DULL, 0xA8A6B918B2F0364AULL }, + { 1, 0x9E3779B185EBCA8DULL, 0x032BE332DD766EF8ULL }, + { 16, 0x9E3779B185EBCA8DULL, 0x663F29333B4DB6B1ULL }, + { 129, 0x9E3779B185EBCA8DULL, 0x21FFFDBCA099C844ULL }, + { 241, 0x9E3779B185EBCA8DULL, 0xDDA9B0A161D4829AULL }, + { 4160, 0x9E3779B185EBCA8DULL, 0x1BF6F5FAF9EECABDULL }, +}; + +// XSUM_XXH128_testdata, seed 0. +constexpr Case128 kXXH128[] = { + { 0, 0x6001C324468D497FULL, 0x99AA06D3014798D8ULL }, + { 1, 0xC44BDFF4074EECDBULL, 0xA6CD5E9392000F6AULL }, + { 16, 0x562980258A998629ULL, 0xC68C368ECF8A9C05ULL }, + { 129, 0x86C9E3BC8F0A3B5CULL, 0x03815FC91F1B30B6ULL }, + { 241, 0xC5A639ECD2030E5EULL, 0x99A80ECF0ECFC647ULL }, + { 4160, 0x4F323B15321E94E1ULL, 0x67140711C1E3E335ULL }, +}; + +} // namespace + +int main() { + for (const auto& c : kXXH32) { + const auto buf = make_sanity_buffer(c.len); + assert(XXH32(buf.data(), c.len, c.seed) == c.expected); + } + for (const auto& c : kXXH64) { + const auto buf = make_sanity_buffer(c.len); + assert(XXH64(buf.data(), c.len, c.seed) == c.expected); + } + for (const auto& c : kXXH3) { + const auto buf = make_sanity_buffer(c.len); + assert(XXH3_64bits(buf.data(), c.len) == c.expected); + } + for (const auto& c : kXXH3Seeded) { + const auto buf = make_sanity_buffer(c.len); + assert(XXH3_64bits_withSeed(buf.data(), c.len, c.seed) == c.expected); + } + for (const auto& c : kXXH128) { + const auto buf = make_sanity_buffer(c.len); + const XXH128_hash_t h = XXH3_128bits(buf.data(), c.len); + assert(h.low64 == c.lo && h.high64 == c.hi); + } + + // --- streaming must agree with the one-shot form --- + // Split at 3 bytes, which is neither the 16-byte stripe nor the 256-byte + // block, so the internal buffering path is actually exercised. + { + const auto buf = make_sanity_buffer(4160); + XXH64_state_t* st = XXH64_createState(); + assert(st != nullptr); + assert(XXH64_reset(st, 0) == XXH_OK); + assert(XXH64_update(st, buf.data(), 3) == XXH_OK); + assert(XXH64_update(st, buf.data() + 3, buf.size() - 3) == XXH_OK); + assert(XXH64_digest(st) == 0xEEE6A4E2AC952A5EULL); + assert(XXH64_freeState(st) == XXH_OK); + + XXH3_state_t* s3 = XXH3_createState(); + assert(s3 != nullptr); + assert(XXH3_64bits_reset(s3) == XXH_OK); + assert(XXH3_64bits_update(s3, buf.data(), 3) == XXH_OK); + assert(XXH3_64bits_update(s3, buf.data() + 3, buf.size() - 3) == XXH_OK); + assert(XXH3_64bits_digest(s3) == 0x4F323B15321E94E1ULL); + assert(XXH3_freeState(s3) == XXH_OK); + } + + // --- the canonical (big-endian) serialization round-trips --- + { + const auto buf = make_sanity_buffer(512); + const std::uint64_t h64 = XXH64(buf.data(), buf.size(), 0); + XXH64_canonical_t c64; + XXH64_canonicalFromHash(&c64, h64); + assert(c64.digest[0] == static_cast(h64 >> 56)); // big-endian, always + assert(XXH64_hashFromCanonical(&c64) == h64); + + const XXH128_hash_t h128 = XXH3_128bits(buf.data(), buf.size()); + XXH128_canonical_t c128; + XXH128_canonicalFromHash(&c128, h128); + const XXH128_hash_t back = XXH128_hashFromCanonical(&c128); + assert(back.low64 == h128.low64 && back.high64 == h128.high64); + } + + // --- a single flipped bit must change the digest --- + { + auto buf = make_sanity_buffer(4160); + const std::uint64_t before = XXH3_64bits(buf.data(), buf.size()); + buf[buf.size() / 2] ^= 0x01; + assert(XXH3_64bits(buf.data(), buf.size()) != before); + } + + return 0; +}