rust.rs 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::{
  5. collections::HashMap,
  6. ffi::OsStr,
  7. fs::{File, FileType},
  8. io::{BufRead, Read, Write},
  9. path::{Path, PathBuf},
  10. process::Command,
  11. str::FromStr,
  12. sync::{mpsc::sync_channel, Arc, Mutex},
  13. time::{Duration, Instant},
  14. };
  15. use anyhow::Context;
  16. use glob::glob;
  17. use ignore::gitignore::{Gitignore, GitignoreBuilder};
  18. use notify::RecursiveMode;
  19. use notify_debouncer_mini::new_debouncer;
  20. use serde::{Deserialize, Deserializer};
  21. use tauri_bundler::{
  22. AppCategory, AppImageSettings, BundleBinary, BundleSettings, DebianSettings, DmgSettings,
  23. MacOsSettings, PackageSettings, Position, RpmSettings, Size, UpdaterSettings, WindowsSettings,
  24. };
  25. use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol, Updater};
  26. use super::{AppSettings, DevProcess, ExitReason, Interface};
  27. use crate::{
  28. helpers::{
  29. app_paths::{app_dir, tauri_dir},
  30. config::{nsis_settings, reload as reload_config, wix_settings, BundleResources, Config},
  31. },
  32. ConfigValue,
  33. };
  34. use tauri_utils::{display_path, platform::Target};
  35. mod cargo_config;
  36. mod desktop;
  37. pub mod installation;
  38. pub mod manifest;
  39. use crate::helpers::config::custom_sign_settings;
  40. use cargo_config::Config as CargoConfig;
  41. use manifest::{rewrite_manifest, Manifest};
  42. #[derive(Debug, Default, Clone)]
  43. pub struct Options {
  44. pub runner: Option<String>,
  45. pub debug: bool,
  46. pub target: Option<String>,
  47. pub features: Option<Vec<String>>,
  48. pub args: Vec<String>,
  49. pub config: Option<ConfigValue>,
  50. pub no_watch: bool,
  51. }
  52. impl From<crate::build::Options> for Options {
  53. fn from(options: crate::build::Options) -> Self {
  54. Self {
  55. runner: options.runner,
  56. debug: options.debug,
  57. target: options.target,
  58. features: options.features,
  59. args: options.args,
  60. config: options.config,
  61. no_watch: true,
  62. }
  63. }
  64. }
  65. impl From<crate::bundle::Options> for Options {
  66. fn from(options: crate::bundle::Options) -> Self {
  67. Self {
  68. debug: options.debug,
  69. config: options.config,
  70. target: options.target,
  71. features: options.features,
  72. no_watch: true,
  73. ..Default::default()
  74. }
  75. }
  76. }
  77. impl From<crate::dev::Options> for Options {
  78. fn from(options: crate::dev::Options) -> Self {
  79. Self {
  80. runner: options.runner,
  81. debug: !options.release_mode,
  82. target: options.target,
  83. features: options.features,
  84. args: options.args,
  85. config: options.config,
  86. no_watch: options.no_watch,
  87. }
  88. }
  89. }
  90. #[derive(Debug, Clone)]
  91. pub struct MobileOptions {
  92. pub debug: bool,
  93. pub features: Option<Vec<String>>,
  94. pub args: Vec<String>,
  95. pub config: Option<ConfigValue>,
  96. pub no_watch: bool,
  97. }
  98. #[derive(Debug)]
  99. pub struct RustupTarget {
  100. name: String,
  101. installed: bool,
  102. }
  103. pub struct Rust {
  104. app_settings: Arc<RustAppSettings>,
  105. config_features: Vec<String>,
  106. available_targets: Option<Vec<RustupTarget>>,
  107. main_binary_name: Option<String>,
  108. }
  109. impl Interface for Rust {
  110. type AppSettings = RustAppSettings;
  111. fn new(config: &Config, target: Option<String>) -> crate::Result<Self> {
  112. let manifest = {
  113. let (tx, rx) = sync_channel(1);
  114. let mut watcher = new_debouncer(Duration::from_secs(1), move |r| {
  115. if let Ok(events) = r {
  116. let _ = tx.send(events);
  117. }
  118. })
  119. .unwrap();
  120. watcher
  121. .watcher()
  122. .watch(&tauri_dir().join("Cargo.toml"), RecursiveMode::Recursive)?;
  123. let (manifest, _modified) = rewrite_manifest(config)?;
  124. let now = Instant::now();
  125. let timeout = Duration::from_secs(2);
  126. loop {
  127. if now.elapsed() >= timeout {
  128. break;
  129. }
  130. if rx.try_recv().is_ok() {
  131. break;
  132. }
  133. }
  134. manifest
  135. };
  136. let target_ios = target.as_ref().map_or(false, |target| {
  137. target.ends_with("ios") || target.ends_with("ios-sim")
  138. });
  139. if target_ios {
  140. std::env::set_var(
  141. "IPHONEOS_DEPLOYMENT_TARGET",
  142. &config.bundle.ios.minimum_system_version,
  143. );
  144. } else if let Some(minimum_system_version) = &config.bundle.macos.minimum_system_version {
  145. std::env::set_var("MACOSX_DEPLOYMENT_TARGET", minimum_system_version);
  146. }
  147. let app_settings = RustAppSettings::new(config, manifest, target)?;
  148. Ok(Self {
  149. app_settings: Arc::new(app_settings),
  150. config_features: config.build.features.clone().unwrap_or_default(),
  151. main_binary_name: config.main_binary_name.clone(),
  152. available_targets: None,
  153. })
  154. }
  155. fn app_settings(&self) -> Arc<Self::AppSettings> {
  156. self.app_settings.clone()
  157. }
  158. fn build(&mut self, options: Options) -> crate::Result<PathBuf> {
  159. desktop::build(
  160. options,
  161. &self.app_settings,
  162. &mut self.available_targets,
  163. self.config_features.clone(),
  164. self.main_binary_name.as_deref(),
  165. )
  166. }
  167. fn dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
  168. &mut self,
  169. mut options: Options,
  170. on_exit: F,
  171. ) -> crate::Result<()> {
  172. let on_exit = Arc::new(on_exit);
  173. let mut run_args = Vec::new();
  174. dev_options(
  175. false,
  176. &mut options.args,
  177. &mut run_args,
  178. &mut options.features,
  179. &self.app_settings,
  180. );
  181. if options.no_watch {
  182. let (tx, rx) = sync_channel(1);
  183. self.run_dev(options, run_args, move |status, reason| {
  184. tx.send(()).unwrap();
  185. on_exit(status, reason)
  186. })?;
  187. rx.recv().unwrap();
  188. Ok(())
  189. } else {
  190. let config = options.config.clone().map(|c| c.0);
  191. let run = Arc::new(|rust: &mut Rust| {
  192. let on_exit = on_exit.clone();
  193. rust.run_dev(options.clone(), run_args.clone(), move |status, reason| {
  194. on_exit(status, reason)
  195. })
  196. });
  197. self.run_dev_watcher(config, run)
  198. }
  199. }
  200. fn mobile_dev<R: Fn(MobileOptions) -> crate::Result<Box<dyn DevProcess + Send>>>(
  201. &mut self,
  202. mut options: MobileOptions,
  203. runner: R,
  204. ) -> crate::Result<()> {
  205. let mut run_args = Vec::new();
  206. dev_options(
  207. true,
  208. &mut options.args,
  209. &mut run_args,
  210. &mut options.features,
  211. &self.app_settings,
  212. );
  213. if options.no_watch {
  214. runner(options)?;
  215. Ok(())
  216. } else {
  217. let config = options.config.clone().map(|c| c.0);
  218. let run = Arc::new(|_rust: &mut Rust| runner(options.clone()));
  219. self.run_dev_watcher(config, run)
  220. }
  221. }
  222. fn env(&self) -> HashMap<&str, String> {
  223. let mut env = HashMap::new();
  224. env.insert(
  225. "TAURI_ENV_TARGET_TRIPLE",
  226. self.app_settings.target_triple.clone(),
  227. );
  228. let target_triple = &self.app_settings.target_triple;
  229. let target_components: Vec<&str> = target_triple.split('-').collect();
  230. let (arch, host, _host_env) = match target_components.as_slice() {
  231. // 3 components like aarch64-apple-darwin
  232. [arch, _, host] => (*arch, *host, None),
  233. // 4 components like x86_64-pc-windows-msvc and aarch64-apple-ios-sim
  234. [arch, _, host, host_env] => (*arch, *host, Some(*host_env)),
  235. _ => {
  236. log::warn!("Invalid target triple: {}", target_triple);
  237. return env;
  238. }
  239. };
  240. env.insert("TAURI_ENV_ARCH", arch.into());
  241. env.insert("TAURI_ENV_PLATFORM", host.into());
  242. env.insert(
  243. "TAURI_ENV_FAMILY",
  244. match host {
  245. "windows" => "windows".into(),
  246. _ => "unix".into(),
  247. },
  248. );
  249. env
  250. }
  251. }
  252. struct IgnoreMatcher(Vec<Gitignore>);
  253. impl IgnoreMatcher {
  254. fn is_ignore(&self, path: &Path, is_dir: bool) -> bool {
  255. for gitignore in &self.0 {
  256. if path.starts_with(gitignore.path())
  257. && gitignore
  258. .matched_path_or_any_parents(path, is_dir)
  259. .is_ignore()
  260. {
  261. return true;
  262. }
  263. }
  264. false
  265. }
  266. }
  267. fn build_ignore_matcher(dir: &Path) -> IgnoreMatcher {
  268. let mut matchers = Vec::new();
  269. // ignore crate doesn't expose an API to build `ignore::gitignore::GitIgnore`
  270. // with custom ignore file names so we have to walk the directory and collect
  271. // our custom ignore files and add it using `ignore::gitignore::GitIgnoreBuilder::add`
  272. for entry in ignore::WalkBuilder::new(dir)
  273. .require_git(false)
  274. .ignore(false)
  275. .overrides(
  276. ignore::overrides::OverrideBuilder::new(dir)
  277. .add(".taurignore")
  278. .unwrap()
  279. .build()
  280. .unwrap(),
  281. )
  282. .build()
  283. .flatten()
  284. {
  285. let path = entry.path();
  286. if path.file_name() == Some(OsStr::new(".taurignore")) {
  287. let mut ignore_builder = GitignoreBuilder::new(path.parent().unwrap());
  288. ignore_builder.add(path);
  289. if let Ok(ignore_file) = std::env::var("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
  290. ignore_builder.add(dir.join(ignore_file));
  291. }
  292. for line in crate::dev::TAURI_CLI_BUILTIN_WATCHER_IGNORE_FILE
  293. .lines()
  294. .map_while(Result::ok)
  295. {
  296. let _ = ignore_builder.add_line(None, &line);
  297. }
  298. matchers.push(ignore_builder.build().unwrap());
  299. }
  300. }
  301. IgnoreMatcher(matchers)
  302. }
  303. fn lookup<F: FnMut(FileType, PathBuf)>(dir: &Path, mut f: F) {
  304. let mut default_gitignore = std::env::temp_dir();
  305. default_gitignore.push(".tauri");
  306. let _ = std::fs::create_dir_all(&default_gitignore);
  307. default_gitignore.push(".gitignore");
  308. if !default_gitignore.exists() {
  309. if let Ok(mut file) = std::fs::File::create(default_gitignore.clone()) {
  310. let _ = file.write_all(crate::dev::TAURI_CLI_BUILTIN_WATCHER_IGNORE_FILE);
  311. }
  312. }
  313. let mut builder = ignore::WalkBuilder::new(dir);
  314. builder.add_custom_ignore_filename(".taurignore");
  315. let _ = builder.add_ignore(default_gitignore);
  316. if let Ok(ignore_file) = std::env::var("TAURI_CLI_WATCHER_IGNORE_FILENAME") {
  317. builder.add_ignore(ignore_file);
  318. }
  319. builder.require_git(false).ignore(false).max_depth(Some(1));
  320. for entry in builder.build().flatten() {
  321. f(entry.file_type().unwrap(), dir.join(entry.path()));
  322. }
  323. }
  324. fn shared_options(
  325. mobile: bool,
  326. args: &mut Vec<String>,
  327. features: &mut Option<Vec<String>>,
  328. app_settings: &RustAppSettings,
  329. ) {
  330. if mobile {
  331. args.push("--lib".into());
  332. features
  333. .get_or_insert(Vec::new())
  334. .push("tauri/rustls-tls".into());
  335. } else {
  336. args.push("--bins".into());
  337. let all_features = app_settings
  338. .manifest
  339. .lock()
  340. .unwrap()
  341. .all_enabled_features(if let Some(f) = features { f } else { &[] });
  342. if !all_features.contains(&"tauri/rustls-tls".into()) {
  343. features
  344. .get_or_insert(Vec::new())
  345. .push("tauri/native-tls".into());
  346. }
  347. }
  348. }
  349. fn dev_options(
  350. mobile: bool,
  351. args: &mut Vec<String>,
  352. run_args: &mut Vec<String>,
  353. features: &mut Option<Vec<String>>,
  354. app_settings: &RustAppSettings,
  355. ) {
  356. let mut dev_args = Vec::new();
  357. let mut reached_run_args = false;
  358. for arg in args.clone() {
  359. if reached_run_args {
  360. run_args.push(arg);
  361. } else if arg == "--" {
  362. reached_run_args = true;
  363. } else {
  364. dev_args.push(arg);
  365. }
  366. }
  367. *args = dev_args;
  368. shared_options(mobile, args, features, app_settings);
  369. if !args.contains(&"--no-default-features".into()) {
  370. let manifest_features = app_settings.manifest.lock().unwrap().features();
  371. let enable_features: Vec<String> = manifest_features
  372. .get("default")
  373. .cloned()
  374. .unwrap_or_default()
  375. .into_iter()
  376. .filter(|feature| {
  377. if let Some(manifest_feature) = manifest_features.get(feature) {
  378. !manifest_feature.contains(&"tauri/custom-protocol".into())
  379. } else {
  380. feature != "tauri/custom-protocol"
  381. }
  382. })
  383. .collect();
  384. args.push("--no-default-features".into());
  385. if !enable_features.is_empty() {
  386. features.get_or_insert(Vec::new()).extend(enable_features);
  387. }
  388. }
  389. }
  390. // Copied from https://github.com/rust-lang/cargo/blob/69255bb10de7f74511b5cef900a9d102247b6029/src/cargo/core/workspace.rs#L665
  391. fn expand_member_path(path: &Path) -> crate::Result<Vec<PathBuf>> {
  392. let Some(path) = path.to_str() else {
  393. return Err(anyhow::anyhow!("path is not UTF-8 compatible"));
  394. };
  395. let res = glob(path).with_context(|| format!("could not parse pattern `{}`", &path))?;
  396. let res = res
  397. .map(|p| p.with_context(|| format!("unable to match path to pattern `{}`", &path)))
  398. .collect::<Result<Vec<_>, _>>()?;
  399. Ok(res)
  400. }
  401. fn get_watch_folders() -> crate::Result<Vec<PathBuf>> {
  402. let tauri_path = tauri_dir();
  403. let workspace_path = get_workspace_dir()?;
  404. // We always want to watch the main tauri folder.
  405. let mut watch_folders = vec![tauri_path.to_path_buf()];
  406. // We also try to watch workspace members, no matter if the tauri cargo project is the workspace root or a workspace member
  407. let cargo_settings = CargoSettings::load(&workspace_path)?;
  408. if let Some(members) = cargo_settings.workspace.and_then(|w| w.members) {
  409. for p in members {
  410. let p = workspace_path.join(p);
  411. match expand_member_path(&p) {
  412. // Sometimes expand_member_path returns an empty vec, for example if the path contains `[]` as in `C:/[abc]/project/`.
  413. // Cargo won't complain unless theres a workspace.members config with glob patterns so we should support it too.
  414. Ok(expanded_paths) => {
  415. if expanded_paths.is_empty() {
  416. watch_folders.push(p);
  417. } else {
  418. watch_folders.extend(expanded_paths);
  419. }
  420. }
  421. Err(err) => {
  422. // If this fails cargo itself should fail too. But we still try to keep going with the unexpanded path.
  423. log::error!("Error watching {}: {}", p.display(), err.to_string());
  424. watch_folders.push(p);
  425. }
  426. };
  427. }
  428. }
  429. Ok(watch_folders)
  430. }
  431. impl Rust {
  432. pub fn build_options(
  433. &self,
  434. args: &mut Vec<String>,
  435. features: &mut Option<Vec<String>>,
  436. mobile: bool,
  437. ) {
  438. features
  439. .get_or_insert(Vec::new())
  440. .push("tauri/custom-protocol".into());
  441. shared_options(mobile, args, features, &self.app_settings);
  442. }
  443. fn run_dev<F: Fn(Option<i32>, ExitReason) + Send + Sync + 'static>(
  444. &mut self,
  445. options: Options,
  446. run_args: Vec<String>,
  447. on_exit: F,
  448. ) -> crate::Result<Box<dyn DevProcess + Send>> {
  449. desktop::run_dev(
  450. options,
  451. run_args,
  452. &mut self.available_targets,
  453. self.config_features.clone(),
  454. &self.app_settings,
  455. self.main_binary_name.clone(),
  456. on_exit,
  457. )
  458. .map(|c| Box::new(c) as Box<dyn DevProcess + Send>)
  459. }
  460. fn run_dev_watcher<F: Fn(&mut Rust) -> crate::Result<Box<dyn DevProcess + Send>>>(
  461. &mut self,
  462. config: Option<serde_json::Value>,
  463. run: Arc<F>,
  464. ) -> crate::Result<()> {
  465. let child = run(self)?;
  466. let process = Arc::new(Mutex::new(child));
  467. let (tx, rx) = sync_channel(1);
  468. let app_path = app_dir();
  469. let watch_folders = get_watch_folders()?;
  470. let common_ancestor = common_path::common_path_all(watch_folders.iter().map(Path::new))
  471. .expect("watch_folders should not be empty");
  472. let ignore_matcher = build_ignore_matcher(&common_ancestor);
  473. let mut watcher = new_debouncer(Duration::from_secs(1), move |r| {
  474. if let Ok(events) = r {
  475. tx.send(events).unwrap()
  476. }
  477. })
  478. .unwrap();
  479. for path in watch_folders {
  480. if !ignore_matcher.is_ignore(&path, true) {
  481. log::info!("Watching {} for changes...", display_path(&path));
  482. lookup(&path, |file_type, p| {
  483. if p != path {
  484. log::debug!("Watching {} for changes...", display_path(&p));
  485. let _ = watcher.watcher().watch(
  486. &p,
  487. if file_type.is_dir() {
  488. RecursiveMode::Recursive
  489. } else {
  490. RecursiveMode::NonRecursive
  491. },
  492. );
  493. }
  494. });
  495. }
  496. }
  497. loop {
  498. if let Ok(events) = rx.recv() {
  499. for event in events {
  500. let event_path = event.path;
  501. if !ignore_matcher.is_ignore(&event_path, event_path.is_dir()) {
  502. if is_configuration_file(self.app_settings.target, &event_path) {
  503. if let Ok(config) = reload_config(config.as_ref()) {
  504. let (manifest, modified) =
  505. rewrite_manifest(config.lock().unwrap().as_ref().unwrap())?;
  506. if modified {
  507. *self.app_settings.manifest.lock().unwrap() = manifest;
  508. // no need to run the watcher logic, the manifest was modified
  509. // and it will trigger the watcher again
  510. continue;
  511. }
  512. }
  513. }
  514. log::info!(
  515. "File {} changed. Rebuilding application...",
  516. display_path(event_path.strip_prefix(app_path).unwrap_or(&event_path))
  517. );
  518. let mut p = process.lock().unwrap();
  519. p.kill().with_context(|| "failed to kill app process")?;
  520. // wait for the process to exit
  521. // note that on mobile, kill() already waits for the process to exit (duct implementation)
  522. loop {
  523. if !matches!(p.try_wait(), Ok(None)) {
  524. break;
  525. }
  526. }
  527. *p = run(self)?;
  528. }
  529. }
  530. }
  531. }
  532. }
  533. }
  534. // Taken from https://github.com/rust-lang/cargo/blob/70898e522116f6c23971e2a554b2dc85fd4c84cd/src/cargo/util/toml/mod.rs#L1008-L1065
  535. /// Enum that allows for the parsing of `field.workspace = true` in a Cargo.toml
  536. ///
  537. /// It allows for things to be inherited from a workspace or defined as needed
  538. #[derive(Clone, Debug)]
  539. pub enum MaybeWorkspace<T> {
  540. Workspace(TomlWorkspaceField),
  541. Defined(T),
  542. }
  543. impl<'de, T: Deserialize<'de>> serde::de::Deserialize<'de> for MaybeWorkspace<T> {
  544. fn deserialize<D>(deserializer: D) -> Result<MaybeWorkspace<T>, D::Error>
  545. where
  546. D: serde::de::Deserializer<'de>,
  547. {
  548. let value = serde_value::Value::deserialize(deserializer)?;
  549. if let Ok(workspace) = TomlWorkspaceField::deserialize(
  550. serde_value::ValueDeserializer::<D::Error>::new(value.clone()),
  551. ) {
  552. return Ok(MaybeWorkspace::Workspace(workspace));
  553. }
  554. T::deserialize(serde_value::ValueDeserializer::<D::Error>::new(value))
  555. .map(MaybeWorkspace::Defined)
  556. }
  557. }
  558. impl<T> MaybeWorkspace<T> {
  559. fn resolve(
  560. self,
  561. label: &str,
  562. get_ws_field: impl FnOnce() -> anyhow::Result<T>,
  563. ) -> anyhow::Result<T> {
  564. match self {
  565. MaybeWorkspace::Defined(value) => Ok(value),
  566. MaybeWorkspace::Workspace(TomlWorkspaceField { workspace: true }) => {
  567. get_ws_field().context(format!(
  568. "error inheriting `{label}` from workspace root manifest's `workspace.package.{label}`"
  569. ))
  570. }
  571. MaybeWorkspace::Workspace(TomlWorkspaceField { workspace: false }) => Err(anyhow::anyhow!(
  572. "`workspace=false` is unsupported for `package.{}`",
  573. label,
  574. )),
  575. }
  576. }
  577. fn _as_defined(&self) -> Option<&T> {
  578. match self {
  579. MaybeWorkspace::Workspace(_) => None,
  580. MaybeWorkspace::Defined(defined) => Some(defined),
  581. }
  582. }
  583. }
  584. #[derive(Deserialize, Clone, Debug)]
  585. pub struct TomlWorkspaceField {
  586. workspace: bool,
  587. }
  588. /// The `workspace` section of the app configuration (read from Cargo.toml).
  589. #[derive(Clone, Debug, Deserialize)]
  590. struct WorkspaceSettings {
  591. /// the workspace members.
  592. members: Option<Vec<String>>,
  593. package: Option<WorkspacePackageSettings>,
  594. }
  595. #[derive(Clone, Debug, Deserialize)]
  596. struct WorkspacePackageSettings {
  597. authors: Option<Vec<String>>,
  598. description: Option<String>,
  599. homepage: Option<String>,
  600. version: Option<String>,
  601. license: Option<String>,
  602. }
  603. #[derive(Clone, Debug, Deserialize)]
  604. struct BinarySettings {
  605. name: String,
  606. path: Option<String>,
  607. }
  608. /// The package settings.
  609. #[derive(Debug, Clone, Deserialize)]
  610. #[serde(rename_all = "kebab-case")]
  611. pub struct CargoPackageSettings {
  612. /// the package's name.
  613. pub name: String,
  614. /// the package's version.
  615. pub version: Option<MaybeWorkspace<String>>,
  616. /// the package's description.
  617. pub description: Option<MaybeWorkspace<String>>,
  618. /// the package's homepage.
  619. pub homepage: Option<MaybeWorkspace<String>>,
  620. /// the package's authors.
  621. pub authors: Option<MaybeWorkspace<Vec<String>>>,
  622. /// the package's license.
  623. pub license: Option<MaybeWorkspace<String>>,
  624. /// the default binary to run.
  625. pub default_run: Option<String>,
  626. }
  627. /// The Cargo settings (Cargo.toml root descriptor).
  628. #[derive(Clone, Debug, Deserialize)]
  629. struct CargoSettings {
  630. /// the package settings.
  631. ///
  632. /// it's optional because ancestor workspace Cargo.toml files may not have package info.
  633. package: Option<CargoPackageSettings>,
  634. /// the workspace settings.
  635. ///
  636. /// it's present if the read Cargo.toml belongs to a workspace root.
  637. workspace: Option<WorkspaceSettings>,
  638. /// the binary targets configuration.
  639. bin: Option<Vec<BinarySettings>>,
  640. }
  641. impl CargoSettings {
  642. /// Try to load a set of CargoSettings from a "Cargo.toml" file in the specified directory.
  643. fn load(dir: &Path) -> crate::Result<Self> {
  644. let toml_path = dir.join("Cargo.toml");
  645. let mut toml_str = String::new();
  646. let mut toml_file = File::open(toml_path).with_context(|| "failed to open Cargo.toml")?;
  647. toml_file
  648. .read_to_string(&mut toml_str)
  649. .with_context(|| "failed to read Cargo.toml")?;
  650. toml::from_str(&toml_str)
  651. .with_context(|| "failed to parse Cargo.toml")
  652. .map_err(Into::into)
  653. }
  654. }
  655. pub struct RustAppSettings {
  656. manifest: Mutex<Manifest>,
  657. cargo_settings: CargoSettings,
  658. cargo_package_settings: CargoPackageSettings,
  659. cargo_ws_package_settings: Option<WorkspacePackageSettings>,
  660. package_settings: PackageSettings,
  661. cargo_config: CargoConfig,
  662. target_triple: String,
  663. target: Target,
  664. }
  665. #[derive(Deserialize)]
  666. #[serde(untagged)]
  667. enum DesktopDeepLinks {
  668. One(DeepLinkProtocol),
  669. List(Vec<DeepLinkProtocol>),
  670. }
  671. #[derive(Deserialize)]
  672. pub struct UpdaterConfig {
  673. /// Signature public key.
  674. pub pubkey: String,
  675. /// The Windows configuration for the updater.
  676. #[serde(default)]
  677. pub windows: UpdaterWindowsConfig,
  678. }
  679. /// Install modes for the Windows update.
  680. #[derive(Debug, PartialEq, Eq, Clone)]
  681. pub enum WindowsUpdateInstallMode {
  682. /// Specifies there's a basic UI during the installation process, including a final dialog box at the end.
  683. BasicUi,
  684. /// The quiet mode means there's no user interaction required.
  685. /// Requires admin privileges if the installer does.
  686. Quiet,
  687. /// Specifies unattended mode, which means the installation only shows a progress bar.
  688. Passive,
  689. // to add more modes, we need to check if the updater relaunch makes sense
  690. // i.e. for a full UI mode, the user can also mark the installer to start the app
  691. }
  692. impl Default for WindowsUpdateInstallMode {
  693. fn default() -> Self {
  694. Self::Passive
  695. }
  696. }
  697. impl<'de> Deserialize<'de> for WindowsUpdateInstallMode {
  698. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  699. where
  700. D: Deserializer<'de>,
  701. {
  702. let s = String::deserialize(deserializer)?;
  703. match s.to_lowercase().as_str() {
  704. "basicui" => Ok(Self::BasicUi),
  705. "quiet" => Ok(Self::Quiet),
  706. "passive" => Ok(Self::Passive),
  707. _ => Err(serde::de::Error::custom(format!(
  708. "unknown update install mode '{s}'"
  709. ))),
  710. }
  711. }
  712. }
  713. impl WindowsUpdateInstallMode {
  714. /// Returns the associated `msiexec.exe` arguments.
  715. pub fn msiexec_args(&self) -> &'static [&'static str] {
  716. match self {
  717. Self::BasicUi => &["/qb+"],
  718. Self::Quiet => &["/quiet"],
  719. Self::Passive => &["/passive"],
  720. }
  721. }
  722. }
  723. #[derive(Default, Deserialize)]
  724. #[serde(rename_all = "camelCase")]
  725. pub struct UpdaterWindowsConfig {
  726. #[serde(default, alias = "install-mode")]
  727. pub install_mode: WindowsUpdateInstallMode,
  728. }
  729. impl AppSettings for RustAppSettings {
  730. fn get_package_settings(&self) -> PackageSettings {
  731. self.package_settings.clone()
  732. }
  733. fn get_bundle_settings(
  734. &self,
  735. config: &Config,
  736. features: &[String],
  737. ) -> crate::Result<BundleSettings> {
  738. let arch64bits =
  739. self.target_triple.starts_with("x86_64") || self.target_triple.starts_with("aarch64");
  740. let updater_enabled = config.bundle.create_updater_artifacts != Updater::Bool(false);
  741. let v1_compatible = matches!(config.bundle.create_updater_artifacts, Updater::String(_));
  742. let updater_settings = if updater_enabled {
  743. let updater: UpdaterConfig = serde_json::from_value(
  744. config
  745. .plugins
  746. .0
  747. .get("updater")
  748. .ok_or_else(|| {
  749. anyhow::anyhow!("failed to get updater configuration: plugins > updater doesn't exist")
  750. })?
  751. .clone(),
  752. )?;
  753. Some(UpdaterSettings {
  754. v1_compatible,
  755. pubkey: updater.pubkey,
  756. msiexec_args: updater.windows.install_mode.msiexec_args(),
  757. })
  758. } else {
  759. None
  760. };
  761. let mut settings = tauri_config_to_bundle_settings(
  762. self,
  763. features,
  764. config.identifier.clone(),
  765. config.bundle.clone(),
  766. updater_settings,
  767. arch64bits,
  768. )?;
  769. if let Some(plugin_config) = config
  770. .plugins
  771. .0
  772. .get("deep-link")
  773. .and_then(|c| c.get("desktop").cloned())
  774. {
  775. let protocols: DesktopDeepLinks = serde_json::from_value(plugin_config.clone())?;
  776. settings.deep_link_protocols = Some(match protocols {
  777. DesktopDeepLinks::One(p) => vec![p],
  778. DesktopDeepLinks::List(p) => p,
  779. });
  780. }
  781. Ok(settings)
  782. }
  783. fn app_binary_path(&self, options: &Options) -> crate::Result<PathBuf> {
  784. let binaries = self.get_binaries()?;
  785. let bin_name = binaries
  786. .iter()
  787. .find(|x| x.main())
  788. .context("failed to find main binary, make sure you have a `package > default-run` in the Cargo.toml file")?
  789. .name();
  790. let out_dir = self
  791. .out_dir(options)
  792. .context("failed to get project out directory")?;
  793. let ext = if self.target_triple.contains("windows") {
  794. "exe"
  795. } else {
  796. ""
  797. };
  798. Ok(out_dir.join(bin_name).with_extension(ext))
  799. }
  800. fn get_binaries(&self) -> crate::Result<Vec<BundleBinary>> {
  801. let mut binaries: Vec<BundleBinary> = vec![];
  802. if let Some(bins) = &self.cargo_settings.bin {
  803. let default_run = self
  804. .package_settings
  805. .default_run
  806. .clone()
  807. .unwrap_or_default();
  808. for bin in bins {
  809. let is_main = bin.name == self.cargo_package_settings.name || bin.name == default_run;
  810. binaries.push(BundleBinary::with_path(
  811. bin.name.clone(),
  812. is_main,
  813. bin.path.clone(),
  814. ))
  815. }
  816. }
  817. let mut binaries_paths = std::fs::read_dir(tauri_dir().join("src/bin"))
  818. .map(|dir| {
  819. dir
  820. .into_iter()
  821. .flatten()
  822. .map(|entry| {
  823. (
  824. entry
  825. .path()
  826. .file_stem()
  827. .unwrap_or_default()
  828. .to_string_lossy()
  829. .into_owned(),
  830. entry.path(),
  831. )
  832. })
  833. .collect::<Vec<_>>()
  834. })
  835. .unwrap_or_default();
  836. if !binaries_paths
  837. .iter()
  838. .any(|(_name, path)| path == Path::new("src/main.rs"))
  839. && tauri_dir().join("src/main.rs").exists()
  840. {
  841. binaries_paths.push((
  842. self.cargo_package_settings.name.clone(),
  843. tauri_dir().join("src/main.rs"),
  844. ));
  845. }
  846. for (name, path) in binaries_paths {
  847. // see https://github.com/tauri-apps/tauri/pull/10977#discussion_r1759742414
  848. let bin_exists = binaries
  849. .iter()
  850. .any(|bin| bin.name() == name || path.ends_with(bin.src_path().unwrap_or(&"".to_string())));
  851. if !bin_exists {
  852. binaries.push(BundleBinary::new(name, false))
  853. }
  854. }
  855. if let Some(default_run) = self.package_settings.default_run.as_ref() {
  856. if let Some(binary) = binaries.iter_mut().find(|bin| bin.name() == default_run) {
  857. binary.set_main(true);
  858. } else {
  859. binaries.push(BundleBinary::new(default_run.clone(), true));
  860. }
  861. }
  862. match binaries.len() {
  863. 0 => binaries.push(BundleBinary::new(
  864. self.cargo_package_settings.name.clone(),
  865. true,
  866. )),
  867. 1 => binaries.get_mut(0).unwrap().set_main(true),
  868. _ => {}
  869. }
  870. Ok(binaries)
  871. }
  872. fn app_name(&self) -> Option<String> {
  873. self
  874. .manifest
  875. .lock()
  876. .unwrap()
  877. .inner
  878. .as_table()
  879. .get("package")
  880. .and_then(|p| p.as_table())
  881. .and_then(|p| p.get("name"))
  882. .and_then(|n| n.as_str())
  883. .map(|n| n.to_string())
  884. }
  885. fn lib_name(&self) -> Option<String> {
  886. self
  887. .manifest
  888. .lock()
  889. .unwrap()
  890. .inner
  891. .as_table()
  892. .get("lib")
  893. .and_then(|p| p.as_table())
  894. .and_then(|p| p.get("name"))
  895. .and_then(|n| n.as_str())
  896. .map(|n| n.to_string())
  897. }
  898. }
  899. impl RustAppSettings {
  900. pub fn new(config: &Config, manifest: Manifest, target: Option<String>) -> crate::Result<Self> {
  901. let cargo_settings =
  902. CargoSettings::load(tauri_dir()).with_context(|| "failed to load cargo settings")?;
  903. let cargo_package_settings = match &cargo_settings.package {
  904. Some(package_info) => package_info.clone(),
  905. None => {
  906. return Err(anyhow::anyhow!(
  907. "No package info in the config file".to_owned(),
  908. ))
  909. }
  910. };
  911. let ws_package_settings = CargoSettings::load(&get_workspace_dir()?)
  912. .with_context(|| "failed to load cargo settings from workspace root")?
  913. .workspace
  914. .and_then(|v| v.package);
  915. let package_settings = PackageSettings {
  916. product_name: config
  917. .product_name
  918. .clone()
  919. .unwrap_or_else(|| cargo_package_settings.name.clone()),
  920. version: config.version.clone().unwrap_or_else(|| {
  921. cargo_package_settings
  922. .version
  923. .clone()
  924. .expect("Cargo manifest must have the `package.version` field")
  925. .resolve("version", || {
  926. ws_package_settings
  927. .as_ref()
  928. .and_then(|p| p.version.clone())
  929. .ok_or_else(|| anyhow::anyhow!("Couldn't inherit value for `version` from workspace"))
  930. })
  931. .expect("Cargo project does not have a version")
  932. }),
  933. description: cargo_package_settings
  934. .description
  935. .clone()
  936. .map(|description| {
  937. description
  938. .resolve("description", || {
  939. ws_package_settings
  940. .as_ref()
  941. .and_then(|v| v.description.clone())
  942. .ok_or_else(|| {
  943. anyhow::anyhow!("Couldn't inherit value for `description` from workspace")
  944. })
  945. })
  946. .unwrap()
  947. })
  948. .unwrap_or_default(),
  949. homepage: cargo_package_settings.homepage.clone().map(|homepage| {
  950. homepage
  951. .resolve("homepage", || {
  952. ws_package_settings
  953. .as_ref()
  954. .and_then(|v| v.homepage.clone())
  955. .ok_or_else(|| {
  956. anyhow::anyhow!("Couldn't inherit value for `homepage` from workspace")
  957. })
  958. })
  959. .unwrap()
  960. }),
  961. authors: cargo_package_settings.authors.clone().map(|authors| {
  962. authors
  963. .resolve("authors", || {
  964. ws_package_settings
  965. .as_ref()
  966. .and_then(|v| v.authors.clone())
  967. .ok_or_else(|| anyhow::anyhow!("Couldn't inherit value for `authors` from workspace"))
  968. })
  969. .unwrap()
  970. }),
  971. default_run: cargo_package_settings.default_run.clone(),
  972. };
  973. let cargo_config = CargoConfig::load(tauri_dir())?;
  974. let target_triple = target.unwrap_or_else(|| {
  975. cargo_config
  976. .build()
  977. .target()
  978. .map(|t| t.to_string())
  979. .unwrap_or_else(|| {
  980. let output = Command::new("rustc")
  981. .args(["-vV"])
  982. .output()
  983. .expect("\"rustc\" could not be found, did you install Rust?");
  984. let stdout = String::from_utf8_lossy(&output.stdout);
  985. stdout
  986. .split('\n')
  987. .find(|l| l.starts_with("host:"))
  988. .unwrap()
  989. .replace("host:", "")
  990. .trim()
  991. .to_string()
  992. })
  993. });
  994. let target = Target::from_triple(&target_triple);
  995. Ok(Self {
  996. manifest: Mutex::new(manifest),
  997. cargo_settings,
  998. cargo_package_settings,
  999. cargo_ws_package_settings: ws_package_settings,
  1000. package_settings,
  1001. cargo_config,
  1002. target_triple,
  1003. target,
  1004. })
  1005. }
  1006. fn target<'a>(&'a self, options: &'a Options) -> Option<&'a str> {
  1007. options
  1008. .target
  1009. .as_deref()
  1010. .or_else(|| self.cargo_config.build().target())
  1011. }
  1012. pub fn out_dir(&self, options: &Options) -> crate::Result<PathBuf> {
  1013. get_target_dir(self.target(options), options)
  1014. }
  1015. }
  1016. #[derive(Deserialize)]
  1017. pub(crate) struct CargoMetadata {
  1018. pub(crate) target_directory: PathBuf,
  1019. pub(crate) workspace_root: PathBuf,
  1020. }
  1021. pub(crate) fn get_cargo_metadata() -> crate::Result<CargoMetadata> {
  1022. let output = Command::new("cargo")
  1023. .args(["metadata", "--no-deps", "--format-version", "1"])
  1024. .current_dir(tauri_dir())
  1025. .output()?;
  1026. if !output.status.success() {
  1027. return Err(anyhow::anyhow!(
  1028. "cargo metadata command exited with a non zero exit code: {}",
  1029. String::from_utf8_lossy(&output.stderr)
  1030. ));
  1031. }
  1032. Ok(serde_json::from_slice(&output.stdout)?)
  1033. }
  1034. /// This function determines the 'target' directory and suffixes it with the profile
  1035. /// to determine where the compiled binary will be located.
  1036. fn get_target_dir(triple: Option<&str>, options: &Options) -> crate::Result<PathBuf> {
  1037. let mut path = if let Some(target) = get_cargo_option(&options.args, "--target-dir") {
  1038. std::env::current_dir()?.join(target)
  1039. } else {
  1040. let mut path = get_cargo_metadata()
  1041. .with_context(|| "failed to get cargo metadata")?
  1042. .target_directory;
  1043. if let Some(triple) = triple {
  1044. path.push(triple);
  1045. }
  1046. path
  1047. };
  1048. path.push(get_profile_dir(options));
  1049. Ok(path)
  1050. }
  1051. #[inline]
  1052. fn get_cargo_option<'a>(args: &'a [String], option: &'a str) -> Option<&'a str> {
  1053. args
  1054. .iter()
  1055. .position(|a| a.starts_with(option))
  1056. .and_then(|i| {
  1057. args[i]
  1058. .split_once('=')
  1059. .map(|(_, p)| Some(p))
  1060. .unwrap_or_else(|| args.get(i + 1).map(|s| s.as_str()))
  1061. })
  1062. }
  1063. /// Executes `cargo metadata` to get the workspace directory.
  1064. pub fn get_workspace_dir() -> crate::Result<PathBuf> {
  1065. Ok(
  1066. get_cargo_metadata()
  1067. .with_context(|| "failed to get cargo metadata")?
  1068. .workspace_root,
  1069. )
  1070. }
  1071. pub fn get_profile(options: &Options) -> &str {
  1072. get_cargo_option(&options.args, "--profile").unwrap_or(if options.debug {
  1073. "dev"
  1074. } else {
  1075. "release"
  1076. })
  1077. }
  1078. pub fn get_profile_dir(options: &Options) -> &str {
  1079. match get_profile(options) {
  1080. "dev" => "debug",
  1081. profile => profile,
  1082. }
  1083. }
  1084. #[allow(unused_variables)]
  1085. fn tauri_config_to_bundle_settings(
  1086. settings: &RustAppSettings,
  1087. features: &[String],
  1088. identifier: String,
  1089. config: crate::helpers::config::BundleConfig,
  1090. updater_config: Option<UpdaterSettings>,
  1091. arch64bits: bool,
  1092. ) -> crate::Result<BundleSettings> {
  1093. let enabled_features = settings
  1094. .manifest
  1095. .lock()
  1096. .unwrap()
  1097. .all_enabled_features(features);
  1098. #[cfg(windows)]
  1099. let windows_icon_path = PathBuf::from(
  1100. config
  1101. .icon
  1102. .iter()
  1103. .find(|i| i.ends_with(".ico"))
  1104. .cloned()
  1105. .expect("the bundle config must have a `.ico` icon"),
  1106. );
  1107. #[cfg(not(windows))]
  1108. let windows_icon_path = PathBuf::from("");
  1109. #[allow(unused_mut)]
  1110. let mut resources = config
  1111. .resources
  1112. .unwrap_or(BundleResources::List(Vec::new()));
  1113. #[allow(unused_mut)]
  1114. let mut depends_deb = config.linux.deb.depends.unwrap_or_default();
  1115. #[allow(unused_mut)]
  1116. let mut depends_rpm = config.linux.rpm.depends.unwrap_or_default();
  1117. // set env vars used by the bundler and inject dependencies
  1118. #[cfg(target_os = "linux")]
  1119. {
  1120. let mut libs: Vec<String> = Vec::new();
  1121. if enabled_features.contains(&"tray-icon".into())
  1122. || enabled_features.contains(&"tauri/tray-icon".into())
  1123. {
  1124. let (tray_kind, path) = std::env::var("TAURI_LINUX_AYATANA_APPINDICATOR")
  1125. .map(|ayatana| {
  1126. if ayatana == "true" || ayatana == "1" {
  1127. (
  1128. pkgconfig_utils::TrayKind::Ayatana,
  1129. format!(
  1130. "{}/libayatana-appindicator3.so.1",
  1131. pkgconfig_utils::get_library_path("ayatana-appindicator3-0.1")
  1132. .expect("failed to get ayatana-appindicator library path using pkg-config.")
  1133. ),
  1134. )
  1135. } else {
  1136. (
  1137. pkgconfig_utils::TrayKind::Libappindicator,
  1138. format!(
  1139. "{}/libappindicator3.so.1",
  1140. pkgconfig_utils::get_library_path("appindicator3-0.1")
  1141. .expect("failed to get libappindicator-gtk library path using pkg-config.")
  1142. ),
  1143. )
  1144. }
  1145. })
  1146. .unwrap_or_else(|_| pkgconfig_utils::get_appindicator_library_path());
  1147. match tray_kind {
  1148. pkgconfig_utils::TrayKind::Ayatana => {
  1149. depends_deb.push("libayatana-appindicator3-1".into());
  1150. libs.push("libayatana-appindicator3.so.1".into());
  1151. }
  1152. pkgconfig_utils::TrayKind::Libappindicator => {
  1153. depends_deb.push("libappindicator3-1".into());
  1154. libs.push("libappindicator3.so.1".into());
  1155. }
  1156. }
  1157. std::env::set_var("TAURI_TRAY_LIBRARY_PATH", path);
  1158. }
  1159. depends_deb.push("libwebkit2gtk-4.1-0".to_string());
  1160. depends_deb.push("libgtk-3-0".to_string());
  1161. libs.push("libwebkit2gtk-4.1.so.0".into());
  1162. libs.push("libgtk-3.so.0".into());
  1163. for lib in libs {
  1164. let mut requires = lib;
  1165. if arch64bits {
  1166. requires.push_str("()(64bit)");
  1167. }
  1168. depends_rpm.push(requires);
  1169. }
  1170. }
  1171. #[cfg(windows)]
  1172. {
  1173. if let crate::helpers::config::WebviewInstallMode::FixedRuntime { path } =
  1174. &config.windows.webview_install_mode
  1175. {
  1176. resources.push(path.display().to_string());
  1177. }
  1178. }
  1179. let signing_identity = match std::env::var_os("APPLE_SIGNING_IDENTITY") {
  1180. Some(signing_identity) => Some(
  1181. signing_identity
  1182. .to_str()
  1183. .expect("failed to convert APPLE_SIGNING_IDENTITY to string")
  1184. .to_string(),
  1185. ),
  1186. None => config.macos.signing_identity,
  1187. };
  1188. let provider_short_name = match std::env::var_os("APPLE_PROVIDER_SHORT_NAME") {
  1189. Some(provider_short_name) => Some(
  1190. provider_short_name
  1191. .to_str()
  1192. .expect("failed to convert APPLE_PROVIDER_SHORT_NAME to string")
  1193. .to_string(),
  1194. ),
  1195. None => config.macos.provider_short_name,
  1196. };
  1197. let (resources, resources_map) = match resources {
  1198. BundleResources::List(paths) => (Some(paths), None),
  1199. BundleResources::Map(map) => (None, Some(map)),
  1200. };
  1201. Ok(BundleSettings {
  1202. identifier: Some(identifier),
  1203. publisher: config.publisher,
  1204. homepage: config.homepage,
  1205. icon: Some(config.icon),
  1206. resources,
  1207. resources_map,
  1208. copyright: config.copyright,
  1209. category: match config.category {
  1210. Some(category) => Some(AppCategory::from_str(&category).map_err(|e| match e {
  1211. Some(e) => anyhow::anyhow!("invalid category, did you mean `{}`?", e),
  1212. None => anyhow::anyhow!("invalid category"),
  1213. })?),
  1214. None => None,
  1215. },
  1216. file_associations: config.file_associations,
  1217. short_description: config.short_description,
  1218. long_description: config.long_description,
  1219. external_bin: config.external_bin,
  1220. deb: DebianSettings {
  1221. depends: if depends_deb.is_empty() {
  1222. None
  1223. } else {
  1224. Some(depends_deb)
  1225. },
  1226. provides: config.linux.deb.provides,
  1227. conflicts: config.linux.deb.conflicts,
  1228. replaces: config.linux.deb.replaces,
  1229. files: config.linux.deb.files,
  1230. desktop_template: config.linux.deb.desktop_template,
  1231. section: config.linux.deb.section,
  1232. priority: config.linux.deb.priority,
  1233. changelog: config.linux.deb.changelog,
  1234. pre_install_script: config.linux.deb.pre_install_script,
  1235. post_install_script: config.linux.deb.post_install_script,
  1236. pre_remove_script: config.linux.deb.pre_remove_script,
  1237. post_remove_script: config.linux.deb.post_remove_script,
  1238. },
  1239. appimage: AppImageSettings {
  1240. files: config.linux.appimage.files,
  1241. },
  1242. rpm: RpmSettings {
  1243. depends: if depends_rpm.is_empty() {
  1244. None
  1245. } else {
  1246. Some(depends_rpm)
  1247. },
  1248. provides: config.linux.rpm.provides,
  1249. conflicts: config.linux.rpm.conflicts,
  1250. obsoletes: config.linux.rpm.obsoletes,
  1251. release: config.linux.rpm.release,
  1252. epoch: config.linux.rpm.epoch,
  1253. files: config.linux.rpm.files,
  1254. desktop_template: config.linux.rpm.desktop_template,
  1255. pre_install_script: config.linux.rpm.pre_install_script,
  1256. post_install_script: config.linux.rpm.post_install_script,
  1257. pre_remove_script: config.linux.rpm.pre_remove_script,
  1258. post_remove_script: config.linux.rpm.post_remove_script,
  1259. },
  1260. dmg: DmgSettings {
  1261. background: config.macos.dmg.background,
  1262. window_position: config
  1263. .macos
  1264. .dmg
  1265. .window_position
  1266. .map(|window_position| Position {
  1267. x: window_position.x,
  1268. y: window_position.y,
  1269. }),
  1270. window_size: Size {
  1271. width: config.macos.dmg.window_size.width,
  1272. height: config.macos.dmg.window_size.height,
  1273. },
  1274. app_position: Position {
  1275. x: config.macos.dmg.app_position.x,
  1276. y: config.macos.dmg.app_position.y,
  1277. },
  1278. application_folder_position: Position {
  1279. x: config.macos.dmg.application_folder_position.x,
  1280. y: config.macos.dmg.application_folder_position.y,
  1281. },
  1282. },
  1283. macos: MacOsSettings {
  1284. frameworks: config.macos.frameworks,
  1285. files: config.macos.files,
  1286. minimum_system_version: config.macos.minimum_system_version,
  1287. exception_domain: config.macos.exception_domain,
  1288. signing_identity,
  1289. hardened_runtime: config.macos.hardened_runtime,
  1290. provider_short_name,
  1291. entitlements: config.macos.entitlements,
  1292. info_plist_path: {
  1293. let path = tauri_dir().join("Info.plist");
  1294. if path.exists() {
  1295. Some(path)
  1296. } else {
  1297. None
  1298. }
  1299. },
  1300. },
  1301. windows: WindowsSettings {
  1302. timestamp_url: config.windows.timestamp_url,
  1303. tsp: config.windows.tsp,
  1304. digest_algorithm: config.windows.digest_algorithm,
  1305. certificate_thumbprint: config.windows.certificate_thumbprint,
  1306. wix: config.windows.wix.map(wix_settings),
  1307. nsis: config.windows.nsis.map(nsis_settings),
  1308. icon_path: windows_icon_path,
  1309. webview_install_mode: config.windows.webview_install_mode,
  1310. allow_downgrades: config.windows.allow_downgrades,
  1311. sign_command: config.windows.sign_command.map(custom_sign_settings),
  1312. },
  1313. license: config.license.or_else(|| {
  1314. settings
  1315. .cargo_package_settings
  1316. .license
  1317. .clone()
  1318. .map(|license| {
  1319. license
  1320. .resolve("license", || {
  1321. settings
  1322. .cargo_ws_package_settings
  1323. .as_ref()
  1324. .and_then(|v| v.license.clone())
  1325. .ok_or_else(|| {
  1326. anyhow::anyhow!("Couldn't inherit value for `license` from workspace")
  1327. })
  1328. })
  1329. .unwrap()
  1330. })
  1331. }),
  1332. license_file: config.license_file.map(|l| tauri_dir().join(l)),
  1333. updater: updater_config,
  1334. ..Default::default()
  1335. })
  1336. }
  1337. #[cfg(target_os = "linux")]
  1338. mod pkgconfig_utils {
  1339. use std::process::Command;
  1340. pub enum TrayKind {
  1341. Ayatana,
  1342. Libappindicator,
  1343. }
  1344. pub fn get_appindicator_library_path() -> (TrayKind, String) {
  1345. match get_library_path("ayatana-appindicator3-0.1") {
  1346. Some(p) => (
  1347. TrayKind::Ayatana,
  1348. format!("{p}/libayatana-appindicator3.so.1"),
  1349. ),
  1350. None => match get_library_path("appindicator3-0.1") {
  1351. Some(p) => (
  1352. TrayKind::Libappindicator,
  1353. format!("{p}/libappindicator3.so.1"),
  1354. ),
  1355. None => panic!("Can't detect any appindicator library"),
  1356. },
  1357. }
  1358. }
  1359. /// Gets the folder in which a library is located using `pkg-config`.
  1360. pub fn get_library_path(name: &str) -> Option<String> {
  1361. let mut cmd = Command::new("pkg-config");
  1362. cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1");
  1363. cmd.arg("--libs-only-L");
  1364. cmd.arg(name);
  1365. if let Ok(output) = cmd.output() {
  1366. if !output.stdout.is_empty() {
  1367. // output would be "-L/path/to/library\n"
  1368. let word = output.stdout[2..].to_vec();
  1369. return Some(String::from_utf8_lossy(&word).trim().to_string());
  1370. } else {
  1371. None
  1372. }
  1373. } else {
  1374. None
  1375. }
  1376. }
  1377. }
  1378. #[cfg(test)]
  1379. mod tests {
  1380. use super::*;
  1381. #[test]
  1382. fn parse_cargo_option() {
  1383. let args = vec![
  1384. "build".into(),
  1385. "--".into(),
  1386. "--profile".into(),
  1387. "holla".into(),
  1388. "--features".into(),
  1389. "a".into(),
  1390. "b".into(),
  1391. "--target-dir".into(),
  1392. "path/to/dir".into(),
  1393. ];
  1394. assert_eq!(get_cargo_option(&args, "--profile"), Some("holla"));
  1395. assert_eq!(get_cargo_option(&args, "--target-dir"), Some("path/to/dir"));
  1396. assert_eq!(get_cargo_option(&args, "--non-existent"), None);
  1397. }
  1398. #[test]
  1399. fn parse_profile_from_opts() {
  1400. let options = Options {
  1401. args: vec![
  1402. "build".into(),
  1403. "--".into(),
  1404. "--profile".into(),
  1405. "testing".into(),
  1406. "--features".into(),
  1407. "feat1".into(),
  1408. ],
  1409. ..Default::default()
  1410. };
  1411. assert_eq!(get_profile(&options), "testing");
  1412. let options = Options {
  1413. args: vec![
  1414. "build".into(),
  1415. "--".into(),
  1416. "--profile=customprofile".into(),
  1417. "testing".into(),
  1418. "--features".into(),
  1419. "feat1".into(),
  1420. ],
  1421. ..Default::default()
  1422. };
  1423. assert_eq!(get_profile(&options), "customprofile");
  1424. let options = Options {
  1425. debug: true,
  1426. args: vec![
  1427. "build".into(),
  1428. "--".into(),
  1429. "testing".into(),
  1430. "--features".into(),
  1431. "feat1".into(),
  1432. ],
  1433. ..Default::default()
  1434. };
  1435. assert_eq!(get_profile(&options), "dev");
  1436. let options = Options {
  1437. debug: false,
  1438. args: vec![
  1439. "build".into(),
  1440. "--".into(),
  1441. "testing".into(),
  1442. "--features".into(),
  1443. "feat1".into(),
  1444. ],
  1445. ..Default::default()
  1446. };
  1447. assert_eq!(get_profile(&options), "release");
  1448. let options = Options {
  1449. args: vec!["build".into(), "--".into(), "--profile".into()],
  1450. ..Default::default()
  1451. };
  1452. assert_eq!(get_profile(&options), "release");
  1453. }
  1454. #[test]
  1455. fn parse_target_dir_from_opts() {
  1456. crate::helpers::app_paths::resolve();
  1457. let current_dir = std::env::current_dir().unwrap();
  1458. let options = Options {
  1459. args: vec![
  1460. "build".into(),
  1461. "--".into(),
  1462. "--target-dir".into(),
  1463. "path/to/some/dir".into(),
  1464. "--features".into(),
  1465. "feat1".into(),
  1466. ],
  1467. debug: false,
  1468. ..Default::default()
  1469. };
  1470. assert_eq!(
  1471. get_target_dir(None, &options).unwrap(),
  1472. current_dir.join("path/to/some/dir/release")
  1473. );
  1474. assert_eq!(
  1475. get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(),
  1476. current_dir.join("path/to/some/dir/release")
  1477. );
  1478. let options = Options {
  1479. args: vec![
  1480. "build".into(),
  1481. "--".into(),
  1482. "--features".into(),
  1483. "feat1".into(),
  1484. ],
  1485. debug: false,
  1486. ..Default::default()
  1487. };
  1488. #[cfg(windows)]
  1489. assert!(get_target_dir(Some("x86_64-pc-windows-msvc"), &options)
  1490. .unwrap()
  1491. .ends_with("x86_64-pc-windows-msvc\\release"));
  1492. #[cfg(not(windows))]
  1493. assert!(get_target_dir(Some("x86_64-pc-windows-msvc"), &options)
  1494. .unwrap()
  1495. .ends_with("x86_64-pc-windows-msvc/release"));
  1496. #[cfg(windows)]
  1497. {
  1498. std::env::set_var("CARGO_TARGET_DIR", "D:\\path\\to\\env\\dir");
  1499. assert_eq!(
  1500. get_target_dir(None, &options).unwrap(),
  1501. PathBuf::from("D:\\path\\to\\env\\dir\\release")
  1502. );
  1503. assert_eq!(
  1504. get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(),
  1505. PathBuf::from("D:\\path\\to\\env\\dir\\x86_64-pc-windows-msvc\\release")
  1506. );
  1507. }
  1508. #[cfg(not(windows))]
  1509. {
  1510. std::env::set_var("CARGO_TARGET_DIR", "/path/to/env/dir");
  1511. assert_eq!(
  1512. get_target_dir(None, &options).unwrap(),
  1513. PathBuf::from("/path/to/env/dir/release")
  1514. );
  1515. assert_eq!(
  1516. get_target_dir(Some("x86_64-pc-windows-msvc"), &options).unwrap(),
  1517. PathBuf::from("/path/to/env/dir/x86_64-pc-windows-msvc/release")
  1518. );
  1519. }
  1520. }
  1521. }