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 {
|
pub enum Commands {
|
||||||
/// Build and install a package from a spec file
|
/// Build and install a package from a spec file
|
||||||
Install {
|
Install {
|
||||||
/// Path to package spec (.toml) or package archive (.tar.zst)
|
/// One or more package names, spec paths (.toml), or package archives (.tar.zst)
|
||||||
#[arg(value_name = "SPEC_OR_ARCHIVE")]
|
#[arg(
|
||||||
spec_or_archive: PathBuf,
|
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)
|
/// 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>,
|
spec: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
/// Remove an installed package
|
/// Remove an installed package
|
||||||
@@ -220,3 +230,43 @@ pub enum RepoKindArg {
|
|||||||
Source,
|
Source,
|
||||||
Binary,
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+162
-94
@@ -1255,73 +1255,47 @@ fn execute_install_plan_with_child_commands(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(cli: Cli) -> Result<()> {
|
fn is_archive_install_request(spec_path: &Path) -> bool {
|
||||||
ui::set_assume_yes(cli.yes);
|
spec_path.exists()
|
||||||
|
|
||||||
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
|
|
||||||
&& spec_path
|
&& spec_path
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_ascii_lowercase()
|
.to_ascii_lowercase()
|
||||||
.ends_with(".tar.zst");
|
.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(());
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut install_lock = locking::open_lock(&config)?;
|
#[derive(Clone, Copy)]
|
||||||
let install_lock_path = locking::lock_path(&config);
|
struct DirectInstallOptions<'a> {
|
||||||
let _install_lock_guard =
|
rootfs: &'a Path,
|
||||||
locking::try_write(&mut install_lock, &install_lock_path, "install")?;
|
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
|
// Repo clone dir is available via `config.repo_clone_dir` and
|
||||||
// is passed explicitly to index builders below.
|
// is passed explicitly to index builders below.
|
||||||
@@ -1342,14 +1316,13 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, repo)| repo.enabled && repo.supports_arch(host_arch))
|
.filter(|(_, repo)| repo.enabled && repo.supports_arch(host_arch))
|
||||||
.collect();
|
.collect();
|
||||||
binary_repos
|
binary_repos.sort_by(|a, b| a.1.priority.cmp(&b.1.priority).then_with(|| a.0.cmp(b.0)));
|
||||||
.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 {
|
for (repo_name, repo_cfg) in binary_repos {
|
||||||
match db::repo::find_binary_repo_package(
|
match db::repo::find_binary_repo_package(
|
||||||
repo_name,
|
repo_name,
|
||||||
repo_cfg,
|
repo_cfg,
|
||||||
&cli.rootfs,
|
options.rootfs,
|
||||||
&config.package_cache_dir,
|
&config.package_cache_dir,
|
||||||
&name,
|
&name,
|
||||||
) {
|
) {
|
||||||
@@ -1357,7 +1330,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
let archive = db::repo::fetch_binary_package_archive(
|
let archive = db::repo::fetch_binary_package_archive(
|
||||||
repo_name,
|
repo_name,
|
||||||
repo_cfg,
|
repo_cfg,
|
||||||
&cli.rootfs,
|
options.rootfs,
|
||||||
&rec,
|
&rec,
|
||||||
&config.package_cache_dir,
|
&config.package_cache_dir,
|
||||||
)?;
|
)?;
|
||||||
@@ -1398,11 +1371,11 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
// Install from spec (normal build)
|
// Install from spec (normal build)
|
||||||
let mut pkg_spec = package::PackageSpec::from_file(&spec_path)?;
|
let mut pkg_spec = package::PackageSpec::from_file(&spec_path)?;
|
||||||
pkg_spec.apply_config(&config);
|
pkg_spec.apply_config(config);
|
||||||
(pkg_spec, None)
|
(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");
|
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
|
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.");
|
ui::info("Dry run enabled, stopping before install/build work.");
|
||||||
return Ok(());
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let install_targets = vec![format!(
|
let install_targets = vec![format!(
|
||||||
@@ -1440,7 +1413,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check dependencies and prompt for auto-install if needed
|
// 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)?;
|
deps::print_dep_status(&pkg_spec, &db_path)?;
|
||||||
|
|
||||||
// Collect all missing dependencies (build + runtime)
|
// Collect all missing dependencies (build + runtime)
|
||||||
@@ -1465,9 +1438,8 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
ui::warn(format!("Missing dependencies: {}", missing.join(", ")));
|
ui::warn(format!("Missing dependencies: {}", missing.join(", ")));
|
||||||
if ui::prompt_package_action("dependency installation", &missing, true)? {
|
if ui::prompt_package_action("dependency installation", &missing, true)? {
|
||||||
// Build package index for fast lookups
|
// Build package index for fast lookups
|
||||||
let pkg_index = index::PackageIndex::build_with_repo_dir(Some(
|
let pkg_index =
|
||||||
config.repo_clone_dir.clone(),
|
index::PackageIndex::build_with_repo_dir(Some(config.repo_clone_dir.clone()));
|
||||||
));
|
|
||||||
|
|
||||||
// Build new dep chain
|
// Build new dep chain
|
||||||
let new_chain = if dep_chain.is_empty() {
|
let new_chain = if dep_chain.is_empty() {
|
||||||
@@ -1485,18 +1457,18 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
ui::info(format!("Installing dependency: {}...", dep));
|
ui::info(format!("Installing dependency: {}...", dep));
|
||||||
|
|
||||||
let mut cmd = std::process::Command::new(std::env::current_exe()?);
|
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");
|
cmd.arg("--no-deps");
|
||||||
}
|
}
|
||||||
if cli.no_flags {
|
if options.no_flags {
|
||||||
cmd.arg("--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);
|
cmd.arg("--cross-prefix").arg(p);
|
||||||
}
|
}
|
||||||
if cli.clean {
|
if options.clean {
|
||||||
cmd.arg("--clean");
|
cmd.arg("--clean");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1509,10 +1481,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
anyhow::bail!("Failed to install dependency: {}", dep);
|
anyhow::bail!("Failed to install dependency: {}", dep);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!(
|
anyhow::bail!("Could not find package spec for dependency: {}", dep);
|
||||||
"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)?;
|
deps::require_runtime_deps(&pkg_spec, &db_path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cross_config = cli
|
let cross_config = options
|
||||||
.cross_prefix
|
.cross_prefix
|
||||||
.as_ref()
|
.map(cross::CrossConfig::from_prefix)
|
||||||
.map(|p| cross::CrossConfig::from_prefix(p))
|
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let mut built_src_dir: Option<PathBuf> = None;
|
let mut built_src_dir: Option<PathBuf> = None;
|
||||||
|
|
||||||
@@ -1543,13 +1511,13 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
.join("destdir")
|
.join("destdir")
|
||||||
.join(&pkg_spec.package.name);
|
.join(&pkg_spec.package.name);
|
||||||
|
|
||||||
if !cli.lib32_only {
|
if !options.lib32_only {
|
||||||
builder::build(
|
builder::build(
|
||||||
&pkg_spec,
|
&pkg_spec,
|
||||||
&src_dir,
|
&src_dir,
|
||||||
&destdir,
|
&destdir,
|
||||||
cross_config.as_ref(),
|
cross_config.as_ref(),
|
||||||
!cli.no_flags,
|
!options.no_flags,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// 3.1 Copy license files into staged tree
|
// 3.1 Copy license files into staged tree
|
||||||
@@ -1560,7 +1528,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
destdir
|
destdir
|
||||||
};
|
};
|
||||||
|
|
||||||
if !cli.lib32_only {
|
if !options.lib32_only {
|
||||||
if staging_dir.is_none() {
|
if staging_dir.is_none() {
|
||||||
// Source-build path: apply staging transforms (strip/compress/static cleanup).
|
// Source-build path: apply staging transforms (strip/compress/static cleanup).
|
||||||
staging::process(&destdir, &pkg_spec)?;
|
staging::process(&destdir, &pkg_spec)?;
|
||||||
@@ -1570,7 +1538,7 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let installed =
|
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 {
|
for pkg in installed {
|
||||||
ui::success(format!(
|
ui::success(format!(
|
||||||
"Successfully installed {} v{}",
|
"Successfully installed {} v{}",
|
||||||
@@ -1584,22 +1552,122 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
&& let Some((lib32_spec, lib32_destdir)) = build_lib32_companion_package(
|
&& let Some((lib32_spec, lib32_destdir)) = build_lib32_companion_package(
|
||||||
&pkg_spec,
|
&pkg_spec,
|
||||||
src_dir,
|
src_dir,
|
||||||
&config,
|
config,
|
||||||
cross_config.as_ref(),
|
cross_config.as_ref(),
|
||||||
!cli.no_flags,
|
!options.no_flags,
|
||||||
cli.lib32_only,
|
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!(
|
ui::success(format!(
|
||||||
"Successfully installed {} v{}",
|
"Successfully installed {} v{}",
|
||||||
lib32_spec.package.name, lib32_spec.package.version
|
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)?;
|
clean_build_workspace(&config)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -429,7 +429,8 @@ fn run_hook_command(
|
|||||||
.env("DEPOT_PACKAGE", ctx.package)
|
.env("DEPOT_PACKAGE", ctx.package)
|
||||||
.env("DEPOT_ROOTFS", rootfs)
|
.env("DEPOT_ROOTFS", rootfs)
|
||||||
.env("DEPOT_HOOK_FILE", &hook.file_path)
|
.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())
|
let status = run_command_with_optional_stdin(&mut command, stdin_payload.as_deref())
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
|
|||||||
+27
-1
@@ -467,6 +467,7 @@ fn run_script_with_rootfs_context(
|
|||||||
.env("DEPOT_ROOTFS", &rootfs_env)
|
.env("DEPOT_ROOTFS", &rootfs_env)
|
||||||
.env("DEPOT_ACTION", hook.action())
|
.env("DEPOT_ACTION", hook.action())
|
||||||
.env("DEPOT_PHASE", hook.phase())
|
.env("DEPOT_PHASE", hook.phase())
|
||||||
|
.env("PATH", crate::runtime_env::safe_script_path())
|
||||||
.status()
|
.status()
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
@@ -497,7 +498,8 @@ fn run_hook_script_in_chroot(
|
|||||||
.env("DEPOT_PACKAGE", pkg_name)
|
.env("DEPOT_PACKAGE", pkg_name)
|
||||||
.env("DEPOT_ROOTFS", "/")
|
.env("DEPOT_ROOTFS", "/")
|
||||||
.env("DEPOT_ACTION", hook.action())
|
.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 {
|
if quiet {
|
||||||
cmd.stdout(Stdio::null()).stderr(Stdio::null());
|
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]
|
#[test]
|
||||||
fn run_hook_if_present_accepts_compact_script_name() {
|
fn run_hook_if_present_accepts_compact_script_name() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ mod install;
|
|||||||
mod locking;
|
mod locking;
|
||||||
mod package;
|
mod package;
|
||||||
mod planner;
|
mod planner;
|
||||||
|
mod runtime_env;
|
||||||
mod shell_helpers;
|
mod shell_helpers;
|
||||||
mod signing;
|
mod signing;
|
||||||
mod source;
|
mod source;
|
||||||
|
|||||||
+26
-5
@@ -179,15 +179,27 @@ impl<'a> Resolver<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn plan_for_install_target(mut self, target: InstallTarget) -> Result<ExecutionPlan> {
|
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 {
|
match target {
|
||||||
InstallTarget::PackageName(name) => {
|
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()) {
|
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 {
|
} else {
|
||||||
let root =
|
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 {
|
if let Some(root_idx) = root {
|
||||||
self.mark_requested_by(root_idx, "requested package".to_string());
|
self.mark_requested_by(root_idx, "requested package".to_string());
|
||||||
}
|
}
|
||||||
@@ -195,11 +207,11 @@ impl<'a> Resolver<'a> {
|
|||||||
}
|
}
|
||||||
InstallTarget::SpecPath(path) => {
|
InstallTarget::SpecPath(path) => {
|
||||||
let root_idx =
|
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.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> {
|
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)
|
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(
|
pub(crate) fn build_dependency_install_plan(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
rootfs: &Path,
|
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 path_value = crate::runtime_env::prepend_helper_to_safe_path(&bin_dir);
|
||||||
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_tempdir: tempdir,
|
_tempdir: tempdir,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ fn apply_patches(
|
|||||||
// Apply with patch(1). Keep it simple: -p1 is the common case.
|
// Apply with patch(1). Keep it simple: -p1 is the common case.
|
||||||
let status = Command::new("patch")
|
let status = Command::new("patch")
|
||||||
.current_dir(src_dir)
|
.current_dir(src_dir)
|
||||||
|
.env("PATH", crate::runtime_env::safe_script_path())
|
||||||
.arg("-p1")
|
.arg("-p1")
|
||||||
.arg("-i")
|
.arg("-i")
|
||||||
.arg(&patch_path)
|
.arg(&patch_path)
|
||||||
@@ -93,6 +94,7 @@ fn run_post_extract_commands(spec: &PackageSpec, source: &Source, src_dir: &Path
|
|||||||
let status = Command::new("sh")
|
let status = Command::new("sh")
|
||||||
.current_dir(src_dir)
|
.current_dir(src_dir)
|
||||||
.env("DEPOT_SPECDIR", &spec.spec_dir)
|
.env("DEPOT_SPECDIR", &spec.spec_dir)
|
||||||
|
.env("PATH", crate::runtime_env::safe_script_path())
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(&cmd)
|
.arg(&cmd)
|
||||||
.status()
|
.status()
|
||||||
|
|||||||
Reference in New Issue
Block a user