cub: support multi-source PKGBUILDs in recipe generation (v6.0 2026)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -77,52 +77,76 @@ struct OptionalPackage {
|
||||
pub fn generate_recipe(rbpkg: &RbPkgBuild) -> Result<String, CubError> {
|
||||
rbpkg.validate()?;
|
||||
|
||||
let source_count = rbpkg.source.sources.len();
|
||||
if source_count > 1 {
|
||||
let names: Vec<String> = 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::<Result<_, _>>()?;
|
||||
|
||||
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<CookbookSource, CubError> {
|
||||
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<CookbookSource, CubError> {
|
||||
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(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,20 +80,17 @@ pub fn convert_pkgbuild(content: &str) -> Result<ConversionResult, CubError> {
|
||||
.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<ConversionResult, CubError> {
|
||||
.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<String> {
|
||||
extract_assignment(content, name).map(|raw| parse_scalar(&raw))
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub source_type: SourceType,
|
||||
#[serde(default)]
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user