use std::path::{Path, PathBuf};
#[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.file_name())
}
}
pub const PINNED: &[Pinned] = &[
Pinned {
tuple: "aarch64-windows-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-18/rucc-sysroot-aarch64-windows-gnu.tar.gz",
sha256: "037b839abe565493360d320d833b5bfa048884eed445b51d41aafb59acfbac84",
},
Pinned {
tuple: "i686-windows-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-18/rucc-sysroot-i686-windows-gnu.tar.gz",
sha256: "e66deb5e67e35e12d2299d136a0d150168568e2b8aa7aab0c2b6cc8254b0cb53",
},
Pinned {
tuple: "x86_64-windows-gnu",
url: "https://github.com/tamnd/rucc-cross/releases/download/sysroots-2026-09-18/rucc-sysroot-x86_64-windows-gnu.tar.gz",
sha256: "8774d78560c196f182b8cf865e9fe57e2937779fdcf14b7f7b3f321de2cf4488",
},
];
#[must_use]
pub fn pinned_for(tuple: &str) -> Option<&'static Pinned> {
look(PINNED, tuple)
}
#[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 rucc_tuple::TargetTuple;
use super::*;
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/rucc-sysroot-aarch64-linux-musl.tar.gz")
);
}
#[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");
}
}