Skip to content

Commit c7f981c

Browse files
committed
Write down how a patch release is cut
2026.08.1 was cut by hand, and both times it got stuck it was for the same reason: a release branch is the one tree that does not derive its version, and nothing in the repository had ever been one. Those two are fixed -- a75f246 and 79a9278 -- but the flow that produced them lived nowhere, so the next person cutting one starts where this one started. The rule the whole thing hangs off is that a patch does not move the target runtime: a binary built against 2026.08.0 has to keep meaning the same thing after 2026.08.1. That is why a patch branches from master and rolls the target pins back, rather than branching from the release and forward-porting host fixes. Two corrections while writing it down. The x86_64 macOS toolchain file has not needed osxcross since it was rewritten to drive the system Xcode with -arch x86_64; osxcross is the alternative for a Linux build machine, which its own header says. And the arm64 file's first line says it targets x86_64.
1 parent 283b076 commit c7f981c

3 files changed

Lines changed: 144 additions & 2 deletions

File tree

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ Available host toolchain files under `cmake/toolchains/`:
204204
| `aarch64-linux-gnu.cmake` | Linux aarch64 (glibc) | alternative to a native arm64 build |
205205
| `x86_64-unknown-freebsd.cmake` | FreeBSD x86_64 | clang + base.txz sysroot, see `scripts/setup-freebsd-cross.sh` |
206206
| `aarch64-unknown-freebsd.cmake` | FreeBSD aarch64 | clang + base.txz sysroot, see `scripts/setup-freebsd-cross.sh` |
207-
| `x86_64-apple-darwin.cmake` | macOS x86_64 | requires osxcross; see file header |
207+
| `x86_64-apple-darwin.cmake` | macOS x86_64 | the system Xcode with `-arch x86_64`, see `scripts/setup-macos-cross.sh`; osxcross on a Linux machine, see file header |
208208
| `aarch64-apple-darwin.cmake` | macOS arm64 | requires osxcross; see file header |
209209

210210
The musl hosts have no toolchain file: they are built inside an Alpine
@@ -225,3 +225,11 @@ Cross building, on the other hand, now requires a stage-1 SDK. Target code
225225
is compiled once and imported; a cross build that rebuilt it would be
226226
compiling the same objects a second time on a machine that has no business
227227
doing it. Configure without `VITASDK_STAGE1_DIR` and the build says so.
228+
229+
## Releases
230+
231+
A snapshot is built from whatever `master` is. A release series is declared by
232+
a `VERSION` file at the root, which is what makes the lock carry a version and
233+
the series it belongs to instead of deriving one from history. Patching a
234+
series is a branch of its own and a fixed order of steps:
235+
[docs/patch-release.md](docs/patch-release.md).

cmake/toolchains/aarch64-apple-darwin.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# CMake toolchain for cross compiling the SDK host binaries to x86_64 macOS
1+
# CMake toolchain for cross compiling the SDK host binaries to arm64 macOS
22
# through osxcross (https://github.com/tpoechtrager/osxcross).
33
#
44
# osxcross requires a macOS SDK extracted from Xcode; Apple's license only

docs/patch-release.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Cutting a patch release
2+
3+
A patch fixes the host side of a series that people already build against.
4+
The compiler, the SDK, the client: anything that runs on the developer's
5+
machine. What it does not do is move the target runtime — newlib, the vita
6+
headers, pthread-embedded — because a binary built against 2026.08.0 has to
7+
keep meaning the same thing after 2026.08.1. A release that swapped those
8+
would not be a patch, whatever it is called.
9+
10+
That single rule is what shapes every step below, and it is why a patch
11+
branches from `master` rather than from the release it patches: the host-side
12+
fixes are already there, and it is the *target* pins that go back.
13+
14+
The worked example is 2026.08.1, cut on 2026-08-25. It exists because the
15+
cores that 2026.08.0 published refused to start on anything older than glibc
16+
2.38, so the SDK was rebuilt on the glibc floor the published cores use now,
17+
with the same target sources.
18+
19+
## 1. Branch and declare the version
20+
21+
```sh
22+
git switch -c next-patch-2026.08.1 master
23+
echo 2026.08.1 > VERSION
24+
```
25+
26+
`VERSION` is the whole mechanism. `buildscripts-ci describe` reads it at the
27+
revision it is describing, and a lock built from a tree that carries one says
28+
so twice: `version` is the string in the file, and `series` is everything up
29+
to the last dot. A tree without the file derives its version from history and
30+
has no series at all, which is what a nightly is.
31+
32+
Both fields end up in the `lock.json` published inside the core release, and
33+
that is what the rest of the chain reads.
34+
35+
## 2. Roll the target back to what the series shipped
36+
37+
In `cmake/Components.cmake`, put the three target components back to the
38+
revisions that series published, and leave everything else alone. For
39+
2026.08.1 that was newlib `6cba9812`, vita-headers `ebc8f4f7` and
40+
pthread-embedded `610934f4` — master was 2445 newlib commits and a major
41+
version ahead. gcc, binutils, gdb, the host libraries, vita-toolchain and the
42+
samples were identical between the two, and vdpm and vita-makepkg stayed
43+
current, since the host side is what the release exists to fix.
44+
45+
Look at what the series actually shipped rather than at what the tag says:
46+
47+
```sh
48+
gh release download <the series' core tag> --repo vitasdk/autobuilds \
49+
--pattern lock.json --output - | jq .sources
50+
```
51+
52+
## 3. The fixes
53+
54+
Ordinary commits. Two things worth knowing before they turn red:
55+
56+
* Infrastructure written since the series was cut has never been pointed at
57+
these sources. 2026.08.1 hit exactly that: `check-toolchain-contract`
58+
compiles every public header on its own, and two headers the series had
59+
always shipped do not compile that way. That is not a reason to skip the
60+
check — it is what a patch is for.
61+
* A fix that has to land in a component takes the same shape there: a branch
62+
off the revision the series pinned, not off the component's development
63+
branch, and then the pin moves to it.
64+
65+
## 4. Build it
66+
67+
Dispatch **Build SDK snapshots** in `vitasdk/autobuilds` with
68+
`buildscripts_ref` set to the branch. It describes the tree, builds every host
69+
in the lock, publishes `sdk-snapshot-<date>.<run>.<attempt>` with `lock.json`
70+
inside it, and announces the tag to the package autobuilder along with the
71+
series it belongs to.
72+
73+
Required hosts are release policy and live in that workflow: if one of them
74+
cannot build at this revision, nothing publishes.
75+
76+
## 5. The catalogue
77+
78+
`vitasdk/vitasdk-autobuild` answers the announcement, builds the packages
79+
against that exact core and publishes `packages-<series>-snapshot-...` with a
80+
`provenance.json` naming the core it was built against. A series' catalogue is
81+
separate from nightly's on purpose: same recipes, different core.
82+
83+
## 6. Point the series at it
84+
85+
Run **Update Channel Manifest** in `vitasdk/autobuilds` with the core tag, the
86+
packages tag, and the channel set to the series name (`2026.08`, not
87+
`nightly`). It refuses the pair unless:
88+
89+
* both releases exist and publish the files it needs;
90+
* the packages record that exact core as the one they were built against;
91+
* the core's lock declares the series being published — and `nightly` takes
92+
only cores that declare none.
93+
94+
Then it signs the manifest, pushes it to the Pages site, waits until the
95+
signed file is actually being served, and tells the image builder the series
96+
moved.
97+
98+
## 7. Tag it
99+
100+
```sh
101+
git tag vitasdk-2026.08.1 <the revision that was built>
102+
git push origin vitasdk-2026.08.1
103+
```
104+
105+
The tag is a name for a revision people can go back to. What identifies the
106+
release everywhere else is the lock: version, series, buildscripts revision
107+
and the exact source of every component.
108+
109+
## What went wrong the first time
110+
111+
Both blockages came from the same place — a release branch is the one tree
112+
that does not derive its version, and nothing had ever been one.
113+
114+
**The CI of the tag went red on tests, not on builds.** All thirteen build
115+
jobs passed; two protocol tests failed because they assume the repository
116+
derives its version. The monotonicity test walks first-parent history
117+
asserting each version precedes the next, and on a release branch every commit
118+
answers with the same declared string. Fixed in `a75f24689`: a declared
119+
version is now a reason to skip that walk, the way a collapsed PR merge range
120+
already was — which also un-skipped the checks below it, the ones that are
121+
about declared versions.
122+
123+
**The publisher had no way to know which series it was publishing.** The lock
124+
did not carry it, so every core was announced the same way, and an
125+
announcement with no series names the unnamed one: nightly. Publishing
126+
2026.08.1 that way would have moved every nightly user onto the target runtime
127+
of a series they are not on. Fixed in `79a927840` by deriving the series where
128+
the version is known, and closed at the other end by the pair check in step 6.
129+
130+
**And one hole found while looking:** `--previous-version` skipped a committed
131+
`VERSION` outright, so the guard that stops a version going backwards covered
132+
only the derived path — the one where it cannot happen by accident — and not
133+
the one where a person types the number. Closed in `287d8a8f4`. Nothing passes
134+
`--previous-version` in production yet.

0 commit comments

Comments
 (0)