Repository navigation
125 lines (112 loc) · 4.85 KB
/
Copy pathrelease.yml
File metadata and controls
125 lines (112 loc) · 4.85 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
name: Release Publication
# This workflow used to contain a full copy of the Android build — SDK, NDK,
# cargo-ndk, the cross-compile and `assembleProdRelease` — which meant that
# publishing a release rebuilt an APK that the tag push had already built.
# Worse, the artefact users downloaded was not the one CI had checked; it was a
# second, separately produced APK that no job had verified.
#
# It now calls `build.yml`, which is the only place an APK is assembled, and
# `build.yml` no longer triggers on tags. A release-stamped APK is therefore
# built exactly once, here, and the native libraries behind it are a cache hit
# against the `android-release` entry the main-branch builds already wrote —
# because the `.so` files do not depend on the build type. Only the APK does.
on:
release:
types: [published]
concurrency:
group: release-${{ github.event.release.tag_name }}
cancel-in-progress: false
# Default deny at the workflow level; each job grants itself the minimum it
# needs. A job added later then starts with no token scopes rather than
# silently inheriting whatever the workflow happened to declare.
permissions: {}
jobs:
# A separate job so the tag is rejected before the build is paid for.
validate:
name: Validate release tag
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Check tag format
env:
TAG_NAME: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Release tag '$TAG_NAME' does not match v*.*.* (e.g. v1.2.3). Stable releases must use that form; the in-app update check parses it, and Versioning.resolveFromGit derives versionCode from it."
exit 1
fi
echo "Release tag '$TAG_NAME' is valid"
build:
name: Build
needs: validate
permissions:
contents: read
uses: ./.github/workflows/build.yml
with:
# The APK is a function of this. Without it the assembly would inherit
# the trigger-derived default and stamp a dev version onto a release.
build_type: release
publish:
name: Sign and upload
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 20
environment: ci:release
permissions:
# Upload the signed asset onto the release.
contents: write
steps:
# Only the composite action, and only from the release's own tag — never
# the full tree, and never a ref anyone without push access can move.
# This job holds the production keystore; it runs no project build code.
- name: Checkout signing action
uses: actions/checkout@v7
with:
sparse-checkout: .github/actions
sparse-checkout-cone-mode: false
persist-credentials: false
- name: Set up Android SDK
uses: android-actions/setup-android@v4
- name: Install Android build tools
run: sdkmanager "build-tools;37.0.0"
- name: Download unsigned release APK
uses: actions/download-artifact@v8
with:
name: ${{ needs.build.outputs.prod-release-artifact }}
path: ./artifacts
- name: Sign
id: sign
uses: ./.github/actions/sign-apk
with:
apk-directory: ./artifacts
keystore-base64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
keystore-password: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
key-alias: ${{ vars.ANDROID_KEY_ALIAS }}
expected-cert-sha256: ${{ vars.ANDROID_SIGNING_CERT_SHA256 }}
# The tag names a version; the APK reports one. If they disagree, the
# release was published from a commit the tag does not point at, and the
# asset would claim a version it is not.
- name: Check the APK matches the tag
env:
TAG_NAME: ${{ github.event.release.tag_name }}
VERSION_NAME: ${{ steps.sign.outputs.version-name }}
run: |
set -euo pipefail
if [[ "$VERSION_NAME" != "$TAG_NAME" ]]; then
echo "::error::The APK reports versionName '$VERSION_NAME' but the release is tagged '$TAG_NAME'. The release was most likely published from a commit that the tag does not point at."
exit 1
fi
echo "APK versionName matches the tag: $VERSION_NAME (code ${{ steps.sign.outputs.version-code }})"
# `letterbox.apk` is a constant, so
# `releases/latest/download/letterbox.apk` resolves for every stable
# release without a caller knowing the version. `letterbox.json` carries
# the version, versionCode and digests that the filename no longer states.
- name: Upload the signed APK to the release
uses: softprops/action-gh-release@v3
with:
files: |
${{ steps.sign.outputs.apk }}
${{ steps.sign.outputs.metadata }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}