feat: Implement build state tracking and interactive package specification creation

- Added StateTracker to manage build steps and allow resuming interrupted builds.
- Updated post_extract function to utilize StateTracker for patch and command execution.
- Introduced makefile.rs to handle building and installing packages via Makefile.
- Created interactive.rs for an interactive package specification creator with SHA256 computation for source URLs.
- Enhanced checksum verification to support multiple algorithms (SHA256, SHA512, MD5).
- Added example configuration files in contrib for system-wide and user-level Depot configurations.
- Updated tests to cover new functionality and ensure correctness of state tracking and interactive creation.
This commit is contained in:
2026-02-16 21:38:05 -06:00
parent 00ca2ebac6
commit 9a00608104
30 changed files with 3501 additions and 434 deletions
+92 -18
View File
@@ -6,6 +6,7 @@ use std::fs;
use std::path::{Path, PathBuf};
/// Global configuration settings
#[derive(Clone)]
pub struct Config {
/// Directory for cached source tarballs
pub cache_dir: PathBuf,
@@ -19,6 +20,11 @@ pub struct Config {
pub package_overrides: toml::Value,
/// Appends found in system TOML files (key -> values to append)
pub appends: HashMap<String, Vec<toml::Value>>,
/// Mirrors mapping read from /etc/depot.d/mirrors.toml (reponame -> git url)
pub mirrors: std::collections::HashMap<String, String>,
/// Directory where git mirrors are cloned (absolute path). Defaults to
/// <rootfs>/usr/src/depot unless overridden in depot.toml
pub repo_clone_dir: PathBuf,
}
impl Config {
@@ -63,6 +69,8 @@ impl Config {
build_overrides: toml::Value::Table(toml::map::Map::new()),
package_overrides: toml::Value::Table(toml::map::Map::new()),
appends: HashMap::new(),
mirrors: std::collections::HashMap::new(),
repo_clone_dir: abs_rootfs.join("usr/src/depot"),
};
if let Err(e) = config.load_system(&abs_rootfs) {
@@ -74,9 +82,18 @@ impl Config {
/// Load system-level and user-level overrides
pub fn load_system(&mut self, rootfs: &Path) -> Result<()> {
let mut config_paths = vec![rootfs.join("etc/depot.toml")];
// Load host system config (fallback) and then the requested rootfs config
// so that rootfs-specific settings override host settings when present.
let mut config_paths: Vec<PathBuf> = Vec::new();
let host_etc = PathBuf::from("/etc/depot.toml");
let rootfs_etc = rootfs.join("etc/depot.toml");
// Add user-level config paths if we are not root or if HOME is set
if host_etc.exists() && host_etc != rootfs_etc {
config_paths.push(host_etc);
}
config_paths.push(rootfs_etc);
// Add user-level config paths (user override) — these should remain highest precedence
if let Ok(home) = std::env::var("HOME") {
let home_path = PathBuf::from(home);
config_paths.push(home_path.join(".config/depot.toml"));
@@ -104,14 +121,14 @@ impl Config {
}
}
// Keep existing etc/depot.d/ support for backward compatibility/modular config
// Keep existing etc/depot.d/ support. Check host (/etc/depot.d) first as a
// fallback, then the rootfs path so the rootfs can override host settings.
let host_build = PathBuf::from("/etc/depot.d/build.toml");
let build_path = rootfs.join("etc/depot.d/build.toml");
if build_path.exists() {
let content = fs::read_to_string(&build_path).with_context(|| {
format!(
"Failed to read system build config: {}",
build_path.display()
)
if host_build.exists() && host_build != build_path {
let content = fs::read_to_string(&host_build).with_context(|| {
format!("Failed to read system build config: {}", host_build.display())
})?;
let (val, appends) = self.preprocess_toml(&content)?;
merge_toml_values(&mut self.build_overrides, &val);
@@ -120,13 +137,23 @@ impl Config {
}
}
if build_path.exists() {
let content = fs::read_to_string(&build_path).with_context(|| {
format!("Failed to read system build config: {}", build_path.display())
})?;
let (val, appends) = self.preprocess_toml(&content)?;
merge_toml_values(&mut self.build_overrides, &val);
for (k, v) in appends {
self.appends.insert(format!("build.{}", k), v);
}
}
let host_package = PathBuf::from("/etc/depot.d/package.toml");
let package_path = rootfs.join("etc/depot.d/package.toml");
if package_path.exists() {
let content = fs::read_to_string(&package_path).with_context(|| {
format!(
"Failed to read system package config: {}",
package_path.display()
)
if host_package.exists() && host_package != package_path {
let content = fs::read_to_string(&host_package).with_context(|| {
format!("Failed to read system package config: {}", host_package.display())
})?;
let (val, appends) = self.preprocess_toml(&content)?;
merge_toml_values(&mut self.package_overrides, &val);
@@ -135,6 +162,43 @@ impl Config {
}
}
if package_path.exists() {
let content = fs::read_to_string(&package_path).with_context(|| {
format!("Failed to read system package config: {}", package_path.display())
})?;
let (val, appends) = self.preprocess_toml(&content)?;
merge_toml_values(&mut self.package_overrides, &val);
for (k, v) in appends {
self.appends.insert(format!("package.{}", k), v);
}
}
// Load mirrors file: /etc/depot.d/mirrors.toml
let mirrors_path = rootfs.join("etc/depot.d/mirrors.toml");
if mirrors_path.exists() {
let content = fs::read_to_string(&mirrors_path).with_context(|| {
format!("Failed to read mirrors config: {}", mirrors_path.display())
})?;
let val: toml::Value = toml::from_str(&content)?;
if let Some(table) = val.as_table() {
for (k, v) in table {
if let Some(s) = v.as_str() {
self.mirrors.insert(k.clone(), s.to_string());
}
}
}
}
// Allow overriding repo clone dir via [repo] clone_dir in depot.toml
if let Some(repo_table) = self.build_overrides.get("repo").and_then(|v| v.as_table()) {
if let Some(clone_val) = repo_table.get("clone_dir").and_then(|v| v.as_str()) {
let p = PathBuf::from(clone_val);
// If relative path, make it relative to rootfs
let repo_dir = if p.is_absolute() { p } else { rootfs.join(p) };
self.repo_clone_dir = repo_dir;
}
}
Ok(())
}
}
@@ -304,9 +368,12 @@ cflags += ["-g"]
#[test]
fn test_config_non_root_fallback() {
// We can't easily mock is_root() here without more complex infrastructure,
// but we can at least check if the logic for abs_rootfs == "/" triggers
// the HOME join if we were non-root.
// Make the test deterministic by overriding HOME for the duration of the check.
// Save and restore the original value so other tests aren't affected.
let orig_home = std::env::var_os("HOME");
let fake_home = tempfile::tempdir().unwrap();
unsafe { std::env::set_var("HOME", &fake_home.path()); }
let config = Config::for_rootfs(Path::new("/"));
if !crate::fakeroot::is_root() {
@@ -322,6 +389,13 @@ cflags += ["-g"]
// If running as root (e.g. in some CI), it should use /var
assert_eq!(config.cache_dir, PathBuf::from("/var/cache/depot/sources"));
}
// restore original HOME
if let Some(v) = orig_home {
unsafe { std::env::set_var("HOME", v); }
} else {
unsafe { std::env::remove_var("HOME"); }
}
}
#[test]