Files
typikon/.forgejo/workflows/release.yml
T
funkpower 7970f785c8 fix what a 1.0 would have welded into PyPI metadata
An adversarial review of the release, and four of its findings were things
PyPI never lets you replace once uploaded.

  - rust-version said 1.74. clap has needed 1.85 since 4.6, and cargo
    hard-errors rather than warning, so the sdist advertised a floor it could
    not build on. docs/releasing.md sends macOS and Windows users to that
    sdist.
  - The wheel was classified Development Status :: 4 - Beta on a release the
    changelog calls stable, with none of the Python version classifiers PyPI
    indexes on.
  - Six README links were relative. PyPI resolves them against the project
    page, so every one 404'd there, as they do on the live 0.2.0 page now.
  -  in that order packs the wheel into the
    sdist. The tarball on disk was 431KB with an x86-64 binary inside it.

Also: twine upload gains --skip-existing, so a retry after a partial upload
finishes rather than dying on a version that cannot be reused; twine check
gains the renderer that makes it parse the description at all, without which
it returns None and reports PASSED; and the README no longer claims a
twenty-year sweep the test did not run, because 366 * 21 / 4 is five years.
The test now runs the twenty-one it was named for.

The crates.io name belongs to an unrelated project, which the README now says
rather than leaving a reader to find out from cargo.
2026-09-18 13:41:41 -04:00

71 lines
3.1 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
# readme-renderer[md] is what makes `twine check` actually parse the
# long description. Without it the render returns None and the
# check reports PASSED regardless, even with --strict.
/tmp/venv/bin/pip install --quiet 'maturin[zig]' twine 'readme-renderer[md]'
# --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 --strict dist/*
# --skip-existing so a retry after a partial upload finishes the job
# instead of dying on the first file. A version cannot be reused,
# so a half-published release would otherwise burn it.
/tmp/venv/bin/twine upload --non-interactive --skip-existing dist/*