use std::path::{Path, PathBuf};
use rucc_tuple::{Env, Os, TargetTuple};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pinned {
pub tuple: &'static str,
pub url: &'static str,
pub sha256: &'static str,
}
impl Pinned {
#[must_use]
pub fn file_name(&self) -> &'static str {
self.url.rsplit('/').next().unwrap_or(self.url)
}
#[must_use]
pub fn archive_in(&self, cache: &Path) -> PathBuf {
cache.join("downloads").join(&self.sha256[..12]).join(self.file_name())
}
}
pub const PINNED: &[Pinned] = &[
Pinned {
tuple: "aarch64-linux-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-24/rucc-sysroot-aarch64-linux-gnu.tar.gz",
sha256: "6f3f443a65c66d6810288a52c6993328736cf22499cf6b5600083753f93069f2",
},
Pinned {
tuple: "aarch64-linux-musl",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-23/rucc-sysroot-aarch64-linux-musl.tar.gz",
sha256: "098c24c0c27d264dceaee76141f0933845dcc8a7c3a4b473954987f731fecde5",
},
Pinned {
tuple: "aarch64-windows-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-21/rucc-sysroot-aarch64-windows-gnu.tar.gz",
sha256: "cc6be4263b09a475895d9054bb4b096d837010b7e5ed25ebc8934accccd5258e",
},
Pinned {
tuple: "armv7-linux-gnueabihf",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-24/rucc-sysroot-armv7-linux-gnueabihf.tar.gz",
sha256: "7b500c8905a91c010ec92ee70b73b0a9255d49eb1c46ca8a1c001cd80a819a95",
},
Pinned {
tuple: "armv7-linux-musleabihf",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-23/rucc-sysroot-armv7-linux-musleabihf.tar.gz",
sha256: "645ceef60e0302b260ad804569243c3470f7545f1b135a143f9b7ec8408b6f78",
},
Pinned {
tuple: "i686-linux-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-24/rucc-sysroot-i686-linux-gnu.tar.gz",
sha256: "6d8e96b37e0395b67e5a178f2e5fd62588c6b30ae62fb3f4533a8905565ade09",
},
Pinned {
tuple: "i686-windows-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-21/rucc-sysroot-i686-windows-gnu.tar.gz",
sha256: "296de7554f57d308c00b405c145eb314886e1e28ff8507aa6e7b34e7e4def7f1",
},
Pinned {
tuple: "loongarch64-linux-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-24/rucc-sysroot-loongarch64-linux-gnu.tar.gz",
sha256: "67e0565d9acc6a768dd2ad6318fab716c34bcbfaeb005398e38d4ca8d7f5c3c2",
},
Pinned {
tuple: "powerpc64le-linux-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-24/rucc-sysroot-powerpc64le-linux-gnu.tar.gz",
sha256: "7a942076c80962f80314bae54f91d2bbdcd915e6a7f2232c936414617db0a88e",
},
Pinned {
tuple: "riscv64-linux-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-24/rucc-sysroot-riscv64-linux-gnu.tar.gz",
sha256: "7e04cef3741577fdec11169d87ff0ebf3545beef952a3fb68a6d066439c90a8a",
},
Pinned {
tuple: "riscv64-linux-musl",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-23/rucc-sysroot-riscv64-linux-musl.tar.gz",
sha256: "00fc00f996d0a9a3de1cabd95840d423de1562ed5345dd5c47f1bf89b30f0b99",
},
Pinned {
tuple: "s390x-linux-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-24/rucc-sysroot-s390x-linux-gnu.tar.gz",
sha256: "933531721ef7428cc3a4c1af70c3dbe94daa007957b9c39a91e5cd1d86bc2249",
},
Pinned {
tuple: "x86_64-linux-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-24/rucc-sysroot-x86_64-linux-gnu.tar.gz",
sha256: "1e22d1247ae697a6b4b014731f8f66729095c742a8154f8880c33036e7da0404",
},
Pinned {
tuple: "x86_64-linux-musl",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-23/rucc-sysroot-x86_64-linux-musl.tar.gz",
sha256: "93b42df66c0a7547c3e96cd5512267790df1ca0227a0000ba4f1808eb214c376",
},
Pinned {
tuple: "x86_64-windows-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-21/rucc-sysroot-x86_64-windows-gnu.tar.gz",
sha256: "2f1e34ea0ad3e1ae8be08c054c61030e0a916053e194916f4045e08dcfd69545",
},
];
pub const KERNEL_HEADERS: Pinned = Pinned {
tuple: "kernel-headers",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-23/rucc-kernel-headers.tar.gz",
sha256: "e96934f553df19d6bdbb6804f1b52d079adc4bea10a819d51eda5bc4ec584f2d",
};
#[must_use]
pub fn pinned_for(tuple: &str) -> Option<&'static Pinned> {
look(PINNED, tuple)
}
#[must_use]
pub fn pinned_for_target(target: TargetTuple) -> Option<&'static Pinned> {
pinned_for(&target.to_canonical_string())
.or_else(|| pinned_for(&glibc_base(target)?.to_canonical_string()))
}
#[must_use]
pub fn glibc_base(target: TargetTuple) -> Option<TargetTuple> {
if target.os() != Os::Linux || target.env() != Env::Gnu {
return None;
}
target.env_version()?;
let mut base = TargetTuple::builder(target.arch(), target.os())
.sub_arch(target.sub_arch())
.endian(target.endian())
.data_model(target.data_model())
.env(target.env())
.abi(target.abi());
if let Some(version) = target.os_version() {
base = base.os_version(version);
}
base.build().ok()
}
#[must_use]
pub fn pinned_targets() -> Vec<&'static str> {
PINNED.iter().map(|what| what.tuple).collect()
}
fn look<'a>(table: &'a [Pinned], tuple: &str) -> Option<&'a Pinned> {
table.iter().find(|what| what.tuple == tuple)
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
#[test]
fn a_pinned_glibc_release_shares_the_artifact_of_its_tuple() {
let tuple = |spelling: &str| spelling.parse::<TargetTuple>().expect("a tuple");
let base = glibc_base(tuple("x86_64-linux-gnu.2.28")).expect("glibc with a release");
assert_eq!(base.to_canonical_string(), "x86_64-linux-gnu");
let arm = glibc_base(tuple("armv7-linux-gnu.2.31eabihf")).expect("glibc with a release");
assert_eq!(arm.to_canonical_string(), "armv7-linux-gnueabihf");
assert_eq!(glibc_base(tuple("x86_64-linux-gnu")), None);
assert_eq!(glibc_base(tuple("x86_64-linux-musl")), None);
assert_eq!(
pinned_for_target(tuple("x86_64-linux-musl")).map(|what| what.tuple),
Some("x86_64-linux-musl")
);
}
const TABLE: &[Pinned] = &[
Pinned {
tuple: "aarch64-linux-musl",
url: "https://example.invalid/rucc-sysroot-aarch64-linux-musl.tar.gz",
sha256: "1111111111111111111111111111111111111111111111111111111111111111",
},
Pinned {
tuple: "x86_64-linux-musl",
url: "https://example.invalid/rucc-sysroot-x86_64-linux-musl.tar.gz",
sha256: "2222222222222222222222222222222222222222222222222222222222222222",
},
];
#[test]
fn a_target_the_table_names_is_found_and_one_it_does_not_is_not() {
let found = look(TABLE, "x86_64-linux-musl").expect("the table has that one");
assert_eq!(found.sha256, TABLE[1].sha256);
assert_eq!(look(TABLE, "riscv64-linux-gnu"), None);
}
#[test]
fn a_longer_tuple_is_not_the_row_it_begins_with() {
assert_eq!(look(TABLE, "x86_64-linux-musl.1.2.5"), None);
assert_eq!(look(TABLE, "x86_64-linux"), None);
}
#[test]
fn the_archive_is_named_by_the_url_and_kept_under_the_cache() {
let what = TABLE[0];
assert_eq!(what.file_name(), "rucc-sysroot-aarch64-linux-musl.tar.gz");
assert_eq!(
what.archive_in(&PathBuf::from("/tmp/cache")),
PathBuf::from(
"/tmp/cache/downloads/111111111111/rucc-sysroot-aarch64-linux-musl.tar.gz"
)
);
}
#[test]
fn two_releases_of_one_target_are_not_the_same_path() {
let cache = PathBuf::from("/tmp/cache");
let old = TABLE[0];
let new = Pinned { sha256: TABLE[1].sha256, ..old };
assert_eq!(old.file_name(), new.file_name());
assert_ne!(old.archive_in(&cache), new.archive_in(&cache));
}
#[test]
fn every_row_is_a_target_a_url_and_a_hash() {
for what in PINNED {
let tuple: TargetTuple =
what.tuple.parse().unwrap_or_else(|why| panic!("{}: {why}", what.tuple));
assert_eq!(
tuple.to_canonical_string(),
what.tuple,
"a row is named by the canonical spelling, because that is what names the \
directory the tree is installed at"
);
assert!(what.url.starts_with("https://"), "{}: {}", what.tuple, what.url);
assert!(!what.url.contains('?') && !what.url.contains('#'), "{}", what.url);
assert!(!what.file_name().is_empty(), "{} ends with a separator", what.url);
assert_eq!(what.sha256.len(), 64, "{}: {}", what.tuple, what.sha256);
assert!(
what.sha256.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)),
"{}: {} is not lowercase hex, and the check compares text",
what.tuple,
what.sha256
);
}
let mut sorted: Vec<&str> = pinned_targets();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted, pinned_targets(), "the rows are in tuple order and each target once");
}
#[test]
fn the_kernel_tree_is_a_url_and_a_hash_and_no_target_is_called_that() {
let what = KERNEL_HEADERS;
assert!(what.url.starts_with("https://"), "{}", what.url);
assert!(!what.url.contains('?') && !what.url.contains('#'), "{}", what.url);
assert_eq!(what.file_name(), "rucc-kernel-headers.tar.gz");
assert_eq!(what.sha256.len(), 64, "{}", what.sha256);
assert!(
what.sha256.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)),
"{} is not lowercase hex",
what.sha256
);
assert_eq!(pinned_for(what.tuple), None);
}
}