25fb843c40
The prior commit switched the recipe to `[source] path = "source"`, but .gitignore:77 still listed `local/recipes/shells/brush/source` (a leftover from when brush was a transient upstream git fetch), so the vendored tree was not tracked — a fresh clone would have no brush source and the build would fail. Drop that ignore line and commit the vendored working tree (reubeno/brush @ 897b373e, with the Redox port patches pre-applied). brush is now a durable local fork like the other path=source recipes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
2.8 KiB
Rust
84 lines
2.8 KiB
Rust
use clap::Parser;
|
|
use std::{borrow::Cow, os::unix::process::CommandExt};
|
|
|
|
use brush_core::{ErrorKind, ExecutionExitCode, ExecutionResult, builtins, commands};
|
|
|
|
/// Exec the provided command.
|
|
#[derive(Parser)]
|
|
pub(crate) struct ExecCommand {
|
|
/// Pass given name as zeroth argument to command.
|
|
#[arg(short = 'a', value_name = "NAME")]
|
|
name_for_argv0: Option<String>,
|
|
|
|
/// Exec command with an empty environment.
|
|
#[arg(short = 'c')]
|
|
empty_environment: bool,
|
|
|
|
/// Exec command as a login shell.
|
|
#[arg(short = 'l')]
|
|
exec_as_login: bool,
|
|
|
|
/// Command and args.
|
|
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
|
args: Vec<String>,
|
|
}
|
|
|
|
impl builtins::Command for ExecCommand {
|
|
type Error = brush_core::Error;
|
|
|
|
async fn execute<SE: brush_core::ShellExtensions>(
|
|
&self,
|
|
context: brush_core::ExecutionContext<'_, SE>,
|
|
) -> Result<ExecutionResult, Self::Error> {
|
|
if self.args.is_empty() {
|
|
// When no arguments are present, then there's nothing for us to execute -- but we need
|
|
// to ensure that any redirections setup for this builtin get applied to the calling
|
|
// shell instance.
|
|
#[allow(clippy::needless_collect)]
|
|
let fds: Vec<_> = context.iter_fds().collect();
|
|
|
|
context.shell.replace_open_files(fds.into_iter());
|
|
return Ok(ExecutionResult::success());
|
|
}
|
|
|
|
// If we know we're already running in a subshell, then `exec`ing is actually
|
|
// unsafe, since it would also replace the *parent* shell instance. We instead
|
|
// delegate to the `command` builtin to perform the execution, with an expectation
|
|
// of returning.
|
|
if context.shell.is_subshell() {
|
|
if self.empty_environment || self.exec_as_login || self.name_for_argv0.is_some() {
|
|
return brush_core::error::unimp("exec with options in subshell not yet supported");
|
|
}
|
|
|
|
let cmd_cmd = crate::command::CommandCommand {
|
|
command_and_args: self.args.clone(),
|
|
..Default::default()
|
|
};
|
|
|
|
return cmd_cmd.execute(context).await;
|
|
}
|
|
|
|
let mut argv0 = Cow::Borrowed(self.name_for_argv0.as_ref().unwrap_or(&self.args[0]));
|
|
|
|
if self.exec_as_login {
|
|
argv0 = Cow::Owned(std::format!("-{argv0}"));
|
|
}
|
|
|
|
let mut cmd = commands::compose_std_command(
|
|
&context,
|
|
&self.args[0],
|
|
argv0.as_str(),
|
|
&self.args[1..],
|
|
self.empty_environment,
|
|
)?;
|
|
|
|
let exec_error = cmd.exec();
|
|
|
|
if exec_error.kind() == std::io::ErrorKind::NotFound {
|
|
Ok(ExecutionExitCode::NotFound.into())
|
|
} else {
|
|
Err(ErrorKind::from(exec_error).into())
|
|
}
|
|
}
|
|
}
|