use std::borrow::Cow;
use cargo_platform::{Cfg, Platform};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use strum_macros::{EnumCount, VariantArray};
mod package_formats;
mod target_triple;
#[doc(inline)]
pub use package_formats::*;
#[doc(inline)]
pub use target_triple::*;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Meta {
pub binstall: Option<PkgMeta>,
}
#[derive(
Debug,
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
EnumCount,
VariantArray,
Deserialize,
Serialize,
)]
#[serde(rename_all = "kebab-case")]
pub enum Strategy {
CrateMetaData,
QuickInstall,
Compile,
}
impl Strategy {
pub const fn to_str(self) -> &'static str {
match self {
Strategy::CrateMetaData => "crate-meta-data",
Strategy::QuickInstall => "quick-install",
Strategy::Compile => "compile",
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default)]
pub struct PkgMeta {
pub pkg_url: Option<String>,
pub pkg_fmt: Option<PkgFmt>,
pub bin_dir: Option<String>,
pub signing: Option<PkgSigning>,
pub disabled_strategies: Option<Box<[Strategy]>>,
pub overrides: PkgOverrides,
}
impl PkgMeta {
pub fn merge(&mut self, pkg_override: &PkgOverride) {
if let Some(o) = &pkg_override.pkg_url {
self.pkg_url = Some(o.clone());
}
if let Some(o) = &pkg_override.pkg_fmt {
self.pkg_fmt = Some(*o);
}
if let Some(o) = &pkg_override.bin_dir {
self.bin_dir = Some(o.clone());
}
}
pub fn merge_overrides<'a, It>(&self, pkg_overrides: It) -> Self
where
It: IntoIterator<Item = &'a PkgOverride> + Clone,
{
let ignore_disabled_strategies = pkg_overrides
.clone()
.into_iter()
.any(|pkg_override| pkg_override.ignore_disabled_strategies);
Self {
pkg_url: pkg_overrides
.clone()
.into_iter()
.find_map(|pkg_override| pkg_override.pkg_url.clone())
.or_else(|| self.pkg_url.clone()),
pkg_fmt: pkg_overrides
.clone()
.into_iter()
.find_map(|pkg_override| pkg_override.pkg_fmt)
.or(self.pkg_fmt),
bin_dir: pkg_overrides
.clone()
.into_iter()
.find_map(|pkg_override| pkg_override.bin_dir.clone())
.or_else(|| self.bin_dir.clone()),
signing: pkg_overrides
.clone()
.into_iter()
.find_map(|pkg_override| pkg_override.signing.clone())
.or_else(|| self.signing.clone()),
disabled_strategies: if ignore_disabled_strategies {
None
} else {
let mut disabled_strategies = pkg_overrides
.into_iter()
.filter_map(|pkg_override| pkg_override.disabled_strategies.as_deref())
.flatten()
.chain(self.disabled_strategies.as_deref().into_iter().flatten())
.copied()
.collect::<Vec<Strategy>>();
disabled_strategies.sort_unstable();
disabled_strategies.dedup();
Some(disabled_strategies.into_boxed_slice())
},
overrides: Default::default(),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default)]
pub struct PkgOverride {
pub pkg_url: Option<String>,
pub pkg_fmt: Option<PkgFmt>,
pub bin_dir: Option<String>,
pub disabled_strategies: Option<Box<[Strategy]>>,
pub signing: Option<PkgSigning>,
#[serde(skip)]
pub ignore_disabled_strategies: bool,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct PkgOverrides(IndexMap<Platform, PkgOverride>);
impl PkgOverrides {
pub fn get_matching<'a>(
&'a self,
target: &'a str,
cfgs: &'a [Cfg],
) -> impl Iterator<Item = &'a PkgOverride> + Clone {
let name = self.0.get(&Platform::Name(target.to_owned()));
let cfgs = self.0.iter().filter_map(|(p, o)| match p {
Platform::Cfg(p) if p.matches(cfgs) => Some(o),
_ => None,
});
name.into_iter().chain(cfgs)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct BinMeta {
pub name: String,
pub path: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct PkgSigning {
pub algorithm: SigningAlgorithm,
pub pubkey: Cow<'static, str>,
#[serde(default)]
pub file: Option<String>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum SigningAlgorithm {
Minisign,
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use serde_json::json;
use strum::VariantArray;
use super::*;
#[test]
fn test_strategy_ser() {
Strategy::VARIANTS.iter().for_each(|strategy| {
assert_eq!(
serde_json::to_string(&strategy).unwrap(),
format!(r#""{}""#, strategy.to_str())
)
});
}
#[test]
fn test_pkg_overrides_parse_target_name() {
let json = json!({
"x86_64-unknown-linux-gnu": {
"pkg-fmt": "tgz",
},
});
let overrides: PkgOverrides = serde_json::from_value(json).unwrap();
assert!(matches!(overrides, PkgOverrides(map) if !map.is_empty()));
}
#[test]
fn test_pkg_overrides_parse_cfg_expression() {
let json = json!({
r#"cfg(target_os = "linux")"#: {
"pkg-fmt": "tgz",
},
});
let overrides: PkgOverrides = serde_json::from_value(json).unwrap();
assert!(matches!(overrides, PkgOverrides(map) if !map.is_empty()));
}
#[test]
fn test_pkg_overrides_parse_mixed() {
let json = json!({
"x86_64-unknown-linux-gnu": {
"pkg-fmt": "tar",
},
r#"cfg(target_os = "linux")"#: {
"pkg-fmt": "tgz",
},
"cfg(unix)": {
"bin-dir": "bin",
},
});
let overrides: PkgOverrides = serde_json::from_value(json).unwrap();
assert!(matches!(overrides, PkgOverrides(map) if !map.is_empty()));
}
#[test]
fn test_pkg_overrides_exact_match_precedence() {
let json = json!({
r#"cfg(target_os = "linux")"#: {
"pkg-fmt": "tgz",
},
"x86_64-unknown-linux-gnu": {
"pkg-fmt": "tar",
},
});
let overrides: PkgOverrides = serde_json::from_value(json).unwrap();
let cfgs = vec![
Cfg::from_str(r#"target_os="linux""#).unwrap(),
Cfg::from_str(r#"target_arch="x86_64""#).unwrap(),
Cfg::from_str("unix").unwrap(),
];
let matches: Vec<_> = overrides
.get_matching("x86_64-unknown-linux-gnu", &cfgs)
.collect();
assert_eq!(matches.len(), 2);
assert_eq!(matches[0].pkg_fmt, Some(PkgFmt::Tar));
assert_eq!(matches[1].pkg_fmt, Some(PkgFmt::Tgz));
}
#[test]
fn test_pkg_overrides_cfg_match_order() {
let json = r#"{
"cfg(unix)": {
"pkg-fmt": "tgz"
},
"cfg(target_os = \"linux\")": {
"pkg-fmt": "tar"
}
}"#;
let overrides: PkgOverrides = serde_json::from_str(json).unwrap();
let cfgs = vec![
Cfg::from_str(r#"target_os="linux""#).unwrap(),
Cfg::from_str("unix").unwrap(),
];
let matches: Vec<_> = overrides
.get_matching("x86_64-unknown-linux-gnu", &cfgs)
.collect();
assert_eq!(matches.len(), 2);
assert_eq!(matches[0].pkg_fmt, Some(PkgFmt::Tgz)); assert_eq!(matches[1].pkg_fmt, Some(PkgFmt::Tar)); }
#[test]
fn test_pkg_overrides_no_match() {
let json = json!({
r#"cfg(target_os = "windows")"#: {
"pkg-fmt": "zip",
},
});
let overrides: PkgOverrides = serde_json::from_value(json).unwrap();
let cfgs = vec![
Cfg::from_str(r#"target_os="linux""#).unwrap(),
Cfg::from_str("unix").unwrap(),
];
let matches: Vec<_> = overrides
.get_matching("x86_64-unknown-linux-gnu", &cfgs)
.collect();
assert!(matches.is_empty());
}
}