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}
98
99#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
100#[serde(default, deny_unknown_fields)]
101pub struct SnapcraftApp {
102 /// Command to run (relative to snap root).
103 pub command: Option<String>,
104 /// Daemon type: simple, forking, oneshot, notify, dbus.
105 pub daemon: Option<String>,
106 /// How to stop the daemon: sigterm, sigkill, etc.
107 #[serde(alias = "stop-mode")]
108 pub stop_mode: Option<String>,
109 /// Interface plugs the app needs.
110 pub plugs: Option<Vec<String>>,
111 /// Environment variables for the app (supports string, integer, and boolean values).
112 pub environment: Option<BTreeMap<String, serde_json::Value>>,
113 /// Additional arguments passed to the command.
114 pub args: Option<String>,
115 /// Restart condition: on-failure, always, on-success, on-abnormal, on-abort, on-watchdog, never.
116 #[serde(alias = "restart-condition")]
117 pub restart_condition: Option<String>,
118 /// Snap adapter type: "none" or "full" (default: "full").
119 pub adapter: Option<String>,
120 /// Services that must start before this app.
121 pub after: Option<Vec<String>>,
122 /// Alternative names for the command.
123 pub aliases: Option<Vec<String>>,
124 /// Desktop file for autostart.
125 pub autostart: Option<String>,
126 /// Services that must start after this app.
127 pub before: Option<Vec<String>>,
128 /// D-Bus well-known bus name.
129 #[serde(alias = "bus-name")]
130 pub bus_name: Option<String>,
131 /// Wrapper commands run before the main command.
132 #[serde(alias = "command-chain")]
133 pub command_chain: Option<Vec<String>>,
134 /// AppStream metadata common ID.
135 #[serde(alias = "common-id")]
136 pub common_id: Option<String>,
137 /// Path to bash completion script relative to snap.
138 pub completer: Option<String>,
139 /// Path to .desktop file relative to snap.
140 pub desktop: Option<String>,
141 /// Snap extensions to apply.
142 pub extensions: Option<Vec<String>>,
143 /// Installation mode: "enable" or "disable".
144 #[serde(alias = "install-mode")]
145 pub install_mode: Option<String>,
146 /// Arbitrary YAML passed through to snap.yaml.
147 pub passthrough: Option<BTreeMap<String, serde_json::Value>>,
148 /// Command to run after daemon stops.
149 #[serde(alias = "post-stop-command")]
150 pub post_stop_command: Option<String>,
151 /// Refresh behavior: "endure" or "restart".
152 #[serde(alias = "refresh-mode")]
153 pub refresh_mode: Option<String>,
154 /// Command to reload daemon config.
155 #[serde(alias = "reload-command")]
156 pub reload_command: Option<String>,
157 /// Delay between restarts (duration string).
158 #[serde(alias = "restart-delay")]
159 pub restart_delay: Option<String>,
160 /// Interface slots this app provides.
161 pub slots: Option<Vec<String>>,
162 /// Socket definitions map.
163 pub sockets: Option<BTreeMap<String, serde_json::Value>>,
164 /// Start timeout duration string.
165 #[serde(alias = "start-timeout")]
166 pub start_timeout: Option<String>,
167 /// Command to gracefully stop the daemon.
168 #[serde(alias = "stop-command")]
169 pub stop_command: Option<String>,
170 /// Stop timeout duration string.
171 #[serde(alias = "stop-timeout")]
172 pub stop_timeout: Option<String>,
173 /// Timer definition (systemd timer syntax).
174 pub timer: Option<String>,
175 /// Watchdog timeout duration string.
176 #[serde(alias = "watchdog-timeout")]
177 pub watchdog_timeout: Option<String>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
181#[serde(default, deny_unknown_fields)]
182pub struct SnapcraftLayout {
183 /// Bind-mount a directory to the snap's layout.
184 pub bind: Option<String>,
185 /// Bind-mount a single file to the snap's layout.
186 pub bind_file: Option<String>,
187 /// Symlink a path to a location in the snap.
188 pub symlink: Option<String>,
189 /// Layout entry type.
190 #[serde(rename = "type")]
191 pub type_: Option<String>,
192}
193
194/// Specifies an extra file for snapcraft. Can be a simple source path string or
195/// a structured object with source, destination, and mode fields (matching
196/// the snapcraft extra-files shape).
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
198#[serde(untagged)]
199pub enum SnapcraftExtraFileSpec {
200 /// Simple source path string.
201 Source(String),
202 /// Structured form with source, destination, and mode.
203 Detailed {
204 source: String,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 destination: Option<String>,
207 #[serde(skip_serializing_if = "Option::is_none")]
208 mode: Option<u32>,
209 },
210}
211
212impl SnapcraftExtraFileSpec {
213 /// Return the source path for this spec.
214 pub fn source(&self) -> &str {
215 match self {
216 SnapcraftExtraFileSpec::Source(s) => s,
217 SnapcraftExtraFileSpec::Detailed { source, .. } => source,
218 }
219 }
220
221 /// Return the optional destination path.
222 pub fn destination(&self) -> Option<&str> {
223 match self {
224 SnapcraftExtraFileSpec::Source(_) => None,
225 SnapcraftExtraFileSpec::Detailed { destination, .. } => destination.as_deref(),
226 }
227 }
228
229 /// Return the optional file mode.
230 pub fn mode(&self) -> Option<u32> {
231 match self {
232 SnapcraftExtraFileSpec::Source(_) => None,
233 SnapcraftExtraFileSpec::Detailed { mode, .. } => *mode,
234 }
235 }
236}