dev.rs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::helpers::{
  5. app_paths::{app_dir, tauri_dir},
  6. config::{get as get_config, reload as reload_config},
  7. manifest::{get_workspace_members, rewrite_manifest},
  8. Logger,
  9. };
  10. use anyhow::Context;
  11. use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
  12. use once_cell::sync::OnceCell;
  13. use shared_child::SharedChild;
  14. use std::{
  15. env::set_current_dir,
  16. ffi::OsStr,
  17. process::{exit, Child, Command},
  18. sync::{
  19. mpsc::{channel, Receiver},
  20. Arc, Mutex,
  21. },
  22. time::Duration,
  23. };
  24. static BEFORE_DEV: OnceCell<Mutex<Child>> = OnceCell::new();
  25. fn kill_before_dev_process() {
  26. if let Some(child) = BEFORE_DEV.get() {
  27. let mut child = child.lock().unwrap();
  28. #[cfg(windows)]
  29. let _ = Command::new("powershell")
  30. .arg("-NoProfile")
  31. .arg("-Command")
  32. .arg(format!("function Kill-Tree {{ Param([int]$ppid); Get-CimInstance Win32_Process | Where-Object {{ $_.ParentProcessId -eq $ppid }} | ForEach-Object {{ Kill-Tree $_.ProcessId }}; Stop-Process -Id $ppid }}; Kill-Tree {}", child.id()))
  33. .status();
  34. #[cfg(not(windows))]
  35. let _ = Command::new("pkill")
  36. .args(&["-TERM", "-P"])
  37. .arg(child.id().to_string())
  38. .status();
  39. let _ = child.kill();
  40. }
  41. }
  42. #[derive(Default)]
  43. pub struct Dev {
  44. runner: Option<String>,
  45. target: Option<String>,
  46. features: Option<Vec<String>>,
  47. exit_on_panic: bool,
  48. config: Option<String>,
  49. args: Vec<String>,
  50. release_mode: bool,
  51. }
  52. impl Dev {
  53. pub fn new() -> Self {
  54. Default::default()
  55. }
  56. pub fn runner(mut self, runner: String) -> Self {
  57. self.runner.replace(runner);
  58. self
  59. }
  60. pub fn target(mut self, target: String) -> Self {
  61. self.target.replace(target);
  62. self
  63. }
  64. pub fn features(mut self, features: Vec<String>) -> Self {
  65. self.features.replace(features);
  66. self
  67. }
  68. pub fn config(mut self, config: String) -> Self {
  69. self.config.replace(config);
  70. self
  71. }
  72. pub fn exit_on_panic(mut self, exit_on_panic: bool) -> Self {
  73. self.exit_on_panic = exit_on_panic;
  74. self
  75. }
  76. pub fn args(mut self, args: Vec<String>) -> Self {
  77. self.args = args;
  78. self
  79. }
  80. pub fn release_mode(mut self, release_mode: bool) -> Self {
  81. self.release_mode = release_mode;
  82. self
  83. }
  84. pub fn run(self) -> crate::Result<()> {
  85. let logger = Logger::new("tauri:dev");
  86. let tauri_path = tauri_dir();
  87. set_current_dir(&tauri_path).with_context(|| "failed to change current working directory")?;
  88. let merge_config = self.config.clone();
  89. let config = get_config(merge_config.as_deref())?;
  90. let mut process: Arc<SharedChild>;
  91. let (settings, out_dir) = {
  92. let config_guard = config.lock().unwrap();
  93. let config_ = config_guard.as_ref().unwrap();
  94. let app_settings = crate::interface::rust::AppSettings::new(config_)?;
  95. let out_dir = app_settings
  96. .get_out_dir(self.target.clone(), true)
  97. .with_context(|| "failed to get project out directory")?;
  98. let settings = crate::interface::get_bundler_settings(
  99. app_settings,
  100. self.target.clone(),
  101. &Default::default(),
  102. config_,
  103. &out_dir,
  104. false,
  105. None,
  106. )
  107. .with_context(|| "failed to build bundler settings")?;
  108. (settings, out_dir)
  109. };
  110. settings.copy_resources(&out_dir)?;
  111. settings.copy_binaries(&out_dir)?;
  112. if let Some(before_dev) = &config
  113. .lock()
  114. .unwrap()
  115. .as_ref()
  116. .unwrap()
  117. .build
  118. .before_dev_command
  119. {
  120. if !before_dev.is_empty() {
  121. logger.log(format!("Running `{}`", before_dev));
  122. #[cfg(target_os = "windows")]
  123. let child = Command::new("cmd")
  124. .arg("/C")
  125. .arg(before_dev)
  126. .current_dir(app_dir())
  127. .spawn()
  128. .with_context(|| format!("failed to run `{}` with `cmd /C`", before_dev))?;
  129. #[cfg(not(target_os = "windows"))]
  130. let child = Command::new("sh")
  131. .arg("-c")
  132. .arg(before_dev)
  133. .current_dir(app_dir())
  134. .spawn()
  135. .with_context(|| format!("failed to run `{}` with `sh -c`", before_dev))?;
  136. BEFORE_DEV.set(Mutex::new(child)).unwrap();
  137. }
  138. }
  139. let runner_from_config = config
  140. .lock()
  141. .unwrap()
  142. .as_ref()
  143. .unwrap()
  144. .build
  145. .runner
  146. .clone();
  147. let runner = self
  148. .runner
  149. .clone()
  150. .or(runner_from_config)
  151. .unwrap_or_else(|| "cargo".to_string());
  152. {
  153. let (tx, rx) = channel();
  154. let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
  155. watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
  156. rewrite_manifest(config.clone())?;
  157. loop {
  158. if let Ok(DebouncedEvent::NoticeWrite(_)) = rx.recv() {
  159. break;
  160. }
  161. }
  162. }
  163. let mut cargo_features = config
  164. .lock()
  165. .unwrap()
  166. .as_ref()
  167. .unwrap()
  168. .build
  169. .features
  170. .clone()
  171. .unwrap_or_default();
  172. if let Some(features) = &self.features {
  173. cargo_features.extend(features.clone());
  174. }
  175. let (child_wait_tx, child_wait_rx) = channel();
  176. let child_wait_rx = Arc::new(Mutex::new(child_wait_rx));
  177. process = self.start_app(&runner, &cargo_features, child_wait_rx.clone());
  178. let (tx, rx) = channel();
  179. let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
  180. watcher.watch(tauri_path.join("src"), RecursiveMode::Recursive)?;
  181. watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
  182. watcher.watch(tauri_path.join("tauri.conf.json"), RecursiveMode::Recursive)?;
  183. for member in get_workspace_members()? {
  184. let workspace_path = tauri_path.join(member);
  185. watcher.watch(workspace_path.join("src"), RecursiveMode::Recursive)?;
  186. watcher.watch(workspace_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
  187. }
  188. loop {
  189. if let Ok(event) = rx.recv() {
  190. let event_path = match event {
  191. DebouncedEvent::Create(path) => Some(path),
  192. DebouncedEvent::Remove(path) => Some(path),
  193. DebouncedEvent::Rename(_, dest) => Some(dest),
  194. DebouncedEvent::Write(path) => Some(path),
  195. _ => None,
  196. };
  197. if let Some(event_path) = event_path {
  198. if event_path.file_name() == Some(OsStr::new("tauri.conf.json")) {
  199. reload_config(merge_config.as_deref())?;
  200. rewrite_manifest(config.clone())?;
  201. } else {
  202. // When tauri.conf.json is changed, rewrite_manifest will be called
  203. // which will trigger the watcher again
  204. // So the app should only be started when a file other than tauri.conf.json is changed
  205. let _ = child_wait_tx.send(());
  206. process
  207. .kill()
  208. .with_context(|| "failed to kill app process")?;
  209. // wait for the process to exit
  210. loop {
  211. if let Ok(Some(_)) = process.try_wait() {
  212. break;
  213. }
  214. }
  215. process = self.start_app(&runner, &cargo_features, child_wait_rx.clone());
  216. }
  217. }
  218. }
  219. }
  220. }
  221. fn start_app(
  222. &self,
  223. runner: &str,
  224. features: &[String],
  225. child_wait_rx: Arc<Mutex<Receiver<()>>>,
  226. ) -> Arc<SharedChild> {
  227. let mut command = Command::new(runner);
  228. command.args(&["run", "--no-default-features"]);
  229. if self.release_mode {
  230. command.args(&["--release"]);
  231. }
  232. if let Some(target) = &self.target {
  233. command.args(&["--target", target]);
  234. }
  235. if !features.is_empty() {
  236. command.args(&["--features", &features.join(",")]);
  237. }
  238. if !self.args.is_empty() {
  239. command.arg("--").args(&self.args);
  240. }
  241. let child =
  242. SharedChild::spawn(&mut command).unwrap_or_else(|_| panic!("failed to run {}", runner));
  243. let child_arc = Arc::new(child);
  244. let child_clone = child_arc.clone();
  245. let exit_on_panic = self.exit_on_panic;
  246. std::thread::spawn(move || {
  247. let status = child_clone.wait().expect("failed to wait on child");
  248. if exit_on_panic {
  249. // we exit if the status is a success code (app closed) or code is 101 (compilation error)
  250. // if the process wasn't killed by the file watcher
  251. if (status.success() || status.code() == Some(101))
  252. // `child_wait_rx` indicates that the process was killed by the file watcher
  253. && child_wait_rx
  254. .lock()
  255. .expect("failed to get child_wait_rx lock")
  256. .try_recv()
  257. .is_err()
  258. {
  259. kill_before_dev_process();
  260. exit(0);
  261. }
  262. } else if status.success() {
  263. // if we're no exiting on panic, we only exit if the status is a success code (app closed)
  264. kill_before_dev_process();
  265. exit(0);
  266. }
  267. });
  268. child_arc
  269. }
  270. }