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:
@@ -640,6 +640,10 @@ pub fn create_interactive() -> Result<PackageSpec> {
|
||||
"Test dependency",
|
||||
"Package needed only for running package test suites",
|
||||
)?;
|
||||
let optional_deps = prompt_repeating_list(
|
||||
"Optional dependency",
|
||||
"Package that enables optional runtime functionality",
|
||||
)?;
|
||||
|
||||
Ok(PackageSpec {
|
||||
package: PackageInfo {
|
||||
@@ -659,6 +663,7 @@ pub fn create_interactive() -> Result<PackageSpec> {
|
||||
build: build_deps,
|
||||
runtime: runtime_deps,
|
||||
test: test_deps,
|
||||
optional: optional_deps,
|
||||
},
|
||||
package_alternatives: Default::default(),
|
||||
package_dependencies: Default::default(),
|
||||
@@ -875,6 +880,12 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
||||
if spec.build.flags.no_strip != defaults.no_strip {
|
||||
flags_tbl.insert("no_strip".into(), Value::Boolean(spec.build.flags.no_strip));
|
||||
}
|
||||
if spec.build.flags.no_delete_static != defaults.no_delete_static {
|
||||
flags_tbl.insert(
|
||||
"no_delete_static".into(),
|
||||
Value::Boolean(spec.build.flags.no_delete_static),
|
||||
);
|
||||
}
|
||||
if spec.build.flags.no_compress_man != defaults.no_compress_man {
|
||||
flags_tbl.insert(
|
||||
"no_compress_man".into(),
|
||||
@@ -1221,6 +1232,7 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
||||
if !spec.dependencies.build.is_empty()
|
||||
|| !spec.dependencies.runtime.is_empty()
|
||||
|| !spec.dependencies.test.is_empty()
|
||||
|| !spec.dependencies.optional.is_empty()
|
||||
{
|
||||
let mut dep_tbl = Table::new();
|
||||
if !spec.dependencies.build.is_empty() {
|
||||
@@ -1259,6 +1271,18 @@ pub fn spec_to_minimal_toml(spec: &PackageSpec) -> anyhow::Result<String> {
|
||||
),
|
||||
);
|
||||
}
|
||||
if !spec.dependencies.optional.is_empty() {
|
||||
dep_tbl.insert(
|
||||
"optional".into(),
|
||||
Value::Array(
|
||||
spec.dependencies
|
||||
.optional
|
||||
.iter()
|
||||
.map(|s| Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
root.insert("dependencies".into(), Value::Table(dep_tbl));
|
||||
}
|
||||
|
||||
@@ -1412,6 +1436,7 @@ mod tests {
|
||||
flags.keep = vec!["etc/locale.gen".into()];
|
||||
flags.no_flags = true;
|
||||
flags.no_strip = true;
|
||||
flags.no_delete_static = true;
|
||||
flags.no_compress_man = true;
|
||||
flags.skip_tests = true;
|
||||
flags.make_vars = vec!["V=1".into()];
|
||||
@@ -1467,6 +1492,7 @@ mod tests {
|
||||
assert!(toml.contains("\"etc/locale.gen\""));
|
||||
assert!(toml.contains("no_flags = true"));
|
||||
assert!(toml.contains("no_strip = true"));
|
||||
assert!(toml.contains("no_delete_static = true"));
|
||||
assert!(toml.contains("no_compress_man = true"));
|
||||
assert!(toml.contains("skip_tests = true"));
|
||||
assert!(toml.contains("make_vars = ["));
|
||||
@@ -1515,7 +1541,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spec_to_minimal_toml_includes_test_dependencies() {
|
||||
fn spec_to_minimal_toml_includes_test_and_optional_dependencies() {
|
||||
let spec = PackageSpec {
|
||||
package: PackageInfo {
|
||||
name: "foo".into(),
|
||||
@@ -1543,6 +1569,7 @@ mod tests {
|
||||
build: vec![],
|
||||
runtime: vec![],
|
||||
test: vec!["python".into(), "bats".into()],
|
||||
optional: vec!["gtk-doc".into()],
|
||||
},
|
||||
package_alternatives: Default::default(),
|
||||
package_dependencies: Default::default(),
|
||||
@@ -1559,6 +1586,13 @@ mod tests {
|
||||
assert_eq!(test_deps.len(), 2);
|
||||
assert_eq!(test_deps[0].as_str(), Some("python"));
|
||||
assert_eq!(test_deps[1].as_str(), Some("bats"));
|
||||
let optional_deps = val
|
||||
.get("dependencies")
|
||||
.and_then(|d| d.get("optional"))
|
||||
.and_then(|t| t.as_array())
|
||||
.expect("expected dependencies.optional array");
|
||||
assert_eq!(optional_deps.len(), 1);
|
||||
assert_eq!(optional_deps[0].as_str(), Some("gtk-doc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1584,6 +1618,7 @@ mod tests {
|
||||
build: Vec::new(),
|
||||
runtime: vec!["foo".into(), "bar".into()],
|
||||
test: Vec::new(),
|
||||
optional: Vec::new(),
|
||||
},
|
||||
package_alternatives: Default::default(),
|
||||
package_dependencies: Default::default(),
|
||||
|
||||
+10
-20
@@ -159,20 +159,9 @@ impl Packager {
|
||||
),
|
||||
);
|
||||
|
||||
// Add dependencies if useful for repo indexing
|
||||
let mut build_deps = toml::map::Map::new();
|
||||
build_deps.insert(
|
||||
"build".to_string(),
|
||||
toml::Value::Array(
|
||||
self.spec
|
||||
.dependencies
|
||||
.build
|
||||
.iter()
|
||||
.map(|s| toml::Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
build_deps.insert(
|
||||
// Add install-relevant dependency kinds for repo/runtime consumers.
|
||||
let mut deps = toml::map::Map::new();
|
||||
deps.insert(
|
||||
"runtime".to_string(),
|
||||
toml::Value::Array(
|
||||
self.spec
|
||||
@@ -183,18 +172,18 @@ impl Packager {
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
build_deps.insert(
|
||||
"test".to_string(),
|
||||
deps.insert(
|
||||
"optional".to_string(),
|
||||
toml::Value::Array(
|
||||
self.spec
|
||||
.dependencies
|
||||
.test
|
||||
.optional
|
||||
.iter()
|
||||
.map(|s| toml::Value::String(s.clone()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
map.insert("dependencies".to_string(), toml::Value::Table(build_deps));
|
||||
map.insert("dependencies".to_string(), toml::Value::Table(deps));
|
||||
|
||||
let toml_str = toml::to_string(&toml::Value::Table(map))
|
||||
.context("Failed to serialize metadata to TOML")?;
|
||||
@@ -357,9 +346,10 @@ mod tests {
|
||||
assert_eq!(val.get("license").and_then(|v| v.as_str()), Some("MIT"));
|
||||
|
||||
let deps = val.get("dependencies").unwrap();
|
||||
assert!(deps.get("build").unwrap().as_array().unwrap().is_empty());
|
||||
assert!(deps.get("runtime").unwrap().as_array().unwrap().is_empty());
|
||||
assert!(deps.get("test").unwrap().as_array().unwrap().is_empty());
|
||||
assert!(deps.get("optional").unwrap().as_array().unwrap().is_empty());
|
||||
assert!(deps.get("build").is_none());
|
||||
assert!(deps.get("test").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+54
-6
@@ -5,7 +5,7 @@ use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -192,6 +192,22 @@ impl PackageSpec {
|
||||
.unwrap_or_else(|| self.dependencies.clone())
|
||||
}
|
||||
|
||||
/// Return all package names/provided features produced by this spec.
|
||||
///
|
||||
/// This includes all output package names and per-output `provides` entries.
|
||||
pub fn local_dependency_provides(&self) -> HashSet<String> {
|
||||
let mut names = HashSet::new();
|
||||
for output in self.outputs() {
|
||||
let output_name = output.name.clone();
|
||||
names.insert(output_name.clone());
|
||||
let alternatives = self.alternatives_for_output(&output_name);
|
||||
for provided in alternatives.provides {
|
||||
names.insert(provided);
|
||||
}
|
||||
}
|
||||
names
|
||||
}
|
||||
|
||||
/// Return alternatives/provides for a specific output package name.
|
||||
///
|
||||
/// If no per-output override exists, returns the top-level alternatives.
|
||||
@@ -481,6 +497,11 @@ impl PackageSpec {
|
||||
self.build.flags.no_strip = b;
|
||||
}
|
||||
}
|
||||
"no_delete_static" | "no-delete-static" => {
|
||||
if let Some(b) = v.as_bool() {
|
||||
self.build.flags.no_delete_static = b;
|
||||
}
|
||||
}
|
||||
"no_compress_man"
|
||||
| "no-compress-man"
|
||||
| "no_compress_manpages"
|
||||
@@ -997,6 +1018,11 @@ impl PackageSpec {
|
||||
self.build.flags.no_strip = b;
|
||||
}
|
||||
}
|
||||
"no_delete_static" | "no-delete-static" => {
|
||||
if let Some(b) = values.last().and_then(|v| v.as_bool()) {
|
||||
self.build.flags.no_delete_static = b;
|
||||
}
|
||||
}
|
||||
"no_compress_man"
|
||||
| "no-compress-man"
|
||||
| "no_compress_manpages"
|
||||
@@ -1535,6 +1561,7 @@ make_test_dirs = ["tests"]
|
||||
make_install_dirs = ["lib"]
|
||||
no_flags = true
|
||||
no_strip = true
|
||||
no_delete_static = true
|
||||
no_compress_man = true
|
||||
skip_tests = true
|
||||
keep = ["etc/locale.gen"]
|
||||
@@ -1572,6 +1599,10 @@ post_configure = ["echo configured"]
|
||||
"build.flags.no_compress_man".to_string(),
|
||||
vec![toml::Value::Boolean(false)],
|
||||
);
|
||||
config.appends.insert(
|
||||
"build.flags.no_delete_static".to_string(),
|
||||
vec![toml::Value::Boolean(false)],
|
||||
);
|
||||
config.appends.insert(
|
||||
"build.flags.passthrough_env".to_string(),
|
||||
vec![toml::Value::String("CARGO_HOME".to_string())],
|
||||
@@ -1627,6 +1658,7 @@ post_configure = ["echo configured"]
|
||||
);
|
||||
assert!(spec.build.flags.no_flags);
|
||||
assert!(!spec.build.flags.no_strip);
|
||||
assert!(!spec.build.flags.no_delete_static);
|
||||
assert!(!spec.build.flags.no_compress_man);
|
||||
assert!(
|
||||
spec.build
|
||||
@@ -1735,7 +1767,7 @@ no_flags = true
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_no_strip_and_no_compress_man_from_spec() {
|
||||
fn parse_no_strip_no_delete_static_and_no_compress_man_from_spec() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let path = tmp.path().join("pkg.toml");
|
||||
|
||||
@@ -1759,6 +1791,7 @@ type = "custom"
|
||||
|
||||
[build.flags]
|
||||
no_strip = true
|
||||
"no-delete-static" = true
|
||||
no-compress-man = true
|
||||
"#,
|
||||
)
|
||||
@@ -1766,6 +1799,7 @@ no-compress-man = true
|
||||
|
||||
let spec = PackageSpec::from_file(&path).unwrap();
|
||||
assert!(spec.build.flags.no_strip);
|
||||
assert!(spec.build.flags.no_delete_static);
|
||||
assert!(spec.build.flags.no_compress_man);
|
||||
}
|
||||
|
||||
@@ -2128,10 +2162,11 @@ extract_dir = "foo"
|
||||
[build]
|
||||
type = "autotools"
|
||||
|
||||
[dependencies]
|
||||
build = ["make"]
|
||||
test = ["python", "bats"]
|
||||
"#,
|
||||
[dependencies]
|
||||
build = ["make"]
|
||||
test = ["python", "bats"]
|
||||
optional = ["gtk-doc"]
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -2140,6 +2175,7 @@ test = ["python", "bats"]
|
||||
spec.dependencies.test,
|
||||
vec!["python".to_string(), "bats".to_string()]
|
||||
);
|
||||
assert_eq!(spec.dependencies.optional, vec!["gtk-doc".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2576,6 +2612,14 @@ pub struct BuildFlags {
|
||||
/// Disable automatic stripping of ELF files during staging.
|
||||
#[serde(default, alias = "no-strip")]
|
||||
pub no_strip: bool,
|
||||
/// Disable automatic deletion of static libraries (`*.a`) during staging.
|
||||
#[serde(
|
||||
default,
|
||||
alias = "no-delete-static",
|
||||
alias = "no_remove_static",
|
||||
alias = "no-remove-static"
|
||||
)]
|
||||
pub no_delete_static: bool,
|
||||
/// Disable automatic zstd compression of man pages during staging.
|
||||
#[serde(
|
||||
default,
|
||||
@@ -2814,6 +2858,7 @@ impl Default for BuildFlags {
|
||||
keep: Vec::new(),
|
||||
no_flags: false,
|
||||
no_strip: false,
|
||||
no_delete_static: false,
|
||||
no_compress_man: false,
|
||||
skip_tests: false,
|
||||
build_32: false,
|
||||
@@ -2981,4 +3026,7 @@ pub struct Dependencies {
|
||||
/// Dependencies required to run package test suites.
|
||||
#[serde(default)]
|
||||
pub test: Vec<String>,
|
||||
/// Optional runtime integrations that enhance functionality when installed.
|
||||
#[serde(default)]
|
||||
pub optional: Vec<String>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user