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, platform::Target as TargetPlatform};
17
18fn patch_binary(binary: &PathBuf, package_type: &PackageType) -> crate::Result<()> {
20 match package_type {
21 #[cfg(target_os = "linux")]
22 PackageType::AppImage | PackageType::Deb | PackageType::Rpm => {
23 log::info!(
24 "Patching binary {:?} for type {}",
25 binary,
26 package_type.short_name()
27 );
28 linux::patch_binary(binary, package_type)?;
29 }
30 PackageType::Nsis | PackageType::WindowsMsi => {
31 log::info!(
32 "Patching binary {:?} for type {}",
33 binary,
34 package_type.short_name()
35 );
36 windows::patch_binary(binary, package_type)?;
37 }
38 _ => (),
39 }
40
41 Ok(())
42}
43
44pub use self::{
45 category::AppCategory,
46 settings::{
47 AppImageSettings, BundleBinary, BundleSettings, CustomSignCommandSettings, DebianSettings,
48 DmgSettings, Entitlements, IosSettings, MacOsSettings, PackageSettings, PackageType, PlistKind,
49 Position, RpmSettings, Settings, SettingsBuilder, Size, UpdaterSettings,
50 },
51};
52pub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings};
53
54use std::{
55 fmt::Write,
56 io::{Seek, SeekFrom},
57 path::PathBuf,
58};
59
60#[derive(Debug)]
62pub struct Bundle {
63 pub package_type: PackageType,
65 pub bundle_paths: Vec<PathBuf>,
67}
68
69pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
72 let mut package_types = settings.package_types()?;
73 if package_types.is_empty() {
74 return Ok(Vec::new());
75 }
76
77 package_types.sort_by_key(|a| a.priority());
78
79 let target_os = settings.target_platform();
80
81 if *target_os != TargetPlatform::current() {
82 log::warn!("Cross-platform compilation is experimental and does not support all features. Please use a matching host system for full compatibility.");
83 }
84
85 sign_binaries_if_needed(settings, target_os)?;
87
88 let main_binary = settings
89 .binaries()
90 .iter()
91 .find(|b| b.main())
92 .expect("Main binary missing in settings");
93 let main_binary_path = settings.binary_path(main_binary);
94
95 let main_binary_reset_required = matches!(target_os, TargetPlatform::Windows)
102 && settings.windows().can_sign()
103 && package_types.len() > 1;
104 let mut unsigned_main_binary_copy = tempfile::tempfile()?;
105 if main_binary_reset_required {
106 let mut unsigned_main_binary = std::fs::File::open(&main_binary_path)?;
107 std::io::copy(&mut unsigned_main_binary, &mut unsigned_main_binary_copy)?;
108 }
109
110 let mut main_binary_signed = false;
111 let mut bundles = Vec::<Bundle>::new();
112 for package_type in &package_types {
113 if bundles.iter().any(|b| b.package_type == *package_type) {
115 continue;
116 }
117
118 if let Err(e) = patch_binary(&main_binary_path, package_type) {
119 log::warn!("Failed to add bundler type to the binary: {e}. Updater plugin may not be able to update this package. This shouldn't normally happen, please report it to https://github.com/tauri-apps/tauri/issues");
120 }
121
122 if matches!(target_os, TargetPlatform::Windows) && settings.windows().can_sign() {
124 if main_binary_signed && main_binary_reset_required {
125 let mut signed_main_binary = std::fs::OpenOptions::new()
126 .write(true)
127 .truncate(true)
128 .open(&main_binary_path)?;
129 unsigned_main_binary_copy.seek(SeekFrom::Start(0))?;
130 std::io::copy(&mut unsigned_main_binary_copy, &mut signed_main_binary)?;
131 }
132 windows::sign::try_sign(&main_binary_path, settings)?;
133 main_binary_signed = true;
134 }
135
136 let bundle_paths = match package_type {
137 #[cfg(target_os = "macos")]
138 PackageType::MacOsBundle => macos::app::bundle_project(settings)?,
139 #[cfg(target_os = "macos")]
140 PackageType::IosBundle => macos::ios::bundle_project(settings)?,
141 #[cfg(target_os = "macos")]
143 PackageType::Dmg => {
144 let bundled = macos::dmg::bundle_project(settings, &bundles)?;
145 if !bundled.app.is_empty() {
146 bundles.push(Bundle {
147 package_type: PackageType::MacOsBundle,
148 bundle_paths: bundled.app,
149 });
150 }
151 bundled.dmg
152 }
153
154 #[cfg(target_os = "windows")]
155 PackageType::WindowsMsi => windows::msi::bundle_project(settings, false)?,
156 PackageType::Nsis => windows::nsis::bundle_project(settings, false)?,
158
159 #[cfg(target_os = "linux")]
160 PackageType::Deb => linux::debian::bundle_project(settings)?,
161 #[cfg(target_os = "linux")]
162 PackageType::Rpm => linux::rpm::bundle_project(settings)?,
163 #[cfg(target_os = "linux")]
164 PackageType::AppImage => linux::appimage::bundle_project(settings)?,
165 _ => {
166 log::warn!("ignoring {}", package_type.short_name());
167 continue;
168 }
169 };
170
171 bundles.push(Bundle {
172 package_type: package_type.to_owned(),
173 bundle_paths,
174 });
175 }
176
177 if let Some(updater) = settings.updater() {
178 if package_types.iter().any(|package_type| {
179 if updater.v1_compatible {
180 matches!(
181 package_type,
182 PackageType::AppImage
183 | PackageType::MacOsBundle
184 | PackageType::Nsis
185 | PackageType::WindowsMsi
186 | PackageType::Deb
187 )
188 } else {
189 matches!(package_type, PackageType::MacOsBundle)
190 }
191 }) {
192 let updater_paths = updater_bundle::bundle_project(settings, &bundles)?;
193 bundles.push(Bundle {
194 package_type: PackageType::Updater,
195 bundle_paths: updater_paths,
196 });
197 } else if updater.v1_compatible
198 || !package_types.iter().any(|package_type| {
199 matches!(
201 package_type,
202 PackageType::AppImage | PackageType::Nsis | PackageType::WindowsMsi | PackageType::Deb
203 )
204 })
205 {
206 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");
207 }
208 if updater.v1_compatible {
209 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");
210 }
211 }
212
213 #[cfg(target_os = "macos")]
214 {
215 if !package_types.contains(&PackageType::MacOsBundle) {
217 if let Some(app_bundle_paths) = bundles
218 .iter()
219 .position(|b| b.package_type == PackageType::MacOsBundle)
220 .map(|i| bundles.remove(i))
221 .map(|b| b.bundle_paths)
222 {
223 for app_bundle_path in &app_bundle_paths {
224 use crate::error::ErrorExt;
225
226 log::info!(action = "Cleaning"; "{}", app_bundle_path.display());
227 match app_bundle_path.is_dir() {
228 true => std::fs::remove_dir_all(app_bundle_path),
229 false => std::fs::remove_file(app_bundle_path),
230 }
231 .fs_context(
232 "failed to clean the app bundle",
233 app_bundle_path.to_path_buf(),
234 )?;
235 }
236 }
237 }
238 }
239
240 if bundles.is_empty() {
241 return Ok(bundles);
242 }
243
244 let finished_bundles = bundles
245 .iter()
246 .filter(|b| b.package_type != PackageType::Updater)
247 .count();
248 let pluralised = if finished_bundles == 1 {
249 "bundle"
250 } else {
251 "bundles"
252 };
253
254 let mut printable_paths = String::new();
255 for bundle in &bundles {
256 for path in &bundle.bundle_paths {
257 let note = if bundle.package_type == crate::PackageType::Updater {
258 " (updater)"
259 } else {
260 ""
261 };
262 let path_display = display_path(path);
263 writeln!(printable_paths, " {path_display}{note}").unwrap();
264 }
265 }
266
267 log::info!(action = "Finished"; "{finished_bundles} {pluralised} at:\n{printable_paths}");
268
269 Ok(bundles)
270}
271
272fn sign_binaries_if_needed(settings: &Settings, target_os: &TargetPlatform) -> crate::Result<()> {
273 if matches!(target_os, TargetPlatform::Windows) {
274 if settings.windows().can_sign() {
275 if settings.no_sign() {
276 log::warn!("Skipping binary signing due to --no-sign flag.");
277 return Ok(());
278 }
279
280 for bin in settings.binaries() {
281 if bin.main() {
282 continue;
284 }
285 let bin_path = settings.binary_path(bin);
286 windows::sign::try_sign(&bin_path, settings)?;
287 }
288
289 for bin in settings.external_binaries() {
291 let path = bin?;
292 let skip = std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").is_ok_and(|v| v == "true");
293 if skip {
294 continue;
295 }
296
297 #[cfg(windows)]
298 if windows::sign::verify(&path)? {
299 log::info!(
300 "sidecar at \"{}\" already signed. Skipping...",
301 path.display()
302 );
303 continue;
304 }
305
306 windows::sign::try_sign(&path, settings)?;
307 }
308 } else {
309 #[cfg(not(target_os = "windows"))]
310 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...");
311 }
312 }
313
314 Ok(())
315}
316
317pub fn check_icons(settings: &Settings) -> crate::Result<bool> {
319 let mut iter = settings.icon_files().peekable();
321
322 if iter.peek().is_none() {
324 Ok(false)
325 } else {
326 Ok(true)
327 }
328}