Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/actions/ci-status-comment/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Upsert CI Status Comment
description: >-
Create or update the single unified CI status comment on a PR, replacing only
the given section (build | lint | tests). Seeds a skeleton with all three
sections the first time it runs, and re-reads/retries so concurrent writers
(build vs. Unity Test) never clobber each other's section.

inputs:
pr-number:
description: Pull request number to comment on.
required: true
section:
description: Which section to replace — one of build, lint, tests.
required: true
body:
description: Markdown for this section (inline badge + message). Rendered as-is between the section markers.
required: true
github-token:
description: Token with pull-requests:write used to read and upsert the comment.
required: true

runs:
using: composite
steps:
- name: Upsert unified CI status comment
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ inputs.pr-number }}
SECTION: ${{ inputs.section }}
SECTION_BODY: ${{ inputs.body }}
run: bash "$GITHUB_ACTION_PATH/upsert-ci-status.sh"
150 changes: 150 additions & 0 deletions .github/actions/ci-status-comment/upsert-ci-status.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env bash
# Create or update the single unified CI status comment on a PR, replacing only
# one section (build | lint | tests). All four CI comment workflows call this
# through the ci-status-comment composite action, so the three separate bot
Comment thread
dalkia marked this conversation as resolved.
# comments collapse into one.
Comment thread
dalkia marked this conversation as resolved.
#
# The comment is keyed by the hidden <!-- ci-status --> marker and holds three
# sections, each fenced by its own start/end markers:
#
# <!-- ci-status -->
# ### 🚦 CI Status
# <!-- ci:build:start --> …build… <!-- ci:build:end -->
# <!-- ci:lint:start --> …lint… <!-- ci:lint:end -->
# <!-- ci:tests:start --> …tests… <!-- ci:tests:end -->
#
# Build and Unity Test run as independent workflows whose comment writers can
# fire at the same time, so a plain read-modify-write would drop a section or
# create a duplicate comment. Each attempt collapses any duplicates (keeping the
# oldest), rewrites only its own section on that comment, then re-reads to
# confirm the section landed and no duplicate slipped in — retrying otherwise.
set -euo pipefail

MARKER="<!-- ci-status -->"
HEADER="### 🚦 CI Status"
BOT="github-actions[bot]"
START="<!-- ci:${SECTION}:start -->"
END="<!-- ci:${SECTION}:end -->"
Comment thread
dalkia marked this conversation as resolved.

# Neutral "waiting" placeholder for a section that has not reported yet. Used
Comment thread
dalkia marked this conversation as resolved.
# only when seeding a brand-new comment; a real run always overwrites its own.
section_default() {
case "$1" in
build) printf '![Build](https://img.shields.io/badge/Build-Waiting-lightgrey?logo=unity&logoColor=white&style=for-the-badge)\n\n_Waiting for the build to start…_' ;;
lint) printf '![Lint](https://img.shields.io/badge/Lint-Waiting-lightgrey?logo=jetbrains&logoColor=white&style=for-the-badge)\n\n_Waiting for lint to start…_' ;;
tests) printf '![Tests](https://img.shields.io/badge/Tests-Waiting-lightgrey?logo=codecov&logoColor=white&style=for-the-badge)\n\n_Waiting for tests to start…_' ;;
esac
}

# One section, fenced by its start/end markers.
wrap_section() { printf '<!-- ci:%s:start -->\n%s\n<!-- ci:%s:end -->' "$1" "$2" "$1"; }

# A fresh comment with every section defaulted to "waiting".
skeleton() {
printf '%s\n%s\n\n%s\n\n%s\n\n%s\n' \
"$MARKER" "$HEADER" \
"$(wrap_section build "$(section_default build)")" \
"$(wrap_section lint "$(section_default lint)")" \
"$(wrap_section tests "$(section_default tests)")"
}

# Emit the section body for this run to a file so awk can splice it verbatim,
# free of shell quoting concerns. Parts of the body (lint findings, failed test
# names) originate in the untrusted pull_request job, so drop any line shaped
# like a section marker before writing it — a body line must never open or close
# a section fence, or it would scramble the comment structure / wedge the survive
# check below.
printf '%s\n' "$SECTION_BODY" \
| grep -vE '^[[:space:]]*<!-- ci[-:][^>]*-->[[:space:]]*$' > section_body.md || true
WANT="$(cat section_body.md)"

# Replace the content between START and END in $1 with section_body.md.
replace_section() {
awk -v s="$START" -v e="$END" -v f="section_body.md" '
$0==s { print; while ((getline line < f) > 0) print line; close(f); skip=1; next }
$0==e { print; skip=0; next }
skip { next }
{ print }
' <<< "$1"
}

# Trimmed content currently between START and END in $1 (for the survive check).
extract_section() {
awk -v s="$START" -v e="$END" '
$0==s { grab=1; next }
$0==e { grab=0; next }
grab { print }
' <<< "$1"
}

# IDs of every marker-bearing bot comment on the PR, oldest first. Input is the
# `--paginate --slurp` shape (an array of per-page arrays), so flatten with
# `.[][]` before filtering — otherwise sort_by would only order within a page.
marker_ids() {
jq -r --arg m "$MARKER" --arg bot "$BOT" \
'[.[][] | select(.user.login==$bot and (.body|contains($m)))] | sort_by(.id) | .[].id' <<< "$1"
}
Comment thread
dalkia marked this conversation as resolved.
Outdated

for attempt in 1 2 3 4 5; do
COMMENTS=$(gh api "/repos/$REPO/issues/$PR_NUMBER/comments" --paginate --slurp)
Comment thread
dalkia marked this conversation as resolved.
IDS=()
while IFS= read -r line; do [ -n "$line" ] && IDS+=("$line"); done <<< "$(marker_ids "$COMMENTS")"
COMMENT_ID="${IDS[0]:-}"

# Collapse accidental duplicates from a create race: keep the oldest, drop the rest.
if [ "${#IDS[@]}" -gt 1 ]; then
for extra in "${IDS[@]:1}"; do
echo "Deleting duplicate CI status comment $extra."
gh api -X DELETE "/repos/$REPO/issues/comments/$extra" >/dev/null || true
done
fi
Comment thread
dalkia marked this conversation as resolved.

if [ -n "$COMMENT_ID" ]; then
CURRENT_BODY=$(jq -r --arg id "$COMMENT_ID" '.[][] | select(.id==($id|tonumber)) | .body' <<< "$COMMENTS")
Comment thread
dalkia marked this conversation as resolved.
Outdated
else
CURRENT_BODY=""
fi

# No unified comment yet, or one missing our section markers: start clean so
# all three sections are always present.
if [ -z "$CURRENT_BODY" ] || ! grep -qF "$START" <<< "$CURRENT_BODY"; then
CURRENT_BODY="$(skeleton)"
fi

NEW_BODY="$(replace_section "$CURRENT_BODY")"

if [ -z "$COMMENT_ID" ]; then
RESULT=$(jq -n --arg b "$NEW_BODY" '{body:$b}' \
| gh api -X POST "/repos/$REPO/issues/$PR_NUMBER/comments" --input -)
COMMENT_ID=$(jq -r '.id' <<< "$RESULT")
else
jq -n --arg b "$NEW_BODY" '{body:$b}' \
| gh api -X PATCH "/repos/$REPO/issues/comments/$COMMENT_ID" --input - >/dev/null
fi

# Re-read and confirm our section landed on the surviving comment, and that no
# concurrent writer left a duplicate behind.
sleep 1
RECHECK=$(gh api "/repos/$REPO/issues/$PR_NUMBER/comments" --paginate --slurp)
RIDS=()
while IFS= read -r line; do [ -n "$line" ] && RIDS+=("$line"); done <<< "$(marker_ids "$RECHECK")"
LIVE_BODY=$(jq -r --arg id "$COMMENT_ID" '.[][] | select(.id==($id|tonumber)) | .body' <<< "$RECHECK")
Comment thread
dalkia marked this conversation as resolved.
Outdated

# Success means our section landed on the comment we wrote — nothing more.
# Duplicate collapsing is best-effort cleanup (the DELETE above may lack
# permission); a duplicate we could not remove must not block reporting that
# already succeeded, or every run would burn all 5 attempts and warn forever.
if [ "$(extract_section "$LIVE_BODY")" = "$WANT" ]; then
echo "CI status '$SECTION' section updated (attempt $attempt)."
if [ "${#RIDS[@]}" -gt 1 ]; then
echo "A duplicate CI status comment remains (could not be deleted); it will be retried next run."
fi
exit 0
fi

echo "Section '$SECTION' not settled (attempt $attempt); retrying."
sleep $((attempt * 2))
done

echo "::warning::Could not confirm the '$SECTION' CI status section after 5 attempts."
exit 0
124 changes: 69 additions & 55 deletions .github/workflows/pr-comment-artifact-url.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@
---
name: Comment Artifact URL on PR

# Writes only the "build" section of the unified CI status comment via the
# ci-status-comment composite action (build / lint / tests live in one comment).
#
# 'requested' -> reset the build section to "pending" the moment a new build
# starts, so last build's download links never linger as stale.
# 'completed' -> fill the section back in: Success (with artifact links) /
# Failed / Skipped (no changes under Explorer/).
on:
workflow_run:
types:
- "requested"
- "completed"
workflows:
- "Unity Cloud Build"
Expand Down Expand Up @@ -34,9 +42,31 @@ jobs:
fi
echo "pr-number=$PR_NUMBER" >> $GITHUB_OUTPUT

comment-pending:
needs: pre-validation
if: github.event.action == 'requested' && needs.pre-validation.outputs.pr-number != ''
runs-on: ubuntu-latest
steps:
- name: Checkout CI status action
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions/ci-status-comment
sparse-checkout-cone-mode: false

- name: Reset build section to pending
uses: ./.github/actions/ci-status-comment
with:
pr-number: ${{ needs.pre-validation.outputs.pr-number }}
section: build
github-token: ${{ github.token }}
body: |-
![Build](https://img.shields.io/badge/Build-Pending!-ffff00?logo=github&style=for-the-badge) <img src="https://ui.decentraland.org/decentraland_256x256.png" width="30">

New build in progress, come back later!

check-build-ran:
needs: pre-validation
if: needs.pre-validation.outputs.pr-number != ''
if: github.event.action == 'completed' && needs.pre-validation.outputs.pr-number != ''
runs-on: ubuntu-latest
outputs:
build-ran: ${{ steps.check.outputs.build-ran }}
Expand Down Expand Up @@ -64,31 +94,34 @@ jobs:
if: github.event.workflow_run.conclusion == 'success' && needs.pre-validation.outputs.pr-number != '' && needs.check-build-ran.outputs.build-ran == 'false'
runs-on: ubuntu-latest
steps:
- name: Find Comment
uses: peter-evans/find-comment@v2
id: find-comment
- name: Checkout CI status action
uses: actions/checkout@v4
with:
issue-number: ${{ needs.pre-validation.outputs.pr-number }}
comment-author: 'github-actions[bot]'
sparse-checkout: .github/actions/ci-status-comment
sparse-checkout-cone-mode: false

- name: Post skipped comment
uses: peter-evans/create-or-update-comment@v3
- name: Post skipped build section
uses: ./.github/actions/ci-status-comment
with:
issue-number: ${{ needs.pre-validation.outputs.pr-number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
pr-number: ${{ needs.pre-validation.outputs.pr-number }}
section: build
github-token: ${{ github.token }}
body: |-
![badge] <img src="https://ui.decentraland.org/decentraland_256x256.png" width="30">
![Build](https://img.shields.io/badge/Build-Skipped-yellow?logo=unity&logoColor=white&style=for-the-badge) <img src="https://ui.decentraland.org/decentraland_256x256.png" width="30">

Build skipped — no changes detected under `Explorer/`.

[badge]: https://img.shields.io/badge/Build-Skipped-yellow?logo=unity&logoColor=white&style=for-the-badge

comment-success:
needs: [pre-validation, check-build-ran]
if: github.event.workflow_run.conclusion == 'success' && needs.pre-validation.outputs.pr-number != '' && needs.check-build-ran.outputs.build-ran == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout CI status action
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions/ci-status-comment
sparse-checkout-cone-mode: false

- name: Get Artifact and Pull request info
env:
GITHUB_TOKEN: ${{ github.token }}
Expand Down Expand Up @@ -194,45 +227,29 @@ jobs:
echo "SIZE_REPORT=" >> "$GITHUB_ENV"
fi

- name: Find Comment
uses: peter-evans/find-comment@v2
id: find-comment
- name: Update build section
uses: ./.github/actions/ci-status-comment
with:
issue-number: ${{ needs.pre-validation.outputs.pr-number }}
comment-author: 'github-actions[bot]'

- name: Update Comment
env:
JOB_PATH: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ env.PREVIOUS_JOB_ID }}"
WINDOWS_ARTIFACT_URL: "${{ github.server_url }}/${{ github.repository }}/suites/${{ env.SUITE_ID }}/artifacts/${{ env.WINDOWS_ARTIFACT_ID }}"
WINDOWS_ARTIFACT_S3_URL: "${{ format('{0}/{1}/Decentraland_windows64.zip', vars.EXPLORER_TEAM_S3_BUCKET_PUBLIC_URL, env.ARTIFACT_S3_DESTINATION_PATH) }}"
MAC_ARTIFACT_URL: "${{ github.server_url }}/${{ github.repository }}/suites/${{ env.SUITE_ID }}/artifacts/${{ env.MAC_ARTIFACT_ID }}"
MAC_ARTIFACT_S3_URL: "${{ format('{0}/{1}/Decentraland_macos.zip', vars.EXPLORER_TEAM_S3_BUCKET_PUBLIC_URL, env.ARTIFACT_S3_DESTINATION_PATH) }}"
HEAD_SHA: "${{ env.HEAD_SHA }}"
uses: peter-evans/create-or-update-comment@v3
with:
issue-number: ${{ needs.pre-validation.outputs.pr-number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
pr-number: ${{ needs.pre-validation.outputs.pr-number }}
section: build
github-token: ${{ github.token }}
body: |-
![badge] <img src="https://ui.decentraland.org/decentraland_256x256.png" width="30">
![Build](https://img.shields.io/badge/Build-Success!-3fb950?logo=unity&logoColor=white&style=for-the-badge) <img src="https://ui.decentraland.org/decentraland_256x256.png" width="30">

Windows and Mac build successful in Unity Cloud! You can find a link to the downloadable artifact below.

| Name | Link |
| -------- | ----------------------- |
| Commit | ${{ env.HEAD_SHA }} |
| Logs | ${{ env.JOB_PATH }} |
| Download Windows | ${{ env.WINDOWS_ARTIFACT_URL }} |
| Download Windows S3 | ${{ env.WINDOWS_ARTIFACT_S3_URL }} |
| Download Mac | ${{ env.MAC_ARTIFACT_URL }} |
| Download Mac S3 | ${{ env.MAC_ARTIFACT_S3_URL }} |
| Logs | ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ env.PREVIOUS_JOB_ID }} |
| Download Windows | ${{ github.server_url }}/${{ github.repository }}/suites/${{ env.SUITE_ID }}/artifacts/${{ env.WINDOWS_ARTIFACT_ID }} |
| Download Windows S3 | ${{ format('{0}/{1}/Decentraland_windows64.zip', vars.EXPLORER_TEAM_S3_BUCKET_PUBLIC_URL, env.ARTIFACT_S3_DESTINATION_PATH) }} |
| Download Mac | ${{ github.server_url }}/${{ github.repository }}/suites/${{ env.SUITE_ID }}/artifacts/${{ env.MAC_ARTIFACT_ID }} |
| Download Mac S3 | ${{ format('{0}/{1}/Decentraland_macos.zip', vars.EXPLORER_TEAM_S3_BUCKET_PUBLIC_URL, env.ARTIFACT_S3_DESTINATION_PATH) }} |
| Built on | ${{ env.BUILD_DATE }} |

${{ env.SIZE_REPORT }}

[badge]: https://img.shields.io/badge/Build-Success!-3fb950?logo=unity&logoColor=white&style=for-the-badge

- name: Find latest release
env:
GITHUB_TOKEN: ${{ github.token }}
Expand All @@ -259,23 +276,20 @@ jobs:
if: github.event.workflow_run.conclusion == 'failure' && needs.pre-validation.outputs.pr-number != '' && needs.check-build-ran.outputs.build-ran == 'true'
runs-on: ubuntu-latest
steps:
- name: Find Comment
uses: peter-evans/find-comment@v2
id: find-comment
- name: Checkout CI status action
uses: actions/checkout@v4
with:
issue-number: ${{ needs.pre-validation.outputs.pr-number }}
comment-author: 'github-actions[bot]'
sparse-checkout: .github/actions/ci-status-comment
sparse-checkout-cone-mode: false

- name: Update Comment
uses: peter-evans/create-or-update-comment@v3
- name: Update build section
uses: ./.github/actions/ci-status-comment
with:
issue-number: ${{ needs.pre-validation.outputs.pr-number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
pr-number: ${{ needs.pre-validation.outputs.pr-number }}
section: build
github-token: ${{ github.token }}
body: |-
![badge] <img src="https://ui.decentraland.org/decentraland_256x256.png" width="30">
![Build](https://img.shields.io/badge/Build-Failed!-ff0000?logo=unity&logoColor=white&style=for-the-badge) <img src="https://ui.decentraland.org/decentraland_256x256.png" width="30">

Build failed! Check the logs to see what went wrong.
If the error repeats please consider the `clean-build` tag.

[badge]: https://img.shields.io/badge/Build-Failed!-ff0000?logo=unity&logoColor=white&style=for-the-badge
Loading
Loading