Skip to content

Commit 194fa9d

Browse files
CopilotDylanBPY
andcommitted
Replace tag-triggered release workflow with CHANGELOG.md-triggered release
The new workflow: - Triggers on push to master when CHANGELOG.md is modified - Parses CHANGELOG.md to extract the latest version and release notes - Guards against duplicate releases (idempotency check) - Builds firmware for all hubs and creates a GitHub release - Auto-detects pre-release versions (alpha/beta/rc) Co-authored-by: DylanBPY <110776467+DylanBPY@users.noreply.github.com>
1 parent b2866d9 commit 194fa9d

1 file changed

Lines changed: 82 additions & 20 deletions

File tree

.github/workflows/release.yml

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
name: Release on Changelog Update
2+
13
on:
24
push:
3-
tags:
4-
- 'v3.*'
5-
- 'v4.*'
6-
7-
name: Upload Release Assets
5+
branches:
6+
- master
7+
paths:
8+
- 'CHANGELOG.md'
89

910
env:
1011
MAKEOPTS: -j
@@ -13,27 +14,78 @@ permissions:
1314
contents: write
1415

1516
jobs:
16-
upload_release:
17-
name: Upload Release Assets
17+
release:
18+
name: Build and Release
1819
runs-on: ubuntu-24.04
1920
steps:
20-
- name: Install cross-compiler
21-
run: sudo apt-get update && sudo apt-get install --yes gcc-arm-none-eabi
2221
- name: Checkout code
2322
uses: actions/checkout@v4
2423
with:
2524
submodules: true
2625
fetch-depth: 0
26+
27+
- name: Extract latest version and release notes from CHANGELOG.md
28+
id: changelog
29+
run: |
30+
# Find the first versioned section (skip [Unreleased])
31+
# Pattern: ## [x.y.z...] - date
32+
VERSION=$(grep -oP '(?<=^## \[)[^\]]+(?=\])' CHANGELOG.md | grep -v -i '^Unreleased$' | head -1)
33+
34+
if [ -z "$VERSION" ]; then
35+
echo "No versioned release found in CHANGELOG.md"
36+
exit 1
37+
fi
38+
39+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
40+
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
41+
42+
# Extract release notes between this version's heading and the next ## heading
43+
# Use awk to capture lines between the target section and the next section
44+
ESCAPED_VERSION=$(printf '%s' "$VERSION" | sed 's/[.[\*^$()+?{|]/\\&/g')
45+
NOTES=$(awk "/^## \\[$ESCAPED_VERSION\\]/{found=1; next} found && /^## \\[/{exit} found{print}" CHANGELOG.md)
46+
47+
# Write notes to a file for the release body
48+
echo "$NOTES" > /tmp/release_notes.md
49+
50+
echo "Detected version: $VERSION"
51+
echo "Release notes:"
52+
cat /tmp/release_notes.md
53+
54+
- name: Check if release already exists
55+
id: check_release
56+
env:
57+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58+
run: |
59+
TAG="v${{ steps.changelog.outputs.version }}"
60+
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
61+
echo "Release $TAG already exists. Skipping."
62+
echo "exists=true" >> "$GITHUB_OUTPUT"
63+
else
64+
echo "exists=false" >> "$GITHUB_OUTPUT"
65+
fi
66+
67+
- name: Install cross-compiler
68+
if: steps.check_release.outputs.exists == 'false'
69+
run: sudo apt-get update && sudo apt-get install --yes gcc-arm-none-eabi
70+
2771
- run: pipx install poetry
72+
if: steps.check_release.outputs.exists == 'false'
73+
2874
- run: poetry install --only=build
75+
if: steps.check_release.outputs.exists == 'false'
76+
2977
- name: Create credits file
78+
if: steps.check_release.outputs.exists == 'false'
3079
run: |
3180
echo "$PYBRICKS_EV3_CREDITS" > bricks/ev3/ci_credits.txt
3281
env:
3382
PYBRICKS_EV3_CREDITS: ${{ secrets.PYBRICKS_EV3_CREDITS }}
83+
3484
- name: Build firmware
85+
if: steps.check_release.outputs.exists == 'false'
3586
run: |
36-
export MICROPY_GIT_TAG=ci-release-${{ github.run_number }}-$(git describe --tags --dirty --always --exclude "@pybricks/*")
87+
TAG="${{ steps.changelog.outputs.tag }}"
88+
export MICROPY_GIT_TAG=ci-release-${{ github.run_number }}-${TAG}
3789
export MICROPY_GIT_HASH=$(echo ${{ github.sha }} | cut -c1-8)
3890
poetry run make $MAKEOPTS -C micropython/mpy-cross
3991
poetry run make $MAKEOPTS -C bricks/movehub
@@ -43,27 +95,37 @@ jobs:
4395
poetry run make $MAKEOPTS -C bricks/essentialhub
4496
poetry run make $MAKEOPTS -C bricks/nxt
4597
poetry run make $MAKEOPTS -C bricks/ev3
46-
- name: Get tag
47-
run: echo "GITHUB_TAG=${GITHUB_REF#*refs/tags/}" >> $GITHUB_ENV
98+
4899
- name: Create Release and Upload Assets
100+
if: steps.check_release.outputs.exists == 'false'
49101
env:
50102
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51-
tag: ${{ github.ref_name }}
52103
run: |
53-
if [[ "${{ contains(env.GITHUB_TAG, 'a') || contains(env.GITHUB_TAG, 'b') || contains(env.GITHUB_TAG, 'c') }}" == "true" ]]; then
104+
TAG="${{ steps.changelog.outputs.tag }}"
105+
VERSION="${{ steps.changelog.outputs.version }}"
106+
107+
# Determine pre-release flag (alpha, beta, release candidate)
108+
PRERELEASE_FLAG=""
109+
if [[ "$VERSION" == *a* ]] || [[ "$VERSION" == *b* ]] || [[ "$VERSION" == *rc* ]]; then
54110
PRERELEASE_FLAG="--prerelease"
55-
else
56-
PRERELEASE_FLAG=""
57111
fi
112+
113+
# Rename and collect firmware assets
58114
HUBS="movehub cityhub technichub primehub essentialhub nxt ev3"
115+
ASSETS=""
59116
for HUB in $HUBS; do
60-
NEW_FILENAME="./bricks/$HUB/build/pybricks-$HUB-${{ env.GITHUB_TAG }}.zip"
117+
NEW_FILENAME="./bricks/$HUB/build/pybricks-$HUB-$TAG.zip"
61118
mv "./bricks/$HUB/build/firmware.zip" "$NEW_FILENAME"
62119
ASSETS="$ASSETS $NEW_FILENAME"
63120
done
64-
gh release create "$tag" \
121+
122+
# Create the git tag and GitHub release
123+
git tag "$TAG"
124+
git push origin "$TAG"
125+
126+
gh release create "$TAG" \
65127
--repo="$GITHUB_REPOSITORY" \
66-
--title="${tag#v}" \
67-
-F CHANGELOG.md \
128+
--title="${VERSION}" \
129+
-F /tmp/release_notes.md \
68130
$PRERELEASE_FLAG \
69131
$ASSETS

0 commit comments

Comments
 (0)