rust.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::{
  5. fs::File,
  6. io::Read,
  7. path::{Path, PathBuf},
  8. process::Command,
  9. str::FromStr,
  10. };
  11. use serde::Deserialize;
  12. use crate::helpers::{app_paths::tauri_dir, config::Config};
  13. #[cfg(windows)]
  14. use tauri_bundler::WindowsSettings;
  15. use tauri_bundler::{
  16. AppCategory, BundleBinary, BundleSettings, DebianSettings, MacOsSettings, PackageSettings,
  17. UpdaterSettings,
  18. };
  19. /// The `workspace` section of the app configuration (read from Cargo.toml).
  20. #[derive(Clone, Debug, Deserialize)]
  21. struct WorkspaceSettings {
  22. /// the workspace members.
  23. members: Option<Vec<String>>,
  24. }
  25. #[derive(Clone, Debug, Deserialize)]
  26. struct BinarySettings {
  27. name: String,
  28. path: Option<String>,
  29. }
  30. /// The package settings.
  31. #[derive(Debug, Clone, Deserialize)]
  32. pub struct CargoPackageSettings {
  33. /// the package's name.
  34. pub name: String,
  35. /// the package's version.
  36. pub version: String,
  37. /// the package's description.
  38. pub description: String,
  39. /// the package's homepage.
  40. pub homepage: Option<String>,
  41. /// the package's authors.
  42. pub authors: Option<Vec<String>>,
  43. /// the default binary to run.
  44. pub default_run: Option<String>,
  45. }
  46. /// The Cargo settings (Cargo.toml root descriptor).
  47. #[derive(Clone, Debug, Deserialize)]
  48. struct CargoSettings {
  49. /// the package settings.
  50. ///
  51. /// it's optional because ancestor workspace Cargo.toml files may not have package info.
  52. package: Option<CargoPackageSettings>,
  53. /// the workspace settings.
  54. ///
  55. /// it's present if the read Cargo.toml belongs to a workspace root.
  56. workspace: Option<WorkspaceSettings>,
  57. /// the binary targets configuration.
  58. bin: Option<Vec<BinarySettings>>,
  59. }
  60. impl CargoSettings {
  61. /// Try to load a set of CargoSettings from a "Cargo.toml" file in the specified directory.
  62. fn load(dir: &Path) -> crate::Result<Self> {
  63. let toml_path = dir.join("Cargo.toml");
  64. let mut toml_str = String::new();
  65. let mut toml_file = File::open(toml_path)?;
  66. toml_file.read_to_string(&mut toml_str)?;
  67. toml::from_str(&toml_str).map_err(Into::into)
  68. }
  69. }
  70. #[derive(Deserialize)]
  71. struct CargoBuildConfig {
  72. #[serde(rename = "target-dir")]
  73. target_dir: Option<String>,
  74. }
  75. #[derive(Deserialize)]
  76. struct CargoConfig {
  77. build: Option<CargoBuildConfig>,
  78. }
  79. pub fn build_project(debug: bool) -> crate::Result<()> {
  80. let mut args = vec!["build", "--features=custom-protocol"];
  81. if !debug {
  82. args.push("--release");
  83. }
  84. let status = Command::new("cargo").args(args).status()?;
  85. if !status.success() {
  86. return Err(anyhow::anyhow!(format!(
  87. "Result of `cargo build` operation was unsuccessful: {}",
  88. status
  89. )));
  90. }
  91. Ok(())
  92. }
  93. pub struct AppSettings {
  94. cargo_settings: CargoSettings,
  95. cargo_package_settings: CargoPackageSettings,
  96. package_settings: PackageSettings,
  97. }
  98. impl AppSettings {
  99. pub fn new(config: &Config) -> crate::Result<Self> {
  100. let cargo_settings = CargoSettings::load(&tauri_dir())?;
  101. let cargo_package_settings = match &cargo_settings.package {
  102. Some(package_info) => package_info.clone(),
  103. None => {
  104. return Err(anyhow::anyhow!(
  105. "No package info in the config file".to_owned(),
  106. ))
  107. }
  108. };
  109. let package_settings = PackageSettings {
  110. product_name: config
  111. .package
  112. .product_name
  113. .clone()
  114. .unwrap_or_else(|| cargo_package_settings.name.clone()),
  115. version: config
  116. .package
  117. .version
  118. .clone()
  119. .unwrap_or_else(|| cargo_package_settings.version.clone()),
  120. description: cargo_package_settings.description.clone(),
  121. homepage: cargo_package_settings.homepage.clone(),
  122. authors: cargo_package_settings.authors.clone(),
  123. default_run: cargo_package_settings.default_run.clone(),
  124. };
  125. Ok(Self {
  126. cargo_settings,
  127. cargo_package_settings,
  128. package_settings,
  129. })
  130. }
  131. pub fn cargo_package_settings(&self) -> &CargoPackageSettings {
  132. &self.cargo_package_settings
  133. }
  134. pub fn get_bundle_settings(&self, config: &Config) -> crate::Result<BundleSettings> {
  135. tauri_config_to_bundle_settings(config.tauri.bundle.clone(), config.tauri.updater.clone())
  136. }
  137. pub fn get_out_dir(&self, debug: bool) -> crate::Result<PathBuf> {
  138. let tauri_dir = tauri_dir();
  139. let workspace_dir = get_workspace_dir(&tauri_dir);
  140. get_target_dir(&workspace_dir, None, !debug)
  141. }
  142. pub fn get_package_settings(&self) -> PackageSettings {
  143. self.package_settings.clone()
  144. }
  145. pub fn get_binaries(&self, config: &Config) -> crate::Result<Vec<BundleBinary>> {
  146. let mut binaries: Vec<BundleBinary> = vec![];
  147. if let Some(bin) = &self.cargo_settings.bin {
  148. let default_run = self
  149. .package_settings
  150. .default_run
  151. .clone()
  152. .unwrap_or_else(|| "".to_string());
  153. for binary in bin {
  154. binaries.push(
  155. if binary.name.as_str() == self.cargo_package_settings.name
  156. || binary.name.as_str() == default_run
  157. {
  158. BundleBinary::new(
  159. config
  160. .package
  161. .product_name
  162. .clone()
  163. .unwrap_or_else(|| binary.name.clone()),
  164. true,
  165. )
  166. } else {
  167. BundleBinary::new(binary.name.clone(), false)
  168. }
  169. .set_src_path(binary.path.clone()),
  170. )
  171. }
  172. }
  173. let mut bins_path = tauri_dir();
  174. bins_path.push("src/bin");
  175. if let Ok(fs_bins) = std::fs::read_dir(bins_path) {
  176. for entry in fs_bins {
  177. let path = entry?.path();
  178. if let Some(name) = path.file_stem() {
  179. let bin_exists = binaries.iter().any(|bin| {
  180. bin.name() == name || path.ends_with(bin.src_path().unwrap_or(&"".to_string()))
  181. });
  182. if !bin_exists {
  183. binaries.push(BundleBinary::new(name.to_string_lossy().to_string(), false))
  184. }
  185. }
  186. }
  187. }
  188. if let Some(default_run) = self.package_settings.default_run.as_ref() {
  189. match binaries.iter_mut().find(|bin| bin.name() == default_run) {
  190. Some(bin) => {
  191. if let Some(product_name) = config.package.product_name.clone() {
  192. bin.set_name(product_name);
  193. }
  194. }
  195. None => {
  196. binaries.push(BundleBinary::new(
  197. config
  198. .package
  199. .product_name
  200. .clone()
  201. .unwrap_or_else(|| default_run.to_string()),
  202. true,
  203. ));
  204. }
  205. }
  206. }
  207. match binaries.len() {
  208. 0 => binaries.push(BundleBinary::new(
  209. self.package_settings.product_name.clone(),
  210. true,
  211. )),
  212. 1 => binaries.get_mut(0).unwrap().set_main(true),
  213. _ => {}
  214. }
  215. Ok(binaries)
  216. }
  217. }
  218. /// This function determines where 'target' dir is and suffixes it with 'release' or 'debug'
  219. /// to determine where the compiled binary will be located.
  220. fn get_target_dir(
  221. project_root_dir: &Path,
  222. target: Option<String>,
  223. is_release: bool,
  224. ) -> crate::Result<PathBuf> {
  225. let mut path: PathBuf = match std::env::var_os("CARGO_TARGET_DIR") {
  226. Some(target_dir) => target_dir.into(),
  227. None => {
  228. let mut root_dir = project_root_dir.to_path_buf();
  229. let target_path: Option<PathBuf> = loop {
  230. // cargo reads configs under .cargo/config.toml or .cargo/config
  231. let mut cargo_config_path = root_dir.join(".cargo/config");
  232. if !cargo_config_path.exists() {
  233. cargo_config_path = root_dir.join(".cargo/config.toml");
  234. }
  235. // if the path exists, parse it
  236. if cargo_config_path.exists() {
  237. let mut config_str = String::new();
  238. let mut config_file = File::open(cargo_config_path)?;
  239. config_file.read_to_string(&mut config_str)?;
  240. let config: CargoConfig = toml::from_str(&config_str)?;
  241. if let Some(build) = config.build {
  242. if let Some(target_dir) = build.target_dir {
  243. break Some(target_dir.into());
  244. }
  245. }
  246. }
  247. if !root_dir.pop() {
  248. break None;
  249. }
  250. };
  251. target_path.unwrap_or_else(|| project_root_dir.join("target"))
  252. }
  253. };
  254. if let Some(ref triple) = target {
  255. path.push(triple);
  256. }
  257. path.push(if is_release { "release" } else { "debug" });
  258. Ok(path)
  259. }
  260. /// Walks up the file system, looking for a Cargo.toml file
  261. /// 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].
  262. ///
  263. /// If this package is part of a workspace, returns the path to the workspace directory
  264. /// Otherwise returns the current directory.
  265. pub fn get_workspace_dir(current_dir: &Path) -> PathBuf {
  266. let mut dir = current_dir.to_path_buf();
  267. let project_path = dir.clone();
  268. while dir.pop() {
  269. if let Ok(cargo_settings) = CargoSettings::load(&dir) {
  270. if let Some(workspace_settings) = cargo_settings.workspace {
  271. if let Some(members) = workspace_settings.members {
  272. if members
  273. .iter()
  274. .any(|member| dir.join(member) == project_path)
  275. {
  276. return dir;
  277. }
  278. }
  279. }
  280. }
  281. }
  282. // Nothing found walking up the file system, return the starting directory
  283. current_dir.to_path_buf()
  284. }
  285. fn tauri_config_to_bundle_settings(
  286. config: crate::helpers::config::BundleConfig,
  287. updater_config: crate::helpers::config::UpdaterConfig,
  288. ) -> crate::Result<BundleSettings> {
  289. Ok(BundleSettings {
  290. identifier: config.identifier,
  291. icon: config.icon,
  292. resources: config.resources,
  293. copyright: config.copyright,
  294. category: match config.category {
  295. Some(category) => Some(AppCategory::from_str(&category).map_err(|e| match e {
  296. Some(e) => anyhow::anyhow!("invalid category, did you mean `{}`?", e),
  297. None => anyhow::anyhow!("invalid category"),
  298. })?),
  299. None => None,
  300. },
  301. short_description: config.short_description,
  302. long_description: config.long_description,
  303. external_bin: config.external_bin,
  304. deb: DebianSettings {
  305. depends: config.deb.depends,
  306. use_bootstrapper: Some(config.deb.use_bootstrapper),
  307. files: config.deb.files,
  308. },
  309. macos: MacOsSettings {
  310. frameworks: config.macos.frameworks,
  311. minimum_system_version: config.macos.minimum_system_version,
  312. license: config.macos.license,
  313. use_bootstrapper: Some(config.macos.use_bootstrapper),
  314. exception_domain: config.macos.exception_domain,
  315. signing_identity: config.macos.signing_identity,
  316. entitlements: config.macos.entitlements,
  317. },
  318. #[cfg(windows)]
  319. windows: WindowsSettings {
  320. timestamp_url: config.windows.timestamp_url,
  321. digest_algorithm: config.windows.digest_algorithm,
  322. certificate_thumbprint: config.windows.certificate_thumbprint,
  323. wix: config.windows.wix.map(|w| w.into()),
  324. },
  325. updater: Some(UpdaterSettings {
  326. active: updater_config.active,
  327. // we set it to true by default we shouldn't have to use
  328. // unwrap_or as we have a default value but used to prevent any failing
  329. dialog: updater_config.dialog.unwrap_or(true),
  330. pubkey: updater_config.pubkey,
  331. endpoints: updater_config.endpoints,
  332. }),
  333. ..Default::default()
  334. })
  335. }