Skip to content

3do: cut v1.0.0 -- broker-based multi-device tester. #26

3do: cut v1.0.0 -- broker-based multi-device tester.

3do: cut v1.0.0 -- broker-based multi-device tester. #26

Workflow file for this run

name: Release
# Triggered by tags shaped like <console>-v<semver>, e.g. gcn-v0.1.0,
# pce-v0.2.3, etc. The console subdir comes from the prefix; only that
# console's app is built and attached to a GitHub Release.
on:
push:
tags:
- '*-v[0-9]+.[0-9]+.[0-9]+'
- '*-v[0-9]+.[0-9]+.[0-9]+-*' # pre-release suffixes (alpha, rc, etc.)
permissions:
contents: write # needed to create the release
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
jobs:
parse:
name: Parse tag
runs-on: ubuntu-latest
outputs:
console: ${{ steps.split.outputs.console }}
version: ${{ steps.split.outputs.version }}
title: ${{ steps.split.outputs.title }}
steps:
- id: split
run: |
TAG="${GITHUB_REF_NAME}"
CONSOLE="${TAG%%-v*}"
VERSION="${TAG#*-v}"
case "$CONSOLE" in
gcn) PRETTY="GameCube" ;;
wii) PRETTY="Wii" ;;
n64) PRETTY="Nintendo 64" ;;
snes) PRETTY="Super Nintendo" ;;
gba) PRETTY="Game Boy Advance" ;;
pce) PRETTY="PC Engine" ;;
3do) PRETTY="3DO" ;;
*) PRETTY="$CONSOLE" ;;
esac
echo "console=$CONSOLE" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "title=Joypad Tester — $PRETTY v$VERSION" >> "$GITHUB_OUTPUT"
build-and-release:
name: Build + release ${{ needs.parse.outputs.console }} v${{ needs.parse.outputs.version }}
needs: parse
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Verify VERSION matches tag
run: |
FILE_VER="$(cat ${{ needs.parse.outputs.console }}/VERSION | tr -d '[:space:]')"
if [ "$FILE_VER" != "${{ needs.parse.outputs.version }}" ]; then
echo "::error::${{ needs.parse.outputs.console }}/VERSION ($FILE_VER) does not match tag (${{ needs.parse.outputs.version }})"
exit 1
fi
- name: Set up Python
if: needs.parse.outputs.console == 'gcn'
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Pillow
if: needs.parse.outputs.console == 'gcn'
run: pip install --quiet Pillow
- name: Generate banner
if: needs.parse.outputs.console == 'gcn'
working-directory: ${{ needs.parse.outputs.console }}
run: python3 buildtools/make_banner.py opening.bnr
- name: Generate screensaver logo header
if: needs.parse.outputs.console == 'gcn'
working-directory: ${{ needs.parse.outputs.console }}
run: python3 buildtools/make_logo.py
- name: Build (GameCube)
if: needs.parse.outputs.console == 'gcn'
run: |
docker run --rm \
-v "${{ github.workspace }}":/workspace \
-w /workspace/${{ needs.parse.outputs.console }} \
-u "$(id -u):$(id -g)" \
ghcr.io/extremscorner/libogc2:latest \
bash -c "TARGET_CONSOLE=gamecube make && TARGET_CONSOLE=wii make"
- name: Build (GBA — both variants)
if: needs.parse.outputs.console == 'gba'
run: |
docker run --rm \
-v "${{ github.workspace }}":/workspace \
-w /workspace/gba \
-u "$(id -u):$(id -g)" \
devkitpro/devkitarm:latest \
make
- name: Build (PC Engine)
if: needs.parse.outputs.console == 'pce'
run: |
docker build -t joypad-tester-huc:release pce/buildtools/
docker run --rm \
-v "${{ github.workspace }}":/workspace \
-w /workspace/pce \
-u "$(id -u):$(id -g)" \
joypad-tester-huc:release \
make
- name: Build (3DO)
if: needs.parse.outputs.console == '3do'
run: |
cd 3do
mkdir -p build .wine-home
docker build --platform=linux/amd64 -t joypad-tester-3do:release buildtools/
docker run --rm \
--platform=linux/amd64 \
-v "$(pwd):/work" \
-u "$(id -u):$(id -g)" \
-e HOME=/work/.wine-home \
joypad-tester-3do:release \
bash -c "
set -e
cp /work/src/main.cpp /opt/3do-devkit/src/main.cpp
cd /opt/3do-devkit
make NAME=joypad-tester
cp iso/joypad-tester.iso /work/build/joypad-tester.iso
"
- name: Stage release artifacts (GameCube)
if: needs.parse.outputs.console == 'gcn'
working-directory: ${{ needs.parse.outputs.console }}
run: |
# Three release artifacts:
# joypad_tester_v<ver>_gcn.zip -- pre-built Swiss folder
# layout for GC SD-card loaders (FlippyDrive, GC Loader,
# SD2SP2, etc.). Contains Joypad Tester/{default.dol,
# opening.bnr}, ready to drop on an SD root.
# joypad_tester_v<ver>_gcn.iso -- bootable iso9660 + El
# Torito disc image (homebrew apploader from
# buildtools/apploader/gbi.hdr + the GC .dol renamed to
# bootldr.dol + opening.bnr in the file tree). Suitable
# for DVD-R burn / IPL-replacement modchips / Dolphin /
# ODE flash carts that prefer ISO over folder layout.
# joypad_tester_v<ver>_wii.dol -- bare Wii build for the
# Homebrew Channel.
sudo apt-get update -qq
sudo apt-get install -y -qq --no-install-recommends genisoimage
VERSION="${{ needs.parse.outputs.version }}"
mkdir -p _release
if [ -f joypad-tester-gamecube.dol ] && [ -f opening.bnr ]; then
# Swiss-folder ZIP
mkdir -p "_stage/Joypad Tester"
cp joypad-tester-gamecube.dol "_stage/Joypad Tester/default.dol"
cp opening.bnr "_stage/Joypad Tester/opening.bnr"
(cd _stage && zip -r "../_release/joypad_tester_v${VERSION}_gcn.zip" "Joypad Tester")
# Bootable ISO: bushing's open-source apploader (gbi.hdr,
# vendored in buildtools/apploader/) wraps a bootldr.dol +
# any extra disc-tree files. mkisofs composes the
# iso9660 image with the El Torito boot extension.
rm -rf _iso_stage
mkdir -p _iso_stage
cp joypad-tester-gamecube.dol _iso_stage/bootldr.dol
cp opening.bnr _iso_stage/opening.bnr
mkisofs -R -J -G buildtools/apploader/gbi.hdr \
-no-emul-boot -b bootldr.dol \
-o "_release/joypad_tester_v${VERSION}_gcn.iso" \
_iso_stage/
rm -rf _stage _iso_stage
fi
[ -f joypad-tester-wii.dol ] && cp joypad-tester-wii.dol "_release/joypad_tester_v${VERSION}_wii.dol"
ls -la _release/
- name: Stage release artifacts (GBA)
if: needs.parse.outputs.console == 'gba'
working-directory: gba
run: |
# Public release ships only the user-facing tester ROM.
# The eyes variant (joypad_mb.gba) is a firmware payload meant
# to be multibooted by a separate host (joypad-os keeps its
# own copy for that role), not run standalone -- end-users
# downloading the release want a playable joypad tester. The
# eyes build still lives in gba/build/ for source-tree
# consumers but doesn't get a Release attachment.
# Flash-cart-friendly name: joypad_tester_v<ver>.gba.
VERSION="${{ needs.parse.outputs.version }}"
mkdir -p _release
cp build/tester/tester_mb.gba "_release/joypad_tester_v${VERSION}.gba"
ls -la _release/
- name: Stage release artifacts (PC Engine)
if: needs.parse.outputs.console == 'pce'
working-directory: pce
run: |
# Flash-cart-friendly name: joypad_tester_v<ver>.pce.
VERSION="${{ needs.parse.outputs.version }}"
mkdir -p _release
cp build/joypad-tester.pce "_release/joypad_tester_v${VERSION}.pce"
ls -la _release/
- name: Stage release artifacts (3DO)
if: needs.parse.outputs.console == '3do'
working-directory: 3do
run: |
# Single .iso artifact; emulators + flash-friendly burn target.
VERSION="${{ needs.parse.outputs.version }}"
mkdir -p _release
cp build/joypad-tester.iso "_release/joypad_tester_v${VERSION}_3do.iso"
ls -la _release/
- name: Build release body
id: notes
working-directory: ${{ needs.parse.outputs.console }}
run: |
# Keep the release page tight: the CHANGELOG file is the
# source of truth for the version-by-version highlights,
# and the Assets section below auto-lists every attached
# artifact. The body just points at the changelog.
CONSOLE="${{ needs.parse.outputs.console }}"
REPO="https://github.com/${{ github.repository }}"
# Skip the anchor -- GitHub munges header text (drops dots,
# collapses em-dashes) so #v1.0.0--2026-05-11 etc. doesn't
# match deterministically. Top of the changelog is the
# latest version anyway.
cat > _notes.md <<EOF
See [\`${CONSOLE}/CHANGELOG.md\`](${REPO}/blob/main/${CONSOLE}/CHANGELOG.md) for release details.
EOF
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: ${{ needs.parse.outputs.title }}
body_path: ${{ needs.parse.outputs.console }}/_notes.md
draft: false
prerelease: ${{ contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-rc') }}
files: ${{ needs.parse.outputs.console }}/_release/*