From 1fd3bc1ba600e5037993ee5eec08a23826933e0f Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Wed, 8 Nov 2023 20:50:25 +0000 Subject: [PATCH] Add `exec` command for Windows (#11001) # Description Based of the work and discussion in #10844, this PR adds the `exec` command for Windows. This is done by simply spawning a `std::process::Command` and then immediately exiting via `std::process::exit` once the child process is finished. The child process's exit code is passed to `exit`. # User-Facing Changes The `exec` command is now available on Windows, and there should be no change in behaviour for Unix systems. --- crates/nu-command/src/default_context.rs | 4 +- crates/nu-command/src/system/exec.rs | 67 ++++++++++++++---------- crates/nu-command/src/system/mod.rs | 2 - crates/nu-command/tests/commands/mod.rs | 1 - 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 0c50edda7b..7f57d6a8df 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -115,6 +115,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { bind_command! { Complete, External, + Exec, NuCheck, Sys, }; @@ -145,9 +146,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { ViewSpan, }; - #[cfg(unix)] - bind_command! { Exec } - #[cfg(windows)] bind_command! { RegistryQuery } diff --git a/crates/nu-command/src/system/exec.rs b/crates/nu-command/src/system/exec.rs index f14692d17b..dd1d5af0cf 100644 --- a/crates/nu-command/src/system/exec.rs +++ b/crates/nu-command/src/system/exec.rs @@ -1,11 +1,10 @@ use super::run_external::create_external_command; -use nu_engine::{current_dir, CallExt}; +use nu_engine::current_dir; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, }; -use std::os::unix::process::CommandExt; #[derive(Clone)] pub struct Exec; @@ -24,11 +23,12 @@ impl Command for Exec { } fn usage(&self) -> &str { - "Execute a command, replacing the current process." + "Execute a command, replacing or exiting the current process, depending on platform." } fn extra_usage(&self) -> &str { - "Currently supported only on Unix-based systems." + r#"On Unix-based systems, the current process is replaced with the command. +On Windows based systems, Nushell will wait for the command to finish and then exit with the command's exit code."# } fn run( @@ -62,36 +62,49 @@ fn exec( stack: &mut Stack, call: &Call, ) -> Result { - let name: Spanned = call.req(engine_state, stack, 0)?; - let name_span = name.span; - - let redirect_stdout = call.has_flag("redirect-stdout"); - let redirect_stderr = call.has_flag("redirect-stderr"); - let redirect_combine = call.has_flag("redirect-combine"); - let trim_end_newline = call.has_flag("trim-end-newline"); - - let external_command = create_external_command( - engine_state, - stack, - call, - redirect_stdout, - redirect_stderr, - redirect_combine, - trim_end_newline, - )?; + let external_command = + create_external_command(engine_state, stack, call, false, false, false, false)?; let cwd = current_dir(engine_state, stack)?; let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy())?; command.current_dir(cwd); - command.envs(&external_command.env_vars); + command.envs(external_command.env_vars); - let err = command.exec(); // this replaces our process, should not return + // this either replaces our process and should not return, + // or the exec fails and we get an error back + exec_impl(command, call.head) +} + +#[cfg(unix)] +fn exec_impl(mut command: std::process::Command, span: Span) -> Result { + use std::os::unix::process::CommandExt; + + let error = command.exec(); Err(ShellError::GenericError( - "Error on exec".to_string(), - err.to_string(), - Some(name_span), + "Error on exec".into(), + error.to_string(), + Some(span), None, Vec::new(), )) } + +#[cfg(windows)] +fn exec_impl(mut command: std::process::Command, span: Span) -> Result { + match command.spawn() { + Ok(mut child) => match child.wait() { + Ok(status) => std::process::exit(status.code().unwrap_or(0)), + Err(e) => Err(ShellError::ExternalCommand { + label: "Error in external command".into(), + help: e.to_string(), + span, + }), + }, + Err(e) => Err(ShellError::ExternalCommand { + label: "Error spawning external command".into(), + help: e.to_string(), + span, + }), + } +} diff --git a/crates/nu-command/src/system/mod.rs b/crates/nu-command/src/system/mod.rs index 12ece2d93b..33a2709e2e 100644 --- a/crates/nu-command/src/system/mod.rs +++ b/crates/nu-command/src/system/mod.rs @@ -1,5 +1,4 @@ mod complete; -#[cfg(unix)] mod exec; mod nu_check; #[cfg(any( @@ -16,7 +15,6 @@ mod sys; mod which_; pub use complete::Complete; -#[cfg(unix)] pub use exec::Exec; pub use nu_check::NuCheck; #[cfg(any( diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 7e75c920de..96f82d8875 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -24,7 +24,6 @@ mod echo; mod empty; mod error_make; mod every; -#[cfg(not(windows))] mod exec; mod export_def; mod fill;