cargo_packager/config/
mod.rs

1// Copyright 2023-2023 CrabNebula Ltd.
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! Configuration type and associated utilities.
6
7use std::{
8    collections::HashMap,
9    ffi::OsString,
10    fmt::{self, Display},
11    fs,
12    path::{Path, PathBuf},
13};
14
15use relative_path::PathExt;
16use serde::{Deserialize, Serialize};
17
18use crate::{util, Error};
19
20mod builder;
21mod category;
22
23pub use builder::*;
24pub use category::AppCategory;
25
26pub use cargo_packager_utils::PackageFormat;
27
28/// **macOS-only**. Corresponds to CFBundleTypeRole
29#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
30#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
31#[serde(rename_all = "camelCase", deny_unknown_fields)]
32#[derive(Default)]
33pub enum BundleTypeRole {
34    /// CFBundleTypeRole.Editor. Files can be read and edited.
35    #[default]
36    Editor,
37    /// CFBundleTypeRole.Viewer. Files can be read.
38    Viewer,
39    /// CFBundleTypeRole.Shell
40    Shell,
41    /// CFBundleTypeRole.QLGenerator
42    QLGenerator,
43    /// CFBundleTypeRole.None
44    None,
45}
46
47impl Display for BundleTypeRole {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            Self::Editor => write!(f, "Editor"),
51            Self::Viewer => write!(f, "Viewer"),
52            Self::Shell => write!(f, "Shell"),
53            Self::QLGenerator => write!(f, "QLGenerator"),
54            Self::None => write!(f, "None"),
55        }
56    }
57}
58
59/// A file association configuration.
60#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
61#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
62#[serde(rename_all = "camelCase", deny_unknown_fields)]
63#[non_exhaustive]
64pub struct FileAssociation {
65    /// File extensions to associate with this app. e.g. 'png'
66    pub extensions: Vec<String>,
67    /// The mime-type e.g. 'image/png' or 'text/plain'. **Linux-only**.
68    #[serde(alias = "mime-type", alias = "mime_type")]
69    pub mime_type: Option<String>,
70    /// The association description. **Windows-only**. It is displayed on the `Type` column on Windows Explorer.
71    pub description: Option<String>,
72    /// The name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`
73    pub name: Option<String>,
74    /// The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.
75    /// Defaults to [`BundleTypeRole::Editor`]
76    #[serde(default)]
77    pub role: BundleTypeRole,
78}
79
80impl FileAssociation {
81    /// Creates a new [`FileAssociation`] using provided extensions.
82    pub fn new<I, S>(extensions: I) -> Self
83    where
84        I: IntoIterator<Item = S>,
85        S: Into<String>,
86    {
87        Self {
88            extensions: extensions.into_iter().map(Into::into).collect(),
89            mime_type: None,
90            description: None,
91            name: None,
92            role: BundleTypeRole::default(),
93        }
94    }
95
96    /// Set the extenstions to associate with this app. e.g. 'png'.
97    pub fn extensions<I, S>(mut self, extensions: I) -> Self
98    where
99        I: IntoIterator<Item = S>,
100        S: Into<String>,
101    {
102        self.extensions = extensions.into_iter().map(Into::into).collect();
103        self
104    }
105
106    /// Set the mime-type e.g. 'image/png' or 'text/plain'. **Linux-only**.
107    pub fn mime_type<S: Into<String>>(mut self, mime_type: S) -> Self {
108        self.mime_type.replace(mime_type.into());
109        self
110    }
111
112    /// Se the association description. **Windows-only**. It is displayed on the `Type` column on Windows Explorer.
113    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
114        self.description.replace(description.into());
115        self
116    }
117
118    /// Set he name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`
119    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
120        self.name.replace(name.into());
121        self
122    }
123
124    /// Set he app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.
125    /// Defaults to [`BundleTypeRole::Editor`]
126    pub fn role(mut self, role: BundleTypeRole) -> Self {
127        self.role = role;
128        self
129    }
130}
131
132/// Deep link protocol
133#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
134#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
135#[serde(rename_all = "camelCase", deny_unknown_fields)]
136#[non_exhaustive]
137pub struct DeepLinkProtocol {
138    /// URL schemes to associate with this app without `://`. For example `my-app`
139    pub schemes: Vec<String>,
140    /// The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`
141    pub name: Option<String>,
142    /// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.
143    #[serde(default)]
144    pub role: BundleTypeRole,
145}
146
147impl DeepLinkProtocol {
148    /// Creates a new [`DeepLinkProtocol``] using provided schemes.
149    pub fn new<I, S>(schemes: I) -> Self
150    where
151        I: IntoIterator<Item = S>,
152        S: Into<String>,
153    {
154        Self {
155            schemes: schemes.into_iter().map(Into::into).collect(),
156            name: None,
157            role: BundleTypeRole::default(),
158        }
159    }
160
161    /// Set the name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`
162    pub fn name<S: Into<String>>(mut self, name: S) -> Self {
163        self.name.replace(name.into());
164        self
165    }
166
167    /// Set he app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.
168    /// Defaults to [`BundleTypeRole::Editor`]
169    pub fn role(mut self, role: BundleTypeRole) -> Self {
170        self.role = role;
171        self
172    }
173}
174
175/// The Linux Debian configuration.
176#[derive(Clone, Debug, Default, Deserialize, Serialize)]
177#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
178#[serde(rename_all = "camelCase", deny_unknown_fields)]
179#[non_exhaustive]
180pub struct DebianConfig {
181    /// The list of Debian dependencies.
182    pub depends: Option<Dependencies>,
183    /// Path to a custom desktop file Handlebars template.
184    ///
185    /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.
186    ///
187    /// Default file contents:
188    /// ```text
189    /// [Desktop Entry]
190    /// Categories={{categories}}
191    /// {{#if comment}}
192    /// Comment={{comment}}
193    /// {{/if}}
194    /// Exec={{exec}} {{exec_arg}}
195    /// Icon={{icon}}
196    /// Name={{name}}
197    /// Terminal=false
198    /// Type=Application
199    /// {{#if mime_type}}
200    /// MimeType={{mime_type}}
201    /// {{/if}}
202    /// ```
203    ///
204    /// The `{{exec_arg}}` will be set to:
205    /// * "%F", if at least one [Config::file_associations] was specified but no deep link protocols were given.
206    ///   * The "%F" arg means that your application can be invoked with multiple file paths.
207    /// * "%U", if at least one [Config::deep_link_protocols] was specified.
208    ///   * The "%U" arg means that your application can be invoked with multiple URLs.
209    ///   * If both [Config::file_associations] and [Config::deep_link_protocols] were specified,
210    ///     the "%U" arg will be used, causing the file paths to be passed to your app as `file://` URLs.
211    /// * An empty string "" (nothing) if neither are given.
212    ///   * This means that your application will never be invoked with any URLs or file paths.
213    ///
214    /// To specify a custom `exec_arg`, just use plaintext directly instead of `{{exec_arg}}`:
215    /// ```text
216    /// Exec={{exec}} %u
217    /// ```
218    ///
219    /// See more here: <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables>.
220    #[serde(alias = "desktop-template", alias = "desktop_template")]
221    pub desktop_template: Option<PathBuf>,
222    /// Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>
223    pub section: Option<String>,
224    /// Change the priority of the Debian Package. By default, it is set to `optional`.
225    /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`
226    pub priority: Option<String>,
227    /// List of custom files to add to the deb package.
228    /// Maps a dir/file to a dir/file inside the debian package.
229    pub files: Option<HashMap<String, String>>,
230    /// Name to use for the `Package` field in the Debian Control file.
231    /// Defaults to [`Config::product_name`] converted to kebab-case.
232    #[serde(alias = "package-name", alias = "package_name")]
233    pub package_name: Option<String>,
234}
235
236impl DebianConfig {
237    /// Creates a new [`DebianConfig`].
238    pub fn new() -> Self {
239        Self::default()
240    }
241
242    /// Set the list of Debian dependencies directly using an iterator of strings.
243    pub fn depends<I, S>(mut self, depends: I) -> Self
244    where
245        I: IntoIterator<Item = S>,
246        S: Into<String>,
247    {
248        self.depends.replace(Dependencies::List(
249            depends.into_iter().map(Into::into).collect(),
250        ));
251        self
252    }
253
254    /// Set the list of Debian dependencies indirectly via a path to a file,
255    /// which must contain one dependency (a package name) per line.
256    pub fn depends_path<P>(mut self, path: P) -> Self
257    where
258        P: Into<PathBuf>,
259    {
260        self.depends.replace(Dependencies::Path(path.into()));
261        self
262    }
263
264    /// Set the path to a custom desktop file Handlebars template.
265    ///
266    /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.
267    ///
268    /// Default file contents:
269    /// ```text
270    /// [Desktop Entry]
271    /// Categories={{categories}}
272    /// {{#if comment}}
273    /// Comment={{comment}}
274    /// {{/if}}
275    /// Exec={{exec}} {{exec_arg}}
276    /// Icon={{icon}}
277    /// Name={{name}}
278    /// Terminal=false
279    /// Type=Application
280    /// {{#if mime_type}}
281    /// MimeType={{mime_type}}
282    /// {{/if}}
283    /// ```
284    pub fn desktop_template<P: Into<PathBuf>>(mut self, desktop_template: P) -> Self {
285        self.desktop_template.replace(desktop_template.into());
286        self
287    }
288
289    /// Define the section in Debian Control file. See : <https://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections>
290    pub fn section<S: Into<String>>(mut self, section: S) -> Self {
291        self.section.replace(section.into());
292        self
293    }
294
295    /// Change the priority of the Debian Package. By default, it is set to `optional`.
296    /// Recognized Priorities as of now are :  `required`, `important`, `standard`, `optional`, `extra`
297    pub fn priority<S: Into<String>>(mut self, priority: S) -> Self {
298        self.priority.replace(priority.into());
299        self
300    }
301
302    /// Set the list of custom files to add to the deb package.
303    /// Maps a dir/file to a dir/file inside the debian package.
304    pub fn files<I, S, T>(mut self, files: I) -> Self
305    where
306        I: IntoIterator<Item = (S, T)>,
307        S: Into<String>,
308        T: Into<String>,
309    {
310        self.files.replace(
311            files
312                .into_iter()
313                .map(|(k, v)| (k.into(), v.into()))
314                .collect(),
315        );
316        self
317    }
318}
319
320/// A list of dependencies specified as either a list of Strings
321/// or as a path to a file that lists the dependencies, one per line.
322#[derive(Debug, Clone, Deserialize, Serialize)]
323#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
324#[serde(untagged)]
325#[non_exhaustive]
326pub enum Dependencies {
327    /// The list of dependencies provided directly as a vector of Strings.
328    List(Vec<String>),
329    /// A path to the file containing the list of dependences, formatted as one per line:
330    /// ```text
331    /// libc6
332    /// libxcursor1
333    /// libdbus-1-3
334    /// libasyncns0
335    /// ...
336    /// ```
337    Path(PathBuf),
338}
339impl Dependencies {
340    /// Returns the dependencies as a list of Strings.
341    pub fn to_list(&self) -> crate::Result<Vec<String>> {
342        match self {
343            Self::List(v) => Ok(v.clone()),
344            Self::Path(path) => {
345                let trimmed_lines = fs::read_to_string(path)
346                    .map_err(|e| Error::IoWithPath(path.clone(), e))?
347                    .lines()
348                    .filter_map(|line| {
349                        let trimmed = line.trim();
350                        if !trimmed.is_empty() {
351                            Some(trimmed.to_owned())
352                        } else {
353                            None
354                        }
355                    })
356                    .collect();
357                Ok(trimmed_lines)
358            }
359        }
360    }
361}
362
363/// The Linux AppImage configuration.
364#[derive(Clone, Debug, Default, Deserialize, Serialize)]
365#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
366#[serde(rename_all = "camelCase", deny_unknown_fields)]
367#[non_exhaustive]
368pub struct AppImageConfig {
369    /// List of libs that exist in `/usr/lib*` to be include in the final AppImage.
370    /// The libs will be searched for, using the command
371    /// `find -L /usr/lib* -name <libname>`
372    pub libs: Option<Vec<String>>,
373    /// List of binary paths to include in the final AppImage.
374    /// For example, if you want `xdg-open`, you'd specify `/usr/bin/xdg-open`
375    pub bins: Option<Vec<String>>,
376    /// List of custom files to add to the appimage package.
377    /// Maps a dir/file to a dir/file inside the appimage package.
378    pub files: Option<HashMap<String, String>>,
379    /// A map of [`linuxdeploy`](https://github.com/linuxdeploy/linuxdeploy)
380    /// plugin name and its URL to be downloaded and executed while packaing the appimage.
381    /// For example, if you want to use the
382    /// [`gtk`](https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh) plugin,
383    /// you'd specify `gtk` as the key and its url as the value.
384    #[serde(alias = "linuxdeploy-plugins", alias = "linuxdeploy_plugins")]
385    pub linuxdeploy_plugins: Option<HashMap<String, String>>,
386    /// List of globs of libraries to exclude from the final AppImage.
387    /// For example, to exclude libnss3.so, you'd specify `libnss3*`
388    #[serde(alias = "excluded-libraries", alias = "excluded_libraries")]
389    pub excluded_libs: Option<Vec<String>>,
390}
391
392impl AppImageConfig {
393    /// Creates a new [`DebianConfig`].
394    pub fn new() -> Self {
395        Self::default()
396    }
397
398    /// Set the list of libs that exist in `/usr/lib*` to be include in the final AppImage.
399    /// The libs will be searched for using, the command
400    /// `find -L /usr/lib* -name <libname>`
401    pub fn libs<I, S>(mut self, libs: I) -> Self
402    where
403        I: IntoIterator<Item = S>,
404        S: Into<String>,
405    {
406        self.libs
407            .replace(libs.into_iter().map(Into::into).collect());
408        self
409    }
410
411    /// Set the list of binary paths to include in the final AppImage.
412    /// For example, if you want `xdg-open`, you'd specify `/usr/bin/xdg-open`
413    pub fn bins<I, S>(mut self, bins: I) -> Self
414    where
415        I: IntoIterator<Item = S>,
416        S: Into<String>,
417    {
418        self.bins
419            .replace(bins.into_iter().map(Into::into).collect());
420        self
421    }
422
423    /// Set the list of custom files to add to the appimage package.
424    /// Maps a dir/file to a dir/file inside the appimage package.
425    pub fn files<I, S, T>(mut self, files: I) -> Self
426    where
427        I: IntoIterator<Item = (S, T)>,
428        S: Into<String>,
429        T: Into<String>,
430    {
431        self.files.replace(
432            files
433                .into_iter()
434                .map(|(k, v)| (k.into(), v.into()))
435                .collect(),
436        );
437        self
438    }
439
440    /// Set the map of [`linuxdeploy`](https://github.com/linuxdeploy/linuxdeploy)
441    /// plugin name and its URL to be downloaded and executed while packaing the appimage.
442    /// For example, if you want to use the
443    /// [`gtk`](https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh) plugin,
444    /// you'd specify `gtk` as the key and its url as the value.
445    pub fn linuxdeploy_plugins<I, S, T>(mut self, linuxdeploy_plugins: I) -> Self
446    where
447        I: IntoIterator<Item = (S, T)>,
448        S: Into<String>,
449        T: Into<String>,
450    {
451        self.linuxdeploy_plugins.replace(
452            linuxdeploy_plugins
453                .into_iter()
454                .map(|(k, v)| (k.into(), v.into()))
455                .collect(),
456        );
457        self
458    }
459}
460
461/// The Linux pacman configuration.
462#[derive(Clone, Debug, Default, Deserialize, Serialize)]
463#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
464#[serde(rename_all = "camelCase", deny_unknown_fields)]
465#[non_exhaustive]
466pub struct PacmanConfig {
467    /// List of custom files to add to the pacman package.
468    /// Maps a dir/file to a dir/file inside the pacman package.
469    pub files: Option<HashMap<String, String>>,
470    /// List of softwares that must be installed for the app to build and run.
471    ///
472    /// See : <https://wiki.archlinux.org/title/PKGBUILD#depends>
473    pub depends: Option<Dependencies>,
474    /// Additional packages that are provided by this app.
475    ///
476    /// See : <https://wiki.archlinux.org/title/PKGBUILD#provides>
477    pub provides: Option<Vec<String>>,
478    /// Packages that conflict or cause problems with the app.
479    /// All these packages and packages providing this item will need to be removed
480    ///
481    /// See : <https://wiki.archlinux.org/title/PKGBUILD#conflicts>
482    pub conflicts: Option<Vec<String>>,
483    /// Only use if this app replaces some obsolete packages.
484    /// For example, if you rename any package.
485    ///
486    /// See : <https://wiki.archlinux.org/title/PKGBUILD#replaces>
487    pub replaces: Option<Vec<String>>,
488    /// Source of the package to be stored at PKGBUILD.
489    /// PKGBUILD is a bash script, so version can be referred as ${pkgver}
490    pub source: Option<Vec<String>>,
491}
492
493impl PacmanConfig {
494    /// Creates a new [`PacmanConfig`].
495    pub fn new() -> Self {
496        Self::default()
497    }
498    /// Set the list of custom files to add to the pacman package.
499    /// Maps a dir/file to a dir/file inside the pacman package.
500    pub fn files<I, S, T>(mut self, files: I) -> Self
501    where
502        I: IntoIterator<Item = (S, T)>,
503        S: Into<String>,
504        T: Into<String>,
505    {
506        self.files.replace(
507            files
508                .into_iter()
509                .map(|(k, v)| (k.into(), v.into()))
510                .collect(),
511        );
512        self
513    }
514
515    /// Set the list of pacman dependencies directly using an iterator of strings.
516    pub fn depends<I, S>(mut self, depends: I) -> Self
517    where
518        I: IntoIterator<Item = S>,
519        S: Into<String>,
520    {
521        self.depends.replace(Dependencies::List(
522            depends.into_iter().map(Into::into).collect(),
523        ));
524        self
525    }
526
527    /// Set the list of pacman dependencies indirectly via a path to a file,
528    /// which must contain one dependency (a package name) per line.
529    pub fn depends_path<P>(mut self, path: P) -> Self
530    where
531        P: Into<PathBuf>,
532    {
533        self.depends.replace(Dependencies::Path(path.into()));
534        self
535    }
536
537    /// Set the list of additional packages that are provided by this app.
538    pub fn provides<I, S>(mut self, provides: I) -> Self
539    where
540        I: IntoIterator<Item = S>,
541        S: Into<String>,
542    {
543        self.provides
544            .replace(provides.into_iter().map(Into::into).collect());
545        self
546    }
547    /// Set the list of packages that conflict with the app.
548    pub fn conflicts<I, S>(mut self, conflicts: I) -> Self
549    where
550        I: IntoIterator<Item = S>,
551        S: Into<String>,
552    {
553        self.conflicts
554            .replace(conflicts.into_iter().map(Into::into).collect());
555        self
556    }
557    /// Set the list of obsolete packages that are replaced by this package.
558    pub fn replaces<I, S>(mut self, replaces: I) -> Self
559    where
560        I: IntoIterator<Item = S>,
561        S: Into<String>,
562    {
563        self.replaces
564            .replace(replaces.into_iter().map(Into::into).collect());
565        self
566    }
567    /// Set the list of sources where the package will be stored.
568    pub fn source<I, S>(mut self, source: I) -> Self
569    where
570        I: IntoIterator<Item = S>,
571        S: Into<String>,
572    {
573        self.source
574            .replace(source.into_iter().map(Into::into).collect());
575        self
576    }
577}
578
579/// Position coordinates struct.
580#[derive(Default, Copy, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
581#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
582#[serde(rename_all = "camelCase", deny_unknown_fields)]
583pub struct Position {
584    /// X coordinate.
585    pub x: u32,
586    /// Y coordinate.
587    pub y: u32,
588}
589
590/// Size struct.
591#[derive(Default, Copy, Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
592#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
593#[serde(rename_all = "camelCase", deny_unknown_fields)]
594pub struct Size {
595    /// Width.
596    pub width: u32,
597    /// Height.
598    pub height: u32,
599}
600
601/// The Apple Disk Image (.dmg) configuration.
602#[derive(Clone, Debug, Default, Deserialize, Serialize)]
603#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
604#[serde(rename_all = "camelCase", deny_unknown_fields)]
605#[non_exhaustive]
606pub struct DmgConfig {
607    /// Image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.
608    pub background: Option<PathBuf>,
609    /// Position of volume window on screen.
610    pub window_position: Option<Position>,
611    /// Size of volume window.
612    #[serde(alias = "window-size", alias = "window_size")]
613    pub window_size: Option<Size>,
614    /// Position of application file on window.
615    #[serde(alias = "app-position", alias = "app_position")]
616    pub app_position: Option<Position>,
617    /// Position of application folder on window.
618    #[serde(
619        alias = "application-folder-position",
620        alias = "application_folder_position"
621    )]
622    pub app_folder_position: Option<Position>,
623}
624
625impl DmgConfig {
626    /// Creates a new [`DmgConfig`].
627    pub fn new() -> Self {
628        Self::default()
629    }
630
631    /// Set an image to use as the background in dmg file. Accepted formats: `png`/`jpg`/`gif`.
632    pub fn background<P: Into<PathBuf>>(mut self, path: P) -> Self {
633        self.background.replace(path.into());
634        self
635    }
636
637    /// Set the poosition of volume window on screen.
638    pub fn window_position(mut self, position: Position) -> Self {
639        self.window_position.replace(position);
640        self
641    }
642
643    /// Set the size of volume window.
644    pub fn window_size(mut self, size: Size) -> Self {
645        self.window_size.replace(size);
646        self
647    }
648
649    /// Set the poosition of app file on window.
650    pub fn app_position(mut self, position: Position) -> Self {
651        self.app_position.replace(position);
652        self
653    }
654
655    /// Set the position of application folder on window.
656    pub fn app_folder_position(mut self, position: Position) -> Self {
657        self.app_folder_position.replace(position);
658        self
659    }
660}
661
662/// Notarization authentication credentials.
663#[derive(Clone, Debug)]
664pub enum MacOsNotarizationCredentials {
665    /// Apple ID authentication.
666    AppleId {
667        /// Apple ID.
668        apple_id: OsString,
669        /// Password.
670        password: OsString,
671        /// Team ID.
672        team_id: OsString,
673    },
674    /// App Store Connect API key.
675    ApiKey {
676        /// API key issuer.
677        issuer: OsString,
678        /// API key ID.
679        key_id: OsString,
680        /// Path to the API key file.
681        key_path: PathBuf,
682    },
683    /// Keychain profile with a stored app-specific password for notarytool to use
684    /// Passwords can be generated at https://account.apple.com when signed in with your developer account.
685    /// The password must then be stored in your keychain for notarytool to access,
686    /// using the following, with the appopriate Apple and Team IDs:
687    /// `xcrun notarytool store-credentials --apple-id "name@example.com" --team-id "ABCD123456"`
688    /// This will prompt for a keychain profile name, and the password itself.
689    /// This setting can only be provided as an environment variable "APPLE_KEYCHAIN_PROFILE"
690    KeychainProfile {
691        /// The keychain profile name (as provided when the password was stored using notarytool)
692        keychain_profile: OsString,
693    },
694}
695
696/// The macOS configuration.
697#[derive(Clone, Debug, Default, Deserialize, Serialize)]
698#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
699#[serde(rename_all = "camelCase", deny_unknown_fields)]
700#[non_exhaustive]
701pub struct MacOsConfig {
702    /// MacOS frameworks that need to be packaged with the app.
703    ///
704    /// Each string can either be the name of a framework (without the `.framework` extension, e.g. `"SDL2"`),
705    /// in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`),
706    /// or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`).  Note that this setting just makes cargo-packager copy the specified frameworks into the OS X app bundle
707    /// (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:
708    ///
709    /// - arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)
710    ///
711    /// - embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath "@executable_path/../Frameworks" path/to/binary` after compiling)
712    pub frameworks: Option<Vec<String>>,
713    /// A version string indicating the minimum MacOS version that the packaged app supports (e.g. `"10.11"`).
714    /// If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.
715    #[serde(alias = "minimum-system-version", alias = "minimum_system_version")]
716    pub minimum_system_version: Option<String>,
717    /// The exception domain to use on the macOS .app package.
718    ///
719    /// This allows communication to the outside world e.g. a web server you're shipping.
720    #[serde(alias = "exception-domain", alias = "exception_domain")]
721    pub exception_domain: Option<String>,
722    /// Code signing identity.
723    ///
724    /// This is typically of the form: `"Developer ID Application: TEAM_NAME (TEAM_ID)"`.
725    #[serde(alias = "signing-identity", alias = "signing_identity")]
726    pub signing_identity: Option<String>,
727    /// Codesign certificate (base64 encoded of the p12 file).
728    ///
729    /// Note: this field cannot be specified via a config file or Cargo package metadata.
730    #[serde(skip)]
731    pub signing_certificate: Option<OsString>,
732    /// Password of the codesign certificate.
733    ///
734    /// Note: this field cannot be specified via a config file or Cargo package metadata.
735    #[serde(skip)]
736    pub signing_certificate_password: Option<OsString>,
737    /// Notarization authentication credentials.
738    ///
739    /// Note: this field cannot be specified via a config file or Cargo package metadata.
740    #[serde(skip)]
741    pub notarization_credentials: Option<MacOsNotarizationCredentials>,
742    /// Provider short name for notarization.
743    #[serde(alias = "provider-short-name", alias = "provider_short_name")]
744    pub provider_short_name: Option<String>,
745    /// Path to the entitlements.plist file.
746    pub entitlements: Option<String>,
747    /// Path to the Info.plist file for the package.
748    #[serde(alias = "info-plist-path", alias = "info_plist_path")]
749    pub info_plist_path: Option<PathBuf>,
750    /// Path to the embedded.provisionprofile file for the package.
751    #[serde(
752        alias = "embedded-provisionprofile-path",
753        alias = "embedded_provisionprofile_path"
754    )]
755    pub embedded_provisionprofile_path: Option<PathBuf>,
756    /// Apps that need to be packaged within the app.
757    #[serde(alias = "embedded-apps", alias = "embedded_apps")]
758    pub embedded_apps: Option<Vec<String>>,
759    /// Whether this is a background application. If true, the app will not appear in the Dock.
760    ///
761    /// Sets the `LSUIElement` flag in the macOS plist file.
762    #[serde(default, alias = "background_app", alias = "background-app")]
763    pub background_app: bool,
764}
765
766impl MacOsConfig {
767    /// Creates a new [`MacOsConfig`].
768    pub fn new() -> Self {
769        Self::default()
770    }
771
772    /// MacOS frameworks that need to be packaged with the app.
773    ///
774    /// Each string can either be the name of a framework (without the `.framework` extension, e.g. `"SDL2"`),
775    /// in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`),
776    /// or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`).  Note that this setting just makes cargo-packager copy the specified frameworks into the OS X app bundle
777    /// (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:
778    ///
779    /// - arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)
780    ///
781    /// - embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath "@executable_path/../Frameworks" path/to/binary` after compiling)
782    pub fn frameworks<I, S>(mut self, frameworks: I) -> Self
783    where
784        I: IntoIterator<Item = S>,
785        S: Into<String>,
786    {
787        self.frameworks
788            .replace(frameworks.into_iter().map(Into::into).collect());
789        self
790    }
791
792    /// A version string indicating the minimum MacOS version that the packaged app supports (e.g. `"10.11"`).
793    /// If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.
794    pub fn minimum_system_version<S: Into<String>>(mut self, minimum_system_version: S) -> Self {
795        self.minimum_system_version
796            .replace(minimum_system_version.into());
797        self
798    }
799
800    /// The exception domain to use on the macOS .app package.
801    ///
802    /// This allows communication to the outside world e.g. a web server you're shipping.
803    pub fn exception_domain<S: Into<String>>(mut self, exception_domain: S) -> Self {
804        self.exception_domain.replace(exception_domain.into());
805        self
806    }
807
808    /// Code signing identity.
809    pub fn signing_identity<S: Into<String>>(mut self, signing_identity: S) -> Self {
810        self.signing_identity.replace(signing_identity.into());
811        self
812    }
813
814    /// Provider short name for notarization.
815    pub fn provider_short_name<S: Into<String>>(mut self, provider_short_name: S) -> Self {
816        self.provider_short_name.replace(provider_short_name.into());
817        self
818    }
819
820    /// Path to the entitlements.plist file.
821    pub fn entitlements<S: Into<String>>(mut self, entitlements: S) -> Self {
822        self.entitlements.replace(entitlements.into());
823        self
824    }
825
826    /// Path to the Info.plist file for the package.
827    pub fn info_plist_path<S: Into<PathBuf>>(mut self, info_plist_path: S) -> Self {
828        self.info_plist_path.replace(info_plist_path.into());
829        self
830    }
831
832    /// Path to the embedded.provisionprofile file for the package.
833    pub fn embedded_provisionprofile_path<S: Into<PathBuf>>(
834        mut self,
835        embedded_provisionprofile_path: S,
836    ) -> Self {
837        self.embedded_provisionprofile_path
838            .replace(embedded_provisionprofile_path.into());
839        self
840    }
841
842    /// Apps that need to be packaged within the app.
843    pub fn embedded_apps<I, S>(mut self, embedded_apps: I) -> Self
844    where
845        I: IntoIterator<Item = S>,
846        S: Into<String>,
847    {
848        self.embedded_apps
849            .replace(embedded_apps.into_iter().map(Into::into).collect());
850        self
851    }
852}
853
854/// Linux configuration
855#[derive(Clone, Debug, Default, Deserialize, Serialize)]
856#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
857#[serde(rename_all = "camelCase", deny_unknown_fields)]
858#[non_exhaustive]
859pub struct LinuxConfig {
860    /// Flag to indicate if desktop entry should be generated.
861    #[serde(
862        default = "default_true",
863        alias = "generate-desktop-entry",
864        alias = "generate_desktop_entry"
865    )]
866    pub generate_desktop_entry: bool,
867}
868
869/// A wix language.
870#[derive(Debug, Clone, Deserialize, Serialize)]
871#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
872#[serde(untagged)]
873#[non_exhaustive]
874pub enum WixLanguage {
875    /// Built-in wix language identifier.
876    Identifier(String),
877    /// Custom wix language.
878    Custom {
879        /// Idenitifier of this language, for example `en-US`
880        identifier: String,
881        /// The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.
882        path: Option<PathBuf>,
883    },
884}
885
886impl Default for WixLanguage {
887    fn default() -> Self {
888        Self::Identifier("en-US".into())
889    }
890}
891
892/// The wix format configuration
893#[derive(Clone, Debug, Default, Deserialize, Serialize)]
894#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
895#[serde(rename_all = "camelCase", deny_unknown_fields)]
896#[non_exhaustive]
897pub struct WixConfig {
898    /// The app languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.
899    pub languages: Option<Vec<WixLanguage>>,
900    /// By default, the packager uses an internal template.
901    /// This option allows you to define your own wix file.
902    pub template: Option<PathBuf>,
903    /// List of merge modules to include in your installer.
904    /// For example, if you want to include [C++ Redis merge modules]
905    ///
906    /// [C++ Redis merge modules]: https://wixtoolset.org/docs/v3/howtos/redistributables_and_install_checks/install_vcredist/
907    #[serde(alias = "merge-modules", alias = "merge_modules")]
908    pub merge_modules: Option<Vec<PathBuf>>,
909    /// A list of paths to .wxs files with WiX fragments to use.
910    #[serde(alias = "fragment-paths", alias = "fragment_paths")]
911    pub fragment_paths: Option<Vec<PathBuf>>,
912    /// List of WiX fragments as strings. This is similar to `config.wix.fragments_paths` but
913    /// is a string so you can define it inline in your config.
914    ///
915    /// ```text
916    /// <?xml version="1.0" encoding="utf-8"?>
917    /// <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
918    /// <Fragment>
919    ///     <CustomAction Id="OpenNotepad" Directory="INSTALLDIR" Execute="immediate" ExeCommand="cmd.exe /c notepad.exe" Return="check" />
920    ///     <InstallExecuteSequence>
921    ///         <Custom Action="OpenNotepad" After="InstallInitialize" />
922    ///     </InstallExecuteSequence>
923    /// </Fragment>
924    /// </Wix>
925    /// ```
926    pub fragments: Option<Vec<String>>,
927    /// The ComponentGroup element ids you want to reference from the fragments.
928    #[serde(alias = "component-group-refs", alias = "component_group_refs")]
929    pub component_group_refs: Option<Vec<String>>,
930    /// The Component element ids you want to reference from the fragments.
931    #[serde(alias = "component-refs", alias = "component_refs")]
932    pub component_refs: Option<Vec<String>>,
933    /// The CustomAction element ids you want to reference from the fragments.
934    #[serde(alias = "custom-action-refs", alias = "custom_action_refs")]
935    pub custom_action_refs: Option<Vec<String>>,
936    /// The FeatureGroup element ids you want to reference from the fragments.
937    #[serde(alias = "feature-group-refs", alias = "feature_group_refs")]
938    pub feature_group_refs: Option<Vec<String>>,
939    /// The Feature element ids you want to reference from the fragments.
940    #[serde(alias = "feature-refs", alias = "feature_refs")]
941    pub feature_refs: Option<Vec<String>>,
942    /// The Merge element ids you want to reference from the fragments.
943    #[serde(alias = "merge-refs", alias = "merge_refs")]
944    pub merge_refs: Option<Vec<String>>,
945    /// Path to a bitmap file to use as the installation user interface banner.
946    /// This bitmap will appear at the top of all but the first page of the installer.
947    ///
948    /// The required dimensions are 493px × 58px.
949    #[serde(alias = "banner-path", alias = "banner_path")]
950    pub banner_path: Option<PathBuf>,
951    /// Path to a bitmap file to use on the installation user interface dialogs.
952    /// It is used on the welcome and completion dialogs.
953    /// The required dimensions are 493px × 312px.
954    #[serde(alias = "dialog-image-path", alias = "dialog_image_path")]
955    pub dialog_image_path: Option<PathBuf>,
956    /// Enables FIPS compliant algorithms.
957    #[serde(default, alias = "fips-compliant", alias = "fips_compliant")]
958    pub fips_compliant: bool,
959}
960
961impl WixConfig {
962    /// Creates a new [`WixConfig`].
963    pub fn new() -> Self {
964        Self::default()
965    }
966
967    /// Set the app languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.
968    pub fn languages<I: IntoIterator<Item = WixLanguage>>(mut self, languages: I) -> Self {
969        self.languages.replace(languages.into_iter().collect());
970        self
971    }
972
973    /// By default, the packager uses an internal template.
974    /// This option allows you to define your own wix file.
975    pub fn template<P: Into<PathBuf>>(mut self, template: P) -> Self {
976        self.template.replace(template.into());
977        self
978    }
979
980    /// Set a list of merge modules to include in your installer.
981    /// For example, if you want to include [C++ Redis merge modules]
982    ///
983    /// [C++ Redis merge modules]: https://wixtoolset.org/docs/v3/howtos/redistributables_and_install_checks/install_vcredist/
984    pub fn merge_modules<I, P>(mut self, merge_modules: I) -> Self
985    where
986        I: IntoIterator<Item = P>,
987        P: Into<PathBuf>,
988    {
989        self.merge_modules
990            .replace(merge_modules.into_iter().map(Into::into).collect());
991        self
992    }
993
994    /// Set a list of paths to .wxs files with WiX fragments to use.
995    pub fn fragment_paths<I, S>(mut self, fragment_paths: I) -> Self
996    where
997        I: IntoIterator<Item = S>,
998        S: Into<PathBuf>,
999    {
1000        self.fragment_paths
1001            .replace(fragment_paths.into_iter().map(Into::into).collect());
1002        self
1003    }
1004
1005    /// Set a list of WiX fragments as strings. This is similar to [`WixConfig::fragment_paths`] but
1006    /// is a string so you can define it inline in your config.
1007    ///
1008    /// ```text
1009    /// <?xml version="1.0" encoding="utf-8"?>
1010    /// <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
1011    /// <Fragment>
1012    ///     <CustomAction Id="OpenNotepad" Directory="INSTALLDIR" Execute="immediate" ExeCommand="cmd.exe /c notepad.exe" Return="check" />
1013    ///     <InstallExecuteSequence>
1014    ///         <Custom Action="OpenNotepad" After="InstallInitialize" />
1015    ///     </InstallExecuteSequence>
1016    /// </Fragment>
1017    /// </Wix>
1018    /// ```
1019    pub fn fragments<I, S>(mut self, fragments: I) -> Self
1020    where
1021        I: IntoIterator<Item = S>,
1022        S: Into<String>,
1023    {
1024        self.fragments
1025            .replace(fragments.into_iter().map(Into::into).collect());
1026        self
1027    }
1028
1029    /// Set the ComponentGroup element ids you want to reference from the fragments.
1030    pub fn component_group_refs<I, S>(mut self, component_group_refs: I) -> Self
1031    where
1032        I: IntoIterator<Item = S>,
1033        S: Into<String>,
1034    {
1035        self.component_group_refs
1036            .replace(component_group_refs.into_iter().map(Into::into).collect());
1037        self
1038    }
1039
1040    /// Set the Component element ids you want to reference from the fragments.
1041    pub fn component_refs<I, S>(mut self, component_refs: I) -> Self
1042    where
1043        I: IntoIterator<Item = S>,
1044        S: Into<String>,
1045    {
1046        self.component_refs
1047            .replace(component_refs.into_iter().map(Into::into).collect());
1048        self
1049    }
1050
1051    /// Set the CustomAction element ids you want to reference from the fragments.
1052    pub fn custom_action_refs<I, S>(mut self, custom_action_refs: I) -> Self
1053    where
1054        I: IntoIterator<Item = S>,
1055        S: Into<String>,
1056    {
1057        self.custom_action_refs
1058            .replace(custom_action_refs.into_iter().map(Into::into).collect());
1059        self
1060    }
1061
1062    /// Set he FeatureGroup element ids you want to reference from the fragments.
1063    pub fn feature_group_refs<I, S>(mut self, feature_group_refs: I) -> Self
1064    where
1065        I: IntoIterator<Item = S>,
1066        S: Into<String>,
1067    {
1068        self.feature_group_refs
1069            .replace(feature_group_refs.into_iter().map(Into::into).collect());
1070        self
1071    }
1072
1073    /// Set the Feature element ids you want to reference from the fragments.
1074    pub fn feature_refs<I, S>(mut self, feature_refs: I) -> Self
1075    where
1076        I: IntoIterator<Item = S>,
1077        S: Into<String>,
1078    {
1079        self.feature_refs
1080            .replace(feature_refs.into_iter().map(Into::into).collect());
1081        self
1082    }
1083
1084    /// Set he Merge element ids you want to reference from the fragments.
1085    pub fn merge_refs<I, S>(mut self, merge_refs: I) -> Self
1086    where
1087        I: IntoIterator<Item = S>,
1088        S: Into<String>,
1089    {
1090        self.merge_refs
1091            .replace(merge_refs.into_iter().map(Into::into).collect());
1092        self
1093    }
1094
1095    /// Set the path to a bitmap file to use as the installation user interface banner.
1096    /// This bitmap will appear at the top of all but the first page of the installer.
1097    ///
1098    /// The required dimensions are 493px × 58px.
1099    pub fn banner_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
1100        self.banner_path.replace(path.into());
1101        self
1102    }
1103
1104    /// Set the path to a bitmap file to use on the installation user interface dialogs.
1105    /// It is used on the welcome and completion dialogs.
1106    /// The required dimensions are 493px × 312px.
1107    pub fn dialog_image_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
1108        self.dialog_image_path.replace(path.into());
1109        self
1110    }
1111
1112    /// Set whether to enable or disable FIPS compliant algorithms.
1113    pub fn fips_compliant(mut self, fips_compliant: bool) -> Self {
1114        self.fips_compliant = fips_compliant;
1115        self
1116    }
1117}
1118
1119/// Install Modes for the NSIS installer.
1120#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
1121#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1122#[serde(rename_all = "camelCase", deny_unknown_fields)]
1123#[non_exhaustive]
1124#[derive(Default)]
1125pub enum NSISInstallerMode {
1126    /// Default mode for the installer.
1127    ///
1128    /// Install the app by default in a directory that doesn't require Administrator access.
1129    ///
1130    /// Installer metadata will be saved under the `HKCU` registry path.
1131    #[default]
1132    CurrentUser,
1133    /// Install the app by default in the `Program Files` folder directory requires Administrator
1134    /// access for the installation.
1135    ///
1136    /// Installer metadata will be saved under the `HKLM` registry path.
1137    PerMachine,
1138    /// Combines both modes and allows the user to choose at install time
1139    /// whether to install for the current user or per machine. Note that this mode
1140    /// will require Administrator access even if the user wants to install it for the current user only.
1141    ///
1142    /// Installer metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.
1143    Both,
1144}
1145
1146/// Compression algorithms used in the NSIS installer.
1147///
1148/// See <https://nsis.sourceforge.io/Reference/SetCompressor>
1149#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
1150#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1151#[serde(rename_all = "camelCase")]
1152#[non_exhaustive]
1153pub enum NsisCompression {
1154    /// ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.
1155    Zlib,
1156    /// BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.
1157    Bzip2,
1158    /// LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.
1159    Lzma,
1160    /// Disable compression.
1161    Off,
1162}
1163
1164/// The NSIS format configuration.
1165#[derive(Clone, Debug, Default, Deserialize, Serialize)]
1166#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1167#[serde(rename_all = "camelCase", deny_unknown_fields)]
1168#[non_exhaustive]
1169pub struct NsisConfig {
1170    /// Set the compression algorithm used to compress files in the installer.
1171    ///
1172    /// See <https://nsis.sourceforge.io/Reference/SetCompressor>
1173    pub compression: Option<NsisCompression>,
1174    /// A custom `.nsi` template to use.
1175    ///
1176    /// See the default template here
1177    /// <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>
1178    pub template: Option<PathBuf>,
1179    /// Logic of an NSIS section that will be ran before the install section.
1180    ///
1181    /// See the available libraries, dlls and global variables here
1182    /// <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>
1183    ///
1184    /// ### Example
1185    /// ```toml
1186    /// [package.metadata.packager.nsis]
1187    /// preinstall-section = """
1188    ///     ; Setup custom messages
1189    ///     LangString webview2AbortError ${LANG_ENGLISH} "Failed to install WebView2! The app can't run without it. Try restarting the installer."
1190    ///     LangString webview2DownloadError ${LANG_ARABIC} "خطأ: فشل تنزيل WebView2 - $0"
1191    ///
1192    ///     Section PreInstall
1193    ///      ; <section logic here>
1194    ///     SectionEnd
1195    ///
1196    ///     Section AnotherPreInstall
1197    ///      ; <section logic here>
1198    ///     SectionEnd
1199    /// """
1200    /// ```
1201    #[serde(alias = "preinstall-section", alias = "preinstall_section")]
1202    pub preinstall_section: Option<String>,
1203    /// The path to a bitmap file to display on the header of installers pages.
1204    ///
1205    /// The recommended dimensions are 150px x 57px.
1206    #[serde(alias = "header-image", alias = "header_image")]
1207    pub header_image: Option<PathBuf>,
1208    /// The path to a bitmap file for the Welcome page and the Finish page.
1209    ///
1210    /// The recommended dimensions are 164px x 314px.
1211    #[serde(alias = "sidebar-image", alias = "sidebar_image")]
1212    pub sidebar_image: Option<PathBuf>,
1213    /// The path to an icon file used as the installer icon.
1214    #[serde(alias = "installer-icon", alias = "installer_icon")]
1215    pub installer_icon: Option<PathBuf>,
1216    /// Whether the installation will be for all users or just the current user.
1217    #[serde(default, alias = "installer-mode", alias = "installer_mode")]
1218    pub install_mode: NSISInstallerMode,
1219    /// A list of installer languages.
1220    /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.
1221    /// To allow the user to select the language, set `display_language_selector` to `true`.
1222    ///
1223    /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.
1224    pub languages: Option<Vec<String>>,
1225    /// An key-value pair where the key is the language and the
1226    /// value is the path to a custom `.nsi` file that holds the translated text for cargo-packager's custom messages.
1227    ///
1228    /// See <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/nsis/languages/English.nsh> for an example `.nsi` file.
1229    ///
1230    /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,
1231    #[serde(alias = "custom-language-file", alias = "custom_language_file")]
1232    pub custom_language_files: Option<HashMap<String, PathBuf>>,
1233    /// Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not.
1234    /// By default the OS language is selected, with a fallback to the first language in the `languages` array.
1235    #[serde(
1236        default,
1237        alias = "display-language-selector",
1238        alias = "display_language_selector"
1239    )]
1240    pub display_language_selector: bool,
1241    /// List of paths where your app stores data.
1242    /// This options tells the uninstaller to provide the user with an option
1243    /// (disabled by default) whether they want to rmeove your app data or keep it.
1244    ///
1245    /// The path should use a constant from <https://nsis.sourceforge.io/Docs/Chapter4.html#varconstant>
1246    /// in addition to `$IDENTIFIER`, `$PUBLISHER` and `$PRODUCTNAME`, for example, if you store your
1247    /// app data in `C:\\Users\\<user>\\AppData\\Local\\<your-company-name>\\<your-product-name>`
1248    /// you'd need to specify
1249    /// ```toml
1250    /// [package.metadata.packager.nsis]
1251    /// appdata-paths = ["$LOCALAPPDATA/$PUBLISHER/$PRODUCTNAME"]
1252    /// ```
1253    #[serde(default, alias = "appdata-paths", alias = "appdata_paths")]
1254    pub appdata_paths: Option<Vec<String>>,
1255}
1256
1257impl NsisConfig {
1258    /// Creates a new [`NsisConfig`].
1259    pub fn new() -> Self {
1260        Self::default()
1261    }
1262
1263    /// Set the compression algorithm used to compress files in the installer.
1264    ///
1265    /// See <https://nsis.sourceforge.io/Reference/SetCompressor>
1266    pub fn compression(mut self, compression: NsisCompression) -> Self {
1267        self.compression.replace(compression);
1268        self
1269    }
1270
1271    /// Set a custom `.nsi` template to use.
1272    ///
1273    /// See the default template here
1274    /// <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>
1275    pub fn template<P: Into<PathBuf>>(mut self, template: P) -> Self {
1276        self.template.replace(template.into());
1277        self
1278    }
1279
1280    /// Set the logic of an NSIS section that will be ran before the install section.
1281    ///
1282    /// See the available libraries, dlls and global variables here
1283    /// <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/package/nsis/installer.nsi>
1284    ///
1285    /// ### Example
1286    /// ```toml
1287    /// [package.metadata.packager.nsis]
1288    /// preinstall-section = """
1289    ///     ; Setup custom messages
1290    ///     LangString webview2AbortError ${LANG_ENGLISH} "Failed to install WebView2! The app can't run without it. Try restarting the installer."
1291    ///     LangString webview2DownloadError ${LANG_ARABIC} "خطأ: فشل تنزيل WebView2 - $0"
1292    ///
1293    ///     Section PreInstall
1294    ///      ; <section logic here>
1295    ///     SectionEnd
1296    ///
1297    ///     Section AnotherPreInstall
1298    ///      ; <section logic here>
1299    ///     SectionEnd
1300    /// """
1301    /// ```
1302    pub fn preinstall_section<S: Into<String>>(mut self, preinstall_section: S) -> Self {
1303        self.preinstall_section.replace(preinstall_section.into());
1304        self
1305    }
1306
1307    /// Set the path to a bitmap file to display on the header of installers pages.
1308    ///
1309    /// The recommended dimensions are 150px x 57px.
1310    pub fn header_image<P: Into<PathBuf>>(mut self, header_image: P) -> Self {
1311        self.header_image.replace(header_image.into());
1312        self
1313    }
1314
1315    /// Set the path to a bitmap file for the Welcome page and the Finish page.
1316    ///
1317    /// The recommended dimensions are 164px x 314px.
1318    pub fn sidebar_image<P: Into<PathBuf>>(mut self, sidebar_image: P) -> Self {
1319        self.sidebar_image.replace(sidebar_image.into());
1320        self
1321    }
1322
1323    /// Set the path to an icon file used as the installer icon.
1324    pub fn installer_icon<P: Into<PathBuf>>(mut self, installer_icon: P) -> Self {
1325        self.installer_icon.replace(installer_icon.into());
1326        self
1327    }
1328
1329    /// Set whether the installation will be for all users or just the current user.
1330    pub fn install_mode(mut self, install_mode: NSISInstallerMode) -> Self {
1331        self.install_mode = install_mode;
1332        self
1333    }
1334
1335    /// Set a list of installer languages.
1336    /// By default the OS language is used. If the OS language is not in the list of languages, the first language will be used.
1337    /// To allow the user to select the language, set `display_language_selector` to `true`.
1338    ///
1339    /// See <https://github.com/kichik/nsis/tree/9465c08046f00ccb6eda985abbdbf52c275c6c4d/Contrib/Language%20files> for the complete list of languages.
1340    pub fn languages<I, S>(mut self, languages: I) -> Self
1341    where
1342        I: IntoIterator<Item = S>,
1343        S: Into<String>,
1344    {
1345        self.languages
1346            .replace(languages.into_iter().map(Into::into).collect());
1347        self
1348    }
1349
1350    /// Set a map of key-value pair where the key is the language and the
1351    /// value is the path to a custom `.nsi` file that holds the translated text for cargo-packager's custom messages.
1352    ///
1353    /// See <https://github.com/crabnebula-dev/cargo-packager/blob/main/crates/packager/src/nsis/languages/English.nsh> for an example `.nsi` file.
1354    ///
1355    /// **Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,
1356    pub fn custom_language_files<I, S, P>(mut self, custom_language_files: I) -> Self
1357    where
1358        I: IntoIterator<Item = (S, P)>,
1359        S: Into<String>,
1360        P: Into<PathBuf>,
1361    {
1362        self.custom_language_files.replace(
1363            custom_language_files
1364                .into_iter()
1365                .map(|(k, v)| (k.into(), v.into()))
1366                .collect(),
1367        );
1368        self
1369    }
1370
1371    /// Set wether to display a language selector dialog before the installer and uninstaller windows are rendered or not.
1372    /// By default the OS language is selected, with a fallback to the first language in the `languages` array.
1373    pub fn display_language_selector(mut self, display: bool) -> Self {
1374        self.display_language_selector = display;
1375        self
1376    }
1377
1378    /// Set a list of paths where your app stores data.
1379    /// This options tells the uninstaller to provide the user with an option
1380    /// (disabled by default) whether they want to rmeove your app data or keep it.
1381    ///
1382    /// The path should use a constant from <https://nsis.sourceforge.io/Docs/Chapter4.html#varconstant>
1383    /// in addition to `$IDENTIFIER`, `$PUBLISHER` and `$PRODUCTNAME`, for example, if you store your
1384    /// app data in `C:\\Users\\<user>\\AppData\\Local\\<your-company-name>\\<your-product-name>`
1385    /// you'd need to specify
1386    /// ```toml
1387    /// [package.metadata.packager.nsis]
1388    /// appdata-paths = ["$LOCALAPPDATA/$PUBLISHER/$PRODUCTNAME"]
1389    /// ```
1390    pub fn appdata_paths<I, S>(mut self, appdata_paths: I) -> Self
1391    where
1392        I: IntoIterator<Item = S>,
1393        S: Into<String>,
1394    {
1395        self.appdata_paths
1396            .replace(appdata_paths.into_iter().map(Into::into).collect());
1397        self
1398    }
1399}
1400
1401/// The Windows configuration.
1402#[derive(Clone, Debug, Deserialize, Serialize)]
1403#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1404#[serde(rename_all = "camelCase", deny_unknown_fields)]
1405#[non_exhaustive]
1406pub struct WindowsConfig {
1407    /// The file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.
1408    #[serde(alias = "digest-algorithm", alias = "digest_algorithm")]
1409    pub digest_algorithm: Option<String>,
1410    /// The SHA1 hash of the signing certificate.
1411    #[serde(alias = "certificate-thumbprint", alias = "certificate_thumbprint")]
1412    pub certificate_thumbprint: Option<String>,
1413    /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may
1414    /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.
1415    #[serde(default)]
1416    pub tsp: bool,
1417    /// Server to use during timestamping.
1418    #[serde(alias = "timestamp-url", alias = "timestamp_url")]
1419    pub timestamp_url: Option<String>,
1420    /// Whether to validate a second app installation, blocking the user from installing an older version if set to `false`.
1421    ///
1422    /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.
1423    ///
1424    /// The default value of this flag is `true`.
1425    #[serde(
1426        default = "default_true",
1427        alias = "allow-downgrades",
1428        alias = "allow_downgrades"
1429    )]
1430    pub allow_downgrades: bool,
1431
1432    /// Specify a custom command to sign the binaries.
1433    /// This command needs to have a `%1` in it which is just a placeholder for the binary path,
1434    /// which we will detect and replace before calling the command.
1435    ///
1436    /// By Default we use `signtool.exe` which can be found only on Windows so
1437    /// if you are on another platform and want to cross-compile and sign you will
1438    /// need to use another tool like `osslsigncode`.
1439    #[serde(alias = "sign-command", alias = "sign_command")]
1440    pub sign_command: Option<String>,
1441}
1442
1443impl Default for WindowsConfig {
1444    fn default() -> Self {
1445        Self {
1446            digest_algorithm: None,
1447            certificate_thumbprint: None,
1448            timestamp_url: None,
1449            tsp: false,
1450            allow_downgrades: true,
1451            sign_command: None,
1452        }
1453    }
1454}
1455
1456impl WindowsConfig {
1457    /// Creates a new [`WindowsConfig`].
1458    pub fn new() -> Self {
1459        Self::default()
1460    }
1461
1462    /// Set the file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.
1463    pub fn digest_algorithm<S: Into<String>>(mut self, digest_algorithm: S) -> Self {
1464        self.digest_algorithm.replace(digest_algorithm.into());
1465        self
1466    }
1467
1468    /// Set the SHA1 hash of the signing certificate.
1469    pub fn certificate_thumbprint<S: Into<String>>(mut self, certificate_thumbprint: S) -> Self {
1470        self.certificate_thumbprint
1471            .replace(certificate_thumbprint.into());
1472        self
1473    }
1474
1475    /// Set whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may
1476    /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.
1477    pub fn tsp(mut self, tsp: bool) -> Self {
1478        self.tsp = tsp;
1479        self
1480    }
1481
1482    /// Set server url to use during timestamping.
1483    pub fn timestamp_url<S: Into<String>>(mut self, timestamp_url: S) -> Self {
1484        self.timestamp_url.replace(timestamp_url.into());
1485        self
1486    }
1487
1488    /// Set whether to validate a second app installation, blocking the user from installing an older version if set to `false`.
1489    ///
1490    /// For instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.
1491    ///
1492    /// The default value of this flag is `true`.
1493    pub fn allow_downgrades(mut self, allow: bool) -> Self {
1494        self.allow_downgrades = allow;
1495        self
1496    }
1497}
1498
1499/// An enum representing the available verbosity levels of the logger.
1500#[derive(Deserialize, Serialize)]
1501#[repr(usize)]
1502#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1503#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1504#[serde(rename_all = "camelCase", deny_unknown_fields)]
1505#[derive(Default)]
1506pub enum LogLevel {
1507    /// The "error" level.
1508    ///
1509    /// Designates very serious errors.
1510    #[default]
1511    Error = 1,
1512    /// The "warn" level.
1513    ///
1514    /// Designates hazardous situations.
1515    Warn,
1516    /// The "info" level.
1517    ///
1518    /// Designates useful information.
1519    Info,
1520    /// The "debug" level.
1521    ///
1522    /// Designates lower priority information.
1523    Debug,
1524    /// The "trace" level.
1525    ///
1526    /// Designates very low priority, often extremely verbose, information.
1527    Trace,
1528}
1529
1530/// A binary to package within the final package.
1531#[derive(Debug, Clone, Deserialize, Serialize)]
1532#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1533#[serde(rename_all = "camelCase", deny_unknown_fields)]
1534#[non_exhaustive]
1535pub struct Binary {
1536    /// Path to the binary (without `.exe` on Windows).
1537    /// If it's relative, it will be resolved from [`Config::out_dir`].
1538    pub path: PathBuf,
1539    /// Whether this is the main binary or not
1540    #[serde(default)]
1541    pub main: bool,
1542}
1543
1544impl Binary {
1545    /// Creates a new [`Binary`] from a path to the binary (without `.exe` on Windows).
1546    /// If it's relative, it will be resolved from [`Config::out_dir`].
1547    pub fn new<P: Into<PathBuf>>(path: P) -> Self {
1548        Self {
1549            path: path.into(),
1550            main: false,
1551        }
1552    }
1553
1554    /// Set the path of the binary.
1555    pub fn path<P: Into<PathBuf>>(mut self, path: P) -> Self {
1556        self.path = path.into();
1557        self
1558    }
1559
1560    /// Set the binary as main binary.
1561    pub fn main(mut self, main: bool) -> Self {
1562        self.main = main;
1563        self
1564    }
1565}
1566
1567/// A path to a resource (with optional glob pattern)
1568/// or an object of `src` and `target` paths.
1569#[derive(Debug, Clone, Deserialize, Serialize)]
1570#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1571#[serde(untagged)]
1572#[non_exhaustive]
1573pub enum Resource {
1574    /// Supports glob patterns
1575    Single(String),
1576    /// An object descriping the src file or directory
1577    /// and its target location in the final package.
1578    Mapped {
1579        /// The src file or directory, supports glob patterns.
1580        src: String,
1581        /// A relative path from the root of the final package.
1582        ///
1583        /// If `src` is a glob, this will always be treated as a directory
1584        /// where all globbed files will be placed under.
1585        target: PathBuf,
1586    },
1587}
1588
1589/// Describes a shell command to be executed when a CLI hook is triggered.
1590#[derive(Debug, Clone, Deserialize, Serialize)]
1591#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1592#[serde(untagged)]
1593#[non_exhaustive]
1594pub enum HookCommand {
1595    /// Run the given script with the default options.
1596    Script(String),
1597    /// Run the given script with custom options.
1598    ScriptWithOptions {
1599        /// The script to execute.
1600        script: String,
1601        /// The working directory.
1602        dir: Option<String>,
1603    },
1604}
1605
1606/// The packaging config.
1607#[derive(Deserialize, Serialize, Default, Debug, Clone)]
1608#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1609#[serde(rename_all = "camelCase", deny_unknown_fields)]
1610pub struct Config {
1611    /// The JSON schema for the config.
1612    ///
1613    /// Setting this field has no effect, this just exists so
1614    /// we can parse the JSON correctly when it has `$schema` field set.
1615    #[serde(rename = "$schema")]
1616    schema: Option<String>,
1617    /// The app name, this is just an identifier that could be used
1618    /// to filter which app to package using `--packages` cli arg when there is multiple apps in the
1619    /// workspace or in the same config.
1620    ///
1621    /// This field resembles, the `name` field in `Cargo.toml` or `package.json`
1622    ///
1623    /// If `unset`, the CLI will try to auto-detect it from `Cargo.toml` or
1624    /// `package.json` otherwise, it will keep it unset.
1625    pub(crate) name: Option<String>,
1626    /// Whether this config is enabled or not. Defaults to `true`.
1627    #[serde(default = "default_true")]
1628    pub(crate) enabled: bool,
1629    /// The package's product name, for example "My Awesome App".
1630    #[serde(default, alias = "product-name", alias = "product_name")]
1631    pub product_name: String,
1632    /// The package's version.
1633    #[serde(default)]
1634    pub version: String,
1635    /// The binaries to package.
1636    #[serde(default)]
1637    pub binaries: Vec<Binary>,
1638    /// The application identifier in reverse domain name notation (e.g. `com.packager.example`).
1639    /// This string must be unique across applications since it is used in some system configurations.
1640    /// This string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-),
1641    /// and periods (.).
1642    #[cfg_attr(feature = "schema", schemars(regex(pattern = r"^[a-zA-Z0-9-\.]*$")))]
1643    pub identifier: Option<String>,
1644    /// The command to run before starting to package an application.
1645    ///
1646    /// This runs only once.
1647    #[serde(alias = "before-packaging-command", alias = "before_packaging_command")]
1648    pub before_packaging_command: Option<HookCommand>,
1649    /// The command to run before packaging each format for an application.
1650    ///
1651    /// This will run multiple times depending on the formats specifed.
1652    #[serde(
1653        alias = "before-each-package-command",
1654        alias = "before_each_package_command"
1655    )]
1656    pub before_each_package_command: Option<HookCommand>,
1657    /// The logging level.
1658    #[serde(alias = "log-level", alias = "log_level")]
1659    pub log_level: Option<LogLevel>,
1660    /// The packaging formats to create, if not present, [`PackageFormat::platform_default`] is used.
1661    pub formats: Option<Vec<PackageFormat>>,
1662    /// The directory where the generated packages will be placed.
1663    ///
1664    /// If [`Config::binaries_dir`] is not set, this is also where the [`Config::binaries`] exist.
1665    #[serde(default, alias = "out-dir", alias = "out_dir")]
1666    pub out_dir: PathBuf,
1667    /// The directory where the [`Config::binaries`] exist.
1668    ///
1669    /// Defaults to [`Config::out_dir`].
1670    #[serde(default, alias = "binaries-dir", alias = "binaries_dir")]
1671    pub binaries_dir: Option<PathBuf>,
1672    /// The target triple we are packaging for.
1673    ///
1674    /// Defaults to the current OS target triple.
1675    #[serde(alias = "target-triple", alias = "target_triple")]
1676    pub target_triple: Option<String>,
1677    /// The package's description.
1678    pub description: Option<String>,
1679    /// The app's long description.
1680    #[serde(alias = "long-description", alias = "long_description")]
1681    pub long_description: Option<String>,
1682    /// The package's homepage.
1683    pub homepage: Option<String>,
1684    /// The package's authors.
1685    #[serde(default)]
1686    pub authors: Option<Vec<String>>,
1687    /// The app's publisher. Defaults to the second element in [`Config::identifier`](Config::identifier) string.
1688    /// Currently maps to the Manufacturer property of the Windows Installer.
1689    pub publisher: Option<String>,
1690    /// A path to the license file.
1691    #[serde(alias = "license-file", alias = "license_file")]
1692    pub license_file: Option<PathBuf>,
1693    /// The app's copyright.
1694    pub copyright: Option<String>,
1695    /// The app's category.
1696    pub category: Option<AppCategory>,
1697    /// The app's icon list. Supports glob patterns.
1698    pub icons: Option<Vec<String>>,
1699    /// The file associations
1700    #[serde(alias = "file-associations", alias = "file_associations")]
1701    pub file_associations: Option<Vec<FileAssociation>>,
1702    /// Deep-link protocols.
1703    #[serde(alias = "deep-link-protocols", alias = "deep_link_protocols")]
1704    pub deep_link_protocols: Option<Vec<DeepLinkProtocol>>,
1705    /// The app's resources to package. This a list of either a glob pattern, path to a file, path to a directory
1706    /// or an object of `src` and `target` paths. In the case of using an object,
1707    /// the `src` could be either a glob pattern, path to a file, path to a directory,
1708    /// and the `target` is a path inside the final resources folder in the installed package.
1709    ///
1710    /// ## Format-specific:
1711    ///
1712    /// - **[PackageFormat::Nsis] / [PackageFormat::Wix]**: The resources are placed next to the executable in the root of the packager.
1713    /// - **[PackageFormat::Deb]**: The resources are placed in `usr/lib` of the package.
1714    pub resources: Option<Vec<Resource>>,
1715    /// Paths to external binaries to add to the package.
1716    ///
1717    /// The path specified should not include `-<target-triple><.exe>` suffix,
1718    /// it will be auto-added when by the packager when reading these paths,
1719    /// so the actual binary name should have the target platform's target triple appended,
1720    /// as well as `.exe` for Windows.
1721    ///
1722    /// For example, if you're packaging an external binary called `sqlite3`, the packager expects
1723    /// a binary named `sqlite3-x86_64-unknown-linux-gnu` on linux,
1724    /// and `sqlite3-x86_64-pc-windows-gnu.exe` on windows.
1725    ///
1726    /// If you are building a universal binary for MacOS, the packager expects
1727    /// your external binary to also be universal, and named after the target triple,
1728    /// e.g. `sqlite3-universal-apple-darwin`. See
1729    /// <https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary>
1730    #[serde(alias = "external-binaries", alias = "external_binaries")]
1731    pub external_binaries: Option<Vec<PathBuf>>,
1732    /// Windows-specific configuration.
1733    pub windows: Option<WindowsConfig>,
1734    /// MacOS-specific configuration.
1735    pub macos: Option<MacOsConfig>,
1736    /// Linux-specific configuration
1737    pub linux: Option<LinuxConfig>,
1738    /// Debian-specific configuration.
1739    pub deb: Option<DebianConfig>,
1740    /// AppImage configuration.
1741    pub appimage: Option<AppImageConfig>,
1742    /// Pacman configuration.
1743    pub pacman: Option<PacmanConfig>,
1744    /// WiX configuration.
1745    pub wix: Option<WixConfig>,
1746    /// Nsis configuration.
1747    pub nsis: Option<NsisConfig>,
1748    /// Dmg configuration.
1749    pub dmg: Option<DmgConfig>,
1750}
1751
1752impl Config {
1753    /// Creates a new [`ConfigBuilder`].
1754    pub fn builder() -> ConfigBuilder {
1755        ConfigBuilder::default()
1756    }
1757
1758    /// Returns the [windows](Config::windows) specific configuration.
1759    pub fn windows(&self) -> Option<&WindowsConfig> {
1760        self.windows.as_ref()
1761    }
1762
1763    /// Returns the [macos](Config::macos) specific configuration.
1764    pub fn macos(&self) -> Option<&MacOsConfig> {
1765        self.macos.as_ref()
1766    }
1767
1768    /// Returns the [linux](Config::linux) specific configuration.
1769    pub fn linux(&self) -> Option<&LinuxConfig> {
1770        self.linux.as_ref()
1771    }
1772
1773    /// Returns the [nsis](Config::nsis) specific configuration.
1774    pub fn nsis(&self) -> Option<&NsisConfig> {
1775        self.nsis.as_ref()
1776    }
1777
1778    /// Returns the [wix](Config::wix) specific configuration.
1779    pub fn wix(&self) -> Option<&WixConfig> {
1780        self.wix.as_ref()
1781    }
1782
1783    /// Returns the [debian](Config::deb) specific configuration.
1784    pub fn deb(&self) -> Option<&DebianConfig> {
1785        self.deb.as_ref()
1786    }
1787
1788    /// Returns the [appimage](Config::appimage) specific configuration.
1789    pub fn appimage(&self) -> Option<&AppImageConfig> {
1790        self.appimage.as_ref()
1791    }
1792
1793    /// Returns the [pacman](Config::pacman) specific configuration.
1794    pub fn pacman(&self) -> Option<&PacmanConfig> {
1795        self.pacman.as_ref()
1796    }
1797
1798    /// Returns the [dmg](Config::dmg) specific configuration.
1799    pub fn dmg(&self) -> Option<&DmgConfig> {
1800        self.dmg.as_ref()
1801    }
1802
1803    /// Returns the target triple of this config, if not set, fallsback to the current OS target triple.
1804    pub fn target_triple(&self) -> String {
1805        self.target_triple.clone().unwrap_or_else(|| {
1806            util::target_triple().expect("Failed to detect current target triple")
1807        })
1808    }
1809
1810    /// Returns the architecture for the package to be built (e.g. "arm", "x86" or "x86_64").
1811    pub fn target_arch(&self) -> crate::Result<&str> {
1812        let target = self.target_triple();
1813        Ok(if target.starts_with("x86_64") {
1814            "x86_64"
1815        } else if target.starts_with('i') {
1816            "x86"
1817        } else if target.starts_with("arm") {
1818            "arm"
1819        } else if target.starts_with("aarch64") {
1820            "aarch64"
1821        } else if target.starts_with("universal") {
1822            "universal"
1823        } else {
1824            return Err(crate::Error::UnexpectedTargetTriple(target));
1825        })
1826    }
1827
1828    /// Returns the path to the specified binary.
1829    pub fn binary_path(&self, binary: &Binary) -> PathBuf {
1830        if binary.path.is_absolute() {
1831            binary.path.clone()
1832        } else {
1833            self.binaries_dir().join(&binary.path)
1834        }
1835    }
1836
1837    /// Returns the package identifier. Defaults an empty string.
1838    pub fn identifier(&self) -> &str {
1839        self.identifier.as_deref().unwrap_or("")
1840    }
1841
1842    /// Returns the package publisher.
1843    /// Defaults to the second element in [`Config::identifier`](Config::identifier()).
1844    pub fn publisher(&self) -> String {
1845        self.publisher.clone().unwrap_or_else(|| {
1846            self.identifier()
1847                .split('.')
1848                .nth(1)
1849                .unwrap_or(self.identifier())
1850                .into()
1851        })
1852    }
1853
1854    /// Returns the out dir. Defaults to the current directory.
1855    pub fn out_dir(&self) -> PathBuf {
1856        if self.out_dir.as_os_str().is_empty() {
1857            return std::env::current_dir().expect("failed to resolve cwd");
1858        }
1859
1860        if !self.out_dir.exists() {
1861            fs::create_dir_all(&self.out_dir).expect("failed to create output directory");
1862        }
1863        dunce::canonicalize(&self.out_dir).unwrap_or_else(|_| self.out_dir.clone())
1864    }
1865
1866    /// Returns the binaries dir. Defaults to [`Self::out_dir`] if [`Self::binaries_dir`] is not set.
1867    pub fn binaries_dir(&self) -> PathBuf {
1868        if let Some(path) = &self.binaries_dir {
1869            dunce::canonicalize(path).unwrap_or_else(|_| path.clone())
1870        } else {
1871            self.out_dir()
1872        }
1873    }
1874
1875    /// Returns the main binary.
1876    pub fn main_binary(&self) -> crate::Result<&Binary> {
1877        self.binaries
1878            .iter()
1879            .find(|bin| bin.main)
1880            .ok_or_else(|| crate::Error::MainBinaryNotFound)
1881    }
1882
1883    /// Returns a mutable reference to the main binary.
1884    pub fn main_binary_mut(&mut self) -> crate::Result<&mut Binary> {
1885        self.binaries
1886            .iter_mut()
1887            .find(|bin| bin.main)
1888            .ok_or_else(|| crate::Error::MainBinaryNotFound)
1889    }
1890
1891    /// Returns the main binary name.
1892    pub fn main_binary_name(&self) -> crate::Result<String> {
1893        self.binaries
1894            .iter()
1895            .find(|bin| bin.main)
1896            .map(|b| b.path.file_stem().unwrap().to_string_lossy().into_owned())
1897            .ok_or_else(|| crate::Error::MainBinaryNotFound)
1898    }
1899
1900    /// Returns all icons path.
1901    pub fn icons(&self) -> crate::Result<Option<Vec<PathBuf>>> {
1902        let Some(patterns) = &self.icons else {
1903            return Ok(None);
1904        };
1905        let mut paths = Vec::new();
1906        for pattern in patterns {
1907            for icon_path in glob::glob(pattern)? {
1908                paths.push(icon_path?);
1909            }
1910        }
1911        Ok(Some(paths))
1912    }
1913}
1914
1915#[derive(Debug, Clone)]
1916pub(crate) struct ResolvedResource {
1917    pub src: PathBuf,
1918    pub target: PathBuf,
1919}
1920
1921impl Config {
1922    #[inline]
1923    pub(crate) fn resources_from_dir(
1924        src_dir: &Path,
1925        target_dir: &Path,
1926    ) -> crate::Result<Vec<ResolvedResource>> {
1927        let mut out = Vec::new();
1928        for entry in walkdir::WalkDir::new(src_dir) {
1929            let entry = entry?;
1930            let path = entry.path();
1931            if path.is_file() {
1932                let relative = path.relative_to(src_dir)?.to_path("");
1933                let src = dunce::canonicalize(path)
1934                    .map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;
1935                let resource = ResolvedResource {
1936                    src,
1937                    target: target_dir.join(relative),
1938                };
1939                out.push(resource);
1940            }
1941        }
1942        Ok(out)
1943    }
1944
1945    #[inline]
1946    pub(crate) fn resources_from_glob(glob: &str) -> crate::Result<Vec<ResolvedResource>> {
1947        let mut out = Vec::new();
1948        for src in glob::glob(glob)? {
1949            let src = src?;
1950            let src = dunce::canonicalize(&src).map_err(|e| Error::IoWithPath(src, e))?;
1951            let target = PathBuf::from(src.file_name().unwrap_or_default());
1952            out.push(ResolvedResource { src, target })
1953        }
1954        Ok(out)
1955    }
1956
1957    pub(crate) fn resources(&self) -> crate::Result<Vec<ResolvedResource>> {
1958        if let Some(resources) = &self.resources {
1959            let mut out = Vec::new();
1960            for r in resources {
1961                match r {
1962                    Resource::Single(src) => {
1963                        let src_dir = PathBuf::from(src);
1964                        if src_dir.is_dir() {
1965                            let target_dir = Path::new(src_dir.file_name().unwrap_or_default());
1966                            out.extend(Self::resources_from_dir(&src_dir, target_dir)?);
1967                        } else {
1968                            out.extend(Self::resources_from_glob(src)?);
1969                        }
1970                    }
1971                    Resource::Mapped { src, target } => {
1972                        let src_path = PathBuf::from(src);
1973                        let target_dir = sanitize_path(target);
1974                        if src_path.is_dir() {
1975                            out.extend(Self::resources_from_dir(&src_path, &target_dir)?);
1976                        } else if src_path.is_file() {
1977                            let src = dunce::canonicalize(&src_path)
1978                                .map_err(|e| Error::IoWithPath(src_path, e))?;
1979                            out.push(ResolvedResource {
1980                                src,
1981                                target: sanitize_path(target),
1982                            });
1983                        } else {
1984                            let globbed_res = Self::resources_from_glob(src)?;
1985                            let retargetd_res = globbed_res.into_iter().map(|mut r| {
1986                                r.target = target_dir.join(r.target);
1987                                r
1988                            });
1989                            out.extend(retargetd_res);
1990                        }
1991                    }
1992                }
1993            }
1994
1995            Ok(out)
1996        } else {
1997            Ok(vec![])
1998        }
1999    }
2000
2001    #[allow(unused)]
2002    pub(crate) fn find_ico(&self) -> crate::Result<Option<PathBuf>> {
2003        let icon = self
2004            .icons()?
2005            .as_ref()
2006            .and_then(|icons| {
2007                icons
2008                    .iter()
2009                    .find(|i| PathBuf::from(i).extension().and_then(|s| s.to_str()) == Some("ico"))
2010                    .or_else(|| {
2011                        icons.iter().find(|i| {
2012                            PathBuf::from(i).extension().and_then(|s| s.to_str()) == Some("png")
2013                        })
2014                    })
2015            })
2016            .map(PathBuf::from);
2017        Ok(icon)
2018    }
2019
2020    #[allow(unused)]
2021    pub(crate) fn copy_resources(&self, path: &Path) -> crate::Result<()> {
2022        for resource in self.resources()? {
2023            let dest = path.join(resource.target);
2024            fs::create_dir_all(
2025                dest.parent()
2026                    .ok_or_else(|| crate::Error::ParentDirNotFound(dest.to_path_buf()))?,
2027            )?;
2028            fs::copy(&resource.src, &dest)
2029                .map_err(|e| Error::CopyFile(resource.src.clone(), dest.clone(), e))?;
2030        }
2031        Ok(())
2032    }
2033
2034    #[allow(unused)]
2035    pub(crate) fn copy_external_binaries(&self, path: &Path) -> crate::Result<Vec<PathBuf>> {
2036        let mut paths = Vec::new();
2037        if let Some(external_binaries) = &self.external_binaries {
2038            let cwd = std::env::current_dir()?;
2039            let target_triple = self.target_triple();
2040            for src in external_binaries {
2041                let file_name = src
2042                    .file_name()
2043                    .ok_or_else(|| crate::Error::FailedToExtractFilename(src.clone()))?
2044                    .to_string_lossy();
2045                #[cfg(windows)]
2046                let src = src.with_file_name(format!("{file_name}-{target_triple}.exe"));
2047                #[cfg(not(windows))]
2048                let src = src.with_file_name(format!("{file_name}-{target_triple}"));
2049                #[cfg(windows)]
2050                let dest = path.join(format!("{file_name}.exe"));
2051                #[cfg(not(windows))]
2052                let dest = path.join(&*file_name);
2053                fs::copy(&src, &dest).map_err(|e| Error::CopyFile(src.clone(), dest.clone(), e))?;
2054                paths.push(dest);
2055            }
2056        }
2057
2058        Ok(paths)
2059    }
2060}
2061
2062fn sanitize_path<P: AsRef<Path>>(path: P) -> PathBuf {
2063    let mut dest = PathBuf::new();
2064    for c in path.as_ref().components() {
2065        if let std::path::Component::Normal(s) = c {
2066            dest.push(s)
2067        }
2068    }
2069    dest
2070}
2071
2072fn default_true() -> bool {
2073    true
2074}