#!/usr/bin/env bash # Build WaiFFI.xcframework: libwai (wai-rs, features `native_sink` and `sink_decode`, no codec # library) for every target the Swift package supports, with include/wai.h and a module map, into # ../build/; and stage the native sink corpus, the integer decode's vectors and the model policy # corpus into the tests' resources. # # scripts/build-xcframework.sh every slice # WAI_SINK_SLICES="macos ios-sim" ... only those (macos, ios, ios-sim, tvos, tvos-sim) # # The phone, tablet and desktop targets are tier 2 and build with the repository's pinned # toolchain (rust-toolchain.toml) once their standard library is installed # (`rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios x86_64-apple-darwin`). # The television # targets are tier 3: their standard library is built from source with the nightly toolchain named # by WAI_SINK_NIGHTLY (default nightly-2026-10-17), which needs its `rust-src` component; their # simulator slice is arm64 alone. set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" RS="$HERE/../wai-rs" OUT="$HERE/build" TARGET_DIR="${CARGO_TARGET_DIR:-$RS/target}" SLICES="${WAI_SINK_SLICES:-macos ios ios-sim tvos tvos-sim}" NIGHTLY="${WAI_SINK_NIGHTLY:-nightly-2026-10-17}" FEATURES=(--no-default-features --features native_sink,sink_decode) export MACOSX_DEPLOYMENT_TARGET=13.0 IPHONEOS_DEPLOYMENT_TARGET=16.0 TVOS_DEPLOYMENT_TARGET=16.0 build() { # [nightly] if [ "${2:-}" = nightly ]; then cargo "+$NIGHTLY" build --release --locked "${FEATURES[@]}" --lib --manifest-path "$RS/Cargo.toml" \ --target "$1" -Zbuild-std=std,panic_abort else cargo build --release --locked "${FEATURES[@]}" --lib --manifest-path "$RS/Cargo.toml" --target "$1" fi } lib() { echo "$TARGET_DIR/$1/release/libwai.a"; } rm -rf "$OUT/WaiFFI.xcframework" "$OUT/headers" "$OUT/lib" mkdir -p "$OUT/headers" "$OUT/lib" cp "$RS/include/wai.h" "$OUT/headers/wai.h" cat > "$OUT/headers/module.modulemap" <<'MAP' module WaiFFI { header "wai.h" export * } MAP args=() for s in $SLICES; do case "$s" in macos) build aarch64-apple-darwin; build x86_64-apple-darwin mkdir -p "$OUT/lib/macos" lipo -create "$(lib aarch64-apple-darwin)" "$(lib x86_64-apple-darwin)" -output "$OUT/lib/macos/libwai.a" args+=(-library "$OUT/lib/macos/libwai.a" -headers "$OUT/headers") ;; ios) build aarch64-apple-ios args+=(-library "$(lib aarch64-apple-ios)" -headers "$OUT/headers") ;; ios-sim) build aarch64-apple-ios-sim; build x86_64-apple-ios mkdir -p "$OUT/lib/ios-sim" lipo -create "$(lib aarch64-apple-ios-sim)" "$(lib x86_64-apple-ios)" -output "$OUT/lib/ios-sim/libwai.a" args+=(-library "$OUT/lib/ios-sim/libwai.a" -headers "$OUT/headers") ;; tvos) build aarch64-apple-tvos nightly args+=(-library "$(lib aarch64-apple-tvos)" -headers "$OUT/headers") ;; tvos-sim) build aarch64-apple-tvos-sim nightly args+=(-library "$(lib aarch64-apple-tvos-sim)" -headers "$OUT/headers") ;; *) echo "build-xcframework: unknown slice $s" >&2; exit 2 ;; esac done xcodebuild -create-xcframework "${args[@]}" -output "$OUT/WaiFFI.xcframework" # The corpus the tests read, staged into their bundle's resources so a test run on a device, where # the repository is not, reads it too. cp "$HERE/../live-conformance/native-sink/vectors.json" "$HERE/Tests/WaiSinkTests/Resources/vectors.json" # The integer decode's vectors (native-sink §13): one or more of each integer capability, with its # set, so the tests decode them with the device's GPU on a device too. INT="$HERE/Tests/WaiSinkTests/Resources/int" V="$HERE/../wai-rs/tests/vectors" rm -rf "$INT" mkdir -p "$INT" for f in int_hyper/tiny_bitstream.wih1 int_hyper/tiny_entropy.json int_hyper/tiny_g_s.bin int_hyper/tiny_h_s.bin \ int_hyper/leaves_bitstream.wih1 int_hyper/leaves_entropy.json int_hyper/leaves_g_s.bin int_hyper/leaves_h_s.bin \ int_video/video_nk_bitstream.wiv1 int_video/video_real_bitstream.wiv1 int_audio/audio_bitstream.wia1 \ int_audio/audio_model.bin int_splat/splat.wis1 int_splat/splat_gs.bin int_mlicpp/stream.wai int_mlicpp/model.wmb \ int_cond/clip.wic1 int_cond/model.wcb; do cp "$V/$f" "$INT/$(echo "$f" | tr / _)" done cp "$HERE/../neural-conformance/vectors/photo/latent.bin" "$INT/photo_latent.bin" cp "$HERE/../neural-conformance/vectors/photo/int_synth.json" "$INT/photo_int_synth.json" cat > "$INT/cases.json" <<'CASES' [ {"capability": "wai.neural.int_hyper", "name": "tiny", "payload": "int_hyper_tiny_bitstream.wih1", "set": {"entropy.json": "int_hyper_tiny_entropy.json", "g_s.whm": "int_hyper_tiny_g_s.bin", "h_s.whm": "int_hyper_tiny_h_s.bin"}}, {"capability": "wai.neural.int_hyper", "name": "leaves", "payload": "int_hyper_leaves_bitstream.wih1", "set": {"entropy.json": "int_hyper_leaves_entropy.json", "g_s.whm": "int_hyper_leaves_g_s.bin", "h_s.whm": "int_hyper_leaves_h_s.bin"}}, {"capability": "wai.video.int_hyper", "name": "video_nk", "payload": "int_video_video_nk_bitstream.wiv1", "set": {"entropy.json": "int_hyper_tiny_entropy.json", "g_s.whm": "int_hyper_tiny_g_s.bin", "h_s.whm": "int_hyper_tiny_h_s.bin"}}, {"capability": "wai.video.int_motion", "name": "video_real", "payload": "int_video_video_real_bitstream.wiv1", "set": {}}, {"capability": "wai.audio.int_codec", "name": "audio", "payload": "int_audio_audio_bitstream.wia1", "set": {"model.wam": "int_audio_audio_model.bin"}}, {"capability": "wai.splat.int_codec", "name": "splat", "payload": "int_splat_splat.wis1", "set": {"g_s.whm": "int_splat_splat_gs.bin"}}, {"capability": "wai.neural.int_synth", "name": "photo", "payload": "photo_latent.bin", "set": {"int_synth.json": "photo_int_synth.json"}}, {"capability": "wai.neural.int_mlicpp", "name": "int_mlicpp", "envelope": "int_mlicpp_stream.wai", "set": {"model.wmb": "int_mlicpp_model.wmb"}}, {"capability": "wai.video.int_cond", "name": "clip", "payload": "int_cond_clip.wic1", "set": {"model.wcb": "int_cond_model.wcb"}} ] CASES # The integer frame forms (native-sink §13.6): a stream of each, its objects in order, its set, and # each frame's SHA-256 (frames.json). python3 -I - "$HERE/.." "$INT" <<'FRAMES' import hashlib, json, shutil, sys wai, out = sys.argv[1], sys.argv[2] fs = json.load(open(f"{wai}/int-payload-conformance/frame-stream/index.json")) ic = json.load(open(f"{wai}/int-payload-conformance/int-cond/index.json")) cases = [] for index, name, common in [(fs, "int_video", None), (fs, "video_nk", None), (ic, "clip", ic["set"])]: s = next(x for x in index["streams"] if x["name"] == name) members = {} for member, rel in (common if common is not None else s["set"]).items(): f = f"frames_{name}_set_{member}" shutil.copyfile(f"{wai}/{rel}", f"{out}/{f}") members[member] = f # A pin in the form `wai.prior.set` covers the set's listing (SPEC §3.1), which the host holds. listing = None if common is None and members: listing = f"frames_{name}_listing" lines = sorted((m, hashlib.sha256(open(f"{out}/{f}", "rb").read()).hexdigest()) for m, f in members.items()) open(f"{out}/{listing}", "wb").write("".join(f"{d} {m}\n" for m, d in lines).encode()) objects = [] for i, o in enumerate(s["objects"]): f = f"frames_{name}_{i:02}.wai" shutil.copyfile(f"{wai}/{o['envelope']}", f"{out}/{f}") objects.append({"envelope": f, "frame_sha256": o["frame_sha256"]}) cases.append({"capability": s["capability"], "name": name, "set": members, "listing": listing, "objects": objects}) json.dump(cases, open(f"{out}/frames.json", "w"), indent=1) FRAMES # A sink that holds a model policy (native-sink §14): the corpus live-conformance/model-sink/, and # every file its cases read, beside it under its path relative to wai/ with each `/` written `_`. MODEL="$HERE/Tests/WaiSinkTests/Resources/model" rm -rf "$MODEL" mkdir -p "$MODEL" python3 -I - "$HERE/.." "$MODEL" <<'MODEL' import json, shutil, sys wai, out = sys.argv[1], sys.argv[2] doc = json.load(open(f"{wai}/live-conformance/model-sink/cases.json")) paths = set() for s in doc["sets"]: paths.update(s["files"].values()) for c in doc["decode_cases"] + doc["roster"]["decode_cases"]: paths.update(p for p in (c["decode"]["payload"], c["decode"]["envelope"]) if p) for c in doc["frame_cases"]: paths.update(c["frames"]["objects"]) for p in sorted(paths): shutil.copyfile(f"{wai}/{p}", f"{out}/{p.replace('/', '_')}") shutil.copyfile(f"{wai}/live-conformance/model-sink/cases.json", f"{out}/cases.json") MODEL echo "wrote $OUT/WaiFFI.xcframework ($SLICES)"