Add SourceHut release artifact pipeline
ober
b87e4cbb27abbcd349e80bd0d638e48056fe946b
new file mode 100644 --- /dev/null +++ b/.builds/release-freebsd-amd64.yml @@ -0,0 +1,42 @@ +image: freebsd/14.x +arch: amd64 +packages: +- bash +- ca_root_nss +- curl +- git +- gmake +- hut +oauth: git.sr.ht/OBJECTS:RW +submitter: + git.sr.ht: + allow-refs: + - refs/tags/v* +environment: + release_target: freebsd-amd64 + sourcehut_repo: ~lisp/jerboa +sources: +- https://git.sr.ht/~lisp/jerboa +tasks: +- build: | + cd jerboa + case "${GIT_REF:-}" in + refs/tags/*) version=${GIT_REF#refs/tags/} ;; + *) version=$(git describe --tags --always) ;; + esac + gmake jerboa + gmake release-artifact RELEASE_VERSION="$version" RELEASE_TARGET="$release_target" +- upload-if-tag: | + cd jerboa + case "${GIT_REF:-}" in + refs/tags/*) + tag=${GIT_REF#refs/tags/} + gmake release-upload RELEASE_VERSION="$tag" RELEASE_REPO="$sourcehut_repo" + ;; + *) + echo "not a tag build; skipping SourceHut git artifact upload" + ;; + esac +artifacts: +- jerboa/dist/release/jerboa-freebsd-amd64.tar.gz +- jerboa/dist/release/jerboa-freebsd-amd64.tar.gz.sha256 new file mode 100644 --- /dev/null +++ b/.builds/release-linux-amd64.yml @@ -0,0 +1,44 @@ +image: debian/stable +arch: amd64 +packages: +- bash +- build-essential +- ca-certificates +- curl +- file +- git +- hut +- tar +oauth: git.sr.ht/OBJECTS:RW +submitter: + git.sr.ht: + allow-refs: + - refs/tags/v* +environment: + release_target: linux-amd64 + sourcehut_repo: ~lisp/jerboa +sources: +- https://git.sr.ht/~lisp/jerboa +tasks: +- build: | + cd jerboa + case "${GIT_REF:-}" in + refs/tags/*) version=${GIT_REF#refs/tags/} ;; + *) version=$(git describe --tags --always) ;; + esac + make jerboa + make release-artifact RELEASE_VERSION="$version" RELEASE_TARGET="$release_target" +- upload-if-tag: | + cd jerboa + case "${GIT_REF:-}" in + refs/tags/*) + tag=${GIT_REF#refs/tags/} + make release-upload RELEASE_VERSION="$tag" RELEASE_REPO="$sourcehut_repo" + ;; + *) + echo "not a tag build; skipping SourceHut git artifact upload" + ;; + esac +artifacts: +- jerboa/dist/release/jerboa-linux-amd64.tar.gz +- jerboa/dist/release/jerboa-linux-amd64.tar.gz.sha256 new file mode 100644 --- /dev/null +++ b/.builds/release-linux-arm64.yml @@ -0,0 +1,44 @@ +image: debian/stable +arch: arm64 +packages: +- bash +- build-essential +- ca-certificates +- curl +- file +- git +- hut +- tar +oauth: git.sr.ht/OBJECTS:RW +submitter: + git.sr.ht: + allow-refs: + - refs/tags/v* +environment: + release_target: linux-arm64 + sourcehut_repo: ~lisp/jerboa +sources: +- https://git.sr.ht/~lisp/jerboa +tasks: +- build: | + cd jerboa + case "${GIT_REF:-}" in + refs/tags/*) version=${GIT_REF#refs/tags/} ;; + *) version=$(git describe --tags --always) ;; + esac + make jerboa + make release-artifact RELEASE_VERSION="$version" RELEASE_TARGET="$release_target" +- upload-if-tag: | + cd jerboa + case "${GIT_REF:-}" in + refs/tags/*) + tag=${GIT_REF#refs/tags/} + make release-upload RELEASE_VERSION="$tag" RELEASE_REPO="$sourcehut_repo" + ;; + *) + echo "not a tag build; skipping SourceHut git artifact upload" + ;; + esac +artifacts: +- jerboa/dist/release/jerboa-linux-arm64.tar.gz +- jerboa/dist/release/jerboa-linux-arm64.tar.gz.sha256 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ help: @echo " Args: CHEZ_TARGET_MACHINE=<mt> CROSS_CC=<cross-cc>" @echo " binary-cross Build a jerboa-bin for a foreign target (same args as chez-cross)" @echo " jerboa Build the busybox-style multicall binary: dist/jerboa" - @echo " + symlinks dist/{jmcp,jlsp,jerbuild} (one image, all four modes)" + @echo " + symlinks dist/{jmcp,jlsp,jerbuild,jpkg} (one image, all five modes)" @echo " system-check Validate .jerboa-system manifest" @echo " system-strict Validate manifest and require declared outputs" @echo " system-report Print unification health/provenance report" @@ -80,8 +80,11 @@ help: @echo " install-policy-check Enforce no install-time code policy" @echo " tcb-report Emit machine-readable TCB inventory" @echo " unification-check Run manifest/report/provenance/diff/audit checks" - @echo " jerboa-portable Build the multicall binary for host + linux-amd64 + freebsd-amd64" - @echo " (cross targets need: make chez-cross CHEZ_TARGET_MACHINE=ta6le|ta6fb ...)" + @echo " jerboa-portable Build multicall release targets: linux-amd64, linux-arm64, freebsd-amd64" + @echo " (cross targets need: make chez-cross CHEZ_TARGET_MACHINE=ta6le|tarm64le|ta6fb ...)" + @echo " release-artifact Package one built target: RELEASE_VERSION=v0.1.0 RELEASE_TARGET=linux-amd64" + @echo " release-artifacts Build/package all release targets" + @echo " release-upload Upload dist/release artifacts to a SourceHut git tag via hut" @echo " native-cross Cross-build Rust native lib for a target" @echo " Args: CHEZ_TARGET_MACHINE=<mt> CROSS_CC=<cc> [CROSS_NATIVE_FEATURES=tls,...]" @echo " typecheck Typecheck Typed Jerboa sources" @@ -104,11 +107,12 @@ help: @echo "" @echo "Cross-platform binaries (one static jerboa + jerbuild/jmcp/jlsp symlinks):" @echo " jerboa-linux-amd64 Cross-build for Linux x86_64 (musl) -> dist/linux-amd64/" + @echo " jerboa-linux-arm64 Cross-build for Linux arm64 (musl) -> dist/linux-arm64/" @echo " jerboa-freebsd-amd64 Cross-build for FreeBSD amd64 -> dist/freebsd-amd64/" @echo " jerboa-cross Generic: CHEZ_TARGET_MACHINE=<mt> CROSS_CC=<cc>" @echo " (e.g. tarm64le -> dist/linux-arm64/, ta6fb -> freebsd-amd64)" @echo " jerbuild-cross Cross-build standalone jerbuild-<mt> (same args)" - @echo " jerboa-portable Host + linux-amd64 + freebsd-amd64 in one shot" + @echo " jerboa-portable linux-amd64 + linux-arm64 + freebsd-amd64 in one shot" @echo " NOTE one-time per target: build the cross Chez first, e.g." @echo " make chez-cross CHEZ_TARGET_MACHINE=ta6le CROSS_CC=x86_64-linux-musl-gcc # Linux amd64" @echo "" @@ -379,7 +383,7 @@ install: # # Generic form (builds the cross Chez on demand via chez-cross): # make jerboa-cross CHEZ_TARGET_MACHINE=ta6le CROSS_CC=x86_64-linux-musl-gcc -.PHONY: jerboa-cross jerboa-linux-amd64 jerboa-freebsd-amd64 jerboa-portable +.PHONY: jerboa-cross jerboa-linux-amd64 jerboa-linux-arm64 jerboa-freebsd-amd64 jerboa-portable jerboa-cross: chez build mcp-check lsp-gen chez-cross @test -n "$(CHEZ_TARGET_MACHINE)" \ || { echo "ERROR: set CHEZ_TARGET_MACHINE=<machine-type>" >&2; exit 1; } @@ -405,6 +409,19 @@ jerboa-linux-amd64: chez build mcp-check lsp-gen CC=x86_64-linux-musl-gcc \ $(SCHEME) --script support/build-jerboa-multicall.ss +jerboa-linux-arm64: chez build mcp-check lsp-gen + @command -v aarch64-linux-musl-gcc >/dev/null 2>&1 || { echo "ERROR: aarch64-linux-musl-gcc not on PATH" >&2; exit 1; } + @if test ! -d "$(JERBOA_HOME)/.chez-cross-tarm64le" || test ! -f "$(CHEZ_BUILD_DIR)/xc-tarm64le/s/xpatch"; then \ + echo "=== cross Chez (tarm64le) missing — building it first ==="; \ + $(MAKE) chez-cross CHEZ_TARGET_MACHINE=tarm64le CROSS_CC=aarch64-linux-musl-gcc; \ + fi + @echo "=== Cross-building jerboa multicall: linux-arm64 (tarm64le) ===" + @JERBOA_HOME=$(JERBOA_HOME) TARGET_MACHINE=tarm64le \ + JERBOA_CROSS_PREFIX=$(JERBOA_HOME)/.chez-cross-tarm64le \ + JERBOA_XPATCH=$(CHEZ_BUILD_DIR)/xc-tarm64le/s/xpatch \ + CC=aarch64-linux-musl-gcc \ + $(SCHEME) --script support/build-jerboa-multicall.ss + jerboa-freebsd-amd64: chez build mcp-check lsp-gen @test -x "$(JERBOA_HOME)/support/cross-cc-freebsd-amd64" || { echo "ERROR: support/cross-cc-freebsd-amd64 not found/executable" >&2; exit 1; } @if test ! -d "$(JERBOA_HOME)/.chez-cross-ta6fb" || test ! -f "$(CHEZ_BUILD_DIR)/xc-ta6fb/s/xpatch"; then \ @@ -418,8 +435,32 @@ jerboa-freebsd-amd64: chez build mcp-check lsp-gen CC="$(JERBOA_HOME)/support/cross-cc-freebsd-amd64" \ $(SCHEME) --script support/build-jerboa-multicall.ss -# Host + the two cross targets. -jerboa-portable: jerboa jerboa-linux-amd64 jerboa-freebsd-amd64 +# Release targets. +jerboa-portable: jerboa-linux-amd64 jerboa-linux-arm64 jerboa-freebsd-amd64 + +# ── Release packaging for SourceHut tag artifacts ─────────────────────────── +# Native SourceHut jobs call `release-artifact` after `make jerboa`. Local +# maintainers can call `release-artifacts` to cross-build and package all +# supported bootstrap toolchains in one pass. +RELEASE_VERSION ?= $(shell git describe --tags --exact-match 2>/dev/null || git describe --tags --always --dirty 2>/dev/null || echo dev) +RELEASE_TARGET ?= $(shell case "$$(uname -s)-$$(uname -m)" in Linux-x86_64) echo linux-amd64 ;; Linux-aarch64|Linux-arm64) echo linux-arm64 ;; FreeBSD-amd64|FreeBSD-x86_64) echo freebsd-amd64 ;; *) echo unknown ;; esac) +RELEASE_TARGETS ?= linux-amd64 linux-arm64 freebsd-amd64 +RELEASE_DIR ?= dist/release +RELEASE_REPO ?= ~lisp/jerboa + +.PHONY: release-artifact release-artifacts release-upload +release-artifact: + @support/package-jerboa-release.sh "$(RELEASE_VERSION)" "$(RELEASE_TARGET)" "$(RELEASE_DIR)" + +release-artifacts: jerboa-portable + @for target in $(RELEASE_TARGETS); do \ + support/package-jerboa-release.sh "$(RELEASE_VERSION)" "$$target" "$(RELEASE_DIR)"; \ + done + +release-upload: + @support/sourcehut-release-upload.sh "$(RELEASE_VERSION)" "$(RELEASE_REPO)" \ + "$(RELEASE_DIR)"/jerboa-"$(RELEASE_VERSION)"-*.tar.gz \ + "$(RELEASE_DIR)"/jerboa-"$(RELEASE_VERSION)"-*.tar.gz.sha256 # Cross-compiled jerbuild. Drives support/build-jerbuild.sh with TARGET_* env, # producing ./jerbuild-<machine> alongside the host ./jerbuild. new file mode 100644 --- /dev/null +++ b/docs/release-artifacts.md @@ -0,0 +1,90 @@ +# Jerboa Release Artifacts + +Jerboa publishes project-local toolchain tarballs for package repos that should +build without cloning and compiling this repository first. + +Each release target produces: + +```text +jerboa-vX.Y.Z-linux-amd64.tar.gz +jerboa-vX.Y.Z-linux-arm64.tar.gz +jerboa-vX.Y.Z-freebsd-amd64.tar.gz +``` + +The archive layout is: + +```text +jerboa-vX.Y.Z-<target>/bin/jerboa +jerboa-vX.Y.Z-<target>/bin/jerbuild -> jerboa +jerboa-vX.Y.Z-<target>/bin/jpkg -> jerboa +jerboa-vX.Y.Z-<target>/bin/jmcp -> jerboa +jerboa-vX.Y.Z-<target>/bin/jlsp -> jerboa +``` + +## Build Locally + +```sh +make release-artifacts RELEASE_VERSION=v0.1.0 +``` + +This cross-builds and packages: + +```text +linux-amd64 +linux-arm64 +freebsd-amd64 +``` + +The output lands in `dist/release/`. + +## SourceHut CI + +The `.builds/release-*.yml` manifests build each release target natively on +SourceHut: + +```text +.builds/release-linux-amd64.yml +.builds/release-linux-arm64.yml +.builds/release-freebsd-amd64.yml +``` + +They are constrained to `refs/tags/v*`, so ordinary branch pushes do not run +the release builders. The build job uses SourceHut's `GIT_REF` environment +variable to name the artifact, because the checkout itself is pinned by commit +hash. Every successful job exposes short-lived build artifacts for inspection. +Tag builds also run: + +```sh +make release-upload RELEASE_VERSION="$tag" +``` + +That attaches the versioned tarball and `.sha256` file to the SourceHut git tag +with `hut git artifact upload --rev "$tag"`. SourceHut job artifacts are pruned; +git tag artifacts are the stable download channel. + +## Downstream Bootstrap + +Package repos can vendor `support/ensure-jerboa.sh` and use: + +```make +JERBOA_VERSION ?= v0.1.0 +JERBOA_BINDIR := .jerboa/bin + +.PHONY: install +install: $(JERBOA_BINDIR)/jerboa + PATH="$(CURDIR)/$(JERBOA_BINDIR):$$PATH" "$(JERBOA_BINDIR)/jpkg" install + +$(JERBOA_BINDIR)/jerboa: + sh support/ensure-jerboa.sh "$(JERBOA_VERSION)" "$(JERBOA_BINDIR)" +``` + +The bootstrap script downloads from: + +```text +https://git.sr.ht/~lisp/jerboa/refs/download/<tag>/<artifact> +``` + +Override `JERBOA_RELEASE_REPO`, `JERBOA_RELEASE_ORIGIN`, or +`JERBOA_RELEASE_BASE` for mirrors or forks. Override +`JERBOA_RELEASE_TARGET` only for tests or unusual hosts where `uname` detection +is not enough. new file mode 100755 --- /dev/null +++ b/support/ensure-jerboa.sh @@ -0,0 +1,119 @@ +#!/bin/sh +# Bootstrap a project-local Jerboa toolchain from SourceHut release artifacts. +# +# Usage: +# support/ensure-jerboa.sh v0.1.0 .jerboa/bin +# +# Override the artifact location with: +# JERBOA_RELEASE_BASE=https://example.org/releases/v0.1.0 +# or the SourceHut repo with: +# JERBOA_RELEASE_REPO=~lisp/jerboa +# For testing or unusual hosts, override target detection with: +# JERBOA_RELEASE_TARGET=linux-amd64 + +set -eu + +usage() { + echo "Usage: $0 VERSION [BINDIR]" >&2 +} + +[ "${1:-}" ] || { usage; exit 2; } + +version=$1 +bindir=${2:-.jerboa/bin} +repo=${JERBOA_RELEASE_REPO:-~lisp/jerboa} +origin=${JERBOA_RELEASE_ORIGIN:-https://git.sr.ht} + +if [ "${JERBOA_RELEASE_TARGET:-}" ]; then + target=$JERBOA_RELEASE_TARGET +else + os=$(uname -s) + arch=$(uname -m) + case "$os-$arch" in + Linux-x86_64|Linux-amd64) target=linux-amd64 ;; + Linux-aarch64|Linux-arm64) target=linux-arm64 ;; + FreeBSD-amd64|FreeBSD-x86_64) target=freebsd-amd64 ;; + *) + echo "ERROR: unsupported platform: $os $arch" >&2 + exit 1 + ;; + esac +fi + +case "$target" in + linux-amd64|linux-arm64|freebsd-amd64) ;; + *) + echo "ERROR: unsupported release target: $target" >&2 + exit 1 + ;; +esac + +file="jerboa-${version}-${target}.tar.gz" +base=${JERBOA_RELEASE_BASE:-${origin}/${repo}/refs/download/${version}} +url="${base%/}/${file}" +sum_url="${url}.sha256" + +download() { + src=$1 + dst=$2 + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$src" -o "$dst" + elif command -v fetch >/dev/null 2>&1; then + fetch -q -o "$dst" "$src" + elif command -v wget >/dev/null 2>&1; then + wget -q -O "$dst" "$src" + else + echo "ERROR: need curl, fetch, or wget to download Jerboa" >&2 + exit 1 + fi +} + +sha256_file() { + path=$1 + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$path" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$path" | awk '{print $1}' + elif command -v sha256 >/dev/null 2>&1; then + sha256 -q "$path" + else + echo "ERROR: need sha256sum, shasum, or sha256" >&2 + exit 1 + fi +} + +tmp=$(mktemp -d "${TMPDIR:-/tmp}/jerboa-bootstrap.XXXXXX") +trap 'rm -rf "$tmp"' EXIT HUP INT TERM + +archive="$tmp/$file" +sum_file="$tmp/$file.sha256" + +echo "fetching $url" +download "$url" "$archive" +download "$sum_url" "$sum_file" + +expected=$(awk '{print $1; exit}' "$sum_file") +actual=$(sha256_file "$archive") +if [ "$expected" != "$actual" ]; then + echo "ERROR: checksum mismatch for $file" >&2 + echo "expected: $expected" >&2 + echo "actual: $actual" >&2 + exit 1 +fi + +mkdir -p "$tmp/extract" +tar -xzf "$archive" -C "$tmp/extract" +root="$tmp/extract/jerboa-${version}-${target}" +[ -x "$root/bin/jerboa" ] || { + echo "ERROR: archive did not contain bin/jerboa" >&2 + exit 1 +} + +mkdir -p "$bindir" +cp "$root/bin/jerboa" "$bindir/jerboa" +chmod 0755 "$bindir/jerboa" +for link in jmcp jlsp jerbuild jpkg; do + ln -sf jerboa "$bindir/$link" +done + +echo "installed $bindir/{jerboa,jmcp,jlsp,jerbuild,jpkg}" new file mode 100755 --- /dev/null +++ b/support/package-jerboa-release.sh @@ -0,0 +1,84 @@ +#!/bin/sh +# Package one Jerboa multicall binary as a SourceHut release artifact. + +set -eu + +usage() { + echo "Usage: $0 VERSION TARGET [RELEASE_DIR]" >&2 + echo " TARGET: linux-amd64 | linux-arm64 | freebsd-amd64" >&2 +} + +[ "${1:-}" ] || { usage; exit 2; } +[ "${2:-}" ] || { usage; exit 2; } + +version=$1 +target=$2 +release_dir=${3:-dist/release} +name="jerboa-${version}-${target}" +versioned="${release_dir}/${name}.tar.gz" +alias_tar="${release_dir}/jerboa-${target}.tar.gz" + +case "$target" in + linux-amd64|linux-arm64|freebsd-amd64) ;; + *) + echo "ERROR: unsupported release target: $target" >&2 + exit 1 + ;; +esac + +if [ -x "dist/${target}/jerboa" ]; then + source_bin="dist/${target}/jerboa" +elif [ -x "dist/jerboa" ]; then + source_bin="dist/jerboa" +else + echo "ERROR: no built jerboa binary found for ${target}" >&2 + echo " expected dist/${target}/jerboa or dist/jerboa" >&2 + exit 1 +fi + +file_desc=$(file "$source_bin") +case "$target:$file_desc" in + linux-amd64:*"ELF 64-bit LSB executable, x86-64"*) ;; + linux-arm64:*"ELF 64-bit LSB executable, ARM aarch64"*) ;; + freebsd-amd64:*"ELF 64-bit LSB executable, x86-64"*"FreeBSD"*) ;; + *) + echo "ERROR: $source_bin does not look like target $target" >&2 + echo " file: $file_desc" >&2 + exit 1 + ;; +esac + +mkdir -p "$release_dir" +tmp=$(mktemp -d "${TMPDIR:-/tmp}/jerboa-release.XXXXXX") +trap 'rm -rf "$tmp"' EXIT HUP INT TERM + +mkdir -p "$tmp/$name/bin" +cp "$source_bin" "$tmp/$name/bin/jerboa" +chmod 0755 "$tmp/$name/bin/jerboa" +for link in jmcp jlsp jerbuild jpkg; do + ln -sf jerboa "$tmp/$name/bin/$link" +done + +cwd=$(pwd) +(cd "$tmp" && tar -czf "$cwd/$versioned" "$name") + +if command -v sha256sum >/dev/null 2>&1; then + sum=$(sha256sum "$versioned" | awk '{print $1}') +elif command -v shasum >/dev/null 2>&1; then + sum=$(shasum -a 256 "$versioned" | awk '{print $1}') +elif command -v sha256 >/dev/null 2>&1; then + sum=$(sha256 -q "$versioned") +else + echo "ERROR: need sha256sum, shasum, or sha256" >&2 + exit 1 +fi + +base=$(basename "$versioned") +printf '%s %s\n' "$sum" "$base" > "${versioned}.sha256" + +# Static aliases make builds.sr.ht job artifacts easy to list literally. +cp "$versioned" "$alias_tar" +printf '%s %s\n' "$sum" "$(basename "$alias_tar")" > "${alias_tar}.sha256" + +echo "release artifact: $versioned" +echo "sha256: $sum" new file mode 100755 --- /dev/null +++ b/support/sourcehut-release-upload.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# Upload versioned release artifacts to a SourceHut git tag. + +set -eu + +usage() { + echo "Usage: $0 VERSION REPO FILE..." >&2 + echo " REPO example: ~lisp/jerboa" >&2 +} + +[ "${1:-}" ] || { usage; exit 2; } +[ "${2:-}" ] || { usage; exit 2; } + +version=$1 +repo=$2 +shift 2 + +[ "$#" -gt 0 ] || { usage; exit 2; } + +if git rev-parse --git-dir >/dev/null 2>&1 && + ! git rev-parse -q --verify "refs/tags/${version}" >/dev/null; then + echo "warning: ${version} is not a local git tag; relying on SourceHut remote tag" >&2 +fi + +if ! command -v hut >/dev/null 2>&1; then + echo "ERROR: hut is required to upload SourceHut git artifacts" >&2 + exit 1 +fi + +files= +for file in "$@"; do + case "$file" in + *'*'*) continue ;; + esac + if [ -f "$file" ]; then + files="${files}${files:+ +}$file" + fi +done + +if [ -z "$files" ]; then + echo "ERROR: no release files matched" >&2 + exit 1 +fi + +printf '%s\n' "$files" | while IFS= read -r file; do + echo "uploading $file to $repo tag $version" + hut git -r "$repo" artifact upload "$file" --rev "$version" +done