Files
libretro/.github/workflows/validate.yml
T
Abdessamad Derraz 7a120d7a51 fix: generate the site before CI runs the suite
docs/ is gitignored and was built after the tests, so the three guards
that read rendered pages skipped on every CI run. The site is generated
first, the suite runs whatever that step returns, and the guards fail
in CI when the pages are absent.
2026-10-21 09:59:22 +02:00

297 lines
11 KiB
YAML

name: Validation
# The same checks on both roads into main. Work lands here by direct push as
# often as by pull request, and a test suite reachable only from a PR guards
# the road nobody takes.
on:
pull_request:
paths:
- "bios/**"
- "platforms/**"
- "emulators/**"
- "schemas/**"
- "scripts/**"
- "tests/**"
# The bootstraps pin install.py's SHA-256 and a test enforces the pin,
# so editing one without the other has to fail the PR.
- "install.py"
- "install.sh"
- "install.ps1"
# Every input of the site build, so a page that would fail the deploy
# fails here first (tests/test_workflow_paths.py).
- "wiki/**"
- "docs_assets/**"
- "provenance/**"
- "exports/**"
- "install/**"
- "database.json"
- "release.json"
- "derivable.json"
- "mkdocs.yml"
# Spelled out twice because the workflow parser reads no YAML anchor. A test
# holds the two lists equal.
push:
branches: [main]
paths:
- "bios/**"
- "platforms/**"
- "emulators/**"
- "schemas/**"
- "scripts/**"
- "tests/**"
- "install.py"
- "install.sh"
- "install.ps1"
- "wiki/**"
- "docs_assets/**"
- "provenance/**"
- "exports/**"
- "install/**"
- "database.json"
- "release.json"
- "derivable.json"
- "mkdocs.yml"
permissions:
contents: read
pull-requests: write
concurrency:
# A push series collapses to the tip: what has to stay verified is the head
# of main, not every commit that passed under it.
group: validate-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
validate-bios:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.12"
- name: Install dependencies
run: pip install pyyaml
- name: Get changed BIOS files
id: changed
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
# NUL-separated and filtered to added/modified/renamed: a deleted
# file has no bytes left to hash, and a path with a space or a
# quote must survive the hand-off to validate_pr.py.
git diff --name-only --diff-filter=AMR -z "$BASE_SHA"..."$HEAD_SHA" \
-- bios/ > changed_files.zlist
# The report has to reach the pull request whatever the outcome, so the
# exit code is recorded here and acted on after the comment is posted.
# Swallowing it outright left the job green on a file that failed its
# hash check.
- name: Validate BIOS files
id: validate
run: |
if [ -s changed_files.zlist ]; then
set +e
xargs -0 python scripts/validate_pr.py --markdown -- \
< changed_files.zlist > report.md 2>&1
echo "rc=$?" >> "$GITHUB_OUTPUT"
set -e
else
echo "No BIOS files changed" > report.md
echo "rc=0" >> "$GITHUB_OUTPUT"
fi
cat report.md
# The job summary is the report every contributor sees, fork or not:
# it needs no write token.
- name: Write the report to the job summary
if: always()
run: |
if [ -f report.md ]; then
cat report.md >> "$GITHUB_STEP_SUMMARY"
else
echo "The validation step stopped before writing a report." >> "$GITHUB_STEP_SUMMARY"
fi
# A pull request from a fork runs with a read-only GITHUB_TOKEN, so a
# comment would fail and turn a valid file red. The comment is posted
# only when the branch lives in this repository. pull_request_target is
# not an option: it hands a write token to a run that would read the
# contributor's files and scripts.
- name: Post validation report
if: always() && github.event.pull_request.head.repo.full_name == github.repository
run: |
gh pr comment "${{ github.event.pull_request.number }}" --body-file report.md
env:
GH_TOKEN: ${{ github.token }}
- name: Fail when a file did not validate
if: steps.validate.outputs.rc != '0'
run: |
echo "::error::validate_pr.py exited ${{ steps.validate.outputs.rc }}. The report is in the job summary of this run."
exit 1
validate-configs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.12"
- name: Install dependencies
run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0"
- name: Validate platform configs and emulator profiles
run: python scripts/validate_schemas.py --source-only
# A profile written from a clone can cite lines that do not hold the value
# it declares, and schemas and tests pass it. Each profile a change adds is
# confronted with its upstream, both roads into main. An edited profile is
# not held to it: the backlog of older refs is reviewed by hand.
check-new-profiles:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.12"
- name: Install dependencies
run: pip install pyyaml
- name: Confront added profiles with their upstream
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BEFORE: ${{ github.event.before }}
run: |
# A pull request is checked out as a merge commit whose first
# parent is the base, while a push names the commit it moved main from.
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
base=HEAD^1
elif [ -z "$BEFORE" ] || [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
echo "first push of the branch: no base to diff against"
exit 0
else
base=$BEFORE
fi
python scripts/profile_sync.py --added --base "$base" --strict
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
# install.py stays on Python 3.8: the suite runs it end to end under a
# real 3.8 interpreter, which import-only and grammar checks miss. 3.9
# is the floor pipeline.py states for the build scripts: a real 3.9
# imports every module under scripts/, so it needs their dependencies.
# jsonschema 4.26 requires 3.10, 4.25.1 is the last release for 3.9.
# The last version listed is the default python.
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: |
3.8
3.9
3.12
- name: Install the build scripts' dependencies for Python 3.9
run: python3.9 -m pip install pyyaml "jsonschema[format-nongpl]==4.25.1"
# markdown: the site validation test renders a wiki line to prove the
# heading guard's premise, and skipped on every run without it. The docs
# group builds the site as deploy-site.yml does, with the same bounds.
- name: Install dependencies
run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0" "markdown==3.10.2" "mkdocs-material>=9.7.5,<10" "pymdown-extensions>=10.14"
# The exporter fidelity cases compare what we hand back against the
# file each platform publishes, so they need those files. They skip
# without them rather than fail, which is why an unreachable upstream
# must not stop the run: the rest of the suite still has to report.
- name: Fetch the platforms' own files
continue-on-error: true
run: python scripts/export_native.py --all --fetch --output-dir "$RUNNER_TEMP/native"
# A pack built without its data directories exits non-zero: it is not
# the pack a user gets. Tests that build packs need the same trees a
# release build has, as deploy-site.yml fetches them.
- name: Fetch the data directories
run: python scripts/refresh_data_dirs.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# docs/ is gitignored, and the tests that read the rendered pages fail
# in CI without it. The suite runs whatever this step returned, so a
# generator that stops still leaves every other test reporting.
- name: Generate the site
run: python scripts/generate_site.py
- name: Run test suite
if: ${{ !cancelled() }}
# No test downloads a release asset: 252 MB files fetched into the
# shared cache filled the runner's disk and changed what later tests
# resolved. The download tests mock the network and clear it.
env:
RETROBIOS_OFFLINE: "1"
run: python -m unittest discover -s tests -t . -v
# deploy-site.yml runs this after the merge, and a dead link or a stray
# heading failed it there with nothing in front of it. Run whatever the
# suite returned, so one red step does not hide the other. The release
# assets are not restored: they change counts, not links or headings.
- name: Build the site
if: ${{ !cancelled() }}
run: |
mkdocs build --strict
python scripts/validate_site.py
label-pr:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
# Labels need a write token, which a pull request from a fork does not
# get. The step is skipped there instead of reporting a success that
# set nothing.
- name: Auto-label PR
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
labels=""
files=$(gh pr diff "${{ github.event.pull_request.number }}" --name-only)
if echo "$files" | grep -q '^bios/'; then
labels="$labels bios"
for sys in $(echo "$files" | grep '^bios/' | cut -d/ -f2 | sort -u); do
labels="$labels system:$(echo "$sys" | tr '[:upper:]' '[:lower:]')"
done
fi
if echo "$files" | grep -q '^platforms/'; then
labels="$labels platform-config"
fi
if echo "$files" | grep -q '^scripts/'; then
labels="$labels automation"
fi
for label in $labels; do
gh pr edit "${{ github.event.pull_request.number }}" --add-label "$label" \
|| echo "::warning::label $label was not added"
done
env:
GH_TOKEN: ${{ github.token }}