mobile: land handoff, logging, and CI updates #10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: read | ||
| jobs: | ||
| upload-testflight: | ||
| runs-on: macos-15 | ||
| timeout-minutes: 90 | ||
| 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 | ||
| SCCACHE_LOG: info | ||
| SCCACHE_ERROR_LOG: ${{ runner.temp }}/sccache-ios.log | ||
| 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: 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 asc meson ninja | ||
| xcodebuild -version | ||
| asc --version | ||
| - 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: | | ||
| rm -f "$SCCACHE_ERROR_LOG" | ||
| sccache --stop-server || true | ||
| sccache --start-server | ||
| sccache --show-stats || true | ||
| - 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 profile | ||
| 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 }} | ||
| run: | | ||
| set -euo pipefail | ||
| CERT_PATH="$RUNNER_TEMP/dist-cert.p12" | ||
| PROFILE_PATH="$RUNNER_TEMP/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 > "$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" | ||
| 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 "PROVISIONING_PROFILE_SPECIFIER=$PROFILE_NAME" >> "$GITHUB_ENV" | ||
| - name: Resolve release inputs | ||
| run: | | ||
| set -euo pipefail | ||
| SCHEME="Litter" | ||
| APP_BUNDLE_ID="com.sigkitten.litter" | ||
| MARKETING_VERSION="1.0.1" | ||
| 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="1" | ||
| fi | ||
| echo "SCHEME=$SCHEME" >> "$GITHUB_ENV" | ||
| echo "APP_BUNDLE_ID=$APP_BUNDLE_ID" >> "$GITHUB_ENV" | ||
| echo "MARKETING_VERSION=$MARKETING_VERSION" >> "$GITHUB_ENV" | ||
| echo "BETA_GROUP_NAMES=$BETA_GROUP_NAMES" >> "$GITHUB_ENV" | ||
| echo "WAIT_FOR_PROCESSING=$WAIT_FOR_PROCESSING" >> "$GITHUB_ENV" | ||
| - name: Upload to TestFlight | ||
| env: | ||
| CARGO_BUILD_JOBS: "2" | ||
| RUSTC_WRAPPER: sccache | ||
| SCHEME: ${{ env.SCHEME }} | ||
| APP_BUNDLE_ID: ${{ env.APP_BUNDLE_ID }} | ||
| APP_STORE_APP_ID: ${{ secrets.IOS_APP_STORE_APP_ID }} | ||
| TEAM_ID: ${{ secrets.IOS_TEAM_ID }} | ||
| PROVISIONING_PROFILE_SPECIFIER: ${{ env.PROVISIONING_PROFILE_SPECIFIER }} | ||
| ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} | ||
| ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} | ||
| ASC_PRIVATE_KEY_PATH: ${{ env.ASC_PRIVATE_KEY_PATH }} | ||
| MARKETING_VERSION: ${{ env.MARKETING_VERSION }} | ||
| BETA_GROUP_NAMES: ${{ env.BETA_GROUP_NAMES }} | ||
| WAIT_FOR_PROCESSING: ${{ env.WAIT_FOR_PROCESSING }} | ||
| run: | | ||
| set -euo pipefail | ||
| 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 | ||
| make testflight | ||