use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TargetMode {
Local,
Remote,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuildTarget {
os: String,
arch: String,
destination: String,
mode: TargetMode,
}
impl BuildTarget {
pub fn local() -> Self {
Self {
os: Self::local_os(),
arch: Self::local_arch(),
destination: "local".to_string(),
mode: TargetMode::Local,
}
}
pub fn remote(os: &str, arch: &str, destination: &str) -> Self {
Self {
os: os.to_string(),
arch: arch.to_string(),
destination: destination.to_string(),
mode: TargetMode::Remote,
}
}
pub fn os(&self) -> &str {
&self.os
}
pub fn arch(&self) -> &str {
&self.arch
}
pub fn destination(&self) -> &str {
&self.destination
}
#[cfg(test)]
pub fn mode(&self) -> &TargetMode {
&self.mode
}
pub fn is_local(&self) -> bool {
matches!(self.mode, TargetMode::Local)
}
pub fn host(&self) -> &str {
self.destination
.split_once(':')
.map(|(host, _)| host)
.unwrap_or(self.destination())
}
pub fn remote_path(&self) -> &str {
self.destination
.split_once(':')
.map(|(_, path)| path)
.unwrap_or(self.destination())
}
pub fn make_cmd(&self) -> &str {
match self.os() {
"FreeBSD" => "gmake",
_ => "make",
}
}
pub fn log_key(&self) -> String {
self.destination()
.chars()
.map(|ch| {
if ch == '/' || ch == ':' || ch == '@' {
'_'
} else {
ch
}
})
.collect()
}
pub fn title(&self) -> String {
if self.is_local() {
format!("{} {} localhost", self.os(), self.arch())
} else {
format!("{} {} {}", self.os(), self.arch(), self.destination())
}
}
fn local_os() -> String {
match std::env::consts::OS {
"linux" => "GNU/Linux".to_string(),
"freebsd" => "FreeBSD".to_string(),
"netbsd" => "NetBSD".to_string(),
"openbsd" => "OpenBSD".to_string(),
other => other.to_string(),
}
}
fn local_arch() -> String {
match std::env::consts::ARCH {
"x86_64" => "x86_64".to_string(),
"aarch64" => "aarch64".to_string(),
other => other.to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResultMirrorPlan {
enabled: bool,
root: PathBuf,
manifest: PathBuf,
}
impl ResultMirrorPlan {
pub fn new(enabled: bool, root: PathBuf, entry: &str) -> Self {
Self {
enabled,
root,
manifest: Self::manifest_for_entry(entry),
}
}
#[cfg(test)]
pub fn disabled(root: PathBuf, entry: &str) -> Self {
Self::new(false, root, entry)
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn root(&self) -> &Path {
&self.root
}
pub fn manifest(&self) -> &Path {
&self.manifest
}
fn manifest_for_entry(entry: &str) -> PathBuf {
PathBuf::from("build")
.join(".mxrun")
.join(format!("{entry}.paths"))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MxrunConfig {
targets: Vec<BuildTarget>,
}
impl MxrunConfig {
pub fn parse(src: &str) -> Result<Self, String> {
Self::from_lines(
src.lines()
.enumerate()
.filter_map(Line::meaningful)
.map(Line::parse)
.collect::<Result<Vec<_>, _>>()?,
)
}
pub fn targets(&self) -> &[BuildTarget] {
&self.targets
}
fn from_lines(targets: Vec<BuildTarget>) -> Result<Self, String> {
(!targets.is_empty())
.then_some(Self { targets })
.ok_or_else(|| "mxrun config has no targets".to_string())
}
}
struct Line<'a> {
lineno: usize,
text: &'a str,
}
impl<'a> Line<'a> {
fn meaningful((lineno, raw): (usize, &'a str)) -> Option<Self> {
let text = raw.trim();
if text.is_empty() || text.starts_with('#') {
None
} else {
Some(Self {
lineno: lineno + 1,
text,
})
}
}
fn parse(self) -> Result<BuildTarget, String> {
if self.text == "local" {
Ok(BuildTarget::local())
} else {
self.remote_target()
}
}
fn remote_target(&self) -> Result<BuildTarget, String> {
self.fields().and_then(|fields| {
fields[2]
.contains(':')
.then_some(BuildTarget::remote(fields[0], fields[1], fields[2]))
.ok_or_else(|| {
format!(
"invalid mxrun line {}: missing host:/destination in third field",
self.lineno
)
})
})
}
fn fields(&self) -> Result<Vec<&str>, String> {
self.text
.split_whitespace()
.collect::<Vec<_>>()
.pipe_ref(|fields| {
(fields.len() == 3)
.then_some(fields.clone())
.ok_or_else(|| {
format!(
"invalid mxrun line {}: expected 3 fields, got {}",
self.lineno,
fields.len()
)
})
})
}
}
trait PipeRef: Sized {
fn pipe_ref<T>(self, f: impl FnOnce(&Self) -> T) -> T {
f(&self)
}
}
impl<T> PipeRef for T {}