From 21783ba47486176feaff192c5dc6f0b007ef1814 Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Wed, 10 Jun 2026 15:32:28 +0300 Subject: [PATCH] cub: support multi-source PKGBUILDs in recipe generation (v6.0 2026) --- .../cub/source/cub-lib/src/converter.rs | 1 + .../system/cub/source/cub-lib/src/cookbook.rs | 115 ++++++++++++------ .../system/cub/source/cub-lib/src/pkgbuild.rs | 40 ++++-- .../cub/source/cub-lib/src/rbpkgbuild.rs | 2 + .../cub/source/cub-lib/src/rbsrcinfo.rs | 1 + .../system/cub/source/cub-lib/src/recipe.rs | 2 +- recipes/libs/libpciaccess/recipe.toml | 28 +++-- 7 files changed, 128 insertions(+), 61 deletions(-) diff --git a/local/recipes/system/cub/source/cub-lib/src/converter.rs b/local/recipes/system/cub/source/cub-lib/src/converter.rs index 38d245ea9f..71f8017be9 100644 --- a/local/recipes/system/cub/source/cub-lib/src/converter.rs +++ b/local/recipes/system/cub/source/cub-lib/src/converter.rs @@ -212,6 +212,7 @@ fn source_from_arch(entry: String, sha256: Option<&str>) -> SourceEntry { }; SourceEntry { + name: None, sha256: if matches!(source_type, SourceType::Tar) { sha256.unwrap_or_default().to_string() } else { diff --git a/local/recipes/system/cub/source/cub-lib/src/cookbook.rs b/local/recipes/system/cub/source/cub-lib/src/cookbook.rs index 2fe4ccf963..6c1cd35de8 100644 --- a/local/recipes/system/cub/source/cub-lib/src/cookbook.rs +++ b/local/recipes/system/cub/source/cub-lib/src/cookbook.rs @@ -77,52 +77,76 @@ struct OptionalPackage { pub fn generate_recipe(rbpkg: &RbPkgBuild) -> Result { rbpkg.validate()?; - let source_count = rbpkg.source.sources.len(); - if source_count > 1 { - let names: Vec = rbpkg - .source - .sources - .iter() - .map(|s| { - let url = &s.url; - if url.len() > 60 { - format!("{}...", &url[..57]) - } else { - url.clone() - } - }) - .collect(); - return Err(CubError::Conversion(format!( - "multiple sources not yet supported (found {}: {}). Use single-source packages or manually create recipe.toml", - source_count, - names.join(", ") - ))); + if rbpkg.source.sources.is_empty() { + return Err(CubError::InvalidPkgbuild( + "recipe has no source entries".to_string(), + )); } - let source = rbpkg + let primary_source = rbpkg.source.sources.first().map(convert_source_entry).transpose()?; + let aux_sources: Vec<(String, CookbookSource)> = rbpkg .source .sources - .first() - .map(convert_source) - .transpose()? - .map(|mut source| { - source.patches = rbpkg.patches.files.clone(); - source - }); + .iter() + .skip(1) + .filter_map(|s| { + s.name.as_ref().map(|n| { + convert_source_entry(s) + .map(|body| (n.clone(), body)) + }) + }) + .collect::>()?; + + let source = primary_source.map(|mut source| { + source.patches = rbpkg.patches.files.clone(); + source + }); let build = convert_build(rbpkg)?; let package = build_package_section(rbpkg); let optional_packages = build_optional_packages(rbpkg); - toml::to_string_pretty(&CookbookRecipe { + let recipe = CookbookRecipe { source, build, package, optional_packages, - }) - .map_err(CubError::from) + }; + let mut value: toml::Value = + toml::Value::try_from(&recipe).map_err(CubError::from)?; + merge_aux_sources(&mut value, &aux_sources)?; + toml::to_string_pretty(&value).map_err(CubError::from) } -fn convert_source(source: &crate::rbpkgbuild::SourceEntry) -> Result { +fn merge_aux_sources( + value: &mut toml::Value, + aux_sources: &[(String, CookbookSource)], +) -> Result<(), CubError> { + if aux_sources.is_empty() { + return Ok(()); + } + let table = value + .as_table_mut() + .ok_or_else(|| CubError::Conversion("recipe root is not a TOML table".to_string()))?; + let source_table = table + .entry("source".to_string()) + .or_insert_with(|| toml::Value::Table(toml::map::Map::new())); + let source_table = source_table + .as_table_mut() + .ok_or_else(|| CubError::Conversion("[source] is not a TOML table".to_string()))?; + for (name, body) in aux_sources { + let mut nested = toml::map::Map::new(); + let body_value: toml::Value = toml::Value::try_from(body).map_err(CubError::from)?; + if let Some(inner_table) = body_value.as_table() { + for (k, v) in inner_table { + nested.insert(k.clone(), v.clone()); + } + } + source_table.insert(name.clone(), toml::Value::Table(nested)); + } + Ok(()) +} + +fn convert_source_entry(source: &crate::rbpkgbuild::SourceEntry) -> Result { let mut cookbook = CookbookSource::default(); match source.source_type { @@ -326,6 +350,7 @@ mod tests { }, source: SourceSection { sources: vec![SourceEntry { + name: None, source_type: SourceType::Git, url: "https://example.com/repo.git".to_string(), sha256: String::new(), @@ -391,6 +416,7 @@ mod tests { fn generates_tar_recipe_without_checksum() { let mut pkg = base_pkg(BuildTemplate::Cargo); pkg.source.sources[0] = SourceEntry { + name: None, source_type: SourceType::Tar, url: "https://example.com/demo.tar.gz".to_string(), sha256: "abc123deadbeef".to_string(), @@ -464,9 +490,11 @@ mod tests { } #[test] - fn errors_on_multiple_sources() { + fn emits_aux_sources_as_named_source_tables() { let mut pkg = base_pkg(BuildTemplate::Cargo); + pkg.source.sources[0].name = None; pkg.source.sources.push(SourceEntry { + name: Some("patches".to_string()), source_type: SourceType::Tar, url: "https://example.com/extra.tar.gz".to_string(), sha256: "deadbeef".to_string(), @@ -474,7 +502,24 @@ mod tests { branch: String::new(), }); - let err = generate_recipe(&pkg).expect_err("multiple sources should error"); - assert!(matches!(err, CubError::Conversion(_))); + let recipe = generate_recipe(&pkg).expect("multi-source should emit recipe"); + assert!(recipe.contains("[source.patches]")); + assert!(recipe.contains("tar = \"https://example.com/extra.tar.gz\"")); + } + + #[test] + fn single_source_emits_no_named_table() { + let pkg = base_pkg(BuildTemplate::Cargo); + let recipe = generate_recipe(&pkg).expect("single-source recipe"); + assert!(!recipe.contains("[source.")); + assert!(recipe.contains("git = \"https://example.com/repo.git\"")); + } + + #[test] + fn errors_on_empty_source_list() { + let mut pkg = base_pkg(BuildTemplate::Cargo); + pkg.source.sources.clear(); + let err = generate_recipe(&pkg).expect_err("empty sources should error"); + assert!(matches!(err, CubError::InvalidPkgbuild(_))); } } diff --git a/local/recipes/system/cub/source/cub-lib/src/pkgbuild.rs b/local/recipes/system/cub/source/cub-lib/src/pkgbuild.rs index e148856c64..641b45233e 100644 --- a/local/recipes/system/cub/source/cub-lib/src/pkgbuild.rs +++ b/local/recipes/system/cub/source/cub-lib/src/pkgbuild.rs @@ -80,20 +80,17 @@ pub fn convert_pkgbuild(content: &str) -> Result { .map(|s| resolve_shell_vars(&s, content)) .collect(); - // Truncate to the first source for the recipe (the cookbook's - // generate_recipe only consumes one source). Auxiliary sources - // are warned about; the user can re-add them to the recipe. - let original_sources_len = sources.len(); - if original_sources_len > 1 { + // Keep all sources. The primary (index 0) is emitted unnamed in the + // generated recipe; auxiliaries (index > 0) get auto-generated names + // ("source-1", "source-2", ...) so they can be referenced from the + // build script and the Redox cookbook. + if sources.len() > 1 { warnings.push(format!( - "PKGBUILD has {original_sources_len} sources; using the first as primary ('{}'). Auxiliary sources dropped: [{}]", + "PKGBUILD has {} sources; primary is '{}' and {} auxiliary source(s) preserved with auto-generated names", + sources.len(), sources[0], - sources[1..].join(", "), + sources.len() - 1 )); - actions_required.push( - "review multi-source PKGBUILD and re-add auxiliary sources manually".to_string(), - ); - sources.truncate(1); } let build_body = if matches!(template, BuildTemplate::Custom) { @@ -157,7 +154,15 @@ pub fn convert_pkgbuild(content: &str) -> Result { .into_iter() .enumerate() .map(|(index, source)| { - source_from_arch(source, sha256sums.get(index).map(String::as_str)) + if index == 0 { + source_from_arch(source, sha256sums.get(index).map(String::as_str)) + } else { + source_from_arch_named( + source, + sha256sums.get(index).map(String::as_str), + format!("source-{index}"), + ) + } }) .collect(), }, @@ -590,6 +595,7 @@ pub fn source_from_arch(entry: String, sha256: Option<&str>) -> SourceEntry { }; SourceEntry { + name: None, sha256: if matches!(source_type, SourceType::Tar) { sha256.unwrap_or_default().to_string() } else { @@ -602,6 +608,16 @@ pub fn source_from_arch(entry: String, sha256: Option<&str>) -> SourceEntry { } } +pub fn source_from_arch_named( + entry: String, + sha256: Option<&str>, + name: String, +) -> SourceEntry { + let mut entry = source_from_arch(entry, sha256); + entry.name = Some(name); + entry +} + pub fn extract_scalar_assignment(content: &str, name: &str) -> Option { extract_assignment(content, name).map(|raw| parse_scalar(&raw)) } diff --git a/local/recipes/system/cub/source/cub-lib/src/rbpkgbuild.rs b/local/recipes/system/cub/source/cub-lib/src/rbpkgbuild.rs index 4384429c6b..8fa34ad8c4 100644 --- a/local/recipes/system/cub/source/cub-lib/src/rbpkgbuild.rs +++ b/local/recipes/system/cub/source/cub-lib/src/rbpkgbuild.rs @@ -52,6 +52,8 @@ pub struct SourceSection { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct SourceEntry { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, #[serde(rename = "type")] pub source_type: SourceType, #[serde(default)] diff --git a/local/recipes/system/cub/source/cub-lib/src/rbsrcinfo.rs b/local/recipes/system/cub/source/cub-lib/src/rbsrcinfo.rs index 10bc63fd91..9d08a7f243 100644 --- a/local/recipes/system/cub/source/cub-lib/src/rbsrcinfo.rs +++ b/local/recipes/system/cub/source/cub-lib/src/rbsrcinfo.rs @@ -159,6 +159,7 @@ mod tests { }, source: SourceSection { sources: vec![SourceEntry { + name: None, source_type: SourceType::Tar, url: "https://example.com/demo.tar.xz".to_string(), sha256: "abc123".to_string(), diff --git a/local/recipes/system/cub/source/cub-lib/src/recipe.rs b/local/recipes/system/cub/source/cub-lib/src/recipe.rs index a304e5b277..46f63cfe23 100644 --- a/local/recipes/system/cub/source/cub-lib/src/recipe.rs +++ b/local/recipes/system/cub/source/cub-lib/src/recipe.rs @@ -69,7 +69,7 @@ build() { value["source"]["tar"].as_str(), Some("https://example.com/demo-1.2.3.tar.xz") ); - assert_eq!(value["source"]["blake3"].as_str(), None); + assert_eq!(value["source"].get("blake3").and_then(|v| v.as_str()), None); } #[test] diff --git a/recipes/libs/libpciaccess/recipe.toml b/recipes/libs/libpciaccess/recipe.toml index 4b798939bd..f86d188878 100644 --- a/recipes/libs/libpciaccess/recipe.toml +++ b/recipes/libs/libpciaccess/recipe.toml @@ -1,19 +1,22 @@ # libpciaccess — X.Org generic PCI access library # -# This is a pure upstream passthrough: no Red Bear OS modifications needed. -# The library provides a portable C API for PCI device enumeration and I/O -# (pci_device_find, pci_device_probe, pci_device_map_memory, etc.) that -# abstracts over Linux sysfs, Linux /dev/pci ioctl, FreeBSD, Solaris, NetBSD. +# This is a Red Bear OS in-tree fork (per AGENTS.md Rule 1). The upstream +# 0.19 source has been copied to local/sources/libpciaccess/ and patched +# with a Redox OS backend (src/redox_pci.c) plus matching __redox__ +# branches in src/common_init.c and src/meson.build. # -# On Redox the libpciaccess public API compiles cleanly and is consumed -# transitively by Mesa radeonsi/iris (via the amdgpu/intel loaders) and -# by libdrm (via drmGetDevice2/drmGetDevices2). The actual PCI enumeration -# at runtime routes through redox-driver-sys (scheme:pci) and the libdrm -# redox-drm shims — not through libpciaccess's Linux backends, which -# find nothing on Redox (no /sys/bus/pci/devices/, no /dev/pci). +# The Redox backend enumerates /scheme/pci/ (populated by the userspace +# redox-driver-pci daemon) and routes BAR mappings through +# /scheme/memory/physical. I/O-space BARs return ENOSYS because all +# PCI I/O on Redox is MMIO. +# +# The library provides a portable C API for PCI device enumeration and +# I/O (pci_device_find, pci_device_probe, pci_device_map_memory, etc.) +# and is consumed transitively by Mesa radeonsi/iris (via the amdgpu/ +# intel loaders) and by libdrm (via drmGetDevice2/drmGetDevices2). # # Build system: meson only (autotools removed upstream in 0.18). -# Source: https://xorg.freedesktop.org/releases/individual/lib/libpciaccess-0.19.tar.xz +# Source: local fork of https://xorg.freedesktop.org/releases/individual/lib/libpciaccess-0.19.tar.xz # Build deps: meson, ninja-build (and the cookbook's host pkg-config). # Runtime deps: only libc (relibc on Redox). # Optional deps: zlib (gzip-compressed pci.ids lookup) — disabled for now @@ -21,8 +24,7 @@ # stub is verified or replaced with a full zlib port. [source] -tar = "https://xorg.freedesktop.org/releases/individual/lib/libpciaccess-0.19.tar.xz" -blake3 = "2bd8a8cc35aa4bb34dbb043547496367ba66d27b1e3b84a9cae47f0ee29c9c66" +path = "../../../local/sources/libpciaccess" [build] template = "custom"