Skip to content

Commit e559532

Browse files
authored
fix(release): resolve an interpreter that has tomllib (#58)
* fix(release): resolve an interpreter that has tomllib `./scripts/release.sh prepare minor` fails outright on macOS: ModuleNotFoundError: No module named 'tomllib' tomllib is stdlib only from 3.11, and `python3` on macOS is 3.9. Every helper in the script -- get_version, get_pkg_name, set_version, bump_version, the changelog update, and the publish-time changelog check -- hardcoded `python3`, so releasing this package was impossible on such a machine. The error names a missing module, which reads as a broken checkout rather than a too-old interpreter. Resolved once at load time instead of per call site: `python3` when it can actually import tomllib, else `uv run`, else a message that says what is needed and what was found. uv is the right fallback because this repo already builds and tests through it, so a machine that can run the suite can run the release. The two `need python3` guards are dropped as redundant -- resolve_python has already exited with a better message by then. Verified under bash, which is how the script runs: PY_BIN=uv run --no-project --python 3.12 python version=0.10.0 pkg=hotdata-framework minor=0.11.0 patch=0.10.1 major=1.0.0 and with no usable interpreter on PATH: error: need python3 >= 3.11 (for tomllib) or uv; python3 is Python 3.9.6 No behaviour change to what a release does -- only which interpreter runs it. Note the same hardcoded `python3 - <<PY` + tomllib pattern is in sdk-python's and hotdata-marimo's copies of this script, so both are latently broken the same way on a pre-3.11 `python3`. Not touched here. * fix(release): resolve lazily, and do not swallow the not-found message Resolving at load time made the interpreter a hard requirement for every invocation, including the ones that never run Python. On a machine with python3 3.9 and no uv, `--help` died with the interpreter error instead of printing usage -- so the message telling you which interpreter you need was itself gated on having it, on exactly the machine the resolution exists for. Now resolved as the first line of cmd_prepare and cmd_publish, with an empty default for set -u. And `$(python3 -V 2>&1 || echo absent)` captured the shell's own "command not found" into the message before appending "absent", so the no-python3-at-all case printed both. Guarded on command -v. Verified: --help prints usage with PATH=/usr/bin:/bin; prepare/publish still refuse with `python3 is Python 3.9.6`; and with python3 entirely absent the message is `python3 is absent`.
1 parent d1d6a1c commit e559532

1 file changed

Lines changed: 33 additions & 8 deletions

File tree

‎scripts/release.sh‎

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,31 @@ cd "$ROOT"
77
die() { echo "error: $*" >&2; exit 1; }
88
need() { command -v "$1" >/dev/null 2>&1 || die "$1 is required"; }
99

10+
# An interpreter that can `import tomllib` — stdlib only from 3.11, while
11+
# `python3` on macOS is still 3.9. Hardcoding `python3` made every step below
12+
# fail with a bare ModuleNotFoundError, which reads as a broken checkout rather
13+
# than a too-old interpreter, and it failed at the FIRST step so nothing was
14+
# half-done. Resolved once here rather than per call site.
15+
#
16+
# `uv` is the fallback because this repo already builds and tests through it, so
17+
# a machine that can run the suite can run the release.
18+
resolve_python() {
19+
if command -v python3 >/dev/null 2>&1 && python3 -c "import tomllib" >/dev/null 2>&1; then
20+
echo python3
21+
elif command -v uv >/dev/null 2>&1; then
22+
echo "uv run --no-project --python 3.12 python"
23+
else
24+
die "need python3 >= 3.11 (for tomllib) or uv; python3 is $(command -v python3 >/dev/null 2>&1 && python3 -V 2>&1 || echo absent)"
25+
fi
26+
}
27+
28+
# Deferred, not resolved at load: the commands that never touch Python must keep
29+
# working without a usable interpreter — otherwise the message telling you which
30+
# interpreter you need is itself gated on having it, and `--help` breaks on
31+
# exactly the machine this resolution exists for. Empty default satisfies `set -u`
32+
# if a helper is ever reached by another path.
33+
PY_BIN=""
34+
1035
usage() {
1136
cat <<'EOF'
1237
Usage:
@@ -24,15 +49,15 @@ EOF
2449
}
2550

2651
get_version() {
27-
python3 - <<'PY'
52+
$PY_BIN - <<'PY'
2853
import tomllib
2954
from pathlib import Path
3055
print(tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"])
3156
PY
3257
}
3358

3459
get_pkg_name() {
35-
python3 - <<'PY'
60+
$PY_BIN - <<'PY'
3661
import tomllib
3762
from pathlib import Path
3863
print(tomllib.loads(Path("pyproject.toml").read_text())["project"]["name"])
@@ -41,7 +66,7 @@ PY
4166

4267
set_version() {
4368
local ver="$1"
44-
python3 - "$ver" <<'PY'
69+
$PY_BIN - "$ver" <<'PY'
4570
import re, sys
4671
from pathlib import Path
4772
ver = sys.argv[1]
@@ -56,7 +81,7 @@ PY
5681

5782
bump_version() {
5883
local kind="$1" current="$2"
59-
python3 - "$kind" "$current" <<'PY'
84+
$PY_BIN - "$kind" "$current" <<'PY'
6085
import re, sys
6186
kind, current = sys.argv[1], sys.argv[2]
6287
match = re.match(r"^(\d+)\.(\d+)\.(\d+)(.*)$", current)
@@ -95,14 +120,14 @@ update_changelog() {
95120
local ver="$1"
96121
local date
97122
date="$(date +%Y-%m-%d)"
98-
python3 scripts/update_changelog.py "$ver" "$date"
123+
$PY_BIN scripts/update_changelog.py "$ver" "$date"
99124
}
100125

101126
cmd_prepare() {
127+
PY_BIN="$(resolve_python)"
102128
local bump="${1:-}"
103129
[[ -n "$bump" ]] || { usage; die "missing bump kind or explicit version"; }
104130
need gh
105-
need python3
106131
ensure_clean
107132

108133
local current new base branch pkg
@@ -150,8 +175,8 @@ After merge, run \`./scripts/release.sh publish\` from a clean \`${base}\` check
150175
}
151176

152177
cmd_publish() {
178+
PY_BIN="$(resolve_python)"
153179
need gh
154-
need python3
155180
ensure_clean
156181

157182
local base ver tag
@@ -166,7 +191,7 @@ cmd_publish() {
166191

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

0 commit comments

Comments
 (0)