dev.rs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. use crate::helpers::{
  2. app_paths::{app_dir, tauri_dir},
  3. config::{get as get_config, reload as reload_config},
  4. manifest::rewrite_manifest,
  5. Logger,
  6. };
  7. use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
  8. use once_cell::sync::OnceCell;
  9. use shared_child::SharedChild;
  10. use std::{
  11. env::set_current_dir,
  12. ffi::OsStr,
  13. process::{exit, Child, Command},
  14. sync::{
  15. mpsc::{channel, Receiver},
  16. Arc, Mutex,
  17. },
  18. time::Duration,
  19. };
  20. static BEFORE_DEV: OnceCell<Mutex<Child>> = OnceCell::new();
  21. fn kill_before_dev_process() {
  22. if let Some(child) = BEFORE_DEV.get() {
  23. let _ = child.lock().unwrap().kill();
  24. }
  25. }
  26. #[derive(Default)]
  27. pub struct Dev {
  28. exit_on_panic: bool,
  29. config: Option<String>,
  30. args: Vec<String>,
  31. }
  32. impl Dev {
  33. pub fn new() -> Self {
  34. Default::default()
  35. }
  36. pub fn config(mut self, config: String) -> Self {
  37. self.config.replace(config);
  38. self
  39. }
  40. pub fn exit_on_panic(mut self, exit_on_panic: bool) -> Self {
  41. self.exit_on_panic = exit_on_panic;
  42. self
  43. }
  44. pub fn args(mut self, args: Vec<String>) -> Self {
  45. self.args = args;
  46. self
  47. }
  48. pub fn run(self) -> crate::Result<()> {
  49. let logger = Logger::new("tauri:dev");
  50. let tauri_path = tauri_dir();
  51. set_current_dir(&tauri_path)?;
  52. let merge_config = self.config.clone();
  53. let config = get_config(merge_config.as_deref())?;
  54. let mut process: Arc<SharedChild>;
  55. if let Some(before_dev) = &config
  56. .lock()
  57. .unwrap()
  58. .as_ref()
  59. .unwrap()
  60. .build
  61. .before_dev_command
  62. {
  63. if !before_dev.is_empty() {
  64. logger.log(format!("Running `{}`", before_dev));
  65. #[cfg(target_os = "windows")]
  66. let child = Command::new("cmd")
  67. .arg("/C")
  68. .arg(before_dev)
  69. .current_dir(app_dir())
  70. .spawn()?;
  71. #[cfg(not(target_os = "windows"))]
  72. let child = Command::new("sh")
  73. .arg("-c")
  74. .arg(before_dev)
  75. .current_dir(app_dir())
  76. .spawn()?;
  77. BEFORE_DEV.set(Mutex::new(child)).unwrap();
  78. }
  79. }
  80. let dev_path = config
  81. .lock()
  82. .unwrap()
  83. .as_ref()
  84. .unwrap()
  85. .build
  86. .dev_path
  87. .to_string();
  88. rewrite_manifest(config.clone())?;
  89. let (child_wait_tx, child_wait_rx) = channel();
  90. let child_wait_rx = Arc::new(Mutex::new(child_wait_rx));
  91. process = self.start_app(child_wait_rx.clone());
  92. let (tx, rx) = channel();
  93. let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
  94. watcher.watch(tauri_path.join("src"), RecursiveMode::Recursive)?;
  95. watcher.watch(tauri_path.join("Cargo.toml"), RecursiveMode::Recursive)?;
  96. watcher.watch(tauri_path.join("tauri.conf.json"), RecursiveMode::Recursive)?;
  97. if !dev_path.starts_with("http") {
  98. watcher.watch(
  99. config
  100. .lock()
  101. .unwrap()
  102. .as_ref()
  103. .unwrap()
  104. .build
  105. .dev_path
  106. .to_string(),
  107. RecursiveMode::Recursive,
  108. )?;
  109. }
  110. loop {
  111. if let Ok(event) = rx.recv() {
  112. let event_path = match event {
  113. DebouncedEvent::Create(path) => Some(path),
  114. DebouncedEvent::Remove(path) => Some(path),
  115. DebouncedEvent::Rename(_, dest) => Some(dest),
  116. DebouncedEvent::Write(path) => Some(path),
  117. _ => None,
  118. };
  119. if let Some(event_path) = event_path {
  120. if event_path.file_name() == Some(OsStr::new("tauri.conf.json")) {
  121. reload_config(merge_config.as_deref())?;
  122. rewrite_manifest(config.clone())?;
  123. } else {
  124. // When tauri.conf.json is changed, rewrite_manifest will be called
  125. // which will trigger the watcher again
  126. // So the app should only be started when a file other than tauri.conf.json is changed
  127. let _ = child_wait_tx.send(());
  128. process.kill()?;
  129. process = self.start_app(child_wait_rx.clone());
  130. }
  131. }
  132. }
  133. }
  134. }
  135. fn start_app(&self, child_wait_rx: Arc<Mutex<Receiver<()>>>) -> Arc<SharedChild> {
  136. let mut command = Command::new("cargo");
  137. command.args(&["run", "--no-default-features"]);
  138. if !self.args.is_empty() {
  139. command.arg("--").args(&self.args);
  140. }
  141. let child = SharedChild::spawn(&mut command).expect("failed to run cargo");
  142. let child_arc = Arc::new(child);
  143. let child_clone = child_arc.clone();
  144. let exit_on_panic = self.exit_on_panic;
  145. std::thread::spawn(move || {
  146. let status = child_clone.wait().expect("failed to wait on child");
  147. if exit_on_panic {
  148. // we exit if the status is a success code (app closed) or code is 101 (compilation error)
  149. // if the process wasn't killed by the file watcher
  150. if (status.success() || status.code() == Some(101))
  151. // `child_wait_rx` indicates that the process was killed by the file watcher
  152. && child_wait_rx
  153. .lock()
  154. .expect("failed to get child_wait_rx lock")
  155. .try_recv()
  156. .is_err()
  157. {
  158. kill_before_dev_process();
  159. exit(0);
  160. }
  161. } else if status.success() {
  162. // if we're no exiting on panic, we only exit if the status is a success code (app closed)
  163. kill_before_dev_process();
  164. exit(0);
  165. }
  166. });
  167. child_arc
  168. }
  169. }