refactor command flow and add signed package/hook safety

- split CLI definitions and command execution out of main.rs into new cli.rs and commands.rs modules\n- add transaction hook support via /etc/depot.d/hooks/*.toml with pre/post phases, operation/package/path filters, wildcard negation, and optional affected-path stdin\n- execute transaction hooks around install/update/remove operations and add package-action prompts for install/build/remove flows\n- require and verify detached minisign signatures for binary package archives when allow_unsigned=false, including cache handling and tests\n- persist optional dependencies from package metadata into repo index data and include optional deps in produced metadata\n- update dependency/planner logic to ignore dependencies provided by local split outputs and stop pulling test deps into install planning\n- auto-disable tests when required test dependencies are missing instead of hard-failing build/install paths\n- improve autotools defaults by injecting standard install directory flags when supported and preserving user overrides\n- remove static .a archives during staging by default with new build.flags.no_delete_static escape hatch\n- preserve symlinks and file metadata (permissions/timestamps) in extractor fallback copy path and add regression tests\n- refresh README docs for detached package signatures, optional dependencies, and transaction hooks\n- adjust dependency set: add filetime, disable reqwest default features, and simplify git2/OpenSSL linkage
This commit is contained in:
2026-02-28 00:54:08 -06:00
parent e045ae78fd
commit ce5fefa833
19 changed files with 3863 additions and 2923 deletions
+84 -17
View File
@@ -9,7 +9,7 @@
//! - `package<=1.2.3` - less than or equal to 1.2.3
use crate::db;
use crate::package::PackageSpec;
use crate::package::{BuildType, PackageSpec};
use crate::ui;
use anyhow::Result;
use std::path::Path;
@@ -139,6 +139,13 @@ fn is_dep_satisfied(
}
}
fn build_type_runs_automatic_tests(spec: &PackageSpec) -> bool {
matches!(
spec.build.build_type,
BuildType::Autotools | BuildType::CMake
)
}
/// Check whether a dependency expression is satisfied by the installed package DB.
pub fn is_dep_satisfied_in_db(dep: &str, db_path: &Path) -> Result<bool> {
if !db_path.exists() {
@@ -173,15 +180,24 @@ pub fn check_build_deps(spec: &PackageSpec, db_path: &Path) -> Result<Vec<String
/// Check if all runtime dependencies are satisfied
pub fn check_runtime_deps(spec: &PackageSpec, db_path: &Path) -> Result<Vec<String>> {
let mut missing = Vec::new();
let local_provides = spec.local_dependency_provides();
if !db_path.exists() {
return Ok(spec.dependencies.runtime.clone());
for dep in &spec.dependencies.runtime {
if !local_provides.contains(dep_name(dep)) {
missing.push(dep.clone());
}
}
return Ok(missing);
}
let installed = db::get_installed_packages(db_path)?;
let provides = db::get_all_provides(db_path)?;
for dep in &spec.dependencies.runtime {
if local_provides.contains(dep_name(dep)) {
continue;
}
if !is_dep_satisfied(dep, &installed, &provides, db_path)? {
missing.push(dep.clone());
}
@@ -244,11 +260,21 @@ pub fn print_dep_status(spec: &PackageSpec, db_path: &Path) -> Result<()> {
"Test dependencies: {}",
spec.dependencies.test.join(", ")
));
if !missing_test.is_empty() {
if !spec.build.flags.skip_tests
&& build_type_runs_automatic_tests(spec)
&& !missing_test.is_empty()
{
ui::warn(format!("Test deps missing: {}", missing_test.join(", ")));
}
}
if !spec.dependencies.optional.is_empty() {
ui::info(format!(
"Optional dependencies: {}",
spec.dependencies.optional.join(", ")
));
}
Ok(())
}
@@ -266,20 +292,6 @@ pub fn require_build_deps(spec: &PackageSpec, db_path: &Path) -> Result<()> {
Ok(())
}
/// Verify all test dependencies are installed, error if not
pub fn require_test_deps(spec: &PackageSpec, db_path: &Path) -> Result<()> {
let missing = check_test_deps(spec, db_path)?;
if !missing.is_empty() {
anyhow::bail!(
"Missing test dependencies: {}\nInstall them first with: depot install <package>",
missing.join(", ")
);
}
Ok(())
}
/// Verify all runtime dependencies are installed, error if not.
pub fn require_runtime_deps(spec: &PackageSpec, db_path: &Path) -> Result<()> {
let missing = check_runtime_deps(spec, db_path)?;
@@ -372,6 +384,7 @@ mod tests {
build: Vec::new(),
runtime: Vec::new(),
test: vec!["bats".into(), "python".into()],
optional: Vec::new(),
},
package_alternatives: Default::default(),
package_dependencies: Default::default(),
@@ -411,6 +424,7 @@ mod tests {
build: Vec::new(),
runtime: vec!["python".into()],
test: Vec::new(),
optional: Vec::new(),
},
package_alternatives: Default::default(),
package_dependencies: Default::default(),
@@ -421,4 +435,57 @@ mod tests {
.expect_err("runtime deps should be required");
assert!(err.to_string().contains("Missing runtime dependencies"));
}
#[test]
fn test_check_runtime_deps_ignores_local_outputs_and_provides() {
let spec = PackageSpec {
package: crate::package::PackageInfo {
name: "foo".into(),
version: "1.0".into(),
revision: 1,
description: "d".into(),
homepage: "h".into(),
license: vec!["MIT".into()],
},
packages: vec![crate::package::PackageInfo {
name: "foo-libs".into(),
version: "1.0".into(),
revision: 1,
description: "d".into(),
homepage: "h".into(),
license: vec!["MIT".into()],
}],
alternatives: Default::default(),
manual_sources: Vec::new(),
source: vec![crate::package::Source {
url: "https://example.test/foo.tar.gz".into(),
sha256: "skip".into(),
extract_dir: "foo".into(),
patches: Vec::new(),
post_extract: Vec::new(),
}],
build: crate::package::Build {
build_type: crate::package::BuildType::Custom,
flags: crate::package::BuildFlags::default(),
},
dependencies: crate::package::Dependencies {
build: Vec::new(),
runtime: vec!["foo-libs".into(), "libfoo".into(), "python".into()],
test: Vec::new(),
optional: Vec::new(),
},
package_alternatives: std::collections::BTreeMap::from([(
"foo-libs".to_string(),
crate::package::Alternatives {
provides: vec!["libfoo".into()],
replaces: Vec::new(),
},
)]),
package_dependencies: Default::default(),
spec_dir: std::path::PathBuf::from("."),
};
let missing = check_runtime_deps(&spec, Path::new("/definitely/not/a/real/db")).unwrap();
assert_eq!(missing, vec!["python".to_string()]);
}
}