Skip to main content

anodizer_core/
packagers.rs

1//! Packaging-axis config types lifted out of the monolithic
2//! `crate::config` module.
3//!
4//! Currently houses [`MakeselfConfig`] + helpers and [`SrpmConfig`].
5//! The remaining packaging types (`NfpmConfig`, `SnapcraftConfig`,
6//! `FlatpakConfig`, `AppBundleConfig`, `DmgConfig`, `PkgConfig`,
7//! `MsiConfig`, `NsisConfig`) still live in `config.rs` and will move
8//! here in subsequent extraction passes — each with their dedicated
9//! deserializer / `*_schema()` helper alongside.
10//!
11//! Public API path is preserved by re-exports in `config.rs` so consumers
12//! can keep importing from `anodizer_core::config::*`.
13
14use crate::config::{
15    NfpmContent, NfpmSignatureConfig, StringOrBool, deserialize_string_or_bool_opt,
16};
17use schemars::JsonSchema;
18use serde::{Deserialize, Deserializer, Serialize};
19use std::collections::BTreeMap;
20
21// ---------------------------------------------------------------------------
22// MakeselfConfig
23// ---------------------------------------------------------------------------
24
25#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
26#[serde(default, deny_unknown_fields)]
27pub struct MakeselfConfig {
28    /// Unique identifier for this makeself config (default: "default").
29    pub id: Option<String>,
30    /// Build IDs filter: only include artifacts whose `id` is in this list.
31    pub ids: Option<Vec<String>>,
32    /// Output filename template (default includes project, version, os, arch).
33    pub filename: Option<String>,
34    /// Display name embedded in the self-extracting archive.
35    pub name: Option<String>,
36    /// Startup script to run when the archive is extracted and executed.
37    /// Required — the archive will not be created without this.
38    pub script: Option<String>,
39    /// Description for LSM metadata.
40    pub description: Option<String>,
41    /// Maintainer for LSM metadata.
42    pub maintainer: Option<String>,
43    /// Keywords for LSM metadata.
44    pub keywords: Option<Vec<String>>,
45    /// Homepage URL for LSM metadata.
46    pub homepage: Option<String>,
47    /// License for LSM metadata.
48    pub license: Option<String>,
49    /// Compression algorithm: gzip, bzip2, xz, lzo, compress, or none.
50    pub compression: Option<String>,
51    /// Extra arguments passed to the makeself command.
52    pub extra_args: Option<Vec<String>>,
53    /// Additional files to include in the archive.
54    pub files: Option<Vec<MakeselfFile>>,
55    /// Target OS filter (default: ["linux", "darwin"]).
56    pub os: Option<Vec<String>>,
57    /// Target architecture filter.
58    pub arch: Option<Vec<String>>,
59    /// Skip this config. Accepts bool or template string.
60    /// Accepts the legacy `disable:` spelling via serde alias for back-compat
61    /// with imported configs.
62    #[serde(
63        alias = "disable",
64        deserialize_with = "deserialize_string_or_bool_opt",
65        default
66    )]
67    pub skip: Option<StringOrBool>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
71#[serde(default, deny_unknown_fields)]
72pub struct MakeselfFile {
73    /// Source file path (relative to project root).
74    /// Accepts the `src:` spelling via serde alias for back-compat
75    /// with imported configs.
76    #[serde(alias = "src")]
77    pub source: String,
78    /// Destination path inside the archive.
79    /// Accepts the `dst:` spelling via serde alias for back-compat
80    /// with imported configs.
81    #[serde(alias = "dst")]
82    pub destination: Option<String>,
83    /// Strip the parent directory from the source path.
84    pub strip_parent: Option<bool>,
85}
86
87/// Deserialize makeselfs: single object → vec of one, array → vec of many.
88pub(crate) fn deserialize_makeselfs<'de, D>(
89    deserializer: D,
90) -> Result<Vec<MakeselfConfig>, D::Error>
91where
92    D: Deserializer<'de>,
93{
94    use serde::de::{self, Visitor};
95
96    struct MakeselfVisitor;
97
98    impl<'de> Visitor<'de> for MakeselfVisitor {
99        type Value = Vec<MakeselfConfig>;
100
101        fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102            f.write_str("a makeself config object or an array of makeself config objects")
103        }
104
105        fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
106            let mut configs = Vec::new();
107            while let Some(item) = seq.next_element::<MakeselfConfig>()? {
108                configs.push(item);
109            }
110            Ok(configs)
111        }
112
113        fn visit_map<M: de::MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
114            let config = MakeselfConfig::deserialize(de::value::MapAccessDeserializer::new(map))?;
115            Ok(vec![config])
116        }
117
118        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
119            Ok(Vec::new())
120        }
121
122        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
123            Ok(Vec::new())
124        }
125    }
126
127    deserializer.deserialize_any(MakeselfVisitor)
128}
129
130pub(crate) fn makeselfs_schema(
131    generator: &mut schemars::r#gen::SchemaGenerator,
132) -> schemars::schema::Schema {
133    let mut schema = generator.subschema_for::<Vec<MakeselfConfig>>();
134    if let schemars::schema::Schema::Object(ref mut obj) = schema {
135        obj.metadata().description = Some(
136            "Makeself self-extracting archive configurations. Accepts a single object or array."
137                .to_owned(),
138        );
139    }
140    schema
141}
142
143// ---------------------------------------------------------------------------
144// AppImageConfig
145// ---------------------------------------------------------------------------
146
147/// AppImage packaging configuration.
148///
149/// Drives the [AppImage](https://appimage.org/) stage, which bundles a built
150/// Linux binary plus its desktop integration (a `.desktop` entry + icon) into
151/// a single self-contained, runnable `.AppImage` file via
152/// [`linuxdeploy`](https://github.com/linuxdeploy/linuxdeploy)'s `appimage`
153/// output plugin. One `.AppImage` is produced per matching Linux target so a
154/// multi-arch build yields distinct, non-colliding outputs.
155///
156/// YAML:
157/// ```yaml
158/// appimages:
159///   - id: helix
160///     ids: [helix-bin]
161///     desktop: contrib/Helix.desktop
162///     icon: contrib/helix.png
163///     appdir_extra:
164///       - src: runtime/
165///         dst: usr/lib/helix/runtime
166///     update_information: "gh-releases-zsync|helix-editor|helix|latest|helix-*.AppImage.zsync"
167///     runtime_harvest:
168///       command: "{{ ArtifactPath }} --populate-runtime {{ HarvestDir }}"
169///       dir: runtime/
170/// ```
171#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
172#[serde(default, deny_unknown_fields)]
173pub struct AppImageConfig {
174    /// Unique identifier for this AppImage config (default: "default").
175    pub id: Option<String>,
176    /// Build IDs filter: only bundle binaries whose `id` is in this list.
177    /// When omitted, every Linux binary in the build matrix is eligible.
178    pub ids: Option<Vec<String>>,
179    /// Output filename template (default includes project, version, os, arch).
180    /// The `.AppImage` extension is appended automatically when absent.
181    pub filename: Option<String>,
182    /// Application name passed to linuxdeploy via the `APP` env var and used
183    /// as the AppDir basename. Defaults to the project name.
184    pub name: Option<String>,
185    /// Path to the `.desktop` entry file (template). Required — linuxdeploy
186    /// will not assemble an AppImage without a desktop file.
187    pub desktop: Option<String>,
188    /// Path to the application icon (template). Required.
189    pub icon: Option<String>,
190    /// Extra files / directories copied into the AppDir before linuxdeploy
191    /// runs (e.g. a harvested `runtime/` tree). Each entry's `dst` is
192    /// interpreted relative to the AppDir root.
193    pub appdir_extra: Option<Vec<AppImageExtra>>,
194    /// zsync delta-update metadata embedded in the AppImage, passed to
195    /// linuxdeploy via the `UPDATE_INFORMATION` env var. When omitted, the
196    /// AppImage carries no update information and `UPDATE_INFORMATION` is
197    /// left unset (matching linuxdeploy's default).
198    pub update_information: Option<String>,
199    /// Runtime-asset harvest hook: run the freshly-built binary ONCE on the
200    /// host to populate a directory, then bundle that directory into the
201    /// AppDir. The harvested data is architecture-independent (grammars,
202    /// themes, queries), so it is produced once on the host-native binary and
203    /// reused for every target's AppImage.
204    pub runtime_harvest: Option<RuntimeHarvest>,
205    /// Extra arguments appended to the linuxdeploy command line.
206    pub extra_args: Option<Vec<String>>,
207    /// Target OS filter (default: ["linux"]). AppImage is a Linux-only format.
208    pub os: Option<Vec<String>>,
209    /// Target architecture filter. When omitted, every architecture in the
210    /// build matrix produces its own `.AppImage`.
211    pub arch: Option<Vec<String>>,
212    /// Skip this config. Accepts bool or template string.
213    #[serde(
214        alias = "disable",
215        deserialize_with = "deserialize_string_or_bool_opt",
216        default
217    )]
218    pub skip: Option<StringOrBool>,
219}
220
221/// A file or directory copied into the AppDir before linuxdeploy assembles
222/// the AppImage. Mirrors [`MakeselfFile`]'s `src` / `dst` shape.
223#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
224#[serde(default, deny_unknown_fields)]
225pub struct AppImageExtra {
226    /// Source path (file or directory, relative to project root). A trailing
227    /// `/` is not required; directories are copied recursively.
228    #[serde(alias = "source")]
229    pub src: String,
230    /// Destination path inside the AppDir (relative to the AppDir root, e.g.
231    /// `usr/lib/helix/runtime`).
232    #[serde(alias = "destination")]
233    pub dst: String,
234}
235
236/// Runtime-asset harvest hook for an AppImage config. The `command` template
237/// runs the freshly-built host-native binary to populate `dir`; the resulting
238/// directory is then bundled into the AppDir (and staged at a stable dist
239/// path so an archive `extra_files` glob can reuse it).
240#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
241#[serde(default, deny_unknown_fields)]
242pub struct RuntimeHarvest {
243    /// Command template run once on the host to populate the harvest dir.
244    /// `{{ .ArtifactPath }}` resolves to the host-native binary's path and
245    /// `{{ .HarvestDir }}` to the absolute harvest output directory. Run via
246    /// `sh -c`.
247    pub command: String,
248    /// Directory (relative to the AppDir root) the harvested assets are
249    /// bundled into. Also the AppDir-relative destination for the staged
250    /// host-harvested tree.
251    pub dir: String,
252}
253
254/// Deserialize appimages: single object → vec of one, array → vec of many.
255pub(crate) fn deserialize_appimages<'de, D>(
256    deserializer: D,
257) -> Result<Vec<AppImageConfig>, D::Error>
258where
259    D: Deserializer<'de>,
260{
261    use serde::de::{self, Visitor};
262
263    struct AppImageVisitor;
264
265    impl<'de> Visitor<'de> for AppImageVisitor {
266        type Value = Vec<AppImageConfig>;
267
268        fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269            f.write_str("an appimage config object or an array of appimage config objects")
270        }
271
272        fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
273            let mut configs = Vec::new();
274            while let Some(item) = seq.next_element::<AppImageConfig>()? {
275                configs.push(item);
276            }
277            Ok(configs)
278        }
279
280        fn visit_map<M: de::MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
281            let config = AppImageConfig::deserialize(de::value::MapAccessDeserializer::new(map))?;
282            Ok(vec![config])
283        }
284
285        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
286            Ok(Vec::new())
287        }
288
289        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
290            Ok(Vec::new())
291        }
292    }
293
294    deserializer.deserialize_any(AppImageVisitor)
295}
296
297pub(crate) fn appimages_schema(
298    generator: &mut schemars::r#gen::SchemaGenerator,
299) -> schemars::schema::Schema {
300    let mut schema = generator.subschema_for::<Vec<AppImageConfig>>();
301    if let schemars::schema::Schema::Object(ref mut obj) = schema {
302        obj.metadata().description =
303            Some("AppImage packaging configurations. Accepts a single object or array.".to_owned());
304    }
305    schema
306}
307
308// ---------------------------------------------------------------------------
309// SrpmConfig
310// ---------------------------------------------------------------------------
311
312#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
313#[serde(default, deny_unknown_fields)]
314pub struct SrpmConfig {
315    /// Enable source RPM generation. Default: false.
316    pub enabled: Option<bool>,
317    /// Package name (default: project_name).
318    pub package_name: Option<String>,
319    /// Output filename template.
320    pub file_name_template: Option<String>,
321    /// Path to the RPM spec file template.
322    pub spec_file: Option<String>,
323    /// RPM epoch.
324    pub epoch: Option<String>,
325    /// RPM section.
326    pub section: Option<String>,
327    /// Package maintainer.
328    pub maintainer: Option<String>,
329    /// Package vendor.
330    pub vendor: Option<String>,
331    /// Summary line.
332    pub summary: Option<String>,
333    /// RPM group.
334    pub group: Option<String>,
335    /// Package description.
336    pub description: Option<String>,
337    /// License identifier.
338    pub license: Option<String>,
339    /// License file name to include.
340    pub license_file_name: Option<String>,
341    /// Homepage URL.
342    pub url: Option<String>,
343    /// RPM packager field.
344    pub packager: Option<String>,
345    /// Compression algorithm (gzip, xz, zstd, none).
346    pub compression: Option<String>,
347    /// Documentation files to include.
348    pub docs: Option<Vec<String>>,
349    /// Additional contents to include in the source RPM. Shares the unified
350    /// [`NfpmContent`] type with nFPM contents; SRPM-style `source:` /
351    /// `destination:` / `type:` keys are accepted via serde aliases.
352    pub contents: Option<Vec<NfpmContent>>,
353    /// RPM signature configuration. Shares the unified
354    /// [`NfpmSignatureConfig`] type with nFPM.
355    pub signature: Option<NfpmSignatureConfig>,
356    /// Map of binary name → install path declared in the spec's `%files`
357    /// section. Each entry tells the generated
358    /// `.spec` which installed file the package owns. When omitted, each
359    /// binary produced by the build for this crate defaults to
360    /// `%{_bindir}/<name>` (i.e. `/usr/bin/<name>`, the RPM-idiomatic
361    /// location for a built binary). Provide this only to override the
362    /// install path or to declare extra owned paths. Stored as a
363    /// `BTreeMap` so the emitted `%files` section iterates in
364    /// deterministic key order.
365    pub bins: Option<BTreeMap<String, String>>,
366    /// Filesystem prefixes the package may install to (RPM `Prefix:` tag).
367    /// Each entry becomes one `Prefix:` directive — relocatable RPMs need
368    /// at least one prefix declared.
369    pub prefixes: Option<Vec<String>>,
370    /// Override the build host recorded in the RPM header. Useful for
371    /// reproducible builds where the actual hostname leaks build-env detail.
372    pub build_host: Option<String>,
373    /// `%pretrans` scriptlet — executed on the package transaction *before*
374    /// any package in the transaction is installed. Path to a script file.
375    pub pretrans: Option<String>,
376    /// `%posttrans` scriptlet — executed *after* all packages in the
377    /// transaction have been installed. Path to a script file.
378    pub posttrans: Option<String>,
379    /// Prerelease suffix appended to the version (e.g. `rc1`, `beta2`).
380    /// Prerelease component of the package version.
381    pub prerelease: Option<String>,
382    /// Build metadata appended to the version (e.g. git commit hash).
383    /// Version-metadata component of the package version.
384    pub version_metadata: Option<String>,
385    /// Skip this config. Accepts bool or template string.
386    #[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
387    pub skip: Option<StringOrBool>,
388}
389
390// SRPM signatures share [`NfpmSignatureConfig`]; the SRPM-style
391// `passphrase:` key is accepted as a serde alias for `key_passphrase:`.
392//
393// SRPM contents share [`NfpmContent`]; both the canonical `src` / `dst`
394// keys and the SRPM-style `source` / `destination` aliases parse.