deb: drive the cross-build repository setup from the distro data

This commit is contained in:
2026-09-18 13:58:27 +02:00
parent 52d5ad064f
commit ccd2e37385
+114 -59
View File
@@ -67,8 +67,41 @@ pub fn setup_environment(
Ok(()) Ok(())
} }
/// The suites a cross-build environment enables for `series`: the series
/// itself plus the distro data's cross pockets (`<series>-updates`,
/// `<series>-backports`, `<series>-security`), plus the explicitly
/// requested `pocket` when one is given ('proposed' stays opt-in exactly
/// this way — it is not a cross pocket). Shared by the source-adjusting
/// pass and the added mirror entry, which used to duplicate the list.
fn cross_suites(
series: &str,
pocket: Option<&str>,
dist: &str,
) -> Result<Vec<String>, Box<dyn Error>> {
let mut suites = vec![series.to_string()];
for p in crate::distro_info::get_cross_pockets(dist)? {
suites.push(format!("{series}-{p}"));
}
if let Some(p) = pocket {
let pocket_suite = format!("{series}-{p}");
if !suites.contains(&pocket_suite) {
suites.push(pocket_suite);
}
}
Ok(suites)
}
/// Ensure that repositories for target architecture are available /// Ensure that repositories for target architecture are available
/// This also handles the 'ports.ubuntu.com' vs 'archive.ubuntu.com' on Ubuntu ///
/// On Ubuntu hosts, driven by the bundled distro data
/// (`data/distro_info.yml`): the official sources served by the mirror of
/// the local architecture (the primary archive and its security sibling)
/// are scoped to it and carry every component and cross-build suite, and
/// the mirror serving the target architecture (ports, for the non-local
/// ones) is added when no existing source serves the arch from it yet.
/// Debian hosts are left alone: one mirror serves every architecture, so
/// the host's own sources already cover the target — the os-release gate
/// below is what makes that a data conclusion instead of hardcoding.
pub fn ensure_repositories( pub fn ensure_repositories(
arch: &str, arch: &str,
series: &str, series: &str,
@@ -97,90 +130,78 @@ pub fn ensure_repositories(
if !os_release.contains("ID=ubuntu") { if !os_release.contains("ID=ubuntu") {
return Ok(()); return Ok(());
} }
let dist = "ubuntu";
// Load existing sources // Load existing sources
let mut sources = crate::apt::sources::load(Some(ctx.clone()))?; let mut sources = crate::apt::sources::load(Some(ctx.clone()))?;
// The mirrors serving each side of the cross build (primary for the
// local architectures, ports for the others) and the distro data's
// components and suites
let local_mirror = crate::distro_info::mirror_for_arch(dist, &local_arch)?;
let target_mirror = crate::distro_info::mirror_for_arch(dist, arch)?;
let components = crate::distro_info::get_dist_components(dist)?;
let required_suites = cross_suites(series, pocket, dist)?;
// Ensure all components are enabled for the primary architecture // Ensure all components are enabled for the primary architecture
for source in &mut sources { for source in &mut sources {
if source.uri.contains("archive.ubuntu.com") || source.uri.contains("security.ubuntu.com") { // Official sources served by the local mirror (the primary archive
// Scope to local_arch if not already scoped // and its security sibling); ports serves the other architectures
if source.architectures.is_empty() { // and is configured below instead
source.architectures.push(local_arch.clone()); if !crate::distro_info::is_mirror_source(local_mirror, &source.uri) {
} continue;
}
// Ensure all components are present // Scope to local_arch if not already scoped
let required_components = ["main", "restricted", "universe", "multiverse"]; if source.architectures.is_empty() {
for &comp in &required_components { source.architectures.push(local_arch.clone());
if !source.components.contains(&comp.to_string()) { }
source.components.push(comp.to_string());
}
}
// Ensure all suites (pockets) are enabled, excluding 'proposed' // Ensure all components are present
// unless explicitly requested through the 'pocket' option for comp in &components {
let mut required_suites = vec![ if !source.components.contains(comp) {
series.to_string(), source.components.push(comp.clone());
format!("{}-updates", series),
format!("{}-backports", series),
format!("{}-security", series),
];
if let Some(p) = pocket {
let pocket_suite = format!("{series}-{p}");
if !required_suites.contains(&pocket_suite) {
required_suites.push(pocket_suite);
}
} }
for suite in required_suites { }
if !source.suite.contains(&suite) {
source.suite.push(suite); // Ensure all suites (pockets) are enabled
} for suite in &required_suites {
if !source.suite.contains(suite) {
source.suite.push(suite.clone());
} }
} }
} }
// Check if ports repository already exists for the target architecture // Check whether an existing source already serves the target
let has_ports = sources // architecture from its mirror (e.g. the ports mirror for a
.iter() // non-local arch); when cross-building for the local architecture,
.any(|s| s.uri.contains("ports.ubuntu.com") && s.architectures.contains(&arch.to_string())); // the primary sources above already do
let has_target = sources.iter().any(|s| {
crate::distro_info::is_mirror_source(target_mirror, &s.uri)
&& s.architectures.contains(&arch.to_string())
});
if !has_ports { if !has_target {
// Add ports repository for the target architecture // Add the target architecture's mirror (ports for the non-local
let mut ports_suites = vec![ // architectures on Ubuntu)
series.to_string(), let mirror_entry = crate::apt::sources::SourceEntry {
format!("{series}-updates"),
format!("{series}-backports"),
format!("{series}-security"),
];
if let Some(p) = pocket {
let pocket_suite = format!("{series}-{p}");
if !ports_suites.contains(&pocket_suite) {
ports_suites.push(pocket_suite);
}
}
let ports_entry = crate::apt::sources::SourceEntry {
enabled: true, enabled: true,
kind: crate::apt::sources::SourceKind::Deb, kind: crate::apt::sources::SourceKind::Deb,
components: vec![ components: components.clone(),
"main".to_string(),
"restricted".to_string(),
"universe".to_string(),
"multiverse".to_string(),
],
architectures: vec![arch.to_string()], architectures: vec![arch.to_string()],
uri: "http://ports.ubuntu.com/ubuntu-ports".to_string(), uri: target_mirror.url.clone(),
signed_by: None, signed_by: None,
trusted: None, trusted: None,
suite: ports_suites, suite: required_suites.clone(),
// No origin: saved to the pkh-owned added-sources file // No origin: saved to the pkh-owned added-sources file
origin: None, origin: None,
}; };
sources.push(ports_entry); sources.push(mirror_entry);
} }
// Save the updated sources: each entry is written back to its origin // Save the updated sources: each entry is written back to its origin
// file in its own format (keeping its own Signed-By and Enabled state), // file in its own format (keeping its own Signed-By and Enabled state),
// and the new ports entry goes to the pkh-owned added-sources file // and the new mirror entry goes to the pkh-owned added-sources file
crate::apt::sources::save(Some(ctx.clone()), sources)?; crate::apt::sources::save(Some(ctx.clone()), sources)?;
Ok(()) Ok(())
@@ -234,4 +255,38 @@ mod tests {
); );
assert_eq!(env.len(), 2); assert_eq!(env.len(), 2);
} }
/// The suite list a cross-build environment enables: the series, its
/// cross pockets from the distro data (updates, backports, security),
/// and the explicitly requested pocket — which is the only way
/// 'proposed' gets in.
#[test]
fn test_cross_suites_from_distro_data() {
assert_eq!(
cross_suites("noble", None, "ubuntu").unwrap(),
vec![
"noble".to_string(),
"noble-updates".to_string(),
"noble-backports".to_string(),
"noble-security".to_string()
]
);
// An explicitly requested pocket is added (not duplicated when it
// is already a cross pocket).
assert_eq!(
cross_suites("noble", Some("proposed"), "ubuntu").unwrap(),
vec![
"noble".to_string(),
"noble-updates".to_string(),
"noble-backports".to_string(),
"noble-security".to_string(),
"noble-proposed".to_string()
]
);
assert_eq!(
cross_suites("noble", Some("updates"), "ubuntu").unwrap(),
cross_suites("noble", None, "ubuntu").unwrap()
);
assert!(cross_suites("noble", None, "not-a-distro").is_err());
}
} }