Skip to content

Commit 5f5e347

Browse files
authored
Fixes #1443: per-language CodeQL targeting (#1510)
* Fixes #1443: per-language CodeQL targeting - The `changes` job now outputs a `matrix` JSON instead of per-language booleans - `paths-filter` only runs on `pull_request` (on `push`/`schedule`, all languages are included) - The `analyze` job uses `matrix: ${{ fromJson(needs.changes.outputs.matrix) }}` — only jobs for changed languages are created - `if: ${{ fromJson(needs.changes.outputs.matrix).include[0] != null }}` prevents the job from running when no languages changed * Upload empty SARIF for skipped languages to satisfy code scanning Code scanning branch protection expects results for every category previously uploaded on main. When the dynamic matrix excludes a language (no paths changed), the missing category blocks PR merge. Add a skip-analysis job that uploads an empty SARIF file for each language not included in the analysis matrix, so all 7 categories always have results.
1 parent ac0a0d6 commit 5f5e347

1 file changed

Lines changed: 84 additions & 31 deletions

File tree

.github/workflows/codeql.yml

Lines changed: 84 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,19 @@ permissions:
1515
jobs:
1616
changes:
1717
name: Detect changed paths
18-
if: github.event_name == 'pull_request'
1918
runs-on: ubuntu-latest
2019
permissions:
2120
contents: read
2221
pull-requests: read
2322
outputs:
24-
java: ${{ steps.filter.outputs.java }}
25-
js: ${{ steps.filter.outputs.js }}
26-
python: ${{ steps.filter.outputs.python }}
27-
go: ${{ steps.filter.outputs.go }}
28-
rust: ${{ steps.filter.outputs.rust }}
29-
csharp: ${{ steps.filter.outputs.csharp }}
30-
actions: ${{ steps.filter.outputs.actions }}
23+
matrix: ${{ steps.build-matrix.outputs.matrix }}
24+
skipped-matrix: ${{ steps.build-matrix.outputs.skipped-matrix }}
3125
steps:
3226
- uses: actions/checkout@v4
27+
3328
- uses: dorny/paths-filter@v3
3429
id: filter
30+
if: github.event_name == 'pull_request'
3531
with:
3632
filters: |
3733
java:
@@ -52,51 +48,108 @@ jobs:
5248
- '.github/workflows/**'
5349
- '.github/actions/**'
5450
51+
- name: Build language matrix
52+
id: build-matrix
53+
run: |
54+
ALL_LANGUAGES=("java-kotlin" "javascript-typescript" "python" "go" "rust" "csharp" "actions")
55+
ALL_GATES=("java" "js" "python" "go" "rust" "csharp" "actions")
56+
57+
# On push/schedule, analyse ALL languages; skip none.
58+
if [[ "${{ github.event_name }}" != "pull_request" ]]; then
59+
entries=()
60+
for lang in "${ALL_LANGUAGES[@]}"; do
61+
entries+=("{\"language\":\"${lang}\"}")
62+
done
63+
joined=$(IFS=,; echo "${entries[*]}")
64+
echo "matrix={\"include\":[${joined}]}" >> "$GITHUB_OUTPUT"
65+
echo 'skipped-matrix={"include":[]}' >> "$GITHUB_OUTPUT"
66+
else
67+
entries=()
68+
skipped=()
69+
filter_outputs=("${{ steps.filter.outputs.java }}" "${{ steps.filter.outputs.js }}" "${{ steps.filter.outputs.python }}" "${{ steps.filter.outputs.go }}" "${{ steps.filter.outputs.rust }}" "${{ steps.filter.outputs.csharp }}" "${{ steps.filter.outputs.actions }}")
70+
71+
for i in "${!ALL_LANGUAGES[@]}"; do
72+
lang="${ALL_LANGUAGES[$i]}"
73+
changed="${filter_outputs[$i]}"
74+
if [[ "$changed" == "true" ]]; then
75+
entries+=("{\"language\":\"${lang}\"}")
76+
else
77+
skipped+=("{\"language\":\"${lang}\"}")
78+
fi
79+
done
80+
81+
if [[ ${#entries[@]} -eq 0 ]]; then
82+
echo 'matrix={"include":[]}' >> "$GITHUB_OUTPUT"
83+
else
84+
joined=$(IFS=,; echo "${entries[*]}")
85+
echo "matrix={\"include\":[${joined}]}" >> "$GITHUB_OUTPUT"
86+
fi
87+
88+
if [[ ${#skipped[@]} -eq 0 ]]; then
89+
echo 'skipped-matrix={"include":[]}' >> "$GITHUB_OUTPUT"
90+
else
91+
joined=$(IFS=,; echo "${skipped[*]}")
92+
echo "skipped-matrix={\"include\":[${joined}]}" >> "$GITHUB_OUTPUT"
93+
fi
94+
fi
95+
5596
analyze:
5697
name: Analyze (${{ matrix.language }})
5798
needs: changes
58-
# Run even if 'changes' is skipped (e.g. on push/schedule where paths-filter
59-
# may not flag changes). Each step has its own gate condition.
60-
if: always()
99+
if: ${{ fromJson(needs.changes.outputs.matrix).include[0] != null }}
61100
runs-on: ubuntu-latest
62101
permissions:
63102
security-events: write
64103
contents: read
65104
strategy:
66105
fail-fast: false
67-
matrix:
68-
include:
69-
- language: java-kotlin
70-
gate: java
71-
- language: javascript-typescript
72-
gate: js
73-
- language: python
74-
gate: python
75-
- language: go
76-
gate: go
77-
- language: rust
78-
gate: rust
79-
- language: csharp
80-
gate: csharp
81-
- language: actions
82-
gate: actions
106+
matrix: ${{ fromJson(needs.changes.outputs.matrix) }}
83107
steps:
84108
- name: Checkout repository
85-
if: ${{ needs.changes.outputs[matrix.gate] == 'true' || github.event_name == 'schedule' || github.event_name == 'push' }}
86109
uses: actions/checkout@v4
87110

88111
- name: Initialize CodeQL
89-
if: ${{ needs.changes.outputs[matrix.gate] == 'true' || github.event_name == 'schedule' || github.event_name == 'push' }}
90112
uses: github/codeql-action/init@v3
91113
with:
92114
languages: ${{ matrix.language }}
93115

94116
- name: Autobuild
95-
if: ${{ needs.changes.outputs[matrix.gate] == 'true' || github.event_name == 'schedule' || github.event_name == 'push' }}
96117
uses: github/codeql-action/autobuild@v3
97118

98119
- name: Perform CodeQL Analysis
99-
if: ${{ needs.changes.outputs[matrix.gate] == 'true' || github.event_name == 'schedule' || github.event_name == 'push' }}
100120
uses: github/codeql-action/analyze@v3
101121
with:
102122
category: "/language:${{ matrix.language }}"
123+
124+
# Upload empty SARIF for languages that were NOT analysed in this PR.
125+
# Code scanning branch protection expects results for every category that
126+
# has ever been uploaded on the default branch; missing categories block merge.
127+
skip-analysis:
128+
name: Skip (${{ matrix.language }})
129+
needs: changes
130+
if: ${{ github.event_name == 'pull_request' && fromJson(needs.changes.outputs.skipped-matrix).include[0] != null }}
131+
runs-on: ubuntu-latest
132+
permissions:
133+
security-events: write
134+
strategy:
135+
fail-fast: false
136+
matrix: ${{ fromJson(needs.changes.outputs.skipped-matrix) }}
137+
steps:
138+
- name: Create empty SARIF
139+
run: |
140+
cat > "$RUNNER_TEMP/empty.sarif" <<'EOF'
141+
{
142+
"version": "2.1.0",
143+
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
144+
"runs": [{
145+
"tool": { "driver": { "name": "CodeQL", "version": "0.0.0" } },
146+
"results": []
147+
}]
148+
}
149+
EOF
150+
151+
- name: Upload empty SARIF
152+
uses: github/codeql-action/upload-sarif@v3
153+
with:
154+
sarif_file: ${{ runner.temp }}/empty.sarif
155+
category: "/language:${{ matrix.language }}"

0 commit comments

Comments
 (0)