diff --git a/.github/workflows/build-unified.yml b/.github/workflows/build-unified.yml index c83a73190df..72c6c732ae1 100644 --- a/.github/workflows/build-unified.yml +++ b/.github/workflows/build-unified.yml @@ -54,7 +54,9 @@ jobs: id: zizmor uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 with: - inputs: .github/workflows/build-unified.yml + inputs: | + .github/workflows/build-unified.yml + .github/workflows/distribute-dev-firebase.yml version: 1.25.2 persona: pedantic min-severity: informational diff --git a/.github/workflows/distribute-dev-firebase.yml b/.github/workflows/distribute-dev-firebase.yml new file mode 100644 index 00000000000..03354d9b3ad --- /dev/null +++ b/.github/workflows/distribute-dev-firebase.yml @@ -0,0 +1,117 @@ +name: "Distribute Dev Build" + +on: + workflow_dispatch: + inputs: + pull-request: + description: "PR number whose existing DevDebug artifact should be uploaded, i.e. 123; leave empty to build the selected branch" + required: false + type: number + release-notes: + description: "Help testers know what to test in this build, i.e. refactor navigation, meetings M1, etc." + required: true + type: string + +permissions: + actions: read # required to find and download DevDebug artifacts from PR build runs + contents: read # required to check out the selected branch + pull-requests: read # required to resolve the PR head commit + +concurrency: + group: firebase-app-distribution-dev-${{ inputs.pull-request || github.ref }} + cancel-in-progress: false + +jobs: + distribute: + name: Distribute DevDebug + runs-on: warp-ubuntu-2404-x64-8x + timeout-minutes: 45 + steps: + - name: Checkout selected branch + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + submodules: recursive + fetch-depth: 1 + + - name: Set up JDK 21 + uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0 + with: + java-version: "21" + distribution: temurin + + - name: Restore Gradle cache + uses: WarpBuilds/cache@40f3443ae7b70e568d6e2070ea897f3df94d7553 # v1 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + + - name: Set up Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version: 24 + + - name: Resolve DevDebug APK + id: apk + env: + GH_TOKEN: ${{ github.token }} + PULL_REQUEST: ${{ inputs.pull-request }} + DATADOG_APP_ID: ${{ secrets.DATADOG_APP_ID }} + DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }} + DOMAIN_REMOVAL_KEYS_FOR_REPAIR: ${{ secrets.DOMAIN_REMOVAL_KEYS_FOR_REPAIR }} + run: | + find_dev_debug_apk() { + local search_root="$1" + local -a apks + mapfile -t apks < <(find "${search_root}" -type f -path '*/apk/dev/debug/*.apk' -print) + (( ${#apks[@]} == 1 )) || { + echo "Expected exactly one DevDebug APK in ${search_root}, found ${#apks[@]}" + return 1 + } + printf '%s\n' "${apks[0]}" + } + + if [[ -n "${PULL_REQUEST}" ]]; then + [[ "${PULL_REQUEST}" =~ ^[0-9]+$ ]] || { echo "pull-request must be a number"; exit 1; } + + head_sha="$(gh pr view "${PULL_REQUEST}" --repo "${GITHUB_REPOSITORY}" --json headRefOid --jq .headRefOid)" + source_run_id="$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/build-develop-pr.yml/runs?event=pull_request&head_sha=${head_sha}&status=success&per_page=1" --jq '.workflow_runs[0].id // empty')" + [[ -n "${source_run_id}" ]] || { echo "No successful Develop PR build found for PR #${PULL_REQUEST}"; exit 1; } + + gh run download "${source_run_id}" --repo "${GITHUB_REPOSITORY}" --name Dev-Debug-artifacts --dir downloaded-artifacts + apk_path="$(find_dev_debug_apk downloaded-artifacts)" + else + ./gradlew :app:assembleDevDebug + apk_path="$(find_dev_debug_apk app/build/outputs)" + fi + + [[ -n "${apk_path}" && -f "${apk_path}" ]] || { echo "DevDebug APK was not found"; exit 1; } + echo "path=${apk_path}" >> "${GITHUB_OUTPUT}" + + - name: Upload to Firebase App Distribution + env: + FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON: ${{ secrets.FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON }} + FIREBASE_APP_DISTRIBUTION_GROUPS: ${{ vars.FIREBASE_APP_DISTRIBUTION_GROUPS }} + APK_PATH: ${{ steps.apk.outputs.path }} + RELEASE_NOTES: ${{ inputs.release-notes }} + run: | + : "${FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON:?Configure the FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON repository secret}" + + credentials_file="$(mktemp "${RUNNER_TEMP}/firebase-app-distribution.XXXXXX.json")" + trap 'rm -f "${credentials_file}"' EXIT + printf '%s' "${FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON}" > "${credentials_file}" + chmod 600 "${credentials_file}" + + firebase_app_id="$(jq -er '.flavors.dev.firebase_app_id | strings | select(length > 0)' default.json)" + upload_args=(appdistribution:distribute "${APK_PATH}" "--app=${firebase_app_id}" "--release-notes=${RELEASE_NOTES}" "--non-interactive") + if [[ -n "${FIREBASE_APP_DISTRIBUTION_GROUPS}" ]]; then + upload_args+=("--groups=${FIREBASE_APP_DISTRIBUTION_GROUPS}") + fi + npx --yes firebase-tools@15.27.0 "${upload_args[@]}" diff --git a/default.json b/default.json index 93ca43be74a..c82d7228cb6 100644 --- a/default.json +++ b/default.json @@ -14,7 +14,7 @@ "link_preview_enabled": true }, "dev": { - "application_id": "com.waz.zclient.dev", + "application_id": "com.wire.android.dev", "developer_features_enabled": true, "logging_enabled": true, "application_is_private_build": true, diff --git a/docs/adr/0014-firebase-app-distribution-for-dev-builds.md b/docs/adr/0014-firebase-app-distribution-for-dev-builds.md new file mode 100644 index 00000000000..faeead17048 --- /dev/null +++ b/docs/adr/0014-firebase-app-distribution-for-dev-builds.md @@ -0,0 +1,34 @@ +# 14. Firebase App Distribution for Dev builds + +Date: 2026-08-20 + +## Status + +Accepted + +## Context + +We need to distribute DevDebug APKs to testers without adding App Distribution uploads to the +shared build workflow. Firebase App Distribution requires the APK package name to match the +selected Firebase Android app. + +## Decision + +Use the Firebase CLI from the distribution workflow. CI writes +`FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON` to a temporary credentials file and passes it as +Application Default Credentials. Local builds do not include App Distribution tooling or tasks. + +Use a manual `Distribute Dev Build` workflow for uploads. The workflow accepts a PR number to reuse +the existing DevDebug artifact from its successful Develop PR build. Without a PR number, it builds +DevDebug from the branch selected when the workflow is dispatched. + +Dev's base application ID is `com.wire.android.dev`, so DevDebug uses +`com.wire.android.dev.debug`, matching the Firebase Android app used for distribution. + +## Consequences + +- App Distribution is opt-in and does not alter the shared build workflow. +- The Dev package-ID change does not change `firebase_app_id`, `firebase_push_sender_id`, or the + Firebase project used for push notifications. +- Existing `com.waz.zclient.dev.debug` installations do not update in place and should be treated + as ephemeral Dev installations. diff --git a/tests/testsSupport/src/androidTest/kotlin/com/wire/android/tests/support/UiAutomatorSetup.kt b/tests/testsSupport/src/androidTest/kotlin/com/wire/android/tests/support/UiAutomatorSetup.kt index 411d207c4f2..f180f10ab59 100644 --- a/tests/testsSupport/src/androidTest/kotlin/com/wire/android/tests/support/UiAutomatorSetup.kt +++ b/tests/testsSupport/src/androidTest/kotlin/com/wire/android/tests/support/UiAutomatorSetup.kt @@ -38,7 +38,7 @@ const val TIMEOUT_IN_MILLISECONDS = 20_000L object UiAutomatorSetup { - const val APP_DEV: String = "com.waz.zclient.dev.debug" + const val APP_DEV: String = "com.wire.android.dev.debug" const val APP_STAGING: String = "com.waz.zclient.dev" const val APP_BETA: String = "com.wire.android.internal" const val APP_PROD: String = "com.wire"