123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
- // SPDX-License-Identifier: Apache-2.0
- // SPDX-License-Identifier: MIT
- use std::{
- fs::File,
- io::Read,
- path::{Path, PathBuf},
- process::Command,
- str::FromStr,
- };
- use anyhow::Context;
- #[cfg(target_os = "linux")]
- use heck::ToKebabCase;
- use serde::Deserialize;
- use crate::helpers::{app_paths::tauri_dir, config::Config, manifest::Manifest, Logger};
- use tauri_bundler::{
- AppCategory, BundleBinary, BundleSettings, DebianSettings, MacOsSettings, PackageSettings,
- UpdaterSettings, WindowsSettings, WixSettings,
- };
- /// The `workspace` section of the app configuration (read from Cargo.toml).
- #[derive(Clone, Debug, Deserialize)]
- struct WorkspaceSettings {
- /// the workspace members.
- members: Option<Vec<String>>,
- }
- #[derive(Clone, Debug, Deserialize)]
- struct BinarySettings {
- name: String,
- path: Option<String>,
- }
- /// The package settings.
- #[derive(Debug, Clone, Deserialize)]
- pub struct CargoPackageSettings {
- /// the package's name.
- pub name: Option<String>,
- /// the package's version.
- pub version: Option<String>,
- /// the package's description.
- pub description: Option<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 Cargo settings (Cargo.toml root descriptor).
- #[derive(Clone, Debug, Deserialize)]
- struct CargoSettings {
- /// the package settings.
- ///
- /// it's optional because ancestor workspace Cargo.toml files may not have package info.
- package: Option<CargoPackageSettings>,
- /// the workspace settings.
- ///
- /// it's present if the read Cargo.toml belongs to a workspace root.
- workspace: Option<WorkspaceSettings>,
- /// the binary targets configuration.
- bin: Option<Vec<BinarySettings>>,
- }
- impl CargoSettings {
- /// Try to load a set of CargoSettings from a "Cargo.toml" file in the specified directory.
- fn load(dir: &Path) -> crate::Result<Self> {
- let toml_path = dir.join("Cargo.toml");
- let mut toml_str = String::new();
- let mut toml_file = File::open(toml_path).with_context(|| "failed to open Cargo.toml")?;
- toml_file
- .read_to_string(&mut toml_str)
- .with_context(|| "failed to read Cargo.toml")?;
- toml::from_str(&toml_str)
- .with_context(|| "failed to parse Cargo.toml")
- .map_err(Into::into)
- }
- }
- #[derive(Deserialize)]
- struct CargoBuildConfig {
- #[serde(rename = "target-dir")]
- target_dir: Option<String>,
- }
- #[derive(Deserialize)]
- struct CargoConfig {
- build: Option<CargoBuildConfig>,
- }
- pub fn build_project(
- runner: String,
- target: &Option<String>,
- features: Vec<String>,
- debug: bool,
- ) -> crate::Result<()> {
- let mut command = Command::new(&runner);
- command.args(&["build", "--features=custom-protocol"]);
- if let Some(target) = target {
- command.arg("--target");
- command.arg(target);
- }
- if !features.is_empty() {
- command.arg("--features");
- command.arg(features.join(","));
- }
- if !debug {
- command.arg("--release");
- }
- let status = command
- .status()
- .with_context(|| format!("failed to run {}", runner))?;
- if !status.success() {
- return Err(anyhow::anyhow!(format!(
- "Result of `{} build` operation was unsuccessful: {}",
- runner, status
- )));
- }
- Ok(())
- }
- pub struct AppSettings {
- cargo_settings: CargoSettings,
- cargo_package_settings: CargoPackageSettings,
- package_settings: PackageSettings,
- }
- impl AppSettings {
- pub fn new(config: &Config) -> crate::Result<Self> {
- let cargo_settings =
- CargoSettings::load(&tauri_dir()).with_context(|| "failed to load cargo settings")?;
- let cargo_package_settings = match &cargo_settings.package {
- Some(package_info) => package_info.clone(),
- None => {
- return Err(anyhow::anyhow!(
- "No package info in the config file".to_owned(),
- ))
- }
- };
- let package_settings = PackageSettings {
- product_name: config.package.product_name.clone().unwrap_or_else(|| {
- cargo_package_settings
- .name
- .clone()
- .expect("Cargo manifest must have the `package.name` field")
- }),
- version: config.package.version.clone().unwrap_or_else(|| {
- cargo_package_settings
- .version
- .clone()
- .expect("Cargo manifest must have the `package.version` field")
- }),
- description: cargo_package_settings
- .description
- .clone()
- .unwrap_or_default(),
- homepage: cargo_package_settings.homepage.clone(),
- authors: cargo_package_settings.authors.clone(),
- default_run: cargo_package_settings.default_run.clone(),
- };
- Ok(Self {
- cargo_settings,
- cargo_package_settings,
- package_settings,
- })
- }
- pub fn cargo_package_settings(&self) -> &CargoPackageSettings {
- &self.cargo_package_settings
- }
- pub fn get_bundle_settings(
- &self,
- config: &Config,
- manifest: &Manifest,
- ) -> crate::Result<BundleSettings> {
- tauri_config_to_bundle_settings(
- manifest,
- config.tauri.bundle.clone(),
- config.tauri.system_tray.clone(),
- config.tauri.updater.clone(),
- )
- }
- pub fn get_out_dir(&self, target: Option<String>, debug: bool) -> crate::Result<PathBuf> {
- let tauri_dir = tauri_dir();
- let workspace_dir = get_workspace_dir(&tauri_dir);
- get_target_dir(&workspace_dir, target, !debug)
- }
- pub fn get_package_settings(&self) -> PackageSettings {
- self.package_settings.clone()
- }
- pub fn get_binaries(&self, config: &Config) -> crate::Result<Vec<BundleBinary>> {
- let mut binaries: Vec<BundleBinary> = vec![];
- if let Some(bin) = &self.cargo_settings.bin {
- let default_run = self
- .package_settings
- .default_run
- .clone()
- .unwrap_or_else(|| "".to_string());
- for binary in bin {
- binaries.push(
- if Some(&binary.name) == self.cargo_package_settings.name.as_ref()
- || binary.name.as_str() == default_run
- {
- BundleBinary::new(
- config
- .package
- .binary_name()
- .unwrap_or_else(|| binary.name.clone()),
- true,
- )
- } else {
- BundleBinary::new(binary.name.clone(), false)
- }
- .set_src_path(binary.path.clone()),
- )
- }
- }
- let mut bins_path = tauri_dir();
- bins_path.push("src/bin");
- if let Ok(fs_bins) = std::fs::read_dir(bins_path) {
- for entry in fs_bins {
- let path = entry?.path();
- if let Some(name) = path.file_stem() {
- let bin_exists = binaries.iter().any(|bin| {
- bin.name() == name || path.ends_with(bin.src_path().unwrap_or(&"".to_string()))
- });
- if !bin_exists {
- binaries.push(BundleBinary::new(name.to_string_lossy().to_string(), false))
- }
- }
- }
- }
- if let Some(default_run) = self.package_settings.default_run.as_ref() {
- match binaries.iter_mut().find(|bin| bin.name() == default_run) {
- Some(bin) => {
- if let Some(bin_name) = config.package.binary_name() {
- bin.set_name(bin_name);
- }
- }
- None => {
- binaries.push(BundleBinary::new(
- config
- .package
- .binary_name()
- .unwrap_or_else(|| default_run.to_string()),
- true,
- ));
- }
- }
- }
- match binaries.len() {
- 0 => binaries.push(BundleBinary::new(
- #[cfg(target_os = "linux")]
- self.package_settings.product_name.to_kebab_case(),
- #[cfg(not(target_os = "linux"))]
- self.package_settings.product_name.clone(),
- true,
- )),
- 1 => binaries.get_mut(0).unwrap().set_main(true),
- _ => {}
- }
- Ok(binaries)
- }
- }
- /// This function determines where 'target' dir is and suffixes it with 'release' or 'debug'
- /// to determine where the compiled binary will be located.
- fn get_target_dir(
- project_root_dir: &Path,
- target: Option<String>,
- is_release: bool,
- ) -> crate::Result<PathBuf> {
- let mut path: PathBuf = match std::env::var_os("CARGO_TARGET_DIR") {
- Some(target_dir) => target_dir.into(),
- None => {
- let mut root_dir = project_root_dir.to_path_buf();
- let target_path: Option<PathBuf> = loop {
- // cargo reads configs under .cargo/config.toml or .cargo/config
- let mut cargo_config_path = root_dir.join(".cargo/config");
- if !cargo_config_path.exists() {
- cargo_config_path = root_dir.join(".cargo/config.toml");
- }
- // if the path exists, parse it
- if cargo_config_path.exists() {
- let mut config_str = String::new();
- let mut config_file = File::open(&cargo_config_path)
- .with_context(|| format!("failed to open {:?}", cargo_config_path))?;
- config_file
- .read_to_string(&mut config_str)
- .with_context(|| "failed to read cargo config file")?;
- let config: CargoConfig =
- toml::from_str(&config_str).with_context(|| "failed to parse cargo config file")?;
- if let Some(build) = config.build {
- if let Some(target_dir) = build.target_dir {
- break Some(target_dir.into());
- }
- }
- }
- if !root_dir.pop() {
- break None;
- }
- };
- target_path.unwrap_or_else(|| project_root_dir.join("target"))
- }
- };
- if let Some(ref triple) = target {
- path.push(triple);
- }
- path.push(if is_release { "release" } else { "debug" });
- Ok(path)
- }
- /// Walks up the file system, looking for a Cargo.toml file
- /// If one is found before reaching the root, then the current_dir's package belongs to that parent workspace if it's listed on [workspace.members].
- ///
- /// If this package is part of a workspace, returns the path to the workspace directory
- /// Otherwise returns the current directory.
- pub fn get_workspace_dir(current_dir: &Path) -> PathBuf {
- let mut dir = current_dir.to_path_buf();
- let project_path = dir.clone();
- let logger = Logger::new("tauri:rust");
- while dir.pop() {
- if dir.join("Cargo.toml").exists() {
- match CargoSettings::load(&dir) {
- Ok(cargo_settings) => {
- if let Some(workspace_settings) = cargo_settings.workspace {
- if let Some(members) = workspace_settings.members {
- if members.iter().any(|member| {
- glob::glob(&dir.join(member).to_string_lossy().into_owned())
- .unwrap()
- .any(|p| p.unwrap() == project_path)
- }) {
- return dir;
- }
- }
- }
- }
- Err(e) => {
- logger.warn(format!(
- "Found `{}`, which may define a parent workspace, but \
- failed to parse it. If this is indeed a parent workspace, undefined behavior may occur: \
- \n {:#}",
- dir.display(),
- e
- ));
- }
- }
- }
- }
- // Nothing found walking up the file system, return the starting directory
- current_dir.to_path_buf()
- }
- #[allow(unused_variables)]
- fn tauri_config_to_bundle_settings(
- manifest: &Manifest,
- config: crate::helpers::config::BundleConfig,
- system_tray_config: Option<crate::helpers::config::SystemTrayConfig>,
- updater_config: crate::helpers::config::UpdaterConfig,
- ) -> crate::Result<BundleSettings> {
- #[cfg(windows)]
- let windows_icon_path = PathBuf::from(
- config
- .icon
- .as_ref()
- .and_then(|icons| icons.iter().find(|i| i.ends_with(".ico")).cloned())
- .expect("the bundle config must have a `.ico` icon"),
- );
- #[cfg(not(windows))]
- let windows_icon_path = PathBuf::from("");
- #[allow(unused_mut)]
- let mut resources = config.resources.unwrap_or_default();
- #[allow(unused_mut)]
- let mut depends = config.deb.depends.unwrap_or_default();
- #[cfg(target_os = "linux")]
- {
- if let Some(system_tray_config) = &system_tray_config {
- let mut icon_path = system_tray_config.icon_path.clone();
- icon_path.set_extension("png");
- resources.push(icon_path.display().to_string());
- depends.push("libappindicator3-1".to_string());
- }
- // provides `libwebkit2gtk-4.0.so.37` and all `4.0` versions have the -37 package name
- depends.push("libwebkit2gtk-4.0-37".to_string());
- depends.push("libgtk-3-0".to_string());
- }
- let signing_identity = match std::env::var_os("APPLE_SIGNING_IDENTITY") {
- Some(signing_identity) => Some(
- signing_identity
- .to_str()
- .expect("failed to convert APPLE_SIGNING_IDENTITY to string")
- .to_string(),
- ),
- None => config.macos.signing_identity,
- };
- Ok(BundleSettings {
- identifier: config.identifier,
- icon: config.icon,
- resources: if resources.is_empty() {
- None
- } else {
- Some(resources)
- },
- copyright: config.copyright,
- category: match config.category {
- Some(category) => Some(AppCategory::from_str(&category).map_err(|e| match e {
- Some(e) => anyhow::anyhow!("invalid category, did you mean `{}`?", e),
- None => anyhow::anyhow!("invalid category"),
- })?),
- None => None,
- },
- short_description: config.short_description,
- long_description: config.long_description,
- external_bin: config.external_bin,
- deb: DebianSettings {
- depends: if depends.is_empty() {
- None
- } else {
- Some(depends)
- },
- use_bootstrapper: Some(config.deb.use_bootstrapper),
- files: config.deb.files,
- },
- macos: MacOsSettings {
- frameworks: config.macos.frameworks,
- minimum_system_version: config.macos.minimum_system_version,
- license: config.macos.license,
- use_bootstrapper: Some(config.macos.use_bootstrapper),
- exception_domain: config.macos.exception_domain,
- signing_identity,
- entitlements: config.macos.entitlements,
- info_plist_path: {
- let path = tauri_dir().join("Info.plist");
- if path.exists() {
- Some(path)
- } else {
- None
- }
- },
- },
- windows: WindowsSettings {
- timestamp_url: config.windows.timestamp_url,
- digest_algorithm: config.windows.digest_algorithm,
- certificate_thumbprint: config.windows.certificate_thumbprint,
- wix: config.windows.wix.map(|w| {
- let mut wix = WixSettings::from(w);
- wix.license = wix.license.map(|l| tauri_dir().join(l));
- wix
- }),
- icon_path: windows_icon_path,
- },
- updater: Some(UpdaterSettings {
- active: updater_config.active,
- // we set it to true by default we shouldn't have to use
- // unwrap_or as we have a default value but used to prevent any failing
- dialog: updater_config.dialog.unwrap_or(true),
- pubkey: updater_config.pubkey,
- endpoints: updater_config.endpoints,
- }),
- ..Default::default()
- })
- }
|