Skip to content

Release v0.2.0

Release v0.2.0 #216

Workflow file for this run

name: Build Apple
on:
pull_request:
branches: [master]
paths:
- 'bindings/apple/**'
- 'crates/xybrid-bolt/**'
- 'crates/xybrid-ffi-facade/**'
- 'crates/xybrid-core/**'
- 'crates/xybrid-sdk/**'
- 'xtask/**'
- 'Cargo.lock'
- 'Cargo.toml'
- '.github/workflows/build-apple.yml'
# Docs-only changes under the paths above (READMEs, examples) never
# affect the native build — exclude markdown so they don't trigger it.
- '!**.md'
# Run on master merges too, so each merge saves a default-branch-scoped
# cache (sccache + rust-cache). GitHub Actions caches are branch-scoped:
# a PR run can only restore caches from its own branch or the default
# branch. Without this trigger master never builds, so master has no
# cache, so every new PR branch's first run is cold (~30 min). Warming
# master here lets new branches start warm (~5 min). Keep these paths in
# sync with the pull_request trigger above.
push:
branches: [master]
paths:
- 'bindings/apple/**'
- 'crates/xybrid-bolt/**'
- 'crates/xybrid-ffi-facade/**'
- 'crates/xybrid-core/**'
- 'crates/xybrid-sdk/**'
- 'xtask/**'
- 'Cargo.lock'
- 'Cargo.toml'
- '.github/workflows/build-apple.yml'
- '!**.md'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: read-all
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
build-xcframework:
name: Build XCFramework
runs-on: macos-14
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable
with:
toolchain: stable
targets: >-
aarch64-apple-ios,
aarch64-apple-ios-sim,
aarch64-apple-darwin
- name: Setup sccache
uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
- name: Configure sccache environment
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
prefix-key: "v1-rust"
shared-key: "apple-xcframework"
cache-all-crates: "true"
save-if: "true"
# boltffi CLI provides the `boltffi pack apple` that
# `cargo xtask build-xcframework` shells out to. Cache the installed
# binary across runs.
- name: Cache boltffi CLI
id: cache-boltffi
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.cargo/bin/boltffi
key: boltffi-cli-${{ runner.os }}-0.25.3
- name: Install boltffi CLI
# `which || install` is idempotent whether or not the cache populated
# the binary. Pinned to 0.25.3 to MATCH the runtime `boltffi` crate
# (Cargo.lock): a CLI/runtime version skew mis-generates unit-ok
# `Result<(), E>` exports (model warmup/unload) as a `void` foreign
# function that drops the error and leaks the result buffer. `--locked`
# installs against the CLI's own bundled Cargo.lock for reproducibility.
run: which boltffi || cargo install boltffi_cli --version 0.25.3 --locked
# Prebuilt-natives fast path (iOS device + arm64 simulator — the two
# slices boltffi's xcframework builds). Best-effort: pull the prebuilt
# llama.cpp slices and point build.rs at them so the xcframework build
# skips the llama.cpp cmake compile per slice. On ANY miss/error this is a
# silent no-op (continue-on-error) and the slice compiles from source — it
# can never break the build. build.rs keys on
# XYBRID_NATIVES_PREBUILT_DIR/<target>, so each slice in the single
# `boltffi pack apple` invocation resolves its own (or falls through).
- name: Setup oras
uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2.0.0
# `oras login`, not docker/login-action: macOS runners have no docker.
# Best-effort + same-repo only (forks have no token → anonymous pull).
- name: Log in to ghcr (best-effort, same-repo)
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
continue-on-error: true
env:
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: echo "$GHCR_TOKEN" | oras login ghcr.io -u "${{ github.actor }}" --password-stdin
- name: Pull prebuilt natives (iOS device + sim, best-effort)
continue-on-error: true
env:
XYBRID_NATIVES_PKG: ghcr.io/xybrid-ai/llama-natives
run: |
# Use the Rust TARGET spelling aarch64-apple-ios-sim (with -sim);
# build.rs keys the slice dir on it.
DEST="$RUNNER_TEMP/natives"
got_any=0
for triple in aarch64-apple-ios aarch64-apple-ios-sim; do
if tools/scripts/natives-pull.sh "$triple" base "$DEST"; then
echo "prebuilt natives staged for $triple (will skip cmake)"
got_any=1
else
echo "no prebuilt natives for $triple — compiles from source"
fi
done
if [ "$got_any" = 1 ]; then
echo "XYBRID_NATIVES_PREBUILT_DIR=$DEST" >> "$GITHUB_ENV"
else
echo "no prebuilt natives — compiles from source (unchanged)"
fi
- name: Build XCFramework
run: cargo xtask build-xcframework --release
- name: Verify XCFramework
run: |
XCFW_PATH="bindings/apple/XCFrameworks/XybridFFI.xcframework"
if [ ! -d "$XCFW_PATH" ]; then
echo "Error: XCFramework not found at $XCFW_PATH"
exit 1
fi
echo "XCFramework contents:"
ls -la "$XCFW_PATH"
echo ""
echo "Verifying architectures..."
# boltffi names the slice static lib libxybrid-bolt.a and lays
# out one dir per slice (ios-arm64, ios-arm64-simulator; macOS is
# excluded via boltffi.toml). Glob rather than hard-code so this
# survives naming/slice changes.
found=$(find "$XCFW_PATH" -name '*.a')
if [ -z "$found" ]; then
echo "Error: no static libraries found inside the XCFramework"
exit 1
fi
echo "$found" | while read -r lib; do file "$lib"; done
- name: Compile Swift binding wrapper (iOS Simulator)
# Compiles the hand-written Xybrid.swift + the bolt-generated
# xybrid_bolt.swift into the SPM `Xybrid` library, so a wrapper break
# (non-exhaustive errorDescription switch, a missing import, an
# enum-shape mismatch vs the regenerated bindings) fails CI here instead
# of only when a human builds the example app. Uses the xcframework
# just built above: the committed state is useLocalNatives = false (so
# SPM consumers resolve the published release asset), so this step flips
# to local mode at CI runtime before resolving — otherwise it would try
# to download the remote asset, which 404s on a release PR while the
# v<version> release is still a draft. The flip is CI-only, never committed.
#
# Targets the iOS Simulator for two reasons: (1) the xcframework ships
# only ios-arm64 + ios-arm64-simulator (boltffi.toml include_macos=false),
# so a host/macOS build has no slice to link; (2) Xybrid.swift gates
# `import UIKit` behind `#if os(iOS)`, so only an iOS build type-checks
# the UIKit-conditional code — the code most likely to break. Compiles
# the wrapper library ONLY (the example .xcodeproj has no shared scheme;
# ContentView/LiveVision aren't built) and boots no simulator (generic
# destination = compile + link, no run, no signing).
run: |
set -euo pipefail
# CI-runtime flip to the locally-built xcframework. Do NOT commit this.
./bindings/apple/scripts/set-natives-mode.sh --set-local
xcodebuild -resolvePackageDependencies
xcodebuild build \
-scheme Xybrid \
-destination 'generic/platform=iOS Simulator' \
-skipPackagePluginValidation \
CODE_SIGNING_ALLOWED=NO
- name: Check iOS vision build (compile-only, not shipped)
# Compile-only gate for the OPT-IN native llama.cpp vision backend
# (mtmd). This backend is in NO platform preset and is NOT shipped in
# the XCFramework above — this step only proves the
# `llm-llamacpp-vision` (mtmd) chain still cross-compiles for iOS, the
# gate the UniFFI->bolt migration dropped (it pointed at the deleted
# `xybrid-uniffi`; retargeted here at `xybrid-sdk`, which now carries
# both `platform-ios` and `llm-llamacpp-vision`: xybrid-sdk ->
# xybrid-core/llm-llamacpp-vision -> xybrid-llama-sys/vision; the -sys
# crate lives at crates/llama-cpp-sys/, package `xybrid-llama-sys`).
#
# `check` (not `build`) is sufficient and matches the old gate: the
# crates/llama-cpp-sys/build.rs runs on `check` and is what compiles
# llama.cpp + the mtmd C++ via cmake and bindgens the mtmd FFI (gated
# on CARGO_FEATURE_VISION). Like the shipped llm-llamacpp build above,
# it sources llama.cpp through build.rs's pinned-commit OUT_DIR clone
# (vendor/llama-cpp is an un-checked-out submodule), so no extra
# checkout is needed.
run: cargo check -p xybrid-sdk --features platform-ios,llm-llamacpp-vision --target aarch64-apple-ios
- name: Upload XCFramework artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: XybridFFI-xcframework
path: bindings/apple/XCFrameworks/XybridFFI.xcframework
if-no-files-found: error