Skip to content
Merged
Show file tree
Hide file tree
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
131 changes: 124 additions & 7 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,124 @@
./lint.sh && \
./test.sh && \
python3 -m pip install --upgrade build && \
python3 -m pip install --upgrade twine && \
rm -rf dist && \
python3 -m build && \
python3 -m twine upload dist/*
#!/bin/bash
set -euo pipefail

PYTHON="${PYTHON:-python}"

usage() {
echo "Usage: $0 [version]" >&2
echo "Example: $0 3.13.5" >&2
}

die() {
echo "deploy.sh: $*" >&2
exit 1
}

current_version() {
"$PYTHON" - <<'PY'
import re
from pathlib import Path

text = Path("mhctools/__init__.py").read_text()
match = re.search(r'^__version__ = "([^"]+)"$', text, re.MULTILINE)
if not match:
raise SystemExit("Unable to find mhctools.__version__")
print(match.group(1))
PY
}

bump_version() {
local version="$1"
"$PYTHON" - "$version" <<'PY'
import re
import sys
from pathlib import Path

version = sys.argv[1]
if not re.fullmatch(r"[0-9]+[.][0-9]+[.][0-9]+", version):
raise SystemExit(
"Version must be X.Y.Z without a leading 'v', got %r" % version)

path = Path("mhctools/__init__.py")
text = path.read_text()
new_text, count = re.subn(
r'^__version__ = "[^"]+"$',
'__version__ = "%s"' % version,
text,
count=1,
flags=re.MULTILINE)
if count != 1:
raise SystemExit("Unable to update mhctools.__version__")
path.write_text(new_text)
PY
}

require_clean_tree() {
git update-index -q --refresh
git diff --quiet || die "working tree has unstaged changes"
git diff --cached --quiet || die "working tree has staged changes"
}

require_release_branch() {
local branch
branch="$(git rev-parse --abbrev-ref HEAD)"
if [[ "$branch" != "main" && "$branch" != "master" ]]; then
die "must deploy from main or master, currently on $branch"
fi
echo "$branch"
}

require_tooling() {
"$PYTHON" -m build --version >/dev/null ||
die "python module 'build' is unavailable; install the dev extra"
"$PYTHON" -m twine --version >/dev/null ||
die "python module 'twine' is unavailable; install the dev extra"
}

if [[ "$#" -gt 1 ]]; then
usage
exit 2
fi

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi

branch="$(require_release_branch)"
require_clean_tree
require_tooling

requested_version="${1:-}"
if [[ -n "$requested_version" ]]; then
requested_version="${requested_version#v}"
old_version="$(current_version)"
if [[ "$requested_version" != "$old_version" ]]; then
bump_version "$requested_version"
./lint.sh
./test.sh
git add mhctools/__init__.py
git commit -m "Bump to $requested_version"
git push origin "$branch"
else
./lint.sh
./test.sh
fi
else
./lint.sh
./test.sh
fi

release_version="$(current_version)"
tag="v$release_version"

git fetch --tags origin
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
die "tag $tag already exists"
fi

rm -rf dist
"$PYTHON" -m build
"$PYTHON" -m twine upload dist/*

git tag -a "$tag" -m "Release $tag"
git push origin "$tag"
2 changes: 1 addition & 1 deletion mhctools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def __getattr__(name):
raise AttributeError(
"module %r has no attribute %r" % (__name__, name))

__version__ = "3.13.4"
__version__ = "3.13.5"

__all__ = [
"Prediction",
Expand Down
31 changes: 31 additions & 0 deletions tests/test_deploy_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os
import subprocess
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
DEPLOY_SH = ROOT / "deploy.sh"


def test_deploy_script_is_valid_bash():
subprocess.run(
["bash", "-n", str(DEPLOY_SH)],
check=True,
cwd=ROOT)


def test_deploy_script_matches_documented_release_guards():
text = DEPLOY_SH.read_text()

assert "git rev-parse --abbrev-ref HEAD" in text
assert '"main" && "$branch" != "master"' in text
assert "git diff --quiet" in text
assert "git diff --cached --quiet" in text
assert "python3 -m pip install" not in text
assert 'PYTHON="${PYTHON:-python}"' in text
assert "git tag -a" in text
assert "git push origin \"$tag\"" in text


def test_deploy_script_is_executable():
assert os.access(DEPLOY_SH, os.X_OK)
Loading