mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-11 23:43:20 -10:00
235 lines
15 KiB
Markdown
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.
|