From 0ee6e23c1b4ec4ef7ee2fcf79c2d50ee1278f649 Mon Sep 17 00:00:00 2001 From: Emile Fugulin Date: Tue, 24 Dec 2024 16:29:20 -0500 Subject: [PATCH 1/6] Add wsl --- README.md | 6 +++--- src/execution.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++---- src/script.rs | 2 +- src/shell.rs | 11 ++++++---- 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f7c5577..06f424b 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ use std::time::Duration; use shell_exec::{Execution, Shell}; let execution = Execution::builder() - .shell(Shell::Cmd) + .shell(Shell::Bash) .cmd( r#" - set /p input="" - echo hello %input% + INPUT=`cat -`; + echo "hello $INPUT" "# .to_string(), ) diff --git a/src/execution.rs b/src/execution.rs index e667bdd..3ad09e3 100644 --- a/src/execution.rs +++ b/src/execution.rs @@ -36,9 +36,15 @@ impl Execution { let full_cmd = Script::build(self.shell, self.cmd, self.init).await?; // Spawn - // NOTE: If kill_on_drop is proven not sufficiently reliable, we might want to explicitly kill the process before exiting the function. This approach is slower since it awaits the process termination. - let mut cmd_handle = Command::new(self.shell.to_string()) - .arg(self.shell.command_arg()) + // NOTE: If kill_on_drop is proven not sufficiently reliable, we might want to explicitly kill the process + // before exiting the function. This approach is slower since it awaits the process termination. + let mut builder = Command::new(self.shell.to_string()); + if let Some(command_arg) = self.shell.command_arg() { + builder.arg(command_arg); + } + let mut cmd_handle = builder + .arg("bash") + .arg("-c") .arg(&full_cmd) .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -95,7 +101,7 @@ mod tests { #[tokio::test] #[cfg(unix)] - async fn should_execute() { + async fn should_execute_sh() { let execution = Execution::builder() .shell(Shell::Sh) .cmd(r#"jq -r .hello"#.to_string()) @@ -107,6 +113,26 @@ mod tests { assert_eq!(b"world"[..], data); } + #[tokio::test] + #[cfg(unix)] + async fn should_execute_bash() { + let execution = Execution::builder() + .shell(Shell::Bash) + .cmd( + r#" + INPUT=`cat -`; + echo "hello $INPUT" + "# + .to_string(), + ) + .timeout(Duration::from_millis(10000)) + .build(); + + let data = execution.execute(b"world").await.unwrap(); + + assert_eq!(b"hello world"[..], data); + } + #[tokio::test] #[cfg(unix)] async fn should_execute_with_envs() { @@ -206,4 +232,24 @@ mod tests { assert_eq!(b"hello\n & WORLD"[..], data); } + + #[tokio::test] + #[cfg(windows)] + async fn should_execute_wsl() { + let execution = Execution::builder() + .shell(Shell::Wsl) + .cmd( + r#" + INPUT=`cat -`; + echo "hello $INPUT" + "# + .to_string(), + ) + .timeout(Duration::from_millis(10000)) + .build(); + + let data = execution.execute(b"world").await.unwrap(); + + assert_eq!(b"hello world"[..], data); + } } diff --git a/src/script.rs b/src/script.rs index f1d73f9..7248b6f 100644 --- a/src/script.rs +++ b/src/script.rs @@ -48,7 +48,7 @@ fn init_line(script: &str, shell: Shell) -> String { match shell { Shell::Cmd => format!("{script} 2> nul"), Shell::Powershell => format!("{script} 2>$null"), - Shell::Bash | Shell::Zsh | Shell::Sh => format!("{script} > /dev/null 2>&1"), + Shell::Bash | Shell::Zsh | Shell::Sh | Shell::Wsl => format!("{script} > /dev/null 2>&1"), } } diff --git a/src/shell.rs b/src/shell.rs index ea33195..97261ea 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -12,14 +12,17 @@ pub enum Shell { Cmd, #[strum(serialize = "powershell")] Powershell, + #[strum(serialize = "wsl")] + Wsl, } impl Shell { - pub fn command_arg<'a>(&self) -> &'a str { + pub fn command_arg(&self) -> Option<&'static str> { match self { - Self::Cmd => "/C", - Self::Powershell => "-Command", - _ => "-c", + Self::Cmd => Some("/C"), + Self::Powershell => Some("-Command"), + Self::Wsl => None, + _ => Some("-c"), } } } From 82f6dd60d025b58a90c027e0e3b11f6b5aa155d4 Mon Sep 17 00:00:00 2001 From: Emile Fugulin Date: Tue, 24 Dec 2024 17:23:53 -0500 Subject: [PATCH 2/6] Fix WSL --- src/argument.rs | 23 +++++++++++++++++++++++ src/execution.rs | 14 ++++++-------- src/lib.rs | 2 ++ src/script.rs | 18 +++++++++--------- src/shell.rs | 12 +++++++----- 5 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 src/argument.rs diff --git a/src/argument.rs b/src/argument.rs new file mode 100644 index 0000000..01ebc71 --- /dev/null +++ b/src/argument.rs @@ -0,0 +1,23 @@ +use std::ffi::OsStr; + +use tokio::process::Command; + +pub enum Argument<'a> { + Normal(&'a str), + Path(&'a OsStr), + Raw(&'a str), +} + +pub trait CommandArgument { + fn argument<'a>(&mut self, arg: &Argument<'a>) -> &mut Self; +} + +impl CommandArgument for Command { + fn argument<'a>(&mut self, arg: &Argument<'a>) -> &mut Self { + match arg { + Argument::Normal(value) => self.arg(value), + Argument::Path(value) => self.arg(value), + Argument::Raw(value) => self.raw_arg(value), + } + } +} diff --git a/src/execution.rs b/src/execution.rs index 3ad09e3..2409564 100644 --- a/src/execution.rs +++ b/src/execution.rs @@ -9,7 +9,7 @@ use tokio::process::Command; use tokio::time::timeout; use typed_builder::TypedBuilder; -use crate::{Result, Script, Shell, ShellError}; +use crate::{CommandArgument, Result, Script, Shell, ShellError}; #[derive(TypedBuilder)] pub struct Execution { @@ -33,19 +33,17 @@ impl Execution { V: AsRef, { // Prepare script - let full_cmd = Script::build(self.shell, self.cmd, self.init).await?; + let script = Script::build(self.shell, self.cmd, self.init).await?; // Spawn // NOTE: If kill_on_drop is proven not sufficiently reliable, we might want to explicitly kill the process // before exiting the function. This approach is slower since it awaits the process termination. let mut builder = Command::new(self.shell.to_string()); - if let Some(command_arg) = self.shell.command_arg() { - builder.arg(command_arg); + for arg in self.shell.command_args() { + builder.argument(arg); } let mut cmd_handle = builder - .arg("bash") - .arg("-c") - .arg(&full_cmd) + .argument(&script.argument()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -240,7 +238,7 @@ mod tests { .shell(Shell::Wsl) .cmd( r#" - INPUT=`cat -`; + INPUT=$(cat); echo "hello $INPUT" "# .to_string(), diff --git a/src/lib.rs b/src/lib.rs index c6255be..9d23b45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ +use self::argument::{Argument, CommandArgument}; use self::errors::Result; pub use self::errors::ShellError; pub use self::execution::Execution; use self::script::Script; pub use self::shell::Shell; +mod argument; mod errors; mod execution; mod script; diff --git a/src/script.rs b/src/script.rs index 7248b6f..55b742b 100644 --- a/src/script.rs +++ b/src/script.rs @@ -1,12 +1,11 @@ -use std::ffi::OsStr; use std::io::Write; use tempfile::TempPath; -use crate::{Result, Shell, ShellError}; +use crate::{Argument, Result, Shell, ShellError}; pub enum Script { - Inline(String), + Inline { raw: String, shell: Shell }, File(TempPath), } @@ -29,17 +28,18 @@ impl Script { let file = write_file(raw).await?; Self::File(file) } - _ => Self::Inline(raw), + _ => Self::Inline { raw, shell }, }; Ok(cmd) } -} -impl AsRef for &Script { - fn as_ref(&self) -> &OsStr { + pub fn argument(&self) -> Argument<'_> { match self { - Script::Inline(v) => v.as_ref(), - Script::File(v) => v.as_os_str(), + Script::Inline { raw, shell } => match shell { + Shell::Wsl => Argument::Raw(raw), + _ => Argument::Normal(raw), + }, + Script::File(path) => Argument::Path(path.as_os_str()), } } } diff --git a/src/shell.rs b/src/shell.rs index 97261ea..8d3c66a 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -1,5 +1,7 @@ use strum::{Display, EnumString}; +use crate::Argument; + #[derive(Debug, EnumString, Display, Copy, Clone)] pub enum Shell { #[strum(serialize = "zsh")] @@ -17,12 +19,12 @@ pub enum Shell { } impl Shell { - pub fn command_arg(&self) -> Option<&'static str> { + pub fn command_args(&self) -> &[Argument<'static>] { match self { - Self::Cmd => Some("/C"), - Self::Powershell => Some("-Command"), - Self::Wsl => None, - _ => Some("-c"), + Self::Cmd => &[Argument::Normal("/C")], + Self::Powershell => &[Argument::Normal("-Command")], + Self::Wsl => &[Argument::Normal("bash"), Argument::Normal("-c")], + _ => &[Argument::Normal("-c")], } } } From 588df657501a99fa4ac9c1497b18beca0bea4bbf Mon Sep 17 00:00:00 2001 From: Emile Fugulin Date: Tue, 24 Dec 2024 17:27:45 -0500 Subject: [PATCH 3/6] Fix windows problem --- src/argument.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/argument.rs b/src/argument.rs index 01ebc71..f44f5b5 100644 --- a/src/argument.rs +++ b/src/argument.rs @@ -17,7 +17,16 @@ impl CommandArgument for Command { match arg { Argument::Normal(value) => self.arg(value), Argument::Path(value) => self.arg(value), - Argument::Raw(value) => self.raw_arg(value), + Argument::Raw(value) => { + #[cfg(windows)] + { + self.raw_arg(value) + } + #[cfg(unix)] + { + self.arg(value) + } + } } } } From 74765ba05e2c78ecafc309aa5e297767fc3c22bc Mon Sep 17 00:00:00 2001 From: Emile Fugulin Date: Tue, 24 Dec 2024 17:29:55 -0500 Subject: [PATCH 4/6] Fix clippy --- src/argument.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/argument.rs b/src/argument.rs index f44f5b5..007f5e3 100644 --- a/src/argument.rs +++ b/src/argument.rs @@ -9,11 +9,11 @@ pub enum Argument<'a> { } pub trait CommandArgument { - fn argument<'a>(&mut self, arg: &Argument<'a>) -> &mut Self; + fn argument(&mut self, arg: &Argument<'_>) -> &mut Self; } impl CommandArgument for Command { - fn argument<'a>(&mut self, arg: &Argument<'a>) -> &mut Self { + fn argument(&mut self, arg: &Argument<'_>) -> &mut Self { match arg { Argument::Normal(value) => self.arg(value), Argument::Path(value) => self.arg(value), From 0a17b2af77edb29458430e97694b0c1453858c80 Mon Sep 17 00:00:00 2001 From: Emile Fugulin Date: Tue, 24 Dec 2024 17:29:59 -0500 Subject: [PATCH 5/6] Bump version --- Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c2f3e9..045cbfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shell_exec" -version = "0.1.0" +version = "0.2.0" authors = ["Caido Labs Inc. "] description = "Cross platform library to execute shell scripts" repository = "https://github.com/caido/shell_exec" @@ -18,10 +18,10 @@ strum = { version = "0.26", features = ["derive"] } tempfile = "3.12" thiserror = "1" tokio = { version = "1", features = [ - "time", - "process", - "io-util", - "macros", - "rt", + "time", + "process", + "io-util", + "macros", + "rt", ] } typed-builder = "0.20" From 2c1e7734c3ffa620b03faa532d9dbcd399c0f4c6 Mon Sep 17 00:00:00 2001 From: Emile Fugulin Date: Tue, 24 Dec 2024 18:02:04 -0500 Subject: [PATCH 6/6] Setup wsl for windows tests --- .github/workflows/ci.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f3e171d..b30411d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,8 +38,8 @@ jobs: test: needs: lint runs-on: ${{ matrix.os }} - timeout-minutes: 5 - + timeout-minutes: 10 + strategy: matrix: os: [ubuntu-latest, windows-latest] @@ -53,5 +53,11 @@ jobs: with: cache: false + - name: Setup WSL + if: ${{ matrix.os == 'windows-latest' }} + uses: caido/action-setup-wsl@v4 + with: + distribution: Ubuntu-22.04 + - name: Run tests - run: cargo test \ No newline at end of file + run: cargo test