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
+138
View File
@@ -208,6 +208,144 @@ impl RepoManager {
}
}
/// Synchronize git mirrors into /usr/src/depot/<reponame>
pub fn sync_mirrors(repo_dir: &std::path::Path, mirrors: &std::collections::HashMap<String, String>) -> Result<()> {
use git2::{Repository, FetchOptions, Cred, RemoteCallbacks, ResetType, build::RepoBuilder};
use std::os::unix::fs::PermissionsExt;
let base = repo_dir.to_path_buf();
if !base.exists() {
std::fs::create_dir_all(&base)?;
}
for (name, url) in mirrors {
let target = base.join(name);
if !target.exists() {
println!("Cloning mirror '{}' -> {}", name, target.display());
// Use git2 RepoBuilder to clone
let mut cb = RemoteCallbacks::new();
cb.credentials(|_url, username_from_url, _allowed| {
// Try default credentials (ssh-agent / keychain)
Cred::ssh_key_from_agent(username_from_url.unwrap_or("git"))
});
let mut fo = FetchOptions::new();
fo.remote_callbacks(cb);
let mut builder = RepoBuilder::new();
builder.fetch_options(fo);
builder.clone(url, &target).with_context(|| format!("Failed to clone {}", url))?;
} else {
println!("Updating mirror '{}' in {}", name, target.display());
// Open repository and fetch updates
let repo = Repository::open(&target)
.with_context(|| format!("Failed to open repository at {}", target.display()))?;
let mut cb = RemoteCallbacks::new();
cb.credentials(|_url, username_from_url, _allowed| {
Cred::ssh_key_from_agent(username_from_url.unwrap_or("git"))
});
let mut fo = FetchOptions::new();
fo.remote_callbacks(cb);
// Fetch from origin
let mut remote = repo.find_remote("origin").or_else(|_| repo.remote_anonymous(url))?;
remote.fetch(&["refs/heads/*:refs/remotes/origin/*"], Some(&mut fo), None)
.with_context(|| format!("Failed to fetch updates for {}", url))?;
// Try to fast-forward HEAD to origin/HEAD by resetting to FETCH_HEAD if present
if let Ok(fetch_head) = repo.find_reference("FETCH_HEAD") {
if let Some(oid) = fetch_head.target() {
let obj = repo.find_object(oid, None)?;
repo.reset(&obj, ResetType::Hard, None)?;
}
}
}
// Make the tree readable and writable by everyone
for entry in walkdir::WalkDir::new(&target) {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o777))?;
} else if path.is_file() {
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o666))?;
}
}
}
Ok(())
}
/// Show status for each mirror repository: path, exists, branch/HEAD, latest commit, dirty
pub fn mirrors_status(repo_dir: &std::path::Path, mirrors: &std::collections::HashMap<String, String>) -> Result<()> {
use git2::Repository;
let base = repo_dir.to_path_buf();
if !base.exists() {
println!("Repo base directory does not exist: {}", base.display());
return Ok(());
}
for (name, _url) in mirrors {
let target = base.join(name);
println!("--- {} ---", name);
if !target.exists() {
println!("Not cloned: {}", target.display());
continue;
}
match Repository::open(&target) {
Ok(repo) => {
// Branch / HEAD
let head = repo.head().ok();
let branch = head
.as_ref()
.and_then(|h| h.shorthand().map(|s| s.to_string()))
.unwrap_or_else(|| "(no branch)".to_string());
// Latest commit OID
let oid = repo.refname_to_id("HEAD").ok();
let short = oid.map(|o| format!("{}", o)).unwrap_or_else(|| "(unknown)".to_string());
// Commit time (seconds since epoch) if available
let mut commit_time = String::new();
if let Some(oid) = oid {
if let Ok(commit) = repo.find_commit(oid) {
let t = commit.time().seconds();
commit_time = format!("{}", t);
}
}
// Dirty status
let statuses = match repo.statuses(None) {
Ok(s) => s,
Err(_) => {
println!("Warning: failed to read status for {}", target.display());
continue;
}
};
let dirty = statuses.iter().any(|s| s.status().intersects(git2::Status::WT_MODIFIED | git2::Status::WT_NEW | git2::Status::WT_DELETED));
println!("Path: {}", target.display());
println!("Branch/HEAD: {}", branch);
println!("HEAD OID: {}", short);
if !commit_time.is_empty() {
println!("Latest commit time (epoch): {}", commit_time);
}
println!("Dirty: {}", if dirty { "yes" } else { "no" });
}
Err(e) => {
println!("Failed to open repo at {}: {}", target.display(), e);
}
}
}
Ok(())
}
fn num_cpus() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())