feat(installer): build for and install on linux-aarch64
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 54s

Step 4 of PLAN.md §5.2, and the half that faces the operator: the
release now cross-compiles aarch64-unknown-linux-gnu, and platform_key()
resolves ("linux","aarch64") to the bundle key link publishes under
instead of refusing the host by name.

Same toolchain shape as the Windows step -- a linker plus a CC/AR pair,
because ring (under ureq's rustls) compiles C and assembly. And the same
packaging trap named in the sums comment: an artifact missing from
SHA256SUMS is one `sha256sum -c` passes over silently, so the new binary
is added to both the sums and the upload list.

Two test changes fall out of the asset map growing a key:

- The exact `assets.len() == 2` assertion is replaced by a check that
  each key CI requires is present and well-formed. An exact count would
  fail on the first bundle that adds arm64 -- reporting correct
  behaviour as a regression.
- The host-binary lookup now accepts either outcome, and says why.
  Bundles are kept unchanged forever so `--bundle` stays reproducible,
  which means one published before arm64 existed can never gain that
  key. On such a host the run must fail with the reason rather than
  something that reads like a corrupt document, so sidecar_asset()'s
  error now says so and the test asserts it.

Verified by cross-building this crate for aarch64 in a
rust:1-slim-bookworm container -- ELF 64-bit LSB pie executable, ARM
aarch64 -- and by running fmt, clippy -D warnings and the tests on both
Linux and the Windows host, since only half of service.rs compiles on
either.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-05 05:34:08 -05:00
parent 14f65d50a0
commit 6941925fa5
2 changed files with 71 additions and 16 deletions

View File

@@ -43,8 +43,10 @@ pub struct LinkComponent {
pub tag: String,
pub version: String,
pub protocol: u32,
/// Keyed by platform (`linux-x86_64`, `windows-x86_64`) — link publishes a binary per OS and
/// the installer runs on both, so a single hash could only ever describe one of them.
/// Keyed by platform (`linux-x86_64`, `linux-aarch64`, `windows-x86_64`) — link publishes a
/// binary per target and the installer runs on each, so a single hash could only ever describe
/// one of them. The set grows over time, so a bundle is not expected to carry every key this
/// binary knows about: an older one pinned with `--bundle` predates arm64 entirely.
pub assets: BTreeMap<String, Asset>,
}
@@ -85,7 +87,10 @@ impl Bundle {
let key = platform_key()?;
self.link.assets.get(key).ok_or_else(|| {
anyhow::anyhow!(
"bundle {} has no uo-link binary for {key} (it has: {})",
"bundle {} has no uo-link binary for {key} (it has: {}).\n\
Bundles published before uo-link built for this platform cannot gain one \
retroactively — they are kept unchanged so `--bundle` stays reproducible. \
Run without `--bundle` to take the current one.",
self.bundle,
self.link
.assets
@@ -102,12 +107,16 @@ impl Bundle {
pub fn platform_key() -> Result<&'static str> {
match (std::env::consts::OS, std::env::consts::ARCH) {
("linux", "x86_64") => Ok("linux-x86_64"),
// Ampere/Graviton and Pi-class hosts (PLAN.md §5.2). Linux only: the shard dials the
// sidecar out on loopback, so the pair has to be co-located, and no ServUO host is a
// Windows-on-arm box or a Mac.
("linux", "aarch64") => Ok("linux-aarch64"),
("windows", "x86_64") => Ok("windows-x86_64"),
// arm64 is not buildable today (PLAN.md §2.6) and macOS is not a target. Saying so beats
// failing later with a missing-key error that reads like a corrupt bundle.
// Naming the platforms that do exist beats failing later with a missing-key error that
// reads like a corrupt bundle.
(os, arch) => bail!(
"no Runic Gateway build exists for {os}/{arch}. \
The released components target linux-x86_64 and windows-x86_64."
The released components target linux-x86_64, linux-aarch64 and windows-x86_64."
),
}
}
@@ -190,18 +199,56 @@ mod tests {
assert_eq!(bundle.link.version, "1.1.0");
assert_eq!(bundle.overlay.version, "0.1.1");
assert_eq!(bundle.overlay.servuo.patches_verified_against, "57.4");
assert_eq!(bundle.link.assets.len(), 2);
assert!(bundle.overlay.asset.name.ends_with(".tar.gz"));
}
#[test]
fn both_platforms_have_a_sidecar_binary() {
// Whichever of the two this test runs on, the lookup must resolve — a bundle missing the
// host's binary would fail an install after the overlay had already been deployed.
fn every_platform_the_bundle_names_is_well_formed() {
// A floor, not an exact count: `linux-aarch64` joins these from link's first arm64 release
// (PLAN.md §5.2), and a test asserting "exactly two" would fail on the bundle that adds it
// rather than on anything being wrong.
let bundle = parse(CURRENT).unwrap();
let asset = bundle.sidecar_asset().unwrap();
assert_eq!(asset.sha256.len(), 64);
assert!(asset.url.contains(&bundle.link.tag));
for required in ["linux-x86_64", "windows-x86_64"] {
let asset = bundle
.link
.assets
.get(required)
.unwrap_or_else(|| panic!("bundle carries no {required} binary"));
assert_eq!(asset.sha256.len(), 64);
assert!(asset.url.contains(&bundle.link.tag));
}
}
#[test]
fn the_hosts_binary_either_resolves_or_says_why_not() {
// On x86_64 the lookup must resolve — a bundle missing the host's binary would otherwise
// fail an install after the overlay had already been deployed. On a host whose platform
// postdates the bundle (an arm64 box reading the first published one), it must fail with
// the reason, since every bundle is kept unchanged forever so `--bundle` stays
// reproducible and therefore cannot gain a key retroactively.
let bundle = parse(CURRENT).unwrap();
match bundle.sidecar_asset() {
Ok(asset) => {
assert_eq!(asset.sha256.len(), 64);
assert!(asset.url.contains(&bundle.link.tag));
}
Err(e) => {
let msg = e.to_string();
assert!(msg.contains(platform_key().unwrap()), "{msg}");
assert!(msg.contains("--bundle"), "{msg}");
}
}
}
#[test]
fn the_host_is_a_platform_the_components_are_built_for() {
// `cargo test` running at all means the host is one the crate compiles on, so a refusal
// here is a build target the release workflows have not caught up with.
let key = platform_key().unwrap();
assert!(
["linux-x86_64", "linux-aarch64", "windows-x86_64"].contains(&key),
"unexpected platform key {key}"
);
}
#[test]