deb: cross uses new apt sources parser

This commit is contained in:
2026-01-10 00:46:04 +01:00
parent d2914c63c6
commit 64b51563e7
2 changed files with 128 additions and 157 deletions

View File

@@ -16,8 +16,8 @@ pub struct SourceEntry {
pub architectures: Vec<String>,
/// Source URI
pub uri: String,
/// Source suite (series-pocket)
pub suite: String,
/// Source suites (series-pocket)
pub suite: Vec<String>,
}
impl SourceEntry {
@@ -28,7 +28,7 @@ impl SourceEntry {
components: Vec::new(),
architectures: Vec::new(),
uri: String::new(),
suite: String::new(),
suite: Vec::new(),
};
for line in data.lines() {
@@ -55,7 +55,10 @@ impl SourceEntry {
// We only care about deb types
}
"URIs" => current_entry.uri = value.to_string(),
"Suites" => current_entry.suite = value.to_string(),
"Suites" => {
current_entry.suite =
value.split_whitespace().map(|s| s.to_string()).collect();
}
"Components" => {
current_entry.components =
value.split_whitespace().map(|s| s.to_string()).collect();
@@ -123,7 +126,7 @@ impl SourceEntry {
}
let uri = parts[1].to_string();
let suite = parts[2].to_string();
let suite = vec![parts[2].to_string()];
let components: Vec<String> = parts[3..].iter().map(|&s| s.to_string()).collect();
Some(SourceEntry {
@@ -139,26 +142,31 @@ impl SourceEntry {
pub fn to_legacy(&self) -> String {
let mut result = String::new();
// Start with "deb" type
result.push_str("deb");
// Legacy entries contain one suite per line
for suite in &self.suite {
// Start with "deb" type
result.push_str("deb");
// Add architectures if present
if !self.architectures.is_empty() {
result.push_str(" [arch=");
result.push_str(&self.architectures.join(","));
result.push(']');
}
// Add architectures if present
if !self.architectures.is_empty() {
result.push_str(" [arch=");
result.push_str(&self.architectures.join(","));
result.push(']');
}
// Add URI and suite
result.push(' ');
result.push_str(&self.uri);
result.push(' ');
result.push_str(&self.suite);
// Add components
if !self.components.is_empty() {
// Add URI and suite
result.push(' ');
result.push_str(&self.components.join(" "));
result.push_str(&self.uri);
result.push(' ');
result.push_str(suite);
// Add components
if !self.components.is_empty() {
result.push(' ');
result.push_str(&self.components.join(" "));
}
result.push('\n');
}
result
@@ -275,12 +283,17 @@ mod tests {
assert_eq!(sources.len(), 3);
assert_eq!(sources[0].uri, "http://fr.archive.ubuntu.com/ubuntu/");
assert_eq!(sources[0].architectures, vec!["amd64"]);
assert_eq!(
sources[0].suite,
vec!["questing", "questing-updates", "questing-backports"]
);
assert_eq!(
sources[0].components,
vec!["main", "restricted", "universe", "multiverse"]
);
assert_eq!(sources[1].uri, "http://security.ubuntu.com/ubuntu/");
assert_eq!(sources[1].architectures, vec!["amd64"]);
assert_eq!(sources[1].suite, vec!["questing-security"]);
assert_eq!(
sources[1].components,
vec!["main", "restricted", "universe", "multiverse"]
@@ -288,6 +301,10 @@ mod tests {
assert_eq!(sources[2].uri, "http://ports.ubuntu.com/ubuntu-ports/");
assert_eq!(sources[2].architectures.len(), 1);
assert_eq!(sources[2].architectures, vec!["riscv64"]);
assert_eq!(
sources[2].suite,
vec!["questing", "questing-updates", "questing-backports"]
);
assert_eq!(
sources[2].components,
vec!["main", "restricted", "universe", "multiverse"]
@@ -305,15 +322,15 @@ mod tests {
let sources = parse_legacy(legacy);
assert_eq!(sources.len(), 3);
assert_eq!(sources[0].uri, "http://archive.ubuntu.com/ubuntu");
assert_eq!(sources[0].suite, "resolute");
assert_eq!(sources[0].suite, vec!["resolute"]);
assert_eq!(sources[0].components, vec!["main", "universe"]);
assert_eq!(sources[0].architectures, vec!["amd64"]);
assert_eq!(sources[1].uri, "http://archive.ubuntu.com/ubuntu");
assert_eq!(sources[1].suite, "resolute-updates");
assert_eq!(sources[1].suite, vec!["resolute-updates"]);
assert_eq!(sources[1].components, vec!["main"]);
assert_eq!(sources[1].architectures, vec!["amd64", "i386"]);
assert_eq!(sources[2].uri, "http://security.ubuntu.com/ubuntu");
assert_eq!(sources[2].suite, "resolute-security");
assert_eq!(sources[2].suite, vec!["resolute-security"]);
assert_eq!(sources[2].components, vec!["main"]);
}
}

View File

@@ -51,145 +51,99 @@ pub fn ensure_repositories(arch: &str, series: &str) -> Result<(), Box<dyn Error
return Ok(());
}
// Handle DEB822 format (Ubuntu 24.04+)
// Load existing sources
let mut sources = crate::apt::sources::load(Some(ctx.clone()))?;
// 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());
}
// 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());
}
}
// Ensure all suites (pockets) are enabled, excluding 'proposed'
let required_suites = [
series.to_string(),
format!("{}-updates", series),
format!("{}-backports", series),
format!("{}-security", series),
];
for suite in required_suites {
if !source.suite.contains(&suite) {
source.suite.push(suite);
}
}
}
}
// 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()));
if !has_ports {
// Add ports repository for the target architecture
let ports_entry = crate::apt::sources::SourceEntry {
enabled: true,
components: vec![
"main".to_string(),
"restricted".to_string(),
"universe".to_string(),
"multiverse".to_string(),
],
architectures: vec![arch.to_string()],
uri: "http://ports.ubuntu.com/ubuntu-ports".to_string(),
suite: vec![
format!("{series}"),
format!("{series}-updates"),
format!("{series}-backports"),
format!("{series}-security"),
],
};
sources.push(ports_entry);
}
// Save the updated sources
// Try to save in DEB822 format first, fall back to legacy format
let deb822_path = "/etc/apt/sources.list.d/ubuntu.sources";
let has_deb822 = ctx
if ctx
.command("test")
.arg("-f")
.arg(deb822_path)
.status()?
.success();
if has_deb822 {
ensure_repositories_deb822(&ctx, arch, &local_arch, series, deb822_path)?;
} else {
ensure_repositories_legacy(&ctx, arch, &local_arch, series, "/etc/apt/sources.list")?;
}
Ok(())
}
fn ensure_repositories_deb822(
ctx: &context::Context,
arch: &str,
local_arch: &str,
series: &str,
deb822_path: &str,
) -> Result<(), Box<dyn Error>> {
// Scope existing to local_arch if not already scoped
ctx.command("sed")
.arg("-i")
.arg(format!("/URIs:.*\\(archive\\|security\\)\\.ubuntu\\.com/ {{ n; /^Architectures:/ ! i Architectures: {} }}", local_arch))
.arg(deb822_path)
.status()?;
// Ensure all components are enabled for the primary architecture
ctx.command("sed")
.arg("-i")
.arg("/URIs:.*\\(archive\\|security\\)\\.ubuntu\\.com/,/Components:/ s/^Components:.*/Components: main restricted universe multiverse/")
.arg(deb822_path)
.status()?;
// Ensure all suites (pockets) are enabled for the primary architecture
// Excluding 'proposed' as it contains unstable software
let suites = format!("{series} {series}-updates {series}-backports {series}-security");
ctx.command("sed")
.arg("-i")
.arg(format!(
"/URIs:.*\\(archive\\|security\\)\\.ubuntu\\.com/,/Suites:/ s/^Suites:.*/Suites: {}/",
suites
))
.arg(deb822_path)
.status()?;
// Add ports if not already present
let has_ports = ctx
.command("grep")
.arg("-q")
.arg("ports.ubuntu.com")
.arg(deb822_path)
.status()?
.success();
if !has_ports {
let ports_block = format!(
"\nTypes: deb\nURIs: http://ports.ubuntu.com/ubuntu-ports\nSuites: {series} {series}-updates {series}-backports {series}-security\nComponents: main restricted universe multiverse\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nArchitectures: {arch}\n"
);
ctx.command("sh")
.arg("-c")
.arg(format!("echo '{}' >> {}", ports_block, deb822_path))
.status()?;
}
Ok(())
}
fn ensure_repositories_legacy(
ctx: &context::Context,
arch: &str,
local_arch: &str,
series: &str,
sources_path: &str,
) -> Result<(), Box<dyn Error>> {
// Scope archive.ubuntu.com and security.ubuntu.com to local_arch if not already scoped
ctx.command("sed")
.arg("-i")
.arg(format!(
r"/archive.ubuntu.com\|security.ubuntu.com/ {{ /arch=/ ! {{ /^deb \[/ ! s/^deb /deb [arch={}] /; /^deb \[/ s/^deb \[\([^]]*\)\]/deb [arch={} \1]/ }} }}",
local_arch, local_arch
))
.arg(sources_path)
.status()?;
// Ensure all components (main restricted universe multiverse) are present for all archive/security lines
ctx.command("sed")
.arg("-i")
.arg(r"/archive.ubuntu.com\|security.ubuntu.com/ s/\( main\)\?\([ ]\+restricted\)\?\([ ]\+universe\)\?\([ ]\+multiverse\)\?$/ main restricted universe multiverse/")
.arg(sources_path)
.status()?;
// Ensure all pockets exist. If not, we append them.
for pocket in ["", "-updates", "-backports", "-security"] {
let suite = format!("{}{}", series, pocket);
let has_suite = ctx
.command("grep")
.arg("-q")
.arg(format!(" {}", suite))
.arg(sources_path)
.status()?
.success();
if !has_suite {
let line = format!(
"deb [arch={}] http://archive.ubuntu.com/ubuntu/ {} main restricted universe multiverse",
local_arch, suite
);
ctx.command("sh")
.arg("-c")
.arg(format!("echo '{}' >> {}", line, sources_path))
.status()?;
.success()
{
// For DEB822 format, we need to reconstruct the file content
let mut content = String::new();
for source in &sources {
if !source.enabled {
continue;
}
content.push_str("Types: deb\n");
content.push_str(&format!("URIs: {}\n", source.uri));
content.push_str(&format!("Suites: {}\n", source.suite.join(" ")));
content.push_str(&format!("Components: {}\n", source.components.join(" ")));
content.push_str("Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\n");
content.push_str(&format!(
"Architectures: {}\n",
source.architectures.join(" ")
));
content.push('\n');
}
ctx.write_file(std::path::Path::new(deb822_path), &content)?;
} else {
// Fall back to legacy format
crate::apt::sources::save_legacy(Some(ctx.clone()), sources, "/etc/apt/sources.list")?;
}
// Add ports repository to sources.list if not already present
let has_ports = ctx
.command("grep")
.arg("-q")
.arg("ports.ubuntu.com")
.arg(sources_path)
.status()?
.success();
if !has_ports {
let ports_lines = format!(
"deb [arch={arch}] http://ports.ubuntu.com/ubuntu-ports {series} main restricted universe multiverse\n\
deb [arch={arch}] http://ports.ubuntu.com/ubuntu-ports {series}-updates main restricted universe multiverse\n\
deb [arch={arch}] http://ports.ubuntu.com/ubuntu-ports {series}-backports main restricted universe multiverse\n\
deb [arch={arch}] http://ports.ubuntu.com/ubuntu-ports {series}-security main restricted universe multiverse"
);
ctx.command("sh")
.arg("-c")
.arg(format!("echo '{}' >> {}", ports_lines, sources_path))
.status()?;
}
Ok(())
}