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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Preserve upstream document bytes and original whitespace on every platform.
upstream/** -text -whitespace
88 changes: 88 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
mcpp:
name: mcpp (${{ matrix.os }}, ${{ matrix.toolchain }}${{ matrix.target && format(', {0}', matrix.target) || '' }})
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-24.04, toolchain: 'gcc@16.1.0' }
- { os: ubuntu-24.04, toolchain: 'gcc@16.1.0', target: x86_64-linux-musl }
- { os: ubuntu-24.04, toolchain: 'llvm@23.1.3' }
- { os: macos-15, toolchain: 'llvm@23.1.3' }
- { os: windows-2025, toolchain: 'llvm@23.1.3' }
runs-on: ${{ matrix.os }}
timeout-minutes: 60
defaults:
run:
shell: bash
env:
XLINGS_NON_INTERACTIVE: 1
TARGET: ${{ matrix.target && format('--target {0}', matrix.target) || '' }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Mwrap identity and bilingual README contract
run: python3 tools/check_mwrap_release.py

- name: Install Xlings (Linux/macOS)
if: runner.os != 'Windows'
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/refs/heads/main/tools/other/quick_install.sh | bash
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install Xlings (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Install project tools (.xlings.json)
run: |
for attempt in 1 2 3; do
xlings update > /dev/null 2>&1 || true
if xlings install; then break; fi
[ "$attempt" = 3 ] && exit 1
sleep 30
done
mcpp --version

- name: Select the toolchain
run: |
name="${{ matrix.toolchain }}"
mcpp toolchain install "${name%@*}" "${name#*@}"
mcpp toolchain default "$name"

- name: Build the modules
run: mcpp build $TARGET

- name: Tests (one shot, parameters and checksums, streams, dictionaries, errors, the constants, every function linked, header mixing)
run: mcpp test $TARGET

- name: Export audit (the headers as clang reads them on this host)
if: startsWith(matrix.toolchain, 'llvm')
run: python3 tools/generate.py --check

- name: Upstream's examples over the same library
if: ${{ !matrix.target }}
run: python3 tools/run_conformance.py --toolchain "${{ matrix.toolchain }}"
49 changes: 49 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Release
on:
push:
tags: ["mwrap-*"]
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
actions: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Check tag, upstream version and bilingual entry pages
run: python3 tools/check_mwrap_release.py --tag "$GITHUB_REF_NAME"
- name: Require successful source CI at this exact commit
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
result=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow ci.yml --commit "$GITHUB_SHA" --limit 1 --json status,conclusion --jq '.[0] | .status == "completed" and .conclusion == "success"')
test "$result" = true || { echo 'No successful source CI at the tag commit'; exit 1; }
- name: Build immutable source asset and publish
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#mwrap-}"
repo="${GITHUB_REPOSITORY#*/}"
asset="${repo}-${version}.tar.gz"
git archive --format=tar.gz --prefix="${repo}-${version}/" "$GITHUB_SHA" > "$RUNNER_TEMP/$asset"
(cd "$RUNNER_TEMP" && sha256sum "$asset" > "$asset.sha256")
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo 'Release already exists; refusing to change immutable assets'; exit 1
fi
extra=()
if [[ "$version" == *-rc* ]]; then extra+=(--prerelease); fi
gh release create "$GITHUB_REF_NAME" "$RUNNER_TEMP/$asset" "$RUNNER_TEMP/$asset.sha256" \
--repo "$GITHUB_REPOSITORY" --verify-tag --title "$repo $version" \
--notes "Module wrapper for upstream $version. New mwrap package identities become installable after mcpp-index publication; see the bilingual README for the tested support scope." "${extra[@]}"
28 changes: 28 additions & 0 deletions .github/workflows/templates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Actual project templates
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: templates-${{ github.ref }}
cancel-in-progress: true
env:
MCPP_VERSION: "2026.11.5.1"
jobs:
templates:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install the pinned mcpp
shell: bash
run: |
archive="mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz"
curl -L -fsS -o "$archive" "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/$archive"
tar -xzf "$archive"
echo "$PWD/mcpp-${MCPP_VERSION}-linux-x86_64/bin" >> "$GITHUB_PATH"
- name: Actual scaffold, strict build and run of the default template
run: python3 tools/test_template.py --toolchain gcc@16.1.0
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
target/
.mcpp/
mcpp.lock
compile_commands.json
__pycache__/
.xlings/
5 changes: 5 additions & 0 deletions .xlings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": {
"xim:mcpp": "2026.11.5.1"
}
}
14 changes: 14 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
zstd-m: C++ module wrapper
Copyright (c) 2026 mcpp-community contributors

The wrapper contributions are licensed under the Apache License, Version 2.0.
Upstream code, copied interfaces and dependencies retain their original
licenses and copyright notices. Zstandard is distributed under the BSD license
or, at the recipient's choice, the GPLv2:

Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.

Its original README and both licenses are retained in upstream/zstd/. The
library itself is provided by compat.zstd from the same release, and the macro
of include/mwrap/zstd/macros.hpp is upstream's definition. Provenance and
checksums are recorded in upstream/documents.json.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ Linux, macOS and Windows CI build the modules with GCC and LLVM and run the
tests: a one-shot round trip, a level-19 frame with a checksum that detects
damage, a stream fed in pieces, a raw-content dictionary, the error code of
garbage input, the version and the bound against their macros, every constant
against its macro, and header mixing. CI also runs:
against its macro, every exported function linked, and header mixing. CI also runs:

- the export audit ([tools/generate.py](tools/generate.py) `--check`): the
module, the macro mapping, the macro header and the constant test are
module, the macro mapping, the macro header and the generated tests are
regenerated from the headers as clang reads them and compared, and on macOS
and Windows every name the headers declare there is exported;
- four of upstream's examples over the same library
Expand Down
75 changes: 75 additions & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# zstd

> Zstandard——快速无损压缩库,以 C++23 模块提供。

[English](README.md) · **简体中文** · [上游](https://github.com/facebook/zstd) · [发布](https://github.com/mcpplibs/zstd-m/releases) · [mcpp](https://github.com/mcpp-community/mcpp) · [索引](https://github.com/mcpplibs/mcpp-index)

[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue.svg)](https://en.cppreference.com/w/cpp/23)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![mcpp-community](https://img.shields.io/badge/mcpp--community-mwrap-blue)](https://github.com/mcpp-community)
[![CI](https://github.com/mcpplibs/zstd-m/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mcpplibs/zstd-m/actions/workflows/ci.yml)

上游 API 不变:`ZSTD_compress`、`ZSTD_compress2`、`ZSTD_decompressStream`、`ZSTD_CCtx`、`ZSTD_cParameter` 及其枚举值、
`<zstd_errors.h>` 的错误码都是上游实体,位于上游声明它们的全局命名空间。库本身是 `compat.zstd` 1.5.7;常量宏映射为
C++ 常量。每次 CI 都在同一份库上运行上游自己的示例并完成往返。

## 模块

- `import mcpplibs.mwrap.zstd;`:`<zstd.h>` 的稳定 API 与 `<zstd_errors.h>` 的错误码,并重新导出宏模块
- `import mcpplibs.mwrap.zstd.macros;`:`ZSTD_CLEVEL_DEFAULT`、`ZSTD_CONTENTSIZE_ERROR`、`ZSTD_MAGICNUMBER`、`ZSTD_VERSION_NUMBER` 等,位于 `mcpplibs::mwrap::zstd::macros`
- `#include <mwrap/zstd/macros.hpp>`:`ZSTD_COMPRESSBOUND(size)`,以常量表达式给出上界

> 更多模块 API -> [用法与 API](docs/usage.md)

## 快速开始

```sh
mcpp new squeeze --template mcpplibs.mwrap.zstd@1.5.7
cd squeeze
mcpp run
```

默认模板以两个级别带校验和压缩一段文本,再还原并打印大小。

```toml
[dependencies]
"mcpplibs.mwrap.zstd" = "1.5.7"
```

```cpp
import std;
import mcpplibs.mwrap.zstd;

namespace z = mcpplibs::mwrap::zstd::macros;

int main() {
std::string text(10000, 'z'), frame(ZSTD_compressBound(text.size()), '\0');
const auto n = ZSTD_compress(frame.data(), frame.size(), text.data(), text.size(), z::ZSTD_CLEVEL_DEFAULT);
std::println("{} -> {} bytes", text.size(), ZSTD_isError(n) ? 0 : n);
}
```

## 范围与宏

模块导出稳定 API。`<zstd.h>` 的实验部分(`ZSTD_STATIC_LINKING_ONLY` 之后)可能在版本之间变化,不导出。`<zdict.h>`
也不导出:`compat.zstd` 不构建字典训练器。原始内容字典可经 `ZSTD_createCDict` 与 `ZSTD_createDDict` 使用。

命名模块不能导出宏。常量映射为 `mcpplibs::mwrap::zstd::macros` 中同名的 `inline constexpr`。`ZSTD_COMPRESSBOUND`
带参数,保留为 `<mwrap/zstd/macros.hpp>` 中的宏;它用到 `size_t`,所以该翻译单元导入 `std.compat`。可见性与弃用宏只用于
书写声明。宏在限定名中也会展开。同时包含 `<zstd.h>` 的翻译单元经头文件的宏读取常量([docs/usage.md](docs/usage.md))。

## 构建与测试

Linux、macOS、Windows 的 CI 用 GCC 与 LLVM 构建模块并运行测试:一次调用的往返、带校验和的 19 级帧及其损坏检测、分段
输入的流、原始内容字典、无效输入的错误码、版本与上界同其宏相等、每个常量同其宏相等、每个导出函数都能链接、头文件混用。CI 还运行:

- 导出审计([tools/generate.py](tools/generate.py) `--check`):按 clang 读到的头文件重新生成模块、宏映射、宏头文件与
生成的测试并逐字节比较;在 macOS 与 Windows 上检查头文件在该平台声明的每个名字都已导出;
- 在同一份库上运行上游的四个示例([tools/run_conformance.py](tools/run_conformance.py))。

另一个作业把默认模板当作已安装的包生成项目并运行。

## 许可

包装部分为 [Apache-2.0](LICENSE),作者 mcpp-community([NOTICE](NOTICE))。Zstandard 仍遵循 BSD 许可或 GPLv2
([upstream/zstd/LICENSE](upstream/zstd/LICENSE)、[COPYING](upstream/zstd/COPYING)、[README](upstream/zstd/README.md))。
55 changes: 55 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Usage and API

`mcpplibs.mwrap.zstd` 1.5.7 exports Zstandard's stable API, upstream's names
unchanged. This page lists what each module holds and the places where a module
and a header differ. [tools/generate.py](../tools/generate.py) writes the lists
from the headers, and CI checks them on every target.

## Modules

| Module | Holds |
|---|---|
| `mcpplibs.mwrap.zstd` | the stable API of `<zstd.h>`: one-shot `ZSTD_compress`, `ZSTD_decompress`, `ZSTD_compressBound`, `ZSTD_getFrameContentSize`, `ZSTD_findFrameCompressedSize`; explicit contexts (`ZSTD_CCtx`, `ZSTD_DCtx` and their `create`, `free`, `compressCCtx`, `decompressDCtx`); the advanced API (`ZSTD_cParameter`, `ZSTD_dParameter` and their enumerators, `ZSTD_CCtx_setParameter`, `ZSTD_compress2`, `ZSTD_cParam_getBounds`, `ZSTD_strategy`, `ZSTD_ResetDirective`); streaming (`ZSTD_inBuffer`, `ZSTD_outBuffer`, `ZSTD_EndDirective`, `ZSTD_compressStream2`, `ZSTD_decompressStream`, the `CStream`/`DStream` forms); dictionaries (`ZSTD_CDict`, `ZSTD_DDict`, `*_usingDict`, `*_usingCDict`, `*_usingDDict`, `ZSTD_CCtx_loadDictionary`, `refPrefix`, `getDictID_*`); `ZSTD_sizeof_*`; `ZSTD_versionNumber`, `ZSTD_versionString`, `ZSTD_isError`, `ZSTD_getErrorName`, levels. From `<zstd_errors.h>`: `ZSTD_ErrorCode` with every `ZSTD_error_*`, `ZSTD_getErrorCode`, `ZSTD_getErrorString`. Re-exports the macros module |
| `mcpplibs.mwrap.zstd.macros` | in `mcpplibs::mwrap::zstd::macros`: `ZSTD_VERSION_MAJOR`, `_MINOR`, `_RELEASE`, `_NUMBER`, `_STRING`; `ZSTD_CLEVEL_DEFAULT`; `ZSTD_MAGICNUMBER`, `ZSTD_MAGIC_DICTIONARY`, `ZSTD_MAGIC_SKIPPABLE_START`, `ZSTD_MAGIC_SKIPPABLE_MASK`; `ZSTD_BLOCKSIZELOG_MAX`, `ZSTD_BLOCKSIZE_MAX`; `ZSTD_CONTENTSIZE_UNKNOWN`, `ZSTD_CONTENTSIZE_ERROR`; `ZSTD_MAX_INPUT_SIZE` |

## What is not exported

- **The experimental API.** The part of `<zstd.h>` behind
`ZSTD_STATIC_LINKING_ONLY` is declared only when a unit defines that macro
before the header, and upstream may change it in any release. A program that
needs it includes `<zstd.h>` with the macro and links the same
`compat.zstd`.
- **`<zdict.h>`.** `compat.zstd` builds `lib/common`, `lib/compress` and
`lib/decompress`, not `lib/dictBuilder`; the `ZDICT_*` functions would not
link. A raw-content dictionary (any bytes) works with `ZSTD_createCDict`.

## Macros

- **Constants.** Each object-like macro whose expansion is a constant
expression is an `inline constexpr` of the same name and value;
tests/macros.cpp compares every one with its macro.
- **`ZSTD_COMPRESSBOUND(srcSize)`** is a constant expression where
`ZSTD_compressBound` is a call, so it stays a macro in
`<mwrap/zstd/macros.hpp>`, reading `ZSTD_MAX_INPUT_SIZE` from the macros
module. It casts to `size_t`: import `std.compat`, which declares `::size_t`.
- **Declaration syntax.** `ZSTDLIB_API`, `ZSTDLIB_VISIBLE`, `ZSTDLIB_HIDDEN`,
their `ZSTDERRORLIB_*` counterparts, `ZSTD_DEPRECATED`, and the
stringification steps `ZSTD_QUOTE`, `ZSTD_EXPAND_AND_QUOTE`,
`ZSTD_LIB_VERSION` (whose result is `ZSTD_VERSION_STRING`) are not offered.
tools/surface.toml lists them.

## A header beside the module

`#include <zstd.h>` and `import mcpplibs.mwrap.zstd;` may appear in one unit:
they declare the same entities. A macro expands even inside a qualified name,
so `mcpplibs::mwrap::zstd::macros::ZSTD_CLEVEL_DEFAULT` written after the header
is `...::macros::3`. In such a unit use the header's macros, or read their values
under other names and `#undef` the macros before naming the constants
(tests/macros.cpp does this).

## Linking

The package depends on `compat.zstd` 1.5.7, which builds Zstandard from the
release sources with the target's toolchain, without assembly
(`ZSTD_DISABLE_ASM`). A program that imports the module links that library; it
does not look for a system zstd.
16 changes: 16 additions & 0 deletions include/mwrap/zstd/macros.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// <mwrap/zstd/macros.hpp> --- the macros of the headers that are neither a constant
// nor a type: they take arguments, or call a function. A named module cannot export a
// macro: include this header before `import mcpplibs.mwrap.zstd;`. Each definition is
// upstream's (Zstandard 1.5.7, BSD license or GPLv2), made only where the upstream header has
// not defined it, with the constants it names read from mcpplibs::mwrap::zstd::macros
// and NULL written nullptr.
// The upstream header included after this one redefines these macros.
// Written by tools/generate.py.
#ifndef MWRAP_ZSTD_MACROS_HPP
#define MWRAP_ZSTD_MACROS_HPP

#ifndef ZSTD_COMPRESSBOUND
#define ZSTD_COMPRESSBOUND(srcSize) (((size_t)(srcSize) >= ::mcpplibs::mwrap::zstd::macros::ZSTD_MAX_INPUT_SIZE) ? 0 : (srcSize) + ((srcSize)>>8) + (((srcSize) < (128<<10)) ? (((128<<10) - (srcSize)) >> 11) : 0))
#endif

#endif // MWRAP_ZSTD_MACROS_HPP
Loading
Loading