-
Notifications
You must be signed in to change notification settings - Fork 0
386 lines (350 loc) · 14.4 KB
/
Copy pathrelease.yml
File metadata and controls
386 lines (350 loc) · 14.4 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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
name: Release
# Build the signed dmg + OTA binary on a real macOS runner and attach them to
# the GitHub release. Triggered by pushing a version tag (e.g. `git tag v0.0.49
# && git push --tags`) — no more flaky local uploads.
#
# Struktur 3 job (wall-clock ±16m → ±7-10m): `gates` (fmt/clippy/test) dan
# `build` (dmg + OTA) berjalan PARALEL; `publish` menunggu keduanya lalu
# menempelkan artefak ke GitHub Release. Rilis tetap tidak terbit bila gate
# gagal — hanya urutannya yang tidak lagi serial.
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Tag to build/release (e.g. v0.0.49)"
required: true
permissions:
contents: write
jobs:
gates:
runs-on: macos-14 # Apple silicon (arm64)
steps:
- name: Resolve tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
tag="${{ inputs.tag }}"
else
tag="$GITHUB_REF_NAME"
fi
case "$tag" in
v*) ;;
*) echo "Release tag must start with v: $tag" >&2; exit 1 ;;
esac
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
ref: ${{ steps.tag.outputs.tag }}
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Validate release tag
run: |
version="$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')"
expected="v$version"
actual="${{ steps.tag.outputs.tag }}"
if [ "$actual" != "$expected" ]; then
echo "Tag/version mismatch: tag=$actual Cargo.toml=$version" >&2
exit 1
fi
- name: Check formatting
run: cargo fmt --check
- name: Run GUI visual QA contracts
run: python3 scripts/gui-visual-qa.py
# clippy = cargo check + lint dengan cakupan flag yang sama, jadi tidak
# ada step `cargo check` terpisah (dulu redundan).
- name: Run clippy
run: cargo clippy --workspace --all-targets -- -D warnings
# oxide-desktop (egui, dorman — tidak ada command yang me-route ke sana)
# dikecualikan: clippy di atas sudah menjaga crate itu tetap compile,
# membangun test-harness-nya hanya membakar menit runner.
- name: Run tests
timeout-minutes: 20
run: cargo test --workspace --exclude oxide-desktop
build:
runs-on: macos-14 # Apple silicon (arm64)
steps:
- name: Resolve tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
tag="${{ inputs.tag }}"
else
tag="$GITHUB_REF_NAME"
fi
case "$tag" in
v*) ;;
*) echo "Release tag must start with v: $tag" >&2; exit 1 ;;
esac
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
ref: ${{ steps.tag.outputs.tag }}
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Validate release tag
run: |
version="$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')"
expected="v$version"
actual="${{ steps.tag.outputs.tag }}"
if [ "$actual" != "$expected" ]; then
echo "Tag/version mismatch: tag=$actual Cargo.toml=$version" >&2
exit 1
fi
# Optional: import the stable "Oxide Dev" signing identity so OTA updates
# don't re-trigger macOS Allow prompts. Set repo secrets OXIDE_CERT_P12
# (base64 of the .p12) and OXIDE_CERT_PASSWORD. Without them the build is
# ad-hoc signed (still works; just prompts on each update).
- name: Import signing certificate
id: signing
timeout-minutes: 3
env:
OXIDE_CERT_P12: ${{ secrets.OXIDE_CERT_P12 }}
OXIDE_CERT_PASSWORD: ${{ secrets.OXIDE_CERT_PASSWORD }}
run: |
if [ -z "$OXIDE_CERT_P12" ]; then
echo "::warning title=Unsigned release::Missing OXIDE_CERT_P12; this build will be ad-hoc signed and macOS may re-ask volume permissions after updates."
echo "available=false" >> "$GITHUB_OUTPUT"
exit 0
fi
KEYCHAIN="$RUNNER_TEMP/build.keychain"
PW="ci-$RANDOM"
security create-keychain -p "$PW" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security unlock-keychain -p "$PW" "$KEYCHAIN"
echo "$OXIDE_CERT_P12" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN" -P "$OXIDE_CERT_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$PW" "$KEYCHAIN" >/dev/null
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | sed s/\"//g)
CERT_PEM="$RUNNER_TEMP/signing-cert.pem"
security find-certificate -c "Oxide Dev" -p "$KEYCHAIN" > "$CERT_PEM" || true
if [ ! -s "$CERT_PEM" ]; then
security find-certificate -a -p "$KEYCHAIN" > "$CERT_PEM" || true
fi
if [ -s "$CERT_PEM" ]; then
python3 - "$KEYCHAIN" "$CERT_PEM" <<'PY'
import subprocess
import sys
commands = [
[
"sudo",
"-n",
"security",
"add-trusted-cert",
"-d",
"-r",
"trustRoot",
"-p",
"codeSign",
"-k",
"/Library/Keychains/System.keychain",
sys.argv[2],
],
[
"security",
"add-trusted-cert",
"-r",
"trustRoot",
"-p",
"codeSign",
"-k",
sys.argv[1],
sys.argv[2],
],
]
trusted = False
timed_out = False
for command in commands:
try:
result = subprocess.run(command, check=False, timeout=30)
except subprocess.TimeoutExpired:
timed_out = True
continue
if result.returncode == 0:
trusted = True
break
if not trusted and timed_out:
print("::warning title=Signing trust timed out::Timed out while trusting the imported signing certificate; release will fall back to ad-hoc signing if no valid identity is available.")
elif not trusted:
print("::warning title=Signing trust failed::Unable to trust the imported signing certificate; release will fall back to ad-hoc signing if no valid identity is available.")
PY
fi
security find-identity -v -p codesigning "$KEYCHAIN"
identity="$(security find-identity -v -p codesigning "$KEYCHAIN" | awk -F '\"' '/\"/ { print $2; exit }')"
if [ -z "$identity" ]; then
identity="$(security find-identity -v -p codesigning | awk -F '\"' '/\"/ { print $2; exit }')"
fi
rm -f "$RUNNER_TEMP/cert.p12"
if [ -z "$identity" ]; then
echo "::warning title=Unsigned release::Signing certificate was imported but no valid codesigning identity was found; this build will be ad-hoc signed."
echo "available=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "identity=$identity" >> "$GITHUB_OUTPUT"
echo "available=true" >> "$GITHUB_OUTPUT"
- name: Install dmg background deps (Pillow)
# Optional — dmg-bg.py skips the styled background gracefully if absent.
# Newer macOS runner images dropped Pillow from the default python3, which
# is why v0.0.104's dmg step failed; install it (best-effort) to restore
# the styled install window.
run: pip3 install --quiet Pillow || pip3 install --quiet --break-system-packages Pillow || true
- name: Build dmg
run: |
if [ "${{ steps.signing.outputs.available }}" = "true" ]; then
export OXIDE_REQUIRE_SIGNING=1
export OXIDE_SIGN_IDENTITY="${{ steps.signing.outputs.identity }}"
fi
bash scripts/make-dmg.sh
- name: Notarize and staple dmg
env:
APPLE_NOTARY_ID: ${{ secrets.APPLE_NOTARY_ID }}
APPLE_NOTARY_TEAM_ID: ${{ secrets.APPLE_NOTARY_TEAM_ID }}
APPLE_NOTARY_PASSWORD: ${{ secrets.APPLE_NOTARY_PASSWORD }}
run: |
if [ -z "$APPLE_NOTARY_ID" ] || [ -z "$APPLE_NOTARY_TEAM_ID" ] || [ -z "$APPLE_NOTARY_PASSWORD" ]; then
echo "::warning title=Notarization skipped::Configure APPLE_NOTARY_ID, APPLE_NOTARY_TEAM_ID, and APPLE_NOTARY_PASSWORD to notarize releases."
exit 0
fi
if [ "${{ steps.signing.outputs.available }}" != "true" ]; then
echo "Notarization credentials exist but the build has no signing identity." >&2
exit 1
fi
xcrun notarytool submit dist/Oxide.dmg \
--apple-id "$APPLE_NOTARY_ID" \
--team-id "$APPLE_NOTARY_TEAM_ID" \
--password "$APPLE_NOTARY_PASSWORD" \
--wait
xcrun stapler staple dist/Oxide.dmg
xcrun stapler validate dist/Oxide.dmg
- name: Package OTA binaries (gzip)
run: |
gzip -9 -c target/release/oxide > oxide-macos-arm64.gz
gzip -9 -c target/release/oxide-term > oxide-term-macos-arm64.gz
- name: Generate asset checksums
run: |
shasum -a 256 dist/Oxide.dmg > dist/Oxide.dmg.sha256
shasum -a 256 oxide-macos-arm64.gz > oxide-macos-arm64.gz.sha256
shasum -a 256 oxide-term-macos-arm64.gz > oxide-term-macos-arm64.gz.sha256
- name: Verify packaged release assets
run: |
shasum -a 256 -c dist/Oxide.dmg.sha256
shasum -a 256 -c oxide-macos-arm64.gz.sha256
shasum -a 256 -c oxide-term-macos-arm64.gz.sha256
gzip -t oxide-macos-arm64.gz
gzip -t oxide-term-macos-arm64.gz
hdiutil verify dist/Oxide.dmg
- name: Upload release assets artifact
uses: actions/upload-artifact@v4
with:
name: release-assets
if-no-files-found: error
retention-days: 3
path: |
dist/Oxide.dmg
dist/Oxide.dmg.sha256
oxide-macos-arm64.gz
oxide-macos-arm64.gz.sha256
oxide-term-macos-arm64.gz
oxide-term-macos-arm64.gz.sha256
publish:
needs: [gates, build]
runs-on: ubuntu-latest
steps:
- name: Resolve tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
tag="${{ inputs.tag }}"
else
tag="$GITHUB_REF_NAME"
fi
case "$tag" in
v*) ;;
*) echo "Release tag must start with v: $tag" >&2; exit 1 ;;
esac
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
ref: ${{ steps.tag.outputs.tag }}
fetch-depth: 0
- name: Resolve previous release tag
id: previous_tag
run: |
tag="${{ steps.tag.outputs.tag }}"
previous="$(git describe --tags --abbrev=0 "$tag^" 2>/dev/null || true)"
if [ -n "$previous" ]; then
echo "tag=$previous" >> "$GITHUB_OUTPUT"
echo "Previous release tag: $previous"
else
echo "No previous release tag found; GitHub will choose the release-notes base."
fi
- name: Download release assets artifact
uses: actions/download-artifact@v4
with:
name: release-assets
- name: Generate release notes
env:
TAG: ${{ steps.tag.outputs.tag }}
PREVIOUS_TAG: ${{ steps.previous_tag.outputs.tag }}
run: |
notes="$RUNNER_TEMP/release-notes.md"
release_utc="$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
release_local="$(TZ=Asia/Makassar date '+%Y-%m-%d %H:%M:%S WITA')"
run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
short_sha="$(git rev-parse --short "${TAG}^{commit}")"
full_sha="$(git rev-parse "${TAG}^{commit}")"
{
echo "Built by GitHub Actions on ${release_local} (${release_utc})."
echo
echo "## Build"
echo "- Workflow run: [${GITHUB_RUN_ID}](${run_url})"
echo "- Commit: \`${short_sha}\`"
echo "- Full SHA: \`${full_sha}\`"
echo
echo "## Changes"
if [ -n "$PREVIOUS_TAG" ]; then
changes="$(git log --pretty=format:'- %s (`%h`)' --no-merges "${PREVIOUS_TAG}..${TAG}" | sed '/^- Release v[0-9][0-9.]* (`[0-9a-f]*`)$/d' || true)"
if [ -n "$changes" ]; then
echo "$changes"
else
echo "- Release packaging and version metadata for ${TAG}."
fi
echo
echo "Changed files:"
git diff --name-only "${PREVIOUS_TAG}..${TAG}" | sed 's/^/- `/' | sed 's/$/`/'
echo
echo "**Full Changelog**: https://github.com/${GITHUB_REPOSITORY}/compare/${PREVIOUS_TAG}...${TAG}"
else
echo "- Initial release notes base was not available."
fi
echo
echo "## Install"
echo "Open the dmg, drag Oxide to Applications, then run:"
echo
echo '```sh'
echo 'xattr -dr com.apple.quarantine /Applications/Oxide.app'
echo '```'
} > "$notes"
cat "$notes" >> "$GITHUB_STEP_SUMMARY"
- name: Attach assets to the release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: ${{ steps.tag.outputs.tag }}
body_path: ${{ runner.temp }}/release-notes.md
files: |
dist/Oxide.dmg
dist/Oxide.dmg.sha256
oxide-macos-arm64.gz
oxide-macos-arm64.gz.sha256
oxide-term-macos-arm64.gz
oxide-term-macos-arm64.gz.sha256
fail_on_unmatched_files: true