mirror of
https://git.pumpin.online/funkpower/typikon.git
synced 2026-10-22 18:57:13 +00:00
Every run died at the first step: "node: executable file not found in $PATH". actions/checkout is a JavaScript action, and the runner executes it with node *inside the job container* — which here is a Rust image that has never carried one. actions/cache and actions/upload-artifact are the same. The usual answer is an image carrying node as well, which for a Rust project means installing the toolchain on every run. Cloning by hand is four lines and keeps the lean image, and the repository is public so it needs no credential. release.yml collapses to a single job for the same reason. Build and publish were split across four, which meant handing wheels between them through upload-artifact — another JS action. Everything a release needs fits in one job, so nothing has to be handed over at all.
65 lines
2.6 KiB
YAML
65 lines
2.6 KiB
YAML
name: release
|
|
|
|
# Tag-driven. Cargo.toml carries the version; the tag only points at it, and the
|
|
# first step refuses to go on if the two disagree — a release tagged v0.3.0 that
|
|
# publishes 0.2.0 cannot be undone, because PyPI will not let a version be reused
|
|
# even after it is yanked.
|
|
on:
|
|
push:
|
|
tags: ['v*']
|
|
|
|
# One job on purpose. Splitting build from publish would mean passing wheels
|
|
# between jobs through actions/upload-artifact, which is a JavaScript action: the
|
|
# runner executes it with `node` inside this container, and a Rust image has none.
|
|
# Everything a release needs fits in one job, so nothing has to be handed over.
|
|
jobs:
|
|
publish:
|
|
runs-on: docker
|
|
container:
|
|
image: rust:1.98-bookworm
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
git init -q .
|
|
git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
|
|
git fetch -q --depth 1 origin "$GITHUB_SHA"
|
|
git checkout -q FETCH_HEAD
|
|
|
|
- name: Tag must match Cargo.toml
|
|
run: |
|
|
tag="${GITHUB_REF_NAME#v}"
|
|
crate=$(grep -m1 '^version = ' Cargo.toml | cut -d'"' -f2)
|
|
echo "tag=$tag crate=$crate"
|
|
[ "$tag" = "$crate" ] || { echo "::error::tag $tag != Cargo.toml $crate"; exit 1; }
|
|
|
|
- name: Tests
|
|
run: cargo test
|
|
|
|
- name: Build wheels and sdist
|
|
run: |
|
|
apt-get update && apt-get install -y --no-install-recommends python3 python3-venv
|
|
python3 -m venv /tmp/venv
|
|
/tmp/venv/bin/pip install --quiet 'maturin[zig]' twine
|
|
# --zig cross-compiles against an older glibc than this image ships, so
|
|
# the wheel is not silently pinned to bookworm's.
|
|
for target in x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do
|
|
rustup target add "$target"
|
|
/tmp/venv/bin/maturin build --release --zig --target "$target" --out dist
|
|
done
|
|
/tmp/venv/bin/maturin sdist --out dist
|
|
ls -l dist
|
|
|
|
- name: Publish
|
|
env:
|
|
# A project-scoped token, not the account-wide one. PyPI's trusted
|
|
# publishing is OIDC and supports GitHub, GitLab, Google Cloud Build and
|
|
# ActiveState — not Forgejo — so this forge has to hold a long-lived
|
|
# credential. Scoping it to this one project is the whole of the
|
|
# mitigation available: if the forge is compromised, what is lost is the
|
|
# ability to publish typikon rather than every package the account owns.
|
|
TWINE_USERNAME: __token__
|
|
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
|
run: |
|
|
/tmp/venv/bin/twine check dist/*
|
|
/tmp/venv/bin/twine upload --non-interactive dist/*
|