-
Notifications
You must be signed in to change notification settings - Fork 91
308 lines (271 loc) · 11.7 KB
/
ios-testflight.yml
File metadata and controls
308 lines (271 loc) · 11.7 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
name: iOS TestFlight Release
on:
workflow_dispatch:
inputs:
beta_group_names:
description: "Comma-separated TestFlight beta groups"
required: false
default: "Internal Testers,External Testers"
type: string
wait_for_processing:
description: "Wait for ASC processing before finishing"
required: false
default: true
type: boolean
permissions:
contents: write
jobs:
prepare-release-assets:
runs-on: macos-26
timeout-minutes: 90
env:
HOMEBREW_NO_AUTO_UPDATE: "1"
SCCACHE_BUCKET: rust-cache
SCCACHE_ENDPOINT: ${{ secrets.SCCACHE_R2_ENDPOINT }}
SCCACHE_REGION: auto
SCCACHE_S3_USE_SSL: "true"
SCCACHE_S3_KEY_PREFIX: ci/ios-release
SCCACHE_LOG: info
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_R2_SECRET_ACCESS_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.3'
- name: Install build dependencies
run: brew install xcodegen meson ninja
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9
- name: Prime sccache
run: |
export SCCACHE_ERROR_LOG="$RUNNER_TEMP/sccache-ios.log"
rm -f "$SCCACHE_ERROR_LOG"
sccache --stop-server || true
sccache --start-server
sccache --show-stats || true
- name: Prepare iOS release assets
env:
CARGO_INCREMENTAL: "0"
CARGO_BUILD_JOBS: "1"
IOS_RUST_PROFILE: mobile-release
RUSTC_WRAPPER: sccache
run: |
set -euo pipefail
export SCCACHE_ERROR_LOG="$RUNNER_TEMP/sccache-ios.log"
trap 'status=$?; echo "==> sccache stats"; sccache --show-stats || true; if [ -f "$SCCACHE_ERROR_LOG" ]; then echo "==> sccache error log"; tail -200 "$SCCACHE_ERROR_LOG" || true; fi; exit $status' EXIT
sccache --zero-stats || true
./apps/ios/scripts/build-rust.sh --preserve-current --device-only
make ios-frameworks xcgen
echo "==> sccache stats after iOS release prep"
sccache --show-stats || true
- name: Package iOS release prep artifact
run: |
mkdir -p .ci-prep/ios
tar -czf .ci-prep/ios/ios-release-prep.tgz -C . \
apps/ios/Frameworks/ios_system \
apps/ios/GeneratedRust \
apps/ios/Sources/Litter/Bridge/UniFFICodexClient.generated.swift
- name: Upload iOS release prep artifact
uses: actions/upload-artifact@v4
with:
name: ios-release-prep
path: .ci-prep/ios
if-no-files-found: error
upload-testflight:
needs: prepare-release-assets
runs-on: macos-26
timeout-minutes: 150
environment: release
env:
HOMEBREW_NO_AUTO_UPDATE: "1"
SCCACHE_BUCKET: rust-cache
SCCACHE_ENDPOINT: ${{ secrets.SCCACHE_R2_ENDPOINT }}
SCCACHE_REGION: auto
SCCACHE_S3_USE_SSL: "true"
SCCACHE_S3_KEY_PREFIX: ci/ios-release
SCCACHE_LOG: info
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_R2_SECRET_ACCESS_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.3'
- name: Validate required secrets
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_PRIVATE_KEY_P8_B64: ${{ secrets.ASC_PRIVATE_KEY_P8_B64 }}
IOS_APP_STORE_APP_ID: ${{ secrets.IOS_APP_STORE_APP_ID }}
IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
IOS_DIST_CERT_P12_B64: ${{ secrets.IOS_DIST_CERT_P12_B64 }}
IOS_DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
IOS_APP_STORE_PROFILE_B64: ${{ secrets.IOS_APP_STORE_PROFILE_B64 }}
run: |
set -euo pipefail
required=(
ASC_KEY_ID
ASC_ISSUER_ID
ASC_PRIVATE_KEY_P8_B64
IOS_APP_STORE_APP_ID
IOS_TEAM_ID
IOS_DIST_CERT_P12_B64
IOS_DIST_CERT_PASSWORD
IOS_APP_STORE_PROFILE_B64
)
for name in "${required[@]}"; do
if [[ -z "${!name:-}" ]]; then
echo "Missing required secret: $name" >&2
exit 1
fi
done
- name: Install build dependencies
run: |
set -euo pipefail
brew install xcodegen jq
HOMEBREW_NO_AUTO_UPDATE=0 brew install asc
xcodebuild -version
asc --version
- name: Download iOS release prep artifact
uses: actions/download-artifact@v4
with:
name: ios-release-prep
path: .ci-prep/ios
- name: Restore iOS release prep
run: |
IOS_PREP_ROOT=.ci-prep/ios
if [ -d "$IOS_PREP_ROOT/ios" ]; then
IOS_PREP_ROOT="$IOS_PREP_ROOT/ios"
fi
tar -xzf "$IOS_PREP_ROOT/ios-release-prep.tgz" -C .
- name: Decode App Store Connect API key
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_PRIVATE_KEY_P8_B64: ${{ secrets.ASC_PRIVATE_KEY_P8_B64 }}
run: |
set -euo pipefail
ASC_KEY_PATH="$RUNNER_TEMP/AuthKey_${ASC_KEY_ID}.p8"
echo "$ASC_PRIVATE_KEY_P8_B64" | base64 --decode > "$ASC_KEY_PATH"
chmod 600 "$ASC_KEY_PATH"
echo "ASC_PRIVATE_KEY_PATH=$ASC_KEY_PATH" >> "$GITHUB_ENV"
- name: Install signing certificate and provisioning profiles
env:
IOS_DIST_CERT_P12_B64: ${{ secrets.IOS_DIST_CERT_P12_B64 }}
IOS_DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
IOS_APP_STORE_PROFILE_B64: ${{ secrets.IOS_APP_STORE_PROFILE_B64 }}
IOS_LIVE_ACTIVITY_APP_STORE_PROFILE_B64: ${{ secrets.IOS_LIVE_ACTIVITY_APP_STORE_PROFILE_B64 }}
run: |
set -euo pipefail
CERT_PATH="$RUNNER_TEMP/dist-cert.p12"
APP_PROFILE_PATH="$RUNNER_TEMP/app-store.mobileprovision"
LIVE_ACTIVITY_PROFILE_PATH="$RUNNER_TEMP/live-activity-app-store.mobileprovision"
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -base64 24)"
echo "$IOS_DIST_CERT_P12_B64" | base64 --decode > "$CERT_PATH"
echo "$IOS_APP_STORE_PROFILE_B64" | base64 --decode > "$APP_PROFILE_PATH"
echo "$IOS_LIVE_ACTIVITY_APP_STORE_PROFILE_B64" | base64 --decode > "$LIVE_ACTIVITY_PROFILE_PATH"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH"
security default-keychain -d user -s "$KEYCHAIN_PATH"
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$IOS_DIST_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
install_profile() {
local profile_path="$1"
local env_name="$2"
local profile_uuid profile_name
profile_uuid="$(security cms -D -i "$profile_path" | plutil -extract UUID raw -)"
profile_name="$(security cms -D -i "$profile_path" | plutil -extract Name raw -)"
cp "$profile_path" "$HOME/Library/MobileDevice/Provisioning Profiles/$profile_uuid.mobileprovision"
echo "$env_name=$profile_name" >> "$GITHUB_ENV"
}
install_profile "$APP_PROFILE_PATH" "APP_PROVISIONING_PROFILE_SPECIFIER"
install_profile "$LIVE_ACTIVITY_PROFILE_PATH" "LIVE_ACTIVITY_PROVISIONING_PROFILE_SPECIFIER"
- name: Resolve release inputs
run: |
set -euo pipefail
SCHEME="Litter"
APP_BUNDLE_ID="com.sigkitten.litter"
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
BETA_GROUP_NAMES="${{ inputs.beta_group_names }}"
if [[ "${{ inputs.wait_for_processing }}" == "true" ]]; then
WAIT_FOR_PROCESSING="1"
else
WAIT_FOR_PROCESSING="0"
fi
else
BETA_GROUP_NAMES="Internal Testers,External Testers"
WAIT_FOR_PROCESSING="0"
fi
echo "SCHEME=$SCHEME" >> "$GITHUB_ENV"
echo "APP_BUNDLE_ID=$APP_BUNDLE_ID" >> "$GITHUB_ENV"
echo "BETA_GROUP_NAMES=$BETA_GROUP_NAMES" >> "$GITHUB_ENV"
echo "WAIT_FOR_PROCESSING=$WAIT_FOR_PROCESSING" >> "$GITHUB_ENV"
- name: Build TestFlight IPA
env:
SCHEME: ${{ env.SCHEME }}
APP_BUNDLE_ID: ${{ env.APP_BUNDLE_ID }}
LIVE_ACTIVITY_BUNDLE_ID: com.sigkitten.litter.liveactivity
APP_STORE_APP_ID: ${{ secrets.IOS_APP_STORE_APP_ID }}
TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
APP_PROVISIONING_PROFILE_SPECIFIER: ${{ env.APP_PROVISIONING_PROFILE_SPECIFIER }}
LIVE_ACTIVITY_PROVISIONING_PROFILE_SPECIFIER: ${{ env.LIVE_ACTIVITY_PROVISIONING_PROFILE_SPECIFIER }}
EXPORT_SIGNING_STYLE: manual
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_PRIVATE_KEY_PATH: ${{ env.ASC_PRIVATE_KEY_PATH }}
BETA_GROUP_NAMES: ${{ env.BETA_GROUP_NAMES }}
WAIT_FOR_PROCESSING: ${{ env.WAIT_FOR_PROCESSING }}
TESTFLIGHT_SKIP_UPLOAD: "1"
run: |
set -euo pipefail
./apps/ios/scripts/testflight-upload.sh
- name: Upload to TestFlight
env:
SCHEME: ${{ env.SCHEME }}
APP_BUNDLE_ID: ${{ env.APP_BUNDLE_ID }}
LIVE_ACTIVITY_BUNDLE_ID: com.sigkitten.litter.liveactivity
APP_STORE_APP_ID: ${{ secrets.IOS_APP_STORE_APP_ID }}
TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
APP_PROVISIONING_PROFILE_SPECIFIER: ${{ env.APP_PROVISIONING_PROFILE_SPECIFIER }}
LIVE_ACTIVITY_PROVISIONING_PROFILE_SPECIFIER: ${{ env.LIVE_ACTIVITY_PROVISIONING_PROFILE_SPECIFIER }}
EXPORT_SIGNING_STYLE: manual
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_PRIVATE_KEY_PATH: ${{ env.ASC_PRIVATE_KEY_PATH }}
BETA_GROUP_NAMES: ${{ env.BETA_GROUP_NAMES }}
WAIT_FOR_PROCESSING: ${{ env.WAIT_FOR_PROCESSING }}
TESTFLIGHT_SKIP_BUILD: "1"
run: |
set -euo pipefail
./apps/ios/scripts/testflight-upload.sh
- name: Commit TestFlight version bump
run: |
set -euo pipefail
if git diff --quiet -- apps/ios/project.yml docs/releases/testflight-whats-new.md; then
echo "No TestFlight version bump to commit."
exit 0
fi
version="$(awk -F'"' '/MARKETING_VERSION:/ {print $2; exit}' apps/ios/project.yml)"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add apps/ios/project.yml docs/releases/testflight-whats-new.md
git commit -m "ios: start TestFlight ${version} [skip mobile-release]"
git push origin "HEAD:${GITHUB_REF_NAME}"