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}