use std::fmt::Write as _;
use rucc_tuple::{Os, TARGETS, TargetTuple};
use crate::artifact::Pinned;
use crate::manifest::Licence;
use crate::wall::Wall;
const HEADER: &str = "rucc distribution manifest 1";
const PRODUCER: &str = "https://github.com/tamnd/rucc-cross";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arrival {
Included,
Pinned(&'static Pinned),
Wall(Wall),
Unpublished,
}
impl Arrival {
#[must_use]
pub fn of(target: TargetTuple) -> Arrival {
if let Some(wall) = Wall::of(target) {
return Arrival::Wall(wall);
}
if let Some(pinned) = crate::artifact::pinned_for(&target.to_canonical_string()) {
return Arrival::Pinned(pinned);
}
if target.os() == Os::None {
return Arrival::Included;
}
Arrival::Unpublished
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Arrival::Included => "included",
Arrival::Pinned(_) => "pinned",
Arrival::Wall(_) => "wall",
Arrival::Unpublished => "unpublished",
}
}
}
#[must_use]
pub fn render(version: &str) -> String {
let mut out = String::new();
let _ = writeln!(out, "{HEADER}");
let _ = writeln!(out, "release\t{version}");
let _ = writeln!(out, "producer\t{PRODUCER}");
let _ = writeln!(out, "payload\trucc\t{}", Licence::Apache2);
let _ = writeln!(out, "targets\t{}", TARGETS.len());
for entry in TARGETS {
let target = match entry.parse() {
Ok(target) => target,
Err(_) => {
let _ = writeln!(out, "target\t{}\tunparsed", entry.tuple);
continue;
}
};
let arrival = Arrival::of(target);
let _ = write!(out, "target\t{}\t{}", entry.tuple, arrival.as_str());
match arrival {
Arrival::Pinned(pinned) => {
let _ = write!(out, "\t{}\t{}", pinned.url, pinned.sha256);
}
Arrival::Wall(wall) => {
let _ = write!(out, "\t{}", wall.under());
}
Arrival::Included | Arrival::Unpublished => {}
}
out.push('\n');
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::artifact::PINNED;
fn lines() -> Vec<String> {
render("0.0.0").lines().map(str::to_owned).collect()
}
fn row(tuple: &str) -> Vec<String> {
let wanted = format!("target\t{tuple}\t");
let text = render("0.0.0");
let line = text
.lines()
.find(|line| line.starts_with(&wanted))
.unwrap_or_else(|| panic!("no line for {tuple}"));
line.split('\t').map(str::to_owned).collect()
}
#[test]
fn the_header_says_what_the_file_is_and_which_release_it_describes() {
let lines = lines();
assert_eq!(lines[0], HEADER);
assert_eq!(lines[1], "release\t0.0.0");
assert_eq!(lines[2], format!("producer\t{PRODUCER}"));
assert_eq!(lines[3], "payload\trucc\tapache-2.0");
}
#[test]
fn there_is_a_line_for_every_target_and_the_count_says_how_many() {
let lines = lines();
assert_eq!(lines[4], format!("targets\t{}", TARGETS.len()));
let rows: Vec<&String> = lines.iter().filter(|line| line.starts_with("target\t")).collect();
assert_eq!(rows.len(), TARGETS.len());
for (row, entry) in rows.iter().zip(TARGETS) {
assert!(row.starts_with(&format!("target\t{}\t", entry.tuple)), "{row}");
}
}
#[test]
fn a_target_behind_a_licence_wall_says_which_licence_put_it_there() {
assert_eq!(row("aarch64-macos"), ["target", "aarch64-macos", "wall", "apple-sdk"]);
assert_eq!(
row("x86_64-windows-msvc"),
["target", "x86_64-windows-msvc", "wall", "microsoft-sdk"]
);
assert_eq!(row("x86_64-windows-gnu"), ["target", "x86_64-windows-gnu", "unpublished"]);
}
#[test]
fn a_freestanding_target_needs_nothing_and_says_so_rather_than_saying_nobody_built_it() {
assert_eq!(row("armv7m-none-eabi")[2], "included");
assert_eq!(row("x86_64-linux-gnu")[2], "unpublished");
}
#[test]
fn a_pinned_target_carries_the_url_and_the_hash_it_is_held_to() {
static PINNED_ROW: Pinned = Pinned {
tuple: "x86_64-linux-musl",
url: "https://example.invalid/rucc-sysroot-x86_64-linux-musl.tar.gz",
sha256: "3333333333333333333333333333333333333333333333333333333333333333",
};
let pinned = &PINNED_ROW;
let arrival = Arrival::Pinned(pinned);
assert_eq!(arrival.as_str(), "pinned");
let mut line = format!("target\t{}\t{}", pinned.tuple, arrival.as_str());
line.push_str(&format!("\t{}\t{}", pinned.url, pinned.sha256));
let fields: Vec<&str> = line.split('\t').collect();
assert_eq!(fields.len(), 5);
assert_eq!(fields[3], pinned.url);
assert_eq!(fields[4], pinned.sha256);
}
#[test]
fn nothing_is_pinned_yet_and_the_file_does_not_pretend_otherwise() {
assert!(PINNED.is_empty(), "a row landed, so this test is the one to update");
let text = render("0.0.0");
assert!(!text.contains("\tpinned"), "{text}");
assert!(text.contains("\tunpublished\n"), "{text}");
}
#[test]
fn every_line_has_a_kind_and_no_field_is_empty() {
for line in lines() {
let fields: Vec<&str> = line.split('\t').collect();
if line == HEADER {
continue;
}
assert!(
matches!(fields[0], "release" | "producer" | "payload" | "targets" | "target"),
"{line}"
);
for field in fields {
assert!(!field.is_empty(), "an empty field in `{line}`");
}
}
}
}