deb: scope the arch-indep build-dep pass to the host arch in cross builds
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:
+29
-16
@@ -276,26 +276,39 @@ pub async fn build(
|
||||
return Err("Could not install build-dependencies for the build".into());
|
||||
}
|
||||
|
||||
// Install arch-independant build dependencies
|
||||
log::debug!("Installing arch-independant build dependencies...");
|
||||
let status = cap(
|
||||
ctx.command("apt-get")
|
||||
.current_dir(package_dir_str)
|
||||
// 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...");
|
||||
let mut cmd = ctx.command("apt-get");
|
||||
cmd.current_dir(package_dir_str)
|
||||
.envs(env.clone())
|
||||
.arg("-y")
|
||||
.arg("build-dep")
|
||||
.arg("./"),
|
||||
&sink,
|
||||
)
|
||||
.status()?;
|
||||
.arg("build-dep");
|
||||
if cross {
|
||||
cmd.arg(format!("--host-architecture={arch}"));
|
||||
}
|
||||
cmd.arg("./");
|
||||
let status = cap(&mut cmd, &sink).status()?;
|
||||
|
||||
// If build-dep fails, we try to explain the failure using dose-debcheck
|
||||
if !status.success() {
|
||||
if let Some(u) = &ui {
|
||||
u.suspend();
|
||||
// If build-dep fails, we try to explain the failure using dose-debcheck
|
||||
if !status.success() {
|
||||
if let Some(u) = &ui {
|
||||
u.suspend();
|
||||
}
|
||||
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
|
||||
return Err("Could not install build-dependencies for the build".into());
|
||||
}
|
||||
dose3_explain_dependencies(package, version, arch, build_root, cross, ctx.clone())?;
|
||||
return Err("Could not install build-dependencies for the build".into());
|
||||
}
|
||||
|
||||
// Run the build step
|
||||
|
||||
+119
@@ -492,4 +492,123 @@ mod tests {
|
||||
async fn test_deb_gcc_debian_end_to_end() {
|
||||
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:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user