Repository navigation
242 lines (222 loc) · 8.72 KB
/
Copy pathrelease.yml
File metadata and controls
242 lines (222 loc) · 8.72 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
name: Release
# Merge to master: bump = the HIGHEST release:major|minor|patch|none label (major > minor > patch > none) over every
# PR merged since the last release tag (first-parent commits lastReleaseTag..HEAD). A PR without a release label
# counts as minor; all PRs release:none = skip; no merged PR for any of those commits = the job fails (releases
# come from labelled PRs). Because the bump covers the whole range, a run cancelled while pending loses nothing:
# the next run carries its bump.
#
# release.sh is the only version implementation; it tags and pushes, and refuses to tag a commit whose history
# lacks the newest release tag. A tag pushed with GITHUB_TOKEN starts no other workflow, so this run calls
# publish-nuget.yml itself with the version release.sh produced. Order: test -> tag -> publish -> GitHub Release.
#
# Recovery: re-run a failed run only while its commit is still master's tip; otherwise do nothing, the next merge
# carries the bump.
on:
push:
branches: [ master ]
workflow_dispatch:
inputs:
version_bump:
description: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major
- custom
custom_version:
description: 'Custom version X.Y.Z.B (only used if version_bump is custom)'
required: false
type: string
permissions:
contents: write
pull-requests: read
# One run at a time, in order; a pending run replaced by a newer one loses nothing (see above).
concurrency:
group: release-master
cancel-in-progress: false
jobs:
tag:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.bump.outputs.skip }}
bump: ${{ steps.bump.outputs.bump }}
version: ${{ steps.release.outputs.version }}
tag: ${{ steps.release.outputs.tag }}
previous: ${{ steps.release.outputs.previous }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure git user
run: |
git config --local user.email "actions@github.com"
git config --local user.name "GitHub Actions"
- name: Determine bump
id: bump
env:
GH_TOKEN: ${{ github.token }}
EVENT: ${{ github.event_name }}
DISPATCH_BUMP: ${{ github.event.inputs.version_bump }}
CUSTOM_VERSION: ${{ github.event.inputs.custom_version }}
REPO: ${{ github.repository }}
SHA: ${{ github.sha }}
run: |
set -o pipefail
if [ "$EVENT" = "workflow_dispatch" ]; then
BUMP="$DISPATCH_BUMP"
if [ "$BUMP" = "custom" ] && [ -z "$CUSTOM_VERSION" ]; then
echo "::error::version_bump is custom but custom_version is empty"
exit 1
fi
else
LAST=$(git tag -l "ModelingEvolution.Drawing/*" | sed 's#^ModelingEvolution.Drawing/##' \
| { grep -v -- '-' || true; } | sort -V | tail -1)
if [ -n "$LAST" ]; then
RANGE="refs/tags/ModelingEvolution.Drawing/$LAST..HEAD"
else
RANGE="HEAD"
fi
COMMITS=$(git rev-list --first-parent "$RANGE")
echo "Last release: ${LAST:-none}; first-parent commits since: $(printf '%s' "$COMMITS" | grep -c . || true)"
if [ -z "$COMMITS" ]; then
echo "::error::No commits since the last release tag ($LAST)"
exit 1
fi
rank() { case "$1" in none) echo 0 ;; patch) echo 1 ;; minor) echo 2 ;; major) echo 3 ;; esac; }
FOUND=0
BEST=-1
for C in $COMMITS; do
# The commit -> PR association can lag right after a merge: HEAD (this run's merge) is retried.
# A failing API call fails the job.
ATTEMPTS=1
[ "$C" = "$SHA" ] && ATTEMPTS=4
PR=""
for attempt in $(seq 1 "$ATTEMPTS"); do
PR=$(gh api "repos/$REPO/commits/$C/pulls" \
| jq -c --arg sha "$C" '[.[] | select(.merge_commit_sha == $sha)][0] // empty')
[ -n "$PR" ] && break
if [ "$attempt" -lt "$ATTEMPTS" ]; then
echo "No merged PR for $C yet (attempt $attempt); retrying in 10 s"
sleep 10
fi
done
if [ -z "$PR" ]; then
if [ "$C" = "$SHA" ]; then
# HEAD is this run's merge: its label decides the bump, so an older PR must never stand in for it.
echo "::error::No merged PR found for HEAD $SHA after $ATTEMPTS attempts; releases come from labelled PRs. Re-run via workflow_dispatch if this was intended."
exit 1
fi
echo "$C: no merged PR"
continue
fi
FOUND=$((FOUND + 1))
LABELS=$(echo "$PR" | jq -r '.labels[].name' \
| { grep -E '^release:(major|minor|patch|none)$' || true; } | sort -u)
COUNT=$(printf '%s' "$LABELS" | grep -c . || true)
NUMBER=$(echo "$PR" | jq -r '.number')
if [ "$COUNT" -gt 1 ]; then
echo "::error::PR #$NUMBER carries more than one release label: ${LABELS//$'\n'/ }"
exit 1
fi
if [ "$COUNT" -eq 1 ]; then
PR_BUMP="${LABELS#release:}"
else
PR_BUMP="minor"
fi
echo "$C: PR #$NUMBER -> $PR_BUMP"
R=$(rank "$PR_BUMP")
if [ "$R" -gt "$BEST" ]; then BEST=$R; BUMP=$PR_BUMP; fi
done
if [ "$FOUND" -eq 0 ]; then
echo "::error::No merged PR found for $SHA; releases come from labelled PRs. Re-run via workflow_dispatch if this was intended."
exit 1
fi
fi
echo "Bump: $BUMP"
echo "bump=$BUMP" >> "$GITHUB_OUTPUT"
if [ "$BUMP" = "none" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "## Release skipped (release:none)" >> "$GITHUB_STEP_SUMMARY"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
# Nothing is tagged while the suite is red. Tests write outside the repo; a dirty tree here is a defect.
- name: Setup .NET
if: steps.bump.outputs.skip != 'true'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Restore
if: steps.bump.outputs.skip != 'true'
working-directory: ./Sources
run: dotnet restore
- name: Build
if: steps.bump.outputs.skip != 'true'
working-directory: ./Sources
run: dotnet build --configuration Release --no-restore
- name: Test
if: steps.bump.outputs.skip != 'true'
working-directory: ./Sources
run: dotnet test --configuration Release --no-build --verbosity normal
- name: Tag with release.sh
id: release
if: steps.bump.outputs.skip != 'true'
env:
BUMP: ${{ steps.bump.outputs.bump }}
CUSTOM_VERSION: ${{ github.event.inputs.custom_version }}
run: |
if [ "$BUMP" = "custom" ]; then
./release.sh --version "$CUSTOM_VERSION" --ci
else
./release.sh "--$BUMP" --ci
fi
publish:
needs: tag
if: needs.tag.outputs.skip != 'true'
permissions:
contents: read
id-token: write # the reusable workflow needs the OIDC token for nuget.org Trusted Publishing
uses: ./.github/workflows/publish-nuget.yml
with:
version: ${{ needs.tag.outputs.version }}
secrets: inherit
# The public GitHub Release exists only after the package was pushed.
github-release:
needs: [ tag, publish ]
if: needs.tag.outputs.skip != 'true'
runs-on: ubuntu-latest
steps:
- name: Create GitHub Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.tag.outputs.tag }}
name: "ModelingEvolution.Drawing v${{ needs.tag.outputs.version }}"
body: |
## ModelingEvolution.Drawing v${{ needs.tag.outputs.version }}
### Installation
```bash
dotnet add package ModelingEvolution.Drawing --version ${{ needs.tag.outputs.version }}
```
---
**Full Changelog**: https://github.com/${{ github.repository }}/compare/ModelingEvolution.Drawing/${{ needs.tag.outputs.previous }}...${{ needs.tag.outputs.tag }}
draft: false
prerelease: false
generateReleaseNotes: true
- name: Summary
env:
BUMP: ${{ needs.tag.outputs.bump }}
PREVIOUS: ${{ needs.tag.outputs.previous }}
VERSION: ${{ needs.tag.outputs.version }}
TAG: ${{ needs.tag.outputs.tag }}
run: |
{
echo "## Released"
echo ""
echo "- **Bump**: $BUMP"
echo "- **Previous Version**: $PREVIOUS"
echo "- **New Version**: $VERSION"
echo "- **Tag**: $TAG"
} >> "$GITHUB_STEP_SUMMARY"