Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,40 @@ cd "$ROOT"
die() { echo "error: $*" >&2; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || die "$1 is required"; }

# An interpreter that can `import tomllib`, which is stdlib only from 3.11.
#
# `python3` carries no version guarantee, and this package's own requires-python
# is >=3.10 — so hardcoding it meant the release script could not run on the
# oldest Python the package claims to support. No workflow runs this script; only
# a human cutting a release does, which is why it went unnoticed for so long. The
# symptom was a bare ModuleNotFoundError, which reads as a broken checkout rather
# than a too-old interpreter.
#
# `uv` is the fallback because `prepare` below already shells out to `uv lock`
# when a uv.lock is present, so a machine able to cut a release here already has
# it. (CI installs with pip, so this is about the local release path only.)
resolve_python() {
if command -v python3 >/dev/null 2>&1 && python3 -c "import tomllib" >/dev/null 2>&1; then
echo python3
elif command -v uv >/dev/null 2>&1; then
# `>=3.11` not `3.12`: an exact request downloads a managed interpreter when
# the machine's uv-visible one is 3.11 or 3.13, and fails outright under
# UV_PYTHON_DOWNLOADS=never. It reads like a redirect but is not one —
# $PY_BIN is expanded unquoted for word splitting, and bash does not rescan
# expansion results for redirection operators, so uv receives it literally.
echo "uv run --no-project --python >=3.11 python"
else
die "need python3 >= 3.11 (for tomllib) or uv; python3 is $(command -v python3 >/dev/null 2>&1 && python3 -V 2>&1 || echo absent)"
fi
}

# Deferred, not resolved at load: the commands that never touch Python must keep
# working without a usable interpreter — otherwise the message telling you which
# interpreter you need is itself gated on having it, and `--help` breaks on
# exactly the machine this resolution exists for. Empty default satisfies `set -u`
# if a helper is ever reached by another path.
PY_BIN=""

usage() {
cat <<'EOF'
Usage:
Expand All @@ -24,15 +58,15 @@ EOF
}

get_version() {
python3 - <<'PY'
$PY_BIN - <<'PY'
import tomllib
from pathlib import Path
print(tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"])
PY
}

get_pkg_name() {
python3 - <<'PY'
$PY_BIN - <<'PY'
import tomllib
from pathlib import Path
print(tomllib.loads(Path("pyproject.toml").read_text())["project"]["name"])
Expand All @@ -41,7 +75,7 @@ PY

set_version() {
local ver="$1"
python3 - "$ver" <<'PY'
$PY_BIN - "$ver" <<'PY'
import re, sys
from pathlib import Path
ver = sys.argv[1]
Expand All @@ -56,7 +90,7 @@ PY

bump_version() {
local kind="$1" current="$2"
python3 - "$kind" "$current" <<'PY'
$PY_BIN - "$kind" "$current" <<'PY'
import re, sys
kind, current = sys.argv[1], sys.argv[2]
match = re.match(r"^(\d+)\.(\d+)\.(\d+)(.*)$", current)
Expand Down Expand Up @@ -95,14 +129,14 @@ update_changelog() {
local ver="$1"
local date
date="$(date +%Y-%m-%d)"
python3 scripts/update_changelog.py "$ver" "$date"
$PY_BIN scripts/update_changelog.py "$ver" "$date"
}

cmd_prepare() {
local bump="${1:-}"
[[ -n "$bump" ]] || { usage; die "missing bump kind or explicit version"; }
need gh
need python3
PY_BIN="$(resolve_python)"
ensure_clean

local current new base branch pkg
Expand Down Expand Up @@ -151,7 +185,7 @@ After merge, run \`./scripts/release.sh publish\` from a clean \`${base}\` check

cmd_publish() {
need gh
need python3
PY_BIN="$(resolve_python)"
ensure_clean

local base ver tag
Expand All @@ -166,7 +200,7 @@ cmd_publish() {

git rev-parse "$tag" >/dev/null 2>&1 && die "tag $tag already exists"
[[ -f CHANGELOG.md ]] || die "CHANGELOG.md is required"
python3 - "$ver" <<'PY'
$PY_BIN - "$ver" <<'PY'
import re, sys
from pathlib import Path
ver = sys.argv[1]
Expand Down