feat: enhance install command to accept multiple package specifications and improve runtime environment handling
This commit is contained in:
+54
-4
@@ -53,12 +53,22 @@ pub struct Cli {
|
||||
pub enum Commands {
|
||||
/// Build and install a package from a spec file
|
||||
Install {
|
||||
/// Path to package spec (.toml) or package archive (.tar.zst)
|
||||
#[arg(value_name = "SPEC_OR_ARCHIVE")]
|
||||
spec_or_archive: PathBuf,
|
||||
/// One or more package names, spec paths (.toml), or package archives (.tar.zst)
|
||||
#[arg(
|
||||
value_name = "SPEC_OR_ARCHIVE",
|
||||
num_args = 1..,
|
||||
required_unless_present = "spec"
|
||||
)]
|
||||
spec_or_archive: Vec<PathBuf>,
|
||||
|
||||
/// Explicitly specify path to package spec (.toml file)
|
||||
#[arg(short, long = "spec", visible_alias = "package", alias = "p")]
|
||||
#[arg(
|
||||
short,
|
||||
long = "spec",
|
||||
visible_alias = "package",
|
||||
alias = "p",
|
||||
conflicts_with = "spec_or_archive"
|
||||
)]
|
||||
spec: Option<PathBuf>,
|
||||
},
|
||||
/// Remove an installed package
|
||||
@@ -220,3 +230,43 @@ pub enum RepoKindArg {
|
||||
Source,
|
||||
Binary,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Cli, Commands};
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn install_accepts_multiple_positional_targets() {
|
||||
let cli = Cli::try_parse_from(["depot", "install", "base", "linux"]).unwrap();
|
||||
match cli.command {
|
||||
Commands::Install {
|
||||
spec_or_archive,
|
||||
spec,
|
||||
} => {
|
||||
assert!(spec.is_none());
|
||||
assert_eq!(
|
||||
spec_or_archive,
|
||||
vec![PathBuf::from("base"), PathBuf::from("linux")]
|
||||
);
|
||||
}
|
||||
_ => panic!("expected install command"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_accepts_spec_flag_without_positional_target() {
|
||||
let cli = Cli::try_parse_from(["depot", "install", "--spec", "pkg.toml"]).unwrap();
|
||||
match cli.command {
|
||||
Commands::Install {
|
||||
spec_or_archive,
|
||||
spec,
|
||||
} => {
|
||||
assert!(spec_or_archive.is_empty());
|
||||
assert_eq!(spec, Some(PathBuf::from("pkg.toml")));
|
||||
}
|
||||
_ => panic!("expected install command"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+165
-97
@@ -1255,73 +1255,47 @@ fn execute_install_plan_with_child_commands(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(cli: Cli) -> Result<()> {
|
||||
ui::set_assume_yes(cli.yes);
|
||||
|
||||
match cli.command {
|
||||
Commands::Install {
|
||||
spec_or_archive,
|
||||
spec,
|
||||
} => {
|
||||
if !crate::fakeroot::is_root() {
|
||||
anyhow::bail!("The 'install' command must be run as root");
|
||||
}
|
||||
let mut spec_path = spec.unwrap_or(spec_or_archive);
|
||||
|
||||
// Load configuration early so we can use the configured repo clone dir
|
||||
let config = config::Config::for_rootfs(&cli.rootfs);
|
||||
|
||||
if !cli.no_deps {
|
||||
let request_path_like = spec_path.exists();
|
||||
let is_archive_request = request_path_like
|
||||
fn is_archive_install_request(spec_path: &Path) -> bool {
|
||||
spec_path.exists()
|
||||
&& spec_path
|
||||
.to_string_lossy()
|
||||
.to_ascii_lowercase()
|
||||
.ends_with(".tar.zst");
|
||||
if !is_archive_request {
|
||||
let target = if request_path_like {
|
||||
planner::InstallTarget::SpecPath(spec_path.clone())
|
||||
} else {
|
||||
planner::InstallTarget::PackageName(spec_path.to_string_lossy().to_string())
|
||||
};
|
||||
let local_sibling_root = spec_path
|
||||
.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.map(Path::to_path_buf);
|
||||
let plan = planner::build_install_plan(
|
||||
&config,
|
||||
&cli.rootfs,
|
||||
target,
|
||||
planner::PlannerOptions {
|
||||
assume_yes: cli.yes,
|
||||
prefer_binary: config.repo_settings.prefer_binary,
|
||||
local_sibling_root,
|
||||
},
|
||||
)?;
|
||||
print_plan_summary(&plan);
|
||||
execute_install_plan_with_child_commands(
|
||||
&plan,
|
||||
&cli.rootfs,
|
||||
&config,
|
||||
InstallPlanExecutionOptions {
|
||||
no_flags: cli.no_flags,
|
||||
cross_prefix: cli.cross_prefix.as_deref(),
|
||||
clean: cli.clean,
|
||||
dry_run: cli.dry_run,
|
||||
confirm_installation: true,
|
||||
},
|
||||
)?;
|
||||
if cli.clean {
|
||||
clean_build_workspace(&config)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
.ends_with(".tar.zst")
|
||||
}
|
||||
|
||||
let mut install_lock = locking::open_lock(&config)?;
|
||||
let install_lock_path = locking::lock_path(&config);
|
||||
let _install_lock_guard =
|
||||
locking::try_write(&mut install_lock, &install_lock_path, "install")?;
|
||||
fn shared_local_sibling_root(spec_paths: &[PathBuf]) -> Option<PathBuf> {
|
||||
let mut roots = spec_paths.iter().filter_map(|path| {
|
||||
path.parent()
|
||||
.and_then(|p| p.parent())
|
||||
.map(Path::to_path_buf)
|
||||
});
|
||||
let first = roots.next()?;
|
||||
if roots.all(|path| path == first) {
|
||||
Some(first)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct DirectInstallOptions<'a> {
|
||||
rootfs: &'a Path,
|
||||
no_deps: bool,
|
||||
no_flags: bool,
|
||||
cross_prefix: Option<&'a str>,
|
||||
clean: bool,
|
||||
dry_run: bool,
|
||||
lib32_only: bool,
|
||||
}
|
||||
|
||||
fn run_direct_install_request(
|
||||
options: DirectInstallOptions<'_>,
|
||||
config: &config::Config,
|
||||
mut spec_path: PathBuf,
|
||||
) -> Result<bool> {
|
||||
let mut install_lock = locking::open_lock(config)?;
|
||||
let install_lock_path = locking::lock_path(config);
|
||||
let _install_lock_guard = locking::try_write(&mut install_lock, &install_lock_path, "install")?;
|
||||
|
||||
// Repo clone dir is available via `config.repo_clone_dir` and
|
||||
// is passed explicitly to index builders below.
|
||||
@@ -1342,14 +1316,13 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
.iter()
|
||||
.filter(|(_, repo)| repo.enabled && repo.supports_arch(host_arch))
|
||||
.collect();
|
||||
binary_repos
|
||||
.sort_by(|a, b| a.1.priority.cmp(&b.1.priority).then_with(|| a.0.cmp(b.0)));
|
||||
binary_repos.sort_by(|a, b| a.1.priority.cmp(&b.1.priority).then_with(|| a.0.cmp(b.0)));
|
||||
|
||||
for (repo_name, repo_cfg) in binary_repos {
|
||||
match db::repo::find_binary_repo_package(
|
||||
repo_name,
|
||||
repo_cfg,
|
||||
&cli.rootfs,
|
||||
options.rootfs,
|
||||
&config.package_cache_dir,
|
||||
&name,
|
||||
) {
|
||||
@@ -1357,7 +1330,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
let archive = db::repo::fetch_binary_package_archive(
|
||||
repo_name,
|
||||
repo_cfg,
|
||||
&cli.rootfs,
|
||||
options.rootfs,
|
||||
&rec,
|
||||
&config.package_cache_dir,
|
||||
)?;
|
||||
@@ -1398,11 +1371,11 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
} else {
|
||||
// Install from spec (normal build)
|
||||
let mut pkg_spec = package::PackageSpec::from_file(&spec_path)?;
|
||||
pkg_spec.apply_config(&config);
|
||||
pkg_spec.apply_config(config);
|
||||
(pkg_spec, None)
|
||||
};
|
||||
|
||||
if cli.lib32_only && staging_dir.is_some() {
|
||||
if options.lib32_only && staging_dir.is_some() {
|
||||
anyhow::bail!("--lib32-only is only supported when installing from a package spec");
|
||||
}
|
||||
|
||||
@@ -1411,9 +1384,9 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
pkg_spec.package.name, pkg_spec.package.version, pkg_spec.package.revision
|
||||
));
|
||||
|
||||
if cli.dry_run {
|
||||
if options.dry_run {
|
||||
ui::info("Dry run enabled, stopping before install/build work.");
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let install_targets = vec![format!(
|
||||
@@ -1440,7 +1413,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
}
|
||||
|
||||
// Check dependencies and prompt for auto-install if needed
|
||||
if !cli.no_deps {
|
||||
if !options.no_deps {
|
||||
deps::print_dep_status(&pkg_spec, &db_path)?;
|
||||
|
||||
// Collect all missing dependencies (build + runtime)
|
||||
@@ -1465,9 +1438,8 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
ui::warn(format!("Missing dependencies: {}", missing.join(", ")));
|
||||
if ui::prompt_package_action("dependency installation", &missing, true)? {
|
||||
// Build package index for fast lookups
|
||||
let pkg_index = index::PackageIndex::build_with_repo_dir(Some(
|
||||
config.repo_clone_dir.clone(),
|
||||
));
|
||||
let pkg_index =
|
||||
index::PackageIndex::build_with_repo_dir(Some(config.repo_clone_dir.clone()));
|
||||
|
||||
// Build new dep chain
|
||||
let new_chain = if dep_chain.is_empty() {
|
||||
@@ -1485,18 +1457,18 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
ui::info(format!("Installing dependency: {}...", dep));
|
||||
|
||||
let mut cmd = std::process::Command::new(std::env::current_exe()?);
|
||||
cmd.arg("-r").arg(&cli.rootfs);
|
||||
cmd.arg("-r").arg(options.rootfs);
|
||||
|
||||
if cli.no_deps {
|
||||
if options.no_deps {
|
||||
cmd.arg("--no-deps");
|
||||
}
|
||||
if cli.no_flags {
|
||||
if options.no_flags {
|
||||
cmd.arg("--no-flags");
|
||||
}
|
||||
if let Some(ref p) = cli.cross_prefix {
|
||||
if let Some(p) = options.cross_prefix {
|
||||
cmd.arg("--cross-prefix").arg(p);
|
||||
}
|
||||
if cli.clean {
|
||||
if options.clean {
|
||||
cmd.arg("--clean");
|
||||
}
|
||||
|
||||
@@ -1509,10 +1481,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
anyhow::bail!("Failed to install dependency: {}", dep);
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Could not find package spec for dependency: {}",
|
||||
dep
|
||||
);
|
||||
anyhow::bail!("Could not find package spec for dependency: {}", dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1523,10 +1492,9 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
deps::require_runtime_deps(&pkg_spec, &db_path)?;
|
||||
}
|
||||
|
||||
let cross_config = cli
|
||||
let cross_config = options
|
||||
.cross_prefix
|
||||
.as_ref()
|
||||
.map(|p| cross::CrossConfig::from_prefix(p))
|
||||
.map(cross::CrossConfig::from_prefix)
|
||||
.transpose()?;
|
||||
let mut built_src_dir: Option<PathBuf> = None;
|
||||
|
||||
@@ -1543,13 +1511,13 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
.join("destdir")
|
||||
.join(&pkg_spec.package.name);
|
||||
|
||||
if !cli.lib32_only {
|
||||
if !options.lib32_only {
|
||||
builder::build(
|
||||
&pkg_spec,
|
||||
&src_dir,
|
||||
&destdir,
|
||||
cross_config.as_ref(),
|
||||
!cli.no_flags,
|
||||
!options.no_flags,
|
||||
)?;
|
||||
|
||||
// 3.1 Copy license files into staged tree
|
||||
@@ -1560,7 +1528,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
destdir
|
||||
};
|
||||
|
||||
if !cli.lib32_only {
|
||||
if !options.lib32_only {
|
||||
if staging_dir.is_none() {
|
||||
// Source-build path: apply staging transforms (strip/compress/static cleanup).
|
||||
staging::process(&destdir, &pkg_spec)?;
|
||||
@@ -1570,7 +1538,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
}
|
||||
|
||||
let installed =
|
||||
install_package_outputs_to_rootfs(&pkg_spec, &destdir, &cli.rootfs, &config)?;
|
||||
install_package_outputs_to_rootfs(&pkg_spec, &destdir, options.rootfs, config)?;
|
||||
for pkg in installed {
|
||||
ui::success(format!(
|
||||
"Successfully installed {} v{}",
|
||||
@@ -1584,22 +1552,122 @@ pub fn run(cli: Cli) -> Result<()> {
|
||||
&& let Some((lib32_spec, lib32_destdir)) = build_lib32_companion_package(
|
||||
&pkg_spec,
|
||||
src_dir,
|
||||
&config,
|
||||
config,
|
||||
cross_config.as_ref(),
|
||||
!cli.no_flags,
|
||||
cli.lib32_only,
|
||||
!options.no_flags,
|
||||
options.lib32_only,
|
||||
)?
|
||||
{
|
||||
install_staged_to_rootfs(&lib32_spec, &lib32_destdir, &cli.rootfs, &config)?;
|
||||
install_staged_to_rootfs(&lib32_spec, &lib32_destdir, options.rootfs, config)?;
|
||||
ui::success(format!(
|
||||
"Successfully installed {} v{}",
|
||||
lib32_spec.package.name, lib32_spec.package.version
|
||||
));
|
||||
}
|
||||
|
||||
install::scripts::run_deferred_hooks_if_possible(&cli.rootfs)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
if cli.clean {
|
||||
pub fn run(cli: Cli) -> Result<()> {
|
||||
ui::set_assume_yes(cli.yes);
|
||||
|
||||
match cli.command {
|
||||
Commands::Install {
|
||||
spec_or_archive,
|
||||
spec,
|
||||
} => {
|
||||
if !crate::fakeroot::is_root() {
|
||||
anyhow::bail!("The 'install' command must be run as root");
|
||||
}
|
||||
let install_requests = match spec {
|
||||
Some(spec_path) => vec![spec_path],
|
||||
None => spec_or_archive,
|
||||
};
|
||||
|
||||
// Load configuration early so we can use configured repos/paths.
|
||||
let config = config::Config::for_rootfs(&cli.rootfs);
|
||||
let mut planned_targets = Vec::new();
|
||||
let mut planned_spec_paths = Vec::new();
|
||||
let mut direct_requests = Vec::new();
|
||||
|
||||
if cli.no_deps {
|
||||
direct_requests = install_requests;
|
||||
} else {
|
||||
for request in install_requests {
|
||||
if is_archive_install_request(&request) {
|
||||
direct_requests.push(request);
|
||||
continue;
|
||||
}
|
||||
if request.exists() {
|
||||
planned_spec_paths.push(request.clone());
|
||||
planned_targets.push(planner::InstallTarget::SpecPath(request));
|
||||
} else {
|
||||
planned_targets.push(planner::InstallTarget::PackageName(
|
||||
request.to_string_lossy().to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut ran_plan_mode = false;
|
||||
if !planned_targets.is_empty() {
|
||||
ran_plan_mode = true;
|
||||
let planner_opts = planner::PlannerOptions {
|
||||
assume_yes: cli.yes,
|
||||
prefer_binary: config.repo_settings.prefer_binary,
|
||||
local_sibling_root: shared_local_sibling_root(&planned_spec_paths),
|
||||
};
|
||||
let plan = if planned_targets.len() == 1 {
|
||||
planner::build_install_plan(
|
||||
&config,
|
||||
&cli.rootfs,
|
||||
planned_targets[0].clone(),
|
||||
planner_opts,
|
||||
)?
|
||||
} else {
|
||||
planner::build_install_plan_for_targets(
|
||||
&config,
|
||||
&cli.rootfs,
|
||||
&planned_targets,
|
||||
planner_opts,
|
||||
)?
|
||||
};
|
||||
print_plan_summary(&plan);
|
||||
execute_install_plan_with_child_commands(
|
||||
&plan,
|
||||
&cli.rootfs,
|
||||
&config,
|
||||
InstallPlanExecutionOptions {
|
||||
no_flags: cli.no_flags,
|
||||
cross_prefix: cli.cross_prefix.as_deref(),
|
||||
clean: cli.clean,
|
||||
dry_run: cli.dry_run,
|
||||
confirm_installation: true,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut ran_direct_install = false;
|
||||
for request in direct_requests {
|
||||
ran_direct_install |= run_direct_install_request(
|
||||
DirectInstallOptions {
|
||||
rootfs: &cli.rootfs,
|
||||
no_deps: cli.no_deps,
|
||||
no_flags: cli.no_flags,
|
||||
cross_prefix: cli.cross_prefix.as_deref(),
|
||||
clean: cli.clean,
|
||||
dry_run: cli.dry_run,
|
||||
lib32_only: cli.lib32_only,
|
||||
},
|
||||
&config,
|
||||
request,
|
||||
)?;
|
||||
}
|
||||
if ran_direct_install {
|
||||
install::scripts::run_deferred_hooks_if_possible(&cli.rootfs)?;
|
||||
}
|
||||
|
||||
if cli.clean && (ran_plan_mode || ran_direct_install) {
|
||||
clean_build_workspace(&config)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +429,8 @@ fn run_hook_command(
|
||||
.env("DEPOT_PACKAGE", ctx.package)
|
||||
.env("DEPOT_ROOTFS", rootfs)
|
||||
.env("DEPOT_HOOK_FILE", &hook.file_path)
|
||||
.env("DEPOT_HOOK_NAME", &hook_name);
|
||||
.env("DEPOT_HOOK_NAME", &hook_name)
|
||||
.env("PATH", crate::runtime_env::safe_script_path());
|
||||
|
||||
let status = run_command_with_optional_stdin(&mut command, stdin_payload.as_deref())
|
||||
.with_context(|| {
|
||||
|
||||
+27
-1
@@ -467,6 +467,7 @@ fn run_script_with_rootfs_context(
|
||||
.env("DEPOT_ROOTFS", &rootfs_env)
|
||||
.env("DEPOT_ACTION", hook.action())
|
||||
.env("DEPOT_PHASE", hook.phase())
|
||||
.env("PATH", crate::runtime_env::safe_script_path())
|
||||
.status()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
@@ -497,7 +498,8 @@ fn run_hook_script_in_chroot(
|
||||
.env("DEPOT_PACKAGE", pkg_name)
|
||||
.env("DEPOT_ROOTFS", "/")
|
||||
.env("DEPOT_ACTION", hook.action())
|
||||
.env("DEPOT_PHASE", hook.phase());
|
||||
.env("DEPOT_PHASE", hook.phase())
|
||||
.env("PATH", crate::runtime_env::safe_script_path());
|
||||
if quiet {
|
||||
cmd.stdout(Stdio::null()).stderr(Stdio::null());
|
||||
}
|
||||
@@ -1068,6 +1070,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_hook_if_present_uses_safe_script_path() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let scripts = tmp.path().join("scripts");
|
||||
let rootfs = tmp.path().join("root");
|
||||
std::fs::create_dir_all(&scripts).unwrap();
|
||||
std::fs::create_dir_all(&rootfs).unwrap();
|
||||
|
||||
std::fs::write(
|
||||
scripts.join("pre_install"),
|
||||
"echo \"$PATH\" > \"$DEPOT_ROOTFS/path.out\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ran = run_hook_if_present(&scripts, Hook::PreInstall, &rootfs, "foo").unwrap();
|
||||
assert!(ran);
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(rootfs.join("path.out"))
|
||||
.unwrap()
|
||||
.trim_end(),
|
||||
crate::runtime_env::safe_script_path()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_hook_if_present_accepts_compact_script_name() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -15,6 +15,7 @@ mod install;
|
||||
mod locking;
|
||||
mod package;
|
||||
mod planner;
|
||||
mod runtime_env;
|
||||
mod shell_helpers;
|
||||
mod signing;
|
||||
mod source;
|
||||
|
||||
+26
-5
@@ -179,15 +179,27 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
|
||||
fn plan_for_install_target(mut self, target: InstallTarget) -> Result<ExecutionPlan> {
|
||||
self.resolve_install_target(&target)?;
|
||||
self.finish_plan()
|
||||
}
|
||||
|
||||
fn plan_for_install_targets(mut self, targets: &[InstallTarget]) -> Result<ExecutionPlan> {
|
||||
for target in targets {
|
||||
self.resolve_install_target(target)?;
|
||||
}
|
||||
self.finish_plan()
|
||||
}
|
||||
|
||||
fn resolve_install_target(&mut self, target: &InstallTarget) -> Result<()> {
|
||||
match target {
|
||||
InstallTarget::PackageName(name) => {
|
||||
if deps::is_dep_satisfied_in_db(&name, &self.db_path)? {
|
||||
if deps::is_dep_satisfied_in_db(name, &self.db_path)? {
|
||||
if self.emitted_installed_roots.insert(name.clone()) {
|
||||
self.add_installed_root_step(name, "requested package".to_string());
|
||||
self.add_installed_root_step(name.clone(), "requested package".to_string());
|
||||
}
|
||||
} else {
|
||||
let root =
|
||||
self.resolve_dep_node(&name, None, "requested package".to_string())?;
|
||||
self.resolve_dep_node(name, None, "requested package".to_string())?;
|
||||
if let Some(root_idx) = root {
|
||||
self.mark_requested_by(root_idx, "requested package".to_string());
|
||||
}
|
||||
@@ -195,11 +207,11 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
InstallTarget::SpecPath(path) => {
|
||||
let root_idx =
|
||||
self.ensure_source_spec_node(&path, true, true, "requested spec".to_string())?;
|
||||
self.ensure_source_spec_node(path, true, true, "requested spec".to_string())?;
|
||||
self.mark_requested_by(root_idx, "requested spec".to_string());
|
||||
}
|
||||
}
|
||||
self.finish_plan()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn plan_for_deps(mut self, deps_to_install: &[String]) -> Result<ExecutionPlan> {
|
||||
@@ -702,6 +714,15 @@ pub(crate) fn build_install_plan(
|
||||
Resolver::new(config, rootfs, opts).plan_for_install_target(target)
|
||||
}
|
||||
|
||||
pub(crate) fn build_install_plan_for_targets(
|
||||
config: &Config,
|
||||
rootfs: &Path,
|
||||
targets: &[InstallTarget],
|
||||
opts: PlannerOptions,
|
||||
) -> Result<ExecutionPlan> {
|
||||
Resolver::new(config, rootfs, opts).plan_for_install_targets(targets)
|
||||
}
|
||||
|
||||
pub(crate) fn build_dependency_install_plan(
|
||||
config: &Config,
|
||||
rootfs: &Path,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//! Shared runtime environment defaults for script and hook execution.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
/// Deterministic command search path for shell scripts and hook commands.
|
||||
pub const SAFE_SCRIPT_PATH: &str = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
||||
|
||||
/// Return the deterministic command search path used for script execution.
|
||||
pub fn safe_script_path() -> &'static str {
|
||||
SAFE_SCRIPT_PATH
|
||||
}
|
||||
|
||||
/// Prepend a helper binary directory before the deterministic script path.
|
||||
pub fn prepend_helper_to_safe_path(helper_bin_dir: &Path) -> String {
|
||||
format!("{}:{}", helper_bin_dir.display(), SAFE_SCRIPT_PATH)
|
||||
}
|
||||
@@ -63,13 +63,7 @@ impl ShellHelpers {
|
||||
})?;
|
||||
}
|
||||
|
||||
let parent_path = std::env::var("PATH").unwrap_or_default();
|
||||
let helper_path = bin_dir.to_string_lossy().into_owned();
|
||||
let path_value = if parent_path.is_empty() {
|
||||
helper_path
|
||||
} else {
|
||||
format!("{}:{}", helper_path, parent_path)
|
||||
};
|
||||
let path_value = crate::runtime_env::prepend_helper_to_safe_path(&bin_dir);
|
||||
|
||||
Ok(Self {
|
||||
_tempdir: tempdir,
|
||||
|
||||
@@ -61,6 +61,7 @@ fn apply_patches(
|
||||
// Apply with patch(1). Keep it simple: -p1 is the common case.
|
||||
let status = Command::new("patch")
|
||||
.current_dir(src_dir)
|
||||
.env("PATH", crate::runtime_env::safe_script_path())
|
||||
.arg("-p1")
|
||||
.arg("-i")
|
||||
.arg(&patch_path)
|
||||
@@ -93,6 +94,7 @@ fn run_post_extract_commands(spec: &PackageSpec, source: &Source, src_dir: &Path
|
||||
let status = Command::new("sh")
|
||||
.current_dir(src_dir)
|
||||
.env("DEPOT_SPECDIR", &spec.spec_dir)
|
||||
.env("PATH", crate::runtime_env::safe_script_path())
|
||||
.arg("-c")
|
||||
.arg(&cmd)
|
||||
.status()
|
||||
|
||||
Reference in New Issue
Block a user