deb: scope the arch-indep build-dep pass to the host arch in cross builds
CI / build (push) Successful in 2m49s
CI / test (push) Skipped
CI / snap (push) Failing after 11s

Without --host-architecture, the second build-dep pass re-resolves the
whole Build-Depends field for the native architecture: apt swaps
host-arch -dev packages for native ones (e.g. libcurl4-gnutls-dev,
whose arch-differing curl-config makes dpkg refuse the co-install) and
breaks the cross build environment.

Per dpkg-checkbuilddeps, both Build-Depends and Build-Depends-Indep
resolve for the host architecture in cross mode, so pass
--host-architecture to the second pass as well. Skip the pass entirely
when the source declares no Build-Depends-Indep.

Add an end-to-end regression test building a package that declares
libdb-dev in both fields and links a host-arch binary against it: the
test only passes if the arch-indep pass did not swap the arm64 -dev
packages for native ones.
This commit is contained in:
2026-09-15 18:05:57 +02:00
parent 5500f98586
commit b34e86dcfe
2 changed files with 148 additions and 16 deletions
+22 -9
View File
@@ -276,18 +276,30 @@ pub async fn build(
return Err("Could not install build-dependencies for the build".into()); return Err("Could not install build-dependencies for the build".into());
} }
// Install arch-independant build dependencies // Install arch-independant build dependencies, only if the source declares
// any: without --arch-only this pass resolves the whole Build-Depends field
// too, which is redundant after the first pass and breaks cross builds.
let has_indep_deps = match ctx.read_file(&package_dir.join("debian/control")) {
Ok(control) => control
.lines()
.any(|l| l.to_ascii_lowercase().starts_with("build-depends-indep:")),
Err(e) => {
log::debug!("cannot read debian/control for indep build-deps: {}", e);
true
}
};
if has_indep_deps {
log::debug!("Installing arch-independant build dependencies..."); log::debug!("Installing arch-independant build dependencies...");
let status = cap( let mut cmd = ctx.command("apt-get");
ctx.command("apt-get") cmd.current_dir(package_dir_str)
.current_dir(package_dir_str)
.envs(env.clone()) .envs(env.clone())
.arg("-y") .arg("-y")
.arg("build-dep") .arg("build-dep");
.arg("./"), if cross {
&sink, cmd.arg(format!("--host-architecture={arch}"));
) }
.status()?; cmd.arg("./");
let status = cap(&mut cmd, &sink).status()?;
// If build-dep fails, we try to explain the failure using dose-debcheck // If build-dep fails, we try to explain the failure using dose-debcheck
if !status.success() { if !status.success() {
@@ -297,6 +309,7 @@ pub async fn build(
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?; dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
return Err("Could not install build-dependencies for the build".into()); return Err("Could not install build-dependencies for the build".into());
} }
}
// Run the build step // Run the build step
log::debug!("Building (debian/rules build) package..."); log::debug!("Building (debian/rules build) package...");
+119
View File
@@ -492,4 +492,123 @@ mod tests {
async fn test_deb_gcc_debian_end_to_end() { async fn test_deb_gcc_debian_end_to_end() {
test_build_end_to_end("gcc-15", "sid", None, None, false).await; test_build_end_to_end("gcc-15", "sid", None, None, false).await;
} }
/// Create a synthetic source package that discriminates which architecture
/// is used to resolve Build-Depends-Indep during cross builds:
///
/// - 'libdb-dev' is an arch:any package that is not Multi-Arch: same, so an
/// amd64 copy can only be installed by replacing the arm64 one
/// - the arch-specific binary links against libdb for the host
/// architecture, so the build only succeeds if the arm64 libdb-dev was
/// left in place by the arch-independant build-dep pass
fn create_indep_cross_test_source(parent: &Path) -> PathBuf {
let pkg_dir = parent.join("pkh-crosstest");
std::fs::create_dir_all(pkg_dir.join("debian/source")).unwrap();
std::fs::write(
pkg_dir.join("debian/changelog"),
"pkh-crosstest (1.0) noble; urgency=medium\n\n \
* Synthetic package exercising Build-Depends-Indep in cross builds.\n\n \
-- pkh tests <pkh@example.com> Tue, 15 Sep 2026 08:00:00 +0000\n",
)
.unwrap();
std::fs::write(
pkg_dir.join("debian/control"),
"Source: pkh-crosstest\n\
Section: devel\n\
Priority: optional\n\
Maintainer: pkh tests <pkh@example.com>\n\
Standards-Version: 4.7.4\n\
Build-Depends: debhelper-compat (= 13), libdb-dev\n\
Build-Depends-Indep: libdb-dev\n\
Architecture: any all\n\
\n\
Package: pkh-crosstest\n\
Architecture: any\n\
Depends: ${misc:Depends}, ${shlibs:Depends}\n\
Description: Cross-build regression package for build-dep resolution\n \
Builds a host-architecture binary against libdb to detect a cross\n \
build environment damaged by a wrongly-scoped build-dep pass.\n\
\n\
Package: pkh-crosstest-data\n\
Architecture: all\n\
Description: Cross-build regression package data (arch-indep)\n \
Arch-indep binary so the indep build path is exercised.\n",
)
.unwrap();
std::fs::write(
pkg_dir.join("debian/rules"),
"#!/usr/bin/make -f\n\
%:\n\
\tdh $@\n\
\n\
override_dh_auto_build:\n\
\tprintf '#include <db.h>\\nint main(void){DB *d; return db_create(&d, NULL, 0);}\\n' > main.c\n\
\t$(DEB_HOST_GNU_TYPE)-gcc main.c -ldb -o pkh-crosstest\n",
)
.unwrap();
use std::os::unix::fs::PermissionsExt;
let rules = pkg_dir.join("debian/rules");
let mut perms = std::fs::metadata(&rules).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&rules, perms).unwrap();
std::fs::write(pkg_dir.join("debian/source/format"), "3.0 (native)\n").unwrap();
pkg_dir
}
/// This ensures the arch-independant build-dep pass of a cross build
/// resolves dependencies for the host architecture, like dpkg-checkbuilddeps
/// does, instead of re-resolving the whole Build-Depends field for the
/// native architecture, which swaps host-arch -dev packages for native ones
/// and breaks the cross build environment.
#[tokio::test]
#[test_log::test]
#[cfg(target_arch = "x86_64")]
async fn test_deb_cross_indep_host_arch_end_to_end() {
let temp_dir = tempfile::tempdir().unwrap();
let pkg_dir = create_indep_cross_test_source(temp_dir.path());
let ctx = Arc::new(Context::new(crate::context::ContextConfig::Local));
crate::deb::build_binary_package(
Some("arm64"),
Some("noble"),
None,
Some(&pkg_dir),
true,
None,
None,
None,
Some(ctx),
None,
None,
)
.await
.expect("Cannot cross-build package declaring Build-Depends-Indep");
// Both binary packages must have been produced, including the
// arch-independant one
let deb_files: Vec<String> = std::fs::read_dir(temp_dir.path())
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().to_string())
.collect();
assert!(
deb_files
.iter()
.any(|f| f.starts_with("pkh-crosstest_1.0_arm64.deb")),
"arch-specific .deb not produced, got: {deb_files:?}"
);
assert!(
deb_files
.iter()
.any(|f| f.starts_with("pkh-crosstest-data_1.0_all.deb")),
"arch-independant .deb not produced, got: {deb_files:?}"
);
}
} }