use crate::context::{Context, current_context}; use std::error::Error; use std::path::{Path, PathBuf}; pub fn build_binary_package( arch: Option<&str>, series: Option<&str>, cwd: Option<&Path>, ) -> Result<(), Box> { let cwd = cwd.unwrap_or_else(|| Path::new(".")); // Parse changelog to get package name and version let changelog_path = cwd.join("debian/changelog"); let (package, version, _series) = crate::changelog::parse_changelog_header(&changelog_path)?; // Find .dsc file let dsc_path = find_dsc_file(cwd, &package, &version)?; println!("Building {} using sbuild...", dsc_path.display()); // Identify all related files from .dsc let mut files_to_ensure = get_dsc_related_files(&dsc_path)?; // Ensure dsc itself is included (usually first) if !files_to_ensure.contains(&dsc_path) { files_to_ensure.insert(0, dsc_path.clone()); } // Prepare Environment let ctx = current_context(); let build_root = ctx.prepare_work_dir()?; // Ensure availability of all needed files for the build let remote_dsc_path = upload_package_files(&ctx, &files_to_ensure, &build_root, &dsc_path)?; println!( "Building {} on {}...", remote_dsc_path.display(), build_root ); // Run sbuild run_sbuild(&ctx, &remote_dsc_path, arch, series, &build_root)?; // Retrieve artifacts // Always retrieve to the directory containing the .dsc file let local_output_dir = dsc_path .parent() .ok_or("Could not determine parent directory of dsc file")?; println!("Retrieving artifacts to {}...", local_output_dir.display()); // Only retrieve .deb files let remote_files = ctx.list_files(Path::new(&build_root))?; for remote_file in remote_files { if remote_file.extension().is_some_and(|ext| ext == "deb") { let file_name = remote_file.file_name().ok_or("Invalid remote filename")?; let local_dest = local_output_dir.join(file_name); ctx.retrieve_path(&remote_file, &local_dest)?; } } Ok(()) } fn find_dsc_file(cwd: &Path, package: &str, version: &str) -> Result> { let parent = cwd.parent().ok_or("Cannot find parent directory")?; let dsc_name = format!("{}_{}.dsc", package, version); let dsc_path = parent.join(&dsc_name); if !dsc_path.exists() { return Err(format!("Could not find .dsc file at {}", dsc_path.display()).into()); } Ok(dsc_path) } fn get_dsc_related_files(dsc_path: &Path) -> Result, Box> { let content = std::fs::read_to_string(dsc_path)?; let parent = dsc_path.parent().unwrap(); // dsc_path exists so parent exists let mut files = Vec::new(); let mut in_files = false; for line in content.lines() { if line.starts_with("Files:") { in_files = true; continue; } if in_files { if line.starts_with(' ') { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() >= 3 { let filename = parts[2]; let filepath = parent.join(filename); if filepath.exists() { files.push(filepath); } else { return Err( format!("Referenced file {} not found", filepath.display()).into() ); } } } else { in_files = false; } } } Ok(files) } fn upload_package_files( ctx: &Context, files: &[PathBuf], dest_root: &str, local_dsc_path: &Path, ) -> Result> { let mut remote_dsc_path = PathBuf::new(); for file in files { let remote_path = ctx.ensure_available(file, dest_root)?; // Check if this is the dsc file by comparing file names if let (Some(f_name), Some(dsc_name)) = (file.file_name(), local_dsc_path.file_name()) && f_name == dsc_name { remote_dsc_path = remote_path; } } if remote_dsc_path.as_os_str().is_empty() { return Err("Failed to determine remote path for .dsc file".into()); } Ok(remote_dsc_path) } fn run_sbuild( ctx: &Context, dsc_path: &Path, arch: Option<&str>, series: Option<&str>, output_dir: &str, ) -> Result<(), Box> { let mut cmd = ctx.command("sbuild"); cmd.arg("--chroot-mode=unshare"); if let Some(a) = arch { cmd.arg(format!("--arch={}", a)); } if let Some(s) = series { cmd.arg(format!("--dist={}", s)); } // Add output directory argument cmd.arg(format!("--build-dir={}", output_dir)); let status = cmd.arg(dsc_path).status()?; if !status.success() { return Err(format!("sbuild failed with status: {}", status).into()); } Ok(()) }