Skip to main content

turtle_build/infrastructure/command_runner/
os.rs

1use crate::infrastructure::CommandRunner;
2use async_trait::async_trait;
3use core::error::Error;
4use std::process::{ExitStatus, Output, Stdio};
5use tokio::{process::Command, sync::Semaphore};
6
7/// A command runner backed by an operating system.
8#[derive(Debug)]
9pub struct OsCommandRunner {
10    semaphore: Semaphore,
11}
12
13impl OsCommandRunner {
14    /// Creates a command runner.
15    pub fn new(job_limit: usize) -> Self {
16        Self {
17            semaphore: Semaphore::new(job_limit),
18        }
19    }
20
21    fn create_command(command: &str) -> Command {
22        if cfg!(target_os = "windows") {
23            let components = command.split_whitespace().collect::<Vec<_>>();
24            let mut process = Command::new(components[0]);
25
26            process.args(&components[1..]);
27            process
28        } else {
29            let mut process = Command::new("sh");
30
31            process.arg("-ec").arg(command);
32            process
33        }
34    }
35}
36
37#[async_trait]
38impl CommandRunner for OsCommandRunner {
39    async fn run(&self, command: &str) -> Result<Output, Box<dyn Error>> {
40        let _permit = self.semaphore.acquire().await?;
41
42        Ok(Self::create_command(command)
43            .stdin(Stdio::null())
44            .output()
45            .await?)
46    }
47
48    async fn run_with_console(&self, command: &str) -> Result<ExitStatus, Box<dyn Error>> {
49        let _permit = self.semaphore.acquire().await?;
50
51        // Inherit standard input, output, and error.
52        Ok(Self::create_command(command).status().await?)
53    }
54}