tauri-bundler 2.0.0-alpha.0

Wrap rust executables in OS-specific app bundles for Tauri
Documentation
// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
// Copyright 2019-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use super::category::AppCategory;
use crate::bundle::{common, platform::target_triple};
pub use tauri_utils::config::WebviewInstallMode;
use tauri_utils::{
  config::BundleType,
  resources::{external_binaries, ResourcePaths},
};

use std::{
  collections::HashMap,
  path::{Path, PathBuf},
};

/// The type of the package we're bundling.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum PackageType {
  /// The macOS application bundle (.app).
  MacOsBundle,
  /// The iOS app bundle.
  IosBundle,
  /// The Windows bundle (.msi).
  WindowsMsi,
  /// The Linux Debian package bundle (.deb).
  Deb,
  /// The Linux RPM bundle (.rpm).
  Rpm,
  /// The Linux AppImage bundle (.AppImage).
  AppImage,
  /// The macOS DMG bundle (.dmg).
  Dmg,
  /// The Updater bundle.
  Updater,
}

impl From<BundleType> for PackageType {
  fn from(bundle: BundleType) -> Self {
    match bundle {
      BundleType::Deb => Self::Deb,
      BundleType::AppImage => Self::AppImage,
      BundleType::Msi => Self::WindowsMsi,
      BundleType::App => Self::MacOsBundle,
      BundleType::Dmg => Self::Dmg,
      BundleType::Updater => Self::Updater,
    }
  }
}

impl PackageType {
  /// Maps a short name to a PackageType.
  /// Possible values are "deb", "ios", "msi", "app", "rpm", "appimage", "dmg", "updater".
  pub fn from_short_name(name: &str) -> Option<PackageType> {
    // Other types we may eventually want to support: apk.
    match name {
      "deb" => Some(PackageType::Deb),
      "ios" => Some(PackageType::IosBundle),
      "msi" => Some(PackageType::WindowsMsi),
      "app" => Some(PackageType::MacOsBundle),
      "rpm" => Some(PackageType::Rpm),
      "appimage" => Some(PackageType::AppImage),
      "dmg" => Some(PackageType::Dmg),
      "updater" => Some(PackageType::Updater),
      _ => None,
    }
  }

  /// Gets the short name of this PackageType.
  #[allow(clippy::trivially_copy_pass_by_ref)]
  pub fn short_name(&self) -> &'static str {
    match *self {
      PackageType::Deb => "deb",
      PackageType::IosBundle => "ios",
      PackageType::WindowsMsi => "msi",
      PackageType::MacOsBundle => "app",
      PackageType::Rpm => "rpm",
      PackageType::AppImage => "appimage",
      PackageType::Dmg => "dmg",
      PackageType::Updater => "updater",
    }
  }

  /// Gets the list of the possible package types.
  pub fn all() -> &'static [PackageType] {
    ALL_PACKAGE_TYPES
  }
}

const ALL_PACKAGE_TYPES: &[PackageType] = &[
  #[cfg(target_os = "linux")]
  PackageType::Deb,
  #[cfg(target_os = "macos")]
  PackageType::IosBundle,
  #[cfg(target_os = "windows")]
  PackageType::WindowsMsi,
  #[cfg(target_os = "macos")]
  PackageType::MacOsBundle,
  #[cfg(target_os = "linux")]
  PackageType::Rpm,
  #[cfg(target_os = "macos")]
  PackageType::Dmg,
  #[cfg(target_os = "linux")]
  PackageType::AppImage,
  PackageType::Updater,
];

/// The package settings.
#[derive(Debug, Clone)]
pub struct PackageSettings {
  /// the package's product name.
  pub product_name: String,
  /// the package's version.
  pub version: String,
  /// the package's description.
  pub description: String,
  /// the package's homepage.
  pub homepage: Option<String>,
  /// the package's authors.
  pub authors: Option<Vec<String>>,
  /// the default binary to run.
  pub default_run: Option<String>,
}

/// The updater settings.
#[derive(Debug, Default, Clone)]
pub struct UpdaterSettings {
  /// Whether the updater is active or not.
  pub active: bool,
  /// The updater endpoints.
  pub endpoints: Option<Vec<String>>,
  /// Signature public key.
  pub pubkey: String,
  /// Display built-in dialog or use event system if disabled.
  pub dialog: bool,
  /// Args to pass to `msiexec.exe` to run the updater on Windows.
  pub msiexec_args: Option<&'static [&'static str]>,
}

/// The Linux debian bundle settings.
#[derive(Clone, Debug, Default)]
pub struct DebianSettings {
  // OS-specific settings:
  /// the list of debian dependencies.
  pub depends: Option<Vec<String>>,
  /// List of custom files to add to the deb package.
  /// Maps the path on the debian package to the path of the file to include (relative to the current working directory).
  pub files: HashMap<PathBuf, PathBuf>,
}

/// The macOS bundle settings.
#[derive(Clone, Debug, Default)]
pub struct MacOsSettings {
  /// MacOS frameworks that need to be bundled with the app.
  ///
  /// Each string can either be the name of a framework (without the `.framework` extension, e.g. `"SDL2"`),
  /// in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`),
  /// or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`).  Note that this setting just makes tauri-bundler copy the specified frameworks into the OS X app bundle
  /// (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:
  ///
  /// - 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)
  ///
  /// - embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath "@executable_path/../Frameworks" path/to/binary` after compiling)
  pub frameworks: Option<Vec<String>>,
  /// A version string indicating the minimum MacOS version that the bundled app supports (e.g. `"10.11"`).
  /// 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`.
  pub minimum_system_version: Option<String>,
  /// The path to the LICENSE file for macOS apps.
  /// Currently only used by the dmg bundle.
  pub license: Option<String>,
  /// The exception domain to use on the macOS .app bundle.
  ///
  /// This allows communication to the outside world e.g. a web server you're shipping.
  pub exception_domain: Option<String>,
  /// Code signing identity.
  pub signing_identity: Option<String>,
  /// Provider short name for notarization.
  pub provider_short_name: Option<String>,
  /// Path to the entitlements.plist file.
  pub entitlements: Option<String>,
  /// Path to the Info.plist file for the bundle.
  pub info_plist_path: Option<PathBuf>,
}

/// Configuration for a target language for the WiX build.
#[derive(Debug, Clone, Default)]
pub struct WixLanguageConfig {
  /// The path to a locale (`.wxl`) file. See <https://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/build_a_localized_version.html>.
  pub locale_path: Option<PathBuf>,
}

/// The languages to build using WiX.
#[derive(Debug, Clone)]
pub struct WixLanguage(pub Vec<(String, WixLanguageConfig)>);

impl Default for WixLanguage {
  fn default() -> Self {
    Self(vec![("en-US".into(), Default::default())])
  }
}

/// Settings specific to the WiX implementation.
#[derive(Clone, Debug, Default)]
pub struct WixSettings {
  /// The app languages to build. See <https://docs.microsoft.com/en-us/windows/win32/msi/localizing-the-error-and-actiontext-tables>.
  pub language: WixLanguage,
  /// By default, the bundler uses an internal template.
  /// This option allows you to define your own wix file.
  pub template: Option<PathBuf>,
  /// A list of paths to .wxs files with WiX fragments to use.
  pub fragment_paths: Vec<PathBuf>,
  /// The ComponentGroup element ids you want to reference from the fragments.
  pub component_group_refs: Vec<String>,
  /// The Component element ids you want to reference from the fragments.
  pub component_refs: Vec<String>,
  /// The FeatureGroup element ids you want to reference from the fragments.
  pub feature_group_refs: Vec<String>,
  /// The Feature element ids you want to reference from the fragments.
  pub feature_refs: Vec<String>,
  /// The Merge element ids you want to reference from the fragments.
  pub merge_refs: Vec<String>,
  /// Disables the Webview2 runtime installation after app install. Will be removed in v2, use [`WindowsSettings::webview_install_mode`] instead.
  pub skip_webview_install: bool,
  /// The path to the LICENSE file.
  pub license: Option<PathBuf>,
  /// Create an elevated update task within Windows Task Scheduler.
  pub enable_elevated_update_task: bool,
  /// Path to a bitmap file to use as the installation user interface banner.
  /// This bitmap will appear at the top of all but the first page of the installer.
  ///
  /// The required dimensions are 493px × 58px.
  pub banner_path: Option<PathBuf>,
  /// Path to a bitmap file to use on the installation user interface dialogs.
  /// It is used on the welcome and completion dialogs.

  /// The required dimensions are 493px × 312px.
  pub dialog_image_path: Option<PathBuf>,
  /// Enables FIPS compliant algorithms.
  pub fips_compliant: bool,
}

/// The Windows bundle settings.
#[derive(Clone, Debug)]
pub struct WindowsSettings {
  /// The file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.
  pub digest_algorithm: Option<String>,
  /// The SHA1 hash of the signing certificate.
  pub certificate_thumbprint: Option<String>,
  /// Server to use during timestamping.
  pub timestamp_url: Option<String>,
  /// Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may
  /// use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.
  pub tsp: bool,
  /// WiX configuration.
  pub wix: Option<WixSettings>,
  /// The path to the application icon. Defaults to `./icons/icon.ico`.
  pub icon_path: PathBuf,
  /// The installation mode for the Webview2 runtime.
  pub webview_install_mode: WebviewInstallMode,
  /// Path to the webview fixed runtime to use.
  ///
  /// Overwrites [`Self::webview_install_mode`] if set.
  ///
  /// Will be removed in v2, use [`Self::webview_install_mode`] instead.
  pub webview_fixed_runtime_path: Option<PathBuf>,
  /// Validates a second app installation, blocking the user from installing an older version if set to `false`.
  ///
  /// 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`.
  ///
  /// /// The default value of this flag is `true`.
  pub allow_downgrades: bool,
}

impl Default for WindowsSettings {
  fn default() -> Self {
    Self {
      digest_algorithm: None,
      certificate_thumbprint: None,
      timestamp_url: None,
      tsp: false,
      wix: None,
      icon_path: PathBuf::from("icons/icon.ico"),
      webview_install_mode: Default::default(),
      webview_fixed_runtime_path: None,
      allow_downgrades: true,
    }
  }
}

/// The bundle settings of the BuildArtifact we're bundling.
#[derive(Clone, Debug, Default)]
pub struct BundleSettings {
  /// the app's identifier.
  pub identifier: Option<String>,
  /// The app's publisher. Defaults to the second element in the identifier string.
  /// Currently maps to the Manufacturer property of the Windows Installer.
  pub publisher: Option<String>,
  /// the app's icon list.
  pub icon: Option<Vec<String>>,
  /// the app's resources to bundle.
  ///
  /// each item can be a path to a file or a path to a folder.
  ///
  /// supports glob patterns.
  pub resources: Option<Vec<String>>,
  /// the app's copyright.
  pub copyright: Option<String>,
  /// the app's category.
  pub category: Option<AppCategory>,
  /// the app's short description.
  pub short_description: Option<String>,
  /// the app's long description.
  pub long_description: Option<String>,
  // Bundles for other binaries:
  /// Configuration map for the apps to bundle.
  pub bin: Option<HashMap<String, BundleSettings>>,
  /// External binaries to add to the bundle.
  ///
  /// Note that each binary name should have the target platform's target triple appended,
  /// as well as `.exe` for Windows.
  /// For example, if you're bundling a sidecar called `sqlite3`, the bundler expects
  /// a binary named `sqlite3-x86_64-unknown-linux-gnu` on linux,
  /// and `sqlite3-x86_64-pc-windows-gnu.exe` on windows.
  ///
  /// Run `tauri build --help` for more info on targets.
  ///
  /// If you are building a universal binary for MacOS, the bundler expects
  /// your external binary to also be universal, and named after the target triple,
  /// e.g. `sqlite3-universal-apple-darwin`. See
  /// <https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary>
  pub external_bin: Option<Vec<String>>,
  /// Debian-specific settings.
  pub deb: DebianSettings,
  /// MacOS-specific settings.
  pub macos: MacOsSettings,
  /// Updater configuration.
  pub updater: Option<UpdaterSettings>,
  /// Windows-specific settings.
  pub windows: WindowsSettings,
}

/// A binary to bundle.
#[derive(Clone, Debug)]
pub struct BundleBinary {
  name: String,
  src_path: Option<String>,
  main: bool,
}

impl BundleBinary {
  /// Creates a new bundle binary.
  pub fn new(name: String, main: bool) -> Self {
    Self {
      name,
      src_path: None,
      main,
    }
  }

  /// Sets the src path of the binary.
  #[must_use]
  pub fn set_src_path(mut self, src_path: Option<String>) -> Self {
    self.src_path = src_path;
    self
  }

  /// Mark the binary as the main executable.
  pub fn set_main(&mut self, main: bool) {
    self.main = main;
  }

  /// Sets the binary name.
  pub fn set_name(&mut self, name: String) {
    self.name = name;
  }

  /// Returns the binary name.
  pub fn name(&self) -> &str {
    &self.name
  }

  /// Returns the binary `main` flag.
  pub fn main(&self) -> bool {
    self.main
  }

  /// Returns the binary source path.
  pub fn src_path(&self) -> Option<&String> {
    self.src_path.as_ref()
  }
}

/// The Settings exposed by the module.
#[derive(Clone, Debug)]
pub struct Settings {
  /// the package settings.
  package: PackageSettings,
  /// the package types we're bundling.
  ///
  /// if not present, we'll use the PackageType list for the target OS.
  package_types: Option<Vec<PackageType>>,
  /// the directory where the bundles will be placed.
  project_out_directory: PathBuf,
  /// the bundle settings.
  bundle_settings: BundleSettings,
  /// the binaries to bundle.
  binaries: Vec<BundleBinary>,
  /// The target triple.
  target: String,
}

/// A builder for [`Settings`].
#[derive(Default)]
pub struct SettingsBuilder {
  project_out_directory: Option<PathBuf>,
  package_types: Option<Vec<PackageType>>,
  package_settings: Option<PackageSettings>,
  bundle_settings: BundleSettings,
  binaries: Vec<BundleBinary>,
  target: Option<String>,
}

impl SettingsBuilder {
  /// Creates the default settings builder.
  pub fn new() -> Self {
    Default::default()
  }

  /// Sets the project output directory. It's used as current working directory.
  #[must_use]
  pub fn project_out_directory<P: AsRef<Path>>(mut self, path: P) -> Self {
    self
      .project_out_directory
      .replace(path.as_ref().to_path_buf());
    self
  }

  /// Sets the package types to create.
  #[must_use]
  pub fn package_types(mut self, package_types: Vec<PackageType>) -> Self {
    self.package_types = Some(package_types);
    self
  }

  /// Sets the package settings.
  #[must_use]
  pub fn package_settings(mut self, settings: PackageSettings) -> Self {
    self.package_settings.replace(settings);
    self
  }

  /// Sets the bundle settings.
  #[must_use]
  pub fn bundle_settings(mut self, settings: BundleSettings) -> Self {
    self.bundle_settings = settings;
    self
  }

  /// Sets the binaries to bundle.
  #[must_use]
  pub fn binaries(mut self, binaries: Vec<BundleBinary>) -> Self {
    self.binaries = binaries;
    self
  }

  /// Sets the target triple.
  #[must_use]
  pub fn target(mut self, target: String) -> Self {
    self.target.replace(target);
    self
  }

  /// Builds a Settings from the CLI args.
  ///
  /// Package settings will be read from Cargo.toml.
  ///
  /// Bundle settings will be read from $TAURI_DIR/tauri.conf.json if it exists and fallback to Cargo.toml's [package.metadata.bundle].
  pub fn build(self) -> crate::Result<Settings> {
    let target = if let Some(t) = self.target {
      t
    } else {
      target_triple()?
    };

    Ok(Settings {
      package: self.package_settings.expect("package settings is required"),
      package_types: self.package_types,
      project_out_directory: self
        .project_out_directory
        .expect("out directory is required"),
      binaries: self.binaries,
      bundle_settings: BundleSettings {
        external_bin: self
          .bundle_settings
          .external_bin
          .as_ref()
          .map(|bins| external_binaries(bins, &target)),
        ..self.bundle_settings
      },
      target,
    })
  }
}

impl Settings {
  /// Returns the directory where the bundle should be placed.
  pub fn project_out_directory(&self) -> &Path {
    &self.project_out_directory
  }

  /// Returns the target triple.
  pub fn target(&self) -> &str {
    &self.target
  }

  /// Returns the architecture for the binary being bundled (e.g. "arm", "x86" or "x86_64").
  pub fn binary_arch(&self) -> &str {
    if self.target.starts_with("x86_64") {
      "x86_64"
    } else if self.target.starts_with('i') {
      "x86"
    } else if self.target.starts_with("arm") {
      "arm"
    } else if self.target.starts_with("aarch64") {
      "aarch64"
    } else if self.target.starts_with("universal") {
      "universal"
    } else {
      panic!("Unexpected target triple {}", self.target)
    }
  }

  /// Returns the file name of the binary being bundled.
  pub fn main_binary_name(&self) -> &str {
    self
      .binaries
      .iter()
      .find(|bin| bin.main)
      .expect("failed to find main binary")
      .name
      .as_str()
  }

  /// Returns the path to the specified binary.
  pub fn binary_path(&self, binary: &BundleBinary) -> PathBuf {
    let mut path = self.project_out_directory.clone();
    path.push(binary.name());
    path
  }

  /// Returns the list of binaries to bundle.
  pub fn binaries(&self) -> &Vec<BundleBinary> {
    &self.binaries
  }

  /// If a list of package types was specified by the command-line, returns
  /// that list filtered by the current target OS available targets.
  ///
  /// If a target triple was specified by the
  /// command-line, returns the native package type(s) for that target.
  ///
  /// Otherwise returns the native package type(s) for the host platform.
  ///
  /// Fails if the host/target's native package type is not supported.
  pub fn package_types(&self) -> crate::Result<Vec<PackageType>> {
    let target_os = std::env::consts::OS;
    let mut platform_types = match target_os {
      "macos" => vec![PackageType::MacOsBundle, PackageType::Dmg],
      "ios" => vec![PackageType::IosBundle],
      "linux" => vec![PackageType::Deb, PackageType::AppImage],
      "windows" => vec![PackageType::WindowsMsi],
      os => {
        return Err(crate::Error::GenericError(format!(
          "Native {} bundles not yet supported.",
          os
        )))
      }
    };

    // add updater if needed
    if self.is_update_enabled() {
      platform_types.push(PackageType::Updater)
    }

    if let Some(package_types) = &self.package_types {
      let mut types = vec![];
      for package_type in package_types {
        let package_type = *package_type;
        if platform_types
          .clone()
          .into_iter()
          .any(|t| t == package_type)
        {
          types.push(package_type);
        }
      }
      Ok(types)
    } else {
      Ok(platform_types)
    }
  }

  /// Returns the product name.
  pub fn product_name(&self) -> &str {
    &self.package.product_name
  }

  /// Returns the bundle's identifier
  pub fn bundle_identifier(&self) -> &str {
    self.bundle_settings.identifier.as_deref().unwrap_or("")
  }

  /// Returns the bundle's identifier
  pub fn publisher(&self) -> Option<&str> {
    self.bundle_settings.publisher.as_deref()
  }

  /// Returns an iterator over the icon files to be used for this bundle.
  pub fn icon_files(&self) -> ResourcePaths<'_> {
    match self.bundle_settings.icon {
      Some(ref paths) => ResourcePaths::new(paths.as_slice(), false),
      None => ResourcePaths::new(&[], false),
    }
  }

  /// Returns an iterator over the resource files to be included in this
  /// bundle.
  pub fn resource_files(&self) -> ResourcePaths<'_> {
    match self.bundle_settings.resources {
      Some(ref paths) => ResourcePaths::new(paths.as_slice(), true),
      None => ResourcePaths::new(&[], true),
    }
  }

  /// Returns an iterator over the external binaries to be included in this
  /// bundle.
  pub fn external_binaries(&self) -> ResourcePaths<'_> {
    match self.bundle_settings.external_bin {
      Some(ref paths) => ResourcePaths::new(paths.as_slice(), true),
      None => ResourcePaths::new(&[], true),
    }
  }

  /// Copies external binaries to a path.
  pub fn copy_binaries(&self, path: &Path) -> crate::Result<()> {
    for src in self.external_binaries() {
      let src = src?;
      let dest = path.join(
        src
          .file_name()
          .expect("failed to extract external binary filename")
          .to_string_lossy()
          .replace(&format!("-{}", self.target), ""),
      );
      common::copy_file(&src, &dest)?;
    }
    Ok(())
  }

  /// Copies resources to a path.
  pub fn copy_resources(&self, path: &Path) -> crate::Result<()> {
    for src in self.resource_files() {
      let src = src?;
      let dest = path.join(tauri_utils::resources::resource_relpath(&src));
      common::copy_file(&src, &dest)?;
    }
    Ok(())
  }

  /// Returns the version string of the bundle.
  pub fn version_string(&self) -> &str {
    &self.package.version
  }

  /// Returns the copyright text.
  pub fn copyright_string(&self) -> Option<&str> {
    self.bundle_settings.copyright.as_deref()
  }

  /// Returns the list of authors name.
  pub fn author_names(&self) -> &[String] {
    match self.package.authors {
      Some(ref names) => names.as_slice(),
      None => &[],
    }
  }

  /// Returns the authors as a comma-separated string.
  pub fn authors_comma_separated(&self) -> Option<String> {
    let names = self.author_names();
    if names.is_empty() {
      None
    } else {
      Some(names.join(", "))
    }
  }

  /// Returns the package's homepage URL, defaulting to "" if not defined.
  pub fn homepage_url(&self) -> &str {
    self.package.homepage.as_deref().unwrap_or("")
  }

  /// Returns the app's category.
  pub fn app_category(&self) -> Option<AppCategory> {
    self.bundle_settings.category
  }

  /// Returns the app's short description.
  pub fn short_description(&self) -> &str {
    self
      .bundle_settings
      .short_description
      .as_ref()
      .unwrap_or(&self.package.description)
  }

  /// Returns the app's long description.
  pub fn long_description(&self) -> Option<&str> {
    self.bundle_settings.long_description.as_deref()
  }

  /// Returns the debian settings.
  pub fn deb(&self) -> &DebianSettings {
    &self.bundle_settings.deb
  }

  /// Returns the MacOS settings.
  pub fn macos(&self) -> &MacOsSettings {
    &self.bundle_settings.macos
  }

  /// Returns the Windows settings.
  pub fn windows(&self) -> &WindowsSettings {
    &self.bundle_settings.windows
  }

  /// Returns the Updater settings.
  pub fn updater(&self) -> Option<&UpdaterSettings> {
    self.bundle_settings.updater.as_ref()
  }

  /// Is update enabled
  pub fn is_update_enabled(&self) -> bool {
    match &self.bundle_settings.updater {
      Some(val) => val.active,
      None => false,
    }
  }
}