Files

235 lines
15 KiB
Markdown

# GitHub, releases & site
Detail for [`AGENTS.md`](../../AGENTS.md).
## Every PR must be linked to an issue — `Closes #N` will NOT do it
**Hard rule: link the PR to its issue in the GitHub *Development* panel (right sidebar → gear →
pick the issue). A closing keyword in the body is not a substitute and does not work here.**
GitHub creates the linked-issue relationship from a closing keyword only when the PR targets the
repo's **default branch**. This repo's default is `master` and essentially every PR targets
`develop`, so `Closes #N` here creates **no link at all** — not merely "no auto-close".
Measured 2026-08-20: PRs #537/#539/#542/#544/#545/#547 all carried `Closes #N`; every one
reported `closingIssuesReferences: []`. Only #546, linked by hand in the UI, had a real link. It
went unnoticed because timeline cross-references still appear — the issue looks connected while
its Development panel stays empty, so the issue never shows which release/pre-release tag
contains the work (that tag is the whole reason to link).
- Check: `gh pr view N --json closingIssuesReferences --jq '.closingIssuesReferences | length'`
— `0` = unlinked.
- **There IS a public API** — `addCloseIssueReferences` ("Adds one or more pull requests as
manually linked closing references on an issue"), idempotent. An earlier revision of this file
said "Web UI only; don't burn time on a GraphQL mutation", which was wrong and cost every new PR
a red check for a step that cannot be done until after the PR exists.
```bash
scripts/pr-link-issue.sh <pr-number> [issue-number] [--repo owner/name]
```
With no issue number it scans the PR body for a tag. Safe to re-run: already-linked is a no-op.
- **Normal flow: tag the PR body, CI links it.** `.github/workflows/pr-issue-link.yml` runs on
`opened/edited/reopened/synchronize/labeled/unlabeled/ready_for_review`, finds the tag, and
creates the link. Accepted tags (case-insensitive):
```
Closes #123 Fixes #123 Resolves #123 Refs #123
<!-- link-issue: #123 --> (invisible in the rendered body)
```
The hidden comment form wins over the keyword forms, so a PR can discuss one issue in prose and
link a different one deliberately. The keywords still do nothing on GitHub's own side here —
they are markers for this job.
- Forgot the tag at creation? **Edit the description.** `edited` re-runs the job and it links then;
no need to push a commit. The job checks out the BASE ref, never the PR head, because it runs
`pull_request_target` with a write token.
- Genuinely issue-less PR → add the `no-issue` label.
- Still close the issue by hand when the PR merges (keywords don't fire on `develop`).
## GitHub Copilot PR reviews
- List unresolved threads: `gh api graphql -f query='{repository(owner:"libretro",name:"virtualjaguar-libretro"){pullRequest(number:N){reviewThreads(first:30){nodes{id isResolved comments(first:1){nodes{id author{login} body}}}}}}}'`
- Inline reply: `gh api -X POST repos/libretro/virtualjaguar-libretro/pulls/N/comments/<REST_ID>/replies -f body="..."` — parent is the REST `id` from `gh api .../comments`, NOT the GraphQL `PRRC_*` id (404s).
- Resolve thread: `gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "PRRT_..."}) { thread { isResolved } } }'`.
- Always reply AND resolve when addressing feedback — an open thread after a fix is noise.
- **Do NOT trigger Copilot reviews.** `gh pr comment N --body "@copilot review"` bills per token
since 2026-06-01 and spawns a coding-agent session; `kimi-review.yml` already reviews every PR
to `develop`/`master` automatically. Use only when a second opinion is worth the spend, and say
why in the PR.
- Repo-level Copilot instructions: `.github/copilot-instructions.md` (input on every request —
keep short). Depth goes in `.github/prompts/*.prompt.md` (loads only when invoked).
- Review bots CodeRabbit/Copilot/Cursor all need a libretro-org App install we cannot do; every
PR push spends Kimi quota; `@kimi review` needs the workflow on master.
## Branching (GitFlow)
Branch new work off **`develop`** (integration branch); `master` is release-only (tagged
commits, hotfix/release merges). PRs targeting `master` get auto-warned by
`.github/workflows/warn-pr-base.yml` — retarget to `develop` unless source is `hotfix/*` or
`release/*`. Full flow in `docs/release-process.md`. **Branch off `libretro/develop`, not local
`develop`** (local can be stale).
## Release process (GitFlow)
Full detail in `docs/release-process.md`. Quick reference:
**Nightlies:** a push to `develop` waits 10 minutes (debounce in `release.yml`'s `guard` job; a newer
push cancels the sleeping run, so a burst of merges builds once), then runs the 16-platform matrix and
replaces the rolling `nightly` prerelease + its pinned tracking issue. Gated on *compiling*, not on the
test suite — don't call nightlies "CI-verified". The `nightly` tag sits outside the `v*` filter so it
can't trigger a real release.
Cutting a release:
1. `git checkout develop && git checkout -b release/vX.Y.Z`
2. Bump version (all must match): `Makefile` → `CORE_BASE_VERSION := vX.Y.Z`;
`dist/info/virtualjaguar_libretro.info` → `display_version = "vX.Y.Z"`;
`src/core/version_fallback.h` → `#define CORE_BASE_VERSION "vX.Y.Z"` (used when the generated
`src/core/version.h` is absent). `bash scripts/check-info-version.sh` checks all three (#734).
3. Release notes: `docs/RELEASE_NOTES_vX.Y.Z.md` (template: `docs/RELEASE_NOTES_v2.3.0.md`) —
highlights, bug fixes, perf, testing, known issues, stats (`git diff --shortstat
vPREV..HEAD`), downloads, maintainers.
4. Verify: `make clean && make -j…` clean, `make test` passes, `strings *.dylib | grep vX.Y.Z`.
Also regenerate both boot matrices on the corpus machine and gate the tag on
`python3 -I test/tools/matrix_diff.py <prev-tag copy> <regenerated>` (exit 1 = backward row =
blocked; not runnable in CI) — `docs/release-process.md` §1.
5. Commit: `chore: bump version to vX.Y.Z, add release notes`.
6. Push + PR: `git push -u libretro release/vX.Y.Z` then `gh pr create --base master`.
7. After merge to master: tag `vX.Y.Z` and push — `release.yml` builds 16 platforms + publishes.
8. Back-merge: `git checkout develop && git merge master && git push libretro develop`.
9. libretro-super: PR updating `dist/info/virtualjaguar_libretro.info` there.
Don't: tag before the PR merges to master; put new features on a release branch (bug fixes only);
forget the back-merge (step 8) or develop diverges from the tagged version string.
Signing and provenance (#1151/#1152): after the builds, `release.yml`'s `stage` job writes and
minisign-signs `SHA256SUMS.txt` and attests every file; `verify-signatures` (macOS runner,
`scripts/release/verify.sh`) then checks each asset against its expected signing state, strict on
tags and `release/**`, warn-only on `develop`, and the per-artifact table goes into the release body.
Windows signatures are required when the Azure Artifact Signing secrets and variables are
configured. To prove a change to this pipeline,
dispatch `release.yml` on the branch with **Dry run** ticked (builds everything, publishes nothing).
Key setup: `app/packaging/README.md`, "Signed checksums" / "Signed AppImages".
Savestate version policy: one bump per release (all in-flight changes share the version);
develop breakage OK.
## The website is NOT published by this repo's `pages.yml`
Live site: **https://jaguar.provenance-emu.com/**, built from `site/pages/*.html` +
`scripts/build_site.py` on **`develop`**.
**A green `Deploy Pages` run here is NOT evidence the site updated.** The libretro org's Pages
config redirects every project site to `www.libretro.com/<repo>/`, which 404s — so this repo's
`pages.yml` deploys, reports success, and publishes to a dead path.
Real publisher: **`Publish site` (workflow id `329140069`) on the
`Provenance-Emu/virtualjaguar-libretro` fork**, branch `provenance`. Checks out
`libretro/…@develop` (`fetch-depth: 0` — generator derives sitemap `lastmod` from per-file git
dates, refuses a shallow clone), builds, deploys to the fork's Pages with the verified domain.
Trigger: cron `17 * * * *` (hourly) + `workflow_dispatch`. A change merged to `develop` goes live
up to an hour later. Publish immediately:
```bash
gh workflow run 329140069 --repo Provenance-Emu/virtualjaguar-libretro --ref provenance
```
Verify against the live domain, not workflow status:
```bash
curl -sI https://jaguar.provenance-emu.com/ | grep -i last-modified
```
That timestamp tracks the fork's last publish; if it predates your `develop` commit the site is
stale. Responses carry `cache-control: max-age=600` — polling past ~10 min with unchanged
`last-modified` means content is old, not cached. Local preview: `python3 scripts/build_site.py`
→ `_site/`; `scripts/check_site.py` asserts canonical/`og:url`/sitemap/robots agree with
`SITE_BASE` (override `VJ_SITE_BASE`).
## Roadmap
Planning lives in GitHub milestones (pinned issue #371), never in md files.
## What runs on a push to `develop`
Only `release.yml` (the nightly), plus path-filtered publishers (`pages.yml`,
`jaguar-toolchain-image.yml`, `crowdin_source_upload.yml`). C/C++ CI, App, Regression, Acid,
Installer, JaguarDemos, Microbench, Jaguar toolchain and Cheat import run on PRs (into `develop`,
`master`, or `release/**`) and on pushes to `master` / tags; they no longer re-run on the squash-merge
commit because the PR already ran them. Consequence: two PRs that are each green but conflict
semantically are caught by the next PR's CI (or by the nightly build, for compile errors), not by a
post-merge run. To test a develop tip by hand, dispatch `c-cpp.yml` / `app.yml` / `acid-test.yml` /
`regression-test.yml` / `jaguar-demos.yml` / `microbench.yml` / `jaguar-toolchain.yml` / `cheat-import.yml` with the branch set
to `develop`. Cache note: caches written on a PR ref are not shared with other PRs; PRs restore from
`develop` and `master`, so after this change the Tier-1 ROM / vasm / miniretro caches are only
refreshed by master pushes or such a dispatch.
## Mac Studio self-hosted runner
A repo-level runner labelled `self-hosted, studio, vj` (macOS arm64, Xcode, Homebrew, non-admin
user) takes the heavy macOS jobs: macOS rows of `c-cpp.yml` (incl. SwiftPM build), `app.yml`,
`regression-test.yml`, and the unsigned iOS/tvOS rows of the develop nightly in `release.yml` (not `release/**`, not tags). It is used **only** when
`github.event_name` is `push`, `schedule` or `workflow_dispatch`, or a `pull_request` whose
`head.repo.full_name == github.repository` (same-repo PR). Fork PRs always use `macos-latest`, and
no `pull_request_target` workflow may ever run there. Not routed (stay hosted): jobs that sign or
notarize (`release.yml` macOS rows, `app-package.yml`, `chdman-tools.yml`; `app.yml` skips its
sign step on the Studio via `runner.environment`), anything on a `v*` tag or `release/**` (whose compile-only cores C/C++ CI reuses), `installer.yml`,
`librashader.yml`, Intel/Rosetta runners. Kill switch: set the repository variable
`VJ_STUDIO_RUNNER` to `off` and all of it falls back to `macos-latest` with no code change. Runner `studio-virtualjaguar` (labels
`self-hosted,macOS,ARM64,studio,vj`) is registered; the repo requires approval for all external
contributors' workflows. Studio prerequisites: Xcode, cmake, ninja, imagemagick, swift. If the runner is
offline, macOS jobs queue until it returns (or set the kill switch). It coexists with `VJ_TEMP_RUNNERS`,
which only remaps lightweight ubuntu jobs.
## Temporary CI runners
`VJ_TEMP_RUNNERS` is an optional repository variable containing a JSON object with
a `refs` allowlist and mappings from hosted labels to private runner labels.
Only listed Git refs use those mappings in opted-in jobs. The C/C++ Linux x86-64
rows and Linux test workflows also honor the `ubuntu-latest` mapping for trusted
events; fork PRs and `pull_request_target` keep GitHub-hosted runners. A matching
runner must actually provide Ubuntu x86-64, including Docker for SDK containers;
the macOS/ARM Studio runner cannot serve these jobs directly.
For example, temporarily map `refs/pull/1096/merge` to a uniquely labelled Linux VM.
Keep the VM isolated from host folders and remove its registration and mapping
after validation. Already-created jobs retain their runner labels; the mapping
applies when GitHub creates a new job or reruns the workflow.
Absent refs or labels keep their original GitHub-hosted runners. Leave the variable unset normally. A temporary release
runner must use a unique custom label with default labels disabled, preserve the
requested OS/architecture, and be removed together with the variable after its
checks finish. Do not map Ubuntu 22.04 packaging jobs onto Ubuntu 24.04: that would
change the supported glibc floor.
## Shared production core builds
For same-repository `release/**` PRs with a merge tree identical to the release head,
C/C++ CI shares ten compile-only production builds from the exact-head Release push run:
macOS Intel, WASM, four Android ABIs, iOS, tvOS, Vita and Switch. Other PRs, forks,
manual runs and integration-branch pushes keep the full compiler matrix. The configurations
live in `.github/ci/core-matrix.json`; a Ruby workflow-contract check rejects mismatched
compiler/SDK/architecture settings before any rows are omitted.
The seven native C/C++ matrix rows, GCC/Clang differences, MSVC checks, coverage,
ASan/UBSan, clang-tidy and production -> `TEST_EXPORTS=1` -> production mode-switch
coverage stay independent. Compile-only fallback builds use the same `RELEASE_DEBUG_INFO=1`
configuration as Release (adds debug information to the optimized production build).
Android's compile-definition and selected-SIMD-object assertions run through
`scripts/build-ndk-ci.sh` in both workflows. Compile-only Mach-O production exports are
checked before Release uploads a shared binary.
Release retains an unstripped `ci-core-<platform>` intermediate before packaging/signing.
`BUILD.json` records the exact commit, platform, filename, production ABI setting and
SHA-256. C/C++ CI requires successful production jobs, both raw and distributable artifact
groups, and verifies the downloaded metadata/checksums before retaining all ten original
C/C++ artifact names. Failed, cancelled, skipped, stale, missing or mismatched evidence
cannot make the shared check green. The hosted waiting job does not occupy the temporary
runner needed by Release metadata jobs.
These intermediates have 14-day retention and are explicitly excluded by **both** release
publishers' staging filters. Shipped binaries, split debug archives, their names and checksum
collection remain unchanged. The C/C++ artifact aliases retain their normal artifact retention.
The shared core verifier and shared app-package verifier must both pass before merging a
release PR that uses these routes; a configured skip of duplicate build rows is not sufficient.