diff --git a/src/deb/cross.rs b/src/deb/cross.rs index 004b2bd..84e39a8 100644 --- a/src/deb/cross.rs +++ b/src/deb/cross.rs @@ -67,8 +67,41 @@ pub fn setup_environment( Ok(()) } +/// The suites a cross-build environment enables for `series`: the series +/// itself plus the distro data's cross pockets (`-updates`, +/// `-backports`, `-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, Box> { + 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 -/// 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( arch: &str, series: &str, @@ -97,90 +130,78 @@ pub fn ensure_repositories( if !os_release.contains("ID=ubuntu") { return Ok(()); } + let dist = "ubuntu"; // Load existing sources 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 for source in &mut sources { - if source.uri.contains("archive.ubuntu.com") || source.uri.contains("security.ubuntu.com") { - // Scope to local_arch if not already scoped - if source.architectures.is_empty() { - source.architectures.push(local_arch.clone()); - } + // Official sources served by the local mirror (the primary archive + // and its security sibling); ports serves the other architectures + // and is configured below instead + if !crate::distro_info::is_mirror_source(local_mirror, &source.uri) { + continue; + } - // Ensure all components are present - let required_components = ["main", "restricted", "universe", "multiverse"]; - for &comp in &required_components { - if !source.components.contains(&comp.to_string()) { - source.components.push(comp.to_string()); - } - } + // Scope to local_arch if not already scoped + if source.architectures.is_empty() { + source.architectures.push(local_arch.clone()); + } - // Ensure all suites (pockets) are enabled, excluding 'proposed' - // unless explicitly requested through the 'pocket' option - let mut required_suites = vec![ - series.to_string(), - 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); - } + // Ensure all components are present + for comp in &components { + if !source.components.contains(comp) { + source.components.push(comp.clone()); } - 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 - let has_ports = sources - .iter() - .any(|s| s.uri.contains("ports.ubuntu.com") && s.architectures.contains(&arch.to_string())); + // Check whether an existing source already serves the target + // architecture from its mirror (e.g. the ports mirror for a + // non-local arch); when cross-building for the local architecture, + // 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 { - // Add ports repository for the target architecture - let mut ports_suites = vec![ - series.to_string(), - 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 { + if !has_target { + // Add the target architecture's mirror (ports for the non-local + // architectures on Ubuntu) + let mirror_entry = crate::apt::sources::SourceEntry { enabled: true, kind: crate::apt::sources::SourceKind::Deb, - components: vec![ - "main".to_string(), - "restricted".to_string(), - "universe".to_string(), - "multiverse".to_string(), - ], + components: components.clone(), architectures: vec![arch.to_string()], - uri: "http://ports.ubuntu.com/ubuntu-ports".to_string(), + uri: target_mirror.url.clone(), signed_by: None, trusted: None, - suite: ports_suites, + suite: required_suites.clone(), // No origin: saved to the pkh-owned added-sources file origin: None, }; - sources.push(ports_entry); + sources.push(mirror_entry); } // 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), - // 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)?; Ok(()) @@ -234,4 +255,38 @@ mod tests { ); 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()); + } }