dev.rs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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("-Command")
  31. .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()))
  32. .status();
  33. #[cfg(not(windows))]
  34. let _ = Command::new("pkill")
  35. .args(&["-TERM", "-P"])
  36. .arg(child.id().to_string())
  37. .status();
  38. let _ = child.kill();
  39. }
  40. }
  41. #[derive(Default)]
  42. pub struct Dev {
  43. runner: Option<String>,
  44. target: Option<String>,
  45. exit_on_panic: bool,
  46. config: Option<String>,
  47. args: Vec<String>,
  48. }
  49. impl Dev {
  50. pub fn new() -> Self {
  51. Default::default()
  52. }
  53. pub fn runner(mut self, runner: String) -> Self {
  54. self.runner.replace(runner);
  55. self
  56. }
  57. pub fn target(mut self, target: String) -> Self {
  58. self.target.replace(target);
  59. self
  60. }
  61. pub fn config(mut self, config: String) -> Self {
  62. self.config.replace(config);
  63. self
  64. }
  65. pub fn exit_on_panic(mut self, exit_on_panic: bool) -> Self {
  66. self.exit_on_panic = exit_on_panic;
  67. self
  68. }
  69. pub fn args(mut self, args: Vec<String>) -> Self {
  70. self.args = args;
  71. self
  72. }
  73. pub fn run(self) -> crate::Result<()> {
  74. let logger = Logger::new("tauri:dev");
  75. let tauri_path = tauri_dir();
  76. set_current_dir(&tauri_path).with_context(|| "failed to change current working directory")?;
  77. let merge_config = self.config.clone();
  78. let config = get_config(merge_config.as_deref())?;
  79. let mut process: Arc<SharedChild>;
  80. if let Some(before_dev) = &config
  81. .lock()
  82. .unwrap()
  83. .as_ref()
  84. .unwrap()
  85. .build
  86. .before_dev_command
  87. {
  88. if !before_dev.is_empty() {
  89. logger.log(format!("Running `{}`", before_dev));
  90. #[cfg(target_os = "windows")]
  91. let child = Command::new("cmd")
  92. .arg("/C")
  93. .arg(before_dev)
  94. .current_dir(app_dir())
  95. .spawn()
  96. .with_context(|| format!("failed to run `{}` with `cmd /C`", before_dev))?;
  97. #[cfg(not(target_os = "windows"))]
  98. let child = Command::new("sh")
  99. .arg("-c")
  100. .arg(before_dev)
  101. .current_dir(app_dir())
  102. .spawn()
  103. .with_context(|| format!("failed to run `{}` with `sh -c`", before_dev))?;
  104. BEFORE_DEV.set(Mutex::new(child)).unwrap();
  105. }
  106. }
  107. let runner_from_config = config
  108. .lock()
  109. .unwrap()
  110. .as_ref()
  111. .unwrap()
  112. .build
  113. .runner
  114. .clone();
  115. let runner = self
  116. .runner
  117. .clone()
  118. .or(runner_from_config)
  119. .unwrap_or_else(|| "cargo".to_string());
  120. {
  121. let (tx, rx) = channel();
  122. let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
  123. watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
  124. rewrite_manifest(config.clone())?;
  125. loop {
  126. if let Ok(DebouncedEvent::NoticeWrite(_)) = rx.recv() {
  127. break;
  128. }
  129. }
  130. }
  131. let (child_wait_tx, child_wait_rx) = channel();
  132. let child_wait_rx = Arc::new(Mutex::new(child_wait_rx));
  133. process = self.start_app(&runner, child_wait_rx.clone());
  134. let (tx, rx) = channel();
  135. let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
  136. watcher.watch(tauri_path.join("src"), RecursiveMode::Recursive)?;
  137. watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
  138. watcher.watch(tauri_path.join("tauri.conf.json"), RecursiveMode::Recursive)?;
  139. for member in get_workspace_members()? {
  140. let workspace_path = tauri_path.join(member);
  141. watcher.watch(workspace_path.join("src"), RecursiveMode::Recursive)?;
  142. watcher.watch(workspace_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
  143. }
  144. loop {
  145. if let Ok(event) = rx.recv() {
  146. let event_path = match event {
  147. DebouncedEvent::Create(path) => Some(path),
  148. DebouncedEvent::Remove(path) => Some(path),
  149. DebouncedEvent::Rename(_, dest) => Some(dest),
  150. DebouncedEvent::Write(path) => Some(path),
  151. _ => None,
  152. };
  153. if let Some(event_path) = event_path {
  154. if event_path.file_name() == Some(OsStr::new("tauri.conf.json")) {
  155. reload_config(merge_config.as_deref())?;
  156. rewrite_manifest(config.clone())?;
  157. } else {
  158. // When tauri.conf.json is changed, rewrite_manifest will be called
  159. // which will trigger the watcher again
  160. // So the app should only be started when a file other than tauri.conf.json is changed
  161. let _ = child_wait_tx.send(());
  162. process
  163. .kill()
  164. .with_context(|| "failed to kill app process")?;
  165. // wait for the process to exit
  166. loop {
  167. if let Ok(Some(_)) = process.try_wait() {
  168. break;
  169. }
  170. }
  171. process = self.start_app(&runner, child_wait_rx.clone());
  172. }
  173. }
  174. }
  175. }
  176. }
  177. fn start_app(&self, runner: &str, child_wait_rx: Arc<Mutex<Receiver<()>>>) -> Arc<SharedChild> {
  178. let mut command = Command::new(runner);
  179. command.args(&["run", "--no-default-features"]);
  180. if let Some(target) = &self.target {
  181. command.args(&["--target", target]);
  182. }
  183. if !self.args.is_empty() {
  184. command.arg("--").args(&self.args);
  185. }
  186. let child =
  187. SharedChild::spawn(&mut command).unwrap_or_else(|_| panic!("failed to run {}", runner));
  188. let child_arc = Arc::new(child);
  189. let child_clone = child_arc.clone();
  190. let exit_on_panic = self.exit_on_panic;
  191. std::thread::spawn(move || {
  192. let status = child_clone.wait().expect("failed to wait on child");
  193. if exit_on_panic {
  194. // we exit if the status is a success code (app closed) or code is 101 (compilation error)
  195. // if the process wasn't killed by the file watcher
  196. if (status.success() || status.code() == Some(101))
  197. // `child_wait_rx` indicates that the process was killed by the file watcher
  198. && child_wait_rx
  199. .lock()
  200. .expect("failed to get child_wait_rx lock")
  201. .try_recv()
  202. .is_err()
  203. {
  204. kill_before_dev_process();
  205. exit(0);
  206. }
  207. } else if status.success() {
  208. // if we're no exiting on panic, we only exit if the status is a success code (app closed)
  209. kill_before_dev_process();
  210. exit(0);
  211. }
  212. });
  213. child_arc
  214. }
  215. }