Skip to content

The toolchain is a release a consumer can name, install and link with - #392

Merged
Japabu merged 5 commits into
mainfrom
toolchain-for-consumers
Sep 3, 2026
Merged

Japabu merged 5 commits into
mainfrom
toolchain-for-consumers

Conversation

@Japabu

@Japabu Japabu commented Sep 3, 2026 •

Copy link
Copy Markdown
Collaborator

gbae's CI is the first third party to build for ToyOS, and it reported what it
needed in order. This is items 1, 2, 4, 5, the glibc answer, and the
raw-window-handle move.

toyos-ld goes in the tarball, and the install path prefers it

rustc's ToyOS target spec names the linker toyos-ld and finds it on PATH
(rust/compiler/rustc_target/src/spec/base/toyos.rs:
linker: Some("toyos-ld".into())), so a consumer holding only the toolchain had
nothing to link with. The publish step copies the host binary the build already
made into <host>/stage2/bin/, beside rustc.

The release tag is sha256(HEAD:rust HEAD:toyos-abi/src HEAD:toyos/src HEAD:userland/libc/src) and toyos-ld is not part of it, so "prefer whatever
was shipped" would have had CI link every build with whichever linker first
produced that key. toyos-ld-witness travels in the tarball beside the binary
and says which sources it is; Owner::Installed takes the shipped one only when
that witness is this checkout's, and builds otherwise.

Negative control — the whole decision reverted to the base's (always build):

---- toolchain::tests::the_shipped_linker_is_taken_only_where_it_is_this_tree_s stdout ----
assertion `left == right` failed: a shipped linker whose witness is this
tree's is the one to link through
  left: None
 right: Some(".../toyos-standing-shipped-ld-67381/toyos-ld")
test result: FAILED. 0 passed; 1 failed

green on the fix. Oracle: rustc's own target spec, quoted above — it is what
decides a bare name on PATH is the linker at all.

A tag a consumer can name

The content hash stays; toolchain-linux-x86_64-sdk-<toyos-abi's version> is
added beside it. GitHub's API hangs an asset off one release id, so the
alternative was re-uploading 401 MiB on every landing; the alias is a second
release carrying the TOOLCHAIN manifest instead — the ToyOS commit, the rust
submodule commit, the host triple, the glibc floor and the five SDK crate
versions from --sdk-versions — with notes naming the tag the tarball is on.
Never on a pull_request, whose head is somebody's branch.

glibc: 2.39, and the brief's premise corrected

readelf --dyn-syms over the published asset
(toolchain-linux-x86_64-d8dbd61f8ae95241) across bin/rustc, bin/rustdoc
and lib/librustc_driver-37f9a7448f819635.so tops out at GLIBC_2.39, from
librustc_driver. That is exactly ubuntu-24.04's, and ubuntu-24.04 is where
the host half is built: run 33525742815's build job carries labels
["ubuntu-24.04"] and ran 15:26:33Z to 16:24:34Z to publish that asset. So
nothing about the build host moves.

The sid-container reasoning at route.yml:119-123 does not bind this job either
way: toolchain.yml's build declares no container: at all and has never run
in debian:sid. That container exists for QEMU's version, which a bootstrap does
not use.

What can move is the machine. A workflow_dispatch routes build to the T14,
which has never run this workflow — zero workflow_dispatch runs of it exist —
and whose glibc is unmeasured. So the floor is declared as GLIBC_FLOOR and
asserted before publishing: every GLIBC_x.y the shipped binaries name is read
back and a build needing more is refused by name. The scan is a grep over the
dynamic string table rather than objdump, adding nothing to the machine's
provisioning; checked against readelf on the asset, both 2.39.

toyos-ld writes an executable

fs::write creates at the umask, so every program the linker produced was 0644
and the rename carried that inode. Nothing here noticed, because every caller
feeds the output to a loader that does not read the mode.

---- a_linked_program_is_executable stdout ----
assertion `left == right` failed: /tmp/toyos-ld-mode-72205 was written 0644
  left: 420
 right: 493
test result: FAILED. 0 passed; 1 failed

green after. The test drives the linker binary through the Case harness the
determinism suite uses, so it measures what a consumer gets.

The documentation

Generated in the workflow and carried by both releases: the install commands,
the SDK crate versions, the glibc floor, and the raw-window-handle patch a
program that opens a window needs until
rust-windowing/raw-window-handle#223 is released — that stanza read out of
userland/Cargo.toml so it cannot rot. The same is stage 4 of
issues/build/toyos-is-a-normal-target.md, once.

raw-window-handle sits on its release

ToyOSOrg/raw-window-handle has a new branch toyos-0.6.2 — the v0.6.2 tag
(5fda8e8) plus the ToyOS handle, +112 and nothing else — and the tree, the winit
fork and the softbuffer fork patch that instead of toyos. Nothing was
rewritten. Re-applied by hand: v0.6.2 predates upstream's owned handles and the
Web* → WasmBindgen* rename, so the cherry-pick conflicts in src/lib.rs;
src/toyos.rs came over byte-identical and cargo test --all-features on the
branch is 42 passed, 0 failed.

Softbuffer's five deleted lines come back with it. They renamed v0.4.8's own
Web arms to WasmBindgen* only because the patch above them named a
master-based fork; against toyos-0.6.2 they are the compile error. toyos-0.4.8
is back on the release's names and its delta off v0.4.8 is purely additive
(+155). winit needed no repair — winit-web already names
RawDisplayHandle::Web.

Branch heads, all fast-forwards:

ToyOSOrg/raw-window-handle toyos-0.6.2  2a5d102bdbd5dc987b653a7ffff5bedbbbfc53b3
ToyOSOrg/winit             toyos        69ee7eef7c21cbd1f344f2508f8965d83693cb02
ToyOSOrg/softbuffer        toyos-0.4.8  63959966e6d5bdd1d2f2a40f631bc0cdc289405c

The workflow change cannot be proven by a run from this branch

toolchain.yml declares workflow_dispatch, so it was dispatched
(run 33790020091, --ref toolchain-for-consumers) — and the T14's own admission
hook refused it before any step:

build  Set up runner  ##[error]t14 refused workflow_dispatch: dispatched
                      workflow is not from main
build  Set up runner  ##[error]Process completed with exit code 1.

route.yml sends every workflow_dispatch to the T14, and
.github/runner/accept-trusted.sh takes a dispatch only from main. There is
no lane that runs this workflow's dispatch from a branch.

What did run is the pull_request event, run 33789990056, build green: the
modified file parses and every new step evaluates its condition correctly —
glibc floor, manifest and notes and sdk alias all skipped, because the tag
toolchain-linux-x86_64-d8dbd61f8ae95241 is already published and a
pull_request is not what moves a consumer's alias.

The steps' shells were run instead, verbatim out of the committed YAML:

  • glibc floor, over the published asset's own binaries, both arms — green at
    GLIBC_FLOOR=2.39, exit 1 with the named refusal at 2.30.
  • manifest and notes, producing the exact release-note text this PR ships.
  • the sdk alias sequence against a scratch repository — first create 0,
    second create 1 with "a release with the same tag name already exists",
    then edit --notes-file 0 and upload --clobber 0, leaving one release with
    the new notes and the replaced manifest.

The first push to main after this lands is what actually creates
toolchain-linux-x86_64-sdk-0.1.0.

gbae's CI is the first third party to build for ToyOS, and it reported what it
needed in order: a linker inside the toolchain tarball, a tag it can pin by SDK
version, the glibc floor stated, and an output it can exec.

**toyos-ld goes in the tarball.** rustc's ToyOS target spec names its linker
`toyos-ld` and finds it on `PATH` (`rust/compiler/rustc_target/src/spec/base/
toyos.rs`: `linker: Some("toyos-ld".into())`), so a consumer with only the
toolchain had nothing to link with and needed a monorepo checkout. The publish
step now copies the host binary the build already made into
`<host>/stage2/bin/`, beside `rustc`.

**And the install path prefers it — where it is this tree's linker.** The
release tag is `sha256(HEAD:rust HEAD:toyos-abi/src HEAD:toyos/src
HEAD:userland/libc/src)`, which `toyos-ld` is not part of: a branch that changes
the linker reuses an existing release, so "take the shipped binary" would have
had CI link every build with whatever linker first produced that key.
`toyos-ld-witness` travels in the tarball beside it and says which sources it
is, by the same fingerprint `std_fork_witness` uses, and `Owner::Installed`
takes the shipped one only when that witness is this checkout's — else it
builds, as it does today.

Negative control, the whole decision reverted to the base's (always build):

    ---- toolchain::tests::the_shipped_linker_is_taken_only_where_it_is_this_tree_s stdout ----
    assertion `left == right` failed: a shipped linker whose witness is this
    tree's is the one to link through
      left: None
     right: Some(".../toyos-standing-shipped-ld-67381/toyos-ld")
    test result: FAILED. 0 passed; 1 failed

and green on the fix. The oracle is rustc's own target spec, quoted above: it
is what decides that a bare name on `PATH` is the linker at all.

**A tag a consumer can write down.** The content hash stays — it is what makes
two branches carrying the same four trees idempotent — and
`toolchain-linux-x86_64-sdk-<toyos-abi's version>` is added beside it. An asset
belongs to one release and GitHub can neither share nor move one, so the alias
is a second release carrying the `TOOLCHAIN` manifest (a few hundred bytes) and
notes naming the tag the 401 MiB tarball is on; re-uploading the tarball was the
alternative. It is never made on a `pull_request`, whose head is somebody's
branch.

`TOOLCHAIN` is also inside the tarball: the ToyOS commit, the rust submodule
commit, the host triple, the glibc floor and the five SDK crate versions, the
last read from `--sdk-versions` so the set lives in `src/sdkversion.rs` alone.

**glibc: 2.39, measured.** Over the published asset
(`toolchain-linux-x86_64-d8dbd61f8ae95241`), `readelf --dyn-syms` across
`bin/rustc`, `bin/rustdoc` and `lib/librustc_driver-37f9a7448f819635.so` names
GLIBC_2.39 at the top, from librustc_driver. That is exactly `ubuntu-24.04`'s,
and `ubuntu-24.04` is where the host half is built: run 33525742815's `build`
job carries labels `["ubuntu-24.04"]` and ran 15:26:33Z to 16:24:34Z to publish
that asset. So nothing moves — but the reasoning at `route.yml:119-123` does not
bind this job either way, because `toolchain.yml`'s `build` declares no
`container:` at all and never runs in `debian:sid`; that container exists for
QEMU's version, which a bootstrap does not use.

What can move is the machine: a `workflow_dispatch` routes `build` to the T14,
which has never run this workflow (zero `workflow_dispatch` runs of it exist)
and whose glibc is unmeasured. So the floor is declared as `GLIBC_FLOOR` and
asserted before publishing: every `GLIBC_x.y` the shipped binaries name is read
back and a build that needs more is refused. The scan is a grep over the dynamic
string table rather than `objdump`, which adds nothing to the machine's
provisioning and gives the same answer — checked against `readelf` on the asset,
both 2.39.

**toyos-ld writes an executable.** `fs::write` creates at the umask, so every
program the linker produced was 0644 and the rename carried that inode. Nothing
in this repository noticed, because every caller feeds the output to a loader
that does not read the mode. Red before, on the base:

    ---- a_linked_program_is_executable stdout ----
    assertion `left == right` failed: /tmp/toyos-ld-mode-72205 was written 0644
      left: 420
     right: 493
    test result: FAILED. 0 passed; 1 failed

and green after. The test drives the linker binary through the `Case` harness
the determinism suite uses, over a synthetic object, so it measures what a
consumer gets and not what a library call returns.

**The documentation is the release notes**, generated in the workflow and
carried by both releases: the install commands, the SDK crate versions, the
glibc floor and the `raw-window-handle` patch a program that opens a window
needs until rust-windowing/raw-window-handle#223 is released — that stanza read
out of `userland/Cargo.toml` so it cannot rot. The same is stage 4 of
`issues/build/toyos-is-a-normal-target.md`, once.
# Conflicts:
#	issues/build/toyos-is-a-normal-target.md
… back

The last fork the window path goes through that was based on an upstream master
now sits on one. `ToyOSOrg/raw-window-handle` has a new branch `toyos-0.6.2` —
the v0.6.2 tag (5fda8e8) plus the ToyOS handle and nothing else — and the tree,
the winit fork and the softbuffer fork all patch that instead of `toyos`.
Nothing was rewritten: `toyos` stays at c39042b and `add-toyos-support` stays
where the maintainer wants the PR.

    git diff --stat v0.6.2 toyos-0.6.2
     CHANGELOG.md |  4 +++
     src/lib.rs   | 16 +++++++++++
     src/toyos.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++
     3 files changed, 112 insertions(+)

Re-applied by hand rather than cherry-picked: v0.6.2 predates upstream's owned
handles and the `Web*` → `WasmBindgen*` rename, so the module list and the
thread-safety asserts sit differently and the pick conflicts in `src/lib.rs`.
`src/toyos.rs` came over byte-identical (`git diff origin/toyos toyos-0.6.2 --
src/toyos.rs` is empty) and every ToyOS line in `src/lib.rs` is the same text.
`cargo test --all-features` on the branch: 42 passed, 0 failed, including the
three `src/toyos.rs` doc examples.

**And that is why softbuffer's five deleted lines come back.** They renamed
v0.4.8's own `RawWindowHandle::Web`, `WebCanvas`, `WebOffscreenCanvas` and
`RawDisplayHandle::Web` arms to `WasmBindgen*`, and they existed only because
the patch above them named a master-based fork where those variants are gone.
Against `toyos-0.6.2` they are the compile error, so `toyos-0.4.8` is back on
the release's own names and its delta off v0.4.8 is now purely additive:

    Cargo.toml              |   7 +++
    src/backend_dispatch.rs |   2 +
    src/backends/mod.rs     |   2 +
    src/backends/toyos.rs   | 142 ++++++++++++++++++++++++++++++++++++++
    src/lib.rs              |   2 +
    5 files changed, 155 insertions(+)

winit needed no such repair — it already names `RawDisplayHandle::Web` and
`RawWindowHandle::WebCanvas` in `winit-web`, so the master-based patch was the
thing that disagreed with it.

Branch heads, all fast-forwards:
  ToyOSOrg/raw-window-handle toyos-0.6.2  2a5d102bdbd5dc987b653a7ffff5bedbbbfc53b3
  ToyOSOrg/winit             toyos        69ee7eef7c21cbd1f344f2508f8965d83693cb02
  ToyOSOrg/softbuffer        toyos-0.4.8  63959966e6d5bdd1d2f2a40f631bc0cdc289405c

`--check-forks` reads all three back: "20 branches asked, all current", with
raw-window-handle at 2a5d102bdb, winit at 69ee7eef7c and softbuffer at
63959966e6 in `userland/Cargo.lock`.
`bin/cargo` is a symlink into the runner's own rustup and the tarball excludes
it; a glob over `bin/*` would have measured another machine's binary and called
the answer this toolchain's. `-type f` is what leaves it out.

Both arms, run from the committed step against the published asset's binaries
(`toolchain-linux-x86_64-d8dbd61f8ae95241`, extracted):

    === GLIBC_FLOOR=2.39 ===
    the host half names up to GLIBC_2.39; this release states 2.39
    EXIT=0
    === GLIBC_FLOOR=2.30 ===
    the host half names up to GLIBC_2.39; this release states 2.30
    ::error::the host half needs GLIBC_2.39 and this release states
    ::error::2.30, so a consumer on the stated floor cannot run it.
    EXIT=1
The `sdk alias` step's create-else-edit-and-clobber sequence, run against a
scratch repository rather than asserted:

    === first create ===
    https://github.com/.../releases/tag/toolchain-linux-x86_64-sdk-0.1.0
    EXIT=0
    === second create ===
    a release with the same tag name already exists: toolchain-linux-x86_64-sdk-0.1.0
    EXIT=1
    === edit notes ===  EXIT=0
    === upload --clobber ===  EXIT=0
    === the release now ===
    asset: TOOLCHAIN-probe
    notes v2

so the `||` branch is the moving one and nothing is re-uploaded but the
manifest. The comment now says that instead of asserting what GitHub cannot do.
@Japabu
Japabu marked this pull request as ready for review September 3, 2026 18:26
@Japabu
Japabu enabled auto-merge September 3, 2026 18:26
@Japabu
Japabu added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 8b07e87 Sep 3, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant