Skip to content

Commit c183cc4

Browse files
authored
Merge pull request #10521 from The-OpenROAD-Project-staging/ci/clang-tidy-bazel-fork-pr-fix
ci: split clang-tidy-bazel into pull_request + workflow_run stages
2 parents 9294934 + 24d544a commit c183cc4

2 files changed

Lines changed: 168 additions & 18 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
name: clang-tidy-bazel-post
2+
3+
# Runs in the base repository's context with a writable GITHUB_TOKEN, so it
4+
# can post reviewdog comments on PRs opened from forks (which the upstream
5+
# `clang-tidy-bazel` workflow cannot, since fork pull_request runs get a
6+
# read-only token by GitHub's design).
7+
#
8+
# Security: this workflow MUST NOT execute untrusted PR code. It only reads
9+
# the text artifact (clang-tidy.txt) produced by the upstream workflow and
10+
# the metadata file we wrote there ourselves. No checkout of the PR head,
11+
# no bazel build, no scripts from the fork.
12+
13+
on:
14+
workflow_run:
15+
workflows: ["clang-tidy-bazel"]
16+
types:
17+
- completed
18+
19+
permissions:
20+
contents: read
21+
pull-requests: write
22+
# Needed for actions/download-artifact@v4 to fetch from another workflow.
23+
actions: read
24+
25+
jobs:
26+
Post-Reviewdog:
27+
# Skip if the upstream build failed before producing an artifact.
28+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
29+
runs-on: ${{ vars.USE_SELF_HOSTED == 'true' && 'self-hosted' || 'ubuntu-latest' }}
30+
steps:
31+
# Reviewdog's github-pr-review reporter resolves the local git root
32+
# before flushing comments and silently no-ops if .git is missing.
33+
# Check out the base repo (default branch, shallow) so reviewdog has
34+
# a .git directory to operate against. No fork code involved — this
35+
# is the base repo at HEAD of its default branch.
36+
- name: Check out base repo for reviewdog .git requirement
37+
uses: actions/checkout@v6
38+
with:
39+
fetch-depth: 1
40+
# Default ref in workflow_run context is the base repo's default
41+
# branch, which is the safe choice here. Reviewdog uses GitHub
42+
# API to fetch the actual PR diff, so the local SHA need not
43+
# match the PR head.
44+
45+
- name: Download clang-tidy artifact
46+
uses: actions/download-artifact@v4
47+
with:
48+
name: clang-tidy-bazel
49+
run-id: ${{ github.event.workflow_run.id }}
50+
github-token: ${{ secrets.GITHUB_TOKEN }}
51+
52+
- name: Load PR metadata
53+
id: meta
54+
run: |
55+
# pr-meta.txt is produced by the upstream workflow from the
56+
# pull_request event payload. It is text we wrote ourselves —
57+
# not arbitrary fork content — and is parsed with a strict
58+
# allowlist below before being exported.
59+
if [ ! -f pr-meta.txt ]; then
60+
echo "::error::pr-meta.txt missing from artifact"
61+
exit 1
62+
fi
63+
while IFS='=' read -r key value; do
64+
case "$key" in
65+
pr_number|head_sha|base_sha|head_repo|base_repo) ;;
66+
*) continue ;;
67+
esac
68+
# Validate values: numbers, hex SHAs, or owner/repo slugs only.
69+
case "$key" in
70+
pr_number)
71+
[[ "$value" =~ ^[0-9]+$ ]] || { echo "::error::bad pr_number"; exit 1; } ;;
72+
head_sha|base_sha)
73+
[[ "$value" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::bad $key"; exit 1; } ;;
74+
head_repo|base_repo)
75+
[[ "$value" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || { echo "::error::bad $key"; exit 1; } ;;
76+
esac
77+
echo "$key=$value" >> "$GITHUB_OUTPUT"
78+
done < pr-meta.txt
79+
80+
- name: Synthesize pull_request event payload
81+
id: event
82+
env:
83+
PR_NUMBER: ${{ steps.meta.outputs.pr_number }}
84+
HEAD_SHA: ${{ steps.meta.outputs.head_sha }}
85+
BASE_SHA: ${{ steps.meta.outputs.base_sha }}
86+
HEAD_REPO: ${{ steps.meta.outputs.head_repo }}
87+
BASE_REPO: ${{ steps.meta.outputs.base_repo }}
88+
run: |
89+
# Reviewdog's `github-pr-review` reporter reads GITHUB_EVENT_PATH
90+
# expecting a pull_request payload. The real event here is
91+
# workflow_run, so we synthesize the minimum payload reviewdog
92+
# needs and point GITHUB_EVENT_PATH at it for the next step.
93+
EVENT_PATH="${RUNNER_TEMP}/pr-event.json"
94+
python3 - <<'PY' > "$EVENT_PATH"
95+
import json, os
96+
payload = {
97+
"action": "synchronize",
98+
"number": int(os.environ["PR_NUMBER"]),
99+
"pull_request": {
100+
"number": int(os.environ["PR_NUMBER"]),
101+
"head": {
102+
"sha": os.environ["HEAD_SHA"],
103+
"repo": {"full_name": os.environ["HEAD_REPO"]},
104+
},
105+
"base": {
106+
"sha": os.environ["BASE_SHA"],
107+
"repo": {"full_name": os.environ["BASE_REPO"]},
108+
},
109+
},
110+
"repository": {
111+
"full_name": os.environ["BASE_REPO"],
112+
"owner": {"login": os.environ["BASE_REPO"].split("/")[0]},
113+
"name": os.environ["BASE_REPO"].split("/")[1],
114+
},
115+
}
116+
print(json.dumps(payload))
117+
PY
118+
echo "event_path=${EVENT_PATH}" >> "$GITHUB_OUTPUT"
119+
120+
- name: Set up reviewdog
121+
uses: reviewdog/action-setup@v1
122+
with:
123+
reviewdog_version: latest
124+
125+
- name: Run reviewdog
126+
env:
127+
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
128+
GITHUB_EVENT_NAME: pull_request
129+
GITHUB_EVENT_PATH: ${{ steps.event.outputs.event_path }}
130+
GITHUB_SHA: ${{ steps.meta.outputs.head_sha }}
131+
GITHUB_REPOSITORY: ${{ steps.meta.outputs.base_repo }}
132+
run: |
133+
reviewdog \
134+
-efm="%E%f:%l:%c: error: %m" \
135+
-efm="%W%f:%l:%c: warning: %m" \
136+
-name="clang-tidy" \
137+
-reporter=github-pr-review \
138+
-filter-mode=added \
139+
-fail-level=any \
140+
< clang-tidy.txt

.github/workflows/github-actions-clang-tidy-bazel.yml

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ on:
55
branches:
66
- master
77

8+
# Read-only by design: fork PRs get a read-only GITHUB_TOKEN regardless of
9+
# what this block requests, so this workflow only builds clang-tidy and
10+
# uploads the findings as an artifact. The companion workflow
11+
# `clang-tidy-bazel-post` runs on `workflow_run` in the base repo context
12+
# with a writable token and posts the reviewdog comments.
813
permissions:
914
contents: read
10-
pull-requests: write
1115

1216
jobs:
1317
Clang-Tidy-Bazel:
@@ -17,7 +21,8 @@ jobs:
1721
uses: actions/checkout@v6
1822
with:
1923
submodules: 'recursive'
20-
# Need full history so reviewdog can diff against the PR base.
24+
# Need full history so the post workflow's reviewdog can diff
25+
# against the PR base via the API.
2126
fetch-depth: 0
2227

2328
- name: Set up bazel
@@ -30,11 +35,6 @@ jobs:
3035
bazelisk-version: 1.x
3136
bazelisk-cache: true
3237

33-
- name: Set up reviewdog
34-
uses: reviewdog/action-setup@v1
35-
with:
36-
reviewdog_version: latest
37-
3838
- name: Run bazel clang-tidy
3939
env:
4040
BAZEL_CACHE_PASSWORD: ${{ secrets.BAZEL_CACHE_PASSWORD }}
@@ -88,15 +88,25 @@ jobs:
8888
echo "::endgroup::"
8989
echo "Findings: $(wc -l < clang-tidy.txt)"
9090
91-
- name: Run reviewdog
92-
env:
93-
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
91+
- name: Save PR metadata for post workflow
9492
run: |
95-
reviewdog \
96-
-efm="%E%f:%l:%c: error: %m" \
97-
-efm="%W%f:%l:%c: warning: %m" \
98-
-name="clang-tidy" \
99-
-reporter=github-pr-review \
100-
-filter-mode=added \
101-
-fail-level=any \
102-
< clang-tidy.txt
93+
# workflow_run.event.pull_requests[] is empty for fork PRs, so the
94+
# post workflow needs the PR number and head SHA delivered via the
95+
# artifact itself.
96+
{
97+
echo "pr_number=${{ github.event.pull_request.number }}"
98+
echo "head_sha=${{ github.event.pull_request.head.sha }}"
99+
echo "base_sha=${{ github.event.pull_request.base.sha }}"
100+
echo "head_repo=${{ github.event.pull_request.head.repo.full_name }}"
101+
echo "base_repo=${{ github.event.pull_request.base.repo.full_name }}"
102+
} > pr-meta.txt
103+
104+
- name: Upload clang-tidy artifact
105+
uses: actions/upload-artifact@v4
106+
with:
107+
name: clang-tidy-bazel
108+
path: |
109+
clang-tidy.txt
110+
pr-meta.txt
111+
retention-days: 7
112+
if-no-files-found: error

0 commit comments

Comments
 (0)