anodizer_core/config/nfpm.rs
1use std::collections::HashMap;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use super::{Amd64Variant, FileInfo, StringOrU32};
7
8// ---------------------------------------------------------------------------
9// NfpmConfig
10// ---------------------------------------------------------------------------
11
12#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
13#[serde(default, deny_unknown_fields)]
14pub struct NfpmConfig {
15 /// Unique identifier for cross-referencing this nFPM config.
16 pub id: Option<String>,
17 /// Package name (defaults to crate name).
18 pub package_name: Option<String>,
19 /// Package formats to produce: deb, rpm, apk, archlinux, termux.deb, ipk,
20 /// msix (at least one required).
21 pub formats: Vec<String>,
22 /// Package vendor name — the distributing entity recorded in the
23 /// rpm/deb Vendor field. When unset, derived from the crate's first
24 /// `Cargo.toml [package].authors` entry with any `<email>` suffix
25 /// stripped (e.g. `"Ada Lovelace <ada@x>"` → `"Ada Lovelace"`).
26 pub vendor: Option<String>,
27 /// Project homepage URL.
28 pub homepage: Option<String>,
29 /// Package maintainer in `Name <email>` format.
30 pub maintainer: Option<String>,
31 /// Package description (multiline supported).
32 pub description: Option<String>,
33 /// SPDX license identifier (e.g., "MIT", "Apache-2.0").
34 pub license: Option<String>,
35 /// Installation directory for binaries (default: /usr/bin).
36 pub bindir: Option<String>,
37 /// Rename the installed binary inside the package only.
38 ///
39 /// When set, the auto-emitted binary content entry is installed under this
40 /// name (in `bindir`) instead of the built file's name; the archive/build
41 /// output is untouched. Use this to resolve Debian/RPM name clashes — e.g.
42 /// `fd` ships its binary as `fdfind` in the Debian package while the tarball
43 /// keeps `fd`. Templated.
44 pub bin_alias: Option<String>,
45 /// Files to include in the package beyond the main binary.
46 pub contents: Option<Vec<NfpmContent>>,
47 /// Runtime package dependencies keyed by format (e.g., {"deb": ["libc6"], "rpm": ["glibc"]}).
48 pub dependencies: Option<HashMap<String, Vec<String>>>,
49 /// Per-format setting overrides (e.g., {"deb": {compression: "xz"}}).
50 pub overrides: Option<HashMap<String, serde_json::Value>>,
51 /// Package filename template (supports templates).
52 pub file_name_template: Option<String>,
53 /// Package lifecycle scripts (preinstall, postinstall, preremove, postremove).
54 pub scripts: Option<NfpmScripts>,
55 /// Packages recommended (soft dependency) by this package.
56 pub recommends: Option<Vec<String>>,
57 /// Packages suggested (weaker than recommends) by this package.
58 pub suggests: Option<Vec<String>>,
59 /// Packages this package conflicts with.
60 pub conflicts: Option<Vec<String>>,
61 /// Packages this package replaces (for upgrade paths from old package names).
62 pub replaces: Option<Vec<String>>,
63 /// Virtual packages provided by this package.
64 pub provides: Option<Vec<String>>,
65 /// Build IDs filter: only include artifacts from builds whose `id` is in this list.
66 /// Accepts the deprecated `builds:` spelling via serde alias for
67 /// back-compat with imported configs (the legacy `builds` key
68 /// marked `deprecated`, aliasing `ids`).
69 #[serde(alias = "builds")]
70 pub ids: Option<Vec<String>>,
71 /// amd64 microarchitecture variant filter (`["v1"]`, `["v2", "v3"]`, etc.),
72 /// set via the `amd64_variant:` key. When set, only amd64 binaries with
73 /// `amd64_variant` matching one of the listed values are included. The
74 /// legacy `goamd64:` spelling is accepted via serde alias for back-compat
75 /// with imported configs. When unset, all amd64 variants are included (no
76 /// filtering).
77 /// Each entry is typed as [`Amd64Variant`], so any value outside
78 /// `v1`..`v4` is rejected when the config is parsed.
79 #[serde(alias = "goamd64")]
80 pub amd64_variant: Option<Vec<Amd64Variant>>,
81 /// Package epoch for versioning (integer as string).
82 pub epoch: Option<String>,
83 /// Package release number.
84 pub release: Option<String>,
85 /// Prerelease version suffix.
86 pub prerelease: Option<String>,
87 /// Version metadata (e.g. git commit hash).
88 pub version_metadata: Option<String>,
89 /// Package section (e.g. "utils", "devel").
90 pub section: Option<String>,
91 /// Package priority (e.g. "optional", "required").
92 pub priority: Option<String>,
93 /// Whether this is a meta-package (no files, only dependencies).
94 pub meta: Option<bool>,
95 /// File permission umask. Accepts a YAML int (`18`), an octal-prefixed
96 /// string (`"0o022"`), or a leading-zero octal string (`"022"`).
97 pub umask: Option<StringOrU32>,
98 /// Default modification time for files in the package.
99 pub mtime: Option<String>,
100 /// RPM-specific configuration.
101 pub rpm: Option<NfpmRpmConfig>,
102 /// Deb-specific configuration.
103 pub deb: Option<NfpmDebConfig>,
104 /// APK-specific configuration.
105 pub apk: Option<NfpmApkConfig>,
106 /// Archlinux-specific configuration.
107 pub archlinux: Option<NfpmArchlinuxConfig>,
108 /// IPK-specific configuration (OpenWrt packages).
109 pub ipk: Option<NfpmIpkConfig>,
110 /// MSIX-specific configuration (Windows app packages).
111 ///
112 /// Only consumed when `formats` includes `msix`. nfpm requires
113 /// `publisher`, `properties.logo`, and at least one `applications` entry;
114 /// everything else has derived defaults.
115 ///
116 /// ```yaml
117 /// msix:
118 /// publisher: "CN=My Company, O=My Company, C=US"
119 /// properties:
120 /// logo: assets/logo.png
121 /// applications:
122 /// - id: MyApp
123 /// executable: myapp.exe
124 /// ```
125 pub msix: Option<NfpmMsixConfig>,
126 /// CGo library installation directories (header, carchive, cshared).
127 pub libdirs: Option<NfpmLibdirs>,
128 /// Path to a YAML-format changelog file for deb/rpm packages.
129 pub changelog: Option<String>,
130 /// Template-conditional: skip this nfpm config if rendered result is "false" or empty.
131 /// Conditional-skip gate.
132 #[serde(rename = "if")]
133 pub if_condition: Option<String>,
134 /// Extra file contents whose source files are Tera-rendered before packaging.
135 /// Each entry mirrors `contents`; the difference is that at stage time the file at `src` is
136 /// read, rendered through the template engine, written to a temp file, and then included
137 /// in the package at `dst` using the temp file as the real source. Useful for shipping
138 /// config files with templated values (version, commit, maintainer, etc.).
139 pub templated_contents: Option<Vec<NfpmContent>>,
140 /// Lifecycle scripts whose script-file bodies are Tera-rendered before packaging
141 /// Each path is read, rendered through the template engine, written to
142 /// a temp file, and used as the real script. If a field is set on both `scripts` and
143 /// `templated_scripts`, the templated version wins.
144 pub templated_scripts: Option<NfpmScripts>,
145}
146
147/// Installation directories for CGo library outputs.
148///
149/// Controls where header files, static archives, and shared libraries
150/// are installed in the package.
151#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
152#[serde(default, deny_unknown_fields)]
153pub struct NfpmLibdirs {
154 /// Installation directory for C header files.
155 pub header: Option<String>,
156 /// Installation directory for carchive (.a) static libraries.
157 pub carchive: Option<String>,
158 /// Installation directory for cshared (.so / .dylib) shared libraries.
159 pub cshared: Option<String>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
163#[serde(default, deny_unknown_fields)]
164pub struct NfpmScripts {
165 /// Path to script run before package installation.
166 pub preinstall: Option<String>,
167 /// Path to script run after package installation.
168 pub postinstall: Option<String>,
169 /// Path to script run before package removal.
170 pub preremove: Option<String>,
171 /// Path to script run after package removal.
172 pub postremove: Option<String>,
173}
174
175/// Backward-compatible alias — nFPM contents share the same `FileInfo` struct.
176pub type NfpmFileInfo = FileInfo;
177
178/// A single file/directory entry in an nFPM (or SRPM) package's `contents`
179/// list. Merged the formerly-separate `NfpmContentConfig`
180/// (used for SRPM) into this struct — `source` / `destination` / `type` are
181/// accepted as aliases for `src` / `dst` / the renamed `type` so srpm-style
182/// keys still parse.
183///
184/// `Default` is intentionally **not** derived because `src` and `dst` are
185/// required fields with no meaningful defaults — forcing callers to provide
186/// them explicitly prevents accidentally packaging empty paths.
187#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
188#[serde(deny_unknown_fields)]
189pub struct NfpmContent {
190 /// Source path on the build machine (supports glob patterns and templates).
191 ///
192 /// Paths are resolved relative to the project root. `..` segments are
193 /// NOT stripped, so a templated value resolving to `../../etc/passwd`
194 /// will reach outside the project tree — avoid splicing untrusted
195 /// template inputs (e.g. arbitrary `{{ Env.X }}` values) into `src`.
196 pub src: String,
197 /// Destination path inside the package (absolute path, supports templates).
198 ///
199 /// Same caveat as `src`: `..` segments are passed through to nfpm
200 /// verbatim. Templated values from untrusted sources should be
201 /// canonicalised by the caller before use.
202 pub dst: String,
203 /// Content entry type: "config", "config|noreplace", "doc", "dir", "symlink", "ghost", or empty for regular file.
204 #[serde(rename = "type")]
205 pub content_type: Option<String>,
206 /// File ownership and permission metadata.
207 pub file_info: Option<NfpmFileInfo>,
208 /// Per-packager filter: only include this content entry for the specified packager
209 /// (e.g. "deb", "rpm", "apk").
210 pub packager: Option<String>,
211 /// When true, expand template variables in the `src` and `dst` paths.
212 pub expand: Option<bool>,
213}
214
215// ---------------------------------------------------------------------------
216// nFPM format-specific configs
217// ---------------------------------------------------------------------------
218
219#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
220#[serde(default, deny_unknown_fields)]
221pub struct NfpmRpmConfig {
222 /// One-line package summary (RPM Summary tag).
223 pub summary: Option<String>,
224 /// RPM compression algorithm (e.g. "lzma", "gzip", "xz", "zstd").
225 pub compression: Option<String>,
226 /// RPM group classification (e.g. "System/Tools").
227 pub group: Option<String>,
228 /// RPM packager identity (e.g. "Build Team <build@example.com>").
229 pub packager: Option<String>,
230 /// Relocatable RPM prefix paths (e.g. ["/usr", "/etc"]).
231 pub prefixes: Option<Vec<String>>,
232 /// RPM signing configuration.
233 pub signature: Option<NfpmSignatureConfig>,
234 /// RPM-specific lifecycle scripts (pretrans/posttrans).
235 pub scripts: Option<NfpmRpmScripts>,
236 /// RPM BuildHost tag value.
237 pub build_host: Option<String>,
238}
239
240/// RPM-specific transaction scripts that run outside the normal install/remove lifecycle.
241#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
242#[serde(default, deny_unknown_fields)]
243pub struct NfpmRpmScripts {
244 /// Script to run before the RPM transaction begins.
245 pub pretrans: Option<String>,
246 /// Script to run after the RPM transaction completes.
247 pub posttrans: Option<String>,
248}
249
250impl NfpmRpmConfig {
251 /// Returns `true` when every field is `None` — the YAML section would be
252 /// empty and should be omitted.
253 pub fn is_empty(&self) -> bool {
254 self.summary.is_none()
255 && self.compression.is_none()
256 && self.group.is_none()
257 && self.packager.is_none()
258 && self.prefixes.is_none()
259 && self.signature.is_none()
260 && self.scripts.is_none()
261 && self.build_host.is_none()
262 }
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
266#[serde(default, deny_unknown_fields)]
267pub struct NfpmDebConfig {
268 /// Deb compression algorithm (e.g. "gzip", "xz", "zstd", "none").
269 pub compression: Option<String>,
270 /// Pre-dependency packages (stronger than Depends).
271 pub predepends: Option<Vec<String>>,
272 /// Deb trigger definitions.
273 pub triggers: Option<NfpmDebTriggers>,
274 /// Packages this package breaks (Breaks relationship).
275 pub breaks: Option<Vec<String>>,
276 /// Lintian overrides to embed in the package.
277 pub lintian_overrides: Option<Vec<String>>,
278 /// Deb signing configuration.
279 pub signature: Option<NfpmSignatureConfig>,
280 /// Additional control fields (e.g. Bugs, Built-Using).
281 pub fields: Option<HashMap<String, String>>,
282 /// Deb-specific maintainer scripts (rules, templates, config).
283 pub scripts: Option<NfpmDebScripts>,
284 /// Target architecture variant in deb nomenclature (e.g. `amd64v3`).
285 ///
286 /// Auto-derived from the built binary's `amd64_variant` (`v1`..`v4`) GOAMD64
287 /// microarchitecture metadata when unset, so an amd64 deb is tagged with the
288 /// microarch it was compiled for. Maps to nfpm's `deb.arch_variant`.
289 pub arch_variant: Option<String>,
290}
291
292/// Deb-specific maintainer scripts for package configuration and rules.
293#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
294#[serde(default, deny_unknown_fields)]
295pub struct NfpmDebScripts {
296 /// Path to debian/rules file.
297 pub rules: Option<String>,
298 /// Path to debian/templates file (debconf templates).
299 pub templates: Option<String>,
300 /// Path to debian/config script (debconf configuration).
301 pub config: Option<String>,
302}
303
304impl NfpmDebConfig {
305 /// Returns `true` when every field is `None` — the YAML section would be
306 /// empty and should be omitted.
307 pub fn is_empty(&self) -> bool {
308 self.compression.is_none()
309 && self.predepends.is_none()
310 && self.triggers.is_none()
311 && self.breaks.is_none()
312 && self.lintian_overrides.is_none()
313 && self.signature.is_none()
314 && self.fields.is_none()
315 && self.scripts.is_none()
316 // `arch_variant` keeps the deb block alive when set alone: a config
317 // carrying only `arch_variant: v3` must not be dropped as "empty",
318 // which would silently lose the microarch tag (`amd64v3` → plain
319 // `amd64`).
320 && self.arch_variant.is_none()
321 }
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
325#[serde(default, deny_unknown_fields)]
326pub struct NfpmDebTriggers {
327 /// Deb interest triggers: package waits for these triggers to complete.
328 pub interest: Option<Vec<String>>,
329 /// Deb interest-await triggers: package waits with synchronous trigger processing.
330 pub interest_await: Option<Vec<String>>,
331 /// Deb interest-noawait triggers: package registers interest without waiting.
332 pub interest_noawait: Option<Vec<String>>,
333 /// Deb activate triggers: package activates these triggers after install.
334 pub activate: Option<Vec<String>>,
335 /// Deb activate-await triggers: activate and wait for synchronous trigger processing.
336 pub activate_await: Option<Vec<String>>,
337 /// Deb activate-noawait triggers: activate without waiting.
338 pub activate_noawait: Option<Vec<String>>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
342#[serde(default, deny_unknown_fields)]
343pub struct NfpmApkConfig {
344 /// APK signing configuration.
345 pub signature: Option<NfpmSignatureConfig>,
346 /// APK-specific lifecycle scripts (preupgrade/postupgrade).
347 pub scripts: Option<NfpmApkScripts>,
348}
349
350/// APK-specific upgrade lifecycle scripts.
351#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
352#[serde(default, deny_unknown_fields)]
353pub struct NfpmApkScripts {
354 /// Script to run before upgrading an existing package.
355 pub preupgrade: Option<String>,
356 /// Script to run after upgrading an existing package.
357 pub postupgrade: Option<String>,
358}
359
360impl NfpmApkConfig {
361 /// Returns `true` when every field is `None` — the YAML section would be
362 /// empty and should be omitted.
363 pub fn is_empty(&self) -> bool {
364 self.signature.is_none() && self.scripts.is_none()
365 }
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
369#[serde(default, deny_unknown_fields)]
370pub struct NfpmArchlinuxConfig {
371 /// Base package name for split packages.
372 pub pkgbase: Option<String>,
373 /// Packager identity (e.g. "Build Team <build@example.com>").
374 pub packager: Option<String>,
375 /// Archlinux-specific lifecycle scripts.
376 pub scripts: Option<NfpmArchlinuxScripts>,
377}
378
379impl NfpmArchlinuxConfig {
380 /// Returns `true` when every field is `None` — the YAML section would be
381 /// empty and should be omitted.
382 pub fn is_empty(&self) -> bool {
383 self.pkgbase.is_none() && self.packager.is_none() && self.scripts.is_none()
384 }
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
388#[serde(default, deny_unknown_fields)]
389pub struct NfpmArchlinuxScripts {
390 /// Script to run before upgrading an existing package.
391 pub preupgrade: Option<String>,
392 /// Script to run after upgrading an existing package.
393 pub postupgrade: Option<String>,
394}
395
396/// IPK (OpenWrt) package-specific configuration.
397#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
398#[serde(default, deny_unknown_fields)]
399pub struct NfpmIpkConfig {
400 /// ABI version string for the package.
401 pub abi_version: Option<String>,
402 /// Alternative file links managed by the update-alternatives system.
403 pub alternatives: Option<Vec<NfpmIpkAlternative>>,
404 /// Whether the package was automatically installed as a dependency.
405 pub auto_installed: Option<bool>,
406 /// Whether the package is essential for the system.
407 pub essential: Option<bool>,
408 /// Strong pre-dependencies that must be fully installed before this package.
409 pub predepends: Option<Vec<String>>,
410 /// Tags for categorizing the package.
411 pub tags: Option<Vec<String>>,
412 /// Additional control fields as key-value pairs.
413 pub fields: Option<HashMap<String, String>>,
414}
415
416impl NfpmIpkConfig {
417 /// Returns `true` when every field is `None` — the YAML section would be
418 /// empty and should be omitted.
419 pub fn is_empty(&self) -> bool {
420 self.abi_version.is_none()
421 && self.alternatives.is_none()
422 && self.auto_installed.is_none()
423 && self.essential.is_none()
424 && self.predepends.is_none()
425 && self.tags.is_none()
426 && self.fields.is_none()
427 }
428}
429
430/// MSIX (Windows app package) specific configuration.
431///
432/// Field names mirror nfpm's `msix:` YAML block. nfpm validates that
433/// `publisher`, `properties.logo`, and at least one application (with `id`
434/// and `executable`) are set; the remaining fields have derived defaults
435/// (e.g. `entry_point` defaults to `Windows.FullTrustApplication`, display
436/// names default to the package name, and `dependencies` defaults to a
437/// `Windows.Desktop` 10.0.17763.0–10.0.22621.0 target device family).
438#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
439#[serde(default, deny_unknown_fields)]
440pub struct NfpmMsixConfig {
441 /// Target architecture override in MSIX nomenclature (`x64`, `x86`,
442 /// `arm64`, `arm`, `neutral`). When unset, derived from the build target
443 /// (e.g. `x86_64` → `x64`).
444 pub arch: Option<String>,
445 /// Publisher identity, matching the signing certificate subject
446 /// (e.g. `"CN=My Company, O=My Company, C=US"`). Required by nfpm.
447 /// Templated.
448 pub publisher: Option<String>,
449 /// Package identity fields.
450 pub identity: Option<NfpmMsixIdentity>,
451 /// Package display properties.
452 pub properties: Option<NfpmMsixProperties>,
453 /// Applications contained in the package (nfpm requires at least one,
454 /// each with `id` and `executable`). When omitted, anodizer derives one
455 /// application per packaged binary — `executable` is the binary's file
456 /// name and `id` its sanitized file stem — so this only needs setting to
457 /// override entry points or visual elements.
458 pub applications: Option<Vec<NfpmMsixApplication>>,
459 /// Target device family dependencies. Defaults to
460 /// `Windows.Desktop` min `10.0.17763.0` / max tested `10.0.22621.0`.
461 pub dependencies: Option<NfpmMsixDependencies>,
462 /// Capability declarations for the package.
463 pub capabilities: Option<NfpmMsixCapabilities>,
464 /// MSIX signing configuration.
465 pub signature: Option<NfpmMsixSignature>,
466}
467
468impl NfpmMsixConfig {
469 /// Returns `true` when every field is `None` — the YAML section would be
470 /// empty and should be omitted.
471 pub fn is_empty(&self) -> bool {
472 self.arch.is_none()
473 && self.publisher.is_none()
474 && self.identity.is_none()
475 && self.properties.is_none()
476 && self.applications.is_none()
477 && self.dependencies.is_none()
478 && self.capabilities.is_none()
479 && self.signature.is_none()
480 }
481}
482
483/// Identity fields for MSIX packages.
484#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
485#[serde(default, deny_unknown_fields)]
486pub struct NfpmMsixIdentity {
487 /// Resource identifier for the package identity (e.g. `"en-us"`).
488 pub resource_id: Option<String>,
489}
490
491/// Display properties for MSIX packages.
492#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
493#[serde(default, deny_unknown_fields)]
494pub struct NfpmMsixProperties {
495 /// Package display name (defaults to the package name). Templated.
496 pub display_name: Option<String>,
497 /// Publisher display name (defaults to the package name). Templated.
498 pub publisher_display_name: Option<String>,
499 /// Path to the package logo image (e.g. `assets/logo.png`). Required by
500 /// nfpm. Templated.
501 pub logo: Option<String>,
502}
503
504/// An application entry in an MSIX package.
505#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
506#[serde(default, deny_unknown_fields)]
507pub struct NfpmMsixApplication {
508 /// Application identifier (e.g. `"MyApp"`). Required by nfpm.
509 pub id: Option<String>,
510 /// Executable path inside the package (e.g. `myapp.exe`). Required by nfpm.
511 pub executable: Option<String>,
512 /// Application entry point (default: `Windows.FullTrustApplication`).
513 pub entry_point: Option<String>,
514 /// Visual presentation settings for this application.
515 pub visual_elements: Option<NfpmMsixVisualElements>,
516}
517
518/// Visual presentation settings for an MSIX application.
519#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
520#[serde(default, deny_unknown_fields)]
521pub struct NfpmMsixVisualElements {
522 /// Application display name (defaults to the package name).
523 pub display_name: Option<String>,
524 /// Application description (defaults to the package description).
525 pub description: Option<String>,
526 /// Tile background color (default: `transparent`).
527 pub background_color: Option<String>,
528 /// Path to the 150x150 tile logo (defaults to `properties.logo`).
529 pub square150x150_logo: Option<String>,
530 /// Path to the 44x44 tile logo (defaults to `properties.logo`).
531 pub square44x44_logo: Option<String>,
532}
533
534/// Dependency information for MSIX packages.
535#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
536#[serde(default, deny_unknown_fields)]
537pub struct NfpmMsixDependencies {
538 /// Target device families the package supports.
539 pub target_device_families: Option<Vec<NfpmMsixTargetDeviceFamily>>,
540}
541
542/// A target device family for an MSIX package.
543#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
544#[serde(default, deny_unknown_fields)]
545pub struct NfpmMsixTargetDeviceFamily {
546 /// Device family name (e.g. `"Windows.Desktop"`).
547 pub name: Option<String>,
548 /// Minimum OS version (e.g. `"10.0.17763.0"`).
549 pub min_version: Option<String>,
550 /// Maximum tested OS version (e.g. `"10.0.22621.0"`).
551 pub max_version_tested: Option<String>,
552}
553
554/// Capability declarations for MSIX packages.
555#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
556#[serde(default, deny_unknown_fields)]
557pub struct NfpmMsixCapabilities {
558 /// General capabilities (e.g. `["internetClient"]`).
559 pub capabilities: Option<Vec<String>>,
560 /// Device capabilities (e.g. `["microphone"]`).
561 pub device_capabilities: Option<Vec<String>>,
562 /// Restricted capabilities (e.g. `["runFullTrust"]` — added
563 /// automatically when an application uses the full-trust entry point).
564 pub restricted: Option<Vec<String>>,
565}
566
567/// Signing configuration for MSIX packages.
568///
569/// The passphrase is NOT a config field: nfpm reads it from the
570/// `NFPM_MSIX_PASSPHRASE` environment variable, which anodizer resolves via
571/// the same `NFPM_{ID}_MSIX_PASSPHRASE` → `NFPM_{ID}_PASSPHRASE` →
572/// `NFPM_PASSPHRASE` fallback the other signature blocks use and forwards to
573/// the nfpm subprocess.
574#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
575#[serde(default, deny_unknown_fields)]
576pub struct NfpmMsixSignature {
577 /// Path to the PFX certificate file used to sign the package. Templated.
578 pub pfx_file: Option<String>,
579}
580
581/// An alternative file link for IPK's update-alternatives system.
582#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
583#[serde(default, deny_unknown_fields)]
584pub struct NfpmIpkAlternative {
585 /// Priority for alternative selection (higher wins).
586 pub priority: Option<i32>,
587 /// Target file path that the alternative points to.
588 pub target: Option<String>,
589 /// Symlink name in the alternatives directory.
590 pub link_name: Option<String>,
591}
592
593#[cfg(test)]
594mod is_empty_tests {
595 use super::*;
596
597 /// `arch_variant` is the load-bearing single field that, when set in
598 /// isolation, must keep the deb block alive — otherwise the "drop empty
599 /// blocks" path silently dropped microarch tagging (`amd64v3` collapsing to
600 /// plain `amd64`).
601 #[test]
602 fn deb_arch_variant_alone_is_not_empty() {
603 let cfg = NfpmDebConfig {
604 arch_variant: Some("v3".to_string()),
605 ..Default::default()
606 };
607 assert!(
608 !cfg.is_empty(),
609 "deb block with only arch_variant must NOT be dropped"
610 );
611 }
612
613 /// Sanity: a fully empty deb block IS empty.
614 #[test]
615 fn deb_default_is_empty() {
616 assert!(NfpmDebConfig::default().is_empty());
617 }
618
619 /// A default MSIX block is empty (every field None) so the "drop empty
620 /// blocks" path omits the section; a single populated field keeps it alive.
621 #[test]
622 fn msix_default_is_empty_but_one_field_keeps_it() {
623 assert!(NfpmMsixConfig::default().is_empty());
624 let with_arch = NfpmMsixConfig {
625 arch: Some("x64".to_string()),
626 ..Default::default()
627 };
628 assert!(
629 !with_arch.is_empty(),
630 "an MSIX block with a set field must NOT be dropped"
631 );
632 let with_publisher = NfpmMsixConfig {
633 publisher: Some("CN=Acme".to_string()),
634 ..Default::default()
635 };
636 assert!(!with_publisher.is_empty());
637 }
638
639 /// The legacy `goamd64:` spelling folds into `amd64_variant` so imported
640 /// configs keep parsing under `deny_unknown_fields`.
641 #[test]
642 fn nfpm_goamd64_alias_parses_into_amd64_variant() {
643 let nfpm: NfpmConfig =
644 serde_yaml_ng::from_str("formats: [deb]\ngoamd64: [v2, v3]").unwrap();
645 assert_eq!(
646 nfpm.amd64_variant.as_deref(),
647 Some([Amd64Variant::V2, Amd64Variant::V3].as_slice())
648 );
649 }
650
651 #[test]
652 fn nfpm_canonical_amd64_variant_still_parses() {
653 let nfpm: NfpmConfig =
654 serde_yaml_ng::from_str("formats: [deb]\namd64_variant: [v1]").unwrap();
655 assert_eq!(
656 nfpm.amd64_variant.as_deref(),
657 Some([Amd64Variant::V1].as_slice())
658 );
659 }
660
661 /// A typo'd level in the list form must fail at parse — the field is a
662 /// closed enum, not free text, so `x86-64-v3` cannot silently disable
663 /// the variant filter.
664 #[test]
665 fn nfpm_amd64_variant_list_rejects_garbage_at_parse() {
666 let err =
667 serde_yaml_ng::from_str::<NfpmConfig>("formats: [deb]\namd64_variant: [v2, x86-64-v3]")
668 .unwrap_err()
669 .to_string();
670 assert!(
671 err.contains("unknown variant `x86-64-v3`")
672 && err.contains("expected one of `v1`, `v2`, `v3`, `v4`"),
673 "parse error must name the bad value and the valid set: {err}"
674 );
675 }
676}
677
678/// Unified signature configuration shared by nFPM (deb/rpm/apk) and SRPM
679/// packages — SRPM's surface is a strict subset, so a single struct covers
680/// both. The legacy SRPM `passphrase:` key is accepted as a serde alias
681/// for `key_passphrase:` so both spellings parse.
682///
683/// There are three distinct signature types (`NFPMRPMSignature`,
684/// `NFPMDebSignature`, `NFPMAPKSignature`) with overlapping but slightly
685/// different fields. Anodizer's union here avoids the 3-struct cascade
686/// when 90% of fields overlap.
687#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
688#[serde(default, deny_unknown_fields)]
689pub struct NfpmSignatureConfig {
690 /// Path to the signing key file.
691 pub key_file: Option<String>,
692 /// Key ID to use for signing.
693 pub key_id: Option<String>,
694 /// Passphrase for the signing key. Falls back to `NFPM_PASSPHRASE` /
695 /// `SRPM_PASSPHRASE` env vars in their respective stages.
696 pub key_passphrase: Option<String>,
697 /// Public key name for APK signatures (defaults to `<maintainer email>.rsa.pub`).
698 pub key_name: Option<String>,
699 /// Signature type for deb packages: "origin", "maint", or "archive" (default: "origin").
700 #[serde(rename = "type")]
701 pub type_: Option<String>,
702}