use serde::{Deserialize, Deserializer};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuildTarget {
Hb,
Bin,
Cdylib,
}
impl BuildTarget {
pub const ALL: &'static [BuildTarget] =
&[BuildTarget::Hb, BuildTarget::Bin, BuildTarget::Cdylib];
pub fn name(&self) -> &'static str {
match self {
BuildTarget::Hb => "hb",
BuildTarget::Bin => "bin",
BuildTarget::Cdylib => "cdylib",
}
}
pub fn names() -> Vec<&'static str> {
BuildTarget::ALL.iter().map(BuildTarget::name).collect()
}
pub fn crate_types(&self) -> &'static [&'static str] {
match self {
BuildTarget::Hb => &[],
BuildTarget::Bin => &[],
BuildTarget::Cdylib => &["rlib", "cdylib", "staticlib"],
}
}
pub fn entry(&self) -> Script {
match self {
BuildTarget::Hb => Script::Either,
BuildTarget::Bin => Script::Either,
BuildTarget::Cdylib => Script::Forbids,
}
}
pub fn runs_it(&self) -> &'static str {
match self {
BuildTarget::Hb => "the htl binary",
BuildTarget::Bin => "the OS, as a binary",
BuildTarget::Cdylib => "a C / Python / Unity caller",
}
}
}
impl fmt::Display for BuildTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl FromStr for BuildTarget {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
BuildTarget::ALL
.iter()
.copied()
.find(|t| t.name() == s)
.ok_or_else(|| {
format!(
"unknown target `{s}`; registered targets: {}",
BuildTarget::names().join(", ")
)
})
}
}
impl<'de> Deserialize<'de> for BuildTarget {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Script {
Requires,
Forbids,
Either,
}
impl Script {
pub fn accepts(self, lib: bool) -> bool {
match self {
Script::Requires => !lib,
Script::Forbids => lib,
Script::Either => true,
}
}
}
#[cfg(test)]
mod tests {
use super::{BuildTarget, Script};
#[test]
fn every_target_round_trips_through_its_name() {
for t in BuildTarget::ALL {
assert_eq!(t.name().parse::<BuildTarget>().unwrap(), *t);
assert_eq!(t.to_string(), t.name());
}
assert_eq!(BuildTarget::names(), vec!["hb", "bin", "cdylib"]);
}
#[test]
fn an_unknown_name_is_refused_with_all_three() {
let e = "rust".parse::<BuildTarget>().unwrap_err();
assert!(e.contains("unknown target `rust`"), "{e}");
for n in BuildTarget::names() {
assert!(e.contains(n), "{e}");
}
}
#[test]
fn crate_types_are_derived_from_the_target() {
assert!(BuildTarget::Hb.crate_types().is_empty());
assert!(BuildTarget::Bin.crate_types().is_empty());
assert_eq!(
BuildTarget::Cdylib.crate_types(),
["rlib", "cdylib", "staticlib"]
);
}
#[test]
fn the_entry_rule_is_derived_from_the_target() {
assert_eq!(BuildTarget::Hb.entry(), Script::Either);
assert_eq!(BuildTarget::Bin.entry(), Script::Either);
assert_eq!(BuildTarget::Cdylib.entry(), Script::Forbids);
assert!(Script::Requires.accepts(false) && !Script::Requires.accepts(true));
assert!(Script::Forbids.accepts(true) && !Script::Forbids.accepts(false));
assert!(Script::Either.accepts(true) && Script::Either.accepts(false));
}
}