mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
migrate bundling logic into psxrecomp, thinner layer at game level
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
name: Fetch psxrecomp portable toolchain
|
||||
description: >
|
||||
Download cmake-clang-v1 from retcomm-toolchains and export TOOLCHAIN_DIR /
|
||||
PSXRECOMP_TOOLCHAIN_DIR / BPE_TOOLCHAIN_DIR for later packaging steps.
|
||||
|
||||
inputs:
|
||||
artifact:
|
||||
description: Platform pack id (linux-x64, windows-x64, macos-arm64, macos-x64)
|
||||
required: true
|
||||
framework:
|
||||
description: Path to the psxrecomp checkout (contains tools/fetch_toolchain.sh)
|
||||
required: false
|
||||
default: psxrecomp
|
||||
dl-dir:
|
||||
description: Download cache directory
|
||||
required: false
|
||||
default: .cache/toolchain-dl
|
||||
out-dir:
|
||||
description: Unpack directory
|
||||
required: false
|
||||
default: .cache/toolchain-pack
|
||||
repo:
|
||||
description: GitHub owner/name for toolchain releases
|
||||
required: false
|
||||
default: TechnicallyComputers/retcomm-toolchains
|
||||
|
||||
outputs:
|
||||
toolchain-dir:
|
||||
description: Absolute path to the unpacked toolchain root (bin/ present)
|
||||
value: ${{ steps.fetch.outputs.toolchain-dir }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- id: fetch
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SCRIPT="${{ inputs.framework }}/tools/fetch_toolchain.sh"
|
||||
if [[ ! -f "${SCRIPT}" ]]; then
|
||||
echo "::error::missing ${SCRIPT}"
|
||||
exit 1
|
||||
fi
|
||||
chmod +x "${SCRIPT}"
|
||||
bash "${SCRIPT}" \
|
||||
--artifact "${{ inputs.artifact }}" \
|
||||
--dl-dir "${{ inputs.dl-dir }}" \
|
||||
--out-dir "${{ inputs.out-dir }}" \
|
||||
--repo "${{ inputs.repo }}"
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Stage psxrecomp setup SDK
|
||||
description: >
|
||||
Copy emitters, OpenBIOS profiles, retcomm-sdk.json, portable toolchain, and
|
||||
Windows MinGW runtime DLLs into an existing setup-host stage directory.
|
||||
|
||||
inputs:
|
||||
stage:
|
||||
description: Absolute or workspace-relative stage directory
|
||||
required: true
|
||||
framework:
|
||||
description: Path to the psxrecomp checkout
|
||||
required: false
|
||||
default: psxrecomp
|
||||
sdk-overlay:
|
||||
description: Optional overlay directory (e.g. psxrecomp-sdk)
|
||||
required: false
|
||||
default: ''
|
||||
recompiler-build:
|
||||
description: Build dir containing psxrecomp-game / psxrecomp-bios
|
||||
required: false
|
||||
default: build-recompiler
|
||||
toolchain-dir:
|
||||
description: Unpacked toolchain root (defaults to env PSXRECOMP_TOOLCHAIN_DIR / TOOLCHAIN_DIR / BPE_TOOLCHAIN_DIR)
|
||||
required: false
|
||||
default: ''
|
||||
host-exe:
|
||||
description: Optional host .exe path for Windows DLL bundling
|
||||
required: false
|
||||
default: ''
|
||||
runtime-bin:
|
||||
description: MinGW bin dir for runtime DLLs (Windows)
|
||||
required: false
|
||||
default: ''
|
||||
allow-no-toolchain:
|
||||
description: If true, warn instead of failing when toolchain is unset
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SCRIPT="${{ inputs.framework }}/tools/stage_setup_sdk.sh"
|
||||
if [[ ! -f "${SCRIPT}" ]]; then
|
||||
echo "::error::missing ${SCRIPT}"
|
||||
exit 1
|
||||
fi
|
||||
chmod +x "${SCRIPT}"
|
||||
args=(
|
||||
--stage "${{ inputs.stage }}"
|
||||
--framework "${{ inputs.framework }}"
|
||||
--recompiler-build "${{ inputs.recompiler-build }}"
|
||||
)
|
||||
if [[ -n "${{ inputs.sdk-overlay }}" ]]; then
|
||||
args+=(--sdk-overlay "${{ inputs.sdk-overlay }}")
|
||||
fi
|
||||
if [[ -n "${{ inputs.toolchain-dir }}" ]]; then
|
||||
args+=(--toolchain-dir "${{ inputs.toolchain-dir }}")
|
||||
fi
|
||||
if [[ "${{ inputs.allow-no-toolchain }}" == "true" ]]; then
|
||||
args+=(--allow-no-toolchain)
|
||||
fi
|
||||
if [[ -n "${{ inputs.host-exe }}" ]]; then
|
||||
args+=(--host-exe "${{ inputs.host-exe }}")
|
||||
fi
|
||||
if [[ -n "${{ inputs.runtime-bin }}" ]]; then
|
||||
args+=(--runtime-bin "${{ inputs.runtime-bin }}")
|
||||
fi
|
||||
bash "${SCRIPT}" "${args[@]}"
|
||||
+11
-9
@@ -16,20 +16,22 @@ New to the codebase? Read [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) and
|
||||
(`recompiler/`), the runtime engine (`runtime/`), shared tools, and docs.
|
||||
Framework changes go here.
|
||||
- **Per-game repos** (`mstan/TombaRecomp`, `mstan/MegaManX6Recomp`,
|
||||
`mstan/ApeEscapeRecomp`, …) — one per title. Each contains only that game's
|
||||
config, seeds, and build glue and links this framework in as a submodule at
|
||||
`psxrecomp/`. Game-specific work goes in the game repo; nothing game-specific
|
||||
belongs in the framework.
|
||||
`mstan/ApeEscapeRecomp`, …) — one per title. Each keeps **game code at the
|
||||
repo root** and pins this framework (and usually `recomp-ui`) as **root-level
|
||||
submodules** (`psxrecomp/`, `recomp-ui/`). Game-specific work goes in the game
|
||||
repo; nothing game-specific belongs in the framework.
|
||||
|
||||
Each game repo pins an **exact framework commit** — the git submodule pointer at
|
||||
`psxrecomp/` (some older game repos also carry a human-readable `psxrecomp-v4.pin`
|
||||
record; the submodule pointer is the source of truth). The framework evolves on
|
||||
its own cadence; a game only moves to a newer framework when someone deliberately
|
||||
bumps that submodule pointer. See
|
||||
[Linking the framework](docs/BUILDING.md#linking-the-framework) for the submodule
|
||||
+ local-junction dev setup, and
|
||||
[Framework changes and the pin](#framework-changes-and-the-pin) below for how a
|
||||
framework change actually reaches a game.
|
||||
bumps that submodule pointer.
|
||||
|
||||
**Starting a new title or shipping a setup-host zip?** Follow
|
||||
[`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md) (layout, CI template
|
||||
under `docs/ci/templates/setup-release.yml`, release checklist). Also see
|
||||
[Linking the framework](docs/BUILDING.md#linking-the-framework) and
|
||||
[Framework changes and the pin](#framework-changes-and-the-pin) below.
|
||||
|
||||
## The core rules
|
||||
|
||||
|
||||
@@ -55,8 +55,10 @@ standalone BIOS runtime supporting OpenBIOS and a compatible retail BIOS** —
|
||||
see [Release Package](#release-package) below.
|
||||
|
||||
Bringing up a title of your own? Start with
|
||||
[`CONTRIBUTING.md`](CONTRIBUTING.md) and open an issue — community projects are
|
||||
listed here alongside the rest.
|
||||
[`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md) (submodules,
|
||||
setup-host CI template, release checklist), then
|
||||
[`CONTRIBUTING.md`](CONTRIBUTING.md). Community projects are listed here
|
||||
alongside the rest.
|
||||
|
||||
## What It Is
|
||||
|
||||
@@ -95,15 +97,22 @@ Three things sit on that foundation:
|
||||
execution.
|
||||
|
||||
PSXRecomp is a **framework**. Game-specific projects live in their own
|
||||
repositories and link this one in as a **git submodule** to build a game binary.
|
||||
repositories with **`psxrecomp/` and `recomp-ui/` as root-level submodules**
|
||||
and game code (`game.toml`, seeds, CMake) at the repo root. See
|
||||
[`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md).
|
||||
|
||||
**New here?** The fastest way in:
|
||||
[`docs/EXECUTION_MODEL.md`](docs/EXECUTION_MODEL.md) (how a game actually
|
||||
runs — static / native-overlay / interpreter), then
|
||||
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md),
|
||||
[`docs/BUILDING.md`](docs/BUILDING.md),
|
||||
[`docs/MOD_PACKAGES.md`](docs/MOD_PACKAGES.md) (versioned runtime mods),
|
||||
[`CONTRIBUTING.md`](CONTRIBUTING.md).
|
||||
|
||||
| Path | Doc |
|
||||
|------|-----|
|
||||
| How a game runs | [`docs/EXECUTION_MODEL.md`](docs/EXECUTION_MODEL.md) |
|
||||
| Architecture | [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) |
|
||||
| Build the framework | [`docs/BUILDING.md`](docs/BUILDING.md) |
|
||||
| **Ship a game repo** (submodules + CI + release checklist) | [`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md) |
|
||||
| Setup-host CI template | [`docs/ci/templates/setup-release.yml`](docs/ci/templates/setup-release.yml) |
|
||||
| Local Generate & rebuild CLI | [`docs/LOCAL_CODEGEN_SDK.md`](docs/LOCAL_CODEGEN_SDK.md) |
|
||||
| Mods | [`docs/MOD_PACKAGES.md`](docs/MOD_PACKAGES.md) |
|
||||
| Contributing | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
|
||||
|
||||
## Which PlayStation BIOS does it use?
|
||||
|
||||
@@ -709,13 +718,16 @@ the selected faithfully recompiled BIOS — OpenBIOS or retail BIOS — is the
|
||||
baseline and oracle, generated code is never hand-edited (fix the recompiler and
|
||||
regenerate), and a change proves itself against the Beetle oracle / on screen
|
||||
rather than by assertion. Game-specific work lives in the game repos, which pin
|
||||
an exact framework commit as a submodule.
|
||||
exact framework and UI commits as root-level submodules.
|
||||
|
||||
Read [`CONTRIBUTING.md`](CONTRIBUTING.md) before opening a PR — it covers the core
|
||||
rules, how to verify a change, the regression checklist across the known games,
|
||||
and how a framework fix reaches a game through its pin. Bugs and build problems go
|
||||
to GitHub issues (include `gcc -v` / OS / generator for build failures); design
|
||||
discussion happens in the **R.A.I.D.** Discord (invite below).
|
||||
- New title / setup-host release:
|
||||
[`docs/GAME_PROJECT_SETUP.md`](docs/GAME_PROJECT_SETUP.md)
|
||||
- Framework PRs: [`CONTRIBUTING.md`](CONTRIBUTING.md) (rules, verification,
|
||||
regression checklist, how a fix reaches a game through its pin)
|
||||
|
||||
Bugs and build problems go to GitHub issues (include `gcc -v` / OS / generator
|
||||
for build failures); design discussion happens in the **R.A.I.D.** Discord
|
||||
(invite below).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
# Bringing up a game project on PSXRecomp
|
||||
|
||||
This guide is for **title developers**: you keep game code and config in your
|
||||
own repository, pin this framework and `recomp-ui` as **root-level submodules**,
|
||||
and (optionally) ship a **setup-host** zip that users generate from a legal disc
|
||||
locally — the same model as Bomberman Party Edition / Masters of Teräs Käsi.
|
||||
|
||||
Related docs:
|
||||
|
||||
| Doc | When |
|
||||
|-----|------|
|
||||
| [`BUILDING.md`](BUILDING.md) | Local build of the framework / BIOS runtime |
|
||||
| [`LOCAL_CODEGEN_SDK.md`](LOCAL_CODEGEN_SDK.md) | CLI contract for Generate & rebuild |
|
||||
| [`ci/README.md`](ci/README.md) | Shared CI scripts and composite actions |
|
||||
| [`CONTRIBUTING.md`](../CONTRIBUTING.md) | Framework contribution rules |
|
||||
|
||||
---
|
||||
|
||||
## Recommended repository layout
|
||||
|
||||
Everything game-specific lives at the **root** of *your* title repo. Framework
|
||||
and UI are submodules next to that code — not nested under each other.
|
||||
|
||||
```text
|
||||
YourGameRecomp/ # your git repo
|
||||
├── .gitmodules
|
||||
├── CMakeLists.txt # game target + setup-host options
|
||||
├── game.toml # disc / recompiler / runtime config
|
||||
├── VERSION # release pin (e.g. 0.1.0)
|
||||
├── seeds/ # function-start seeds for psxrecomp-game
|
||||
├── host/ # optional: codegen host glue for the wizard
|
||||
├── codegen_setup.c / .h # optional: PSX_HAS_GAME_CODEGEN wiring
|
||||
├── launcher_assets/ # boxart / fonts / brand (recomp-ui)
|
||||
├── scripts/
|
||||
│ └── package_setup_release.sh # title packager (thin; calls psxrecomp tools)
|
||||
├── .github/workflows/
|
||||
│ └── release.yml # copied from docs/ci/templates/setup-release.yml
|
||||
├── psxrecomp/ # submodule → mstan/psxrecomp (pinned commit)
|
||||
├── recomp-ui/ # submodule → TechnicallyComputers/recomp-ui
|
||||
├── psxrecomp-sdk/ # optional: CLI / prepare_disc overlay for zips
|
||||
├── generated/ # local only — gitignore (not in CI setup zip)
|
||||
└── prepared_disc/ / disc/ # local disc working tree — gitignore
|
||||
```
|
||||
|
||||
### Add the submodules
|
||||
|
||||
```bash
|
||||
cd YourGameRecomp
|
||||
git submodule add https://github.com/mstan/psxrecomp.git psxrecomp
|
||||
git submodule add https://github.com/TechnicallyComputers/recomp-ui.git recomp-ui
|
||||
# optional SDK surface used by RetComM / setup zips:
|
||||
# git submodule add <your-sdk-url> psxrecomp-sdk
|
||||
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
Pin **exact commits** (do not track `main` floating). Bump deliberately when you
|
||||
want a newer framework or UI.
|
||||
|
||||
Clone for contributors:
|
||||
|
||||
```bash
|
||||
git clone --recurse-submodules https://github.com/you/YourGameRecomp.git
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local development loop
|
||||
|
||||
1. **Legal disc** — place your dump where `game.toml` expects it (or pass
|
||||
`--disc` to the CLI / wizard). Never commit disc images or retail BIOS dumps.
|
||||
2. **Build emitters** (once per machine / after recompiler changes):
|
||||
|
||||
```bash
|
||||
cmake -S psxrecomp/recompiler -B build-recompiler -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build-recompiler --target psxrecomp-game psxrecomp-bios -j"$(nproc)"
|
||||
```
|
||||
|
||||
3. **Generate game (+ OpenBIOS) C** — either the setup wizard, or:
|
||||
|
||||
```bash
|
||||
python3 psxrecomp/psxrecomp_cli.py generate \
|
||||
--config game.toml --project-root . --disc /path/to/dump.cue
|
||||
```
|
||||
|
||||
4. **Build the playable runtime**:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build-release --target psx-runtime -j"$(nproc)"
|
||||
```
|
||||
|
||||
Details: [`LOCAL_CODEGEN_SDK.md`](LOCAL_CODEGEN_SDK.md), [`BUILDING.md`](BUILDING.md).
|
||||
|
||||
---
|
||||
|
||||
## Setup-host releases (self-build zip)
|
||||
|
||||
CI ships a zip **without** `generated/` game C and **without** retail BIOS
|
||||
dumps. The zip includes:
|
||||
|
||||
- Setup host exe (`recomp-ui` + generate/rebuild wizard)
|
||||
- Your game sources (`game.toml`, seeds, CMake, host glue, …)
|
||||
- `psxrecomp/` (runtime + CLI + OpenBIOS profiles + emitters)
|
||||
- `recomp-ui/` sources (needed to rebuild)
|
||||
- `toolchain/` — portable `cmake-clang-v1` from
|
||||
[retcomm-toolchains](https://github.com/TechnicallyComputers/retcomm-toolchains)
|
||||
- On Windows: MinGW runtime DLLs beside the host and emitters
|
||||
|
||||
Players (or [RetComM](https://github.com/TechnicallyComputers/RetComM-Launcher))
|
||||
run Generate & rebuild locally. After a successful rebuild the CLI can prune
|
||||
`toolchain/` and build intermediates.
|
||||
|
||||
Do **not** set `PSX_PGO` in CI. PGO stays user-local when `[pgo] enabled = true`.
|
||||
|
||||
---
|
||||
|
||||
## CI workflow template
|
||||
|
||||
1. Copy the template into your title repo:
|
||||
|
||||
```bash
|
||||
mkdir -p .github/workflows
|
||||
cp psxrecomp/docs/ci/templates/setup-release.yml .github/workflows/release.yml
|
||||
```
|
||||
|
||||
2. Replace every `YOUR_*` placeholder (exe name, zip prefix, cmake setup flag,
|
||||
release title, package script path).
|
||||
3. Keep using the shared actions — do not reimplement toolchain fetch or DLL
|
||||
bundling:
|
||||
|
||||
```yaml
|
||||
- uses: ./psxrecomp/.github/actions/fetch-toolchain
|
||||
with:
|
||||
artifact: ${{ matrix.artifact }}
|
||||
```
|
||||
|
||||
4. Your `scripts/package_setup_release.sh` should:
|
||||
- Stage the host exe, assets, and game sources
|
||||
- Copy filtered `psxrecomp/` + `recomp-ui/`
|
||||
- Call `psxrecomp/tools/stage_setup_sdk.sh` (emitters, OpenBIOS checks,
|
||||
`toolchain/`, MinGW DLLs)
|
||||
|
||||
Full action reference: [`ci/README.md`](ci/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Bundled release checklist
|
||||
|
||||
Use this before tagging a setup-host release that matches other titles
|
||||
(BPE / MotK / RetComM).
|
||||
|
||||
### Repository
|
||||
|
||||
- [ ] `psxrecomp/` and `recomp-ui/` are root-level submodules on pinned commits
|
||||
- [ ] `game.toml` has disc identity (`[prepare_disc]` hashes/sizes) and boot EXE
|
||||
- [ ] Seeds cover the boot path; `VERSION` matches the release you will tag
|
||||
- [ ] Disc images, `generated/`, and `SCPH1001.BIN` are gitignored
|
||||
- [ ] Setup-host CMake path builds with **no** game C and **no** BIOS backends
|
||||
(e.g. `-DPSXRECOMP_ALLOW_NO_BIOS=ON` + your title’s force-setup option)
|
||||
- [ ] Codegen host / `PSX_HAS_GAME_CODEGEN` wired so the wizard can regenerate
|
||||
|
||||
### Packaging (shared helpers)
|
||||
|
||||
- [ ] CI builds `psxrecomp-game` and `psxrecomp-bios` into `build-recompiler/`
|
||||
- [ ] CI uses `./psxrecomp/.github/actions/fetch-toolchain` for the matrix OS
|
||||
- [ ] Packager calls `psxrecomp/tools/stage_setup_sdk.sh` with
|
||||
`--recompiler-build`, `--toolchain-dir` (or env from fetch), and on
|
||||
Windows `--runtime-bin /mingw64/bin` + `--host-exe`
|
||||
- [ ] Staged tree includes `psxrecomp/psxrecomp_cli.py` (sdk overlay if needed)
|
||||
- [ ] Staged `psxrecomp/bios/` has `OpenBIOS.toml`, `openbios.bin`,
|
||||
`OpenBIOS.LICENSE`, `SCPH1001.toml` — and **no** retail `.BIN`
|
||||
- [ ] Windows zip has `libstdc++-6.dll` + `libgcc_s_seh-1.dll` next to both
|
||||
emitters (and host deps such as `zlib1.dll` next to the exe)
|
||||
- [ ] Zip mtimes are normalized (packager `touch`) so Ninja does not see
|
||||
“future” files after extract
|
||||
|
||||
### Zip contents smoke test
|
||||
|
||||
- [ ] Linux / macOS / Windows artifacts named consistently
|
||||
(`YOUR_PREFIX-<ver>-<linux-x64|windows-x64|macos-arm64|macos-x64>.zip`)
|
||||
- [ ] Extract → run host → Generate with a legal disc succeeds end-to-end
|
||||
- [ ] After rebuild, game launches; saves/settings land beside the exe
|
||||
- [ ] RetComM install/update (if you publish a catalog entry) uses this **same**
|
||||
zip — no separate tools pack required
|
||||
|
||||
### Publish
|
||||
|
||||
- [ ] Tag `vX.Y.Z`; GitHub Release attaches all four platform zips
|
||||
- [ ] Release notes say the zip is a setup host (no disc / no retail BIOS /
|
||||
no pre-generated game C)
|
||||
- [ ] Catalog / RetComM entry points at the release assets when ready
|
||||
@@ -20,7 +20,12 @@ then game C, then rebuild links everything.
|
||||
|-------|------|
|
||||
| Setup exe | `recomp-ui` + codegen host; opens Generate & rebuild |
|
||||
| Game zip `psxrecomp/` | `psxrecomp_cli.py`, `tools/`, `psxrecomp-game` + `psxrecomp-bios` |
|
||||
| `tools/fetch_toolchain.sh` | CI: download/unpack portable `cmake-clang-v1` |
|
||||
| `tools/stage_setup_sdk.sh` | CI/pack: emitters, OpenBIOS checks, embed `toolchain/`, MinGW DLLs |
|
||||
| `tools/bundle_mingw_dlls.sh` | Windows: copy MinGW runtime DLLs next to host + emitters |
|
||||
| Game zip `toolchain/` | Portable `cmake-clang-v1` (cmake/ninja/clang); pruned after rebuild |
|
||||
| `docs/ci/` | Composite actions + [`templates/setup-release.yml`](ci/templates/setup-release.yml) |
|
||||
| `docs/GAME_PROJECT_SETUP.md` | Submodules, CI template usage, bundled-release checklist |
|
||||
| Game sources | `game.toml`, seeds, `CMakeLists.txt`, `psxrecomp/`, `recomp-ui/` |
|
||||
|
||||
RetComM harvests emitters + toolchain into shared caches (no separate tools
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# CI helpers for setup-host releases
|
||||
|
||||
Shared scripts and GitHub Actions so new PSXRecomp titles do not reimplement
|
||||
toolchain fetch, emitter staging, or Windows MinGW DLL bundling.
|
||||
|
||||
**Start here for a new game repo:**
|
||||
[`../GAME_PROJECT_SETUP.md`](../GAME_PROJECT_SETUP.md)
|
||||
|
||||
## Workflow template
|
||||
|
||||
Copy into the **game** repository (not into this framework alone):
|
||||
|
||||
```bash
|
||||
mkdir -p .github/workflows
|
||||
cp psxrecomp/docs/ci/templates/setup-release.yml .github/workflows/release.yml
|
||||
# edit YOUR_* placeholders
|
||||
```
|
||||
|
||||
Template path: [`templates/setup-release.yml`](templates/setup-release.yml)
|
||||
|
||||
A shorter stub also lives at [`setup-release.yml.example`](setup-release.yml.example)
|
||||
(points at the full template).
|
||||
|
||||
## Tools
|
||||
|
||||
| Script | Role |
|
||||
|--------|------|
|
||||
| `tools/fetch_toolchain.sh` | Download/unpack `cmake-clang-v1` from retcomm-toolchains |
|
||||
| `tools/stage_setup_sdk.sh` | Stage emitters, OpenBIOS checks, `toolchain/`, MinGW DLLs |
|
||||
| `tools/bundle_mingw_dlls.sh` | Copy imported non-system DLLs next to Windows PEs |
|
||||
|
||||
## Composite actions
|
||||
|
||||
Use from a game repo after `actions/checkout` with `submodules: recursive`:
|
||||
|
||||
```yaml
|
||||
- uses: ./psxrecomp/.github/actions/fetch-toolchain
|
||||
with:
|
||||
artifact: ${{ matrix.artifact }} # linux-x64 | windows-x64 | macos-arm64 | macos-x64
|
||||
|
||||
- uses: ./psxrecomp/.github/actions/stage-setup-sdk
|
||||
with:
|
||||
stage: dist/stage-setup-${{ matrix.artifact }}
|
||||
sdk-overlay: psxrecomp-sdk
|
||||
recompiler-build: build-recompiler
|
||||
runtime-bin: /mingw64/bin # Windows / MSYS2 only
|
||||
```
|
||||
|
||||
`fetch-toolchain` exports `TOOLCHAIN_DIR`, `PSXRECOMP_TOOLCHAIN_DIR`, and
|
||||
`BPE_TOOLCHAIN_DIR` via `GITHUB_ENV` for later steps.
|
||||
|
||||
## Title responsibilities
|
||||
|
||||
Keep in the game repo:
|
||||
|
||||
- Setup-host CMake flags and exe basename
|
||||
- Copying game sources / assets into the stage
|
||||
- Zip naming and release notes
|
||||
- Thin `scripts/package_setup_release.sh` that stages title files then calls
|
||||
`psxrecomp/tools/stage_setup_sdk.sh`
|
||||
|
||||
## Release checklist
|
||||
|
||||
See the **Bundled release checklist** in
|
||||
[`../GAME_PROJECT_SETUP.md`](../GAME_PROJECT_SETUP.md#bundled-release-checklist).
|
||||
@@ -0,0 +1,23 @@
|
||||
# Deprecated stub — use the full template:
|
||||
#
|
||||
# docs/ci/templates/setup-release.yml
|
||||
#
|
||||
# Install:
|
||||
# cp psxrecomp/docs/ci/templates/setup-release.yml .github/workflows/release.yml
|
||||
#
|
||||
# Guide: docs/GAME_PROJECT_SETUP.md
|
||||
# Actions: docs/ci/README.md
|
||||
|
||||
name: Release builds (see docs/ci/templates/setup-release.yml)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
redirect:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- run: |
|
||||
echo "Copy psxrecomp/docs/ci/templates/setup-release.yml to .github/workflows/release.yml"
|
||||
echo "See psxrecomp/docs/GAME_PROJECT_SETUP.md"
|
||||
exit 1
|
||||
@@ -0,0 +1,270 @@
|
||||
# Setup-host multi-platform release template for a PSXRecomp *game* repository.
|
||||
#
|
||||
# Install:
|
||||
# mkdir -p .github/workflows
|
||||
# cp psxrecomp/docs/ci/templates/setup-release.yml .github/workflows/release.yml
|
||||
# Then replace every YOUR_* token. See docs/GAME_PROJECT_SETUP.md.
|
||||
#
|
||||
# Shared (do not fork these into the title):
|
||||
# ./psxrecomp/.github/actions/fetch-toolchain
|
||||
# ./psxrecomp/.github/actions/stage-setup-sdk
|
||||
# psxrecomp/tools/{fetch_toolchain,stage_setup_sdk,bundle_mingw_dlls}.sh
|
||||
#
|
||||
# Title-owned:
|
||||
# CMake setup-host flags, exe basename, zip prefix, package_setup_release.sh
|
||||
|
||||
name: Release builds
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version (e.g. 0.1.0 or v0.1.0)'
|
||||
required: true
|
||||
type: string
|
||||
publish:
|
||||
description: 'Create git tag vX.Y.Z and GitHub Release'
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
concurrency:
|
||||
group: yourgame-release-${{ github.event_name }}-${{ github.ref }}-${{ github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
CMAKE_BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: Resolve version
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
version: ${{ steps.meta.outputs.version }}
|
||||
tag: ${{ steps.meta.outputs.tag }}
|
||||
publish: ${{ steps.meta.outputs.publish }}
|
||||
steps:
|
||||
- name: Normalize version / tag
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
RAW="${{ github.event.inputs.version }}"
|
||||
PUBLISH="${{ github.event.inputs.publish }}"
|
||||
else
|
||||
RAW="${{ github.ref_name }}"
|
||||
PUBLISH="true"
|
||||
fi
|
||||
RAW="$(printf '%s' "${RAW}" | tr -d '[:space:]')"
|
||||
VER="${RAW#v}"
|
||||
if [[ ! "${VER}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.+-][A-Za-z0-9.+-]*)?$ ]]; then
|
||||
echo "::error::invalid version '${RAW}' (want X.Y.Z or vX.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
echo "version=${VER}" >>"$GITHUB_OUTPUT"
|
||||
echo "tag=v${VER}" >>"$GITHUB_OUTPUT"
|
||||
echo "publish=${PUBLISH}" >>"$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: ${{ matrix.artifact }}
|
||||
needs: prepare
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
artifact: linux-x64
|
||||
shell: bash
|
||||
- os: windows-2022
|
||||
artifact: windows-x64
|
||||
shell: 'msys2 {0}'
|
||||
- os: macos-15
|
||||
artifact: macos-arm64
|
||||
shell: bash
|
||||
deployment_target: '11.0'
|
||||
- os: macos-15-intel
|
||||
artifact: macos-x64
|
||||
shell: bash
|
||||
deployment_target: '11.0'
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell }}
|
||||
env:
|
||||
# Rename to match your packager (e.g. BPE_RELEASE_VERSION).
|
||||
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Pin VERSION
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
printf '%s\n' "${RELEASE_VERSION}" >VERSION
|
||||
echo "VERSION=$(cat VERSION)"
|
||||
|
||||
- name: Setup host mode (no game C / BIOS backends in CI)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf generated psxrecomp/generated
|
||||
echo "Cleared generated/ — CI builds a BIOS-free setup host."
|
||||
|
||||
- name: Setup MSYS2 (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: MINGW64
|
||||
update: true
|
||||
install: >-
|
||||
mingw-w64-x86_64-toolchain
|
||||
mingw-w64-x86_64-cmake
|
||||
mingw-w64-x86_64-ninja
|
||||
mingw-w64-x86_64-SDL2
|
||||
mingw-w64-x86_64-pkgconf
|
||||
curl
|
||||
zip
|
||||
|
||||
- name: Install deps (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
build-essential cmake ninja-build pkg-config \
|
||||
libsdl2-dev libgl1-mesa-dev zip
|
||||
|
||||
- name: Install deps (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew install cmake ninja pkg-config sdl3 || brew install cmake ninja pkg-config sdl2
|
||||
|
||||
- name: Build recompiler tools (emitters)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
JOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
|
||||
if [[ -d /mingw64/bin ]]; then
|
||||
export PATH="/mingw64/bin:${PATH}"
|
||||
fi
|
||||
cmake -S psxrecomp/recompiler -B build-recompiler -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build-recompiler \
|
||||
--target psxrecomp-game psxrecomp-bios -j"${JOBS}"
|
||||
for bin in psxrecomp-bios psxrecomp-game; do
|
||||
if [[ -f "build-recompiler/${bin}.exe" ]]; then
|
||||
chmod +x "build-recompiler/${bin}.exe"
|
||||
elif [[ -f "build-recompiler/${bin}" ]]; then
|
||||
chmod +x "build-recompiler/${bin}"
|
||||
else
|
||||
echo "::error::${bin} missing after recompiler build"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Configure & build setup host
|
||||
env:
|
||||
CMAKE_OSX_DEPLOYMENT_TARGET: ${{ matrix.deployment_target || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
EXTRA=()
|
||||
if [[ -n "${CMAKE_OSX_DEPLOYMENT_TARGET:-}" ]]; then
|
||||
EXTRA+=("-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||
fi
|
||||
JOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
|
||||
# YOUR_SETUP_CMAKE_FLAGS: title-specific. Example from BPE:
|
||||
# -DYOURGAME_FORCE_SETUP_HOST=ON -DPSXRECOMP_ALLOW_NO_BIOS=ON
|
||||
cmake -S . -B build-ci -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DPSXRECOMP_ALLOW_NO_BIOS=ON \
|
||||
-DYOURGAME_FORCE_SETUP_HOST=ON \
|
||||
-DPSX_GAME_VERSION="${RELEASE_VERSION}" \
|
||||
"${EXTRA[@]}"
|
||||
cmake --build build-ci --target psx-runtime -j"${JOBS}"
|
||||
|
||||
- name: Fetch portable toolchain pack
|
||||
uses: ./psxrecomp/.github/actions/fetch-toolchain
|
||||
with:
|
||||
artifact: ${{ matrix.artifact }}
|
||||
|
||||
- name: Package setup zip
|
||||
run: |
|
||||
set -euo pipefail
|
||||
chmod +x scripts/package_setup_release.sh
|
||||
if [[ "${{ runner.os }}" == "Windows" ]]; then
|
||||
export PSXRECOMP_RUNTIME_BIN_DIR=/mingw64/bin
|
||||
export BPE_RUNTIME_BIN_DIR=/mingw64/bin
|
||||
fi
|
||||
export RELEASE_VERSION
|
||||
# Packager stages title files, then calls stage_setup_sdk.sh.
|
||||
if [[ -z "${PSXRECOMP_TOOLCHAIN_DIR:-${TOOLCHAIN_DIR:-${BPE_TOOLCHAIN_DIR:-}}}" ]]; then
|
||||
echo "::error::toolchain dir not set — fetch-toolchain failed?"
|
||||
exit 1
|
||||
fi
|
||||
scripts/package_setup_release.sh build-ci "${{ matrix.artifact }}" build-recompiler
|
||||
ls -la dist/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
# YOUR_ZIP_PREFIX e.g. bpe, motk, yourgame
|
||||
name: YOUR_ZIP_PREFIX-${{ matrix.artifact }}
|
||||
path: dist/YOUR_ZIP_PREFIX-*-${{ matrix.artifact }}.zip
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
release:
|
||||
name: GitHub Release
|
||||
needs: [prepare, build]
|
||||
if: needs.prepare.outputs.publish == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Flatten zips
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p dist
|
||||
find artifacts -type f -name 'YOUR_ZIP_PREFIX-*.zip' -exec cp -v {} dist/ \;
|
||||
ls -la dist/
|
||||
|
||||
- name: Ensure release tag exists
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
TAG: ${{ needs.prepare.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
if ! git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||
git tag -a "${TAG}" -m "YOUR_GAME_TITLE ${TAG}"
|
||||
fi
|
||||
if ! git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
|
||||
git push origin "refs/tags/${TAG}"
|
||||
fi
|
||||
|
||||
- name: Publish release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ needs.prepare.outputs.tag }}
|
||||
name: YOUR_GAME_TITLE ${{ needs.prepare.outputs.tag }}
|
||||
files: |
|
||||
dist/YOUR_ZIP_PREFIX-*.zip
|
||||
generate_release_notes: true
|
||||
fail_on_unmatched_files: true
|
||||
target_commitish: ${{ github.sha }}
|
||||
Executable
+225
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bundle imported non-system DLLs next to Windows PE executables.
|
||||
#
|
||||
# Title packagers (BPE/MotK setup zips) call this so MSYS2 MinGW emitters
|
||||
# (psxrecomp-game / psxrecomp-bios) ship with libstdc++/libgcc — the portable
|
||||
# llvm-mingw toolchain does not provide those GCC runtimes.
|
||||
#
|
||||
# Usage:
|
||||
# bundle_mingw_dlls.sh [options] --exe <path> [--dest <dir>] [--label <name>] ...
|
||||
#
|
||||
# Options:
|
||||
# --runtime-bin DIR Preferred DLL search directory (repeatable)
|
||||
# --search-dir DIR Extra DLL search directory (repeatable)
|
||||
# --exe PATH PE to inspect (repeatable; --dest/--label apply to it)
|
||||
# --dest DIR Copy DLLs here (default: dirname of the preceding --exe)
|
||||
# --label NAME Log label for the preceding --exe
|
||||
# --require DLL After bundling, DLL must exist in every --dest that
|
||||
# imported it (repeatable)
|
||||
# --soft-missing Warn instead of exit 1 when objdump is unavailable
|
||||
#
|
||||
# Exit 1 if an imported DLL cannot be found, or a --require check fails.
|
||||
set -euo pipefail
|
||||
|
||||
SOFT_MISSING=0
|
||||
RUNTIME_BINS=()
|
||||
SEARCH_DIRS=()
|
||||
REQUIRE=()
|
||||
|
||||
# Parallel arrays for targets.
|
||||
EXE_PATHS=()
|
||||
DEST_DIRS=()
|
||||
LABELS=()
|
||||
|
||||
pending_dest=""
|
||||
pending_label=""
|
||||
|
||||
usage() {
|
||||
sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 2
|
||||
}
|
||||
|
||||
flush_pending_meta() {
|
||||
# Apply trailing --dest/--label to the last --exe.
|
||||
local n="${#EXE_PATHS[@]}"
|
||||
if [[ "$n" -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
local i=$((n - 1))
|
||||
if [[ -n "${pending_dest}" ]]; then
|
||||
DEST_DIRS[$i]="${pending_dest}"
|
||||
pending_dest=""
|
||||
fi
|
||||
if [[ -n "${pending_label}" ]]; then
|
||||
LABELS[$i]="${pending_label}"
|
||||
pending_label=""
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help) usage ;;
|
||||
--soft-missing) SOFT_MISSING=1; shift ;;
|
||||
--runtime-bin)
|
||||
RUNTIME_BINS+=("${2:?}"); shift 2 ;;
|
||||
--search-dir)
|
||||
SEARCH_DIRS+=("${2:?}"); shift 2 ;;
|
||||
--require)
|
||||
REQUIRE+=("${2:?}"); shift 2 ;;
|
||||
--exe)
|
||||
flush_pending_meta
|
||||
EXE_PATHS+=("${2:?}")
|
||||
DEST_DIRS+=("")
|
||||
LABELS+=("")
|
||||
shift 2
|
||||
;;
|
||||
--dest)
|
||||
pending_dest="${2:?}"; shift 2 ;;
|
||||
--label)
|
||||
pending_label="${2:?}"; shift 2 ;;
|
||||
*)
|
||||
echo "error: unknown arg: $1" >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
flush_pending_meta
|
||||
|
||||
if [[ ${#EXE_PATHS[@]} -eq 0 ]]; then
|
||||
echo "error: at least one --exe is required" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
OBJDUMP=""
|
||||
if command -v x86_64-w64-mingw32-objdump >/dev/null 2>&1; then
|
||||
OBJDUMP="x86_64-w64-mingw32-objdump"
|
||||
elif command -v objdump >/dev/null 2>&1; then
|
||||
OBJDUMP="objdump"
|
||||
else
|
||||
if [[ "${SOFT_MISSING}" -eq 1 ]]; then
|
||||
echo "warning: no objdump; skipping MinGW DLL bundling" >&2
|
||||
exit 0
|
||||
fi
|
||||
echo "error: no objdump; cannot bundle MinGW DLLs" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SYSTEM_DLL_RE='^(KERNEL32|USER32|GDI32|ADVAPI32|SHELL32|OLE32|OLEAUT32|WS2_32|WINMM|IMM32|SETUPAPI|VERSION|OPENGL32|COMCTL32|COMDLG32|RPCRT4|SHLWAPI|CRYPT32|BCRYPT|IPHLPAPI|NSI|DNSAPI|MSVCRT|UCRTBASE|VCRUNTIME|API-MS-).*\.DLL$'
|
||||
|
||||
PROBE_DLLS=(
|
||||
SDL2.dll
|
||||
zlib1.dll
|
||||
libgcc_s_seh-1.dll
|
||||
libstdc++-6.dll
|
||||
libwinpthread-1.dll
|
||||
libssp-0.dll
|
||||
)
|
||||
|
||||
exe_imports_dll() {
|
||||
local exe="$1"
|
||||
local dll="$2"
|
||||
"${OBJDUMP}" -p "${exe}" 2>/dev/null | grep -qi "DLL Name:[[:space:]]*${dll}"
|
||||
}
|
||||
|
||||
bundle_one() {
|
||||
local exe="$1"
|
||||
local dest_dir="$2"
|
||||
local label="$3"
|
||||
local dll src key cand d
|
||||
local -a needed=()
|
||||
local -a unique=()
|
||||
local -a candidates=()
|
||||
local -A seen=()
|
||||
|
||||
if [[ ! -f "${exe}" ]]; then
|
||||
echo "error: cannot bundle DLLs; missing ${exe}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${dest_dir}" ]]; then
|
||||
dest_dir="$(dirname "${exe}")"
|
||||
fi
|
||||
if [[ -z "${label}" ]]; then
|
||||
label="$(basename "${exe}")"
|
||||
fi
|
||||
mkdir -p "${dest_dir}"
|
||||
|
||||
mapfile -t needed < <(
|
||||
"${OBJDUMP}" -p "${exe}" 2>/dev/null \
|
||||
| awk '/DLL Name:/{print $3}' \
|
||||
| grep -viE "${SYSTEM_DLL_RE}" \
|
||||
| sort -u || true
|
||||
)
|
||||
needed+=("${PROBE_DLLS[@]}")
|
||||
|
||||
for dll in "${needed[@]}"; do
|
||||
[[ -n "${dll}" ]] || continue
|
||||
key="$(printf '%s' "${dll}" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ -n "${seen[$key]:-}" ]]; then
|
||||
continue
|
||||
fi
|
||||
seen[$key]=1
|
||||
unique+=("${dll}")
|
||||
done
|
||||
|
||||
for dll in "${unique[@]}"; do
|
||||
if ! exe_imports_dll "${exe}" "${dll}"; then
|
||||
continue
|
||||
fi
|
||||
src=""
|
||||
candidates=(
|
||||
"$(dirname "${exe}")/${dll}"
|
||||
"${dest_dir}/${dll}"
|
||||
"/mingw64/bin/${dll}"
|
||||
"/usr/x86_64-w64-mingw32/bin/${dll}"
|
||||
)
|
||||
for d in "${SEARCH_DIRS[@]+"${SEARCH_DIRS[@]}"}"; do
|
||||
candidates+=("${d}/${dll}")
|
||||
done
|
||||
for d in "${RUNTIME_BINS[@]+"${RUNTIME_BINS[@]}"}"; do
|
||||
candidates+=("${d}/${dll}")
|
||||
done
|
||||
for cand in "${candidates[@]}"; do
|
||||
[[ -n "${cand}" ]] || continue
|
||||
if [[ -f "${cand}" ]]; then
|
||||
src="${cand}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "${src}" ]]; then
|
||||
echo "error: required DLL missing for ${label}: ${dll}" >&2
|
||||
echo " exe: ${exe}" >&2
|
||||
echo " dest: ${dest_dir}" >&2
|
||||
if [[ ${#SEARCH_DIRS[@]} -gt 0 ]]; then
|
||||
echo " search-dirs: ${SEARCH_DIRS[*]}" >&2
|
||||
fi
|
||||
if [[ ${#RUNTIME_BINS[@]} -gt 0 ]]; then
|
||||
echo " runtime-bins: ${RUNTIME_BINS[*]}" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
cp -f "${src}" "${dest_dir}/"
|
||||
echo "bundled ${dll} → ${dest_dir}/ (${label})"
|
||||
done
|
||||
}
|
||||
|
||||
for i in "${!EXE_PATHS[@]}"; do
|
||||
bundle_one "${EXE_PATHS[$i]}" "${DEST_DIRS[$i]}" "${LABELS[$i]}"
|
||||
done
|
||||
|
||||
for dll in "${REQUIRE[@]}"; do
|
||||
[[ -n "${dll}" ]] || continue
|
||||
for i in "${!EXE_PATHS[@]}"; do
|
||||
exe="${EXE_PATHS[$i]}"
|
||||
dest="${DEST_DIRS[$i]:-}"
|
||||
if [[ -z "${dest}" ]]; then
|
||||
dest="$(dirname "${exe}")"
|
||||
fi
|
||||
if ! exe_imports_dll "${exe}" "${dll}"; then
|
||||
continue
|
||||
fi
|
||||
if [[ ! -f "${dest}/${dll}" ]]; then
|
||||
echo "error: $(basename "${exe}") imported ${dll} but it is missing under ${dest}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
done
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download and unpack a retcomm-toolchains cmake-clang-v1 pack.
|
||||
#
|
||||
# Usage:
|
||||
# fetch_toolchain.sh --artifact <linux-x64|windows-x64|macos-arm64|macos-x64> \
|
||||
# [--dl-dir DIR] [--out-dir DIR] [--repo OWNER/NAME]
|
||||
#
|
||||
# Prints: TOOLCHAIN_DIR=<absolute path with bin/>
|
||||
# When GITHUB_ENV is set (Actions), also appends:
|
||||
# TOOLCHAIN_DIR=...
|
||||
# PSXRECOMP_TOOLCHAIN_DIR=...
|
||||
# BPE_TOOLCHAIN_DIR=... (compat alias for existing title workflows)
|
||||
#
|
||||
# Env:
|
||||
# GH_TOKEN / GITHUB_TOKEN — optional; used for gh or Authorization on curl
|
||||
set -euo pipefail
|
||||
|
||||
ARTIFACT=""
|
||||
DL_DIR=".cache/toolchain-dl"
|
||||
OUT_DIR=".cache/toolchain-pack"
|
||||
REPO="TechnicallyComputers/retcomm-toolchains"
|
||||
|
||||
usage() {
|
||||
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help) usage ;;
|
||||
--artifact) ARTIFACT="${2:?}"; shift 2 ;;
|
||||
--dl-dir) DL_DIR="${2:?}"; shift 2 ;;
|
||||
--out-dir) OUT_DIR="${2:?}"; shift 2 ;;
|
||||
--repo) REPO="${2:?}"; shift 2 ;;
|
||||
*)
|
||||
echo "error: unknown arg: $1" >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${ARTIFACT}" ]]; then
|
||||
echo "error: --artifact is required" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
case "${ARTIFACT}" in
|
||||
linux-x64)
|
||||
PATTERN='cmake-clang-v1*linux*.zip'
|
||||
ASSET_NAME='cmake-clang-v1-linux-x64.zip'
|
||||
;;
|
||||
windows-x64)
|
||||
PATTERN='cmake-clang-v1*windows*.zip'
|
||||
ASSET_NAME='cmake-clang-v1-windows-x64.zip'
|
||||
;;
|
||||
macos-arm64|macos-x64)
|
||||
PATTERN='cmake-clang-v1*macos*.zip'
|
||||
ASSET_NAME='cmake-clang-v1-macos-universal.zip'
|
||||
;;
|
||||
*)
|
||||
echo "error: unknown artifact '${ARTIFACT}' (want linux-x64|windows-x64|macos-arm64|macos-x64)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
|
||||
|
||||
rm -rf "${DL_DIR}" "${OUT_DIR}"
|
||||
mkdir -p "${DL_DIR}" "${OUT_DIR}"
|
||||
|
||||
if command -v gh >/dev/null 2>&1; then
|
||||
if [[ -n "${TOKEN}" ]]; then
|
||||
export GH_TOKEN="${TOKEN}"
|
||||
fi
|
||||
gh release download -R "${REPO}" -p "${PATTERN}" -D "${DL_DIR}" --clobber
|
||||
elif command -v curl >/dev/null 2>&1; then
|
||||
ASSET_URL="https://github.com/${REPO}/releases/latest/download/${ASSET_NAME}"
|
||||
echo "gh not on PATH; curling ${ASSET_URL}"
|
||||
curl_args=(-fsSL -o "${DL_DIR}/${ASSET_NAME}")
|
||||
if [[ -n "${TOKEN}" ]]; then
|
||||
curl_args+=(-H "Authorization: Bearer ${TOKEN}")
|
||||
fi
|
||||
curl "${curl_args[@]}" "${ASSET_URL}"
|
||||
else
|
||||
echo "error: neither gh nor curl available to fetch toolchain pack" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ZIP="$(find "${DL_DIR}" -maxdepth 1 -type f -name '*.zip' | head -n1)"
|
||||
if [[ -z "${ZIP}" ]]; then
|
||||
echo "error: no toolchain zip matching ${PATTERN}" >&2
|
||||
ls -la "${DL_DIR}" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Unpacking toolchain from ${ZIP}"
|
||||
if command -v unzip >/dev/null 2>&1; then
|
||||
unzip -q "${ZIP}" -d "${OUT_DIR}"
|
||||
else
|
||||
python3 -c '
|
||||
import pathlib, zipfile, sys
|
||||
z = next(pathlib.Path(sys.argv[1]).glob("*.zip"))
|
||||
zipfile.ZipFile(z).extractall(sys.argv[2])
|
||||
' "${DL_DIR}" "${OUT_DIR}"
|
||||
fi
|
||||
|
||||
# Unwrap single top-level directory if present.
|
||||
shopt -s nullglob
|
||||
entries=("${OUT_DIR}"/*)
|
||||
shopt -u nullglob
|
||||
if [[ ${#entries[@]} -eq 1 && -d "${entries[0]}" ]]; then
|
||||
TOOLCHAIN_DIR="$(cd "${entries[0]}" && pwd)"
|
||||
else
|
||||
TOOLCHAIN_DIR="$(cd "${OUT_DIR}" && pwd)"
|
||||
fi
|
||||
|
||||
if [[ ! -d "${TOOLCHAIN_DIR}/bin" ]]; then
|
||||
echo "error: toolchain pack missing bin/ under ${TOOLCHAIN_DIR}" >&2
|
||||
find "${OUT_DIR}" -maxdepth 3 >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "TOOLCHAIN_DIR=${TOOLCHAIN_DIR}"
|
||||
if [[ -n "${GITHUB_ENV:-}" ]]; then
|
||||
{
|
||||
echo "TOOLCHAIN_DIR=${TOOLCHAIN_DIR}"
|
||||
echo "PSXRECOMP_TOOLCHAIN_DIR=${TOOLCHAIN_DIR}"
|
||||
echo "BPE_TOOLCHAIN_DIR=${TOOLCHAIN_DIR}"
|
||||
} >>"${GITHUB_ENV}"
|
||||
fi
|
||||
if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
|
||||
echo "toolchain-dir=${TOOLCHAIN_DIR}" >>"${GITHUB_OUTPUT}"
|
||||
fi
|
||||
Executable
+251
@@ -0,0 +1,251 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stage psxrecomp emitters + OpenBIOS surface + optional portable toolchain
|
||||
# into an existing setup-host release stage directory.
|
||||
#
|
||||
# Title packagers copy the host exe, game sources, and framework tree first,
|
||||
# then call this to finish the RetComM/wizard-complete zip layout.
|
||||
#
|
||||
# Usage:
|
||||
# stage_setup_sdk.sh --stage <stage-dir> [options]
|
||||
#
|
||||
# Options:
|
||||
# --framework DIR psxrecomp source tree (default: <cwd>/psxrecomp)
|
||||
# --sdk-overlay DIR Overlay (e.g. psxrecomp-sdk) onto stage/psxrecomp
|
||||
# --recompiler-build DIR Where psxrecomp-game/bios were built (repeatable)
|
||||
# --toolchain-dir DIR Pack root with bin/; embedded as stage/toolchain/
|
||||
# --allow-no-toolchain Warn instead of failing when toolchain unset
|
||||
# --host-exe PATH Host PE for Windows DLL bundling (optional)
|
||||
# --runtime-bin DIR MinGW runtime DLL search dir (repeatable)
|
||||
# --search-dir DIR Extra DLL search dir (repeatable)
|
||||
# --require-cli Require psxrecomp_cli.py after overlays (default on)
|
||||
#
|
||||
# Env aliases for toolchain dir (first wins):
|
||||
# PSXRECOMP_TOOLCHAIN_DIR, TOOLCHAIN_DIR, BPE_TOOLCHAIN_DIR
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
FW_TOOLS="${SCRIPT_DIR}"
|
||||
|
||||
STAGE=""
|
||||
FRAMEWORK=""
|
||||
SDK_OVERLAY=""
|
||||
RECOMPILER_BUILDS=()
|
||||
TOOLCHAIN_DIR="${PSXRECOMP_TOOLCHAIN_DIR:-${TOOLCHAIN_DIR:-${BPE_TOOLCHAIN_DIR:-}}}"
|
||||
ALLOW_NO_TOOLCHAIN=0
|
||||
HOST_EXE=""
|
||||
RUNTIME_BINS=()
|
||||
SEARCH_DIRS=()
|
||||
REQUIRE_CLI=1
|
||||
|
||||
usage() {
|
||||
sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help) usage ;;
|
||||
--stage) STAGE="${2:?}"; shift 2 ;;
|
||||
--framework) FRAMEWORK="${2:?}"; shift 2 ;;
|
||||
--sdk-overlay) SDK_OVERLAY="${2:?}"; shift 2 ;;
|
||||
--recompiler-build) RECOMPILER_BUILDS+=("${2:?}"); shift 2 ;;
|
||||
--toolchain-dir) TOOLCHAIN_DIR="${2:?}"; shift 2 ;;
|
||||
--allow-no-toolchain) ALLOW_NO_TOOLCHAIN=1; shift ;;
|
||||
--host-exe) HOST_EXE="${2:?}"; shift 2 ;;
|
||||
--runtime-bin) RUNTIME_BINS+=("${2:?}"); shift 2 ;;
|
||||
--search-dir) SEARCH_DIRS+=("${2:?}"); shift 2 ;;
|
||||
--require-cli) REQUIRE_CLI=1; shift ;;
|
||||
--no-require-cli) REQUIRE_CLI=0; shift ;;
|
||||
*)
|
||||
echo "error: unknown arg: $1" >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${STAGE}" ]]; then
|
||||
echo "error: --stage is required" >&2
|
||||
usage
|
||||
fi
|
||||
STAGE="$(cd "${STAGE}" && pwd)"
|
||||
|
||||
if [[ -z "${FRAMEWORK}" ]]; then
|
||||
if [[ -d "${PWD}/psxrecomp" ]]; then
|
||||
FRAMEWORK="$(cd "${PWD}/psxrecomp" && pwd)"
|
||||
else
|
||||
FRAMEWORK="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
fi
|
||||
else
|
||||
FRAMEWORK="$(cd "${FRAMEWORK}" && pwd)"
|
||||
fi
|
||||
|
||||
mkdir -p "${STAGE}/psxrecomp"
|
||||
|
||||
# Optional SDK overlay (CLI / prepare_disc / progress helpers).
|
||||
if [[ -n "${SDK_OVERLAY}" ]]; then
|
||||
if [[ ! -d "${SDK_OVERLAY}" ]]; then
|
||||
echo "error: --sdk-overlay not a directory: ${SDK_OVERLAY}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -a "${SDK_OVERLAY}/" "${STAGE}/psxrecomp/"
|
||||
else
|
||||
cp -a "${SDK_OVERLAY}/." "${STAGE}/psxrecomp/"
|
||||
fi
|
||||
echo "overlaid SDK from ${SDK_OVERLAY}"
|
||||
fi
|
||||
|
||||
find_tool_bin() {
|
||||
local name="$1"
|
||||
local dir cand
|
||||
local -a roots=()
|
||||
local r
|
||||
for r in "${RECOMPILER_BUILDS[@]+"${RECOMPILER_BUILDS[@]}"}"; do
|
||||
roots+=("$(cd "${r}" && pwd)")
|
||||
done
|
||||
roots+=(
|
||||
"${STAGE}/psxrecomp/recompiler/build"
|
||||
"${FRAMEWORK}/recompiler/build"
|
||||
"${PWD}/build-recompiler"
|
||||
)
|
||||
for dir in "${roots[@]}"; do
|
||||
[[ -d "${dir}" ]] || continue
|
||||
for cand in \
|
||||
"${dir}/${name}" \
|
||||
"${dir}/${name}.exe" \
|
||||
"${dir}/Release/${name}.exe"
|
||||
do
|
||||
if [[ -f "${cand}" ]]; then
|
||||
echo "${cand}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
GAME_BIN="$(find_tool_bin psxrecomp-game || true)"
|
||||
BIOS_BIN="$(find_tool_bin psxrecomp-bios || true)"
|
||||
if [[ -z "${GAME_BIN}" ]]; then
|
||||
echo "error: psxrecomp-game not found (pass --recompiler-build)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${BIOS_BIN}" ]]; then
|
||||
echo "error: psxrecomp-bios not found (required for OpenBIOS regen)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${STAGE}/psxrecomp/recompiler/build"
|
||||
cp -a "${GAME_BIN}" \
|
||||
"${STAGE}/psxrecomp/recompiler/build/$(basename "${GAME_BIN}")"
|
||||
cp -a "${BIOS_BIN}" \
|
||||
"${STAGE}/psxrecomp/recompiler/build/$(basename "${BIOS_BIN}")"
|
||||
chmod +x "${STAGE}/psxrecomp/recompiler/build/$(basename "${GAME_BIN}")" 2>/dev/null || true
|
||||
chmod +x "${STAGE}/psxrecomp/recompiler/build/$(basename "${BIOS_BIN}")" 2>/dev/null || true
|
||||
echo "staged emitters from ${GAME_BIN%/*}"
|
||||
|
||||
cat >"${STAGE}/psxrecomp/retcomm-sdk.json" <<'EOF'
|
||||
{
|
||||
"cli": "psxrecomp_cli.py",
|
||||
"id": "psxrecomp-tools",
|
||||
"game_bin": "recompiler/build/psxrecomp-game",
|
||||
"bios_bin": "recompiler/build/psxrecomp-bios"
|
||||
}
|
||||
EOF
|
||||
|
||||
for f in OpenBIOS.toml openbios.bin OpenBIOS.LICENSE SCPH1001.toml; do
|
||||
if [[ ! -f "${STAGE}/psxrecomp/bios/${f}" ]]; then
|
||||
echo "error: missing psxrecomp/bios/${f} in staged tree" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${REQUIRE_CLI}" -eq 1 && ! -f "${STAGE}/psxrecomp/psxrecomp_cli.py" ]]; then
|
||||
echo "error: missing psxrecomp/psxrecomp_cli.py (pass --sdk-overlay or copy CLI into stage)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Never ship retail BIOS dumps from the stage.
|
||||
rm -f "${STAGE}/psxrecomp/bios/SCPH1001.BIN" \
|
||||
"${STAGE}/psxrecomp/bios/SCPH1001.bin" 2>/dev/null || true
|
||||
|
||||
if [[ -n "${TOOLCHAIN_DIR}" && -d "${TOOLCHAIN_DIR}" ]]; then
|
||||
if [[ ! -d "${TOOLCHAIN_DIR}/bin" ]]; then
|
||||
echo "error: toolchain dir missing bin/: ${TOOLCHAIN_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "${STAGE}/toolchain"
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -a --delete "${TOOLCHAIN_DIR}/" "${STAGE}/toolchain/"
|
||||
else
|
||||
rm -rf "${STAGE}/toolchain"
|
||||
mkdir -p "${STAGE}/toolchain"
|
||||
cp -a "${TOOLCHAIN_DIR}/." "${STAGE}/toolchain/"
|
||||
fi
|
||||
echo "bundled toolchain from ${TOOLCHAIN_DIR}"
|
||||
elif [[ "${ALLOW_NO_TOOLCHAIN}" -eq 1 ]]; then
|
||||
echo "warning: toolchain unset — zip will need system cmake/ninja" >&2
|
||||
else
|
||||
echo "error: toolchain dir required (pass --toolchain-dir or set PSXRECOMP_TOOLCHAIN_DIR)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Windows MinGW DLL bundling for host + emitters.
|
||||
BUNDLE="${FW_TOOLS}/bundle_mingw_dlls.sh"
|
||||
if [[ ! -f "${BUNDLE}" ]]; then
|
||||
# Staged copy (after sdk overlay) may own the helper.
|
||||
if [[ -f "${STAGE}/psxrecomp/tools/bundle_mingw_dlls.sh" ]]; then
|
||||
BUNDLE="${STAGE}/psxrecomp/tools/bundle_mingw_dlls.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
need_dlls=0
|
||||
if [[ -n "${HOST_EXE}" && "${HOST_EXE}" == *.exe ]]; then
|
||||
need_dlls=1
|
||||
fi
|
||||
if [[ -f "${STAGE}/psxrecomp/recompiler/build/psxrecomp-game.exe" ]]; then
|
||||
need_dlls=1
|
||||
fi
|
||||
|
||||
if [[ "${need_dlls}" -eq 1 ]]; then
|
||||
if [[ ! -f "${BUNDLE}" ]]; then
|
||||
echo "error: bundle_mingw_dlls.sh not found next to stage_setup_sdk.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
chmod +x "${BUNDLE}" 2>/dev/null || true
|
||||
|
||||
for emitter in psxrecomp-game.exe psxrecomp-bios.exe; do
|
||||
if [[ ! -f "${STAGE}/psxrecomp/recompiler/build/${emitter}" ]]; then
|
||||
echo "error: missing ${emitter} in staged tree" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
args=()
|
||||
for d in "${RUNTIME_BINS[@]+"${RUNTIME_BINS[@]}"}"; do
|
||||
args+=(--runtime-bin "${d}")
|
||||
done
|
||||
for d in "${SEARCH_DIRS[@]+"${SEARCH_DIRS[@]}"}"; do
|
||||
args+=(--search-dir "${d}")
|
||||
done
|
||||
if [[ -n "${HOST_EXE}" && -f "${HOST_EXE}" ]]; then
|
||||
args+=(
|
||||
--exe "${HOST_EXE}"
|
||||
--dest "${STAGE}"
|
||||
--label "$(basename "${HOST_EXE}")"
|
||||
)
|
||||
fi
|
||||
args+=(
|
||||
--exe "${STAGE}/psxrecomp/recompiler/build/psxrecomp-game.exe"
|
||||
--dest "${STAGE}/psxrecomp/recompiler/build"
|
||||
--label "psxrecomp-game.exe"
|
||||
--exe "${STAGE}/psxrecomp/recompiler/build/psxrecomp-bios.exe"
|
||||
--dest "${STAGE}/psxrecomp/recompiler/build"
|
||||
--label "psxrecomp-bios.exe"
|
||||
--require libgcc_s_seh-1.dll
|
||||
--require libstdc++-6.dll
|
||||
)
|
||||
bash "${BUNDLE}" "${args[@]}"
|
||||
fi
|
||||
|
||||
echo "stage_setup_sdk: ready under ${STAGE}/psxrecomp"
|
||||
Reference in New Issue
Block a user