name: Release binaries
on:
workflow_call:
inputs:
tag:
description: Existing release tag to build and attach binaries to
required: true
type: string
workflow_dispatch:
inputs:
tag:
description: Existing release tag to build and attach binaries to
required: true
type: string
permissions:
contents: read
jobs:
build:
name: Binary (${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-22.04
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04-arm
- target: x86_64-apple-darwin
os: macos-15-intel
- target: aarch64-apple-darwin
os: macos-14
- target: x86_64-pc-windows-msvc
os: windows-2022
- target: aarch64-pc-windows-msvc
os: windows-11-arm
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.tag || github.sha }}
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Validate release tag against crate version
if: inputs.tag != ''
env:
RELEASE_TAG: ${{ inputs.tag }}
run: |
python - <<'PYTHON'
import os, tomllib
with open("Cargo.toml", "rb") as manifest:
version = tomllib.load(manifest)["package"]["version"]
assert os.environ["RELEASE_TAG"] == f"v{version}", "Release tag must match Cargo.toml"
PYTHON
- name: Install Linux audio dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libasound2-dev
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: release-${{ matrix.target }}
- name: Build all release binaries with default GUI and JIT features
run: cargo build --locked --release --bins --target '${{ matrix.target }}'
- name: Package and smoke-test archive
run: |
python - '${{ matrix.target }}' <<'PYTHON'
import hashlib
import json
import pathlib
import shutil
import subprocess
import sys
import tarfile
import tempfile
import tomllib
import zipfile
target = sys.argv[1]
with open("Cargo.toml", "rb") as manifest:
version = tomllib.load(manifest)["package"]["version"]
name = f"systemless-{target}-v{version}"
windows = "windows" in target
metadata = json.loads(subprocess.check_output(
["cargo", "metadata", "--locked", "--no-deps", "--format-version", "1"], text=True
))
crate = next(p for p in metadata["packages"] if p["name"] == "systemless")
binaries = [t["name"] + (".exe" if windows else "")
for t in crate["targets"] if "bin" in t["kind"]]
assert binaries, "Cargo metadata contains no binaries"
dist = pathlib.Path("dist")
dist.mkdir(exist_ok=True)
archive = dist / (name + (".zip" if windows else ".tgz"))
with tempfile.TemporaryDirectory() as temporary:
root = pathlib.Path(temporary)
package = root / name
package.mkdir()
for binary in binaries:
shutil.copy2(pathlib.Path("target") / target / "release" / binary, package)
for filename in ("LICENSE", "OFL.txt", "README.md"):
shutil.copy2(filename, package)
if windows:
with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED) as output:
for path in sorted(package.iterdir()):
output.write(path, f"{name}/{path.name}")
else:
with tarfile.open(archive, "w:gz") as output:
output.add(package, arcname=name)
# Run the executable extracted from the archive to check both the package
# layout and native runtime dependencies without requiring a GUI display.
extracted = root / "extracted"
if windows:
with zipfile.ZipFile(archive) as source:
source.extractall(extracted)
else:
with tarfile.open(archive) as source:
source.extractall(extracted, filter="data")
for binary in binaries:
assert (extracted / name / binary).is_file(), f"Archive is missing {binary}"
binary = "systemless.exe" if windows else "systemless"
subprocess.run([str(extracted / name / binary), "--help"], check=True)
checksum = hashlib.file_digest(archive.open("rb"), "sha256").hexdigest()
archive.with_name(archive.name + ".sha256").write_text(
f"{checksum} {archive.name}\n", encoding="utf-8"
)
print(archive)
PYTHON
- uses: actions/upload-artifact@v4
with:
name: systemless-${{ matrix.target }}
path: dist/*
if-no-files-found: error
upload:
needs: build
if: inputs.tag != '' && github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: write
concurrency:
group: release-binaries-${{ inputs.tag }}
cancel-in-progress: false
steps:
- uses: actions/download-artifact@v4
with:
pattern: systemless-*
merge-multiple: true
path: dist
- name: Attach archives and checksums to the existing release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ inputs.tag }}
run: |
gh release view "$RELEASE_TAG"
gh release upload "$RELEASE_TAG" dist/* --clobber