Skip to content

Commit d7afce1

Browse files
authored
Merge pull request #139 from malkoG/ci/add-lint-workflow
Add lint CI workflow that annotates PRs
2 parents 8cbd0bb + 258903c commit d7afce1

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

.github/workflows/lint.yml

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
name: Lint
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
pull-requests: read
15+
checks: write
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Set up JDK 17
24+
uses: actions/setup-java@v4
25+
with:
26+
java-version: '17'
27+
distribution: 'temurin'
28+
29+
- name: Setup Gradle
30+
uses: gradle/actions/setup-gradle@v4
31+
32+
- name: Run lintDebug
33+
run: ./gradlew lintDebug --console=plain 2>&1 | tee lint.log
34+
continue-on-error: true
35+
36+
- name: Collect changed files
37+
id: changed
38+
run: |
39+
set -euo pipefail
40+
HEAD_SHA="${{ github.sha }}"
41+
if [ "${{ github.event_name }}" = "pull_request" ]; then
42+
BASE="${{ github.event.pull_request.base.sha }}"
43+
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
44+
else
45+
BEFORE="${{ github.event.before }}"
46+
ZERO="0000000000000000000000000000000000000000"
47+
if [ -z "$BEFORE" ] || [ "$BEFORE" = "$ZERO" ] || ! git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then
48+
BASE="${HEAD_SHA}^"
49+
else
50+
BASE="$BEFORE"
51+
fi
52+
fi
53+
git diff --name-only "$BASE" "$HEAD_SHA" > changed.txt
54+
echo "changed files:"
55+
cat changed.txt
56+
57+
- name: Emit annotations
58+
shell: python
59+
run: |
60+
import os, re, sys
61+
import xml.etree.ElementTree as ET
62+
63+
workspace = os.environ.get("GITHUB_WORKSPACE", "")
64+
65+
# None = no changed-files list available, annotate everything.
66+
# set() = list was produced but empty, annotate nothing.
67+
changed = None
68+
try:
69+
with open("changed.txt") as f:
70+
changed = {line.strip() for line in f if line.strip()}
71+
except FileNotFoundError:
72+
pass
73+
74+
errors = 0
75+
warnings = 0
76+
77+
def emit(sev, path, line, col, msg):
78+
global errors, warnings
79+
if workspace and path.startswith(workspace + "/"):
80+
path = path[len(workspace) + 1:]
81+
if changed is not None and path not in changed:
82+
return
83+
if sev == "error":
84+
errors += 1
85+
elif sev == "warning":
86+
warnings += 1
87+
msg = msg.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
88+
loc = f"file={path},line={line}"
89+
if col:
90+
loc += f",col={col}"
91+
print(f"::{sev} {loc}::{msg}")
92+
93+
# Kotlin compiler diagnostics from the Gradle console log.
94+
compiler_pattern = re.compile(
95+
r"^(?P<sev>[we]):\s+file://(?P<path>[^:]+):(?P<line>\d+):(?P<col>\d+)\s+(?P<msg>.*)$"
96+
)
97+
try:
98+
with open("lint.log", "r", errors="replace") as f:
99+
for raw in f:
100+
m = compiler_pattern.match(raw.rstrip("\n"))
101+
if not m:
102+
continue
103+
sev = "error" if m.group("sev") == "e" else "warning"
104+
emit(sev, m.group("path"), m.group("line"), m.group("col"), m.group("msg"))
105+
except FileNotFoundError:
106+
pass
107+
108+
# Android Lint issues from the XML report. The text report drops
109+
# column info and the console only prints a summary, so the XML is
110+
# the authoritative source for inline annotations.
111+
severity_map = {
112+
"Fatal": "error",
113+
"Error": "error",
114+
"Warning": "warning",
115+
"Informational": "notice",
116+
}
117+
lint_xml = "app/build/reports/lint-results-debug.xml"
118+
if os.path.exists(lint_xml):
119+
tree = ET.parse(lint_xml)
120+
for issue in tree.getroot().findall("issue"):
121+
sev = severity_map.get(issue.get("severity", ""), "warning")
122+
if sev == "notice":
123+
continue
124+
base_msg = issue.get("message", "").strip()
125+
issue_id = issue.get("id", "")
126+
msg = f"{base_msg} [{issue_id}]" if issue_id else base_msg
127+
for loc in issue.findall("location"):
128+
path = loc.get("file", "")
129+
if not path:
130+
continue
131+
emit(sev, path, loc.get("line") or "1", loc.get("column") or "", msg)
132+
133+
print(f"::notice::Lint (changed files) — errors: {errors}, warnings: {warnings}")
134+
sys.exit(1 if errors > 0 else 0)
135+
136+
- name: Upload lint log
137+
if: always()
138+
uses: actions/upload-artifact@v4
139+
with:
140+
name: lint-log
141+
path: lint.log

0 commit comments

Comments
 (0)