From 40f098809fcab5eb3d910bb5120c9d94cfce8149 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 7 Nov 2026 07:20:05 +0900 Subject: [PATCH 1/3] mcpplibs.mwrap zlib, zstd, xz, xxhash, libarchive and libc: the six wrappers of the headers mcpp and mcppls include Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/validate.yml | 30 +++++++++++ CHANGELOG.md | 9 ++++ docs/mwrap.md | 5 ++ docs/zh/mwrap.md | 3 ++ mcpp.toml | 6 +++ pkgs/mwrap/libarchive.lua | 51 +++++++++++++++++++ pkgs/mwrap/libc.lua | 49 ++++++++++++++++++ pkgs/mwrap/xxhash.lua | 50 ++++++++++++++++++ pkgs/mwrap/xz.lua | 50 ++++++++++++++++++ pkgs/mwrap/zlib.lua | 50 ++++++++++++++++++ pkgs/mwrap/zstd.lua | 50 ++++++++++++++++++ tests/mwrap/check_installed_template.py | 34 +++++++++++++ tests/mwrap/libarchive-bundle/mcpp.toml | 11 ++++ .../libarchive-bundle/tests/behavior.cpp | 49 ++++++++++++++++++ tests/mwrap/libc-stdio/mcpp.toml | 11 ++++ tests/mwrap/libc-stdio/tests/behavior.cpp | 22 ++++++++ tests/mwrap/libc-stdio/tests/side_header.cpp | 23 +++++++++ tests/mwrap/xxhash-digest/mcpp.toml | 11 ++++ tests/mwrap/xxhash-digest/tests/behavior.cpp | 24 +++++++++ tests/mwrap/xz-pack/mcpp.toml | 11 ++++ tests/mwrap/xz-pack/tests/behavior.cpp | 39 ++++++++++++++ tests/mwrap/xz-pack/tests/side_header.cpp | 14 +++++ tests/mwrap/zlib-compress/mcpp.toml | 11 ++++ tests/mwrap/zlib-compress/tests/behavior.cpp | 29 +++++++++++ .../mwrap/zlib-compress/tests/side_header.cpp | 31 +++++++++++ tests/mwrap/zstd-squeeze/mcpp.toml | 11 ++++ tests/mwrap/zstd-squeeze/tests/behavior.cpp | 32 ++++++++++++ .../mwrap/zstd-squeeze/tests/side_header.cpp | 11 ++++ 28 files changed, 727 insertions(+) create mode 100644 pkgs/mwrap/libarchive.lua create mode 100644 pkgs/mwrap/libc.lua create mode 100644 pkgs/mwrap/xxhash.lua create mode 100644 pkgs/mwrap/xz.lua create mode 100644 pkgs/mwrap/zlib.lua create mode 100644 pkgs/mwrap/zstd.lua create mode 100644 tests/mwrap/libarchive-bundle/mcpp.toml create mode 100644 tests/mwrap/libarchive-bundle/tests/behavior.cpp create mode 100644 tests/mwrap/libc-stdio/mcpp.toml create mode 100644 tests/mwrap/libc-stdio/tests/behavior.cpp create mode 100644 tests/mwrap/libc-stdio/tests/side_header.cpp create mode 100644 tests/mwrap/xxhash-digest/mcpp.toml create mode 100644 tests/mwrap/xxhash-digest/tests/behavior.cpp create mode 100644 tests/mwrap/xz-pack/mcpp.toml create mode 100644 tests/mwrap/xz-pack/tests/behavior.cpp create mode 100644 tests/mwrap/xz-pack/tests/side_header.cpp create mode 100644 tests/mwrap/zlib-compress/mcpp.toml create mode 100644 tests/mwrap/zlib-compress/tests/behavior.cpp create mode 100644 tests/mwrap/zlib-compress/tests/side_header.cpp create mode 100644 tests/mwrap/zstd-squeeze/mcpp.toml create mode 100644 tests/mwrap/zstd-squeeze/tests/behavior.cpp create mode 100644 tests/mwrap/zstd-squeeze/tests/side_header.cpp diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 2731a34a..549324b9 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1675,6 +1675,36 @@ jobs: shell: bash run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.mimalloc@3.6.1 --deploy + - name: Installed zlib wrapper archive, actual default compression template and its relocated notices + if: contains(format(' {0} ', env.MEMBERS), ' zlib-compress ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.zlib@1.3.2 --deploy + + - name: Installed zstd wrapper archive, actual default compression template and its relocated notices + if: contains(format(' {0} ', env.MEMBERS), ' zstd-squeeze ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.zstd@1.5.7 --deploy + + - name: Installed xz wrapper archive, actual default compression template and its relocated notices + if: contains(format(' {0} ', env.MEMBERS), ' xz-pack ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.xz@5.8.3 --deploy + + - name: Installed xxhash wrapper archive, actual default hashing template and its relocated notices + if: contains(format(' {0} ', env.MEMBERS), ' xxhash-digest ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.xxhash@0.8.3 --deploy + + - name: Installed libarchive wrapper archive, actual default archive template and its relocated notices + if: contains(format(' {0} ', env.MEMBERS), ' libarchive-bundle ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.libarchive@3.8.7 --deploy + + - name: Installed libc wrapper archive, actual default C stdio template and its relocated notices + if: contains(format(' {0} ', env.MEMBERS), ' libc-stdio ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.libc@17.0.0 --deploy + - name: Installed libwebp wrapper archive, actual default image template and its relocated notices if: contains(format(' {0} ', env.MEMBERS), ' libwebp-image ') && matrix.toolchain == 'default' shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index f5ce5d89..ba26d4ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,15 @@ ### Added +- **mcpplibs.mwrap.zlib 1.3.2、.zstd 1.5.7、.xz 5.8.3、.xxhash 0.8.3、.libarchive 3.8.7、.libc 17.0.0(新包)。** 六个 mwrap + 包装把头文件收敛到各自的包中(mcpp 设计记录 2026-11-07 D10)。前五个以 C++23 模块使用同版本的 compat 实现:函数、类型、 + 枚举值以上游的全局名导出,常量宏与类型宏在 `.macros` 中为同名常量与别名,带参数的宏在 `/macros.hpp>`; + 导出清单由各仓库的 `tools/generate.py` 按 clang 读到的头文件生成并在 CI 中审计。`mcpplibs.mwrap.libc` 映射 ISO C17 + 附录 B 的对象宏(`stdout()`、`errno()`、`EOF`、`INT_MAX` 等),实现为目标的 C 库。消费者测试 `tests/mwrap/zlib-compress`、 + `zstd-squeeze`、`xz-pack`、`xxhash-digest`、`libarchive-bundle`、`libc-stdio`,以及已安装模板与重定位许可检查(检查脚本以 + `STATIC_NOTICES` 表统一六个包的约定)。GLOBAL 为 GitHub 发布资产,CN 为 GitCode `mcpp-res/mwrap-` 镜像,sha256 + 两处一致。`docs/mwrap.md` 补充 C 标准库这一实现来源。 + - **openkal-macos 0.22.1.** The confinement rules are sized to the grants of each start; sixteen grants named near `MAXPATHLEN` no longer refuse a well-formed start with `kal_err_invalid`. OpenKal is the compatible range `^0.24.0`. First member of the openkal 0.24.1 correction group (mcpp design record 2026-11-07 §4.2.3). diff --git a/docs/mwrap.md b/docs/mwrap.md index ae0d16dd..7c9ffc27 100644 --- a/docs/mwrap.md +++ b/docs/mwrap.md @@ -28,6 +28,11 @@ A wrapper should depend on it when its configuration and ABI are sufficient. Complex implementations may instead vendor pinned sources under `upstream/`. Each implementation has one owner in a program: do not link both a vendored copy and a compat copy of the same library. +The C standard library is the third source: its implementation is the target's +C library, which mcpp selects with the target (glibc, musl, the Windows CRT, +macOS libSystem, openkal-musl, mingw-w64). `mcpplibs.mwrap.libc` links nothing; +its version names the standard (17.0.0 for ISO/IEC 9899:2018), recorded with +`standard` in `[package.metadata.mwrap]`. ## Repository layout diff --git a/docs/zh/mwrap.md b/docs/zh/mwrap.md index 19c75a1a..8c2a343e 100644 --- a/docs/zh/mwrap.md +++ b/docs/zh/mwrap.md @@ -20,6 +20,9 @@ 包和模块名不带 -m,不创建另一套普通 API namespace。已发布旧身份、版本和内容保持不可变, 通过新 mwrap 身份迁移。简单实现优先依赖 compat.xxx;复杂库可以 vendor 到 upstream/。 同一程序的同一实现只能有一个所有者,不能把 compat 与 vendored 实现重复链接。 +C 标准库是第三种实现来源:实现由目标的 C 库提供,由 mcpp 随目标选定(glibc、musl、Windows CRT、macOS libSystem、 +openkal-musl、mingw-w64)。`mcpplibs.mwrap.libc` 不链接任何实现;版本号表示所依据的标准(ISO/IEC 9899:2018 为 17.0.0), +记录在 `[package.metadata.mwrap]` 的 `standard` 中。 ## 仓库分层 diff --git a/mcpp.toml b/mcpp.toml index a1895bf0..88fcd3d3 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -31,6 +31,12 @@ members = [ "tests/mwrap/libvpx-webm", "tests/mwrap/box2d-physics", "tests/mwrap/mimalloc-heap", + "tests/mwrap/zlib-compress", + "tests/mwrap/zstd-squeeze", + "tests/mwrap/xz-pack", + "tests/mwrap/xxhash-digest", + "tests/mwrap/libarchive-bundle", + "tests/mwrap/libc-stdio", "tests/mwrap/miniaudio-engine", "tests/mwrap/freetype-render", "tests/mwrap/harfbuzz-shape", diff --git a/pkgs/mwrap/libarchive.lua b/pkgs/mwrap/libarchive.lua new file mode 100644 index 00000000..7425acab --- /dev/null +++ b/pkgs/mwrap/libarchive.lua @@ -0,0 +1,51 @@ +-- Immutable upstream-aligned mwrap source release (libarchive-m mwrap-3.8.7); +-- GLOBAL and CN bytes are identical. +-- +-- The wrapper is Apache-2.0; the library it links, compat.libarchive +-- (libarchive 3.8.7), is BSD-2-Clause with the exceptions its COPYING lists, +-- linked statically: the package deploys its README and COPYING beside +-- programs (runtime.deploy). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "libarchive", + description = "libarchive-m: libarchive 3.8.7 (tar, cpio, zip, 7-Zip, ISO 9660 … with their compression filters, entries, disk access) as C++23 modules, with the constants of archive.h as C++ constants", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/libarchive-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/libarchive-m/releases/download/${tag}/libarchive-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["3.8.7"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libarchive-m/releases/download/mwrap-3.8.7/libarchive-m-3.8.7.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-libarchive/releases/download/3.8.7/libarchive-m-3.8.7.tar.gz", + }, + sha256 = "b55c102f8743112d94bd57ab8e79b31cdfe49935c1bebaed846b1a4508e9c8f8", + }, + }, + macosx = { + ["3.8.7"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libarchive-m/releases/download/mwrap-3.8.7/libarchive-m-3.8.7.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-libarchive/releases/download/3.8.7/libarchive-m-3.8.7.tar.gz", + }, + sha256 = "b55c102f8743112d94bd57ab8e79b31cdfe49935c1bebaed846b1a4508e9c8f8", + }, + }, + windows = { + ["3.8.7"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libarchive-m/releases/download/mwrap-3.8.7/libarchive-m-3.8.7.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-libarchive/releases/download/3.8.7/libarchive-m-3.8.7.tar.gz", + }, + sha256 = "b55c102f8743112d94bd57ab8e79b31cdfe49935c1bebaed846b1a4508e9c8f8", + }, + }, + }, +} diff --git a/pkgs/mwrap/libc.lua b/pkgs/mwrap/libc.lua new file mode 100644 index 00000000..d5a6923e --- /dev/null +++ b/pkgs/mwrap/libc.lua @@ -0,0 +1,49 @@ +-- Immutable upstream-aligned mwrap source release (libc-m mwrap-17.0.0); +-- GLOBAL and CN bytes are identical. +-- +-- The wrapper is Apache-2.0 and links nothing: the implementation is the +-- target's C library. The version names the standard, ISO/IEC 9899:2018 (C17). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "libc", + description = "libc-m: the C standard library (ISO C17) for C++ modules: std.compat with the object-like macros of ISO C (stdout, errno, EOF, INT_MAX …) as C++ constants and functions", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/libc-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/libc-m/releases/download/${tag}/libc-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["17.0.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libc-m/releases/download/mwrap-17.0.0/libc-m-17.0.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-libc/releases/download/17.0.0/libc-m-17.0.0.tar.gz", + }, + sha256 = "e601179504f0fc94bb4d2f002982b2f5b9cc4b95a50a13822bd9b272519a3a9c", + }, + }, + macosx = { + ["17.0.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libc-m/releases/download/mwrap-17.0.0/libc-m-17.0.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-libc/releases/download/17.0.0/libc-m-17.0.0.tar.gz", + }, + sha256 = "e601179504f0fc94bb4d2f002982b2f5b9cc4b95a50a13822bd9b272519a3a9c", + }, + }, + windows = { + ["17.0.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/libc-m/releases/download/mwrap-17.0.0/libc-m-17.0.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-libc/releases/download/17.0.0/libc-m-17.0.0.tar.gz", + }, + sha256 = "e601179504f0fc94bb4d2f002982b2f5b9cc4b95a50a13822bd9b272519a3a9c", + }, + }, + }, +} diff --git a/pkgs/mwrap/xxhash.lua b/pkgs/mwrap/xxhash.lua new file mode 100644 index 00000000..2a69f114 --- /dev/null +++ b/pkgs/mwrap/xxhash.lua @@ -0,0 +1,50 @@ +-- Immutable upstream-aligned mwrap source release (xxhash-m mwrap-0.8.3); +-- GLOBAL and CN bytes are identical. +-- +-- The wrapper is Apache-2.0; the library it links, compat.xxhash (xxHash +-- 0.8.3), is BSD-2-Clause, linked statically: the package deploys its README +-- and LICENSE beside programs (runtime.deploy). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "xxhash", + description = "xxhash-m: xxHash 0.8.3 (XXH32, XXH64, XXH3 64 and 128 bits, one-shot and streaming) as C++23 modules", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/xxhash-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/xxhash-m/releases/download/${tag}/xxhash-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["0.8.3"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/xxhash-m/releases/download/mwrap-0.8.3/xxhash-m-0.8.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-xxhash/releases/download/0.8.3/xxhash-m-0.8.3.tar.gz", + }, + sha256 = "4e389afbece2290b44c24c12b4ad1086c28f146e78de8cebf5c701f676dc36cc", + }, + }, + macosx = { + ["0.8.3"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/xxhash-m/releases/download/mwrap-0.8.3/xxhash-m-0.8.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-xxhash/releases/download/0.8.3/xxhash-m-0.8.3.tar.gz", + }, + sha256 = "4e389afbece2290b44c24c12b4ad1086c28f146e78de8cebf5c701f676dc36cc", + }, + }, + windows = { + ["0.8.3"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/xxhash-m/releases/download/mwrap-0.8.3/xxhash-m-0.8.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-xxhash/releases/download/0.8.3/xxhash-m-0.8.3.tar.gz", + }, + sha256 = "4e389afbece2290b44c24c12b4ad1086c28f146e78de8cebf5c701f676dc36cc", + }, + }, + }, +} diff --git a/pkgs/mwrap/xz.lua b/pkgs/mwrap/xz.lua new file mode 100644 index 00000000..62cce7f0 --- /dev/null +++ b/pkgs/mwrap/xz.lua @@ -0,0 +1,50 @@ +-- Immutable upstream-aligned mwrap source release (xz-m mwrap-5.8.3); +-- GLOBAL and CN bytes are identical. +-- +-- The wrapper is Apache-2.0; the library it links, compat.xz (liblzma of XZ +-- Utils 5.8.3), is 0BSD, linked statically: the package deploys its README and +-- licenses beside programs (runtime.deploy). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "xz", + description = "xz-m: liblzma of XZ Utils 5.8.3 (.xz and .lzma streams, filters, blocks, indexes, checks) as C++23 modules, with the constants of lzma.h as C++ constants", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/xz-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/xz-m/releases/download/${tag}/xz-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["5.8.3"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/xz-m/releases/download/mwrap-5.8.3/xz-m-5.8.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-xz/releases/download/5.8.3/xz-m-5.8.3.tar.gz", + }, + sha256 = "6b3f720c05a66916ddb3e14ba98dfd65ace6018b66eda1400ce40adb05a3b957", + }, + }, + macosx = { + ["5.8.3"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/xz-m/releases/download/mwrap-5.8.3/xz-m-5.8.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-xz/releases/download/5.8.3/xz-m-5.8.3.tar.gz", + }, + sha256 = "6b3f720c05a66916ddb3e14ba98dfd65ace6018b66eda1400ce40adb05a3b957", + }, + }, + windows = { + ["5.8.3"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/xz-m/releases/download/mwrap-5.8.3/xz-m-5.8.3.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-xz/releases/download/5.8.3/xz-m-5.8.3.tar.gz", + }, + sha256 = "6b3f720c05a66916ddb3e14ba98dfd65ace6018b66eda1400ce40adb05a3b957", + }, + }, + }, +} diff --git a/pkgs/mwrap/zlib.lua b/pkgs/mwrap/zlib.lua new file mode 100644 index 00000000..2a82e00b --- /dev/null +++ b/pkgs/mwrap/zlib.lua @@ -0,0 +1,50 @@ +-- Immutable upstream-aligned mwrap source release (zlib-m mwrap-1.3.2); +-- GLOBAL and CN bytes are identical. +-- +-- The wrapper is Apache-2.0; the library it links, compat.zlib (zlib 1.3.2), is +-- under the zlib license, linked statically: the package deploys zlib's README +-- and LICENSE beside programs (runtime.deploy). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "zlib", + description = "zlib-m: zlib 1.3.2 (deflate, inflate, gzip files and checksums) as C++23 modules, with the constants of zlib.h as C++ constants", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/zlib-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/zlib-m/releases/download/${tag}/zlib-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["1.3.2"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/zlib-m/releases/download/mwrap-1.3.2/zlib-m-1.3.2.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-zlib/releases/download/1.3.2/zlib-m-1.3.2.tar.gz", + }, + sha256 = "93a855abd3688dcba1526e6de1a61121e51eaeddef31da263342be30e2818b4b", + }, + }, + macosx = { + ["1.3.2"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/zlib-m/releases/download/mwrap-1.3.2/zlib-m-1.3.2.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-zlib/releases/download/1.3.2/zlib-m-1.3.2.tar.gz", + }, + sha256 = "93a855abd3688dcba1526e6de1a61121e51eaeddef31da263342be30e2818b4b", + }, + }, + windows = { + ["1.3.2"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/zlib-m/releases/download/mwrap-1.3.2/zlib-m-1.3.2.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-zlib/releases/download/1.3.2/zlib-m-1.3.2.tar.gz", + }, + sha256 = "93a855abd3688dcba1526e6de1a61121e51eaeddef31da263342be30e2818b4b", + }, + }, + }, +} diff --git a/pkgs/mwrap/zstd.lua b/pkgs/mwrap/zstd.lua new file mode 100644 index 00000000..935a1f0f --- /dev/null +++ b/pkgs/mwrap/zstd.lua @@ -0,0 +1,50 @@ +-- Immutable upstream-aligned mwrap source release (zstd-m mwrap-1.5.7); +-- GLOBAL and CN bytes are identical. +-- +-- The wrapper is Apache-2.0; the library it links, compat.zstd (Zstandard +-- 1.5.7), is under the BSD license or the GPLv2, linked statically: the package +-- deploys its README and both licenses beside programs (runtime.deploy). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "zstd", + description = "zstd-m: Zstandard 1.5.7 (one-shot, streaming, parameters, dictionaries, error codes) as C++23 modules, with the constants of zstd.h as C++ constants", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/zstd-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/zstd-m/releases/download/${tag}/zstd-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["1.5.7"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/zstd-m/releases/download/mwrap-1.5.7/zstd-m-1.5.7.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-zstd/releases/download/1.5.7/zstd-m-1.5.7.tar.gz", + }, + sha256 = "276a81e640f510db8d491e8ba038e9f68ddc4a64c4371b798c79807e508fc950", + }, + }, + macosx = { + ["1.5.7"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/zstd-m/releases/download/mwrap-1.5.7/zstd-m-1.5.7.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-zstd/releases/download/1.5.7/zstd-m-1.5.7.tar.gz", + }, + sha256 = "276a81e640f510db8d491e8ba038e9f68ddc4a64c4371b798c79807e508fc950", + }, + }, + windows = { + ["1.5.7"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/zstd-m/releases/download/mwrap-1.5.7/zstd-m-1.5.7.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-zstd/releases/download/1.5.7/zstd-m-1.5.7.tar.gz", + }, + sha256 = "276a81e640f510db8d491e8ba038e9f68ddc4a64c4371b798c79807e508fc950", + }, + }, + }, +} diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index 9cafedee..360d6a76 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -12,6 +12,18 @@ import tomllib +# Identity: (root module source, retained upstream license under licenses/, +# a phrase of that license, a phrase of the wrapper's NOTICE naming the authors). +STATIC_NOTICES = { + "mcpplibs.mwrap.zlib": ("zlib.cppm", "upstream/zlib/LICENSE", b"Jean-loup Gailly", b"Mark Adler"), + "mcpplibs.mwrap.zstd": ("zstd.cppm", "upstream/zstd/LICENSE", b"BSD License", b"Meta Platforms"), + "mcpplibs.mwrap.xz": ("xz.cppm", "upstream/xz/COPYING.0BSD", b"Permission to use, copy, modify", b"XZ Utils authors"), + "mcpplibs.mwrap.xxhash": ("xxhash.cppm", "upstream/xxhash/LICENSE", b"BSD 2-Clause", b"Yann Collet"), + "mcpplibs.mwrap.libarchive": ("libarchive.cppm", "upstream/libarchive/COPYING", b"Tim Kientzle", b"Tim Kientzle"), + "mcpplibs.mwrap.libc": ("libc.cppm", None, b"", b"ISO/IEC 9899:2018"), +} + + def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--mcpp", default="mcpp") @@ -417,6 +429,28 @@ def main(): assert b"Apache License" in (roots[0] / "LICENSE").read_bytes() assert b"mcpp-community" in (roots[0] / "NOTICE").read_bytes() print(f"Verified {len(declared)} byte-identical wrapper/SDL license files", flush=True) + elif identity in STATIC_NOTICES: + # A statically linked library whose notices the package + # deploys beside the wrapper's (libc-m: the wrapper's only). + module, upstream, marker, author = STATIC_NOTICES[identity] + short = identity.rsplit(".", 1)[1] + sources = [path for store in (home / "xpkgs", home / "registry/data/xpkgs") + for path in store.glob(f"mcpplibs.mwrap-x-{short}/{version}/*/src/{module}")] + assert len(sources) == 1, sources + source = sources[0].parent.parent + declared = tomllib.loads((source / "mcpp.toml").read_text(encoding="utf-8"))["runtime"]["deploy"] + roots = list(relocated.rglob(f"licenses/{short}")) + assert len(roots) == 1, f"Missing {short} runtime license tree: {roots}" + for entry in declared: + notice = source / entry["from"] + copied = roots[0].parent.parent / entry["to"] / notice.name + assert copied.is_file() and copied.read_bytes() == notice.read_bytes(), copied + if upstream: + assert marker in (roots[0] / upstream).read_bytes(), upstream + assert b"Apache License" in (roots[0] / "LICENSE").read_bytes() + notice = (roots[0] / "NOTICE").read_bytes() + assert b"mcpp-community" in notice and author in notice + print(f"Verified {len(declared)} byte-identical wrapper/{short} license files", flush=True) else: raise ValueError(f"No deployment acceptance contract for {identity}") runtime_env = {key: value for key, value in os.environ.items() diff --git a/tests/mwrap/libarchive-bundle/mcpp.toml b/tests/mwrap/libarchive-bundle/mcpp.toml new file mode 100644 index 00000000..4dc92eda --- /dev/null +++ b/tests/mwrap/libarchive-bundle/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "libarchive-bundle" +version = "3.8.7" +description = "Consume the immutable libarchive wrapper: tar.gz, zip and 7-Zip written to memory and read back with their formats detected, and entry metadata" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +"mcpplibs.mwrap.libarchive" = "3.8.7" diff --git a/tests/mwrap/libarchive-bundle/tests/behavior.cpp b/tests/mwrap/libarchive-bundle/tests/behavior.cpp new file mode 100644 index 00000000..11c21b2f --- /dev/null +++ b/tests/mwrap/libarchive-bundle/tests/behavior.cpp @@ -0,0 +1,49 @@ +// The installed libarchive wrapper: archives written to memory in three formats +// and read back with their formats detected, and an entry's metadata. +import std; +import mcpplibs.mwrap.libarchive; + +namespace la = mcpplibs::mwrap::libarchive::macros; + +static std::string write(const char* format, const char* filter, const std::string& data) { + std::string out(1 << 18, '\0'); + std::size_t used = 0; + archive* a = archive_write_new(); + archive_write_set_format_by_name(a, format); + if (filter) archive_write_add_filter_by_name(a, filter); + archive_write_open_memory(a, out.data(), out.size(), &used); + archive_entry* e = archive_entry_new(); + archive_entry_set_pathname(e, "dir/file.txt"); + archive_entry_set_filetype(e, la::AE_IFREG); + archive_entry_set_perm(e, 0640); + archive_entry_set_size(e, static_cast(data.size())); + archive_write_header(a, e); + archive_write_data(a, data.data(), data.size()); + archive_entry_free(e); + archive_write_free(a); + out.resize(used); + return out; +} + +int main() { + const std::string data(3000, 'a'); + for (auto [format, filter, expected] : {std::tuple{"ustar", "gzip", "POSIX ustar format"}, + std::tuple{"zip", static_cast(nullptr), "ZIP"}, + std::tuple{"7zip", static_cast(nullptr), "7-Zip"}}) { + const std::string bytes = write(format, filter, data); + archive* a = archive_read_new(); + archive_read_support_filter_all(a); + archive_read_support_format_all(a); + archive_entry* e; + if (archive_read_open_memory(a, bytes.data(), bytes.size()) != la::ARCHIVE_OK + || archive_read_next_header(a, &e) != la::ARCHIVE_OK) return 1; + std::string back(static_cast(archive_entry_size(e)), '\0'); + archive_read_data(a, back.data(), back.size()); + const bool ok = back == data && std::string_view(archive_entry_pathname(e)) == "dir/file.txt" + && (archive_entry_perm(e) & 0777) == 0640 && std::string_view(archive_format_name(a)).starts_with(expected); + std::println("{}: {} bytes, {}", format, bytes.size(), archive_format_name(a)); + archive_read_free(a); + if (!ok) return 2; + } + return archive_version_number() == la::ARCHIVE_VERSION_NUMBER ? 0 : 3; +} diff --git a/tests/mwrap/libc-stdio/mcpp.toml b/tests/mwrap/libc-stdio/mcpp.toml new file mode 100644 index 00000000..0fae5b95 --- /dev/null +++ b/tests/mwrap/libc-stdio/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "libc-stdio" +version = "17.0.0" +description = "Consume the immutable C standard library wrapper: stdout() and stderr(), errno() as strtol sets it, the stdio constants on a temporary file, and the side header's va_* and offsetof" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +"mcpplibs.mwrap.libc" = "17.0.0" diff --git a/tests/mwrap/libc-stdio/tests/behavior.cpp b/tests/mwrap/libc-stdio/tests/behavior.cpp new file mode 100644 index 00000000..6ff8fdbb --- /dev/null +++ b/tests/mwrap/libc-stdio/tests/behavior.cpp @@ -0,0 +1,22 @@ +// The installed C standard library wrapper: the streams, errno() as strtol +// sets it, and the stdio constants on a temporary file. +import mcpplibs.mwrap.libc; + +namespace c = mcpplibs::mwrap::libc::macros; + +int main() { + if (std::fputs("libc-stdio writes to stdout()\n", c::stdout()) == c::EOF) return 1; + if (std::fputs("libc-stdio writes to stderr()\n", c::stderr()) == c::EOF) return 2; + c::errno() = 0; + std::strtol("99999999999999999999999", nullptr, 10); + if (c::errno() != c::ERANGE) return 3; + std::FILE* f = std::tmpfile(); + if (!f) return 4; + std::fputs("xyz", f); + std::fseek(f, 0, c::SEEK_SET); + const bool first = std::fgetc(f) == 'x'; + std::fseek(f, 0, c::SEEK_END); + const bool end = std::fgetc(f) == c::EOF; + std::fclose(f); + return first && end && c::CHAR_BIT == 8 && c::BUFSIZ > 0 ? 0 : 5; +} diff --git a/tests/mwrap/libc-stdio/tests/side_header.cpp b/tests/mwrap/libc-stdio/tests/side_header.cpp new file mode 100644 index 00000000..5b07820e --- /dev/null +++ b/tests/mwrap/libc-stdio/tests/side_header.cpp @@ -0,0 +1,23 @@ +// The side header from the installed package: va_* and offsetof are the C +// library's macros, and the mapped names still work in the same unit. +#include +import mcpplibs.mwrap.libc; + +namespace c = mcpplibs::mwrap::libc::macros; + +static int sum(int count, ...) { + va_list args; + va_start(args, count); + int total = 0; + for (int i = 0; i < count; ++i) total += va_arg(args, int); + va_end(args); + return total; +} + +struct pair { char tag; double value; }; + +int main() { + c::errno() = 0; + return sum(3, 4, 5, 6) == 15 && offsetof(pair, tag) == 0 && c::errno() == 0 + && c::SIZE_MAX == std::numeric_limits::max() ? 0 : 1; +} diff --git a/tests/mwrap/xxhash-digest/mcpp.toml b/tests/mwrap/xxhash-digest/mcpp.toml new file mode 100644 index 00000000..d7bf3725 --- /dev/null +++ b/tests/mwrap/xxhash-digest/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "xxhash-digest" +version = "0.8.3" +description = "Consume the immutable xxHash wrapper: the published hashes of the empty input, streaming equal to one call, and the canonical form" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +"mcpplibs.mwrap.xxhash" = "0.8.3" diff --git a/tests/mwrap/xxhash-digest/tests/behavior.cpp b/tests/mwrap/xxhash-digest/tests/behavior.cpp new file mode 100644 index 00000000..96f88d18 --- /dev/null +++ b/tests/mwrap/xxhash-digest/tests/behavior.cpp @@ -0,0 +1,24 @@ +// The installed xxHash wrapper: the published hashes of the empty input, +// streaming equal to one call, and the canonical form. +import std; +import mcpplibs.mwrap.xxhash; + +namespace h = mcpplibs::mwrap::xxhash::macros; + +int main() { + if (XXH32(nullptr, 0, 0) != 0x02CC5D05u || XXH64(nullptr, 0, 0) != 0xEF46DB3751D8E999ull + || XXH3_64bits(nullptr, 0) != 0x2D06800538D394C2ull) return 1; + std::string text; + for (int i = 0; i < 400; ++i) text += std::format("{},", i * 31); + XXH3_state_t* s = XXH3_createState(); + XXH3_64bits_reset(s); + for (std::size_t at = 0; at < text.size(); at += 37) + XXH3_64bits_update(s, text.data() + at, std::min(37, text.size() - at)); + const bool same = XXH3_64bits_digest(s) == XXH3_64bits(text.data(), text.size()); + XXH3_freeState(s); + XXH64_canonical_t canonical; + XXH64_canonicalFromHash(&canonical, 0x0102030405060708ull); + if (!same || canonical.digest[0] != 1 || XXH64_hashFromCanonical(&canonical) != 0x0102030405060708ull) return 2; + std::println("xxHash {}", XXH_versionNumber()); + return XXH_versionNumber() == h::XXH_VERSION_NUMBER ? 0 : 3; +} diff --git a/tests/mwrap/xz-pack/mcpp.toml b/tests/mwrap/xz-pack/mcpp.toml new file mode 100644 index 00000000..99ca8aa0 --- /dev/null +++ b/tests/mwrap/xz-pack/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "xz-pack" +version = "5.8.3" +description = "Consume the immutable liblzma wrapper: an .xz stream through the side header's LZMA_STREAM_INIT, a BCJ filter chain, the .lzma format, and the published CRC-64" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +"mcpplibs.mwrap.xz" = "5.8.3" diff --git a/tests/mwrap/xz-pack/tests/behavior.cpp b/tests/mwrap/xz-pack/tests/behavior.cpp new file mode 100644 index 00000000..1766ccf6 --- /dev/null +++ b/tests/mwrap/xz-pack/tests/behavior.cpp @@ -0,0 +1,39 @@ +// The installed liblzma wrapper: buffers one call each way, a BCJ and LZMA2 +// filter chain read back by the automatic decoder, and the published CRC-64. +import std; +import mcpplibs.mwrap.xz; + +namespace x = mcpplibs::mwrap::xz::macros; + +int main() { + std::string text; + for (int i = 0; i < 800; ++i) text += std::format("record {} of a journal\n", i % 17); + const auto* bytes = reinterpret_cast(text.data()); + std::vector xz(lzma_stream_buffer_bound(text.size())); + std::size_t size = 0; + if (lzma_easy_buffer_encode(x::LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64, nullptr, bytes, text.size(), + xz.data(), &size, xz.size()) != LZMA_OK) return 1; + std::string back(text.size(), '\0'); + std::uint64_t limit = std::numeric_limits::max(); + std::size_t in_pos = 0, out_pos = 0; + if (lzma_stream_buffer_decode(&limit, 0, nullptr, xz.data(), &in_pos, size, + reinterpret_cast(back.data()), &out_pos, back.size()) != LZMA_OK + || back != text) return 2; + + lzma_options_lzma lzma2{}; + lzma_lzma_preset(&lzma2, 1); + lzma_filter chain[] = {{x::LZMA_FILTER_X86, nullptr}, {x::LZMA_FILTER_LZMA2, &lzma2}, {x::LZMA_VLI_UNKNOWN, nullptr}}; + std::vector filtered(lzma_stream_buffer_bound(text.size())); + std::size_t filtered_size = 0; + if (lzma_stream_buffer_encode(chain, LZMA_CHECK_CRC32, nullptr, bytes, text.size(), filtered.data(), + &filtered_size, filtered.size()) != LZMA_OK) return 3; + limit = std::numeric_limits::max(); + in_pos = out_pos = 0; + std::string unfiltered(text.size(), '\0'); + if (lzma_stream_buffer_decode(&limit, 0, nullptr, filtered.data(), &in_pos, filtered_size, + reinterpret_cast(unfiltered.data()), &out_pos, unfiltered.size()) != LZMA_OK + || unfiltered != text) return 4; + if (lzma_crc64(reinterpret_cast("123456789"), 9, 0) != 0x995DC9BBDF1939FAull) return 5; + std::println("liblzma {}: {} -> {} bytes", lzma_version_string(), text.size(), size); + return 0; +} diff --git a/tests/mwrap/xz-pack/tests/side_header.cpp b/tests/mwrap/xz-pack/tests/side_header.cpp new file mode 100644 index 00000000..ef8a9f17 --- /dev/null +++ b/tests/mwrap/xz-pack/tests/side_header.cpp @@ -0,0 +1,14 @@ +// The side header from the installed package: LZMA_STREAM_INIT, an initializer +// macro, and lzma_vli_is_valid, which reads LZMA_VLI_MAX from the macros module. +#include +import std; +import mcpplibs.mwrap.xz; + +namespace x = mcpplibs::mwrap::xz::macros; + +int main() { + lzma_stream s = LZMA_STREAM_INIT; + if (lzma_alone_decoder(&s, std::numeric_limits::max()) != LZMA_OK) return 1; + lzma_end(&s); + return lzma_vli_is_valid(x::LZMA_VLI_MAX) && !lzma_vli_is_valid(x::LZMA_VLI_MAX + 1) ? 0 : 2; +} diff --git a/tests/mwrap/zlib-compress/mcpp.toml b/tests/mwrap/zlib-compress/mcpp.toml new file mode 100644 index 00000000..dcb43d34 --- /dev/null +++ b/tests/mwrap/zlib-compress/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "zlib-compress" +version = "1.3.2" +description = "Consume the immutable zlib wrapper: a one-call round trip, an inflate stream through the side header's inflateInit, a gzip file, and the published CRC-32" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +"mcpplibs.mwrap.zlib" = "1.3.2" diff --git a/tests/mwrap/zlib-compress/tests/behavior.cpp b/tests/mwrap/zlib-compress/tests/behavior.cpp new file mode 100644 index 00000000..8d249008 --- /dev/null +++ b/tests/mwrap/zlib-compress/tests/behavior.cpp @@ -0,0 +1,29 @@ +// The installed zlib wrapper: one call each way, the published CRC-32, and a +// gzip file written and read back. +import std; +import mcpplibs.mwrap.zlib; + +namespace z = mcpplibs::mwrap::zlib::macros; + +int main() { + std::string text; + for (int i = 0; i < 300; ++i) text += std::format("row {} of a table\n", i % 11); + const auto* bytes = reinterpret_cast(text.data()); + std::vector packed(compressBound(text.size())); + uLongf size = packed.size(); + if (compress2(packed.data(), &size, bytes, text.size(), z::Z_BEST_COMPRESSION) != z::Z_OK) return 1; + std::string back(text.size(), '\0'); + uLongf back_size = back.size(); + if (uncompress(reinterpret_cast(back.data()), &back_size, packed.data(), size) != z::Z_OK || back != text) return 2; + if (crc32(0, reinterpret_cast("123456789"), 9) != 0xCBF43926u) return 3; + const auto path = (std::filesystem::temp_directory_path() / "zlib-compress-member.gz").string(); + gzFile out = gzopen(path.c_str(), "wb"); + if (!out || gzputs(out, "gzip through a module\n") < 0 || gzclose(out) != z::Z_OK) return 4; + gzFile in = gzopen(path.c_str(), "rb"); + char line[64] = {}; + const bool read = in && gzgets(in, line, sizeof line) && std::string_view(line) == "gzip through a module\n"; + if (in) gzclose(in); + std::filesystem::remove(path); + std::println("zlib {}: {} -> {} bytes", zlibVersion(), text.size(), size); + return read ? 0 : 5; +} diff --git a/tests/mwrap/zlib-compress/tests/side_header.cpp b/tests/mwrap/zlib-compress/tests/side_header.cpp new file mode 100644 index 00000000..ef8a21b6 --- /dev/null +++ b/tests/mwrap/zlib-compress/tests/side_header.cpp @@ -0,0 +1,31 @@ +// The side header from the installed package, beside the import: inflateInit +// and deflateInit are macros that pass ZLIB_VERSION from the macros module. +#include +import std; +import mcpplibs.mwrap.zlib; + +namespace z = mcpplibs::mwrap::zlib::macros; + +int main() { + std::string text(5000, 'z'); + z_stream out{}; + if (deflateInit(&out, z::Z_DEFAULT_COMPRESSION) != z::Z_OK) return 1; + std::vector packed(deflateBound(&out, text.size())); + out.next_in = reinterpret_cast(text.data()); + out.avail_in = static_cast(text.size()); + out.next_out = packed.data(); + out.avail_out = static_cast(packed.size()); + if (deflate(&out, z::Z_FINISH) != z::Z_STREAM_END) return 2; + const auto packed_size = out.total_out; + deflateEnd(&out); + z_stream in{}; + if (inflateInit(&in) != z::Z_OK) return 3; + std::string back(text.size(), '\0'); + in.next_in = packed.data(); + in.avail_in = static_cast(packed_size); + in.next_out = reinterpret_cast(back.data()); + in.avail_out = static_cast(back.size()); + const int rc = inflate(&in, z::Z_FINISH); + inflateEnd(&in); + return rc == z::Z_STREAM_END && back == text ? 0 : 4; +} diff --git a/tests/mwrap/zstd-squeeze/mcpp.toml b/tests/mwrap/zstd-squeeze/mcpp.toml new file mode 100644 index 00000000..ce8165e6 --- /dev/null +++ b/tests/mwrap/zstd-squeeze/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "zstd-squeeze" +version = "1.5.7" +description = "Consume the immutable Zstandard wrapper: a one-shot round trip with the frame's content size, a stream, a damaged checksum refused, and the side header's ZSTD_COMPRESSBOUND" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +"mcpplibs.mwrap.zstd" = "1.5.7" diff --git a/tests/mwrap/zstd-squeeze/tests/behavior.cpp b/tests/mwrap/zstd-squeeze/tests/behavior.cpp new file mode 100644 index 00000000..90de7518 --- /dev/null +++ b/tests/mwrap/zstd-squeeze/tests/behavior.cpp @@ -0,0 +1,32 @@ +// The installed Zstandard wrapper: a one-shot round trip, a stream, and a +// damaged checksum refused with its error code. +import std; +import mcpplibs.mwrap.zstd; + +namespace z = mcpplibs::mwrap::zstd::macros; + +int main() { + std::string text; + for (int i = 0; i < 500; ++i) text += std::format("entry {} of a log\n", i % 13); + std::string frame(ZSTD_compressBound(text.size()), '\0'); + const auto size = ZSTD_compress(frame.data(), frame.size(), text.data(), text.size(), z::ZSTD_CLEVEL_DEFAULT); + if (ZSTD_isError(size)) return 1; + frame.resize(size); + if (ZSTD_getFrameContentSize(frame.data(), frame.size()) != text.size()) return 2; + std::string back(text.size(), '\0'); + if (ZSTD_decompress(back.data(), back.size(), frame.data(), frame.size()) != text.size() || back != text) return 3; + + ZSTD_CCtx* c = ZSTD_createCCtx(); + ZSTD_CCtx_setParameter(c, ZSTD_c_checksumFlag, 1); + std::string streamed(ZSTD_compressBound(text.size()), '\0'); + ZSTD_inBuffer in{text.data(), text.size(), 0}; + ZSTD_outBuffer out{streamed.data(), streamed.size(), 0}; + if (ZSTD_compressStream2(c, &out, &in, ZSTD_e_end) != 0) return 4; + ZSTD_freeCCtx(c); + streamed.resize(out.pos); + streamed.back() ^= 1; + const auto damaged = ZSTD_decompress(back.data(), back.size(), streamed.data(), streamed.size()); + if (!ZSTD_isError(damaged) || ZSTD_getErrorCode(damaged) != ZSTD_error_checksum_wrong) return 5; + std::println("zstd {}: {} -> {} bytes", ZSTD_versionString(), text.size(), size); + return 0; +} diff --git a/tests/mwrap/zstd-squeeze/tests/side_header.cpp b/tests/mwrap/zstd-squeeze/tests/side_header.cpp new file mode 100644 index 00000000..94fcee26 --- /dev/null +++ b/tests/mwrap/zstd-squeeze/tests/side_header.cpp @@ -0,0 +1,11 @@ +// The side header from the installed package: ZSTD_COMPRESSBOUND is a constant +// expression, reading ZSTD_MAX_INPUT_SIZE from the macros module. +#include +import std.compat; +import mcpplibs.mwrap.zstd; + +int main() { + constexpr std::size_t bound = ZSTD_COMPRESSBOUND(1 << 20); + std::array small{}; + return bound == ZSTD_compressBound(1 << 20) && small.size() == ZSTD_compressBound(64) ? 0 : 1; +} From 51482d5a715c1e5d86eb0258aff03f1c2d80c43b Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 7 Nov 2026 08:18:06 +0900 Subject: [PATCH 2/3] validate: the llvm leg's ffmpeg headers over IPv4, waiting for the dpkg lock, three attempts Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/validate.yml | 12 +++++++++--- CHANGELOG.md | 5 +++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 549324b9..a153fa87 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1444,15 +1444,21 @@ jobs: cp /etc/apt/sources.list.d/ubuntu.sources "$RUNNER_TEMP/apt-ubuntu/" apt_opts+=(-o Dir::Etc::SourceList=/dev/null -o "Dir::Etc::SourceParts=$RUNNER_TEMP/apt-ubuntu") fi + # IPv4: on 2026-11-06 the install stalled without output for the + # whole timeout on several runners while the index update had taken + # seconds. The install now reports its progress, waits for a held + # dpkg lock rather than stalling on it, and is tried three times. + apt_opts+=(-o Acquire::ForceIPv4=true -o DPkg::Lock::Timeout=120) time sudo timeout 180 apt-get update -qq "${apt_opts[@]}" - for attempt in 1 2; do - if time sudo timeout 300 apt-get install -y -qq --no-install-recommends "${apt_opts[@]}" \ + for attempt in 1 2 3; do + if time sudo timeout 240 apt-get install -y -q --no-install-recommends "${apt_opts[@]}" \ libavutil-dev libavcodec-dev libavformat-dev \ libavfilter-dev libavdevice-dev libswscale-dev libswresample-dev; then break fi - [[ $attempt == 2 ]] && { echo "::error::the ffmpeg dev headers did not install"; exit 1; } + [[ $attempt == 3 ]] && { echo "::error::the ffmpeg dev headers did not install"; exit 1; } echo "::warning::apt-get install failed (attempt $attempt); retrying" + sudo timeout 120 apt-get update -qq "${apt_opts[@]}" || true done dpkg -l | grep -E 'libav(util|codec)-dev|libswscale-dev' diff --git a/CHANGELOG.md b/CHANGELOG.md index ba26d4ea..e8f215e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ 块,链接时找不到这两个归档;新增 `openkal` 块,不列链接参数。`-latomic` 上游只在找到时才链接,mimalloc 的原子操作在本索引 构建的各架构上都是无锁的,LLVM 工具链在图中 C 库之上没有这个归档。 +### Chore + +- **validate:llvm 腿安装宿主 ffmpeg 头文件时改走 IPv4、等待 dpkg 锁并重试三次。** 2026-11-06 起多个运行器上安装在超时内没有任何 + 输出,而索引更新只用数秒;安装现在输出进度,失败后重新更新索引再试。 + ### Added - **mcpplibs.mwrap.zlib 1.3.2、.zstd 1.5.7、.xz 5.8.3、.xxhash 0.8.3、.libarchive 3.8.7、.libc 17.0.0(新包)。** 六个 mwrap From c75e75ab4e1d0c916b146972dfac51d983536f57 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sat, 7 Nov 2026 08:51:01 +0900 Subject: [PATCH 3/3] validate: the llvm leg plants FFmpeg's header names under /usr/include instead of installing Ubuntu's Each public header name of the release compat.ffmpeg names becomes a file that stops the compile and names itself, so a reach of the host's header fails at the include that made it. Ubuntu's ffmpeg closure no longer downloaded within any timeout on ubuntu-26.04 runners (2026-11-06). Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/validate.yml | 78 ++++++++++++++++++++-------------- CHANGELOG.md | 6 ++- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a153fa87..ffa86365 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1421,46 +1421,60 @@ jobs: # A bare GitHub runner has no libav* under /usr/include, so a # descriptor that loses its vendored headers to the host's would still # be green here. That is precisely how compat.ffmpeg's -idirafter hole - # survived: nothing in CI had a system ffmpeg to lose to. Installing - # the dev headers is what turns this leg into a real test of include - # ORDER rather than of include EXISTENCE. + # survived: nothing in CI had a system ffmpeg to lose to. Headers of + # FFmpeg's own names under /usr/include are what turn this leg into a + # test of include ORDER rather than of include EXISTENCE. + # + # They are planted, not installed: each public header of the release + # compat.ffmpeg names, as a file that stops the compile, naming itself. + # A reach of the host's header is then an error at the line that made + # it, where an installed header of another FFmpeg compiled silently or + # failed somewhere else. And nothing is downloaded from Ubuntu's mirror, + # whose ffmpeg closure (~100 MiB at the 2026-11-06 rates) outlasted + # every timeout on ubuntu-26.04 runners. # # Deliberately only on the llvm leg. The default leg reaches its # compiler through --sysroot, so it is immune by construction and - # installing there would change an existing job's environment for no + # planting there would change an existing job's environment for no # coverage. Keeping the blast radius to the new leg means a red here # is always about the thing this leg was added to watch. - - name: Install host ffmpeg dev headers (llvm leg only) + - name: Plant host ffmpeg headers that stop the compile (llvm leg only) if: matrix.platform == 'linux' && matrix.toolchain == 'llvm' shell: bash - # Only Ubuntu's own archive is refreshed: a bare `apt-get update` also - # walks every third-party source the runner image carries (PPAs, - # vendor repos), and one slow mirror among them held this step for - # minutes. Bounded timeouts and retries, and no recommended packages. run: | - apt_opts=(-o Acquire::Retries=3 -o Acquire::http::Timeout=20 -o Acquire::https::Timeout=20) - if [[ -f /etc/apt/sources.list.d/ubuntu.sources ]]; then - mkdir -p "$RUNNER_TEMP/apt-ubuntu" - cp /etc/apt/sources.list.d/ubuntu.sources "$RUNNER_TEMP/apt-ubuntu/" - apt_opts+=(-o Dir::Etc::SourceList=/dev/null -o "Dir::Etc::SourceParts=$RUNNER_TEMP/apt-ubuntu") - fi - # IPv4: on 2026-11-06 the install stalled without output for the - # whole timeout on several runners while the index update had taken - # seconds. The install now reports its progress, waits for a held - # dpkg lock rather than stalling on it, and is tried three times. - apt_opts+=(-o Acquire::ForceIPv4=true -o DPkg::Lock::Timeout=120) - time sudo timeout 180 apt-get update -qq "${apt_opts[@]}" - for attempt in 1 2 3; do - if time sudo timeout 240 apt-get install -y -q --no-install-recommends "${apt_opts[@]}" \ - libavutil-dev libavcodec-dev libavformat-dev \ - libavfilter-dev libavdevice-dev libswscale-dev libswresample-dev; then - break - fi - [[ $attempt == 3 ]] && { echo "::error::the ffmpeg dev headers did not install"; exit 1; } - echo "::warning::apt-get install failed (attempt $attempt); retrying" - sudo timeout 120 apt-get update -qq "${apt_opts[@]}" || true - done - dpkg -l | grep -E 'libav(util|codec)-dev|libswscale-dev' + python3 - <<'PY' + import hashlib, io, re, subprocess, tarfile, urllib.request + from pathlib import Path + lua = Path("pkgs/c/compat.ffmpeg.lua").read_text() + entry = re.search(r'linux\s*=\s*\{\s*\["([^"]+)"\]\s*=\s*\{.*?GLOBAL\s*=\s*"([^"]+)".*?CN\s*=\s*"([^"]+)".*?sha256\s*=\s*"([0-9a-f]{64})"', lua, re.S) + version, urls, sha = entry.group(1), (entry.group(2), entry.group(3)), entry.group(4) + data = None + for url in urls: + try: + data = urllib.request.urlopen(url, timeout=120).read() + except OSError as e: + print(f"::warning::{url}: {e}") + continue + if hashlib.sha256(data).hexdigest() == sha: + break + data = None + if data is None: + raise SystemExit(f"::error::the FFmpeg {version} source matching the descriptor could not be read") + libs = ("libavutil", "libavcodec", "libavformat", "libavfilter", "libavdevice", "libswscale", "libswresample") + planted = 0 + with tarfile.open(fileobj=io.BytesIO(data)) as tar: + for name in tar.getnames(): + parts = name.split("/") + if len(parts) == 3 and parts[1] in libs and parts[2].endswith(".h"): + target = Path("/usr/include") / parts[1] / parts[2] + text = f'#error "host <{parts[1]}/{parts[2]}> reached: a vendored FFmpeg header was not found first"\n' + subprocess.run(["sudo", "mkdir", "-p", str(target.parent)], check=True) + subprocess.run(["sudo", "tee", str(target)], input=text.encode(), check=True, stdout=subprocess.DEVNULL) + planted += 1 + if planted < 100: + raise SystemExit(f"::error::only {planted} FFmpeg headers were planted") + print(f"planted {planted} FFmpeg {version} headers under /usr/include") + PY # ── Select the toolchain for this leg ───────────────────────────── # No version pin: `llvm` resolves to whatever the toolchain family's diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f215e8..3a608b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,10 @@ ### Chore -- **validate:llvm 腿安装宿主 ffmpeg 头文件时改走 IPv4、等待 dpkg 锁并重试三次。** 2026-11-06 起多个运行器上安装在超时内没有任何 - 输出,而索引更新只用数秒;安装现在输出进度,失败后重新更新索引再试。 +- **validate:llvm 腿不再从 Ubuntu 镜像安装 ffmpeg 开发包,改为在 `/usr/include` 下放置 FFmpeg 公开头文件同名、内容为 + `#error` 的文件。** 文件名取自 compat.ffmpeg 描述符所指发布(按描述符的 sha256 校验)。宿主头文件一旦被包含即在包含处报错, + 比安装另一版本的头文件更直接地检验包含顺序;2026-11-06 起 ubuntu-26.04 运行器上 ffmpeg 依赖闭包(约 100 MiB)的下载在每个 + 超时内都完成不了。 ### Added