1mod category;
7#[cfg(target_os = "linux")]
8mod linux;
9#[cfg(target_os = "macos")]
10mod macos;
11mod platform;
12mod settings;
13mod updater_bundle;
14mod windows;
15
16use tauri_utils::display_path;
17
18pub use self::{
19 category::AppCategory,
20 settings::{
21 AppImageSettings, BundleBinary, BundleSettings, CustomSignCommandSettings, DebianSettings,
22 DmgSettings, MacOsSettings, PackageSettings, PackageType, Position, RpmSettings, Settings,
23 SettingsBuilder, Size, UpdaterSettings,
24 },
25};
26#[cfg(target_os = "macos")]
27use anyhow::Context;
28pub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings};
29
30use std::{fmt::Write, path::PathBuf};
31
32#[derive(Debug)]
34pub struct Bundle {
35 pub package_type: PackageType,
37 pub bundle_paths: Vec<PathBuf>,
39}
40
41pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
44 let mut package_types = settings.package_types()?;
45 if package_types.is_empty() {
46 return Ok(Vec::new());
47 }
48
49 package_types.sort_by_key(|a| a.priority());
50
51 let target_os = settings
52 .target()
53 .split('-')
54 .nth(2)
55 .unwrap_or(std::env::consts::OS)
56 .replace("darwin", "macos");
57
58 if target_os != std::env::consts::OS {
59 log::warn!("Cross-platform compilation is experimental and does not support all features. Please use a matching host system for full compatibility.");
60 }
61
62 if target_os == "windows" {
64 if settings.can_sign() {
65 for bin in settings.binaries() {
66 let bin_path = settings.binary_path(bin);
67 windows::sign::try_sign(&bin_path, settings)?;
68 }
69
70 for bin in settings.external_binaries() {
72 let path = bin?;
73 let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").is_ok_and(|v| v == "true");
74 if skip {
75 continue;
76 }
77
78 #[cfg(windows)]
79 if windows::sign::verify(&path)? {
80 log::info!(
81 "sidecar at \"{}\" already signed. Skipping...",
82 path.display()
83 );
84 continue;
85 }
86
87 windows::sign::try_sign(&path, settings)?;
88 }
89 } else {
90 #[cfg(not(target_os = "windows"))]
91 log::warn!("Signing, by default, is only supported on Windows hosts, but you can specify a custom signing command in `bundler > windows > sign_command`, for now, skipping signing the installer...");
92 }
93 }
94
95 let mut bundles = Vec::<Bundle>::new();
96 for package_type in &package_types {
97 if bundles.iter().any(|b| b.package_type == *package_type) {
99 continue;
100 }
101
102 let bundle_paths = match package_type {
103 #[cfg(target_os = "macos")]
104 PackageType::MacOsBundle => macos::app::bundle_project(settings)?,
105 #[cfg(target_os = "macos")]
106 PackageType::IosBundle => macos::ios::bundle_project(settings)?,
107 #[cfg(target_os = "macos")]
109 PackageType::Dmg => {
110 let bundled = macos::dmg::bundle_project(settings, &bundles)?;
111 if !bundled.app.is_empty() {
112 bundles.push(Bundle {
113 package_type: PackageType::MacOsBundle,
114 bundle_paths: bundled.app,
115 });
116 }
117 bundled.dmg
118 }
119
120 #[cfg(target_os = "windows")]
121 PackageType::WindowsMsi => windows::msi::bundle_project(settings, false)?,
122 PackageType::Nsis => windows::nsis::bundle_project(settings, false)?,
123
124 #[cfg(target_os = "linux")]
125 PackageType::Deb => linux::debian::bundle_project(settings)?,
126 #[cfg(target_os = "linux")]
127 PackageType::Rpm => linux::rpm::bundle_project(settings)?,
128 #[cfg(target_os = "linux")]
129 PackageType::AppImage => linux::appimage::bundle_project(settings)?,
130 _ => {
131 log::warn!("ignoring {}", package_type.short_name());
132 continue;
133 }
134 };
135
136 bundles.push(Bundle {
137 package_type: package_type.to_owned(),
138 bundle_paths,
139 });
140 }
141
142 if let Some(updater) = settings.updater() {
143 if package_types.iter().any(|package_type| {
144 if updater.v1_compatible {
145 matches!(
146 package_type,
147 PackageType::AppImage
148 | PackageType::MacOsBundle
149 | PackageType::Nsis
150 | PackageType::WindowsMsi
151 | PackageType::Deb
152 )
153 } else {
154 matches!(package_type, PackageType::MacOsBundle)
155 }
156 }) {
157 let updater_paths = updater_bundle::bundle_project(settings, &bundles)?;
158 bundles.push(Bundle {
159 package_type: PackageType::Updater,
160 bundle_paths: updater_paths,
161 });
162 } else if updater.v1_compatible
163 || !package_types.iter().any(|package_type| {
164 matches!(
166 package_type,
167 PackageType::AppImage | PackageType::Nsis | PackageType::WindowsMsi | PackageType::Deb
168 )
169 })
170 {
171 log::warn!("The bundler was configured to create updater artifacts but no updater-enabled targets were built. Please enable one of these targets: app, appimage, msi, nsis");
172 }
173 if updater.v1_compatible {
174 log::warn!("Legacy v1 compatible updater is deprecated and will be removed in v3, change bundle > createUpdaterArtifacts to true when your users are updated to the version with v2 updater plugin");
175 }
176 }
177
178 #[cfg(target_os = "macos")]
179 {
180 if !package_types.contains(&PackageType::MacOsBundle) {
182 if let Some(app_bundle_paths) = bundles
183 .iter()
184 .position(|b| b.package_type == PackageType::MacOsBundle)
185 .map(|i| bundles.remove(i))
186 .map(|b| b.bundle_paths)
187 {
188 for app_bundle_path in &app_bundle_paths {
189 log::info!(action = "Cleaning"; "{}", app_bundle_path.display());
190 match app_bundle_path.is_dir() {
191 true => std::fs::remove_dir_all(app_bundle_path),
192 false => std::fs::remove_file(app_bundle_path),
193 }
194 .with_context(|| {
195 format!(
196 "Failed to clean the app bundle at {}",
197 app_bundle_path.display()
198 )
199 })?
200 }
201 }
202 }
203 }
204
205 if bundles.is_empty() {
206 return Err(anyhow::anyhow!("No bundles were built").into());
207 }
208
209 let bundles_wo_updater = bundles
210 .iter()
211 .filter(|b| b.package_type != PackageType::Updater)
212 .collect::<Vec<_>>();
213 let finished_bundles = bundles_wo_updater.len();
214 let pluralised = if finished_bundles == 1 {
215 "bundle"
216 } else {
217 "bundles"
218 };
219
220 let mut printable_paths = String::new();
221 for bundle in &bundles {
222 for path in &bundle.bundle_paths {
223 let note = if bundle.package_type == crate::PackageType::Updater {
224 " (updater)"
225 } else {
226 ""
227 };
228 let path_display = display_path(path);
229 writeln!(printable_paths, " {path_display}{note}").unwrap();
230 }
231 }
232
233 log::info!(action = "Finished"; "{finished_bundles} {pluralised} at:\n{printable_paths}");
234
235 Ok(bundles)
236}
237
238pub fn check_icons(settings: &Settings) -> crate::Result<bool> {
240 let mut iter = settings.icon_files().peekable();
242
243 if iter.peek().is_none() {
245 Ok(false)
246 } else {
247 Ok(true)
248 }
249}