Skip to main content

anodizer_core/config/
upx.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Deserializer, Serialize};
3
4use super::{StringOrBool, deserialize_string_or_bool_opt};
5
6// ---------------------------------------------------------------------------
7// UpxConfig
8// ---------------------------------------------------------------------------
9
10#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
11#[serde(default)]
12pub struct UpxConfig {
13    /// Unique identifier for this UPX config.
14    pub id: Option<String>,
15    /// Build IDs filter: only compress binaries from builds whose `id` is in this list.
16    pub ids: Option<Vec<String>>,
17    /// Whether to compress binaries with UPX.
18    /// Accepts a bool or a template string that evaluates to a bool.
19    #[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
20    pub enabled: Option<StringOrBool>,
21    /// UPX executable path or name (default: "upx").
22    pub binary: String,
23    /// Extra arguments passed to UPX (e.g., ["-9", "--brute"]).
24    pub args: Vec<String>,
25    /// When true, fail the build if UPX is not found.
26    pub required: bool,
27    /// Target triples to compress binaries for (empty means all targets).
28    pub targets: Option<Vec<String>>,
29    /// UPX compression level string (e.g., "1"-"9", "best"). Maps to `--compress` flag.
30    pub compress: Option<String>,
31    /// Use LZMA compression (--lzma flag).
32    pub lzma: Option<bool>,
33    /// Use brute-force compression (--brute flag). Very slow but produces smallest output.
34    pub brute: Option<bool>,
35}
36
37impl Default for UpxConfig {
38    fn default() -> Self {
39        UpxConfig {
40            id: None,
41            ids: None,
42            enabled: None,
43            binary: "upx".to_string(),
44            args: Vec::new(),
45            required: false,
46            targets: None,
47            compress: None,
48            lzma: None,
49            brute: None,
50        }
51    }
52}
53
54/// Custom deserializer for the `upx` field.
55/// Accepts:
56///   - null/missing → empty vec (via serde default)
57///   - a single object → vec of one UpxConfig
58///   - an array → vec of UpxConfig
59pub(super) fn deserialize_upx<'de, D>(deserializer: D) -> Result<Vec<UpxConfig>, D::Error>
60where
61    D: Deserializer<'de>,
62{
63    use serde::de::{self, Visitor};
64
65    struct UpxVisitor;
66
67    impl<'de> Visitor<'de> for UpxVisitor {
68        type Value = Vec<UpxConfig>;
69
70        fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71            f.write_str("a UPX config object or an array of UPX config objects")
72        }
73
74        fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
75            let mut configs = Vec::new();
76            while let Some(item) = seq.next_element::<UpxConfig>()? {
77                configs.push(item);
78            }
79            Ok(configs)
80        }
81
82        fn visit_map<M: de::MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
83            let config = UpxConfig::deserialize(de::value::MapAccessDeserializer::new(map))?;
84            Ok(vec![config])
85        }
86
87        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
88            Ok(Vec::new())
89        }
90
91        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
92            Ok(Vec::new())
93        }
94    }
95
96    deserializer.deserialize_any(UpxVisitor)
97}