ADFA-2602: Resolve buildscript classpaths from the on-device Maven re… #7829
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: Build and deploy to firebase app distribution | |
| permissions: | |
| id-token: write | |
| contents: read | |
| actions: write | |
| pull-requests: read | |
| on: | |
| push: | |
| branches-ignore: | |
| - main | |
| workflow_dispatch: { } | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| IDE_SIGNING_ALIAS: ${{ secrets.IDE_SIGNING_ALIAS }} | |
| IDE_SIGNING_AUTH_PASS: ${{ secrets.IDE_SIGNING_AUTH_PASS }} | |
| IDE_SIGNING_AUTH_USER: ${{ secrets.IDE_SIGNING_AUTH_USER }} | |
| IDE_SIGNING_KEY_PASS: ${{ secrets.IDE_SIGNING_KEY_PASS }} | |
| IDE_SIGNING_STORE_PASS: ${{ secrets.IDE_SIGNING_STORE_PASS }} | |
| IDE_SIGNING_URL: ${{ secrets.IDE_SIGNING_URL }} | |
| IDE_SIGNING_KEY_BIN: ${{ secrets.IDE_SIGNING_KEY_BIN }} | |
| ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MVN_USERNAME }} | |
| ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MVN_PASSWORD }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MVN_SIGNING_KEY }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MVN_SIGNING_KEY_ID }} | |
| ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MVN_SIGNING_KEY_PASSWORD }} | |
| FIREBASE_CONSOLE_URL: ${{ secrets.FIREBASE_CONSOLE_URL }} | |
| GLITCHTIP_DSN: ${{ secrets.GLITCHTIP_DSN }} | |
| jobs: | |
| check_changes: | |
| name: Check for meaningful changes | |
| runs-on: self-hosted | |
| outputs: | |
| must_build: ${{ steps.check.outputs.must_build }} | |
| steps: | |
| - name: Checkout PR branch | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.pull_request.head.ref }} | |
| fetch-depth: 0 | |
| - name: Check if new branch with no meaningful commits | |
| id: check | |
| run: | | |
| # Get the current branch name | |
| BRANCH_NAME=$(git branch --show-current) | |
| echo "Current branch: $BRANCH_NAME" | |
| # Check commit count for logging | |
| BRANCH_ONLY_COMMITS=$(git log --oneline origin/stage..HEAD 2>/dev/null | wc -l | tr -d ' ' || echo "0") | |
| echo "Commits ahead of stage: $BRANCH_ONLY_COMMITS" | |
| # For non-stage branches, check if they are identical to stage | |
| if [[ "$BRANCH_NAME" != "stage" ]]; then | |
| DIFF_COUNT=$(git diff --name-only origin/stage..HEAD | wc -l | tr -d ' ' || echo "0") | |
| echo "Files changed from stage: $DIFF_COUNT" | |
| if [[ "$DIFF_COUNT" -eq 0 ]]; then | |
| echo "::notice::Skipping build for branch identical to stage" | |
| echo "must_build=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Branch has changes, proceeding with build" | |
| echo "must_build=true" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "Stage branch detected, proceeding with build" | |
| echo "must_build=true" >> $GITHUB_OUTPUT | |
| fi | |
| validate_branch: | |
| name: Validate branch and Jira ticket | |
| runs-on: self-hosted | |
| outputs: | |
| branch_type: ${{ steps.classify.outputs.branch_type }} | |
| jira_ticket: ${{ steps.classify.outputs.jira_ticket }} | |
| steps: | |
| - name: Classify branch | |
| id: classify | |
| run: | | |
| BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} | |
| echo "Branch name: $BRANCH_NAME" | |
| JIRA_TICKET="" | |
| if [[ "$BRANCH_NAME" == community/* ]]; then | |
| BRANCH_TYPE="community" | |
| echo "Community contribution branch detected, Jira steps will be skipped" | |
| else | |
| JIRA_TICKET=$(echo "$BRANCH_NAME" | grep -o 'ADFA-[0-9]\+' | head -1) | |
| if [ -n "$JIRA_TICKET" ]; then | |
| BRANCH_TYPE="jira" | |
| echo "Jira branch detected with ticket $JIRA_TICKET" | |
| else | |
| BRANCH_TYPE="internal" | |
| echo "Internal branch detected, proceeding with current automation" | |
| fi | |
| fi | |
| echo "branch_type=$BRANCH_TYPE" >> $GITHUB_OUTPUT | |
| echo "jira_ticket=$JIRA_TICKET" >> $GITHUB_OUTPUT | |
| - name: Fail fast on nonexistent Jira ticket | |
| if: steps.classify.outputs.branch_type == 'jira' | |
| env: | |
| JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| JIRA_TICKET: ${{ steps.classify.outputs.jira_ticket }} | |
| run: | | |
| if [ -z "$JIRA_EMAIL" ] || [ -z "$JIRA_API_TOKEN" ]; then | |
| echo "::warning::Jira credentials are not configured; skipping validation of $JIRA_TICKET" | |
| exit 0 | |
| fi | |
| HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ | |
| -H "Accept: application/json" \ | |
| "https://appdevforall.atlassian.net/rest/api/3/issue/${JIRA_TICKET}?fields=summary") || HTTP_STATUS="000" | |
| if [ "$HTTP_STATUS" -eq 404 ]; then | |
| echo "::error::Jira ticket $JIRA_TICKET does not exist. Rename the branch to reference a real ticket, or use the community/ prefix for contributions without Jira access." | |
| exit 1 | |
| fi | |
| # Only a definitive 404 blocks the build; outages or auth issues must not | |
| if [ "$HTTP_STATUS" -ne 200 ]; then | |
| echo "::warning::Could not validate Jira ticket $JIRA_TICKET (HTTP $HTTP_STATUS); proceeding" | |
| exit 0 | |
| fi | |
| echo "Jira ticket $JIRA_TICKET exists" | |
| build_apk: | |
| name: Build Universal APK | |
| runs-on: self-hosted | |
| timeout-minutes: 60 | |
| needs: [ check_changes, validate_branch ] | |
| if: needs.check_changes.outputs.must_build == 'true' | |
| env: | |
| INCLUDE_LLAMA_ASSETS: "false" | |
| steps: | |
| - name: Cancel previous runs | |
| uses: styfle/cancel-workflow-action@0.12.1 | |
| with: | |
| access_token: ${{ github.token }} | |
| - name: Checkout PR branch | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.pull_request.head.ref }} | |
| fetch-depth: 0 | |
| - name: Validate branch name length | |
| run: | | |
| BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} | |
| BRANCH_LENGTH=${#BRANCH_NAME} | |
| echo "Branch name: $BRANCH_NAME" | |
| echo "Branch name length: $BRANCH_LENGTH characters" | |
| if [ $BRANCH_LENGTH -gt 80 ]; then | |
| echo "ERROR: Branch name exceeds the 80 character limit ($BRANCH_LENGTH characters)" | |
| echo "This causes Google Cloud authentication to fail due to subject mapping limits." | |
| echo "Please rename your branch to 80 characters or fewer and try again." | |
| exit 1 | |
| fi | |
| echo "Branch name length is valid ($BRANCH_LENGTH ≤ 80 characters)" | |
| - name: Check if Nix is installed | |
| id: check_nix | |
| run: | | |
| if command -v nix >/dev/null 2>&1; then | |
| echo "nix is installed" | |
| echo "nix_installed=true" >> $GITHUB_ENV | |
| else | |
| echo "nix is not installed" | |
| echo "nix_installed=false" >> $GITHUB_ENV | |
| fi | |
| - name: Install Flox | |
| if: env.nix_installed == 'false' | |
| uses: flox/install-flox-action@v2 | |
| - name: Create google-services.json | |
| env: | |
| GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| run: | | |
| echo "$GOOGLE_SERVICES_JSON" > app/google-services.json | |
| echo "google-services.json created successfully" | |
| - name: Verify plugin-api binary compatibility | |
| run: | | |
| flox activate -d flox/base -- ./gradlew :plugin-api:apiCheck --no-daemon | |
| # testDebugUnitTest, not testV8DebugUnitTest: :plugin-api declares no product flavors, so the | |
| # v8 task every other module has does not exist here. Named as its own step rather than left | |
| # to jacocoAggregateReport, which is not on the PR path at all. | |
| - name: Run plugin-api unit tests | |
| run: | | |
| flox activate -d flox/base -- ./gradlew :plugin-api:testDebugUnitTest --no-daemon | |
| # Combined with a compile task on purpose: spotlessCheck alone never pulls a | |
| # source-tree-writing task into the graph, so the implicit-dependency failure ADFA-5244 fixed | |
| # cannot reproduce in a standalone run. Without this step a regression of that exclude is | |
| # invisible to CI and shows up only on a developer's machine at push time. | |
| - name: Run spotlessCheck alongside a compile task | |
| run: | | |
| # Two tasks, so two possible causes: the message must not name one of them. Blaming | |
| # Spotless for a compile error is the exact misdiagnosis this step exists to catch. | |
| set +e | |
| flox activate -d flox/base -- ./gradlew :common:compileV8DebugKotlin spotlessCheck --no-daemon | |
| status=$? | |
| set -e | |
| if [ "$status" -ne 0 ]; then | |
| echo "::error::Gradle failed on ':common:compileV8DebugKotlin spotlessCheck'. If the output above shows formatting violations, run './gradlew spotlessApply' locally and commit the result; if it shows a compile error, spotlessApply will not help. The Gradle output says which." | |
| exit "$status" | |
| fi | |
| - name: Install advancecomp (advzip, used by recompressApk) | |
| run: | | |
| command -v advzip || { sudo apt-get update && sudo apt-get install -y advancecomp; } | |
| - name: Assemble Universal APK | |
| run: | | |
| echo "gradle_time_start=$(date +%s)" >> $GITHUB_ENV | |
| flox activate -d flox/base -- ./gradlew :app:assembleV8Debug --no-daemon | |
| echo "gradle_time_end=$(date +%s)" >> $GITHUB_ENV | |
| - name: Find APK file | |
| id: find_apk | |
| run: | | |
| apk_path=$(find app/build/outputs/apk/ -path "*v8*/debug/*.apk" | head -n 1) | |
| echo "APK_PATH=$apk_path" >> $GITHUB_OUTPUT | |
| - name: Set branch name and build type | |
| run: | | |
| BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} | |
| echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV | |
| if [[ "${BRANCH_NAME,,}" == "stage" ]]; then | |
| BUILD_TYPE="STAGE" | |
| elif [[ "$BRANCH_NAME" == community/* ]]; then | |
| BUILD_TYPE="COMMUNITY ($BRANCH_NAME)" | |
| else | |
| BUILD_TYPE="BRANCH ($BRANCH_NAME)" | |
| fi | |
| echo "BUILD_TYPE=$BUILD_TYPE" >> $GITHUB_ENV | |
| - name: Install jq | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y jq | |
| - name: Get PR and Commit Information | |
| id: pr_info | |
| run: | | |
| if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then | |
| EVENT_PATH="${GITHUB_EVENT_PATH}" | |
| COMMIT_MSG=$(jq -r '.pull_request.title' "$EVENT_PATH" | tr -d '\n\r' | sed 's/[*]/•/g') | |
| PR_AUTHOR=$(jq -r '.pull_request.user.login' "$EVENT_PATH") | |
| PR_URL=$(jq -r '.pull_request.html_url' "$EVENT_PATH") | |
| echo "COMMIT_MSG=$COMMIT_MSG" >> $GITHUB_OUTPUT | |
| echo "PR_AUTHOR=$PR_AUTHOR" >> $GITHUB_OUTPUT | |
| echo "PR_URL=$PR_URL" >> $GITHUB_OUTPUT | |
| else | |
| COMMIT_MSG=$(git log -1 --pretty=%B | head -1 | tr -d '\n\r' | sed 's/[*]/•/g') | |
| COMMIT_AUTHOR=$(git log -1 --pretty=%an) | |
| echo "COMMIT_MSG=$COMMIT_MSG" >> $GITHUB_OUTPUT | |
| echo "PR_AUTHOR=$COMMIT_AUTHOR" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Extract Jira Ticket and Fetch Title | |
| id: extract_jira | |
| env: | |
| COMMIT_MSG: ${{ steps.pr_info.outputs.COMMIT_MSG }} | |
| BRANCH_TYPE: ${{ needs.validate_branch.outputs.branch_type }} | |
| JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| SHA: ${{ github.sha }} | |
| REF: ${{ github.ref }} | |
| run: | | |
| if [[ "$BRANCH_TYPE" == "community" ]]; then | |
| echo "Community branch, skipping Jira ticket extraction" | |
| JIRA_TICKET="" | |
| else | |
| JIRA_TICKET=$(echo "$BRANCH_NAME" | grep -o 'ADFA-[0-9]\+' | head -1) | |
| # If no Jira ticket found in branch name, check the commit message | |
| if [ -z "$JIRA_TICKET" ]; then | |
| JIRA_TICKET=$(echo "$COMMIT_MSG" | grep -o 'ADFA-[0-9]\+' | head -1) | |
| fi | |
| fi | |
| if [ -n "$JIRA_TICKET" ]; then | |
| JIRA_URL="https://appdevforall.atlassian.net/browse/${JIRA_TICKET}" | |
| # Fetch Jira ticket title if credentials are available | |
| if [ -n "$JIRA_EMAIL" ] && [ -n "$JIRA_API_TOKEN" ]; then | |
| JIRA_TITLE=$(curl -s \ | |
| -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ | |
| -H "Accept: application/json" \ | |
| "https://appdevforall.atlassian.net/rest/api/3/issue/${JIRA_TICKET}?fields=summary" \ | |
| | jq -r '.fields.summary // ""' 2>/dev/null || echo "") | |
| # If unable to fetch title, default to commit message | |
| if [ -z "$JIRA_TITLE" ]; then | |
| JIRA_TITLE="$COMMIT_MSG" | |
| fi | |
| else | |
| JIRA_TITLE="$COMMIT_MSG" | |
| fi | |
| else | |
| JIRA_TICKET="N/A" | |
| JIRA_TITLE="$COMMIT_MSG" | |
| JIRA_URL="https://github.com/${REPO}/commit/${SHA} (Ref: ${REF})" | |
| fi | |
| echo "JIRA_TICKET=$JIRA_TICKET" >> $GITHUB_OUTPUT | |
| echo "JIRA_TITLE=$JIRA_TITLE" >> $GITHUB_OUTPUT | |
| echo "JIRA_URL=$JIRA_URL" >> $GITHUB_OUTPUT | |
| - name: Authenticate to Google Cloud via Workload Identity | |
| uses: google-github-actions/auth@v2 | |
| with: | |
| workload_identity_provider: ${{ secrets.WIF_PROVIDER }} | |
| service_account: ${{ secrets.IDENTITY_EMAIL }} | |
| - name: Verify APK exists | |
| run: | | |
| if [ ! -f "${{ steps.find_apk.outputs.APK_PATH }}" ]; then | |
| echo "ERROR: APK file not found at ${{ steps.find_apk.outputs.APK_PATH }}" | |
| exit 1 | |
| fi | |
| ls -la "${{ steps.find_apk.outputs.APK_PATH }}" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v6 | |
| - name: Write CI Performance Data | |
| env: | |
| APK_PATH: ${{ steps.find_apk.outputs.APK_PATH }} | |
| GRADLE_START: ${{ env.gradle_time_start }} | |
| GRADLE_END: ${{ env.gradle_time_end }} | |
| DB_PASSWORD: ${{ secrets.PG_PERFORMANCE_PASSWORD }} | |
| run: | | |
| apk_size_bytes=$( /usr/bin/stat -c '%s' $APK_PATH ) | |
| uv run --with psycopg[binary] scripts/insert-ci-perf-data.py $( basename $APK_PATH ) $GRADLE_START $GRADLE_END $apk_size_bytes | |
| - name: Deploy to Firebase App Distribution | |
| id: firebase_upload | |
| env: | |
| APK_PATH: ${{ steps.find_apk.outputs.APK_PATH }} | |
| FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }} | |
| BUILD_TYPE: ${{ env.BUILD_TYPE }} | |
| JIRA_TITLE: ${{ steps.extract_jira.outputs.JIRA_TITLE }} | |
| JIRA_URL: ${{ steps.extract_jira.outputs.JIRA_URL }} | |
| run: | | |
| echo "Starting Firebase deployment..." | |
| echo "APK Path: $APK_PATH" | |
| echo "Firebase App ID: $FIREBASE_APP_ID" | |
| # Check if Firebase CLI is authenticated | |
| echo "Checking Firebase authentication..." | |
| firebase projects:list || { | |
| echo "ERROR: Firebase authentication failed" | |
| exit 1 | |
| } | |
| RELEASE_NOTES_FILE=$(mktemp) | |
| cat > "$RELEASE_NOTES_FILE" << EOF | |
| Build Type: $BUILD_TYPE | |
| JIRA Title: $JIRA_TITLE | |
| Ticket: $JIRA_URL | |
| EOF | |
| echo "Running Firebase distribution command..." | |
| set +e # Disable exit on error temporarily | |
| output=$(firebase appdistribution:distribute "$APK_PATH" \ | |
| --app "$FIREBASE_APP_ID" \ | |
| --groups "testers" \ | |
| --release-notes-file "$RELEASE_NOTES_FILE" 2>&1) | |
| exit_code=$? | |
| set -e # Re-enable exit on error | |
| echo "Firebase command exit code: $exit_code" | |
| echo "Firebase command output:" | |
| echo "$output" | |
| if [ $exit_code -ne 0 ]; then | |
| echo "ERROR: Firebase deployment failed with exit code $exit_code" | |
| exit 1 | |
| fi | |
| FIREBASE_URL=$(echo "$output" | grep -oE 'https://console\.firebase\.google\.com/project/[^/]+/appdistribution/app/[^/]+/releases/[^?]+(\?[^"]*)?') || FIREBASE_URL="" | |
| if [ -z "$FIREBASE_URL" ]; then | |
| FIREBASE_URL="${{ env.FIREBASE_CONSOLE_URL }}" | |
| fi | |
| echo "FIREBASE_CONSOLE_URL=$FIREBASE_URL" >> $GITHUB_OUTPUT | |
| - name: Update Jira Fix Version | |
| if: github.ref == 'refs/heads/stage' | |
| env: | |
| JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }} | |
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | |
| NEXT_RELEASE_VERSION: ${{ vars.NEXT_RELEASE_VERSION }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # Try to get Jira ticket from extract_jira step output first (handles branch name and commit message) | |
| JIRA_TICKET="${{ steps.extract_jira.outputs.JIRA_TICKET }}" | |
| # If extract_jira didn't find a ticket (returned "N/A"), try merge commit message | |
| if [ "$JIRA_TICKET" == "N/A" ] || [ -z "$JIRA_TICKET" ]; then | |
| MERGE_COMMIT_MSG=$(git log -1 --pretty=%B 2>/dev/null || echo "") | |
| JIRA_TICKET=$(echo "$MERGE_COMMIT_MSG" | grep -o 'ADFA-[0-9]\+' | head -1 || echo "") | |
| fi | |
| if [ -z "$JIRA_TICKET" ] || [ "$JIRA_TICKET" == "N/A" ]; then | |
| # The branch here is always stage, so identify the merged PR that | |
| # introduced this commit to tell community merges from internal ones | |
| PR_JSON=$(curl -s \ | |
| -H "Authorization: Bearer $GH_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls") || PR_JSON="[]" | |
| MERGED_PR=$(echo "$PR_JSON" | jq -c 'if type == "array" then [.[] | select(.merged_at != null)][0] // {} else {} end') | |
| HEAD_REF=$(echo "$MERGED_PR" | jq -r '.head.ref // ""') | |
| HEAD_REPO=$(echo "$MERGED_PR" | jq -r '.head.repo.full_name // ""') | |
| AUTHOR_ASSOCIATION=$(echo "$MERGED_PR" | jq -r '.author_association // ""') | |
| echo "Merged PR head: ${HEAD_REPO}:${HEAD_REF} (author association: ${AUTHOR_ASSOCIATION:-unknown})" | |
| if [[ "$HEAD_REF" == community/* ]]; then | |
| echo "::warning::Community branch merged without a Jira ticket; skipping fix version update" | |
| exit 0 | |
| fi | |
| # Author association is the primary community signal; the fork check | |
| # only decides when no association is available | |
| case "$AUTHOR_ASSOCIATION" in | |
| MEMBER|OWNER|COLLABORATOR) | |
| ;; | |
| "") | |
| if [ -n "$HEAD_REPO" ] && [ "$HEAD_REPO" != "$GITHUB_REPOSITORY" ]; then | |
| echo "::warning::Fork contribution merged without a Jira ticket; skipping fix version update" | |
| exit 0 | |
| fi | |
| ;; | |
| *) | |
| echo "::warning::External contribution merged without a Jira ticket; skipping fix version update" | |
| exit 0 | |
| ;; | |
| esac | |
| echo "ERROR: No Jira ticket found in branch name, commit message, or merge commit" | |
| echo "Branch name or commit message must contain a Jira ticket in format ADFA-XXXX" | |
| exit 1 | |
| fi | |
| echo "Found Jira ticket: $JIRA_TICKET" | |
| # Validate NEXT_RELEASE_VERSION exists and exactly two digits, dot, two digits | |
| if [ -z "$NEXT_RELEASE_VERSION" ]; then | |
| echo "ERROR: NEXT_RELEASE_VERSION variable is not set" | |
| exit 1 | |
| fi | |
| if [[ ! "$NEXT_RELEASE_VERSION" =~ ^[0-9]{2}\.[0-9]{2}$ ]]; then | |
| echo "ERROR: NEXT_RELEASE_VERSION format is invalid: $NEXT_RELEASE_VERSION" | |
| echo "Expected format: YY.ww (two digits, dot, two digits, e.g., 25.50)" | |
| exit 1 | |
| fi | |
| echo "Validated NEXT_RELEASE_VERSION: $NEXT_RELEASE_VERSION" | |
| # Update Jira fix version via API | |
| JIRA_BASE_URL="https://appdevforall.atlassian.net" | |
| JIRA_API_URL="${JIRA_BASE_URL}/rest/api/3/issue/${JIRA_TICKET}" | |
| # Prepare JSON payload for fixVersions update | |
| # Assumes NEXT_RELEASE_VERSION has already been created in Jira. | |
| JSON_PAYLOAD=$(jq -n \ | |
| --arg version "$NEXT_RELEASE_VERSION" \ | |
| '{ | |
| fields: { | |
| fixVersions: [{ | |
| name: $version | |
| }] | |
| } | |
| }') | |
| echo "Updating Jira ticket $JIRA_TICKET with fix version $NEXT_RELEASE_VERSION..." | |
| HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" \ | |
| -X PUT \ | |
| -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ | |
| -H "Accept: application/json" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$JSON_PAYLOAD" \ | |
| "$JIRA_API_URL") | |
| HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed -n '1p') | |
| HTTP_STATUS=$(echo "$HTTP_RESPONSE" | sed -n '2p') | |
| if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then | |
| echo "Successfully updated Jira ticket $JIRA_TICKET with fix version $NEXT_RELEASE_VERSION" | |
| else | |
| echo "ERROR: Failed to update Jira ticket" | |
| echo "HTTP Status: $HTTP_STATUS" | |
| echo "Response: $HTTP_BODY" | |
| exit 1 | |
| fi | |
| - name: Clean up APK output after upload | |
| run: | | |
| echo "Removing built APK(s) after Firebase upload..." | |
| # Only remove the packaged APKs (prevents 'Find APK file' from picking a | |
| # stale APK on the next run). Compiled intermediates are preserved so the | |
| # next build compiles :app incrementally instead of from scratch. | |
| rm -rf app/build/outputs/apk/ | |
| echo "APK output cleanup completed" | |
| - name: Send Rich Slack Notification | |
| env: | |
| SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} | |
| JIRA_URL: ${{ steps.extract_jira.outputs.JIRA_URL }} | |
| JIRA_TITLE: ${{ steps.extract_jira.outputs.JIRA_TITLE }} | |
| PR_AUTHOR: ${{ steps.pr_info.outputs.PR_AUTHOR }} | |
| COMMIT_MSG: ${{ steps.pr_info.outputs.COMMIT_MSG }} | |
| FIREBASE_CONSOLE_URL: ${{ steps.firebase_upload.outputs.FIREBASE_CONSOLE_URL }} | |
| run: | | |
| BRANCH_NAME="${{ env.BRANCH_NAME }}" | |
| BUILD_TYPE="${{ env.BUILD_TYPE }}" | |
| jq -n \ | |
| --arg jira_url "$JIRA_URL" \ | |
| --arg jira_title "$JIRA_TITLE" \ | |
| --arg commit_msg "$COMMIT_MSG" \ | |
| --arg build_type "$BUILD_TYPE" \ | |
| --arg pr_author "$PR_AUTHOR" \ | |
| --arg firebase_url "$FIREBASE_CONSOLE_URL" \ | |
| --arg branch_name "$BRANCH_NAME" \ | |
| '{ | |
| blocks: [ | |
| { | |
| type: "header", | |
| text: { | |
| type: "plain_text", | |
| text: ":rocket: New Build Available", | |
| emoji: true | |
| } | |
| }, | |
| { | |
| type: "section", | |
| text: { | |
| type: "mrkdwn", | |
| text: "@here Please review and test this build." | |
| } | |
| }, | |
| { | |
| type: "section", | |
| text: { | |
| type: "mrkdwn", | |
| text: "*Build Type:* \($build_type)" | |
| } | |
| }, | |
| { | |
| type: "section", | |
| text: { | |
| type: "mrkdwn", | |
| text: "*Jira Ticket:* <\($jira_url)|\($jira_title)>" | |
| } | |
| }, | |
| { | |
| type: "section", | |
| text: { | |
| type: "mrkdwn", | |
| text: "*Commit:* \($commit_msg)" | |
| } | |
| }, | |
| { | |
| type: "section", | |
| text: { | |
| type: "mrkdwn", | |
| text: "*Author:* @\($pr_author)" | |
| } | |
| }, | |
| { | |
| type: "actions", | |
| elements: [ | |
| { | |
| type: "button", | |
| text: { | |
| type: "plain_text", | |
| text: "View on Firebase", | |
| emoji: true | |
| }, | |
| url: $firebase_url, | |
| action_id: "firebase-console" | |
| }, | |
| { | |
| type: "button", | |
| text: { | |
| type: "plain_text", | |
| text: "View Ticket", | |
| emoji: true | |
| }, | |
| url: $jira_url, | |
| action_id: "jira-ticket" | |
| } | |
| ] | |
| }, | |
| { | |
| type: "divider" | |
| }, | |
| { | |
| type: "context", | |
| elements: [ | |
| { | |
| type: "mrkdwn", | |
| text: "Deployed from branch `\($branch_name)`" | |
| } | |
| ] | |
| } | |
| ] | |
| }' > payload.json | |
| curl -X POST -H "Content-type: application/json" --data @payload.json "$SLACK_WEBHOOK" | |
| rm -f payload.json | |
| - name: Cleanup google-services.json | |
| if: always() | |
| run: | | |
| rm -f app/google-services.json | |
| echo "google-services.json cleaned up successfully" |