Files
kst4contest/.github/workflows/pr-compile-check.yml
T
dependabot[bot] a0039cd52e build(deps): bump actions/setup-java (#141)
Bumps the minor-and-patch group with 1 update: [actions/setup-java](https://github.com/actions/setup-java).


Updates `actions/setup-java` from 6.0.1 to 6.1.0
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v6.0.1...v6.1.0)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-17 22:50:19 +02:00

296 lines
12 KiB
YAML

name: PR Compile Check
on:
pull_request:
branches:
- main
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
compile:
name: Compile (Java 21)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Java 21
uses: actions/setup-java@v6.1.0
with:
distribution: temurin
java-version: "21"
- name: Ensure gradlew is executable
run: chmod +x gradlew
# classes + testClasses rather than compileJava + compileTestJava. The old pair did
# cover Kotlin -- checked with a deliberately broken Kotlin test file, and also in
# --dry-run: the Kotlin plugin makes compileTestJava depend on compileTestKotlin,
# because Java has to see the Kotlin output. But that is the plugin's wiring and not
# this job's statement of intent, and it covered no resources. These two aggregates
# say "compile everything in all three modules" and do not depend on knowing that.
#
# They also run the translation generator, which app-desktop's compileKotlin depends
# on. That is why a pull request contributing a language needs no workflow of its own:
# an orphan key, a dropped placeholder or a duplicate fails here, with the generator's
# own message naming the file.
- name: Compile
run: ./gradlew -S classes testClasses
# Report only: both tools run with ignoreFailures, so a finding cannot fail this step
# -- a crash of the tool itself still does, which is wanted. The findings used to exist
# only in the console log; since #110 they are files, and the table and the artifact
# below are what makes them readable from a pull request.
- name: Static analysis (report only)
run: ./gradlew -S pmdMain spotbugsMain
# Counted from the report XML, never from Gradle's output or exit code. A missing
# report prints "no report" and does not fail the step: that case is already visible
# in the step above.
- name: Summarise the static analysis
if: always()
run: |
set -euo pipefail
shopt -s nullglob
count() {
local file="$1" element="$2"
if [ -f "$file" ]; then
# grep exits 1 on zero matches, which pipefail would turn into a failed step.
{ grep -o "<$element " "$file" || true; } | wc -l | tr -d ' '
else
echo "no report"
fi
}
{
echo "### Static analysis"
echo ""
echo "| module | PMD violations | SpotBugs bugs |"
echo "|---|---|---|"
} >> "$GITHUB_STEP_SUMMARY"
for module in core app-desktop i18n-generator; do
pmd=$(count "$module/build/reports/pmd/main.xml" violation)
spotbugs=$(count "$module/build/reports/spotbugs/main.xml" BugInstance)
echo "$module: pmd=$pmd spotbugs=$spotbugs"
echo "| \`$module\` | $pmd | $spotbugs |" >> "$GITHUB_STEP_SUMMARY"
done
{
echo ""
echo "Report only: nothing fails on a finding. The XML and HTML reports are the"
echo "static-analysis artifact of this run. i18n-generator is Kotlin only, so PMD"
echo "has no Java source there and writes no report."
} >> "$GITHUB_STEP_SUMMARY"
- name: Keep the static analysis reports
if: always()
uses: actions/upload-artifact@v7
with:
name: static-analysis
path: |
*/build/reports/pmd/
*/build/reports/spotbugs/
if-no-files-found: ignore
retention-days: 14
# What a translation PR is actually about, put where its author will look. The counts
# come from the files rather than from Gradle's output, so an up-to-date task cannot
# make the table disappear.
- name: Summarise the interface texts
if: always()
run: |
set -euo pipefail
shopt -s nullglob
base="app-desktop/src/main/i18n/strings_en.properties"
if [ ! -f "$base" ]; then
echo "no base translation file at $base" >&2
exit 1
fi
count_keys() { grep -cE '^[^#!][^=]*=' "$1" || true; }
total=$(count_keys "$base")
{
echo "### Interface texts"
echo ""
echo "| language | translated | of | missing |"
echo "|---|---|---|---|"
} >> "$GITHUB_STEP_SUMMARY"
for file in app-desktop/src/main/i18n/strings_*.properties; do
lang=$(basename "$file" .properties); lang=${lang#strings_}
have=$(count_keys "$file")
missing=$(( total - have ))
[ "$missing" -lt 0 ] && missing=0
echo "| \`$lang\` | $have | $total | $missing |" >> "$GITHUB_STEP_SUMMARY"
done
{
echo ""
echo "A language may be incomplete: a key it leaves out falls back to English,"
echo "which is what lets a translation be contributed a piece at a time. German is"
echo "held complete by a test, because it ships."
} >> "$GITHUB_STEP_SUMMARY"
worksheets=(app-desktop/build/generated/i18n/todo/*.todo.properties)
if [ ${#worksheets[@]} -gt 0 ]; then
{
echo ""
echo "The build wrote a worksheet of what is still missing for:"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
for w in "${worksheets[@]}"; do
echo "- \`$(basename "$w")\`" >> "$GITHUB_STEP_SUMMARY"
done
fi
test:
name: Test (Java 21, headless)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Java 21
uses: actions/setup-java@v6.1.0
with:
distribution: temurin
java-version: "21"
- name: Ensure gradlew is executable
run: chmod +x gradlew
# Skiko's native library links against libGL, so it has to be loadable even though
# nothing here renders through it: the drawing tests use Skia's software backend and
# runComposeUiTest never opens a window. Without this the whole Compose half of the
# suite dies with "libskiko-linux-x64.so: libGL.so.1: cannot open shared object
# file" -- which reads like a missing display and is not one. Found by running this
# job under act, where the container has no libGL; GitHub's own runner image happens
# to ship it, so the step is what makes the job reproducible off GitHub.
- name: Install the OpenGL client library skiko links against
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libgl1
# No Xvfb, deliberately. Everything that pins behaviour runs without a display:
# runComposeUiTest reaches the semantics tree headless (measured before this job was
# written), and the ImageComposeScene drawing tests render in software. The handful
# of tests that open a real window guard themselves with assumeFalse(isHeadless) and
# skip here; the summary below prints the skip count, so that is visible rather than
# assumed.
#
# Software GL under Xvfb was considered and rejected: a missing Skia GL context is
# the hardest failure in this codebase to read, and importing that class of flake
# into CI would cost more than the few window tests are worth here.
# All three modules. The translation generator is a Gradle module and not buildSrc
# precisely so that its 42 tests run here: buildSrc tests do not run on `./gradlew
# build` -- measured, not assumed. The summary below globs */build/test-results, so
# the module was counted the moment it existed.
- name: Test
run: ./gradlew -S test
# Coverage is a report, not a gate: no threshold, nothing in it can fail on a
# percentage. Deliberately not `if: always()` -- without a completed test run the
# report would be partial, and after a failed test the result XML below is what matters.
# jacocoTestReport writes the per-module XML the summary reads; the aggregate merges
# core and app-desktop (i18n-generator is build tooling and stays out).
- name: Coverage report
run: ./gradlew -S jacocoTestReport jacocoAggregatedReport
- name: Summarise the test results
if: always()
run: |
set -euo pipefail
shopt -s nullglob
files=(*/build/test-results/test/TEST-*.xml)
if [ ${#files[@]} -eq 0 ]; then
echo "No test result XML was produced -- the test task did not run." >&2
exit 1
fi
# Counted from the XML and not from Gradle's console output: this project's own
# rule is never to infer "all tests passed" from an exit code.
summary=$(grep -h "<testsuite " "${files[@]}" \
| sed 's/.*tests="\([0-9]*\)".*skipped="\([0-9]*\)".*failures="\([0-9]*\)".*errors="\([0-9]*\)".*/\1 \2 \3 \4/' \
| awk '{t+=$1; s+=$2; f+=$3; e+=$4} END {print t" "s" "f" "e}')
read -r total skipped failures errors <<< "$summary"
echo "tests=$total skipped=$skipped failures=$failures errors=$errors"
{
echo "### Tests"
echo ""
echo "| total | skipped | failures | errors |"
echo "|---|---|---|---|"
echo "| $total | $skipped | $failures | $errors |"
echo ""
echo "Skipped are the tests that need a display, plus the deliberately"
echo "disabled reference-data generator. This job runs headless on purpose."
} >> "$GITHUB_STEP_SUMMARY"
if [ "$failures" != "0" ] || [ "$errors" != "0" ]; then
echo "The result XML reports failures or errors." >&2
exit 1
fi
- name: Summarise the coverage
if: always()
run: |
set -euo pipefail
# The last LINE/BRANCH counter in a JaCoCo XML is the report-level total; the
# per-package and per-class counters come before it.
percent() {
local file="$1" type="$2"
{ grep -o "<counter type=\"$type\"[^>]*/>" "$file" || true; } | tail -1 \
| sed 's/.*missed="\([0-9]*\)".*covered="\([0-9]*\)".*/\1 \2/' \
| awk 'NF == 2 { t=$1+$2; if (t==0) print "n/a"; else printf "%.1f%% (%d/%d)", 100*$2/t, $2, t }'
}
row() {
local label="$1" file="$2"
if [ -f "$file" ]; then
echo "| $label | $(percent "$file" LINE) | $(percent "$file" BRANCH) |"
else
echo "| $label | no report | no report |"
fi
}
{
echo ""
echo "### Coverage"
echo ""
echo "| module | lines | branches |"
echo "|---|---|---|"
row "core" core/build/reports/jacoco/test/jacocoTestReport.xml
row "app-desktop" app-desktop/build/reports/jacoco/test/jacocoTestReport.xml
row "core + app-desktop" build/reports/jacoco/aggregated/jacoco.xml
echo ""
echo "Report only: nothing fails on a percentage. The aggregated HTML report is the"
echo "coverage-report artifact of this run. i18n-generator is build tooling and is"
echo "not counted."
} >> "$GITHUB_STEP_SUMMARY"
- name: Keep the coverage report
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage-report
path: build/reports/jacoco/aggregated/html/
if-no-files-found: ignore
retention-days: 14
- name: Keep the test reports on a failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: test-results
path: |
*/build/test-results/test/
*/build/reports/tests/test/
if-no-files-found: ignore
retention-days: 7