@@ -4,6 +4,7 @@ permissions:
44 id-token : write
55 contents : read
66 actions : write
7+ pull-requests : read
78
89on :
910 push :
@@ -70,11 +71,73 @@ jobs:
7071 echo "must_build=true" >> $GITHUB_OUTPUT
7172 fi
7273
74+ validate_branch :
75+ name : Validate branch and Jira ticket
76+ runs-on : self-hosted
77+ outputs :
78+ branch_type : ${{ steps.classify.outputs.branch_type }}
79+ jira_ticket : ${{ steps.classify.outputs.jira_ticket }}
80+ steps :
81+ - name : Classify branch
82+ id : classify
83+ run : |
84+ BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
85+ echo "Branch name: $BRANCH_NAME"
86+
87+ JIRA_TICKET=""
88+ if [[ "$BRANCH_NAME" == community/* ]]; then
89+ BRANCH_TYPE="community"
90+ echo "Community contribution branch detected, Jira steps will be skipped"
91+ else
92+ JIRA_TICKET=$(echo "$BRANCH_NAME" | grep -o 'ADFA-[0-9]\+' | head -1)
93+
94+ if [ -n "$JIRA_TICKET" ]; then
95+ BRANCH_TYPE="jira"
96+ echo "Jira branch detected with ticket $JIRA_TICKET"
97+ else
98+ BRANCH_TYPE="internal"
99+ echo "Internal branch detected, proceeding with current automation"
100+ fi
101+ fi
102+
103+ echo "branch_type=$BRANCH_TYPE" >> $GITHUB_OUTPUT
104+ echo "jira_ticket=$JIRA_TICKET" >> $GITHUB_OUTPUT
105+
106+ - name : Fail fast on nonexistent Jira ticket
107+ if : steps.classify.outputs.branch_type == 'jira'
108+ env :
109+ JIRA_EMAIL : ${{ secrets.JIRA_EMAIL }}
110+ JIRA_API_TOKEN : ${{ secrets.JIRA_API_TOKEN }}
111+ JIRA_TICKET : ${{ steps.classify.outputs.jira_ticket }}
112+ run : |
113+ if [ -z "$JIRA_EMAIL" ] || [ -z "$JIRA_API_TOKEN" ]; then
114+ echo "::warning::Jira credentials are not configured; skipping validation of $JIRA_TICKET"
115+ exit 0
116+ fi
117+
118+ HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
119+ -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
120+ -H "Accept: application/json" \
121+ "https://appdevforall.atlassian.net/rest/api/3/issue/${JIRA_TICKET}?fields=summary") || HTTP_STATUS="000"
122+
123+ if [ "$HTTP_STATUS" -eq 404 ]; then
124+ 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."
125+ exit 1
126+ fi
127+
128+ # Only a definitive 404 blocks the build; outages or auth issues must not
129+ if [ "$HTTP_STATUS" -ne 200 ]; then
130+ echo "::warning::Could not validate Jira ticket $JIRA_TICKET (HTTP $HTTP_STATUS); proceeding"
131+ exit 0
132+ fi
133+
134+ echo "Jira ticket $JIRA_TICKET exists"
135+
73136 build_apk :
74137 name : Build Universal APK
75138 runs-on : self-hosted
76139 timeout-minutes : 60
77- needs : check_changes
140+ needs : [ check_changes, validate_branch ]
78141 if : needs.check_changes.outputs.must_build == 'true'
79142 env :
80143 INCLUDE_LLAMA_ASSETS : " false"
@@ -130,6 +193,10 @@ jobs:
130193 echo "$GOOGLE_SERVICES_JSON" > app/google-services.json
131194 echo "google-services.json created successfully"
132195
196+ - name : Verify plugin-api binary compatibility
197+ run : |
198+ flox activate -d flox/base -- ./gradlew :plugin-api:apiCheck --no-daemon
199+
133200 - name : Assemble Universal APK
134201 run : |
135202 echo "gradle_time_start=$(date +%s)" >> $GITHUB_ENV
@@ -149,6 +216,8 @@ jobs:
149216
150217 if [[ "${BRANCH_NAME,,}" == "stage" ]]; then
151218 BUILD_TYPE="STAGE"
219+ elif [[ "$BRANCH_NAME" == community/* ]]; then
220+ BUILD_TYPE="COMMUNITY ($BRANCH_NAME)"
152221 else
153222 BUILD_TYPE="BRANCH ($BRANCH_NAME)"
154223 fi
@@ -181,12 +250,18 @@ jobs:
181250 id : extract_jira
182251 env :
183252 COMMIT_MSG : ${{ steps.pr_info.outputs.COMMIT_MSG }}
253+ BRANCH_TYPE : ${{ needs.validate_branch.outputs.branch_type }}
184254 run : |
185- JIRA_TICKET=$(echo "$BRANCH_NAME" | grep -o 'ADFA-[0-9]\+' | head -1)
255+ if [[ "$BRANCH_TYPE" == "community" ]]; then
256+ echo "Community branch, skipping Jira ticket extraction"
257+ JIRA_TICKET=""
258+ else
259+ JIRA_TICKET=$(echo "$BRANCH_NAME" | grep -o 'ADFA-[0-9]\+' | head -1)
186260
187- # If no Jira ticket found in branch name, check the commit message
188- if [ -z "$JIRA_TICKET" ]; then
189- JIRA_TICKET=$(echo "$COMMIT_MSG" | grep -o 'ADFA-[0-9]\+' | head -1)
261+ # If no Jira ticket found in branch name, check the commit message
262+ if [ -z "$JIRA_TICKET" ]; then
263+ JIRA_TICKET=$(echo "$COMMIT_MSG" | grep -o 'ADFA-[0-9]\+' | head -1)
264+ fi
190265 fi
191266
192267 if [ -n "$JIRA_TICKET" ]; then
@@ -303,6 +378,7 @@ jobs:
303378 JIRA_EMAIL : ${{ secrets.JIRA_EMAIL }}
304379 JIRA_API_TOKEN : ${{ secrets.JIRA_API_TOKEN }}
305380 NEXT_RELEASE_VERSION : ${{ vars.NEXT_RELEASE_VERSION }}
381+ GH_TOKEN : ${{ github.token }}
306382 run : |
307383 # Try to get Jira ticket from extract_jira step output first (handles branch name and commit message)
308384 JIRA_TICKET="${{ steps.extract_jira.outputs.JIRA_TICKET }}"
@@ -314,6 +390,41 @@ jobs:
314390 fi
315391
316392 if [ -z "$JIRA_TICKET" ] || [ "$JIRA_TICKET" == "N/A" ]; then
393+ # The branch here is always stage, so identify the merged PR that
394+ # introduced this commit to tell community merges from internal ones
395+ PR_JSON=$(curl -s \
396+ -H "Authorization: Bearer $GH_TOKEN" \
397+ -H "Accept: application/vnd.github+json" \
398+ "https://api.github.com/repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls") || PR_JSON="[]"
399+
400+ MERGED_PR=$(echo "$PR_JSON" | jq -c 'if type == "array" then [.[] | select(.merged_at != null)][0] // {} else {} end')
401+ HEAD_REF=$(echo "$MERGED_PR" | jq -r '.head.ref // ""')
402+ HEAD_REPO=$(echo "$MERGED_PR" | jq -r '.head.repo.full_name // ""')
403+ AUTHOR_ASSOCIATION=$(echo "$MERGED_PR" | jq -r '.author_association // ""')
404+ echo "Merged PR head: ${HEAD_REPO}:${HEAD_REF} (author association: ${AUTHOR_ASSOCIATION:-unknown})"
405+
406+ if [[ "$HEAD_REF" == community/* ]]; then
407+ echo "::warning::Community branch merged without a Jira ticket; skipping fix version update"
408+ exit 0
409+ fi
410+
411+ # Author association is the primary community signal; the fork check
412+ # only decides when no association is available
413+ case "$AUTHOR_ASSOCIATION" in
414+ MEMBER|OWNER|COLLABORATOR)
415+ ;;
416+ "")
417+ if [ -n "$HEAD_REPO" ] && [ "$HEAD_REPO" != "$GITHUB_REPOSITORY" ]; then
418+ echo "::warning::Fork contribution merged without a Jira ticket; skipping fix version update"
419+ exit 0
420+ fi
421+ ;;
422+ *)
423+ echo "::warning::External contribution merged without a Jira ticket; skipping fix version update"
424+ exit 0
425+ ;;
426+ esac
427+
317428 echo "ERROR: No Jira ticket found in branch name, commit message, or merge commit"
318429 echo "Branch name or commit message must contain a Jira ticket in format ADFA-XXXX"
319430 exit 1
@@ -373,11 +484,14 @@ jobs:
373484 exit 1
374485 fi
375486
376- - name : Clean up build folder after upload
487+ - name : Clean up APK output after upload
377488 run : |
378- echo "Cleaning up build folder after Firebase upload..."
379- rm -rf app/build/
380- echo "Build folder cleanup completed"
489+ echo "Removing built APK(s) after Firebase upload..."
490+ # Only remove the packaged APKs (prevents 'Find APK file' from picking a
491+ # stale APK on the next run). Compiled intermediates are preserved so the
492+ # next build compiles :app incrementally instead of from scratch.
493+ rm -rf app/build/outputs/apk/
494+ echo "APK output cleanup completed"
381495
382496 - name : Send Rich Slack Notification
383497 env :
0 commit comments