Files
RedBear-OS/local/recipes/system/cub/source/cub-lib/src/resolver.rs
T
vasilito 7706617e7f cub: full AUR package manager + Phase 1-5 native build tools
cub redesign (local/recipes/system/cub/):
- AUR RPC v5 client (serde_json) with search/info
- ~/.cub/ user-local recipe/source/repo storage
- Enhanced PKGBUILD parser: optdepends, .SRCINFO, split packages, 19 linuxism patterns
- Recipe generation: host: prefix on dev-deps, shallow_clone, cargopath, installs, optional-packages
- Dependency resolver: scans build errors for missing commands/headers/libs/pkgconfig, maps to packages
- Dependency installation: checks installed packages, fetches AUR deps, interactive prompt
- ~110 Arc→Redox dependency mappings
- ratatui TUI: search, info, install, build, query views
- 14 Arch-style CLI switches (-S/-Si/-Syu/-G/-R/-Q/-Qi/-Ql)
- 65 tests, 0 failures, clean build

Phase 1-5 native build tools (local/recipes/dev/):
- P1 Substrate: tar, m4, diffutils (gnulib bypass), mkfifo kernel patch (1085 lines)
- P2 Build Systems: bison, flex, meson (standalone wrapper), ninja-build, libtool
- P3 Native GCC: gcc-native, binutils-native (cross-compiled for redox host)
- P4 Native LLVM: llvm-native (clang + lld from monorepo)
- P5 Native Rust: rust-native (rustc + cargo)
- Groups: build-essential-native, dev-essential expanded

Config:
- redbear-mini: +7 tools (diffutils, tar, bison, flex, meson, ninja, m4)
- redbear-full: +4 native tools (gcc, binutils, llvm, rust)
- All recipes moved to local/ with symlinks for cookbook discovery (Red Bear policy)

Docs:
- BUILD-TOOLS-PORTING-PLAN.md: phased porting roadmap
- CUB-WORKFLOW-ASSESSMENT.md: gap analysis and integration assessment
2026-05-08 00:13:31 +01:00

439 lines
19 KiB
Rust

use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ResolvedDep {
pub missing: String,
pub package: String,
pub kind: DepKind,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DepKind {
Command,
Header,
Library,
PkgConfig,
Unknown,
}
pub struct DepResolver {
command_map: HashMap<String, String>,
header_map: HashMap<String, String>,
library_map: HashMap<String, String>,
pkgconfig_map: HashMap<String, String>,
}
impl DepResolver {
pub fn new() -> Self {
let mut command_map = HashMap::new();
let mut header_map = HashMap::new();
let mut library_map = HashMap::new();
// ── Compilers ──
for cmd in &["gcc", "g++", "cc", "c++", "cpp"] {
command_map.insert(cmd.to_string(), "gcc-native".to_string());
}
for cmd in &["clang", "clang++", "clang-cpp", "clang-cl"] {
command_map.insert(cmd.to_string(), "llvm-native".to_string());
}
for cmd in &["rustc", "cargo", "rustdoc", "rustfmt", "clippy-driver"] {
command_map.insert(cmd.to_string(), "rust-native".to_string());
}
command_map.insert("nasm".to_string(), "nasm".to_string());
command_map.insert("yasm".to_string(), "yasm".to_string());
// ── Build systems ──
for cmd in &["make", "gmake", "gnumake"] {
command_map.insert(cmd.to_string(), "gnu-make".to_string());
}
command_map.insert("cmake".to_string(), "cmake".to_string());
command_map.insert("meson".to_string(), "meson".to_string());
for cmd in &["ninja", "ninja-build"] {
command_map.insert(cmd.to_string(), "ninja-build".to_string());
}
command_map.insert("autoconf".to_string(), "autoconf".to_string());
command_map.insert("autoheader".to_string(), "autoconf".to_string());
command_map.insert("autoreconf".to_string(), "autoconf".to_string());
command_map.insert("autoscan".to_string(), "autoconf".to_string());
command_map.insert("automake".to_string(), "automake".to_string());
command_map.insert("aclocal".to_string(), "automake".to_string());
command_map.insert("libtool".to_string(), "libtool".to_string());
command_map.insert("libtoolize".to_string(), "libtool".to_string());
command_map.insert("m4".to_string(), "m4".to_string());
command_map.insert("pkg-config".to_string(), "pkg-config".to_string());
command_map.insert("pkgconf".to_string(), "pkg-config".to_string());
// ── Binutils ──
for cmd in &["ld", "ar", "as", "nm", "strip", "objdump", "objcopy",
"ranlib", "readelf", "size", "strings", "addr2line"] {
command_map.insert(cmd.to_string(), "binutils-native".to_string());
}
// ── Text / file tools ──
command_map.insert("patch".to_string(), "patch".to_string());
for cmd in &["sed", "gsed"] {
command_map.insert(cmd.to_string(), "sed".to_string());
}
for cmd in &["grep", "egrep", "fgrep", "rgrep"] {
command_map.insert(cmd.to_string(), "gnu-grep".to_string());
}
for cmd in &["awk", "gawk", "mawk", "nawk"] {
command_map.insert(cmd.to_string(), "gawk".to_string());
}
for cmd in &["diff", "cmp", "diff3", "sdiff"] {
command_map.insert(cmd.to_string(), "diffutils".to_string());
}
// ── Archives ──
command_map.insert("tar".to_string(), "uutils-tar".to_string());
for cmd in &["gzip", "gunzip", "zcat"] {
command_map.insert(cmd.to_string(), "gzip".to_string());
}
for cmd in &["bzip2", "bunzip2"] {
command_map.insert(cmd.to_string(), "bzip2".to_string());
}
for cmd in &["xz", "unxz", "lzma"] {
command_map.insert(cmd.to_string(), "xz".to_string());
}
for cmd in &["zstd", "unzstd", "zstdcat"] {
command_map.insert(cmd.to_string(), "zstd".to_string());
}
// ── VCS / Network ──
command_map.insert("git".to_string(), "git".to_string());
for cmd in &["curl", "wget"] {
command_map.insert(cmd.to_string(), "curl".to_string());
}
// ── Languages ──
for cmd in &["python", "python3"] {
command_map.insert(cmd.to_string(), "python312".to_string());
}
command_map.insert("perl".to_string(), "perl5".to_string());
command_map.insert("lua".to_string(), "lua".to_string());
command_map.insert("ruby".to_string(), "ruby".to_string());
// ── Shell ──
for cmd in &["bash", "sh"] {
command_map.insert(cmd.to_string(), "bash".to_string());
}
// ── Parser generators ──
for cmd in &["flex", "lex"] {
command_map.insert(cmd.to_string(), "flex".to_string());
}
for cmd in &["bison", "yacc"] {
command_map.insert(cmd.to_string(), "bison".to_string());
}
command_map.insert("gperf".to_string(), "gperf".to_string());
// ── i18n / docs ──
for cmd in &["gettext", "msgfmt", "xgettext", "msgmerge"] {
command_map.insert(cmd.to_string(), "gettext".to_string());
}
for cmd in &["intltool-update", "intltool-extract", "intltool-merge"] {
command_map.insert(cmd.to_string(), "intltool".to_string());
}
for cmd in &["makeinfo", "texi2any", "texi2dvi", "texi2pdf"] {
command_map.insert(cmd.to_string(), "texinfo".to_string());
}
for cmd in &["help2man"] {
command_map.insert(cmd.to_string(), "help2man".to_string());
}
// ── Core system ──
command_map.insert("install".to_string(), "coreutils".to_string());
for cmd in &["cp", "mv", "rm", "ln", "mkdir", "rmdir", "chmod", "chown",
"cat", "echo", "touch", "ls", "find", "xargs", "dirname",
"basename", "tr", "cut", "sort", "uniq", "wc", "head", "tail"] {
command_map.insert(cmd.to_string(), "coreutils".to_string());
}
// ── Header files → packages ──
header_map.insert("stdio.h".to_string(), "relibc".to_string());
header_map.insert("stdlib.h".to_string(), "relibc".to_string());
header_map.insert("string.h".to_string(), "relibc".to_string());
header_map.insert("unistd.h".to_string(), "relibc".to_string());
header_map.insert("fcntl.h".to_string(), "relibc".to_string());
header_map.insert("signal.h".to_string(), "relibc".to_string());
header_map.insert("pthread.h".to_string(), "relibc".to_string());
header_map.insert("dlfcn.h".to_string(), "relibc".to_string());
header_map.insert("zlib.h".to_string(), "zlib".to_string());
header_map.insert("bzlib.h".to_string(), "bzip2".to_string());
header_map.insert("lzma.h".to_string(), "xz".to_string());
header_map.insert("zstd.h".to_string(), "zstd".to_string());
header_map.insert("openssl/ssl.h".to_string(), "openssl3".to_string());
header_map.insert("curl/curl.h".to_string(), "curl".to_string());
header_map.insert("expat.h".to_string(), "expat".to_string());
header_map.insert("ffi.h".to_string(), "libffi".to_string());
header_map.insert("pcre2.h".to_string(), "pcre2".to_string());
header_map.insert("ncurses.h".to_string(), "ncurses".to_string());
header_map.insert("readline/readline.h".to_string(), "readline".to_string());
header_map.insert("sqlite3.h".to_string(), "sqlite3".to_string());
header_map.insert("fontconfig/fontconfig.h".to_string(), "fontconfig".to_string());
header_map.insert("freetype2/freetype/freetype.h".to_string(), "freetype".to_string());
header_map.insert("harfbuzz/hb.h".to_string(), "harfbuzz".to_string());
header_map.insert("png.h".to_string(), "libpng".to_string());
header_map.insert("jpeglib.h".to_string(), "libjpeg-turbo".to_string());
// ── Library files → packages ──
library_map.insert("libz".to_string(), "zlib".to_string());
library_map.insert("libbz2".to_string(), "bzip2".to_string());
library_map.insert("liblzma".to_string(), "xz".to_string());
library_map.insert("libzstd".to_string(), "zstd".to_string());
library_map.insert("libssl".to_string(), "openssl3".to_string());
library_map.insert("libcrypto".to_string(), "openssl3".to_string());
library_map.insert("libcurl".to_string(), "curl".to_string());
library_map.insert("libexpat".to_string(), "expat".to_string());
library_map.insert("libffi".to_string(), "libffi".to_string());
library_map.insert("libpcre2".to_string(), "pcre2".to_string());
library_map.insert("libncurses".to_string(), "ncurses".to_string());
library_map.insert("libreadline".to_string(), "readline".to_string());
library_map.insert("libsqlite3".to_string(), "sqlite3".to_string());
library_map.insert("libpng".to_string(), "libpng".to_string());
library_map.insert("libjpeg".to_string(), "libjpeg-turbo".to_string());
library_map.insert("libfontconfig".to_string(), "fontconfig".to_string());
library_map.insert("libfreetype".to_string(), "freetype".to_string());
library_map.insert("libharfbuzz".to_string(), "harfbuzz".to_string());
library_map.insert("libxml2".to_string(), "libxml2".to_string());
library_map.insert("libxslt".to_string(), "libxslt".to_string());
let mut pkgconfig_map = HashMap::new();
pkgconfig_map.insert("gtk+-3.0".to_string(), "gtk".to_string());
pkgconfig_map.insert("gtk4".to_string(), "gtk".to_string());
pkgconfig_map.insert("glib-2.0".to_string(), "glib".to_string());
pkgconfig_map.insert("gobject-2.0".to_string(), "glib".to_string());
pkgconfig_map.insert("gio-2.0".to_string(), "glib".to_string());
pkgconfig_map.insert("cairo".to_string(), "cairo".to_string());
pkgconfig_map.insert("pango".to_string(), "pango".to_string());
pkgconfig_map.insert("atk".to_string(), "atk".to_string());
pkgconfig_map.insert("gdk-pixbuf-2.0".to_string(), "gdk-pixbuf".to_string());
pkgconfig_map.insert("libpng".to_string(), "libpng".to_string());
pkgconfig_map.insert("libjpeg".to_string(), "libjpeg-turbo".to_string());
pkgconfig_map.insert("freetype2".to_string(), "freetype".to_string());
pkgconfig_map.insert("fontconfig".to_string(), "fontconfig".to_string());
pkgconfig_map.insert("harfbuzz".to_string(), "harfbuzz".to_string());
pkgconfig_map.insert("openssl".to_string(), "openssl3".to_string());
pkgconfig_map.insert("libcurl".to_string(), "curl".to_string());
pkgconfig_map.insert("zlib".to_string(), "zlib".to_string());
pkgconfig_map.insert("bzip2".to_string(), "bzip2".to_string());
pkgconfig_map.insert("liblzma".to_string(), "xz".to_string());
pkgconfig_map.insert("libzstd".to_string(), "zstd".to_string());
pkgconfig_map.insert("expat".to_string(), "expat".to_string());
pkgconfig_map.insert("libffi".to_string(), "libffi".to_string());
pkgconfig_map.insert("libpcre2-8".to_string(), "pcre2".to_string());
pkgconfig_map.insert("ncurses".to_string(), "ncurses".to_string());
pkgconfig_map.insert("readline".to_string(), "readline".to_string());
pkgconfig_map.insert("sqlite3".to_string(), "sqlite3".to_string());
pkgconfig_map.insert("dbus-1".to_string(), "dbus".to_string());
pkgconfig_map.insert("wayland-client".to_string(), "wayland".to_string());
pkgconfig_map.insert("wayland-server".to_string(), "wayland".to_string());
pkgconfig_map.insert("x11".to_string(), "libx11".to_string());
pkgconfig_map.insert("xcb".to_string(), "libxcb".to_string());
pkgconfig_map.insert("libxml-2.0".to_string(), "libxml2".to_string());
pkgconfig_map.insert("libxslt".to_string(), "libxslt".to_string());
pkgconfig_map.insert("alsa".to_string(), "alsa-lib".to_string());
pkgconfig_map.insert("libpulse".to_string(), "pulseaudio".to_string());
Self {
command_map,
header_map,
library_map,
pkgconfig_map,
}
}
pub fn scan_build_error(&self, error_output: &str) -> Vec<ResolvedDep> {
let mut resolved = Vec::new();
for line in error_output.lines() {
let line_lower = line.to_ascii_lowercase();
if let Some(header) = extract_missing_header(&line_lower) {
let base = std::path::Path::new(&header)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&header)
.to_string();
if let Some(pkg) = self.header_map.get(&base.to_ascii_lowercase()) {
resolved.push(ResolvedDep {
missing: header,
package: pkg.clone(),
kind: DepKind::Header,
});
}
continue;
}
if let Some(lib) = extract_missing_library(&line_lower) {
let key = lib.to_ascii_lowercase();
let pkg = self
.library_map
.get(&key)
.or_else(|| self.library_map.get(&format!("lib{}", key)))
.cloned();
if let Some(pkg) = pkg {
resolved.push(ResolvedDep {
missing: format!("lib{}", lib),
package: pkg,
kind: DepKind::Library,
});
}
continue;
}
if let Some(pc) = extract_missing_pkgconfig(&line_lower) {
let key = pc.to_ascii_lowercase();
let pkg = self
.command_map
.get(&key)
.or_else(|| self.pkgconfig_map.get(&key))
.cloned();
if let Some(pkg) = pkg {
resolved.push(ResolvedDep {
missing: pc,
package: pkg,
kind: DepKind::PkgConfig,
});
}
continue;
}
if let Some(cmd) = extract_command_not_found(&line_lower) {
if let Some(pkg) = self.command_map.get(&cmd.to_ascii_lowercase()) {
if !resolved.iter().any(|r: &ResolvedDep| r.missing == cmd) {
resolved.push(ResolvedDep {
missing: cmd,
package: pkg.clone(),
kind: DepKind::Command,
});
}
}
}
}
resolved
}
}
impl Default for DepResolver {
fn default() -> Self {
Self::new()
}
}
fn extract_command_not_found(line_lower: &str) -> Option<String> {
// "sh: line 1: gcc: command not found"
if line_lower.contains(": command not found") {
if let Some(rest) = line_lower.strip_suffix(": command not found") {
if let Some(cmd) = rest.rsplit(':').next() {
let cmd = cmd.trim();
if !cmd.is_empty() && cmd.len() < 50 {
return Some(cmd.to_string());
}
}
}
}
// "make: gcc: No such file or directory"
if line_lower.contains(": no such file or directory") {
let rest = line_lower.replace(": no such file or directory", "");
if let Some(cmd) = rest.rsplit(':').next() {
let cmd = cmd.trim();
if !cmd.is_empty() && cmd.len() < 50 {
return Some(cmd.to_string());
}
}
}
None
}
fn extract_missing_header(line_lower: &str) -> Option<String> {
// "fatal error: X.h: No such file or directory"
if line_lower.contains("fatal error:") && line_lower.contains("no such file") {
if let Some(after) = line_lower.split("fatal error:").nth(1) {
if let Some(header) = after.split(':').next() {
let h = header.trim();
if !h.is_empty() {
return Some(h.to_string());
}
}
}
}
None
}
fn extract_missing_library(line_lower: &str) -> Option<String> {
if line_lower.contains("cannot find -l") {
for part in line_lower.split_whitespace() {
if part.starts_with("-l") && part.len() > 2 {
let mut lib = part[2..].to_string();
lib = lib.trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_').to_string();
if !lib.is_empty() && lib.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Some(lib);
}
}
}
}
None
}
fn extract_missing_pkgconfig(line_lower: &str) -> Option<String> {
// "No package 'gtk+-3.0' found"
if line_lower.contains("no package '") {
if let Some(after) = line_lower.split("no package '").nth(1) {
if let Some(pkg) = after.split('\'').next() {
let p = pkg.trim();
if !p.is_empty() {
return Some(p.to_string());
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_command_not_found() {
let output = "sh: line 1: gcc: command not found\nmake: *** [all] Error 127";
let resolver = DepResolver::new();
let deps = resolver.scan_build_error(output);
assert!(deps.iter().any(|d| d.missing == "gcc" && d.package == "gcc-native"));
}
#[test]
fn detects_missing_header() {
let output = "src/main.c:3:10: fatal error: zlib.h: No such file or directory";
let resolver = DepResolver::new();
let deps = resolver.scan_build_error(output);
assert!(deps.iter().any(|d| d.missing.contains("zlib.h") && d.package == "zlib"));
}
#[test]
fn detects_missing_library() {
let output = "/usr/bin/ld: cannot find -lz: No such file or directory";
let resolver = DepResolver::new();
let deps = resolver.scan_build_error(output);
assert!(deps.iter().any(|d| d.package == "zlib"));
}
#[test]
fn detects_missing_pkgconfig() {
let output = "Package gtk+-3.0 was not found in the pkg-config search path.\nNo package 'gtk+-3.0' found";
let resolver = DepResolver::new();
let deps = resolver.scan_build_error(output);
assert!(deps.iter().any(|d| d.missing == "gtk+-3.0"));
}
#[test]
fn detects_make_command_not_found() {
let output = "make: cmake: No such file or directory";
let resolver = DepResolver::new();
let deps = resolver.scan_build_error(output);
assert!(deps.iter().any(|d| d.missing == "cmake" && d.package == "cmake"));
}
}