mod category;
mod common;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
mod path_utils;
mod platform;
mod settings;
mod updater_bundle;
mod windows;
use tauri_utils::display_path;
pub use self::{
category::AppCategory,
settings::{
AppImageSettings, BundleBinary, BundleSettings, CustomSignCommandSettings, DebianSettings,
DmgSettings, MacOsSettings, PackageSettings, PackageType, Position, RpmSettings, Settings,
SettingsBuilder, Size, UpdaterSettings,
},
};
#[cfg(target_os = "macos")]
use anyhow::Context;
pub use settings::{NsisSettings, WindowsSettings, WixLanguage, WixLanguageConfig, WixSettings};
use std::{fmt::Write, path::PathBuf};
#[derive(Debug)]
pub struct Bundle {
pub package_type: PackageType,
pub bundle_paths: Vec<PathBuf>,
}
pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<Bundle>> {
let mut package_types = settings.package_types()?;
if package_types.is_empty() {
return Ok(Vec::new());
}
package_types.sort_by_key(|a| a.priority());
let target_os = settings
.target()
.split('-')
.nth(2)
.unwrap_or(std::env::consts::OS)
.replace("darwin", "macos");
if target_os != std::env::consts::OS {
log::warn!("Cross-platform compilation is experimental and does not support all features. Please use a matching host system for full compatibility.");
}
if target_os == "windows" {
if settings.can_sign() {
for bin in settings.binaries() {
let bin_path = settings.binary_path(bin);
windows::sign::try_sign(&bin_path, settings)?;
}
for bin in settings.external_binaries() {
let path = bin?;
let skip =
std::env::var("TAURI_SKIP_SIDECAR_SIGNATURE_CHECK").map_or(false, |v| v == "true");
if skip {
continue;
}
#[cfg(windows)]
if windows::sign::verify(&path)? {
log::info!(
"sidecar at \"{}\" already signed. Skipping...",
path.display()
);
continue;
}
windows::sign::try_sign(&path, settings)?;
}
} else {
#[cfg(not(target_os = "windows"))]
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...");
}
}
let mut bundles = Vec::<Bundle>::new();
for package_type in &package_types {
if bundles.iter().any(|b| b.package_type == *package_type) {
continue;
}
let bundle_paths = match package_type {
#[cfg(target_os = "macos")]
PackageType::MacOsBundle => macos::app::bundle_project(settings)?,
#[cfg(target_os = "macos")]
PackageType::IosBundle => macos::ios::bundle_project(settings)?,
#[cfg(target_os = "macos")]
PackageType::Dmg => {
let bundled = macos::dmg::bundle_project(settings, &bundles)?;
if !bundled.app.is_empty() {
bundles.push(Bundle {
package_type: PackageType::MacOsBundle,
bundle_paths: bundled.app,
});
}
bundled.dmg
}
#[cfg(target_os = "windows")]
PackageType::WindowsMsi => windows::msi::bundle_project(settings, false)?,
PackageType::Nsis => windows::nsis::bundle_project(settings, false)?,
#[cfg(target_os = "linux")]
PackageType::Deb => linux::debian::bundle_project(settings)?,
#[cfg(target_os = "linux")]
PackageType::Rpm => linux::rpm::bundle_project(settings)?,
#[cfg(target_os = "linux")]
PackageType::AppImage => linux::appimage::bundle_project(settings)?,
_ => {
log::warn!("ignoring {}", package_type.short_name());
continue;
}
};
bundles.push(Bundle {
package_type: package_type.to_owned(),
bundle_paths,
});
}
if let Some(updater) = settings.updater() {
if package_types.iter().any(|package_type| {
if updater.v1_compatible {
matches!(
package_type,
PackageType::AppImage
| PackageType::MacOsBundle
| PackageType::Nsis
| PackageType::WindowsMsi
)
} else {
matches!(package_type, PackageType::MacOsBundle)
}
}) {
let updater_paths = updater_bundle::bundle_project(settings, &bundles)?;
bundles.push(Bundle {
package_type: PackageType::Updater,
bundle_paths: updater_paths,
});
} else if updater.v1_compatible
|| !package_types.iter().any(|package_type| {
matches!(
package_type,
PackageType::AppImage | PackageType::Nsis | PackageType::WindowsMsi
)
})
{
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");
}
if updater.v1_compatible {
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");
}
}
#[cfg(target_os = "macos")]
{
if !package_types.contains(&PackageType::MacOsBundle) {
if let Some(app_bundle_paths) = bundles
.iter()
.position(|b| b.package_type == PackageType::MacOsBundle)
.map(|i| bundles.remove(i))
.map(|b| b.bundle_paths)
{
for app_bundle_path in &app_bundle_paths {
log::info!(action = "Cleaning"; "{}", app_bundle_path.display());
match app_bundle_path.is_dir() {
true => std::fs::remove_dir_all(app_bundle_path),
false => std::fs::remove_file(app_bundle_path),
}
.with_context(|| {
format!(
"Failed to clean the app bundle at {}",
app_bundle_path.display()
)
})?
}
}
}
}
if bundles.is_empty() {
return Err(anyhow::anyhow!("No bundles were built").into());
}
let bundles_wo_updater = bundles
.iter()
.filter(|b| b.package_type != PackageType::Updater)
.collect::<Vec<_>>();
let finished_bundles = bundles_wo_updater.len();
let pluralised = if finished_bundles == 1 {
"bundle"
} else {
"bundles"
};
let mut printable_paths = String::new();
for bundle in &bundles {
for path in &bundle.bundle_paths {
let note = if bundle.package_type == crate::PackageType::Updater {
" (updater)"
} else {
""
};
let path_display = display_path(path);
writeln!(printable_paths, " {path_display}{note}").unwrap();
}
}
log::info!(action = "Finished"; "{finished_bundles} {pluralised} at:\n{printable_paths}");
Ok(bundles)
}
pub fn check_icons(settings: &Settings) -> crate::Result<bool> {
let mut iter = settings.icon_files().peekable();
if iter.peek().is_none() {
Ok(false)
} else {
Ok(true)
}
}