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
This commit is contained in:
2026-05-08 00:13:31 +01:00
parent 0a928348b9
commit 7706617e7f
46 changed files with 2909 additions and 37 deletions
@@ -3,11 +3,14 @@ use std::env;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::rc::Rc;
use std::time::{SystemTime, UNIX_EPOCH};
use std::collections::HashSet;
use clap::{CommandFactory, Parser, Subcommand};
use cub::aur::{AurClient, AurPackage};
use cub::cook;
@@ -475,6 +478,27 @@ fn build_local_dir(context: &AppContext, dir: &Path) -> Result<(), Box<dyn std::
let rbpkg = RbPkgBuild::from_file(&rbpkg_path)?;
rbpkg.validate()?;
let missing = check_missing_dependencies(context, &rbpkg)?;
if !missing.is_empty() {
println!(
"The following dependencies are not installed and must be resolved before building {}:",
rbpkg.package.name
);
for (dep, kind) in &missing {
println!(" - {} ({})", dep, kind);
}
println!();
println!("Would you like to try installing missing dependencies? [y/N]");
let mut answer = String::new();
io::stdin().read_line(&mut answer)?;
if answer.trim().to_ascii_lowercase().starts_with('y') {
resolve_dependencies_interactive(context, &missing)?;
} else {
println!("Proceeding with build — it may fail if dependencies are missing at cook time.");
}
}
let work_dir = create_temp_dir("cub-build")?;
let recipe_dir = work_dir.join(&rbpkg.package.name);
CookbookAdapter::write_recipe_dir(&rbpkg, &recipe_dir)?;
@@ -525,6 +549,126 @@ fn build_local_dir(context: &AppContext, dir: &Path) -> Result<(), Box<dyn std::
Ok(())
}
fn check_missing_dependencies(
context: &AppContext,
rbpkg: &RbPkgBuild,
) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
let library = context.open_library()?;
let installed: Vec<String> = library
.get_installed_packages()?
.into_iter()
.map(|p| p.to_string().to_ascii_lowercase())
.collect();
let mut missing = Vec::new();
let all_deps: Vec<(&String, &str)> = rbpkg
.dependencies
.build
.iter()
.map(|d| (d, "build dependency"))
.chain(
rbpkg
.dependencies
.runtime
.iter()
.map(|d| (d, "runtime dependency")),
)
.collect();
let mut seen = HashSet::new();
for (dep, kind) in all_deps {
let lower = dep.to_ascii_lowercase();
if !seen.insert(lower.clone()) {
continue;
}
if installed.contains(&lower) {
continue;
}
missing.push((dep.clone(), kind.to_string()));
}
Ok(missing)
}
fn resolve_dependencies_interactive(
context: &AppContext,
missing: &[(String, String)],
) -> Result<(), Box<dyn std::error::Error>> {
let mut library = context.open_library()?;
for (dep, _kind) in missing {
let package_name = match PackageName::new(dep.clone()) {
Ok(name) => name,
Err(_) => {
eprintln!(" skipping invalid package name: {dep}");
continue;
}
};
print!(" installing {dep} from official repo... ");
io::stdout().flush()?;
match library.install(vec![package_name.clone()]) {
Ok(()) => {
println!("done");
}
Err(pkg::backend::Error::PackageNotFound(_)) => {
println!("not found in official repo — trying AUR");
print!(" fetching {dep} from AUR into ~/.cub/... ");
io::stdout().flush()?;
match fetch_aur_to_store(dep) {
Ok(_) => println!("done (recipe saved, build with `cub -B ~/.cub/recipes/{dep}`)"),
Err(e) => println!("failed: {e}"),
}
}
Err(e) => {
println!("failed: {e}");
}
}
}
let _ = apply_library_changes(&mut library);
Ok(())
}
fn fetch_aur_to_store(package: &str) -> Result<(), Box<dyn std::error::Error>> {
let store = CubStore::new()?;
store.init()?;
let recipe_dir = store.recipes_dir().join(package);
if recipe_dir.exists() {
return Ok(());
}
let repo_url = aur_repo_url(package);
let clone_dir = create_temp_dir("cub-dep-aur")?;
let status = Command::new("git")
.arg("clone")
.arg("--depth")
.arg("1")
.arg("--")
.arg(&repo_url)
.arg(&clone_dir)
.status()?;
if !status.success() {
return Err(Box::new(CubError::BuildFailed(format!(
"failed to clone AUR source from {repo_url}"
))));
}
let pkgbuild_path = clone_dir.join("PKGBUILD");
let pkgbuild_content = fs::read_to_string(&pkgbuild_path)?;
let conversion = pkgbuild::convert_pkgbuild(&pkgbuild_content)?;
fs::create_dir_all(&recipe_dir)?;
fs::write(recipe_dir.join("RBPKGBUILD"), conversion.rbpkg.to_toml()?)?;
cub::recipe::save_recipe_to_store(&conversion.rbpkg, &store)?;
Ok(())
}
fn fetch_bur_recipe(package: &str) -> Result<(), Box<dyn std::error::Error>> {
let source_dir = ensure_bur_package_dir(package)?;
let destination = env::current_dir()?.join(package);