rust.rs 14 KB

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