From 7c53b268dd3c025fc972d709746a429b8e82c2dd Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Wed, 11 Feb 2026 11:58:59 +0100 Subject: [PATCH] deb: use qemu_binfmt on ephemeral/local builds of different arch --- src/deb/ephemeral.rs | 62 ++++++++++++++++++++++++++++++++------------ src/deb/mod.rs | 9 ++++++- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/deb/ephemeral.rs b/src/deb/ephemeral.rs index c11c125..971013b 100644 --- a/src/deb/ephemeral.rs +++ b/src/deb/ephemeral.rs @@ -17,7 +17,12 @@ pub struct EphemeralContextGuard { impl EphemeralContextGuard { /// Create a new ephemeral unshare context for the specified series - pub async fn new(series: &str) -> Result> { + /// + /// # Arguments + /// * `series` - The distribution series (e.g., "noble", "sid") + /// * `arch` - Optional target architecture. If provided and different from host, + /// downloads a chroot for that architecture (uses qemu_binfmt transparently) + pub async fn new(series: &str, arch: Option<&str>) -> Result> { let current_context_name = context::manager().current_name(); // Create a temporary directory for the chroot @@ -25,13 +30,14 @@ impl EphemeralContextGuard { let chroot_path = PathBuf::from(chroot_path_str); log::debug!( - "Creating new chroot for {} at {}...", + "Creating new chroot for {} (arch: {:?}) at {}...", series, + arch, chroot_path.display() ); // Download and extract the chroot tarball - Self::download_and_extract_chroot(series, &chroot_path).await?; + Self::download_and_extract_chroot(series, arch, &chroot_path).await?; // Switch to an ephemeral context to build the package in the chroot context::manager().set_current_ephemeral(Context::new(ContextConfig::Unshare { @@ -48,6 +54,7 @@ impl EphemeralContextGuard { async fn download_and_extract_chroot( series: &str, + arch: Option<&str>, chroot_path: &PathBuf, ) -> Result<(), Box> { // Get project directories for caching @@ -56,8 +63,12 @@ impl EphemeralContextGuard { let cache_dir = proj_dirs.cache_dir(); fs::create_dir_all(cache_dir)?; - // Create tarball filename based on series - let tarball_filename = format!("{}-buildd.tar.xz", series); + // Create tarball filename based on series and architecture + let tarball_filename = if let Some(a) = arch { + format!("{}-{}-buildd.tar.xz", series, a) + } else { + format!("{}-buildd.tar.xz", series) + }; let tarball_path = cache_dir.join(&tarball_filename); // Check for existing lockfile, and wait for a timeout if it exists @@ -94,10 +105,18 @@ impl EphemeralContextGuard { // Download tarball if it doesn't exist if !tarball_path.exists() { - log::debug!("Downloading chroot tarball for {}...", series); - Self::download_chroot_tarball(series, &tarball_path).await?; + log::debug!( + "Downloading chroot tarball for {} (arch: {:?})...", + series, + arch + ); + Self::download_chroot_tarball(series, arch, &tarball_path).await?; } else { - log::debug!("Using cached chroot tarball for {}", series); + log::debug!( + "Using cached chroot tarball for {} (arch: {:?})", + series, + arch + ); } // Extract tarball to chroot directory @@ -113,6 +132,7 @@ impl EphemeralContextGuard { async fn download_chroot_tarball( series: &str, + arch: Option<&str>, tarball_path: &Path, ) -> Result<(), Box> { let ctx = context::current(); @@ -134,15 +154,21 @@ impl EphemeralContextGuard { } // Use mmdebstrap to download the tarball to the cache directory - let status = ctx - .command("mmdebstrap") - .arg("--variant=buildd") + let mut cmd = ctx.command("mmdebstrap"); + cmd.arg("--variant=buildd") .arg("--mode=unshare") .arg("--include=mount,curl,ca-certificates") - .arg("--format=tar") - .arg(series) - .arg(tarball_path.to_string_lossy().to_string()) - .status()?; + .arg("--format=tar"); + + // Add architecture if specified + if let Some(a) = arch { + cmd.arg(format!("--arch={}", a)); + } + + cmd.arg(series) + .arg(tarball_path.to_string_lossy().to_string()); + + let status = cmd.status()?; if !status.success() { // Remove file on error @@ -156,7 +182,11 @@ impl EphemeralContextGuard { .arg("-f") .arg(lockfile_path.to_string_lossy().to_string()) .status(); - return Err(format!("Failed to download chroot tarball for series {}", series).into()); + return Err(format!( + "Failed to download chroot tarball for series {} (arch: {:?})", + series, arch + ) + .into()); } // Remove lockfile: tarball is fully downloaded diff --git a/src/deb/mod.rs b/src/deb/mod.rs index f40d44d..33a6e5d 100644 --- a/src/deb/mod.rs +++ b/src/deb/mod.rs @@ -49,8 +49,15 @@ pub async fn build_binary_package( }; // Create an ephemeral unshare context for all Local builds + // Use qemu_binfmt when target architecture differs from host and cross is not requested + let chroot_arch = if mode == BuildMode::Local && arch != current_arch && !cross { + Some(arch) + } else { + None + }; + let mut guard = if mode == BuildMode::Local { - Some(ephemeral::EphemeralContextGuard::new(series).await?) + Some(ephemeral::EphemeralContextGuard::new(series, chroot_arch).await?) } else { None };