426 lines
18 KiB
YAML
426 lines
18 KiB
YAML
name: Build and Release XWorkmate Packages
|
|
|
|
env:
|
|
VAULT_ADDR: https://vault.svc.plus
|
|
FLUTTER_VERSION: 3.41.4
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- "release/**"
|
|
tags:
|
|
- "v*"
|
|
paths-ignore:
|
|
- "README.md"
|
|
pull_request:
|
|
paths:
|
|
- "lib/**"
|
|
- "assets/**"
|
|
- "android/**"
|
|
- "ios/**"
|
|
- "macos/**"
|
|
- "linux/**"
|
|
- "windows/**"
|
|
- "rust/**"
|
|
- "test/**"
|
|
- "scripts/**"
|
|
- "pubspec.*"
|
|
- "Makefile"
|
|
- ".github/actions/setup-flutter-sdk/action.yml"
|
|
- ".github/workflows/build-and-release.yml"
|
|
workflow_dispatch:
|
|
inputs:
|
|
enable_github_release:
|
|
description: "Upload assets to GitHub Release"
|
|
type: boolean
|
|
default: true
|
|
enable_testflight:
|
|
description: "Build & upload TestFlight (macOS/iOS App Store) artifacts"
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
|
|
concurrency:
|
|
group: build-and-release-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
prepare:
|
|
if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.base_ref == 'main') }}
|
|
runs-on: ubuntu-22.04
|
|
needs:
|
|
- verify
|
|
permissions:
|
|
contents: write
|
|
outputs:
|
|
should_release: ${{ steps.flags.outputs.should_release }}
|
|
testflight_enabled: ${{ steps.flags.outputs.testflight_enabled }}
|
|
github_release_enabled: ${{ steps.flags.outputs.github_release_enabled }}
|
|
release_tag: ${{ steps.meta.outputs.release_tag }}
|
|
release_title: ${{ steps.meta.outputs.release_title }}
|
|
release_notes: ${{ steps.meta.outputs.release_notes }}
|
|
steps:
|
|
- name: Checkout source
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Determine release mode
|
|
id: flags
|
|
shell: bash
|
|
env:
|
|
ENABLE_TESTFLIGHT_INPUT: ${{ github.event.inputs.enable_testflight }}
|
|
ENABLE_TESTFLIGHT_VAR: ${{ vars.ENABLE_TESTFLIGHT }}
|
|
ENABLE_GITHUB_RELEASE_INPUT: ${{ github.event.inputs.enable_github_release }}
|
|
run: |
|
|
if [[ "${GITHUB_REF:-}" == refs/tags/v* || "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" || "${GITHUB_REF:-}" == "refs/heads/main" ]]; then
|
|
echo "should_release=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "should_release=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
# TestFlight is opt-in (default OFF). Enabled only when explicitly
|
|
# requested via the workflow_dispatch input or the ENABLE_TESTFLIGHT
|
|
# repo/org variable. Keeps missing Apple signing secrets from failing
|
|
# the normal DMG/IPA release path.
|
|
if [[ "${ENABLE_TESTFLIGHT_INPUT:-}" == "true" || "${ENABLE_TESTFLIGHT_VAR:-}" == "true" ]]; then
|
|
echo "testflight_enabled=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "testflight_enabled=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
if [[ "${GITHUB_EVENT_NAME:-}" == "workflow_dispatch" && "${ENABLE_GITHUB_RELEASE_INPUT:-}" == "false" ]]; then
|
|
echo "github_release_enabled=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "github_release_enabled=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Compute release metadata
|
|
id: meta
|
|
shell: bash
|
|
run: bash ./scripts/ci/compute_release_metadata.sh
|
|
|
|
verify:
|
|
runs-on: ubuntu-22.04
|
|
steps:
|
|
- name: Checkout source
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Set up Flutter SDK
|
|
uses: ./.github/actions/setup-flutter-sdk
|
|
with:
|
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
|
|
- name: Install Linux dependencies
|
|
shell: bash
|
|
run: bash ./scripts/ci/setup_platform_deps.sh linux
|
|
|
|
- name: Run Flutter verification suite
|
|
shell: bash
|
|
run: bash ./scripts/ci/run_flutter_ci_suite.sh
|
|
|
|
build:
|
|
if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.base_ref == 'main') }}
|
|
name: Build ${{ matrix.platform }} ${{ matrix.package }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: linux
|
|
arch: amd64
|
|
package: deb-rpm
|
|
runs_on: ubuntu-22.04
|
|
artifact_name: build-linux-amd64-deb-rpm
|
|
artifact_paths: |
|
|
dist/linux/*.deb
|
|
dist/linux/*.rpm
|
|
- platform: windows
|
|
arch: amd64
|
|
package: msi
|
|
runs_on: windows-2022
|
|
artifact_name: build-windows-amd64-msi
|
|
artifact_paths: |
|
|
dist/windows/*.msi
|
|
dist/windows/*.zip
|
|
- platform: macos
|
|
arch: arm64
|
|
package: dmg
|
|
runs_on: macos-14
|
|
artifact_name: build-macos-arm64-dmg
|
|
artifact_paths: |
|
|
dist/macos/*.dmg
|
|
- platform: macos
|
|
arch: arm64
|
|
package: app-store-pkg
|
|
# Quoted so the `matrix.release_only != 'true'` guard compares
|
|
# string-to-string. A YAML boolean here coerces to a number in
|
|
# GitHub expressions (true -> 1, 'true' -> NaN), making the guard
|
|
# always true and building this release-only leg on every PR.
|
|
release_only: "true"
|
|
runs_on: macos-14
|
|
artifact_name: build-macos-arm64-pkg
|
|
artifact_paths: |
|
|
dist/macos-app-store/*.pkg
|
|
- platform: ios
|
|
arch: arm64
|
|
package: ipa
|
|
runs_on: macos-14
|
|
artifact_name: build-ios-arm64-ipa
|
|
artifact_paths: |
|
|
dist/ios/*.ipa
|
|
dist/ios/*.zip
|
|
- platform: android
|
|
arch: arm64
|
|
package: apk
|
|
runs_on: ubuntu-22.04
|
|
artifact_name: build-android-arm64-apk
|
|
artifact_paths: |
|
|
dist/android/*.apk
|
|
runs-on: ${{ matrix.runs_on }}
|
|
needs:
|
|
- prepare
|
|
env:
|
|
PLATFORM: ${{ matrix.platform }}
|
|
ARCH: ${{ matrix.arch }}
|
|
SHOULD_RELEASE: ${{ needs.prepare.outputs.should_release }}
|
|
steps:
|
|
- name: Checkout source
|
|
uses: actions/checkout@v7
|
|
|
|
# Secrets are loaded per-platform so a missing/extra field for one OS
|
|
# family never fails the matrix legs of the others (vault-action's
|
|
# ignoreNotFound does NOT suppress field-level "No match data" errors).
|
|
- name: Load Vault secrets (Apple)
|
|
id: vault_apple
|
|
if: ${{ (matrix.platform == 'macos' || matrix.platform == 'ios') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
|
uses: hashicorp/vault-action@v4
|
|
with:
|
|
url: ${{ env.VAULT_ADDR }}
|
|
method: jwt
|
|
role: github-actions-xworkmate-app
|
|
jwtGithubAudience: vault
|
|
ignoreNotFound: true
|
|
secrets: |
|
|
kv/data/github-actions/xworkmate-app XWORKMATE_SIGN_IDENTITY | XWORKMATE_SIGN_IDENTITY ;
|
|
kv/data/github-actions/xworkmate-app APPLE_CERT_P12_BASE64 | APPLE_CERT_P12_BASE64 ;
|
|
kv/data/github-actions/xworkmate-app APPLE_CERT_PASSWORD | APPLE_CERT_PASSWORD ;
|
|
kv/data/github-actions/xworkmate-app APPLE_PROVISION_PROFILE_BASE64 | APPLE_PROVISION_PROFILE_BASE64 ;
|
|
kv/data/github-actions/xworkmate-app APPLE_MAC_PROVISION_PROFILE_BASE64 | APPLE_MAC_PROVISION_PROFILE_BASE64 ;
|
|
kv/data/github-actions/xworkmate-app APPLE_KEYCHAIN_PASSWORD | APPLE_KEYCHAIN_PASSWORD ;
|
|
kv/data/github-actions/xworkmate-app APPLE_EXPORT_METHOD | APPLE_EXPORT_METHOD
|
|
|
|
- name: Load Vault secrets (Windows)
|
|
id: vault_windows
|
|
if: ${{ matrix.platform == 'windows' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
|
uses: hashicorp/vault-action@v4
|
|
with:
|
|
url: ${{ env.VAULT_ADDR }}
|
|
method: jwt
|
|
role: github-actions-xworkmate-app
|
|
jwtGithubAudience: vault
|
|
ignoreNotFound: true
|
|
secrets: |
|
|
kv/data/github-actions/xworkmate-app WINDOWS_PFX_BASE64 | WINDOWS_PFX_BASE64 ;
|
|
kv/data/github-actions/xworkmate-app WINDOWS_PFX_PASSWORD | WINDOWS_PFX_PASSWORD ;
|
|
kv/data/github-actions/xworkmate-app WINDOWS_CODESIGN_SUBJECT | WINDOWS_CODESIGN_SUBJECT
|
|
|
|
- name: Load Vault secrets (Android)
|
|
id: vault_android
|
|
if: ${{ matrix.platform == 'android' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
|
uses: hashicorp/vault-action@v4
|
|
with:
|
|
url: ${{ env.VAULT_ADDR }}
|
|
method: jwt
|
|
role: github-actions-xworkmate-app
|
|
jwtGithubAudience: vault
|
|
ignoreNotFound: true
|
|
secrets: |
|
|
kv/data/github-actions/xworkmate-app ANDROID_KEYSTORE_BASE64 | ANDROID_KEYSTORE_BASE64 ;
|
|
kv/data/github-actions/xworkmate-app ANDROID_KEYSTORE_PASSWORD | ANDROID_KEYSTORE_PASSWORD ;
|
|
kv/data/github-actions/xworkmate-app ANDROID_KEY_ALIAS | ANDROID_KEY_ALIAS ;
|
|
kv/data/github-actions/xworkmate-app ANDROID_KEY_PASSWORD | ANDROID_KEY_PASSWORD
|
|
|
|
- name: Export signing secrets
|
|
shell: bash
|
|
run: |
|
|
{
|
|
echo "XWORKMATE_SIGN_IDENTITY=${{ steps.vault_apple.outputs.XWORKMATE_SIGN_IDENTITY }}"
|
|
echo "APPLE_CERT_P12_BASE64=${{ steps.vault_apple.outputs.APPLE_CERT_P12_BASE64 }}"
|
|
echo "APPLE_CERT_PASSWORD=${{ steps.vault_apple.outputs.APPLE_CERT_PASSWORD }}"
|
|
echo "APPLE_PROVISION_PROFILE_BASE64=${{ steps.vault_apple.outputs.APPLE_PROVISION_PROFILE_BASE64 }}"
|
|
echo "APPLE_MAC_PROVISION_PROFILE_BASE64=${{ steps.vault_apple.outputs.APPLE_MAC_PROVISION_PROFILE_BASE64 }}"
|
|
echo "APPLE_KEYCHAIN_PASSWORD=${{ steps.vault_apple.outputs.APPLE_KEYCHAIN_PASSWORD }}"
|
|
echo "APPLE_EXPORT_METHOD=${{ steps.vault_apple.outputs.APPLE_EXPORT_METHOD }}"
|
|
echo "WINDOWS_PFX_BASE64=${{ steps.vault_windows.outputs.WINDOWS_PFX_BASE64 }}"
|
|
echo "WINDOWS_PFX_PASSWORD=${{ steps.vault_windows.outputs.WINDOWS_PFX_PASSWORD }}"
|
|
echo "WINDOWS_CODESIGN_SUBJECT=${{ steps.vault_windows.outputs.WINDOWS_CODESIGN_SUBJECT }}"
|
|
echo "ANDROID_KEYSTORE_BASE64=${{ steps.vault_android.outputs.ANDROID_KEYSTORE_BASE64 }}"
|
|
echo "ANDROID_KEYSTORE_PASSWORD=${{ steps.vault_android.outputs.ANDROID_KEYSTORE_PASSWORD }}"
|
|
echo "ANDROID_KEY_ALIAS=${{ steps.vault_android.outputs.ANDROID_KEY_ALIAS }}"
|
|
echo "ANDROID_KEY_PASSWORD=${{ steps.vault_android.outputs.ANDROID_KEY_PASSWORD }}"
|
|
} >> "$GITHUB_ENV"
|
|
|
|
- name: Set up Flutter SDK
|
|
uses: ./.github/actions/setup-flutter-sdk
|
|
with:
|
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
|
|
- name: Set up Java 17 for Android
|
|
if: ${{ matrix.platform == 'android' }}
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: temurin
|
|
java-version: "17"
|
|
|
|
- name: Preflight platform lane
|
|
id: preflight
|
|
shell: bash
|
|
run: bash ./scripts/ci/platform_preflight.sh "$PLATFORM" "$SHOULD_RELEASE"
|
|
|
|
- name: Install platform dependencies
|
|
if: ${{ steps.preflight.outputs.should_build_platform == 'true' }}
|
|
shell: bash
|
|
run: bash ./scripts/ci/setup_platform_deps.sh "$PLATFORM"
|
|
|
|
- name: Install Go
|
|
if: ${{ matrix.platform == 'macos' && steps.preflight.outputs.should_build_platform == 'true' }}
|
|
uses: actions/setup-go@v6
|
|
with:
|
|
go-version: "1.24.1"
|
|
|
|
- name: Build platform artifacts
|
|
if: ${{ steps.preflight.outputs.should_build_platform == 'true' && (matrix.release_only != 'true' || env.SHOULD_RELEASE == 'true') && (matrix.package != 'app-store-pkg' || needs.prepare.outputs.testflight_enabled == 'true') }}
|
|
shell: bash
|
|
run: bash ./scripts/ci/build_matrix_artifacts.sh "$PLATFORM" "$ARCH" "${{ matrix.package }}" "$SHOULD_RELEASE"
|
|
|
|
- name: Upload build artifacts
|
|
if: ${{ steps.preflight.outputs.should_build_platform == 'true' && (matrix.release_only != 'true' || env.SHOULD_RELEASE == 'true') && (matrix.package != 'app-store-pkg' || needs.prepare.outputs.testflight_enabled == 'true') }}
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: ${{ matrix.artifact_name }}
|
|
path: ${{ matrix.artifact_paths }}
|
|
if-no-files-found: error
|
|
|
|
remote_contract:
|
|
name: Test - remote provider contract
|
|
runs-on: ubuntu-22.04
|
|
needs:
|
|
- build
|
|
# Test-stage quality gate: runs between build and release.
|
|
# continue-on-error keeps it skippable so a failure never blocks release.
|
|
continue-on-error: true
|
|
if: ${{ github.event_name != 'push' && github.event_name != 'pull_request' }}
|
|
steps:
|
|
- name: Checkout source
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Load Vault secrets
|
|
id: vault
|
|
uses: hashicorp/vault-action@v4
|
|
with:
|
|
url: ${{ env.VAULT_ADDR }}
|
|
method: jwt
|
|
role: github-actions-xworkmate-app
|
|
jwtGithubAudience: vault
|
|
ignoreNotFound: true
|
|
secrets: |
|
|
kv/data/github-actions/xworkmate-app REVIEW_ACCOUNT_LOGIN_PASSWORD | REVIEW_ACCOUNT_LOGIN_PASSWORD
|
|
|
|
- name: Export remote contract secrets
|
|
run: echo "REVIEW_ACCOUNT_LOGIN_PASSWORD=${{ steps.vault.outputs.REVIEW_ACCOUNT_LOGIN_PASSWORD }}" >> "$GITHUB_ENV"
|
|
|
|
- name: Verify accounts to bridge provider contract
|
|
shell: bash
|
|
env:
|
|
REVIEW_ACCOUNT_BASE_URL: ${{ vars.REVIEW_ACCOUNT_BASE_URL }}
|
|
REVIEW_ACCOUNT_LOGIN_NAME: ${{ vars.REVIEW_ACCOUNT_LOGIN_NAME }}
|
|
run: bash ./scripts/ci/verify_remote_provider_contract.sh
|
|
|
|
release:
|
|
# always() so release waits for the remote_contract gate to finish but is
|
|
# never blocked by it being skipped (e.g. push events) or failing.
|
|
# build/prepare must still genuinely succeed.
|
|
if: ${{ always() && needs.prepare.outputs.should_release == 'true' && needs.prepare.result == 'success' && needs.build.result == 'success' }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- target: github_release
|
|
runs_on: ubuntu-22.04
|
|
- target: testflight_ios
|
|
runs_on: macos-14
|
|
artifact_name: build-ios-arm64-ipa
|
|
artifact_path: release-artifacts/build-ios-arm64-ipa
|
|
testflight_platform: ios
|
|
- target: testflight_macos
|
|
runs_on: macos-14
|
|
artifact_name: build-macos-arm64-pkg
|
|
artifact_path: release-artifacts/build-macos-arm64-pkg
|
|
testflight_platform: macos
|
|
runs-on: ${{ matrix.runs_on }}
|
|
permissions:
|
|
contents: write
|
|
needs:
|
|
- prepare
|
|
- build
|
|
- remote_contract
|
|
steps:
|
|
- name: Checkout source
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Load App Store Connect secrets
|
|
id: vault
|
|
if: ${{ matrix.target != 'github_release' && needs.prepare.outputs.testflight_enabled == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
|
uses: hashicorp/vault-action@v4
|
|
with:
|
|
url: ${{ env.VAULT_ADDR }}
|
|
method: jwt
|
|
role: github-actions-xworkmate-app
|
|
jwtGithubAudience: vault
|
|
ignoreNotFound: true
|
|
secrets: |
|
|
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_API_KEY_ID | APP_STORE_CONNECT_API_KEY_ID ;
|
|
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_ISSUER_ID | APP_STORE_CONNECT_ISSUER_ID ;
|
|
kv/data/github-actions/xworkmate-app APP_STORE_CONNECT_API_KEY_P8_BASE64 | APP_STORE_CONNECT_API_KEY_P8_BASE64
|
|
|
|
- name: Export App Store Connect secrets
|
|
if: ${{ matrix.target != 'github_release' && needs.prepare.outputs.testflight_enabled == 'true' }}
|
|
run: |
|
|
{
|
|
echo "APP_STORE_CONNECT_API_KEY_ID=${{ steps.vault.outputs.APP_STORE_CONNECT_API_KEY_ID }}"
|
|
echo "APP_STORE_CONNECT_ISSUER_ID=${{ steps.vault.outputs.APP_STORE_CONNECT_ISSUER_ID }}"
|
|
echo "APP_STORE_CONNECT_API_KEY_P8_BASE64=${{ steps.vault.outputs.APP_STORE_CONNECT_API_KEY_P8_BASE64 }}"
|
|
} >> "$GITHUB_ENV"
|
|
|
|
- name: Download all artifacts
|
|
if: ${{ matrix.target == 'github_release' && needs.prepare.outputs.github_release_enabled == 'true' }}
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
path: release-artifacts
|
|
|
|
- name: Upload assets to GitHub Release
|
|
if: ${{ matrix.target == 'github_release' && needs.prepare.outputs.github_release_enabled == 'true' }}
|
|
shell: bash
|
|
run: bash ./scripts/ci/github_release_upload.sh release-artifacts
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
|
|
RELEASE_TITLE: ${{ needs.prepare.outputs.release_title }}
|
|
RELEASE_NOTES: ${{ needs.prepare.outputs.release_notes }}
|
|
|
|
- name: Download TestFlight artifact
|
|
if: ${{ matrix.target != 'github_release' && needs.prepare.outputs.testflight_enabled == 'true' }}
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: ${{ matrix.artifact_name }}
|
|
path: ${{ matrix.artifact_path }}
|
|
|
|
- name: Upload to TestFlight
|
|
if: ${{ matrix.target != 'github_release' && needs.prepare.outputs.testflight_enabled == 'true' }}
|
|
shell: bash
|
|
run: bash ./scripts/ci/testflight_upload.sh "${{ matrix.testflight_platform }}" "${{ matrix.artifact_path }}"
|