Files
libretro/.github/workflows/validate.yml
T
Abdessamad Derraz 9646a9f429 feat: offer the other card in offline launchers
Without a network, muOS and ArkOS launchers ask to copy the BIOS files from the card the system does not read (muOS bind.sh, dArkOS roms2), then install with --from and the pack. CI checks that shellcheck is present, and the test fails under CI without it.
2026-10-23 16:56:28 +02:00

329 lines
12 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"
# The handheld port bundles install.py and its tests read both.
- "ports/**"
# 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"
- "website/**"
# 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"
- "ports/**"
- "wiki/**"
- "docs_assets/**"
- "provenance/**"
- "exports/**"
- "install/**"
- "database.json"
- "release.json"
- "derivable.json"
- "website/**"
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. pyte
# reads the installer's full screen back in its end-to-end tests,
# hypothesis fuzzes its key decoder, and vermin holds install.py to 3.8.
- name: Install dependencies
run: pip install pyyaml "jsonschema[format-nongpl]==4.26.0" "markdown==3.10.2" "pyte==0.8.2" "hypothesis==6.170.8" "vermin==1.9.2"
# The full screen also runs under the 3.8 floor: the end-to-end tests
# start install.py with the interpreter that runs them.
- name: Install the screen tests' dependency for Python 3.8
run: python3.8 -m pip install "pyte==0.8.2"
- name: Hold install.py to Python 3.8
run: vermin --no-tips -t=3.8- --violations install.py
# The handheld launchers' test runs shellcheck and fails under CI
# without it. The Ubuntu image ships it: this names it if one stops.
- name: Check that shellcheck is installed
run: shellcheck --version
# The site is built as deploy-site.yml builds it, from the lockfile.
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24"
cache: npm
cache-dependency-path: website/package-lock.json
- run: npm ci --prefix website
# 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 }}
# The pages are gitignored, and the tests that read them 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
- name: Run the full-screen interface tests on Python 3.8
if: ${{ !cancelled() }}
env:
RETROBIOS_OFFLINE: "1"
run: python3.8 -m unittest tests.test_install_tui -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: |
npm run build --prefix website
python scripts/validate_site.py --site-dir website/dist
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 }}