Skip to content

bindings-release

bindings-release #5

name: bindings-release
# Build librockbox_ffi for macOS + Linux + FreeBSD/NetBSD, package the prebuilt
# binary into the Ruby / Python / TypeScript bindings, and attach every
# artifact to a GitHub Release. No registry tokens required — uploads use the
# built-in GITHUB_TOKEN.
#
# Manually triggerable (workflow_dispatch, pick a tag) or on a `bindings-v*`
# tag push. Targets: darwin-arm64, darwin-x64, linux-x64, linux-arm64 (native
# runners); freebsd-x64, netbsd-x64 (vmactions VMs). BSD jobs are
# continue-on-error and every packaging step skips a target whose artifact is
# missing, so a flaky BSD build never blocks the macOS/Linux release.
on:
workflow_dispatch:
inputs:
tag:
description: "Release tag to create / upload assets to"
required: true
default: "bindings-v0.1.1"
push:
tags:
- "bindings-v*"
permissions:
contents: read
env:
# Ruby gem version (drives the .gem filenames). Python and npm carry their
# own versions in their manifests — the Ruby gem intentionally lags at 0.1.0.
BINDING_VERSION: "0.1.0"
jobs:
# 1. Build the shared library on a native runner per target.
build-lib:
name: build-lib (${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- target: darwin-arm64
os: macos-latest
libfile: librockbox_ffi.dylib
- target: darwin-x64
os: macos-15-intel
libfile: librockbox_ffi.dylib
- target: linux-x64
os: ubuntu-latest
libfile: librockbox_ffi.so
- target: linux-arm64
os: ubuntu-24.04-arm
libfile: librockbox_ffi.so
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install Linux audio deps
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev pkg-config
- name: Build librockbox_ffi
run: cargo build --release -p rockbox-ffi
- name: Stage binary
run: |
mkdir -p out
cp "target/release/${{ matrix.libfile }}" "out/${{ matrix.libfile }}"
- uses: actions/upload-artifact@v4
with:
name: librockbox_ffi-${{ matrix.target }}
path: out/${{ matrix.libfile }}
if-no-files-found: error
# 1b. FreeBSD amd64 — built inside a VM (no native GitHub runner).
build-lib-freebsd:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Build in FreeBSD VM
uses: vmactions/freebsd-vm@v1
with:
usesh: true
prepare: |
pkg install -y rust alsa-lib pkgconf
run: |
cargo build --release -p rockbox-ffi
mkdir -p out
cp target/release/librockbox_ffi.so out/librockbox_ffi.so
- uses: actions/upload-artifact@v4
with:
name: librockbox_ffi-freebsd-x64
path: out/librockbox_ffi.so
if-no-files-found: error
# 1c. NetBSD amd64 — built inside a VM (no native GitHub runner).
build-lib-netbsd:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Build in NetBSD VM
uses: vmactions/netbsd-vm@v1
with:
prepare: |
/usr/sbin/pkg_add rust alsa-lib pkgconf
run: |
cargo build --release -p rockbox-ffi
mkdir -p out
cp target/release/librockbox_ffi.so out/librockbox_ffi.so
- uses: actions/upload-artifact@v4
with:
name: librockbox_ffi-netbsd-x64
path: out/librockbox_ffi.so
if-no-files-found: error
# 2a. Ruby platform gems (packaging is host-independent — build all on one runner).
ruby:
needs: [build-lib, build-lib-freebsd, build-lib-netbsd]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Build platform gems
working-directory: bindings/ruby
run: |
set -euxo pipefail
declare -A PLAT=(
[darwin-arm64]=arm64-darwin
[darwin-x64]=x86_64-darwin
[linux-x64]=x86_64-linux
[linux-arm64]=aarch64-linux
[freebsd-x64]=x86_64-freebsd
[netbsd-x64]=x86_64-netbsd
)
declare -A LIB=(
[darwin-arm64]=librockbox_ffi.dylib
[darwin-x64]=librockbox_ffi.dylib
[linux-x64]=librockbox_ffi.so
[linux-arm64]=librockbox_ffi.so
[freebsd-x64]=librockbox_ffi.so
[netbsd-x64]=librockbox_ffi.so
)
mkdir -p pkg
for target in "${!PLAT[@]}"; do
src="$GITHUB_WORKSPACE/artifacts/librockbox_ffi-${target}/${LIB[$target]}"
if [ ! -f "$src" ]; then echo "skip ${target}: no artifact"; continue; fi
rm -rf vendor && mkdir vendor
cp "$src" vendor/
ROCKBOX_GEM_PLATFORM="${PLAT[$target]}" gem build rockbox_ffi.gemspec -o "pkg/rockbox_ffi-${BINDING_VERSION}-${PLAT[$target]}.gem"
done
rm -rf vendor
# source gem (fallback for unlisted platforms / repo checkouts)
gem build rockbox_ffi.gemspec -o "pkg/rockbox_ffi-${BINDING_VERSION}.gem"
ls -l pkg
- uses: actions/upload-artifact@v4
with:
name: ruby-gems
path: bindings/ruby/pkg/*.gem
# 2b. Python wheels. macOS/BSD: retag the pure wheel; Linux: auditwheel
# repair (bundles libasound + sets a real manylinux tag). BSD wheels are
# best-effort — pip falls back to the sdist when the tag does not match.
python:
needs: [build-lib, build-lib-freebsd, build-lib-netbsd]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Install build tooling
run: pip install build wheel auditwheel patchelf
- name: Build wheels
working-directory: bindings/python
run: |
set -euxo pipefail
declare -A LIB=(
[darwin-arm64]=librockbox_ffi.dylib
[darwin-x64]=librockbox_ffi.dylib
[linux-x64]=librockbox_ffi.so
[linux-arm64]=librockbox_ffi.so
[freebsd-x64]=librockbox_ffi.so
[netbsd-x64]=librockbox_ffi.so
)
declare -A TAG=(
[darwin-arm64]=macosx_11_0_arm64
[darwin-x64]=macosx_10_12_x86_64
[freebsd-x64]=freebsd_13_amd64
[netbsd-x64]=netbsd_10_amd64
)
mkdir -p wheelhouse
python -m build --sdist --outdir wheelhouse
for target in darwin-arm64 darwin-x64 linux-x64 linux-arm64 freebsd-x64 netbsd-x64; do
src="$GITHUB_WORKSPACE/artifacts/librockbox_ffi-${target}/${LIB[$target]}"
if [ ! -f "$src" ]; then echo "skip ${target}: no artifact"; continue; fi
rm -rf src/rockbox_ffi/_lib dist && mkdir -p src/rockbox_ffi/_lib
cp "$src" src/rockbox_ffi/_lib/
python -m build --wheel --outdir dist
case "$target" in
linux-*)
# auditwheel sets the manylinux tag. libasound is treated as an
# external system dependency (--exclude): it can't be vendored
# cross-arch on a single-arch runner, and a bundled libasound
# can't reliably dlopen the system's ALSA plugins anyway.
# Linux users need the libasound2 system package at runtime.
auditwheel repair --exclude libasound.so.2 --wheel-dir wheelhouse dist/*.whl
;;
*)
python -m wheel tags --platform-tag "${TAG[$target]}" --remove dist/*.whl
cp dist/*.whl wheelhouse/
;;
esac
done
rm -rf src/rockbox_ffi/_lib
ls -l wheelhouse
- uses: actions/upload-artifact@v4
with:
name: python-wheels
path: bindings/python/wheelhouse/*
# 2c. npm: pack the main package + per-platform binary packages into tarballs.
npm:
needs: [build-lib, build-lib-freebsd, build-lib-netbsd]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: "20"
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Build dist, stage binaries, pack tarballs
working-directory: bindings/typescript
run: |
set -euxo pipefail
bun install
bun run build
declare -A LIB=(
[darwin-arm64]=librockbox_ffi.dylib
[darwin-x64]=librockbox_ffi.dylib
[linux-x64]=librockbox_ffi.so
[linux-arm64]=librockbox_ffi.so
[freebsd-x64]=librockbox_ffi.so
[netbsd-x64]=librockbox_ffi.so
)
dest="$GITHUB_WORKSPACE/bindings/typescript/tarballs"
mkdir -p "$dest"
for target in darwin-arm64 darwin-x64 linux-x64 linux-arm64 freebsd-x64 netbsd-x64; do
src="$GITHUB_WORKSPACE/artifacts/librockbox_ffi-${target}/${LIB[$target]}"
if [ ! -f "$src" ]; then echo "skip ${target}: no artifact"; continue; fi
cp "$src" "npm/${target}/${LIB[$target]}"
(cd "npm/${target}" && npm pack --pack-destination "$dest")
done
npm pack --pack-destination "$dest"
ls -l "$dest"
- uses: actions/upload-artifact@v4
with:
name: npm-tarballs
path: bindings/typescript/tarballs/*.tgz
# 3. Collect every artifact and attach it to the GitHub Release.
release:
needs: [build-lib, build-lib-freebsd, build-lib-netbsd, ruby, python, npm]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Collect release assets
run: |
set -euxo pipefail
mkdir -p release
# packaged: gems, wheels + sdist, npm tarballs
cp artifacts/ruby-gems/*.gem release/ 2>/dev/null || true
cp artifacts/python-wheels/* release/ 2>/dev/null || true
cp artifacts/npm-tarballs/*.tgz release/ 2>/dev/null || true
# raw shared libs, renamed per target so they don't collide
for d in artifacts/librockbox_ffi-*; do
[ -d "$d" ] || continue
t="$(basename "$d" | sed 's/^librockbox_ffi-//')"
for f in "$d"/librockbox_ffi.*; do
[ -f "$f" ] || continue
cp "$f" "release/librockbox_ffi-${t}.${f##*.}"
done
done
ls -l release
- name: Determine tag
id: tag
run: |
if [ "${{ github.event_name }}" = "push" ]; then
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
fi
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
files: release/*
fail_on_unmatched_files: true