#!/usr/bin/env bash # DESCRIPTION: Verilator: CI script for 'pages.yml', notifies PRs # # SPDX-FileCopyrightText: 2025 Geza Lore # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 # Notify PRs via comment that their workflow reports are available # Note this deliberately does not 'set -e'. A run that cannot be notified, say # because its pull request was locked, must not hold up the notification of all # the others, so each failure below moves on to the next run instead. Its # artifact is then left in place, so it is retried on the next run of 'pages.yml'. # The account we post as, which is how we tell our own earlier notifications # from anybody else's comments. 'pages.yml' sets this to the slug of the app the # token belongs to. Set it to your own login when running this by hand. The 'jq' # filter below picks it up from the environment. if [ -z "${GH_AUTHOR:-}" ]; then echo "GH_AUTHOR must hold the account we post as" >&2 exit 1 fi echo "Posting as '${GH_AUTHOR}'" # Marker line separating the report from the history of the older reports readonly HISTORY_MARKER='' # Opening line of the HTML comment stashing the history entry of the report # itself. It is not shown in its own history, only inherited by the ones # superseding it, hence it is kept out of sight until then. readonly NEXTHIST_MARKER='" # Find the stale notifications from the same source on this PR. That is, our # own comments with the same marker on their first line. Oldest first, as # returned by the API. An app posts as '[bot]', so drop that suffix to # compare against the slug. Move on if they cannot be listed, as without them # we would post a duplicate instead of superseding them. if ! COMMENT_MARKER="${COMMENT_MARKER}" \ gh api --paginate "repos/{owner}/{repo}/issues/${PR_NUMBER}/comments" \ --jq '.[] | select((.user.login | rtrimstr("[bot]")) == env.GH_AUTHOR) | select((.body | split("\n") | .[0] | rtrimstr("\r")) == env.COMMENT_MARKER) | {id, body}' > ${ARTIFACTS_DIR}/stale.json; then echo "Failed to list the comments of PR #${PR_NUMBER}" >&2 continue fi # Inherit the history of the newest stale notification. There should only ever # be one, and it holds the whole history anyway, as each notification inherits # the history of the one it superseded. Any older ones are only deleted below. STALE_COUNT=$(wc -l < ${ARTIFACTS_DIR}/stale.json) if [ ${STALE_COUNT} -gt 1 ]; then echo "Found ${STALE_COUNT} stale notifications, only the newest is inherited from" fi HISTORY=${ARTIFACTS_DIR}/history.txt touch ${HISTORY} if [ ${STALE_COUNT} -gt 0 ]; then tail -n 1 ${ARTIFACTS_DIR}/stale.json | jq -r '.body' | tr -d '\r' > ${ARTIFACTS_DIR}/stale-body.txt # Its own entry, stashed in an HTML comment awk -v marker="${NEXTHIST_MARKER}" ' $0 == marker { inside = 1; next } !inside { next } $0 == "-->" { exit } NF { print } ' ${ARTIFACTS_DIR}/stale-body.txt >> ${HISTORY} # Followed by its history, without the enclosing '
' markup awk -v marker="${HISTORY_MARKER}" ' $0 == marker { inside = 1; next } !inside { next } $0 == "
" || /^/ { next } $0 == "
" { exit } NF { print } ' ${ARTIFACTS_DIR}/stale-body.txt >> ${HISTORY} fi # Assemble the comment. Starts with the marker and the report. COMMENT=${ARTIFACTS_DIR}/comment.txt cat > ${COMMENT} <> ${COMMENT} < PR history $(cat ${HISTORY})
HISTORY_TEMPLATE fi # Finally this report's own history entry, stashed out of sight until a later # notification inherits it. cat >> ${COMMENT} < NEXTHIST_TEMPLATE # Post it. Move on if this failed, without deleting anything below, otherwise # the history held by the stale notifications would be lost. if ! jq -Rs '{body: .}' ${COMMENT} \ | gh api --method POST "repos/{owner}/{repo}/issues/${PR_NUMBER}/comments" \ --input - > ${ARTIFACTS_DIR}/posted.json; then echo "Failed to post this notification on PR #${PR_NUMBER}:" >&2 cat ${COMMENT} >&2 continue fi echo "Posted $(jq -r '.html_url' ${ARTIFACTS_DIR}/posted.json)" # Delete the stale notifications, so only the new one remains for COMMENT_ID in $(jq -r '.id' ${ARTIFACTS_DIR}/stale.json); do echo "Deleting stale comment ${COMMENT_ID}" gh api --method DELETE "repos/{owner}/{repo}/issues/comments/${COMMENT_ID}" done # Get the artifact IDs. Note there can be more than one artifact named # 'pr-notification' for a single run, as the artifacts endpoint lists # artifacts across all run attempts, and a re-run uploads a new one while # keeping the previous attempt's artifact. ARTIFACT_IDS=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id') # Delete them all, so we only notify once for ARTIFACT_ID in ${ARTIFACT_IDS}; do gh api --method DELETE "repos/{owner}/{repo}/actions/artifacts/${ARTIFACT_ID}" done done