feat: enhance command execution with shell fallback and wrap helper commands
This commit is contained in:
+10
-5
@@ -116,9 +116,14 @@ pub fn build(
|
|||||||
build_function_mode_command(spec, destdir, &abs_build_script)?
|
build_function_mode_command(spec, destdir, &abs_build_script)?
|
||||||
} else {
|
} else {
|
||||||
let mut cmd = fakeroot::wrap_install_command("sh", destdir);
|
let mut cmd = fakeroot::wrap_install_command("sh", destdir);
|
||||||
// Run custom scripts with `-e` so command failures stop the build immediately
|
let wrapper = crate::shell_helpers::wrap_shell_command(". \"$1\"");
|
||||||
// instead of being masked by later shell commands.
|
// Run custom scripts through `sh -c` so helper commands like `haul`
|
||||||
cmd.arg("-e").arg(&abs_build_script);
|
// work even when the helper scripts live on a `noexec` mount.
|
||||||
|
cmd.arg("-eu")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(wrapper)
|
||||||
|
.arg("sh")
|
||||||
|
.arg(&abs_build_script);
|
||||||
cmd
|
cmd
|
||||||
};
|
};
|
||||||
cmd.current_dir(&build_dir);
|
cmd.current_dir(&build_dir);
|
||||||
@@ -179,8 +184,8 @@ fn build_function_mode_command(
|
|||||||
destdir: &Path,
|
destdir: &Path,
|
||||||
build_script: &Path,
|
build_script: &Path,
|
||||||
) -> Result<Command> {
|
) -> Result<Command> {
|
||||||
let mut wrapper = String::new();
|
let mut wrapper = crate::shell_helpers::wrap_shell_command("");
|
||||||
wrapper.push_str("set -eu\n");
|
wrapper.push_str("\nset -eu\n");
|
||||||
wrapper.push_str("depot_has_function() {\n");
|
wrapper.push_str("depot_has_function() {\n");
|
||||||
wrapper.push_str(" case \"$(type \"$1\" 2>/dev/null || :)\" in\n");
|
wrapper.push_str(" case \"$(type \"$1\" 2>/dev/null || :)\" in\n");
|
||||||
wrapper.push_str(" *function*) return 0 ;;\n");
|
wrapper.push_str(" *function*) return 0 ;;\n");
|
||||||
|
|||||||
+55
-4
@@ -59,7 +59,7 @@ pub fn build(
|
|||||||
}
|
}
|
||||||
crate::builder::prepare_tool_command(&mut configure_cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut configure_cmd, &env_vars);
|
||||||
|
|
||||||
let status = configure_cmd.status().with_context(|| {
|
let status = command_status_with_sh_fallback(&mut configure_cmd).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to run perl {} in {}",
|
"Failed to run perl {} in {}",
|
||||||
configure_script.display(),
|
configure_script.display(),
|
||||||
@@ -102,7 +102,7 @@ pub fn build(
|
|||||||
}
|
}
|
||||||
crate::builder::prepare_tool_command(&mut make_cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut make_cmd, &env_vars);
|
||||||
|
|
||||||
let status = make_cmd.status().with_context(|| {
|
let status = command_status_with_sh_fallback(&mut make_cmd).with_context(|| {
|
||||||
format!("Failed to run {} in {}", make_exec, build_dir.display())
|
format!("Failed to run {} in {}", make_exec, build_dir.display())
|
||||||
})?;
|
})?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
@@ -148,7 +148,7 @@ pub fn build(
|
|||||||
}
|
}
|
||||||
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
crate::builder::prepare_tool_command(&mut test_cmd, &env_vars);
|
||||||
|
|
||||||
let status = test_cmd.status().with_context(|| {
|
let status = command_status_with_sh_fallback(&mut test_cmd).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to run {} {} in {}",
|
"Failed to run {} {} in {}",
|
||||||
make_exec,
|
make_exec,
|
||||||
@@ -221,7 +221,7 @@ pub fn build(
|
|||||||
));
|
));
|
||||||
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
crate::builder::prepare_tool_command(&mut install_cmd, &install_env);
|
||||||
|
|
||||||
let status = install_cmd.status().with_context(|| {
|
let status = command_status_with_sh_fallback(&mut install_cmd).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to run {} {} for {} in {}",
|
"Failed to run {} {} for {} in {}",
|
||||||
make_exec,
|
make_exec,
|
||||||
@@ -265,6 +265,57 @@ fn resolve_perl_configure_script(spec: &PackageSpec, actual_src: &Path) -> PathB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn command_status_with_sh_fallback(cmd: &mut Command) -> std::io::Result<std::process::ExitStatus> {
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) => Ok(status),
|
||||||
|
Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||||
|
let Some(script) = resolved_script_path(cmd) else {
|
||||||
|
return Err(err);
|
||||||
|
};
|
||||||
|
let contents = fs::read(&script);
|
||||||
|
let is_script = contents.ok().is_some_and(|bytes| bytes.starts_with(b"#!"));
|
||||||
|
if !is_script {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fallback = Command::new("sh");
|
||||||
|
fallback.arg(&script);
|
||||||
|
fallback.args(cmd.get_args());
|
||||||
|
if let Some(dir) = cmd.get_current_dir() {
|
||||||
|
fallback.current_dir(dir);
|
||||||
|
}
|
||||||
|
fallback.env_clear();
|
||||||
|
for (key, value) in cmd.get_envs() {
|
||||||
|
match value {
|
||||||
|
Some(value) => {
|
||||||
|
fallback.env(key, value);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
fallback.env_remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fallback.status()
|
||||||
|
}
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolved_script_path(cmd: &Command) -> Option<PathBuf> {
|
||||||
|
let program = Path::new(cmd.get_program());
|
||||||
|
if program.components().count() > 1 || program.is_absolute() {
|
||||||
|
return Some(program.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_value = cmd
|
||||||
|
.get_envs()
|
||||||
|
.find_map(|(key, value)| (key == "PATH").then_some(value))
|
||||||
|
.flatten()?;
|
||||||
|
std::env::split_paths(path_value)
|
||||||
|
.map(|dir| dir.join(program))
|
||||||
|
.find(|candidate| candidate.is_file())
|
||||||
|
}
|
||||||
|
|
||||||
fn has_assignment_prefix(args: &[String], name: &str) -> bool {
|
fn has_assignment_prefix(args: &[String], name: &str) -> bool {
|
||||||
args.iter().any(|arg| {
|
args.iter().any(|arg| {
|
||||||
let trimmed = arg.trim();
|
let trimmed = arg.trim();
|
||||||
|
|||||||
+36
-1
@@ -143,7 +143,7 @@ fn run_install_command_with_program(
|
|||||||
cmd.env("DEPOT_DEPCHAIN", dep_chain);
|
cmd.env("DEPOT_DEPCHAIN", dep_chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = cmd.status().with_context(|| {
|
let status = command_status_with_sh_fallback(&mut cmd).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to spawn child install for {}",
|
"Failed to spawn child install for {}",
|
||||||
install_request_display(install_requests)
|
install_request_display(install_requests)
|
||||||
@@ -160,6 +160,41 @@ fn run_install_command_with_program(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn command_status_with_sh_fallback(
|
||||||
|
cmd: &mut std::process::Command,
|
||||||
|
) -> std::io::Result<std::process::ExitStatus> {
|
||||||
|
match cmd.status() {
|
||||||
|
Ok(status) => Ok(status),
|
||||||
|
Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||||
|
let program = cmd.get_program();
|
||||||
|
let contents = fs::read(program);
|
||||||
|
let is_script = contents.ok().is_some_and(|bytes| bytes.starts_with(b"#!"));
|
||||||
|
if !is_script {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fallback = std::process::Command::new("sh");
|
||||||
|
fallback.arg(program);
|
||||||
|
fallback.args(cmd.get_args());
|
||||||
|
if let Some(dir) = cmd.get_current_dir() {
|
||||||
|
fallback.current_dir(dir);
|
||||||
|
}
|
||||||
|
for (key, value) in cmd.get_envs() {
|
||||||
|
match value {
|
||||||
|
Some(value) => {
|
||||||
|
fallback.env(key, value);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
fallback.env_remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fallback.status()
|
||||||
|
}
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_child_install_command(
|
fn run_child_install_command(
|
||||||
install_requests: &[PathBuf],
|
install_requests: &[PathBuf],
|
||||||
rootfs: &Path,
|
rootfs: &Path,
|
||||||
|
|||||||
+27
-1
@@ -10,12 +10,16 @@ use tempfile::TempDir;
|
|||||||
pub const INTERNAL_DEPOT_DIR: &str = ".depot";
|
pub const INTERNAL_DEPOT_DIR: &str = ".depot";
|
||||||
/// Internal split-output staging root inside `DESTDIR`.
|
/// Internal split-output staging root inside `DESTDIR`.
|
||||||
pub const INTERNAL_OUTPUTS_DIR: &str = ".depot/outputs";
|
pub const INTERNAL_OUTPUTS_DIR: &str = ".depot/outputs";
|
||||||
|
const DEPOT_HAUL_HELPER_ENV: &str = "DEPOT_HAUL_HELPER";
|
||||||
|
const DEPOT_SUBDESTDIR_HELPER_ENV: &str = "DEPOT_SUBDESTDIR_HELPER";
|
||||||
|
|
||||||
/// Ephemeral helper command directory to prepend to PATH while running scripts.
|
/// Ephemeral helper command directory to prepend to PATH while running scripts.
|
||||||
pub struct ShellHelpers {
|
pub struct ShellHelpers {
|
||||||
_tempdir: TempDir,
|
_tempdir: TempDir,
|
||||||
path_value: String,
|
path_value: String,
|
||||||
outputs_dir: PathBuf,
|
outputs_dir: PathBuf,
|
||||||
|
haul_path: PathBuf,
|
||||||
|
subdestdir_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShellHelpers {
|
impl ShellHelpers {
|
||||||
@@ -77,6 +81,8 @@ impl ShellHelpers {
|
|||||||
_tempdir: tempdir,
|
_tempdir: tempdir,
|
||||||
path_value,
|
path_value,
|
||||||
outputs_dir: destdir.join(INTERNAL_OUTPUTS_DIR),
|
outputs_dir: destdir.join(INTERNAL_OUTPUTS_DIR),
|
||||||
|
haul_path,
|
||||||
|
subdestdir_path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,16 +95,36 @@ impl ShellHelpers {
|
|||||||
self.outputs_dir.to_string_lossy().into_owned(),
|
self.outputs_dir.to_string_lossy().into_owned(),
|
||||||
);
|
);
|
||||||
set_env_var(env_vars, "DEPOT_INTERNAL_DIR", INTERNAL_DEPOT_DIR);
|
set_env_var(env_vars, "DEPOT_INTERNAL_DIR", INTERNAL_DEPOT_DIR);
|
||||||
|
set_env_var(
|
||||||
|
env_vars,
|
||||||
|
DEPOT_HAUL_HELPER_ENV,
|
||||||
|
self.haul_path.to_string_lossy().into_owned(),
|
||||||
|
);
|
||||||
|
set_env_var(
|
||||||
|
env_vars,
|
||||||
|
DEPOT_SUBDESTDIR_HELPER_ENV,
|
||||||
|
self.subdestdir_path.to_string_lossy().into_owned(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply helper-related variables directly to a `std::process::Command`.
|
/// Apply helper-related variables directly to a `std::process::Command`.
|
||||||
pub fn apply_to_command(&self, cmd: &mut std::process::Command) {
|
pub fn apply_to_command(&self, cmd: &mut std::process::Command) {
|
||||||
cmd.env("PATH", &self.path_value)
|
cmd.env("PATH", &self.path_value)
|
||||||
.env("DEPOT_OUTPUTS_DIR", &self.outputs_dir)
|
.env("DEPOT_OUTPUTS_DIR", &self.outputs_dir)
|
||||||
.env("DEPOT_INTERNAL_DIR", INTERNAL_DEPOT_DIR);
|
.env("DEPOT_INTERNAL_DIR", INTERNAL_DEPOT_DIR)
|
||||||
|
.env(DEPOT_HAUL_HELPER_ENV, &self.haul_path)
|
||||||
|
.env(DEPOT_SUBDESTDIR_HELPER_ENV, &self.subdestdir_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrap a shell command with helper functions that invoke the helper scripts
|
||||||
|
/// through `/bin/sh`, avoiding direct execution from mounts that may be `noexec`.
|
||||||
|
pub fn wrap_shell_command(command: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"haul() {{ /bin/sh \"${{{DEPOT_HAUL_HELPER_ENV}:?}}\" \"$@\"; }}\nsubdestdir() {{ /bin/sh \"${{{DEPOT_SUBDESTDIR_HELPER_ENV}:?}}\" \"$@\"; }}\n{command}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert a package name into a safe shell identifier suffix.
|
/// Convert a package name into a safe shell identifier suffix.
|
||||||
pub fn shell_ident_suffix(pkg_name: &str) -> String {
|
pub fn shell_ident_suffix(pkg_name: &str) -> String {
|
||||||
let mut out = String::with_capacity(pkg_name.len().max(1));
|
let mut out = String::with_capacity(pkg_name.len().max(1));
|
||||||
|
|||||||
+6
-3
@@ -125,6 +125,7 @@ pub fn run_post_configure_commands(
|
|||||||
for cmd in commands {
|
for cmd in commands {
|
||||||
let cmd_str = spec.expand_vars(cmd);
|
let cmd_str = spec.expand_vars(cmd);
|
||||||
crate::log_info!(" post_configure: {}", cmd_str);
|
crate::log_info!(" post_configure: {}", cmd_str);
|
||||||
|
let wrapped_cmd = crate::shell_helpers::wrap_shell_command(&cmd_str);
|
||||||
|
|
||||||
let mut shell_cmd = Command::new("sh");
|
let mut shell_cmd = Command::new("sh");
|
||||||
shell_cmd.current_dir(src_dir);
|
shell_cmd.current_dir(src_dir);
|
||||||
@@ -136,7 +137,7 @@ pub fn run_post_configure_commands(
|
|||||||
.env("CC", &spec.build.flags.cc)
|
.env("CC", &spec.build.flags.cc)
|
||||||
.env("AR", &spec.build.flags.ar)
|
.env("AR", &spec.build.flags.ar)
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(&cmd_str)
|
.arg(&wrapped_cmd)
|
||||||
.status()
|
.status()
|
||||||
.with_context(|| format!("Failed to run post_configure command: {}", cmd_str))?;
|
.with_context(|| format!("Failed to run post_configure command: {}", cmd_str))?;
|
||||||
|
|
||||||
@@ -161,6 +162,7 @@ pub fn run_post_compile_commands(spec: &PackageSpec, src_dir: &Path, destdir: &P
|
|||||||
for cmd in commands {
|
for cmd in commands {
|
||||||
let cmd_str = spec.expand_vars(cmd);
|
let cmd_str = spec.expand_vars(cmd);
|
||||||
crate::log_info!(" post_compile: {}", cmd_str);
|
crate::log_info!(" post_compile: {}", cmd_str);
|
||||||
|
let wrapped_cmd = crate::shell_helpers::wrap_shell_command(&cmd_str);
|
||||||
|
|
||||||
let mut shell_cmd = Command::new("sh");
|
let mut shell_cmd = Command::new("sh");
|
||||||
shell_cmd.current_dir(src_dir);
|
shell_cmd.current_dir(src_dir);
|
||||||
@@ -172,7 +174,7 @@ pub fn run_post_compile_commands(spec: &PackageSpec, src_dir: &Path, destdir: &P
|
|||||||
.env("CC", &spec.build.flags.cc)
|
.env("CC", &spec.build.flags.cc)
|
||||||
.env("AR", &spec.build.flags.ar)
|
.env("AR", &spec.build.flags.ar)
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(&cmd_str)
|
.arg(&wrapped_cmd)
|
||||||
.status()
|
.status()
|
||||||
.with_context(|| format!("Failed to run post_compile command: {}", cmd_str))?;
|
.with_context(|| format!("Failed to run post_compile command: {}", cmd_str))?;
|
||||||
|
|
||||||
@@ -197,6 +199,7 @@ pub fn run_post_install_commands(spec: &PackageSpec, src_dir: &Path, destdir: &P
|
|||||||
for cmd in commands {
|
for cmd in commands {
|
||||||
let cmd_str = spec.expand_vars(cmd);
|
let cmd_str = spec.expand_vars(cmd);
|
||||||
crate::log_info!(" post_install: {}", cmd_str);
|
crate::log_info!(" post_install: {}", cmd_str);
|
||||||
|
let wrapped_cmd = crate::shell_helpers::wrap_shell_command(&cmd_str);
|
||||||
|
|
||||||
let mut shell_cmd = Command::new("sh");
|
let mut shell_cmd = Command::new("sh");
|
||||||
shell_cmd.current_dir(src_dir);
|
shell_cmd.current_dir(src_dir);
|
||||||
@@ -208,7 +211,7 @@ pub fn run_post_install_commands(spec: &PackageSpec, src_dir: &Path, destdir: &P
|
|||||||
.env("CC", &spec.build.flags.cc)
|
.env("CC", &spec.build.flags.cc)
|
||||||
.env("AR", &spec.build.flags.ar)
|
.env("AR", &spec.build.flags.ar)
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(&cmd_str)
|
.arg(&wrapped_cmd)
|
||||||
.status()
|
.status()
|
||||||
.with_context(|| format!("Failed to run post_install command: {}", cmd_str))?;
|
.with_context(|| format!("Failed to run post_install command: {}", cmd_str))?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user