feat(installer): build for and install on linux-aarch64 #10
@@ -66,6 +66,9 @@ env:
|
|||||||
BIN: runicgateway-installer
|
BIN: runicgateway-installer
|
||||||
LINUX_TARGET: x86_64-unknown-linux-gnu
|
LINUX_TARGET: x86_64-unknown-linux-gnu
|
||||||
WINDOWS_TARGET: x86_64-pc-windows-gnu
|
WINDOWS_TARGET: x86_64-pc-windows-gnu
|
||||||
|
# The installer has to run wherever the sidecar it installs can run, and link
|
||||||
|
# publishes an arm64 Linux binary from v1.2.0 (PLAN.md §5.2, step 4 of 4).
|
||||||
|
ARM64_TARGET: aarch64-unknown-linux-gnu
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -237,7 +240,7 @@ jobs:
|
|||||||
echo "Release credentials present."
|
echo "Release credentials present."
|
||||||
|
|
||||||
# ── RUST ADAPTER: toolchain + cross-compile deps ─────────────────────
|
# ── RUST ADAPTER: toolchain + cross-compile deps ─────────────────────
|
||||||
- name: Install Rust toolchain, Windows target, and MinGW linker
|
- name: Install Rust toolchain, cross targets, and their linkers
|
||||||
if: ${{ steps.plan.outputs.release == 'true' }}
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -254,6 +257,7 @@ jobs:
|
|||||||
export PATH="${HOME}/.cargo/bin:${PATH}"
|
export PATH="${HOME}/.cargo/bin:${PATH}"
|
||||||
rustup component add rustfmt
|
rustup component add rustfmt
|
||||||
rustup target add "${WINDOWS_TARGET}"
|
rustup target add "${WINDOWS_TARGET}"
|
||||||
|
rustup target add "${ARM64_TARGET}"
|
||||||
|
|
||||||
- name: Set the crate version to match the release
|
- name: Set the crate version to match the release
|
||||||
if: ${{ steps.plan.outputs.release == 'true' }}
|
if: ${{ steps.plan.outputs.release == 'true' }}
|
||||||
@@ -299,8 +303,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cp "target/${LINUX_TARGET}/release/${BIN}" "dist/${BIN}-linux-x86_64"
|
cp "target/${LINUX_TARGET}/release/${BIN}" "dist/${BIN}-linux-x86_64"
|
||||||
|
cp "target/${ARM64_TARGET}/release/${BIN}" "dist/${BIN}-linux-aarch64"
|
||||||
cp "target/${WINDOWS_TARGET}/release/${BIN}.exe" "dist/${BIN}-windows-x86_64.exe"
|
cp "target/${WINDOWS_TARGET}/release/${BIN}.exe" "dist/${BIN}-windows-x86_64.exe"
|
||||||
( cd dist && sha256sum "${BIN}-linux-x86_64" "${BIN}-windows-x86_64.exe" > SHA256SUMS )
|
# Every artifact must be listed: `sha256sum -c` passes silently over a
|
||||||
|
# file the sums do not mention, and an operator verifying a download
|
||||||
|
# would get a pass on a binary nobody vouched for.
|
||||||
|
( cd dist && sha256sum "${BIN}-linux-x86_64" "${BIN}-linux-aarch64" "${BIN}-windows-x86_64.exe" > SHA256SUMS )
|
||||||
ls -l dist && echo "----" && cat dist/SHA256SUMS
|
ls -l dist && echo "----" && cat dist/SHA256SUMS
|
||||||
|
|
||||||
# ── RELEASE ENGINE: commit the bump, tag, push ───────────────────────
|
# ── RELEASE ENGINE: commit the bump, tag, push ───────────────────────
|
||||||
@@ -365,7 +373,7 @@ jobs:
|
|||||||
| jq -r '.id')"
|
| jq -r '.id')"
|
||||||
echo "Created release ${TAG} (id=${REL_ID})"
|
echo "Created release ${TAG} (id=${REL_ID})"
|
||||||
|
|
||||||
for f in "${BIN}-linux-x86_64" "${BIN}-windows-x86_64.exe" SHA256SUMS; do
|
for f in "${BIN}-linux-x86_64" "${BIN}-linux-aarch64" "${BIN}-windows-x86_64.exe" SHA256SUMS; do
|
||||||
curl -sSf -X POST "${API}/releases/${REL_ID}/assets?name=${f}" \
|
curl -sSf -X POST "${API}/releases/${REL_ID}/assets?name=${f}" \
|
||||||
-H "Authorization: token ${CI_TOKEN}" \
|
-H "Authorization: token ${CI_TOKEN}" \
|
||||||
-F "attachment=@dist/${f}" >/dev/null
|
-F "attachment=@dist/${f}" >/dev/null
|
||||||
|
|||||||
@@ -43,8 +43,10 @@ pub struct LinkComponent {
|
|||||||
pub tag: String,
|
pub tag: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub protocol: u32,
|
pub protocol: u32,
|
||||||
/// Keyed by platform (`linux-x86_64`, `windows-x86_64`) — link publishes a binary per OS and
|
/// Keyed by platform (`linux-x86_64`, `linux-aarch64`, `windows-x86_64`) — link publishes a
|
||||||
/// the installer runs on both, so a single hash could only ever describe one of them.
|
/// 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>,
|
pub assets: BTreeMap<String, Asset>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,10 @@ impl Bundle {
|
|||||||
let key = platform_key()?;
|
let key = platform_key()?;
|
||||||
self.link.assets.get(key).ok_or_else(|| {
|
self.link.assets.get(key).ok_or_else(|| {
|
||||||
anyhow::anyhow!(
|
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.bundle,
|
||||||
self.link
|
self.link
|
||||||
.assets
|
.assets
|
||||||
@@ -102,12 +107,16 @@ impl Bundle {
|
|||||||
pub fn platform_key() -> Result<&'static str> {
|
pub fn platform_key() -> Result<&'static str> {
|
||||||
match (std::env::consts::OS, std::env::consts::ARCH) {
|
match (std::env::consts::OS, std::env::consts::ARCH) {
|
||||||
("linux", "x86_64") => Ok("linux-x86_64"),
|
("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"),
|
("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
|
// Naming the platforms that do exist beats failing later with a missing-key error that
|
||||||
// failing later with a missing-key error that reads like a corrupt bundle.
|
// reads like a corrupt bundle.
|
||||||
(os, arch) => bail!(
|
(os, arch) => bail!(
|
||||||
"no Runic Gateway build exists for {os}/{arch}. \
|
"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.link.version, "1.1.0");
|
||||||
assert_eq!(bundle.overlay.version, "0.1.1");
|
assert_eq!(bundle.overlay.version, "0.1.1");
|
||||||
assert_eq!(bundle.overlay.servuo.patches_verified_against, "57.4");
|
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"));
|
assert!(bundle.overlay.asset.name.ends_with(".tar.gz"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn both_platforms_have_a_sidecar_binary() {
|
fn every_platform_the_bundle_names_is_well_formed() {
|
||||||
// Whichever of the two this test runs on, the lookup must resolve — a bundle missing the
|
// A floor, not an exact count: `linux-aarch64` joins these from link's first arm64 release
|
||||||
// host's binary would fail an install after the overlay had already been deployed.
|
// (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 bundle = parse(CURRENT).unwrap();
|
||||||
let asset = bundle.sidecar_asset().unwrap();
|
for required in ["linux-x86_64", "windows-x86_64"] {
|
||||||
assert_eq!(asset.sha256.len(), 64);
|
let asset = bundle
|
||||||
assert!(asset.url.contains(&bundle.link.tag));
|
.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]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user