Repository navigation
211 lines (187 loc) · 8.26 KB
/
Copy pathrelease.yml
File metadata and controls
211 lines (187 loc) · 8.26 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
name: Release
# Dispatch from main with the version a merged release PR set, or push its tag.
# A dispatch pushes the tag only after every gate, so a refused dispatch leaves no tag.
# A pushed tag runs the same gates but already exists, and the org forbids deleting tags,
# so a refused pushed tag is spent: fix the cause and release the next patch.
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
version:
description: 'Version to release, e.g. 0.3.0 (must match VERSION)'
required: true
type: string
dry_run:
description: 'Run every gate and stage the artifacts, but tag, publish and release nothing'
required: false
type: boolean
default: false
concurrency:
group: release-${{ github.event_name == 'push' && github.ref_name || format('v{0}', inputs.version) }}
cancel-in-progress: false
permissions:
contents: read
jobs:
release:
name: Gate, publish to Maven Central & release
# Kotlin/Native cross-compiles the Apple klibs from Linux and kzstd has no cinterop, so
# Linux publishes every target. It cannot run the Apple tests; the green-CI gate covers them.
runs-on: ubuntu-latest
timeout-minutes: 90
permissions:
contents: write
id-token: write
attestations: write
checks: read
statuses: read
env:
DRY_RUN: ${{ inputs.dry_run || false }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Resolve version
id: version
env:
REQUESTED: ${{ inputs.version || github.ref_name }}
run: |
# A dispatch takes the bare version so the concurrency group above is canonical.
if [ "$GITHUB_EVENT_NAME" = workflow_dispatch ] && [ "${REQUESTED#v}" != "$REQUESTED" ]; then
echo "::error::pass the version without a leading v: ${REQUESTED#v}"
exit 1
fi
echo "version=${REQUESTED#v}" >> "$GITHUB_OUTPUT"
- name: Release from main only
if: env.DRY_RUN != 'true'
run: |
git fetch --quiet origin main
if ! git merge-base --is-ancestor HEAD origin/main; then
echo "::error::$GITHUB_REF is not on main; release from main (dry_run may run anywhere)"
exit 1
fi
- name: Verify the version sources agree
env:
V: ${{ steps.version.outputs.version }}
run: |
VF=$(tr -d '[:space:]' < VERSION)
VG=$(sed -n 's/^VERSION_NAME=//p' gradle.properties | tr -d '[:space:]')
if [ "$V" != "$VF" ] || [ "$V" != "$VG" ]; then
echo "::error::releasing $V, but VERSION is $VF and VERSION_NAME is $VG"
exit 1
fi
git fetch --quiet origin "refs/tags/v$V:refs/tags/v$V" 2>/dev/null || true
if git rev-parse --quiet --verify "v$V^{commit}" >/dev/null &&
[ "$(git rev-parse "v$V^{commit}")" != "$(git rev-parse HEAD)" ]; then
if [ "$DRY_RUN" = true ]; then
echo "::warning::v$V already exists on another commit; a real release would stop here"
else
echo "::error::v$V already exists on another commit"
exit 1
fi
fi
echo "releasing $V from $(git rev-parse --short HEAD)"
- name: Render release notes
env:
V: ${{ steps.version.outputs.version }}
run: |
NOTES=$(scripts/changelog.sh notes "$V")
cat > release-notes.md <<EOF
Pure-Kotlin multiplatform Zstandard (zstd) codec with dictionary support,
interoperable with libzstd, zero runtime dependencies.
\`\`\`kotlin
implementation("org.meshtastic:kzstd:${V}")
\`\`\`
${NOTES}
EOF
cat release-notes.md
# CI does not run on tags, so the Apple tests this runner cannot execute are only
# guaranteed by the required checks having passed on this exact commit.
- name: Require green CI on the commit being released
env:
GH_TOKEN: ${{ github.token }}
run: scripts/release-checks.sh green-ci "$(git rev-parse HEAD)"
- name: Setup JDK 25
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
with:
distribution: temurin
java-version: 25.0.4+101.0.LTS
- name: Setup Gradle
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
with:
develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
- name: Cache Konan (Kotlin/Native toolchain)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.konan
key: konan-${{ runner.os }}-${{ hashFiles('gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
konan-${{ runner.os }}-
- name: Build, test & API check (all targets)
run: ./gradlew build --stacktrace
- name: Stage signed artifacts locally
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
run: ./gradlew publishToMavenLocal --no-configuration-cache --stacktrace
- name: Refuse a -SNAPSHOT dependency
run: scripts/release-checks.sh no-snapshots "${{ steps.version.outputs.version }}"
- name: Collect every staged artifact for attestation
run: |
mkdir -p maven-attestation
find ~/.m2/repository/org/meshtastic -path "*/${{ steps.version.outputs.version }}/*" -type f \
\( -name '*.jar' -o -name '*.klib' -o -name '*.pom' -o -name '*.module' \) \
-exec cp {} maven-attestation/ \;
ls -1 maven-attestation
- name: Attest the staged artifacts
if: env.DRY_RUN != 'true'
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: maven-attestation/*
- name: Check Maven Central state
# Sonatype rejects a re-upload, so a re-run after a partial release skips the publish.
id: central
env:
V: ${{ steps.version.outputs.version }}
run: |
# Only a 404 means unpublished; anything else stops the release rather than guess.
for _ in 1 2 3 4 5; do
HTTP=$(curl -sS -o /dev/null -w "%{http_code}" \
"https://repo1.maven.org/maven2/org/meshtastic/kzstd-jvm/$V/kzstd-jvm-$V.pom" || echo 000)
echo "Maven Central POM probe for $V: HTTP $HTTP"
case "$HTTP" in
200) echo "published=true" >> "$GITHUB_OUTPUT"; exit 0 ;;
404) echo "published=false" >> "$GITHUB_OUTPUT"; exit 0 ;;
esac
sleep 20
done
echo "::error::Maven Central did not answer 200 or 404 for $V"
exit 1
- name: Push the release tag
if: env.DRY_RUN != 'true'
env:
V: ${{ steps.version.outputs.version }}
run: |
if git rev-parse --quiet --verify "v$V" >/dev/null; then
echo "v$V already on origin"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "v$V" -m "v$V"
git push origin "v$V"
- name: Publish to Maven Central
if: env.DRY_RUN != 'true' && steps.central.outputs.published != 'true'
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
run: ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache --stacktrace
- name: Create or update the GitHub Release
if: env.DRY_RUN != 'true'
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
tag_name: v${{ steps.version.outputs.version }}
name: v${{ steps.version.outputs.version }}
body_path: release-notes.md
- name: Dry-run summary
if: env.DRY_RUN == 'true'
run: echo "dry run passed every gate for ${{ steps.version.outputs.version }}; nothing was tagged, published or released"