Skip to content

Commit 7906c48

Browse files
authored
feat: offload release to GHA (#60)
1 parent c8d97ff commit 7906c48

2 files changed

Lines changed: 279 additions & 0 deletions

File tree

.github/workflows/release-cut.yml

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
name: Release
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
bump:
7+
description: Semver bump to apply when version is not provided.
8+
required: true
9+
type: choice
10+
default: patch
11+
options:
12+
- patch
13+
- minor
14+
- major
15+
version:
16+
description: Optional explicit version (x.y.z). If set, bump is ignored.
17+
required: false
18+
type: string
19+
20+
permissions:
21+
contents: write
22+
23+
concurrency:
24+
group: release-cut-${{ github.workflow }}-${{ github.ref_name }}
25+
cancel-in-progress: false
26+
27+
jobs:
28+
prepare:
29+
name: Bump, Commit, and Tag
30+
runs-on: ubuntu-latest
31+
outputs:
32+
tag: ${{ steps.version.outputs.tag }}
33+
version: ${{ steps.version.outputs.version }}
34+
release_commit: ${{ steps.commit.outputs.release_commit }}
35+
steps:
36+
- name: Checkout
37+
uses: actions/checkout@v4
38+
with:
39+
fetch-depth: 0
40+
41+
- name: Validate release branch
42+
shell: bash
43+
run: |
44+
set -euo pipefail
45+
if [[ "${GITHUB_REF_TYPE}" != "branch" ]]; then
46+
echo "workflow_dispatch release must run from a branch." >&2
47+
exit 1
48+
fi
49+
if [[ "${GITHUB_REF_NAME}" != "main" ]]; then
50+
echo "Releases must run from main (current: ${GITHUB_REF_NAME})." >&2
51+
exit 1
52+
fi
53+
54+
- name: Compute and apply release version
55+
id: version
56+
shell: bash
57+
run: |
58+
set -euo pipefail
59+
60+
if [[ -n "${{ inputs.version }}" ]]; then
61+
VERSION="$(./scripts/release_bump.sh --version "${{ inputs.version }}")"
62+
else
63+
VERSION="$(./scripts/release_bump.sh --bump "${{ inputs.bump }}")"
64+
fi
65+
66+
TAG="v${VERSION}"
67+
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
68+
echo "Tag ${TAG} already exists locally." >&2
69+
exit 1
70+
fi
71+
if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q "${TAG}$"; then
72+
echo "Tag ${TAG} already exists on origin." >&2
73+
exit 1
74+
fi
75+
76+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
77+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
78+
79+
- name: Commit, tag, and push
80+
id: commit
81+
shell: bash
82+
run: |
83+
set -euo pipefail
84+
85+
git config user.name "github-actions[bot]"
86+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
87+
88+
git add Cargo.lock \
89+
MODULE.bazel \
90+
crates/tokenizers/BUILD.bazel \
91+
crates/tokenizers/Cargo.toml \
92+
crates/tokenizers/src/lib.rs \
93+
tokenizer.go
94+
if git diff --cached --quiet; then
95+
echo "No release version changes detected." >&2
96+
exit 1
97+
fi
98+
99+
git commit -m "chore: release ${{ steps.version.outputs.tag }}"
100+
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
101+
102+
git push origin "HEAD:${GITHUB_REF_NAME}"
103+
git push origin "${{ steps.version.outputs.tag }}"
104+
105+
echo "release_commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
106+
107+
publish:
108+
name: Create GitHub Release
109+
needs: prepare
110+
runs-on: ubuntu-latest
111+
permissions:
112+
contents: write
113+
steps:
114+
- name: Create or update GitHub release
115+
uses: softprops/action-gh-release@v2
116+
with:
117+
tag_name: ${{ needs.prepare.outputs.tag }}
118+
name: ${{ needs.prepare.outputs.tag }}
119+
target_commitish: ${{ needs.prepare.outputs.release_commit }}
120+
generate_release_notes: true

scripts/release_bump.sh

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
usage() {
5+
cat <<'EOF'
6+
Usage: scripts/release_bump.sh [--bump patch|minor|major] [--version x.y.z]
7+
8+
Examples:
9+
scripts/release_bump.sh --bump patch
10+
scripts/release_bump.sh --version 1.25.0
11+
EOF
12+
}
13+
14+
validate_semver() {
15+
local version="$1"
16+
[[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
17+
}
18+
19+
semver_gt() {
20+
local left="$1"
21+
local right="$2"
22+
23+
local l_major l_minor l_patch r_major r_minor r_patch
24+
IFS='.' read -r l_major l_minor l_patch <<< "${left}"
25+
IFS='.' read -r r_major r_minor r_patch <<< "${right}"
26+
27+
if (( l_major != r_major )); then
28+
(( l_major > r_major ))
29+
return
30+
fi
31+
if (( l_minor != r_minor )); then
32+
(( l_minor > r_minor ))
33+
return
34+
fi
35+
(( l_patch > r_patch ))
36+
}
37+
38+
bump_level="patch"
39+
explicit_version=""
40+
41+
while [[ $# -gt 0 ]]; do
42+
case "$1" in
43+
--bump)
44+
if [[ $# -lt 2 ]]; then
45+
echo "Missing value for --bump" >&2
46+
usage
47+
exit 1
48+
fi
49+
bump_level="$2"
50+
shift 2
51+
;;
52+
--version)
53+
if [[ $# -lt 2 ]]; then
54+
echo "Missing value for --version" >&2
55+
usage
56+
exit 1
57+
fi
58+
explicit_version="$2"
59+
shift 2
60+
;;
61+
--help|-h)
62+
usage
63+
exit 0
64+
;;
65+
*)
66+
echo "Unknown argument: $1" >&2
67+
usage
68+
exit 1
69+
;;
70+
esac
71+
done
72+
73+
current_version="$(perl -0ne 'if (/\[package\]\nname = "tokenizers-ffi"\nversion = "([^"]+)"/s) { print $1; exit }' crates/tokenizers/Cargo.toml)"
74+
if [[ -z "${current_version}" ]]; then
75+
echo "Failed to read current version from crates/tokenizers/Cargo.toml" >&2
76+
exit 1
77+
fi
78+
if ! validate_semver "${current_version}"; then
79+
echo "Current version is not semver: ${current_version}" >&2
80+
exit 1
81+
fi
82+
83+
latest_tag="$(git tag -l 'v*' --sort=-v:refname | head -n1 || true)"
84+
latest_tag_version=""
85+
if [[ -n "${latest_tag}" ]]; then
86+
latest_tag_version="${latest_tag#v}"
87+
if ! validate_semver "${latest_tag_version}"; then
88+
latest_tag_version=""
89+
fi
90+
fi
91+
92+
base_version="${current_version}"
93+
if [[ -n "${latest_tag_version}" ]] && semver_gt "${latest_tag_version}" "${base_version}"; then
94+
base_version="${latest_tag_version}"
95+
fi
96+
97+
if [[ -n "${explicit_version}" ]]; then
98+
if ! validate_semver "${explicit_version}"; then
99+
echo "Invalid semver value for --version: ${explicit_version}" >&2
100+
exit 1
101+
fi
102+
if ! semver_gt "${explicit_version}" "${base_version}"; then
103+
echo "Explicit version must be greater than ${base_version}: ${explicit_version}" >&2
104+
exit 1
105+
fi
106+
next_version="${explicit_version}"
107+
else
108+
IFS='.' read -r major minor patch <<< "${base_version}"
109+
case "${bump_level}" in
110+
patch)
111+
patch=$((patch + 1))
112+
;;
113+
minor)
114+
minor=$((minor + 1))
115+
patch=0
116+
;;
117+
major)
118+
major=$((major + 1))
119+
minor=0
120+
patch=0
121+
;;
122+
*)
123+
echo "Invalid --bump value: ${bump_level} (expected patch|minor|major)" >&2
124+
exit 1
125+
;;
126+
esac
127+
next_version="${major}.${minor}.${patch}"
128+
fi
129+
130+
next_symbol="${next_version//./_}"
131+
132+
perl -0pi -e "s/(\\[package\\]\\nname = \"tokenizers-ffi\"\\nversion = \")[^\"]+(\"\\n)/\$1${next_version}\$2/s" crates/tokenizers/Cargo.toml
133+
perl -0pi -e "s/(\\[\\[package\\]\\]\\nname = \"tokenizers-ffi\"\\nversion = \")[^\"]+(\"\\n)/\$1${next_version}\$2/s" Cargo.lock
134+
perl -pi -e "s/tokenizers_version_\\d+_\\d+_\\d+/tokenizers_version_${next_symbol}/g" tokenizer.go crates/tokenizers/src/lib.rs
135+
perl -pi -e 'if (/name = "tokenizers_rs"/) { $in = 1 } if ($in && /version = "/) { s/version = "[^"]+"/version = "'"${next_version}"'"/; $in = 0 }' crates/tokenizers/BUILD.bazel
136+
perl -0pi -e "s/(module\\(\\n\\s*name = \"com_github_daulet_tokenizers\",\\n\\s*version = \")[^\"]+(\",\\n\\))/\$1${next_version}\$2/s" MODULE.bazel
137+
138+
if ! grep -Eq "^version = \"${next_version}\"$" crates/tokenizers/Cargo.toml; then
139+
echo "Failed to write crates/tokenizers/Cargo.toml version ${next_version}" >&2
140+
exit 1
141+
fi
142+
if ! grep -Eq "tokenizers_version_${next_symbol}" tokenizer.go; then
143+
echo "Failed to write tokenizer.go symbol tokenizers_version_${next_symbol}" >&2
144+
exit 1
145+
fi
146+
if ! grep -Eq "tokenizers_version_${next_symbol}" crates/tokenizers/src/lib.rs; then
147+
echo "Failed to write crates/tokenizers/src/lib.rs symbol tokenizers_version_${next_symbol}" >&2
148+
exit 1
149+
fi
150+
if ! grep -Eq "^ version = \"${next_version}\",$" MODULE.bazel; then
151+
echo "Failed to write MODULE.bazel module version ${next_version}" >&2
152+
exit 1
153+
fi
154+
if ! grep -Eq "^ version = \"${next_version}\",$" crates/tokenizers/BUILD.bazel; then
155+
echo "Failed to write crates/tokenizers/BUILD.bazel version ${next_version}" >&2
156+
exit 1
157+
fi
158+
159+
echo "${next_version}"

0 commit comments

Comments
 (0)