forked from extern/nushell
feat: spawn the executables directly if possible (#3974)
* feat: spawn the executables directly if possible This pull request changes nu-command so that it spawns the process directly if: - They are a `.exe` on Windows - They are not a `.sh` or `.bash` on not windows. Benefits: - As I explained in [this comment](https://github.com/nushell/nushell/issues/3898#issuecomment-894000812), this is another step towards making Nushell a standalone shell, that doesn't need to shell out unless it is running a script for a particular shell (cmd, sh, ps1, etc.). - Fixes the bug with multiline strings - Better performance due to direct spawning. For example, this script shows ~20 ms less latency. After: ```nu C:\> benchmark { node -e 'console.log("sssss")' } ───┬────────────────── # │ real time ───┼────────────────── 0 │ 63ms 921us 600ns ───┴────────────────── ``` Before ```nu C:\> benchmark { node -e 'console.log("sssss")' } ───┬────────────────── # │ real time ───┼────────────────── 0 │ 79ms 136us 800ns ───┴────────────────── ``` Fixes #3898 * fix: make which dependency optional Also fixes clippy warnings * refactor: refactor spawn_exe, spawn_cmd, spawn_sh, and spawn_any * fix: use which feature instead of which-support * fix: use which_in to use the cwd of nu * fix: use case insensitive comparison of the extensions Sometimes the case of the extension is uppercased by the "which_in" function Also use unix instead of not windows. Some os might not have sh support
This commit is contained in:
parent
08014c6a98
commit
260ff99710
@ -4,7 +4,10 @@ use nu_engine::{MaybeTextCodec, StringOrBinary};
|
|||||||
use nu_test_support::NATIVE_PATH_ENV_VAR;
|
use nu_test_support::NATIVE_PATH_ENV_VAR;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
use std::env;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::{borrow::Cow, io::BufReader};
|
use std::{borrow::Cow, io::BufReader};
|
||||||
@ -17,6 +20,9 @@ use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
|||||||
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
|
|
||||||
|
#[cfg(feature = "which")]
|
||||||
|
use which::which_in;
|
||||||
|
|
||||||
pub(crate) fn run_external_command(
|
pub(crate) fn run_external_command(
|
||||||
command: ExternalCommand,
|
command: ExternalCommand,
|
||||||
context: &mut EvaluationContext,
|
context: &mut EvaluationContext,
|
||||||
@ -141,19 +147,18 @@ fn run_with_stdin(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn(
|
/// Spawn a direct exe
|
||||||
command: &ExternalCommand,
|
#[allow(unused)]
|
||||||
path: &str,
|
fn spawn_exe(full_path: PathBuf, args: &[String]) -> Command {
|
||||||
args: &[String],
|
let mut process = Command::new(full_path);
|
||||||
input: InputStream,
|
for arg in args {
|
||||||
external_redirection: ExternalRedirection,
|
process.arg(&arg);
|
||||||
scope: &Scope,
|
}
|
||||||
) -> Result<InputStream, ShellError> {
|
process
|
||||||
let command = command.clone();
|
}
|
||||||
|
|
||||||
let mut process = {
|
/// Spawn a cmd command with `cmd /c args...`
|
||||||
#[cfg(windows)]
|
fn spawn_cmd_command(command: &ExternalCommand, args: &[String]) -> Command {
|
||||||
{
|
|
||||||
let mut process = Command::new("cmd");
|
let mut process = Command::new("cmd");
|
||||||
process.arg("/c");
|
process.arg("/c");
|
||||||
process.arg(&command.name);
|
process.arg(&command.name);
|
||||||
@ -165,17 +170,67 @@ fn spawn(
|
|||||||
process.arg(&arg);
|
process.arg(&arg);
|
||||||
}
|
}
|
||||||
process
|
process
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
/// Spawn a sh command with `sh -c args...`
|
||||||
{
|
fn spawn_sh_command(command: &ExternalCommand, args: &[String]) -> Command {
|
||||||
let cmd_with_args = vec![command.name.clone(), args.join(" ")].join(" ");
|
let cmd_with_args = vec![command.name.clone(), args.join(" ")].join(" ");
|
||||||
let mut process = Command::new("sh");
|
let mut process = Command::new("sh");
|
||||||
process.arg("-c").arg(cmd_with_args);
|
process.arg("-c").arg(cmd_with_args);
|
||||||
process
|
process
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
/// a function to spawn any external command
|
||||||
|
#[allow(unused)] // for minimal builds cwd is unused
|
||||||
|
fn spawn_any(command: &ExternalCommand, args: &[String], cwd: &str) -> Command {
|
||||||
|
// resolve the executable name if it is spawnable directly
|
||||||
|
#[cfg(feature = "which")]
|
||||||
|
// TODO add more available paths to `env::var_os("PATH")`?
|
||||||
|
if let Result::Ok(full_path) = which_in(&command.name, env::var_os("PATH"), cwd) {
|
||||||
|
if let Some(extension) = full_path.extension() {
|
||||||
|
#[cfg(windows)]
|
||||||
|
if extension.eq_ignore_ascii_case("exe") {
|
||||||
|
// if exe spawn it directly
|
||||||
|
return spawn_exe(full_path, args);
|
||||||
|
} else {
|
||||||
|
// TODO implement special care for various executable types such as .bat, .ps1, .cmd, etc
|
||||||
|
// https://github.com/mklement0/Native/blob/e0e0b8785cad39a73053e35084d1f60d87fbac58/Native.psm1#L749
|
||||||
|
// otherwise shell out to cmd
|
||||||
|
return spawn_cmd_command(command, args);
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
if !["sh", "bash"]
|
||||||
|
.iter()
|
||||||
|
.any(|ext| extension.eq_ignore_ascii_case(ext))
|
||||||
|
{
|
||||||
|
// if exe spawn it directly
|
||||||
|
return spawn_exe(full_path, args);
|
||||||
|
} else {
|
||||||
|
// otherwise shell out to sh
|
||||||
|
return spawn_sh_command(command, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// in all the other cases shell out
|
||||||
|
if cfg!(windows) {
|
||||||
|
spawn_cmd_command(command, args)
|
||||||
|
} else {
|
||||||
|
// TODO what happens if that os doesn't support spawning sh?
|
||||||
|
spawn_sh_command(command, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn(
|
||||||
|
command: &ExternalCommand,
|
||||||
|
path: &str,
|
||||||
|
args: &[String],
|
||||||
|
input: InputStream,
|
||||||
|
external_redirection: ExternalRedirection,
|
||||||
|
scope: &Scope,
|
||||||
|
) -> Result<InputStream, ShellError> {
|
||||||
|
let command = command.clone();
|
||||||
|
|
||||||
|
let mut process = spawn_any(&command, args, path);
|
||||||
process.current_dir(path);
|
process.current_dir(path);
|
||||||
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
trace!(target: "nu::run::external", "cwd = {:?}", &path);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user