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.