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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+395
-327
@@ -1255,6 +1255,319 @@ fn execute_install_plan_with_child_commands(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_archive_install_request(spec_path: &Path) -> bool {
|
||||||
|
spec_path.exists()
|
||||||
|
&& spec_path
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.ends_with(".tar.zst")
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
// If the provided path doesn't exist, treat it as a package name and
|
||||||
|
// try to locate a spec under configured repo dir or local packages/.
|
||||||
|
if !spec_path.exists() {
|
||||||
|
let name = spec_path.to_string_lossy().to_string();
|
||||||
|
ui::info(format!("Looking up package '{}' in local indexes...", name));
|
||||||
|
let pkg_index =
|
||||||
|
index::PackageIndex::build_with_repo_dir(Some(config.repo_clone_dir.clone()));
|
||||||
|
if let Some(found) = pkg_index.find(&name) {
|
||||||
|
spec_path = found;
|
||||||
|
} else {
|
||||||
|
let host_arch = std::env::consts::ARCH;
|
||||||
|
let mut binary_repos: Vec<_> = config
|
||||||
|
.binary_repos
|
||||||
|
.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)));
|
||||||
|
|
||||||
|
for (repo_name, repo_cfg) in binary_repos {
|
||||||
|
match db::repo::find_binary_repo_package(
|
||||||
|
repo_name,
|
||||||
|
repo_cfg,
|
||||||
|
options.rootfs,
|
||||||
|
&config.package_cache_dir,
|
||||||
|
&name,
|
||||||
|
) {
|
||||||
|
Ok(Some(rec)) => {
|
||||||
|
let archive = db::repo::fetch_binary_package_archive(
|
||||||
|
repo_name,
|
||||||
|
repo_cfg,
|
||||||
|
options.rootfs,
|
||||||
|
&rec,
|
||||||
|
&config.package_cache_dir,
|
||||||
|
)?;
|
||||||
|
ui::info(format!(
|
||||||
|
"Resolved '{}' from binary repo '{}' as {}-{} (package {}) ({} bytes){} -> {}",
|
||||||
|
name,
|
||||||
|
repo_name,
|
||||||
|
rec.version,
|
||||||
|
rec.revision,
|
||||||
|
rec.name,
|
||||||
|
rec.size,
|
||||||
|
rec.description
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| format!(" [{}]", d))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
archive.display()
|
||||||
|
));
|
||||||
|
spec_path = archive;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(e) => {
|
||||||
|
crate::log_warn!("Binary repo '{}': {}", repo_name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui::info(format!("Installing package from: {}", spec_path.display()));
|
||||||
|
|
||||||
|
let (mut pkg_spec, staging_dir): (package::PackageSpec, Option<tempfile::TempDir>) =
|
||||||
|
if spec_path.to_string_lossy().ends_with(".tar.zst") {
|
||||||
|
// Install from archive
|
||||||
|
ui::info(format!("Detected package archive: {}", spec_path.display()));
|
||||||
|
let (spec, tmp_dir) = load_package_archive_into_staging(&spec_path)?;
|
||||||
|
(spec, Some(tmp_dir))
|
||||||
|
} else {
|
||||||
|
// Install from spec (normal build)
|
||||||
|
let mut pkg_spec = package::PackageSpec::from_file(&spec_path)?;
|
||||||
|
pkg_spec.apply_config(config);
|
||||||
|
(pkg_spec, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
if options.lib32_only && staging_dir.is_some() {
|
||||||
|
anyhow::bail!("--lib32-only is only supported when installing from a package spec");
|
||||||
|
}
|
||||||
|
|
||||||
|
ui::info(format!(
|
||||||
|
"Package: {} v{}-{}",
|
||||||
|
pkg_spec.package.name, pkg_spec.package.version, pkg_spec.package.revision
|
||||||
|
));
|
||||||
|
|
||||||
|
if options.dry_run {
|
||||||
|
ui::info("Dry run enabled, stopping before install/build work.");
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let install_targets = vec![format!(
|
||||||
|
"{} v{}-{}",
|
||||||
|
pkg_spec.package.name, pkg_spec.package.version, pkg_spec.package.revision
|
||||||
|
)];
|
||||||
|
if !ui::prompt_package_action("installation", &install_targets, true)? {
|
||||||
|
anyhow::bail!("Aborted");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(snapper): create pre-install snapshot before install work starts.
|
||||||
|
|
||||||
|
// Ensure database directory exists
|
||||||
|
std::fs::create_dir_all(&config.db_dir).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to create database directory: {}",
|
||||||
|
config.db_dir.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let db_path = config.db_dir.join("packages.db");
|
||||||
|
|
||||||
|
if staging_dir.is_none() {
|
||||||
|
maybe_disable_tests_for_missing_deps(&mut pkg_spec, &db_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check dependencies and prompt for auto-install if needed
|
||||||
|
if !options.no_deps {
|
||||||
|
deps::print_dep_status(&pkg_spec, &db_path)?;
|
||||||
|
|
||||||
|
// Collect all missing dependencies (build + runtime)
|
||||||
|
let missing = merge_missing_dependencies(
|
||||||
|
deps::check_build_deps(&pkg_spec, &db_path)?,
|
||||||
|
deps::check_runtime_deps(&pkg_spec, &db_path)?,
|
||||||
|
);
|
||||||
|
if !missing.is_empty() {
|
||||||
|
// Check for dependency cycles via DEPOT_DEPCHAIN env var
|
||||||
|
let dep_chain = std::env::var("DEPOT_DEPCHAIN").unwrap_or_default();
|
||||||
|
let chain_set: std::collections::HashSet<&str> =
|
||||||
|
dep_chain.split(',').filter(|s| !s.is_empty()).collect();
|
||||||
|
|
||||||
|
if chain_set.contains(pkg_spec.package.name.as_str()) {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Dependency cycle detected! {} is already in chain: {}",
|
||||||
|
pkg_spec.package.name,
|
||||||
|
dep_chain
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
|
||||||
|
// Build new dep chain
|
||||||
|
let new_chain = if dep_chain.is_empty() {
|
||||||
|
pkg_spec.package.name.clone()
|
||||||
|
} else {
|
||||||
|
format!("{},{}", dep_chain, pkg_spec.package.name)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attempt to install missing deps
|
||||||
|
for dep in missing {
|
||||||
|
// Use package index for O(1) lookup
|
||||||
|
let candidate = pkg_index.find(&dep);
|
||||||
|
|
||||||
|
if let Some(dep_spec_path) = candidate {
|
||||||
|
ui::info(format!("Installing dependency: {}...", dep));
|
||||||
|
|
||||||
|
let mut cmd = std::process::Command::new(std::env::current_exe()?);
|
||||||
|
cmd.arg("-r").arg(options.rootfs);
|
||||||
|
|
||||||
|
if options.no_deps {
|
||||||
|
cmd.arg("--no-deps");
|
||||||
|
}
|
||||||
|
if options.no_flags {
|
||||||
|
cmd.arg("--no-flags");
|
||||||
|
}
|
||||||
|
if let Some(p) = options.cross_prefix {
|
||||||
|
cmd.arg("--cross-prefix").arg(p);
|
||||||
|
}
|
||||||
|
if options.clean {
|
||||||
|
cmd.arg("--clean");
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.arg("install").arg(&dep_spec_path);
|
||||||
|
cmd.env("DEPOT_DEPCHAIN", &new_chain);
|
||||||
|
|
||||||
|
let status = cmd.status()?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
anyhow::bail!("Failed to install dependency: {}", dep);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("Could not find package spec for dependency: {}", dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce required dependencies before building/installing.
|
||||||
|
deps::require_build_deps(&pkg_spec, &db_path)?;
|
||||||
|
deps::require_runtime_deps(&pkg_spec, &db_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cross_config = options
|
||||||
|
.cross_prefix
|
||||||
|
.map(cross::CrossConfig::from_prefix)
|
||||||
|
.transpose()?;
|
||||||
|
let mut built_src_dir: Option<PathBuf> = None;
|
||||||
|
|
||||||
|
let destdir = if let Some(dir) = &staging_dir {
|
||||||
|
dir.path().to_path_buf()
|
||||||
|
} else {
|
||||||
|
// 1-2. Fetch + extract sources (supports archives and git URL#rev)
|
||||||
|
let src_dir = source::prepare(&pkg_spec, &config.cache_dir, &config.build_dir)?;
|
||||||
|
built_src_dir = Some(src_dir.clone());
|
||||||
|
|
||||||
|
// 3. Build
|
||||||
|
let destdir = config
|
||||||
|
.build_dir
|
||||||
|
.join("destdir")
|
||||||
|
.join(&pkg_spec.package.name);
|
||||||
|
|
||||||
|
if !options.lib32_only {
|
||||||
|
builder::build(
|
||||||
|
&pkg_spec,
|
||||||
|
&src_dir,
|
||||||
|
&destdir,
|
||||||
|
cross_config.as_ref(),
|
||||||
|
!options.no_flags,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 3.1 Copy license files into staged tree
|
||||||
|
staging::add_licenses(&src_dir, &destdir, &pkg_spec.package.name)?;
|
||||||
|
install::scripts::stage_scripts_from_spec_dir(&pkg_spec, &destdir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
destdir
|
||||||
|
};
|
||||||
|
|
||||||
|
if !options.lib32_only {
|
||||||
|
if staging_dir.is_none() {
|
||||||
|
// Source-build path: apply staging transforms (strip/compress/static cleanup).
|
||||||
|
staging::process(&destdir, &pkg_spec)?;
|
||||||
|
} else {
|
||||||
|
// Binary archive path: install as-packaged without post-build transformations.
|
||||||
|
ui::info("Installing binary archive payload without staging transforms");
|
||||||
|
}
|
||||||
|
|
||||||
|
let installed =
|
||||||
|
install_package_outputs_to_rootfs(&pkg_spec, &destdir, options.rootfs, config)?;
|
||||||
|
for pkg in installed {
|
||||||
|
ui::success(format!(
|
||||||
|
"Successfully installed {} v{}",
|
||||||
|
pkg.name, pkg.version
|
||||||
|
));
|
||||||
|
// TODO(snapper): create post-install snapshot after install commit succeeds.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(src_dir) = built_src_dir.as_deref()
|
||||||
|
&& let Some((lib32_spec, lib32_destdir)) = build_lib32_companion_package(
|
||||||
|
&pkg_spec,
|
||||||
|
src_dir,
|
||||||
|
config,
|
||||||
|
cross_config.as_ref(),
|
||||||
|
!options.no_flags,
|
||||||
|
options.lib32_only,
|
||||||
|
)?
|
||||||
|
{
|
||||||
|
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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(cli: Cli) -> Result<()> {
|
pub fn run(cli: Cli) -> Result<()> {
|
||||||
ui::set_assume_yes(cli.yes);
|
ui::set_assume_yes(cli.yes);
|
||||||
|
|
||||||
@@ -1266,340 +1579,95 @@ pub fn run(cli: Cli) -> Result<()> {
|
|||||||
if !crate::fakeroot::is_root() {
|
if !crate::fakeroot::is_root() {
|
||||||
anyhow::bail!("The 'install' command must be run as root");
|
anyhow::bail!("The 'install' command must be run as root");
|
||||||
}
|
}
|
||||||
let mut spec_path = spec.unwrap_or(spec_or_archive);
|
let install_requests = match spec {
|
||||||
|
Some(spec_path) => vec![spec_path],
|
||||||
// Load configuration early so we can use the configured repo clone dir
|
None => spec_or_archive,
|
||||||
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
|
|
||||||
.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(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
// If the provided path doesn't exist, treat it as a package name and
|
|
||||||
// try to locate a spec under configured repo dir or local packages/.
|
|
||||||
if !spec_path.exists() {
|
|
||||||
let name = spec_path.to_string_lossy().to_string();
|
|
||||||
ui::info(format!("Looking up package '{}' in local indexes...", name));
|
|
||||||
let pkg_index =
|
|
||||||
index::PackageIndex::build_with_repo_dir(Some(config.repo_clone_dir.clone()));
|
|
||||||
if let Some(found) = pkg_index.find(&name) {
|
|
||||||
spec_path = found;
|
|
||||||
} else {
|
|
||||||
let host_arch = std::env::consts::ARCH;
|
|
||||||
let mut binary_repos: Vec<_> = config
|
|
||||||
.binary_repos
|
|
||||||
.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)));
|
|
||||||
|
|
||||||
for (repo_name, repo_cfg) in binary_repos {
|
|
||||||
match db::repo::find_binary_repo_package(
|
|
||||||
repo_name,
|
|
||||||
repo_cfg,
|
|
||||||
&cli.rootfs,
|
|
||||||
&config.package_cache_dir,
|
|
||||||
&name,
|
|
||||||
) {
|
|
||||||
Ok(Some(rec)) => {
|
|
||||||
let archive = db::repo::fetch_binary_package_archive(
|
|
||||||
repo_name,
|
|
||||||
repo_cfg,
|
|
||||||
&cli.rootfs,
|
|
||||||
&rec,
|
|
||||||
&config.package_cache_dir,
|
|
||||||
)?;
|
|
||||||
ui::info(format!(
|
|
||||||
"Resolved '{}' from binary repo '{}' as {}-{} (package {}) ({} bytes){} -> {}",
|
|
||||||
name,
|
|
||||||
repo_name,
|
|
||||||
rec.version,
|
|
||||||
rec.revision,
|
|
||||||
rec.name,
|
|
||||||
rec.size,
|
|
||||||
rec.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|d| format!(" [{}]", d))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
archive.display()
|
|
||||||
));
|
|
||||||
spec_path = archive;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(None) => {}
|
|
||||||
Err(e) => {
|
|
||||||
crate::log_warn!("Binary repo '{}': {}", repo_name, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui::info(format!("Installing package from: {}", spec_path.display()));
|
|
||||||
|
|
||||||
let (mut pkg_spec, staging_dir): (package::PackageSpec, Option<tempfile::TempDir>) =
|
|
||||||
if spec_path.to_string_lossy().ends_with(".tar.zst") {
|
|
||||||
// Install from archive
|
|
||||||
ui::info(format!("Detected package archive: {}", spec_path.display()));
|
|
||||||
let (spec, tmp_dir) = load_package_archive_into_staging(&spec_path)?;
|
|
||||||
(spec, Some(tmp_dir))
|
|
||||||
} else {
|
|
||||||
// Install from spec (normal build)
|
|
||||||
let mut pkg_spec = package::PackageSpec::from_file(&spec_path)?;
|
|
||||||
pkg_spec.apply_config(&config);
|
|
||||||
(pkg_spec, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
if cli.lib32_only && staging_dir.is_some() {
|
|
||||||
anyhow::bail!("--lib32-only is only supported when installing from a package spec");
|
|
||||||
}
|
|
||||||
|
|
||||||
ui::info(format!(
|
|
||||||
"Package: {} v{}-{}",
|
|
||||||
pkg_spec.package.name, pkg_spec.package.version, pkg_spec.package.revision
|
|
||||||
));
|
|
||||||
|
|
||||||
if cli.dry_run {
|
|
||||||
ui::info("Dry run enabled, stopping before install/build work.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let install_targets = vec![format!(
|
|
||||||
"{} v{}-{}",
|
|
||||||
pkg_spec.package.name, pkg_spec.package.version, pkg_spec.package.revision
|
|
||||||
)];
|
|
||||||
if !ui::prompt_package_action("installation", &install_targets, true)? {
|
|
||||||
anyhow::bail!("Aborted");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(snapper): create pre-install snapshot before install work starts.
|
|
||||||
|
|
||||||
// Ensure database directory exists
|
|
||||||
std::fs::create_dir_all(&config.db_dir).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Failed to create database directory: {}",
|
|
||||||
config.db_dir.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let db_path = config.db_dir.join("packages.db");
|
|
||||||
|
|
||||||
if staging_dir.is_none() {
|
|
||||||
maybe_disable_tests_for_missing_deps(&mut pkg_spec, &db_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check dependencies and prompt for auto-install if needed
|
|
||||||
if !cli.no_deps {
|
|
||||||
deps::print_dep_status(&pkg_spec, &db_path)?;
|
|
||||||
|
|
||||||
// Collect all missing dependencies (build + runtime)
|
|
||||||
let missing = merge_missing_dependencies(
|
|
||||||
deps::check_build_deps(&pkg_spec, &db_path)?,
|
|
||||||
deps::check_runtime_deps(&pkg_spec, &db_path)?,
|
|
||||||
);
|
|
||||||
if !missing.is_empty() {
|
|
||||||
// Check for dependency cycles via DEPOT_DEPCHAIN env var
|
|
||||||
let dep_chain = std::env::var("DEPOT_DEPCHAIN").unwrap_or_default();
|
|
||||||
let chain_set: std::collections::HashSet<&str> =
|
|
||||||
dep_chain.split(',').filter(|s| !s.is_empty()).collect();
|
|
||||||
|
|
||||||
if chain_set.contains(pkg_spec.package.name.as_str()) {
|
|
||||||
anyhow::bail!(
|
|
||||||
"Dependency cycle detected! {} is already in chain: {}",
|
|
||||||
pkg_spec.package.name,
|
|
||||||
dep_chain
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Build new dep chain
|
|
||||||
let new_chain = if dep_chain.is_empty() {
|
|
||||||
pkg_spec.package.name.clone()
|
|
||||||
} else {
|
|
||||||
format!("{},{}", dep_chain, pkg_spec.package.name)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attempt to install missing deps
|
|
||||||
for dep in missing {
|
|
||||||
// Use package index for O(1) lookup
|
|
||||||
let candidate = pkg_index.find(&dep);
|
|
||||||
|
|
||||||
if let Some(dep_spec_path) = candidate {
|
|
||||||
ui::info(format!("Installing dependency: {}...", dep));
|
|
||||||
|
|
||||||
let mut cmd = std::process::Command::new(std::env::current_exe()?);
|
|
||||||
cmd.arg("-r").arg(&cli.rootfs);
|
|
||||||
|
|
||||||
if cli.no_deps {
|
|
||||||
cmd.arg("--no-deps");
|
|
||||||
}
|
|
||||||
if cli.no_flags {
|
|
||||||
cmd.arg("--no-flags");
|
|
||||||
}
|
|
||||||
if let Some(ref p) = cli.cross_prefix {
|
|
||||||
cmd.arg("--cross-prefix").arg(p);
|
|
||||||
}
|
|
||||||
if cli.clean {
|
|
||||||
cmd.arg("--clean");
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.arg("install").arg(&dep_spec_path);
|
|
||||||
cmd.env("DEPOT_DEPCHAIN", &new_chain);
|
|
||||||
|
|
||||||
let status = cmd.status()?;
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
anyhow::bail!("Failed to install dependency: {}", dep);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
anyhow::bail!(
|
|
||||||
"Could not find package spec for dependency: {}",
|
|
||||||
dep
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce required dependencies before building/installing.
|
|
||||||
deps::require_build_deps(&pkg_spec, &db_path)?;
|
|
||||||
deps::require_runtime_deps(&pkg_spec, &db_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cross_config = cli
|
|
||||||
.cross_prefix
|
|
||||||
.as_ref()
|
|
||||||
.map(|p| cross::CrossConfig::from_prefix(p))
|
|
||||||
.transpose()?;
|
|
||||||
let mut built_src_dir: Option<PathBuf> = None;
|
|
||||||
|
|
||||||
let destdir = if let Some(dir) = &staging_dir {
|
|
||||||
dir.path().to_path_buf()
|
|
||||||
} else {
|
|
||||||
// 1-2. Fetch + extract sources (supports archives and git URL#rev)
|
|
||||||
let src_dir = source::prepare(&pkg_spec, &config.cache_dir, &config.build_dir)?;
|
|
||||||
built_src_dir = Some(src_dir.clone());
|
|
||||||
|
|
||||||
// 3. Build
|
|
||||||
let destdir = config
|
|
||||||
.build_dir
|
|
||||||
.join("destdir")
|
|
||||||
.join(&pkg_spec.package.name);
|
|
||||||
|
|
||||||
if !cli.lib32_only {
|
|
||||||
builder::build(
|
|
||||||
&pkg_spec,
|
|
||||||
&src_dir,
|
|
||||||
&destdir,
|
|
||||||
cross_config.as_ref(),
|
|
||||||
!cli.no_flags,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 3.1 Copy license files into staged tree
|
|
||||||
staging::add_licenses(&src_dir, &destdir, &pkg_spec.package.name)?;
|
|
||||||
install::scripts::stage_scripts_from_spec_dir(&pkg_spec, &destdir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
destdir
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !cli.lib32_only {
|
// Load configuration early so we can use configured repos/paths.
|
||||||
if staging_dir.is_none() {
|
let config = config::Config::for_rootfs(&cli.rootfs);
|
||||||
// Source-build path: apply staging transforms (strip/compress/static cleanup).
|
let mut planned_targets = Vec::new();
|
||||||
staging::process(&destdir, &pkg_spec)?;
|
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 {
|
} else {
|
||||||
// Binary archive path: install as-packaged without post-build transformations.
|
planner::build_install_plan_for_targets(
|
||||||
ui::info("Installing binary archive payload without staging transforms");
|
&config,
|
||||||
}
|
&cli.rootfs,
|
||||||
|
&planned_targets,
|
||||||
let installed =
|
planner_opts,
|
||||||
install_package_outputs_to_rootfs(&pkg_spec, &destdir, &cli.rootfs, &config)?;
|
)?
|
||||||
for pkg in installed {
|
};
|
||||||
ui::success(format!(
|
print_plan_summary(&plan);
|
||||||
"Successfully installed {} v{}",
|
execute_install_plan_with_child_commands(
|
||||||
pkg.name, pkg.version
|
&plan,
|
||||||
));
|
&cli.rootfs,
|
||||||
// TODO(snapper): create post-install snapshot after install commit succeeds.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(src_dir) = built_src_dir.as_deref()
|
|
||||||
&& let Some((lib32_spec, lib32_destdir)) = build_lib32_companion_package(
|
|
||||||
&pkg_spec,
|
|
||||||
src_dir,
|
|
||||||
&config,
|
&config,
|
||||||
cross_config.as_ref(),
|
InstallPlanExecutionOptions {
|
||||||
!cli.no_flags,
|
no_flags: cli.no_flags,
|
||||||
cli.lib32_only,
|
cross_prefix: cli.cross_prefix.as_deref(),
|
||||||
)?
|
clean: cli.clean,
|
||||||
{
|
dry_run: cli.dry_run,
|
||||||
install_staged_to_rootfs(&lib32_spec, &lib32_destdir, &cli.rootfs, &config)?;
|
confirm_installation: true,
|
||||||
ui::success(format!(
|
},
|
||||||
"Successfully installed {} v{}",
|
)?;
|
||||||
lib32_spec.package.name, lib32_spec.package.version
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
install::scripts::run_deferred_hooks_if_possible(&cli.rootfs)?;
|
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 {
|
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