Skip to main content

anodizer_core/config/
snapcraft.rs

1use std::collections::BTreeMap;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use super::archives::TemplatedExtraFile;
7use super::{StringOrBool, deserialize_string_or_bool_opt};
8
9// ---------------------------------------------------------------------------
10// SnapcraftConfig
11// ---------------------------------------------------------------------------
12
13#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
14#[serde(default, deny_unknown_fields)]
15pub struct SnapcraftConfig {
16    /// Unique identifier for this snapcraft config.
17    pub id: Option<String>,
18    /// Build IDs to include. Empty means all builds.
19    pub ids: Option<Vec<String>>,
20    /// Snap package name in the store.
21    pub name: Option<String>,
22    /// Canonical application title (user-facing in store).
23    pub title: Option<String>,
24    /// Single-line elevator pitch (max 79 characters).
25    pub summary: Option<String>,
26    /// Extended description (user-facing in store).
27    pub description: Option<String>,
28    /// Path to the snap icon image (`.png` or `.svg`).
29    ///
30    /// When set, anodizer copies the file to `meta/gui/<name>.<ext>` inside
31    /// the staged prime directory before `snapcraft pack` runs. The icon is
32    /// delivered to the Snap Store via snapcraft's GUI metadata channel and
33    /// never appears in `snap.json`, keeping uploads schema-clean. (The Snap
34    /// Store rejects `snap.json` that contains an `icon:` key with
35    /// "Additional properties are not allowed ('icon' was unexpected)".)
36    ///
37    /// The source path may be absolute or relative to the project root.
38    /// Anodizer errors before staging if the file does not exist.
39    pub icon: Option<String>,
40    /// Runtime base snap: core, core18, core20, core22, core24, bare.
41    pub base: Option<String>,
42    /// Release stability level: stable, devel.
43    pub grade: Option<String>,
44    /// License identifier (SPDX format).
45    pub license: Option<String>,
46    /// Whether to publish to the snapcraft store.
47    pub publish: Option<bool>,
48    /// Distribution channels: edge, beta, candidate, stable.
49    pub channel_templates: Option<Vec<String>>,
50    /// Security confinement level: strict, devmode, classic.
51    pub confinement: Option<String>,
52    /// Top-level snap plug definitions (structured map).
53    /// Keys are plug names, values are either `null` (simple plug) or an object
54    /// with `interface` and optional attributes (e.g. `{ interface: "content", target: "$SNAP/shared" }`).
55    /// An arbitrary key/value map for this field.
56    pub plugs: Option<BTreeMap<String, serde_json::Value>>,
57    // No top-level `slots:` — Snapcraft itself has no top-level slots
58    // concept; use `apps.<name>.slots` for per-app slots.
59    /// Required snapd features/versions.
60    pub assumes: Option<Vec<String>>,
61    /// Application configurations defining daemons, commands, env vars.
62    pub apps: Option<BTreeMap<String, SnapcraftApp>>,
63    /// Directory mappings for sandbox accessibility.
64    ///
65    /// Accepts the legacy plural `layouts:` spelling via serde alias for
66    /// back-compat with imported configs.
67    #[serde(alias = "layouts")]
68    pub layout: Option<BTreeMap<String, SnapcraftLayout>>,
69    /// Additional static files to bundle (string shorthand or structured form).
70    pub extra_files: Option<Vec<SnapcraftExtraFileSpec>>,
71    /// Extra files whose contents are rendered through the template engine before bundling.
72    /// Unlike `extra_files` which copy as-is, template variables like `{{ Tag }}` are expanded.
73    pub templated_extra_files: Option<Vec<TemplatedExtraFile>>,
74    /// Template for the output snap filename.
75    pub name_template: Option<String>,
76    /// Skip this snapcraft config. Accepts bool or template string
77    /// (e.g. `"{{ if .IsSnapshot }}true{{ endif }}"` for conditional skip).
78    /// Accepts the legacy `disable:` spelling via serde alias for back-compat.
79    #[serde(
80        default,
81        alias = "disable",
82        deserialize_with = "deserialize_string_or_bool_opt"
83    )]
84    pub skip: Option<StringOrBool>,
85    /// Remove source archives from artifacts, keeping only snap.
86    pub replace: Option<bool>,
87    /// Output timestamp for reproducible builds.
88    pub mod_timestamp: Option<String>,
89    /// Snap hooks — maps hook name to arbitrary hook config.
90    pub hooks: Option<BTreeMap<String, serde_json::Value>>,
91    /// Template-conditional gate: when the rendered result is falsy
92    /// (`"false"` / `"0"` / `"no"` / empty), the snapcraft config is
93    /// skipped. Render failure hard-errors. The
94    /// `snapcrafts[].if:`. Distinct from `skip:` (always-skip predicate).
95    #[serde(rename = "if")]
96    pub if_condition: Option<String>,
97    /// When `true`, a failed snap upload counts as a required-publisher
98    /// failure: trips the submitter gate and surfaces in
99    /// `report.required_failures()` so the CLI exits non-zero.
100    ///
101    /// Default: `false` — a failed snap upload does not abort the pipeline.
102    /// It is still reported as a post-publish landing issue by
103    /// `verify-release` regardless of this setting: `required` governs only
104    /// whether the run ABORTS mid-pipeline, never whether an attempted
105    /// failure gets certified as a clean release.
106    ///
107    /// When multiple snapcraft configs exist, the stage records ONE
108    /// aggregated `PublisherResult`: `required = true` if ANY config opted
109    /// in.
110    pub required: Option<bool>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
114#[serde(default, deny_unknown_fields)]
115pub struct SnapcraftApp {
116    /// Command to run (relative to snap root).
117    pub command: Option<String>,
118    /// Daemon type: simple, forking, oneshot, notify, dbus.
119    pub daemon: Option<String>,
120    /// How to stop the daemon: sigterm, sigkill, etc.
121    #[serde(alias = "stop-mode")]
122    pub stop_mode: Option<String>,
123    /// Interface plugs the app needs.
124    pub plugs: Option<Vec<String>>,
125    /// Environment variables for the app (supports string, integer, and boolean values).
126    pub environment: Option<BTreeMap<String, serde_json::Value>>,
127    /// Additional arguments passed to the command.
128    pub args: Option<String>,
129    /// Restart condition: on-failure, always, on-success, on-abnormal, on-abort, on-watchdog, never.
130    #[serde(alias = "restart-condition")]
131    pub restart_condition: Option<String>,
132    /// Snap adapter type: "none" or "full" (default: "full").
133    pub adapter: Option<String>,
134    /// Services that must start before this app.
135    pub after: Option<Vec<String>>,
136    /// Alternative names for the command.
137    pub aliases: Option<Vec<String>>,
138    /// Desktop file for autostart.
139    pub autostart: Option<String>,
140    /// Services that must start after this app.
141    pub before: Option<Vec<String>>,
142    /// D-Bus well-known bus name.
143    #[serde(alias = "bus-name")]
144    pub bus_name: Option<String>,
145    /// Wrapper commands run before the main command.
146    #[serde(alias = "command-chain")]
147    pub command_chain: Option<Vec<String>>,
148    /// AppStream metadata common ID.
149    #[serde(alias = "common-id")]
150    pub common_id: Option<String>,
151    /// Path to bash completion script relative to snap.
152    pub completer: Option<String>,
153    /// Path to .desktop file relative to snap.
154    pub desktop: Option<String>,
155    /// Snap extensions to apply.
156    pub extensions: Option<Vec<String>>,
157    /// Installation mode: "enable" or "disable".
158    #[serde(alias = "install-mode")]
159    pub install_mode: Option<String>,
160    /// Arbitrary YAML passed through to snap.yaml.
161    pub passthrough: Option<BTreeMap<String, serde_json::Value>>,
162    /// Command to run after daemon stops.
163    #[serde(alias = "post-stop-command")]
164    pub post_stop_command: Option<String>,
165    /// Refresh behavior: "endure" or "restart".
166    #[serde(alias = "refresh-mode")]
167    pub refresh_mode: Option<String>,
168    /// Command to reload daemon config.
169    #[serde(alias = "reload-command")]
170    pub reload_command: Option<String>,
171    /// Delay between restarts (duration string).
172    #[serde(alias = "restart-delay")]
173    pub restart_delay: Option<String>,
174    /// Interface slots this app provides.
175    pub slots: Option<Vec<String>>,
176    /// Socket definitions map.
177    pub sockets: Option<BTreeMap<String, serde_json::Value>>,
178    /// Start timeout duration string.
179    #[serde(alias = "start-timeout")]
180    pub start_timeout: Option<String>,
181    /// Command to gracefully stop the daemon.
182    #[serde(alias = "stop-command")]
183    pub stop_command: Option<String>,
184    /// Stop timeout duration string.
185    #[serde(alias = "stop-timeout")]
186    pub stop_timeout: Option<String>,
187    /// Timer definition (systemd timer syntax).
188    pub timer: Option<String>,
189    /// Watchdog timeout duration string.
190    #[serde(alias = "watchdog-timeout")]
191    pub watchdog_timeout: Option<String>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
195#[serde(default, deny_unknown_fields)]
196pub struct SnapcraftLayout {
197    /// Bind-mount a directory to the snap's layout.
198    pub bind: Option<String>,
199    /// Bind-mount a single file to the snap's layout.
200    pub bind_file: Option<String>,
201    /// Symlink a path to a location in the snap.
202    pub symlink: Option<String>,
203    /// Layout entry type.
204    #[serde(rename = "type")]
205    pub type_: Option<String>,
206}
207
208/// Specifies an extra file for snapcraft. Can be a simple source path string or
209/// a structured object with source, destination, and mode fields (matching
210/// the snapcraft extra-files shape).
211#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
212#[serde(untagged)]
213pub enum SnapcraftExtraFileSpec {
214    /// Simple source path string.
215    Source(String),
216    /// Structured form with source, destination, and mode.
217    Detailed {
218        source: String,
219        #[serde(skip_serializing_if = "Option::is_none")]
220        destination: Option<String>,
221        #[serde(skip_serializing_if = "Option::is_none")]
222        mode: Option<u32>,
223    },
224}
225
226impl SnapcraftExtraFileSpec {
227    /// Return the source path for this spec.
228    pub fn source(&self) -> &str {
229        match self {
230            SnapcraftExtraFileSpec::Source(s) => s,
231            SnapcraftExtraFileSpec::Detailed { source, .. } => source,
232        }
233    }
234
235    /// Return the optional destination path.
236    pub fn destination(&self) -> Option<&str> {
237        match self {
238            SnapcraftExtraFileSpec::Source(_) => None,
239            SnapcraftExtraFileSpec::Detailed { destination, .. } => destination.as_deref(),
240        }
241    }
242
243    /// Return the optional file mode.
244    pub fn mode(&self) -> Option<u32> {
245        match self {
246            SnapcraftExtraFileSpec::Source(_) => None,
247            SnapcraftExtraFileSpec::Detailed { mode, .. } => *mode,
248        }
249    }
250}