command.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  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. collections::HashMap,
  6. io::{BufRead, BufReader, Write},
  7. path::PathBuf,
  8. process::{Command as StdCommand, Stdio},
  9. sync::{Arc, Mutex, RwLock},
  10. thread::spawn,
  11. };
  12. #[cfg(unix)]
  13. use std::os::unix::process::ExitStatusExt;
  14. #[cfg(windows)]
  15. use std::os::windows::process::CommandExt;
  16. #[cfg(windows)]
  17. const CREATE_NO_WINDOW: u32 = 0x0800_0000;
  18. use crate::async_runtime::{block_on as block_on_task, channel, Receiver};
  19. use os_pipe::{pipe, PipeWriter};
  20. use serde::Serialize;
  21. use shared_child::SharedChild;
  22. use tauri_utils::platform;
  23. type ChildStore = Arc<Mutex<HashMap<u32, Arc<SharedChild>>>>;
  24. fn commands() -> &'static ChildStore {
  25. use once_cell::sync::Lazy;
  26. static STORE: Lazy<ChildStore> = Lazy::new(Default::default);
  27. &STORE
  28. }
  29. /// Kills all child processes created with [`Command`].
  30. /// By default it's called before the [`crate::App`] exits.
  31. pub fn kill_children() {
  32. for child in commands().lock().unwrap().values() {
  33. let _ = child.kill();
  34. }
  35. }
  36. /// Payload for the [`CommandEvent::Terminated`] command event.
  37. #[derive(Debug, Clone, Serialize)]
  38. pub struct TerminatedPayload {
  39. /// Exit code of the process.
  40. pub code: Option<i32>,
  41. /// If the process was terminated by a signal, represents that signal.
  42. pub signal: Option<i32>,
  43. }
  44. /// A event sent to the command callback.
  45. #[derive(Debug, Clone, Serialize)]
  46. #[serde(tag = "event", content = "payload")]
  47. #[non_exhaustive]
  48. pub enum CommandEvent {
  49. /// Stderr line.
  50. Stderr(String),
  51. /// Stdout line.
  52. Stdout(String),
  53. /// An error happened.
  54. Error(String),
  55. /// Command process terminated.
  56. Terminated(TerminatedPayload),
  57. }
  58. macro_rules! get_std_command {
  59. ($self: ident) => {{
  60. let mut command = StdCommand::new($self.program);
  61. command.args(&$self.args);
  62. command.stdout(Stdio::piped());
  63. command.stdin(Stdio::piped());
  64. command.stderr(Stdio::piped());
  65. if $self.env_clear {
  66. command.env_clear();
  67. }
  68. command.envs($self.env);
  69. if let Some(current_dir) = $self.current_dir {
  70. command.current_dir(current_dir);
  71. }
  72. #[cfg(windows)]
  73. command.creation_flags(CREATE_NO_WINDOW);
  74. command
  75. }};
  76. }
  77. /// The type to spawn commands.
  78. #[derive(Debug)]
  79. pub struct Command {
  80. program: String,
  81. args: Vec<String>,
  82. env_clear: bool,
  83. env: HashMap<String, String>,
  84. current_dir: Option<PathBuf>,
  85. }
  86. /// Spawned child process.
  87. #[derive(Debug)]
  88. pub struct CommandChild {
  89. inner: Arc<SharedChild>,
  90. stdin_writer: PipeWriter,
  91. }
  92. impl CommandChild {
  93. /// Writes to process stdin.
  94. pub fn write(&mut self, buf: &[u8]) -> crate::api::Result<()> {
  95. self.stdin_writer.write_all(buf)?;
  96. Ok(())
  97. }
  98. /// Sends a kill signal to the child.
  99. pub fn kill(self) -> crate::api::Result<()> {
  100. self.inner.kill()?;
  101. Ok(())
  102. }
  103. /// Returns the process pid.
  104. pub fn pid(&self) -> u32 {
  105. self.inner.id()
  106. }
  107. }
  108. /// Describes the result of a process after it has terminated.
  109. #[derive(Debug)]
  110. pub struct ExitStatus {
  111. code: Option<i32>,
  112. }
  113. impl ExitStatus {
  114. /// Returns the exit code of the process, if any.
  115. pub fn code(&self) -> Option<i32> {
  116. self.code
  117. }
  118. /// Returns true if exit status is zero. Signal termination is not considered a success, and success is defined as a zero exit status.
  119. pub fn success(&self) -> bool {
  120. self.code == Some(0)
  121. }
  122. }
  123. /// The output of a finished process.
  124. #[derive(Debug)]
  125. pub struct Output {
  126. /// The status (exit code) of the process.
  127. pub status: ExitStatus,
  128. /// The data that the process wrote to stdout.
  129. pub stdout: String,
  130. /// The data that the process wrote to stderr.
  131. pub stderr: String,
  132. }
  133. #[cfg(not(windows))]
  134. fn relative_command_path(command: String) -> crate::Result<String> {
  135. match std::env::current_exe()?.parent() {
  136. Some(exe_dir) => Ok(format!("{}/{}", exe_dir.to_string_lossy(), command)),
  137. None => Err(crate::api::Error::Command("Could not evaluate executable dir".to_string()).into()),
  138. }
  139. }
  140. #[cfg(windows)]
  141. fn relative_command_path(command: String) -> crate::Result<String> {
  142. match std::env::current_exe()?.parent() {
  143. Some(exe_dir) => Ok(format!("{}/{}.exe", exe_dir.to_string_lossy(), command)),
  144. None => Err(crate::api::Error::Command("Could not evaluate executable dir".to_string()).into()),
  145. }
  146. }
  147. impl Command {
  148. /// Creates a new Command for launching the given program.
  149. pub fn new<S: Into<String>>(program: S) -> Self {
  150. Self {
  151. program: program.into(),
  152. args: Default::default(),
  153. env_clear: false,
  154. env: Default::default(),
  155. current_dir: None,
  156. }
  157. }
  158. /// Creates a new Command for launching the given sidecar program.
  159. ///
  160. /// A sidecar program is a embedded external binary in order to make your application work
  161. /// or to prevent users having to install additional dependencies (e.g. Node.js, Python, etc).
  162. pub fn new_sidecar<S: Into<String>>(program: S) -> crate::Result<Self> {
  163. let program = format!(
  164. "{}-{}",
  165. program.into(),
  166. platform::target_triple().expect("unsupported platform")
  167. );
  168. Ok(Self::new(relative_command_path(program)?))
  169. }
  170. /// Appends arguments to the command.
  171. pub fn args<I, S>(mut self, args: I) -> Self
  172. where
  173. I: IntoIterator<Item = S>,
  174. S: AsRef<str>,
  175. {
  176. for arg in args {
  177. self.args.push(arg.as_ref().to_string());
  178. }
  179. self
  180. }
  181. /// Clears the entire environment map for the child process.
  182. pub fn env_clear(mut self) -> Self {
  183. self.env_clear = true;
  184. self
  185. }
  186. /// Adds or updates multiple environment variable mappings.
  187. pub fn envs(mut self, env: HashMap<String, String>) -> Self {
  188. self.env = env;
  189. self
  190. }
  191. /// Sets the working directory for the child process.
  192. pub fn current_dir(mut self, current_dir: PathBuf) -> Self {
  193. self.current_dir.replace(current_dir);
  194. self
  195. }
  196. /// Spawns the command.
  197. pub fn spawn(self) -> crate::api::Result<(Receiver<CommandEvent>, CommandChild)> {
  198. let mut command = get_std_command!(self);
  199. let (stdout_reader, stdout_writer) = pipe()?;
  200. let (stderr_reader, stderr_writer) = pipe()?;
  201. let (stdin_reader, stdin_writer) = pipe()?;
  202. command.stdout(stdout_writer);
  203. command.stderr(stderr_writer);
  204. command.stdin(stdin_reader);
  205. let shared_child = SharedChild::spawn(&mut command)?;
  206. let child = Arc::new(shared_child);
  207. let child_ = child.clone();
  208. let guard = Arc::new(RwLock::new(()));
  209. commands().lock().unwrap().insert(child.id(), child.clone());
  210. let (tx, rx) = channel(1);
  211. let tx_ = tx.clone();
  212. let guard_ = guard.clone();
  213. spawn(move || {
  214. let _lock = guard_.read().unwrap();
  215. let reader = BufReader::new(stdout_reader);
  216. for line in reader.lines() {
  217. let tx_ = tx_.clone();
  218. block_on_task(async move {
  219. let _ = match line {
  220. Ok(line) => tx_.send(CommandEvent::Stdout(line)).await,
  221. Err(e) => tx_.send(CommandEvent::Error(e.to_string())).await,
  222. };
  223. });
  224. }
  225. });
  226. let tx_ = tx.clone();
  227. let guard_ = guard.clone();
  228. spawn(move || {
  229. let _lock = guard_.read().unwrap();
  230. let reader = BufReader::new(stderr_reader);
  231. for line in reader.lines() {
  232. let tx_ = tx_.clone();
  233. block_on_task(async move {
  234. let _ = match line {
  235. Ok(line) => tx_.send(CommandEvent::Stderr(line)).await,
  236. Err(e) => tx_.send(CommandEvent::Error(e.to_string())).await,
  237. };
  238. });
  239. }
  240. });
  241. spawn(move || {
  242. let _ = match child_.wait() {
  243. Ok(status) => {
  244. let _l = guard.write().unwrap();
  245. commands().lock().unwrap().remove(&child_.id());
  246. let _ = block_on_task(async move {
  247. tx.send(CommandEvent::Terminated(TerminatedPayload {
  248. code: status.code(),
  249. #[cfg(windows)]
  250. signal: None,
  251. #[cfg(unix)]
  252. signal: status.signal(),
  253. }))
  254. .await
  255. });
  256. }
  257. Err(e) => {
  258. let _l = guard.write().unwrap();
  259. let _ = block_on_task(async move { tx.send(CommandEvent::Error(e.to_string())).await });
  260. }
  261. };
  262. });
  263. Ok((
  264. rx,
  265. CommandChild {
  266. inner: child,
  267. stdin_writer,
  268. },
  269. ))
  270. }
  271. /// Executes a command as a child process, waiting for it to finish and collecting its exit status.
  272. /// Stdin, stdout and stderr are ignored.
  273. pub fn status(self) -> crate::api::Result<ExitStatus> {
  274. let (mut rx, _child) = self.spawn()?;
  275. let code = crate::async_runtime::safe_block_on(async move {
  276. let mut code = None;
  277. #[allow(clippy::collapsible_match)]
  278. while let Some(event) = rx.recv().await {
  279. if let CommandEvent::Terminated(payload) = event {
  280. code = payload.code;
  281. }
  282. }
  283. code
  284. });
  285. Ok(ExitStatus { code })
  286. }
  287. /// Executes the command as a child process, waiting for it to finish and collecting all of its output.
  288. /// Stdin is ignored.
  289. pub fn output(self) -> crate::api::Result<Output> {
  290. let (mut rx, _child) = self.spawn()?;
  291. let output = crate::async_runtime::safe_block_on(async move {
  292. let mut code = None;
  293. let mut stdout = String::new();
  294. let mut stderr = String::new();
  295. while let Some(event) = rx.recv().await {
  296. match event {
  297. CommandEvent::Terminated(payload) => {
  298. code = payload.code;
  299. }
  300. CommandEvent::Stdout(line) => {
  301. stdout.push_str(line.as_str());
  302. stdout.push('\n');
  303. }
  304. CommandEvent::Stderr(line) => {
  305. stderr.push_str(line.as_str());
  306. stderr.push('\n');
  307. }
  308. CommandEvent::Error(_) => {}
  309. }
  310. }
  311. Output {
  312. status: ExitStatus { code },
  313. stdout,
  314. stderr,
  315. }
  316. });
  317. Ok(output)
  318. }
  319. }
  320. // tests for the commands functions.
  321. #[cfg(test)]
  322. mod test {
  323. #[cfg(not(windows))]
  324. use super::*;
  325. #[cfg(not(windows))]
  326. #[test]
  327. fn test_cmd_output() {
  328. // create a command to run cat.
  329. let cmd = Command::new("cat").args(&["test/api/test.txt"]);
  330. let (mut rx, _) = cmd.spawn().unwrap();
  331. crate::async_runtime::block_on(async move {
  332. while let Some(event) = rx.recv().await {
  333. match event {
  334. CommandEvent::Terminated(payload) => {
  335. assert_eq!(payload.code, Some(0));
  336. }
  337. CommandEvent::Stdout(line) => {
  338. assert_eq!(line, "This is a test doc!".to_string());
  339. }
  340. _ => {}
  341. }
  342. }
  343. });
  344. }
  345. #[cfg(not(windows))]
  346. #[test]
  347. // test the failure case
  348. fn test_cmd_fail() {
  349. let cmd = Command::new("cat").args(&["test/api/"]);
  350. let (mut rx, _) = cmd.spawn().unwrap();
  351. crate::async_runtime::block_on(async move {
  352. while let Some(event) = rx.recv().await {
  353. match event {
  354. CommandEvent::Terminated(payload) => {
  355. assert_eq!(payload.code, Some(1));
  356. }
  357. CommandEvent::Stderr(line) => {
  358. assert_eq!(line, "cat: test/api/: Is a directory".to_string());
  359. }
  360. _ => {}
  361. }
  362. }
  363. });
  364. }
  365. }