mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 12:33:53 -06:00
No Ubuntu mirror resolved on two runs: the bubblewrap step failed, and setup-node, npm ci and both fetches were skipped, so the suite failed on missing data trees and astro. apt now retries each file and the whole install once more after 30 s, and the four steps that do not need the sandbox run unless the job is cancelled. The sandbox step still fails the job, and its tests already skip naming the missing bwrap.
391 lines
16 KiB
YAML
391 lines
16 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/**"
|
|
# The PyPI package is built from install.py and its tests build it.
|
|
- "packaging/**"
|
|
# 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"
|
|
- "retired.json"
|
|
- "recipes/**"
|
|
- "measurements/**"
|
|
- "website/**"
|
|
# The type check's configuration and baseline.
|
|
- "pyproject.toml"
|
|
- "mypy-baseline.txt"
|
|
# 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/**"
|
|
- "packaging/**"
|
|
- "wiki/**"
|
|
- "docs_assets/**"
|
|
- "provenance/**"
|
|
- "exports/**"
|
|
- "install/**"
|
|
- "database.json"
|
|
- "release.json"
|
|
- "derivable.json"
|
|
- "retired.json"
|
|
- "recipes/**"
|
|
- "measurements/**"
|
|
- "website/**"
|
|
- "pyproject.toml"
|
|
- "mypy-baseline.txt"
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
|
|
concurrency:
|
|
# A run on main finishes before the next starts: with three sessions pushing
|
|
# every 25 minutes and a 40-minute suite, cancelling left main with no
|
|
# finished run at all. GitHub keeps one pending run per group and replaces
|
|
# it with each newer push, so main gets a finished run and then its tip,
|
|
# never a growing queue. A pull request still collapses to its latest push.
|
|
group: validate-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
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
|
|
|
|
# The annotations are checked against mypy-baseline.txt, the errors the
|
|
# code carried when the check started: a new error fails, and so does
|
|
# an entry that no longer occurs until the baseline is synced. mypy
|
|
# reads no installed package (no_site_packages in pyproject.toml), so
|
|
# what this job has installed does not change the result.
|
|
- name: Type-check the scripts and the installer
|
|
run: |
|
|
pip install "mypy==1.18.2" "mypy-baseline==0.7.4"
|
|
mypy > "$RUNNER_TEMP/mypy.txt" || true
|
|
mypy-baseline filter --no-colors < "$RUNNER_TEMP/mypy.txt"
|
|
|
|
# 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
|
|
|
|
# ubuntu-latest moved to 26.04, where setup-python has no 3.8 build: the
|
|
# job is held on the last image that still carries the floor interpreter.
|
|
run-tests:
|
|
runs-on: ubuntu-24.04
|
|
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.
|
|
# setuptools reads pyproject.toml for the packaging guard, and uv builds
|
|
# the PyPI package the way a release builds it.
|
|
- 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" "setuptools==80.9.0" "uv==0.13.4"
|
|
|
|
# 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
|
|
|
|
# core_probe and the campaign run third-party cores only inside a
|
|
# bubblewrap sandbox and refuse without one. Ubuntu 24.04 blocks the
|
|
# unprivileged user namespaces bwrap needs, so the campaign tests saw
|
|
# "refused". The last command proves a sandbox starts. On 2026-11-11
|
|
# two runs could not resolve any Ubuntu mirror: apt retries each file,
|
|
# and the whole install runs once more after 30 s.
|
|
- name: Give the sandbox tests a working bubblewrap
|
|
run: |
|
|
apt="sudo apt-get -o Acquire::Retries=5 -o Acquire::http::Timeout=30"
|
|
get_bwrap() { $apt update && $apt install -y --no-install-recommends bubblewrap; }
|
|
get_bwrap || { sleep 30; get_bwrap; }
|
|
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
bwrap --ro-bind /usr /usr --symlink usr/lib /lib --symlink usr/lib64 /lib64 --symlink usr/bin /bin --proc /proc --dev /dev --unshare-all /bin/true
|
|
|
|
# The site is built as deploy-site.yml builds it, from the lockfile.
|
|
# Neither this nor the two fetches below needs bubblewrap: a failed
|
|
# sandbox step skipped all four, and the suite then failed on missing
|
|
# data trees and an absent astro instead of on the sandbox alone. The
|
|
# bubblewrap step still fails the job.
|
|
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
|
if: ${{ !cancelled() }}
|
|
with:
|
|
node-version: "24"
|
|
cache: npm
|
|
cache-dependency-path: website/package-lock.json
|
|
|
|
- if: ${{ !cancelled() }}
|
|
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. --fetch
|
|
# exported as well and stopped on the absent dist/truth before any
|
|
# download, so the cases skipped on every run: --refresh-cache only
|
|
# downloads them.
|
|
- name: Fetch the platforms' own files
|
|
if: ${{ !cancelled() }}
|
|
run: python scripts/export_native.py --all --refresh-cache
|
|
|
|
# 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
|
|
if: ${{ !cancelled() }}
|
|
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. It runs
|
|
# after a failed data fetch too: one bad mirror stays red on its own
|
|
# step instead of skipping the site and failing 228 site tests with it.
|
|
- name: Generate the site
|
|
if: ${{ !cancelled() }}
|
|
run: python scripts/generate_site.py
|
|
|
|
# website/dist is gitignored too. Built after the suite, the tests that
|
|
# read the rendered pages (page budgets, sidebar, search) found none and
|
|
# skipped on every run.
|
|
- name: Build the site
|
|
if: ${{ !cancelled() }}
|
|
run: npm run build --prefix website
|
|
|
|
- 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: Check the built site
|
|
if: ${{ !cancelled() }}
|
|
run: 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 }}
|