Files
shoes/.github/workflows/build.yml
cfal c3453ef1ab Add ARMv7 release builds and verified artifact packaging (#239)
Add Linux ARMv7 hard-float GNU and static musl binaries so 32-bit ARMv7 systems can use prebuilt Shoes releases. Retain the six existing targets, pin build tooling and cross images, and validate each packaged binary with native or QEMU checks before uploading it. Harden future releases with version and tag validation, SHA-256 checksums, and GitHub build-provenance attestations, and document platform requirements and artifact verification.
2026-10-28 12:29:58 +08:00

224 lines
7.6 KiB
YAML

name: Build Binaries
on:
push:
branches: [ master ]
tags: [ 'v*' ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: binaries-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_TERM_COLOR: always
CARGO_BUILD_JOBS: 1
CROSS_VERSION: 0.2.5
defaults:
run:
shell: bash
jobs:
version:
name: Validate release version
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check manifest, lockfile, and tag
run: |
python3 - <<'PY'
import os
import tomllib
from pathlib import Path
manifest = tomllib.loads(Path("Cargo.toml").read_text())["package"]
packages = tomllib.loads(Path("Cargo.lock").read_text())["package"]
locked = [p["version"] for p in packages if p["name"] == manifest["name"] and "source" not in p]
assert locked == [manifest["version"]], "Manifest and lockfile versions differ"
if os.environ["GITHUB_REF_TYPE"] == "tag":
assert os.environ["GITHUB_REF_NAME"] == f'v{manifest["version"]}', "Tag does not match package version"
PY
- name: Test release smoke checks
run: python3 scripts/test-smoke-release.py
build:
name: Build ${{ matrix.target }}
needs: version
runs-on: ${{ matrix.os }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-24.04
use_cross: false
- target: x86_64-unknown-linux-musl
os: ubuntu-24.04
use_cross: true
- target: aarch64-unknown-linux-gnu
os: ubuntu-24.04
use_cross: true
- target: aarch64-unknown-linux-musl
os: ubuntu-24.04
use_cross: true
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-24.04
use_cross: true
- target: armv7-unknown-linux-musleabihf
os: ubuntu-24.04
use_cross: true
- target: x86_64-apple-darwin
os: macos-15-intel
use_cross: false
- target: aarch64-apple-darwin
os: macos-15
use_cross: false
env:
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec
with:
toolchain: 1.99.1
targets: ${{ matrix.target }}
- name: Install pinned cross binary
if: matrix.use_cross
run: |
curl --fail --location --retry 3 \
"https://github.com/cross-rs/cross/releases/download/v${CROSS_VERSION}/cross-x86_64-unknown-linux-gnu.tar.gz" \
--output "$RUNNER_TEMP/cross.tar.gz"
echo "642375d1bcf3bd88272c32ba90e999f3d983050adf45e66bd2d3887e8e838bad $RUNNER_TEMP/cross.tar.gz" | sha256sum --check --strict
tar -xzf "$RUNNER_TEMP/cross.tar.gz" -C "$HOME/.cargo/bin" cross
- name: Setup cache
uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2
with:
key: release-${{ matrix.target }}
save-if: ${{ github.ref == 'refs/heads/master' }}
- name: Build binary (cross)
if: matrix.use_cross
run: cross build --release --locked --target "$TARGET" --bin shoes
- name: Build binary (native)
if: ${{ !matrix.use_cross }}
run: cargo build --release --locked --target "$TARGET" --bin shoes
- name: Verify lockfile unchanged
run: git diff --exit-code -- Cargo.lock
# Cargo's release profile strips with the target toolchain before packaging.
- name: Package binary and notices
run: |
test -x "target/$TARGET/release/shoes"
file "target/$TARGET/release/shoes"
case "$TARGET" in
*musl*) file "target/$TARGET/release/shoes" | grep -E 'static.*linked' ;;
esac
mkdir -p package artifacts smoke
cp "target/$TARGET/release/shoes" THIRD_PARTY_NOTICES.md package/
cp -R licenses package/
tar -czf "artifacts/shoes-$TARGET.tar.gz" -C package shoes THIRD_PARTY_NOTICES.md licenses
python3 scripts/check-release-notices.py "artifacts/shoes-$TARGET.tar.gz"
tar -xzf "artifacts/shoes-$TARGET.tar.gz" -C smoke
- name: Smoke-test packaged binary (cross/QEMU)
if: matrix.use_cross
timeout-minutes: 5
run: |
image="$(python3 -c 'import sys, tomllib; print(tomllib.load(open("Cross.toml", "rb"))["target"][sys.argv[1]]["image"])' "$TARGET")"
runner=/linux-runner
case "$TARGET" in
*musl*) runner=/qemu-runner ;;
esac
python3 scripts/smoke-release.py docker run --rm \
--volume "$PWD:/work:ro" --workdir /work \
"$image" "$runner" "${TARGET%%-*}" ./smoke/shoes
- name: Smoke-test packaged binary (native)
if: ${{ !matrix.use_cross }}
timeout-minutes: 5
run: python3 scripts/smoke-release.py ./smoke/shoes
- name: Upload verified archive
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: shoes-${{ matrix.target }}
path: artifacts/shoes-${{ matrix.target }}.tar.gz
if-no-files-found: error
release:
name: Create Draft Release
needs: build
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Verify tag still targets this commit and release is unpublished
env:
GH_TOKEN: ${{ github.token }}
run: |
git fetch --no-tags origin "refs/tags/$GITHUB_REF_NAME"
test "$(git rev-parse 'FETCH_HEAD^{commit}')" = "$GITHUB_SHA"
gh api --paginate "repos/$GITHUB_REPOSITORY/releases" \
--jq '.[] | {tag_name, draft}' > "$RUNNER_TEMP/releases.jsonl"
jq --slurp --exit-status --arg tag "$GITHUB_REF_NAME" \
'[.[] | select(.tag_name == $tag)] | all(.draft)' "$RUNNER_TEMP/releases.jsonl"
- name: Download verified archives
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
pattern: shoes-*
path: release-artifacts
merge-multiple: true
- name: Verify notices and checksum final archives
run: |
archives=(release-artifacts/shoes-*.tar.gz)
test "${#archives[@]}" -eq 8
for archive in "${archives[@]}"; do
python3 scripts/check-release-notices.py "$archive"
done
cd release-artifacts
sha256sum shoes-*.tar.gz > SHA256SUMS
sha256sum --check --strict SHA256SUMS
- name: Attest final release artifacts
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
with:
subject-path: release-artifacts/*
- name: Create Draft Release
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3
with:
files: release-artifacts/*
fail_on_unmatched_files: true
draft: true
generate_release_notes: true