-
Notifications
You must be signed in to change notification settings - Fork 1
205 lines (190 loc) · 9.38 KB
/
Copy pathrelease-apk.yml
File metadata and controls
205 lines (190 loc) · 9.38 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
name: Release Android (signed APK + AAB)
# When a GitHub Release is published, build SIGNED Android artifacts and attach
# them to the release:
# * a signed .apk — for direct sideloading / out-of-store distribution
# * a signed .aab — Android App Bundle, required for Google Play uploads
# Also runnable manually (uploads the artifacts to the workflow run only).
#
# REQUIRED REPOSITORY SECRETS (Settings -> Secrets and variables -> Actions):
# ANDROID_KEYSTORE_BASE64 base64-encoded release keystore (.jks/.keystore)
# ANDROID_KEYSTORE_PASSWORD the keystore (store) password
# ANDROID_KEY_ALIAS the alias of the signing key inside the keystore
# ANDROID_KEY_PASSWORD the password for that key alias
# See docs/release-signing.md for how to generate and upload the keystore.
on:
release:
types: [published]
workflow_dispatch:
permissions:
contents: write # required to upload assets onto the release
jobs:
build-android:
name: Build & attach signed Android artifacts
runs-on: ubuntu-latest
env:
# Path the keystore is decoded to at build time (runner-local, ephemeral).
KEYSTORE_PATH: ${{ github.workspace }}/release.keystore
steps:
- name: Checkout
uses: actions/checkout@v4
# .NET for Android (net10.0-android) requires JDK 17 specifically.
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
# global.json pins the SDK *feature band* (rollForward: latestPatch), not
# just the major version. A release must not silently jump onto a band the
# code has never been built against: when 10.0.400 first appeared, its
# baseline maui manifest (10.0.0) regressed XAML source generation and this
# workflow failed with MAUIX2000 while CI on 10.0.302 was green. Bump the
# band deliberately — see docs/store/release-checklist.md.
- name: Set up .NET (from global.json)
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Install MAUI Android workload
run: dotnet workload restore src/SharpClient.App/SharpClient.App.csproj
# Acquire any missing Android SDK platform/build-tools for the target API
# into the runner's pre-installed SDK (ANDROID_SDK_ROOT). Accepts licenses.
- name: Install Android SDK dependencies
run: >
dotnet build src/SharpClient.App/SharpClient.App.csproj
-f net10.0-android
-t:InstallAndroidDependencies
-p:AcceptAndroidSdkLicenses=True
-p:JavaSdkDirectory="$JAVA_HOME"
-p:AndroidSdkDirectory="$ANDROID_SDK_ROOT"
# Decode the base64 keystore secret into a real file. Fail loudly if the
# secret is missing/empty so a release never silently ships a debug build.
- name: Decode signing keystore
run: |
if [ -z "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" ]; then
echo "::error::ANDROID_KEYSTORE_BASE64 secret is not set — cannot produce a signed release." >&2
exit 1
fi
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > "$KEYSTORE_PATH"
echo "Decoded keystore to $KEYSTORE_PATH"
# Derive the Android versionName + versionCode from the release tag so the
# installed APK reports the same version the GitHub Release advertises.
# Without this, every build ships the csproj defaults (versionName 1.0,
# versionCode 1): Obtainium then shows "1.0" against a "v0.2" release, and
# Android refuses in-place updates because the versionCode never changes.
# versionName = tag with leading 'v', pre-release (-...) and build (+...) stripped
# versionCode = major*100000000 + minor*10000 + patch (monotonic with semver;
# room for minor/patch < 10000, major <= 20 to stay under Android's
# 2,100,000,000 versionCode cap). Fails fast on out-of-range/bad tags.
# Manual (workflow_dispatch) runs only upload to the run (never attached to a
# release), so they use a fixed 0.0.0-dev / versionCode 1 that can never overtake a
# real release and block its in-place update.
- name: Compute version from release tag
id: ver
run: |
if [ "${{ github.event_name }}" != "release" ]; then
echo "display=0.0.0-dev" >> "$GITHUB_OUTPUT"
echo "code=1" >> "$GITHUB_OUTPUT"
echo "Non-release build: versionName=0.0.0-dev versionCode=1"
exit 0
fi
VERSION="${{ github.event.release.tag_name }}"
VERSION="${VERSION#v}" # v0.2.1 -> 0.2.1
VERSION="${VERSION%%+*}" # 0.2.1+build -> 0.2.1 (strip build metadata)
VERSION="${VERSION%%-*}" # 0.2.1-rc1 -> 0.2.1 (strip pre-release)
IFS='.' read -r MAJ MIN PAT <<< "$VERSION"
MAJ=${MAJ:-0}; MIN=${MIN:-0}; PAT=${PAT:-0}
if ! [[ "$MAJ" =~ ^[0-9]+$ && "$MIN" =~ ^[0-9]+$ && "$PAT" =~ ^[0-9]+$ ]]; then
echo "::error::Release tag '${{ github.event.release.tag_name }}' is not a numeric semver (parsed major='$MAJ' minor='$MIN' patch='$PAT')." >&2
exit 1
fi
if [ "$MAJ" -gt 20 ] || [ "$MIN" -gt 9999 ] || [ "$PAT" -gt 9999 ]; then
echo "::error::Version $VERSION out of versionCode range (require major<=20, minor/patch<=9999)." >&2
exit 1
fi
CODE=$(( 10#$MAJ * 100000000 + 10#$MIN * 10000 + 10#$PAT ))
echo "display=$VERSION" >> "$GITHUB_OUTPUT"
echo "code=$CODE" >> "$GITHUB_OUTPUT"
echo "Resolved versionName=$VERSION versionCode=$CODE"
# Publish a SIGNED .apk (sideload). AndroidKeyStore=true + the signing
# properties make dotnet sign with our release keystore instead of the
# auto-generated debug key. AndroidPackageFormat=apk is the default but is
# set explicitly for clarity.
- name: Publish signed APK (Release)
run: >
dotnet publish src/SharpClient.App/SharpClient.App.csproj
-c Release
-f net10.0-android
-p:JavaSdkDirectory="$JAVA_HOME"
-p:AndroidSdkDirectory="$ANDROID_SDK_ROOT"
-p:AcceptAndroidSdkLicenses=True
-p:ApplicationDisplayVersion=${{ steps.ver.outputs.display }}
-p:ApplicationVersion=${{ steps.ver.outputs.code }}
-p:AndroidPackageFormat=apk
-p:AndroidKeyStore=true
-p:AndroidSigningKeyStore="$KEYSTORE_PATH"
-p:AndroidSigningStorePass='${{ secrets.ANDROID_KEYSTORE_PASSWORD }}'
-p:AndroidSigningKeyAlias='${{ secrets.ANDROID_KEY_ALIAS }}'
-p:AndroidSigningKeyPass='${{ secrets.ANDROID_KEY_PASSWORD }}'
# Publish a SIGNED .aab (App Bundle) for Google Play. Same signing inputs,
# AndroidPackageFormat=aab switches the packaging.
- name: Publish signed AAB (Release)
run: >
dotnet publish src/SharpClient.App/SharpClient.App.csproj
-c Release
-f net10.0-android
-p:JavaSdkDirectory="$JAVA_HOME"
-p:AndroidSdkDirectory="$ANDROID_SDK_ROOT"
-p:AcceptAndroidSdkLicenses=True
-p:ApplicationDisplayVersion=${{ steps.ver.outputs.display }}
-p:ApplicationVersion=${{ steps.ver.outputs.code }}
-p:AndroidPackageFormat=aab
-p:AndroidKeyStore=true
-p:AndroidSigningKeyStore="$KEYSTORE_PATH"
-p:AndroidSigningStorePass='${{ secrets.ANDROID_KEYSTORE_PASSWORD }}'
-p:AndroidSigningKeyAlias='${{ secrets.ANDROID_KEY_ALIAS }}'
-p:AndroidSigningKeyPass='${{ secrets.ANDROID_KEY_PASSWORD }}'
# Locate the produced artifacts. Prefer the *-Signed.apk emitted by the
# signing pipeline; fall back to any .apk. The .aab is always signed.
- name: Locate artifacts
id: artifacts
run: |
base="src/SharpClient.App/bin/Release/net10.0-android"
apk="$(find "$base" -name '*-Signed.apk' | head -n1)"
if [ -z "$apk" ]; then
apk="$(find "$base" -name '*.apk' | head -n1)"
fi
aab="$(find "$base" -name '*.aab' | head -n1)"
if [ -z "$apk" ]; then
echo "::error::No APK found under $base" >&2
find "$base" -name '*.apk' >&2 || true
exit 1
fi
if [ -z "$aab" ]; then
echo "::error::No AAB found under $base" >&2
find "$base" -name '*.aab' >&2 || true
exit 1
fi
echo "apk=$apk" >> "$GITHUB_OUTPUT"
echo "aab=$aab" >> "$GITHUB_OUTPUT"
echo "Found APK: $apk"
echo "Found AAB: $aab"
# Remove the decoded keystore from the runner as soon as the build is done.
- name: Remove decoded keystore
if: always()
run: rm -f "$KEYSTORE_PATH"
- name: Upload artifacts to the workflow run
uses: actions/upload-artifact@v4
with:
name: sharpclient-android
path: |
${{ steps.artifacts.outputs.apk }}
${{ steps.artifacts.outputs.aab }}
if-no-files-found: error
- name: Attach signed artifacts to the GitHub Release
if: github.event_name == 'release'
env:
GH_TOKEN: ${{ github.token }}
run: >
gh release upload "${{ github.event.release.tag_name }}"
"${{ steps.artifacts.outputs.apk }}"
"${{ steps.artifacts.outputs.aab }}"
--clobber