From f53f09f57188bfecf27bf77557e2c524902a2381 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Wed, 28 Oct 2026 15:51:39 +0900 Subject: [PATCH] mcpplibs.mwrap.sheenbidi 3.0.0: sheenbidi-m (SheenBidi as C++23 modules) Descriptor pkgs/mwrap/sheenbidi.lua (form A, the immutable mwrap-3.0.0 release asset; CN is the gitcode mcpp-res/mwrap-sheenbidi mirror with the same bytes), over compat.sheenbidi 3.0.0. Consumer tests/mwrap/sheenbidi-bidi: UAX #9's N0 bracket-pair example in an RTL paragraph, a line's visual order with mirrored brackets, the mirror and script locators, Unicode 17.0's Sidetic, the mapped macros and enumerators, and the side header beside the import. The installed-template check in validate.yml with its deployment contract (the wrapper's LICENSE/NOTICE, SheenBidi's README and LICENSE and the Unicode License v3, byte for byte in a relocated bundle), and the plan. --- .../2026-10-28-add-sheenbidi-mwrap-plan.md | 54 +++++++++++++++++++ .github/workflows/validate.yml | 5 ++ CHANGELOG.md | 7 +++ mcpp.toml | 1 + pkgs/mwrap/sheenbidi.lua | 51 ++++++++++++++++++ tests/mwrap/check_installed_template.py | 21 ++++++++ tests/mwrap/sheenbidi-bidi/mcpp.toml | 11 ++++ tests/mwrap/sheenbidi-bidi/tests/behavior.cpp | 52 ++++++++++++++++++ .../sheenbidi-bidi/tests/side_header.cpp | 20 +++++++ 9 files changed, 222 insertions(+) create mode 100644 .agents/docs/2026-10-28-add-sheenbidi-mwrap-plan.md create mode 100644 pkgs/mwrap/sheenbidi.lua create mode 100644 tests/mwrap/sheenbidi-bidi/mcpp.toml create mode 100644 tests/mwrap/sheenbidi-bidi/tests/behavior.cpp create mode 100644 tests/mwrap/sheenbidi-bidi/tests/side_header.cpp diff --git a/.agents/docs/2026-10-28-add-sheenbidi-mwrap-plan.md b/.agents/docs/2026-10-28-add-sheenbidi-mwrap-plan.md new file mode 100644 index 00000000..2f5bef16 --- /dev/null +++ b/.agents/docs/2026-10-28-add-sheenbidi-mwrap-plan.md @@ -0,0 +1,54 @@ +# Add mcpplibs.mwrap.sheenbidi (sheenbidi-m 3.0.0) + +## Form + +- Third-party upstream [Tehreer/SheenBidi](https://github.com/Tehreer/SheenBidi), release v3.0.0 (Unicode 17.0.0), + Apache-2.0. The tag archive (`86c56014…f786`) is the one compat.sheenbidi 3.0.0 pins (the parent change). +- Form A external mwrap module repository [mcpplibs/sheenbidi-m](https://github.com/mcpplibs/sheenbidi-m), package + `mcpplibs.mwrap.sheenbidi`, version 3.0.0 (first release of the identity, no revision suffix), tag series + `mwrap-${version}`; release asset `sheenbidi-m-3.0.0.tar.gz` (`76a0b0fe…3c41`), CN mirror + `gitcode.com/mcpp-res/mwrap-sheenbidi` with identical bytes. +- Implementation ownership: the library is compat.sheenbidi's (a static library from upstream's unity source; its + configuration and ABI are sufficient). The wrapper vendors no upstream code. It retains upstream's README.md and + LICENSE under `upstream/sheenbidi/` and the Unicode License v3 under `upstream/unicode/`, with checksums in + `upstream/documents.json`, and deploys them with its own LICENSE/NOTICE (`runtime.deploy`). + +## Modules + +- `mcpplibs.mwrap.sheenbidi`: every type (32), struct tag (11) and `SB_PUBLIC` function (51) of the headers upstream + installs, by `export using`, generated from the pinned headers. The experimental text API is compiled out + (compat.sheenbidi does not define `SB_CONFIG_EXPERIMENTAL_TEXT_API`, as upstream's default builds do not). +- `mcpplibs.mwrap.sheenbidi.macros`: 12 object-like macros as `inline constexpr` of the type of their expansion, 16 + function-like macros as constexpr function templates whose body is the macro, and the 236 enumerators of the + headers' unnamed enums (TU-local, so not exportable as they are) as constants of the typedef the header gives their + values (`SBBidiType`, `SBGeneralCategory`, `SBScript`, `SBStringEncoding`, `SBBoolean`). +- Stay macros, in `` (= ``): `SB_PUBLIC`, `SB_DEPRECATED`, + `SB_EXTERN_C_BEGIN` / `SB_EXTERN_C_END`. Not mapped: the 22 include guards. + +`tools/check_exports.py` fails when the generated sources are stale, a declaration is not exported, an enumerator is +not mapped, or a macro is neither mapped nor listed; every mapping is checked against the raw header in a +header-mixing test. + +## Verification + +Wrapper CI (Linux x86_64 GCC 16.1 and LLVM 23.1.3, Linux arm64 GCC 16.1, macOS LLVM, Windows LLVM, all required): +build, the four test programs, the export audit, and Unicode 17.0.0's conformance tests on `examples/sheenbidi` +(programs written against the module), the files taken from the release and checked byte for byte against +unicode.org's: BidiTest.txt (770,241 cases) and BidiCharacterTest.txt (91,707 cases, with the mirrored characters +against BidiMirroring.txt), 0 errors, nothing excluded. The templates job scaffolds the default `bidi` template as an +installed package and runs it. Until compat.sheenbidi is on this index's main, the wrapper's CI resolves the compat +namespace from the parent change's branch (`tools/candidate_index.py`, a no-op once published). + +Here, with the CI-pinned mcpp 2026.10.26.1: + +- `bash tests/run_members.sh sheenbidi-bidi`: the real release archive through the candidate index; UAX #9's N0 + bracket-pair example, a line's visual order with mirrored brackets, the mirror and script locators, Unicode 17.0's + Sidetic, the macros, and the side header beside the import: `test result ok`. +- `python3 tests/mwrap/check_installed_template.py --package mcpplibs.mwrap.sheenbidi@3.0.0 --deploy`: the unmodified + template from the real archive, then a relocated self-contained bundle whose `licenses/sheenbidi/` holds the + wrapper's LICENSE/NOTICE, SheenBidi's README and LICENSE and the Unicode license byte for byte. + +## Motivation + +openmui's text engine computes bidi levels; SheenBidi through this module replaces FriBidi there, whose +LGPL-2.1 does not fit a statically linked, permissively licensed framework. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 6021ffe8..e63085cc 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1633,6 +1633,11 @@ jobs: shell: bash run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.fribidi@1.0.16 --deploy + - name: Installed SheenBidi wrapper archive, actual default bidi template and its relocated notices + if: contains(format(' {0} ', env.MEMBERS), ' sheenbidi-bidi ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.sheenbidi@3.0.0 --deploy + - name: Installed FreeType wrapper archive, actual default banner template and its relocated FTL notices if: contains(format(' {0} ', env.MEMBERS), ' freetype-render ') && matrix.toolchain == 'default' shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index f50104f5..7daef0e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ ### Added +- **mcpplibs.mwrap.sheenbidi 3.0.0(新包)。** sheenbidi-m:以 C++23 模块(`import mcpplibs.mwrap.sheenbidi;`) + 使用 compat.sheenbidi 3.0.0。导出已安装头文件的全部类型、结构体标签与 51 个 `SB_PUBLIC` 函数(上游全局名不变); + `mcpplibs.mwrap.sheenbidi.macros` 把宏映射为常量与 constexpr 函数,并把无名枚举的 236 个枚举值映射为其 typedef + 类型的常量;`SB_PUBLIC`、`SB_DEPRECATED` 与 `extern "C"` 括号留在 ``。包装仓库的 CI + (Linux x86_64 GCC/LLVM、Linux arm64、macOS、Windows)运行 Unicode 17.0.0 的 BidiTest.txt(770,241 例)与 + BidiCharacterTest.txt(91,707 例),0 错误。GLOBAL 为 `mwrap-3.0.0` 发布资产,CN 为 GitCode `mcpp-res/mwrap-sheenbidi` + 镜像,sha256 一致。新增测试成员 `tests/mwrap/sheenbidi-bidi` 与已安装模板及其许可文件的部署检查。 - **compat.sheenbidi 3.0.0(新包)。** SheenBidi:Unicode 双向算法(UAX #9)的 C 实现,Apache-2.0,无依赖; 3.0.0 实现 Unicode 17.0.0,通过该版本的 BidiTest.txt 与 BidiCharacterTest.txt 全部用例。以上游的单一编译单元 `Source/SheenBidi.c`(`SB_CONFIG_UNITY`)从源码构建为静态库,只导出公共头文件的 51 个 `SB*` 入口;使用者的包含路径 diff --git a/mcpp.toml b/mcpp.toml index ce75e05e..3dbe8a89 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -22,6 +22,7 @@ members = [ "tests/mwrap/libpng-image", "tests/mwrap/concurrentqueue-mpmc", "tests/mwrap/fribidi-bidi", + "tests/mwrap/sheenbidi-bidi", "tests/mwrap/freetype-render", "tests/mwrap/harfbuzz-shape", "tests/mwrap/sdl3-headless", diff --git a/pkgs/mwrap/sheenbidi.lua b/pkgs/mwrap/sheenbidi.lua new file mode 100644 index 00000000..4165c10c --- /dev/null +++ b/pkgs/mwrap/sheenbidi.lua @@ -0,0 +1,51 @@ +-- Immutable upstream-aligned mwrap source release (sheenbidi-m mwrap-3.0.0); +-- GLOBAL and CN bytes are identical. +-- +-- The wrapper is Apache-2.0, and so is the library it links, compat.sheenbidi +-- (SheenBidi 3.0.0, Unicode 17.0.0), linked statically: the package deploys +-- SheenBidi's README and LICENSE and the Unicode License v3 of the data its +-- tables come from beside programs (runtime.deploy). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "sheenbidi", + description = "sheenbidi-m: SheenBidi (the Unicode Bidirectional Algorithm, Unicode 17.0.0) as C++23 modules", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/sheenbidi-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/sheenbidi-m/releases/download/${tag}/sheenbidi-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["3.0.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/sheenbidi-m/releases/download/mwrap-3.0.0/sheenbidi-m-3.0.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-sheenbidi/releases/download/3.0.0/sheenbidi-m-3.0.0.tar.gz", + }, + sha256 = "76a0b0fe0000ae43483b1b6e606908c09381f2d1cd8c736bff8b685c2fec3c41", + }, + }, + macosx = { + ["3.0.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/sheenbidi-m/releases/download/mwrap-3.0.0/sheenbidi-m-3.0.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-sheenbidi/releases/download/3.0.0/sheenbidi-m-3.0.0.tar.gz", + }, + sha256 = "76a0b0fe0000ae43483b1b6e606908c09381f2d1cd8c736bff8b685c2fec3c41", + }, + }, + windows = { + ["3.0.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/sheenbidi-m/releases/download/mwrap-3.0.0/sheenbidi-m-3.0.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/mwrap-sheenbidi/releases/download/3.0.0/sheenbidi-m-3.0.0.tar.gz", + }, + sha256 = "76a0b0fe0000ae43483b1b6e606908c09381f2d1cd8c736bff8b685c2fec3c41", + }, + }, + }, +} diff --git a/tests/mwrap/check_installed_template.py b/tests/mwrap/check_installed_template.py index 7488b5c2..66820fb9 100644 --- a/tests/mwrap/check_installed_template.py +++ b/tests/mwrap/check_installed_template.py @@ -162,6 +162,27 @@ 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/FriBidi license files", flush=True) + elif identity == "mcpplibs.mwrap.sheenbidi": + # SheenBidi is Apache-2.0 and linked statically: its LICENSE and + # README, and the Unicode License v3 of the data its tables are + # generated from, travel with the program beside the wrapper's. + sources = [path for store in (home / "xpkgs", home / "registry/data/xpkgs") + for path in store.glob(f"mcpplibs.mwrap-x-sheenbidi/{version}/*/src/sheenbidi.cppm")] + 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("licenses/sheenbidi")) + assert len(roots) == 1, f"Missing SheenBidi 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 + assert b"Apache License" in (roots[0] / "upstream/sheenbidi/LICENSE").read_bytes() + assert b"UNICODE LICENSE V3" in (roots[0] / "upstream/unicode/license.txt").read_bytes() + assert b"Apache License" in (roots[0] / "LICENSE").read_bytes() + notice = (roots[0] / "NOTICE").read_bytes() + assert b"mcpp-community" in notice and b"Muhammad Tayyab Akram" in notice + print(f"Verified {len(declared)} byte-identical wrapper/SheenBidi license files", flush=True) elif identity == "mcpplibs.mwrap.freetype": # FreeType is linked statically under the FreeType License (FTL), # whose credit the documentation carries: the license selector, the diff --git a/tests/mwrap/sheenbidi-bidi/mcpp.toml b/tests/mwrap/sheenbidi-bidi/mcpp.toml new file mode 100644 index 00000000..5e0779bb --- /dev/null +++ b/tests/mwrap/sheenbidi-bidi/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "sheenbidi-bidi" +version = "3.0.0" +description = "Consume the immutable SheenBidi wrapper: UAX #9's bracket-pair example in an RTL paragraph, a line's runs in visual order, the mirror and script locators, the mapped macros and enumerators, the side header beside the import" + +[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.sheenbidi" = "3.0.0" diff --git a/tests/mwrap/sheenbidi-bidi/tests/behavior.cpp b/tests/mwrap/sheenbidi-bidi/tests/behavior.cpp new file mode 100644 index 00000000..21e119fb --- /dev/null +++ b/tests/mwrap/sheenbidi-bidi/tests/behavior.cpp @@ -0,0 +1,52 @@ +import std; +import mcpplibs.mwrap.sheenbidi; + +using namespace mcpplibs::mwrap::sheenbidi::macros; + +int main() { + if (std::string_view(SBVersionGetString()) != "3.0.0") return 1; + + // UAX #9 N0's example in an RTL paragraph: the bracket pairs resolve together. + const std::u32string s = U"אב(גד[&ef]!)gh"; + const SBCodepointSequence seq{SBStringEncodingUTF32, s.data(), s.size()}; + SBAlgorithmRef algorithm = SBAlgorithmCreate(&seq); + SBParagraphRef paragraph = SBAlgorithmCreateParagraph(algorithm, 0, s.size(), 1); + const SBLevel* levels = SBParagraphGetLevelsPtr(paragraph); + if (std::vector(levels, levels + s.size()) != std::vector{1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 2}) return 2; + + // The line's runs in visual order, and its mirrored brackets. + SBLineRef line = SBParagraphCreateLine(paragraph, 0, s.size()); + std::u32string visual; + const SBRun* runs = SBLineGetRunsPtr(line); + for (SBUInteger r = 0; r < SBLineGetRunCount(line); ++r) + for (SBUInteger k = 0; k < runs[r].length; ++k) { + const SBUInteger i = runs[r].level & 1 ? runs[r].offset + runs[r].length - 1 - k : runs[r].offset + k; + const SBCodepoint mirror = runs[r].level & 1 ? SBCodepointGetMirror(s[i]) : 0; + visual += static_cast(mirror != 0 ? mirror : s[i]); + } + if (visual != U"gh(![ef&]דג)בא") return 3; + SBMirrorLocatorRef mirrors = SBMirrorLocatorCreate(); + SBMirrorLocatorLoadLine(mirrors, line, s.data()); + int mirrored = 0; + while (SBMirrorLocatorMoveNext(mirrors)) ++mirrored; + SBMirrorLocatorRelease(mirrors); + if (mirrored != 4) return 4; + SBLineRelease(line); + SBParagraphRelease(paragraph); + SBAlgorithmRelease(algorithm); + + // Scripts over UTF-8, and Unicode 17.0's Sidetic. + const std::string_view utf8 = "abc \xD8\xB9\xD8\xB1\xD8\xA8\xD9\x8A"; + const SBCodepointSequence u8seq{SBStringEncodingUTF8, utf8.data(), utf8.size()}; + SBScriptLocatorRef scripts = SBScriptLocatorCreate(); + SBScriptLocatorLoadCodepoints(scripts, &u8seq); + std::vector found; + while (SBScriptLocatorMoveNext(scripts)) found.push_back(SBScriptLocatorGetAgent(scripts)->script); + SBScriptLocatorRelease(scripts); + if (found != std::vector{SBScriptLATN, SBScriptARAB}) return 5; + if (SBCodepointGetBidiType(0x10940) != SBBidiTypeR || SBCodepointGetScript(0x10940) != SBScriptSIDT) return 6; + + // The mapped macros, at compile time. + static_assert(SBBidiTypeIsStrong(SBBidiTypeAL) && !SBCodepointIsValid(0xD800u) && SBLevelMax == 125); + return 0; +} diff --git a/tests/mwrap/sheenbidi-bidi/tests/side_header.cpp b/tests/mwrap/sheenbidi-bidi/tests/side_header.cpp new file mode 100644 index 00000000..1a577056 --- /dev/null +++ b/tests/mwrap/sheenbidi-bidi/tests/side_header.cpp @@ -0,0 +1,20 @@ +// The side header from the installed package, beside the import: the raw +// header's enumerators and macros with the module's entities. +#include + +import std; +import mcpplibs.mwrap.sheenbidi; + +SB_EXTERN_C_BEGIN +SB_PUBLIC int mwrap_sheenbidi_consumer(int value); +SB_EXTERN_C_END +int mwrap_sheenbidi_consumer(int value) { return value * 2; } + +int main() { + namespace m = mcpplibs::mwrap::sheenbidi::macros; + static_assert(m::SBBidiTypeR == ::SBBidiTypeR && std::is_same_v); + if (::SBCodepointGetBidiType(0x05D0) != SBBidiTypeR) return 1; + if (SBBidiTypeIsStrong(SBBidiTypeR) != (m::SBBidiTypeIsStrong)(SBBidiTypeR)) return 2; + if (&::SBAlgorithmCreate != &SBAlgorithmCreate) return 3; + return mwrap_sheenbidi_consumer(21) == 42 ? 0 : 4; +}