binstalk_types/
cargo_toml_binstall.rs

1//! The format of the `[package.metadata.binstall]` manifest.
2//!
3//! This manifest defines how a particular binary crate may be installed by Binstall.
4
5use std::{borrow::Cow, collections::BTreeMap};
6
7use serde::{Deserialize, Serialize};
8use strum_macros::{EnumCount, VariantArray};
9
10mod package_formats;
11#[doc(inline)]
12pub use package_formats::*;
13
14/// `binstall` metadata container
15///
16/// Required to nest metadata under `package.metadata.binstall`
17#[derive(Clone, Debug, Serialize, Deserialize)]
18#[serde(rename_all = "kebab-case")]
19pub struct Meta {
20    pub binstall: Option<PkgMeta>,
21}
22
23/// Strategies to use for binary discovery
24#[derive(
25    Debug,
26    Copy,
27    Clone,
28    Eq,
29    PartialEq,
30    Ord,
31    PartialOrd,
32    EnumCount,
33    VariantArray,
34    Deserialize,
35    Serialize,
36)]
37#[serde(rename_all = "kebab-case")]
38pub enum Strategy {
39    /// Attempt to download official pre-built artifacts using
40    /// information provided in `Cargo.toml`.
41    CrateMetaData,
42    /// Query third-party QuickInstall for the crates.
43    QuickInstall,
44    /// Build the crates from source using `cargo-build`.
45    Compile,
46}
47
48impl Strategy {
49    pub const fn to_str(self) -> &'static str {
50        match self {
51            Strategy::CrateMetaData => "crate-meta-data",
52            Strategy::QuickInstall => "quick-install",
53            Strategy::Compile => "compile",
54        }
55    }
56}
57
58/// Metadata for binary installation use.
59///
60/// Exposed via `[package.metadata]` in `Cargo.toml`
61#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
62#[serde(rename_all = "kebab-case", default)]
63pub struct PkgMeta {
64    /// URL template for package downloads
65    pub pkg_url: Option<String>,
66
67    /// Format for package downloads
68    pub pkg_fmt: Option<PkgFmt>,
69
70    /// Path template for binary files in packages
71    pub bin_dir: Option<String>,
72
73    /// Package signing configuration
74    pub signing: Option<PkgSigning>,
75
76    /// Strategies to disable
77    pub disabled_strategies: Option<Box<[Strategy]>>,
78
79    /// Target specific overrides
80    pub overrides: BTreeMap<String, PkgOverride>,
81}
82
83impl PkgMeta {
84    /// Merge configuration overrides into object
85    pub fn merge(&mut self, pkg_override: &PkgOverride) {
86        if let Some(o) = &pkg_override.pkg_url {
87            self.pkg_url = Some(o.clone());
88        }
89        if let Some(o) = &pkg_override.pkg_fmt {
90            self.pkg_fmt = Some(*o);
91        }
92        if let Some(o) = &pkg_override.bin_dir {
93            self.bin_dir = Some(o.clone());
94        }
95    }
96
97    /// Merge configuration overrides into object
98    ///
99    ///  * `pkg_overrides` - ordered in preference
100    pub fn merge_overrides<'a, It>(&self, pkg_overrides: It) -> Self
101    where
102        It: IntoIterator<Item = &'a PkgOverride> + Clone,
103    {
104        let ignore_disabled_strategies = pkg_overrides
105            .clone()
106            .into_iter()
107            .any(|pkg_override| pkg_override.ignore_disabled_strategies);
108
109        Self {
110            pkg_url: pkg_overrides
111                .clone()
112                .into_iter()
113                .find_map(|pkg_override| pkg_override.pkg_url.clone())
114                .or_else(|| self.pkg_url.clone()),
115
116            pkg_fmt: pkg_overrides
117                .clone()
118                .into_iter()
119                .find_map(|pkg_override| pkg_override.pkg_fmt)
120                .or(self.pkg_fmt),
121
122            bin_dir: pkg_overrides
123                .clone()
124                .into_iter()
125                .find_map(|pkg_override| pkg_override.bin_dir.clone())
126                .or_else(|| self.bin_dir.clone()),
127
128            signing: pkg_overrides
129                .clone()
130                .into_iter()
131                .find_map(|pkg_override| pkg_override.signing.clone())
132                .or_else(|| self.signing.clone()),
133
134            disabled_strategies: if ignore_disabled_strategies {
135                None
136            } else {
137                let mut disabled_strategies = pkg_overrides
138                    .into_iter()
139                    .filter_map(|pkg_override| pkg_override.disabled_strategies.as_deref())
140                    .flatten()
141                    .chain(self.disabled_strategies.as_deref().into_iter().flatten())
142                    .copied()
143                    .collect::<Vec<Strategy>>();
144
145                disabled_strategies.sort_unstable();
146                disabled_strategies.dedup();
147
148                Some(disabled_strategies.into_boxed_slice())
149            },
150
151            overrides: Default::default(),
152        }
153    }
154}
155
156/// Target specific overrides for binary installation
157///
158/// Exposed via `[package.metadata.TARGET]` in `Cargo.toml`
159#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
160#[serde(rename_all = "kebab-case", default)]
161pub struct PkgOverride {
162    /// URL template override for package downloads
163    pub pkg_url: Option<String>,
164
165    /// Format override for package downloads
166    pub pkg_fmt: Option<PkgFmt>,
167
168    /// Path template override for binary files in packages
169    pub bin_dir: Option<String>,
170
171    /// Strategies to disable
172    pub disabled_strategies: Option<Box<[Strategy]>>,
173
174    /// Package signing configuration
175    pub signing: Option<PkgSigning>,
176
177    #[serde(skip)]
178    pub ignore_disabled_strategies: bool,
179}
180
181#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
182#[serde(rename_all = "kebab-case")]
183pub struct BinMeta {
184    /// Binary name
185    pub name: String,
186
187    /// Binary template (path within package)
188    pub path: String,
189}
190
191#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
192#[serde(rename_all = "kebab-case")]
193pub struct PkgSigning {
194    /// Signing algorithm supported by Binstall.
195    pub algorithm: SigningAlgorithm,
196
197    /// Signing public key
198    pub pubkey: Cow<'static, str>,
199
200    /// Signature file override template (url to download)
201    #[serde(default)]
202    pub file: Option<String>,
203}
204
205#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
206#[serde(rename_all = "kebab-case")]
207#[non_exhaustive]
208pub enum SigningAlgorithm {
209    /// [minisign](https://jedisct1.github.io/minisign/)
210    Minisign,
211}
212
213#[cfg(test)]
214mod tests {
215    use strum::VariantArray;
216
217    use super::*;
218
219    #[test]
220    fn test_strategy_ser() {
221        Strategy::VARIANTS.iter().for_each(|strategy| {
222            assert_eq!(
223                serde_json::to_string(&strategy).unwrap(),
224                format!(r#""{}""#, strategy.to_str())
225            )
226        });
227    }
228}