deb: ensure universe is enabled on Ubuntu by default
Some checks failed
CI / build (push) Has been cancelled
Some checks failed
CI / build (push) Has been cancelled
Added apt source parser, module apt
This commit is contained in:
319
src/apt.rs
Normal file
319
src/apt.rs
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
/// APT sources.list management
|
||||||
|
/// Provides a simple structure for managing APT repository sources
|
||||||
|
use crate::context;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Represents a single source entry in sources.list
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SourceEntry {
|
||||||
|
/// Is the source enabled?
|
||||||
|
pub enabled: bool,
|
||||||
|
/// Source components (universe, main, contrib)
|
||||||
|
pub components: Vec<String>,
|
||||||
|
/// Source architectures (amd64, riscv64, arm64)
|
||||||
|
pub architectures: Vec<String>,
|
||||||
|
/// Source URI
|
||||||
|
pub uri: String,
|
||||||
|
/// Source suite (series-pocket)
|
||||||
|
pub suite: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceEntry {
|
||||||
|
/// Parse a string describing a source entry in deb822 format
|
||||||
|
pub fn from_deb822(data: &str) -> Option<Self> {
|
||||||
|
let mut current_entry = SourceEntry {
|
||||||
|
enabled: true,
|
||||||
|
components: Vec::new(),
|
||||||
|
architectures: Vec::new(),
|
||||||
|
uri: String::new(),
|
||||||
|
suite: String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for line in data.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty line: end of an entry, or beginning
|
||||||
|
if line.is_empty() {
|
||||||
|
if !current_entry.uri.is_empty() {
|
||||||
|
return Some(current_entry);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((key, value)) = line.split_once(':') {
|
||||||
|
let key = key.trim();
|
||||||
|
let value = value.trim();
|
||||||
|
|
||||||
|
match key {
|
||||||
|
"Types" => {
|
||||||
|
// We only care about deb types
|
||||||
|
}
|
||||||
|
"URIs" => current_entry.uri = value.to_string(),
|
||||||
|
"Suites" => current_entry.suite = value.to_string(),
|
||||||
|
"Components" => {
|
||||||
|
current_entry.components =
|
||||||
|
value.split_whitespace().map(|s| s.to_string()).collect();
|
||||||
|
}
|
||||||
|
"Architectures" => {
|
||||||
|
current_entry.architectures =
|
||||||
|
value.split_whitespace().map(|s| s.to_string()).collect();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of entry, or empty file?
|
||||||
|
if !current_entry.uri.is_empty() {
|
||||||
|
Some(current_entry)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a line describing a legacy source entry
|
||||||
|
pub fn from_legacy(data: &str) -> Option<Self> {
|
||||||
|
let line = data.lines().next()?.trim();
|
||||||
|
|
||||||
|
if line.is_empty() || line.starts_with("#") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse legacy deb line format: deb [arch=... / signed_by=] uri suite [components...]
|
||||||
|
|
||||||
|
// Extract bracket parameters first
|
||||||
|
let mut architectures = Vec::new();
|
||||||
|
let mut line_without_brackets = line.to_string();
|
||||||
|
|
||||||
|
// Find and process bracket parameters
|
||||||
|
if let Some(start_bracket) = line.find('[')
|
||||||
|
&& let Some(end_bracket) = line.find(']')
|
||||||
|
{
|
||||||
|
let bracket_content = &line[start_bracket + 1..end_bracket];
|
||||||
|
|
||||||
|
// Parse parameters inside brackets
|
||||||
|
for param in bracket_content.split_whitespace() {
|
||||||
|
if param.starts_with("arch=") {
|
||||||
|
let arch_values = param.split('=').nth(1).unwrap_or("");
|
||||||
|
architectures = arch_values
|
||||||
|
.split(',')
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
// signed-by parameter is parsed but not stored
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the bracket section from the line
|
||||||
|
line_without_brackets = line[..start_bracket].to_string() + &line[end_bracket + 1..];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim and split the remaining line
|
||||||
|
let line_without_brackets = line_without_brackets.trim();
|
||||||
|
let parts: Vec<&str> = line_without_brackets.split_whitespace().collect();
|
||||||
|
|
||||||
|
// We need at least: deb, uri, suite
|
||||||
|
if parts.len() < 3 || parts[0] != "deb" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uri = parts[1].to_string();
|
||||||
|
let suite = parts[2].to_string();
|
||||||
|
let components: Vec<String> = parts[3..].iter().map(|&s| s.to_string()).collect();
|
||||||
|
|
||||||
|
Some(SourceEntry {
|
||||||
|
enabled: true,
|
||||||
|
components,
|
||||||
|
architectures,
|
||||||
|
uri,
|
||||||
|
suite,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert this source entry to legacy format
|
||||||
|
pub fn to_legacy(&self) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
// 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 URI and suite
|
||||||
|
result.push(' ');
|
||||||
|
result.push_str(&self.uri);
|
||||||
|
result.push(' ');
|
||||||
|
result.push_str(&self.suite);
|
||||||
|
|
||||||
|
// Add components
|
||||||
|
if !self.components.is_empty() {
|
||||||
|
result.push(' ');
|
||||||
|
result.push_str(&self.components.join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a 'source list' string in deb822 format into a SourceEntry vector
|
||||||
|
pub fn parse_deb822(data: &str) -> Vec<SourceEntry> {
|
||||||
|
data.split("\n\n")
|
||||||
|
.flat_map(SourceEntry::from_deb822)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a 'source list' string in legacy format into a SourceEntry vector
|
||||||
|
pub fn parse_legacy(data: &str) -> Vec<SourceEntry> {
|
||||||
|
data.split("\n")
|
||||||
|
.flat_map(SourceEntry::from_legacy)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load sources from context (or current context by default)
|
||||||
|
pub fn load(ctx: Option<Arc<crate::context::Context>>) -> Result<Vec<SourceEntry>, Box<dyn Error>> {
|
||||||
|
let mut sources = Vec::new();
|
||||||
|
let ctx = ctx.unwrap_or_else(context::current);
|
||||||
|
|
||||||
|
// Try DEB822 format first (Ubuntu 24.04+ and Debian Trixie+)
|
||||||
|
if let Ok(entries) = load_deb822(&ctx, "/etc/apt/sources.list.d/ubuntu.sources") {
|
||||||
|
sources.extend(entries);
|
||||||
|
} else if let Ok(entries) = load_deb822(&ctx, "/etc/apt/sources.list.d/debian.sources") {
|
||||||
|
sources.extend(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to legacy format
|
||||||
|
if let Ok(entries) = load_legacy(&ctx, "/etc/apt/sources.list") {
|
||||||
|
sources.extend(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save sources back to context
|
||||||
|
pub fn save_legacy(
|
||||||
|
ctx: Option<Arc<crate::context::Context>>,
|
||||||
|
sources: Vec<SourceEntry>,
|
||||||
|
path: &str,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let ctx = if let Some(c) = ctx {
|
||||||
|
c
|
||||||
|
} else {
|
||||||
|
context::current()
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = sources
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.to_legacy())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
ctx.write_file(Path::new(path), &content)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load sources from DEB822 format
|
||||||
|
fn load_deb822(ctx: &context::Context, path: &str) -> Result<Vec<SourceEntry>, Box<dyn Error>> {
|
||||||
|
let path = Path::new(path);
|
||||||
|
if path.exists() {
|
||||||
|
let content = ctx.read_file(path)?;
|
||||||
|
return Ok(parse_deb822(&content));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load sources from legacy format
|
||||||
|
fn load_legacy(ctx: &context::Context, path: &str) -> Result<Vec<SourceEntry>, Box<dyn Error>> {
|
||||||
|
let path = Path::new(path);
|
||||||
|
if path.exists() {
|
||||||
|
let content = ctx.read_file(path)?;
|
||||||
|
return Ok(content.lines().flat_map(SourceEntry::from_legacy).collect());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_parse_deb822() {
|
||||||
|
let deb822 = "\
|
||||||
|
Types: deb\n\
|
||||||
|
URIs: http://fr.archive.ubuntu.com/ubuntu/\n\
|
||||||
|
Suites: questing questing-updates questing-backports\n\
|
||||||
|
Components: main restricted universe multiverse\n\
|
||||||
|
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\n\
|
||||||
|
Architectures: amd64\n\
|
||||||
|
\n\
|
||||||
|
Types: deb\n\
|
||||||
|
URIs: http://security.ubuntu.com/ubuntu/\n\
|
||||||
|
Suites: questing-security\n\
|
||||||
|
Components: main restricted universe multiverse\n\
|
||||||
|
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\n\
|
||||||
|
Architectures: amd64\n\
|
||||||
|
\n\
|
||||||
|
Types: deb\n\
|
||||||
|
URIs: http://ports.ubuntu.com/ubuntu-ports/\n\
|
||||||
|
Suites: questing questing-updates questing-backports\n\
|
||||||
|
Components: main restricted universe multiverse\n\
|
||||||
|
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\n\
|
||||||
|
Architectures: riscv64\n\
|
||||||
|
";
|
||||||
|
|
||||||
|
let sources = parse_deb822(deb822);
|
||||||
|
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].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].components,
|
||||||
|
vec!["main", "restricted", "universe", "multiverse"]
|
||||||
|
);
|
||||||
|
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].components,
|
||||||
|
vec!["main", "restricted", "universe", "multiverse"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_parse_legacy() {
|
||||||
|
let legacy = "\
|
||||||
|
deb [signed-by=\"/usr/share/keyrings/ubuntu-archive-keyring.gpg\" arch=amd64] http://archive.ubuntu.com/ubuntu resolute main universe\n\
|
||||||
|
deb [arch=amd64,i386 signed-by=\"/usr/share/keyrings/ubuntu-archive-keyring.gpg\"] http://archive.ubuntu.com/ubuntu resolute-updates main\n\
|
||||||
|
deb [signed-by=\"/usr/share/keyrings/ubuntu-archive-keyring.gpg\"] http://security.ubuntu.com/ubuntu resolute-security main\n\
|
||||||
|
";
|
||||||
|
|
||||||
|
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].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].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].components, vec!["main"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use std::collections::HashMap;
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::apt;
|
||||||
use crate::deb::cross;
|
use crate::deb::cross;
|
||||||
|
|
||||||
pub fn build(
|
pub fn build(
|
||||||
@@ -29,6 +30,19 @@ pub fn build(
|
|||||||
cross::ensure_repositories(arch, series)?;
|
cross::ensure_repositories(arch, series)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UBUNTU: Ensure 'universe' repository is enabled
|
||||||
|
let mut sources = apt::load(None)?;
|
||||||
|
let mut modified = false;
|
||||||
|
for source in &mut sources {
|
||||||
|
if source.uri.contains("ubuntu") && !source.components.contains(&"universe".to_string()) {
|
||||||
|
source.components.push("universe".to_string());
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if modified {
|
||||||
|
apt::save_legacy(None, sources, "/etc/apt/sources.list")?;
|
||||||
|
}
|
||||||
|
|
||||||
// Update package lists
|
// Update package lists
|
||||||
log::debug!("Updating package lists for local build...");
|
log::debug!("Updating package lists for local build...");
|
||||||
let status = ctx
|
let status = ctx
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
//! pkh allows working with Debian packages, with multiple actions/submodules
|
//! pkh allows working with Debian packages, with multiple actions/submodules
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
/// Handle apt data (apt sources)
|
||||||
|
pub mod apt;
|
||||||
/// Build a Debian source package (into a .dsc)
|
/// Build a Debian source package (into a .dsc)
|
||||||
pub mod build;
|
pub mod build;
|
||||||
/// Parse or edit a Debian changelog of a source package
|
/// Parse or edit a Debian changelog of a source package
|
||||||
|
|||||||
Reference in New Issue
Block a user