-
Notifications
You must be signed in to change notification settings - Fork 5
245 lines (210 loc) · 7.76 KB
/
Copy pathrelease.yml
File metadata and controls
245 lines (210 loc) · 7.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
name: Build and Release
on:
push:
branches: [main, master]
paths:
- "pyproject.toml"
permissions:
contents: write
id-token: write
packages: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
format-lint:
name: "Format and Lint"
uses: ./.github/workflows/_format-lint-action.yml
test:
name: "Run Tests"
needs: format-lint
uses: ./.github/workflows/_run-tests-action.yml
detect-version-change:
name: "Detect Version Change"
runs-on: ubuntu-latest
outputs:
version: ${{ steps.detect.outputs.version }}
prerelease: ${{ steps.detect.outputs.prerelease }}
has-change: ${{ steps.detect.outputs.has-change }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect root version change
id: detect
env:
BEFORE_SHA: ${{ github.event.before }}
run: |
set -euo pipefail
HAS_CHANGE="false"
VERSION=""
IS_PRERELEASE="false"
# Robust base selection: before SHA -> HEAD~1 -> empty tree
EMPTY_TREE="$(git hash-object -t tree /dev/null)"
if [ -n "${BEFORE_SHA:-}" ] && git cat-file -e "${BEFORE_SHA}^{commit}" 2>/dev/null; then
: # valid BEFORE_SHA from event
elif git rev-parse HEAD~1 >/dev/null 2>&1; then
BEFORE_SHA="$(git rev-parse HEAD~1)"
else
BEFORE_SHA="$EMPTY_TREE"
fi
# Check if root pyproject.toml has a version change in the diff
if git diff "$BEFORE_SHA" HEAD -- pyproject.toml | grep -qE '^\+version\s*=\s*["'\''"]'; then
VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
if echo "$VERSION" | grep -qE '([.-]?(a|b|rc|dev|pre|preview))[0-9]*'; then
IS_PRERELEASE="true"
fi
HAS_CHANGE="true"
echo "Version change detected: $VERSION (prerelease: $IS_PRERELEASE)"
else
echo "No version change detected in root pyproject.toml"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT"
echo "has-change=$HAS_CHANGE" >> "$GITHUB_OUTPUT"
# Gate the release on all sub-package versions matching the root version.
# If any package in packages/ is out of sync the release is blocked.
# Fix with: ./dev/sync-versions.sh
verify-version-sync:
name: "Verify Version Sync"
runs-on: ubuntu-latest
needs: detect-version-change
if: needs.detect-version-change.outputs.has-change == 'true'
steps:
- uses: actions/checkout@v4
- name: Verify all packages match root version
env:
ROOT_VERSION: ${{ needs.detect-version-change.outputs.version }}
run: |
set -euo pipefail
MISMATCH=0
for toml in packages/*/pyproject.toml; do
PKG_VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('$toml','rb'))['project']['version'])")
PKG_NAME=$(basename "$(dirname "$toml")")
if [ "$PKG_VERSION" != "$ROOT_VERSION" ]; then
echo "::error::$PKG_NAME version ($PKG_VERSION) does not match root ($ROOT_VERSION)"
MISMATCH=1
else
echo "$PKG_NAME: $PKG_VERSION (ok)"
fi
done
if [ "$MISMATCH" -ne 0 ]; then
echo "::error::Package versions are out of sync. Run ./dev/sync-versions.sh"
exit 1
fi
echo "All package versions match: $ROOT_VERSION"
check-tag:
name: "Check Existing Tag"
runs-on: ubuntu-latest
needs: detect-version-change
if: needs.detect-version-change.outputs.has-change == 'true'
outputs:
has-release: ${{ steps.check.outputs.has-release }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if tag exists
id: check
env:
VERSION: ${{ needs.detect-version-change.outputs.version }}
run: |
set -euo pipefail
TAG="v${VERSION}"
if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then
echo "::warning::Tag $TAG already exists, skipping release"
echo "has-release=false" >> "$GITHUB_OUTPUT"
else
echo "Tag $TAG does not exist, will release v$VERSION"
echo "has-release=true" >> "$GITHUB_OUTPUT"
fi
build-and-publish:
name: "Build & Publish All Packages"
runs-on: ubuntu-latest
needs: [test, detect-version-change, verify-version-sync, check-tag]
if: needs.check-tag.outputs.has-release == 'true'
environment: release
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
- name: Set up Python
run: uv python install 3.10.14
- name: Build all packages
run: |
set -euo pipefail
rm -rf dist
mkdir -p dist
# Build root package
echo "=== Building root package ==="
uv build --out-dir dist .
# Build each workspace package
for pkg_dir in packages/*/; do
if [ -f "$pkg_dir/pyproject.toml" ]; then
pkg_name=$(basename "$pkg_dir")
echo "=== Building $pkg_name ==="
uv build --out-dir dist "$pkg_dir"
fi
done
echo "=== All built artifacts ==="
ls -la dist/
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist-v${{ needs.detect-version-change.outputs.version }}
path: dist/
retention-days: 120
- name: Publish to PyPI
id: publish
env:
PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
run: |
set -euo pipefail
uv publish --token "$PYPI_TOKEN" dist/*
- name: Create GitHub Release
if: steps.publish.outcome == 'success'
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.detect-version-change.outputs.version }}
name: "Release v${{ needs.detect-version-change.outputs.version }}"
draft: false
prerelease: ${{ needs.detect-version-change.outputs.prerelease }}
generate_release_notes: true
files: dist/*
release-status:
name: "Release Status"
runs-on: ubuntu-latest
needs: [test, detect-version-change, verify-version-sync, check-tag, build-and-publish]
if: always()
steps:
- name: Check release status
env:
TEST_RESULT: ${{ needs.test.result }}
VERSION_SYNC_RESULT: ${{ needs.verify-version-sync.result }}
PUBLISH_RESULT: ${{ needs.build-and-publish.result }}
HAS_CHANGE: ${{ needs.detect-version-change.outputs.has-change }}
VERSION: ${{ needs.detect-version-change.outputs.version }}
HAS_RELEASE: ${{ needs.check-tag.outputs.has-release }}
run: |
echo "=== Release Summary ==="
echo "Tests: $TEST_RESULT"
echo "Version change: $HAS_CHANGE"
echo "Version: $VERSION"
echo "Version sync: $VERSION_SYNC_RESULT"
echo "Tag check: $HAS_RELEASE"
echo "Build and publish: $PUBLISH_RESULT"
if [[ "$TEST_RESULT" == "failure" ]]; then
echo "::error::Tests failed"
exit 1
fi
if [[ "$VERSION_SYNC_RESULT" == "failure" ]]; then
echo "::error::Package versions are out of sync"
exit 1
fi
if [[ "$PUBLISH_RESULT" == "failure" ]]; then
echo "::error::Build and publish failed"
exit 1
fi
echo "Release workflow completed successfully"