mirror of
https://git.pumpin.online/funkpower/typikon.git
synced 2026-10-22 18:57:13 +00:00
build it on tags, not on trust
Tag-driven releases. main is always releasable, a vX.Y.Z tag on it builds the sdist and the manylinux wheels and publishes them; Cargo.toml carries the version and the first job refuses to go on if the tag disagrees with it, because PyPI will not let a version be reused even after it is yanked. CI fires on push and on nothing else. This forge is public and takes pull requests from strangers, and a runner executes whatever the workflow checks out, so a pull_request trigger would run unread code from a stranger on the runner host. Push-only means the runner only ever runs what someone with push rights put there. A pull request therefore gets no automatic check: read it, push the branch, let it run then. No cargo fmt gate. The tables here are aligned by hand and rustfmt wants one entry per line, which loses the shape that makes MONTHS and JURISDICTIONS readable — a gate that fights the house style is a gate that gets ignored. Clippy stays, and passes. The Python job builds the bindings against the CLI it just built and runs them against each other, because they are one engine and the only way that stays true is if something checks. docs/releasing.md covers what does not exist yet: Actions is off on this forge on purpose, there is no runner, and PyPI's trusted publishing does not speak to Forgejo — so a long-lived token has to live here, project-scoped rather than account-wide so a compromised forge costs this package rather than all of them.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
name: release
|
||||
|
||||
# Tag-driven. Cargo.toml carries the version; the tag only points at it, and the
|
||||
# first job refuses to go on if the two disagree — a release tagged v0.3.0 that
|
||||
# publishes 0.2.0 is not recoverable, because PyPI will not let a version be
|
||||
# reused even after it is yanked.
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: rust:1.98-bookworm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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
|
||||
|
||||
build:
|
||||
needs: verify
|
||||
runs-on: docker
|
||||
container:
|
||||
image: rust:1.98-bookworm
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheel
|
||||
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]
|
||||
# --zig cross-compiles against an older glibc than the build image
|
||||
# ships, so the wheel is not silently pinned to bookworm's.
|
||||
/tmp/venv/bin/maturin build --release --zig \
|
||||
--target ${{ matrix.target }} --out dist
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels-${{ matrix.target }}
|
||||
path: dist
|
||||
|
||||
sdist:
|
||||
needs: verify
|
||||
runs-on: docker
|
||||
container:
|
||||
image: rust:1.98-bookworm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build 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
|
||||
/tmp/venv/bin/maturin sdist --out dist
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: sdist
|
||||
path: dist
|
||||
|
||||
publish:
|
||||
needs: [build, sdist]
|
||||
runs-on: docker
|
||||
container:
|
||||
image: python:3.12-bookworm
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: dist
|
||||
- name: Collect
|
||||
run: |
|
||||
mkdir -p upload
|
||||
find dist -name '*.whl' -o -name '*.tar.gz' | xargs -I{} cp {} upload/
|
||||
ls -l upload
|
||||
- name: Publish
|
||||
env:
|
||||
# A PROJECT-SCOPED token, not an 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, the blast radius is
|
||||
# releases of typikon rather than every package the account owns.
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
||||
run: |
|
||||
pip install --quiet twine
|
||||
twine check upload/*
|
||||
twine upload --non-interactive upload/*
|
||||
Reference in New Issue
Block a user