// async cross-toolchain runner that uses parser.rs info (no hardcoding) use crate::pkgs::by_name::bi::binutils::parser::{BinutilsInfo, fetch_page, parse_binutils}; use reqwest::Client; use std::{ error::Error, path::{Path, PathBuf}, }; use tokio::process::Command; use tracing::{info, warn}; /// Configuration object - uses environment if values omitted. #[derive(Debug, Clone)] pub struct BinutilsConfig { pub lfs_root: PathBuf, // where the LFS tree will be (used for $LFS) pub target: String, // LFS_TGT (e.g. x86_64-lfs-linux-gnu) pub info: BinutilsInfo, // parsed page info } impl BinutilsConfig { /// create from env or params. If target is None, tries $LFS_TGT env var. pub fn new(lfs_root: impl AsRef, target: Option, info: BinutilsInfo) -> Self { let lfs_root = lfs_root.as_ref().to_path_buf(); let target = target .or_else(|| std::env::var("LFS_TGT").ok()) .unwrap_or_else(|| { // fallback best-effort if cfg!(target_os = "linux") { "x86_64-lfs-linux-gnu".to_string() } else { "x86_64-lfs-linux-gnu".to_string() } }); Self { lfs_root, target, info, } } /// default places (non-hardcoded) where sources live. /// If env `BINUTILS_SRC_DIR` is set, use that; else try LFS layout: /// - $LFS/src/pkgs/by-name/bi/binutils pub fn source_base_dir(&self) -> PathBuf { if let Ok(s) = std::env::var("BINUTILS_SRC_DIR") { PathBuf::from(s) } else { self.lfs_root .join("src") .join("pkgs") .join("by-name") .join("bi") .join("binutils") } } /// build directory inside LFS tree (following LFS style) pub fn build_dir(&self) -> PathBuf { self.lfs_root.join("build").join("binutils-pass1") } /// install dir (tools) pub fn install_dir(&self) -> PathBuf { self.lfs_root.join("tools") } } /// High-level orchestration. Async. pub async fn build_binutils_from_page( page_url: &str, lfs_root: impl AsRef, target: Option, ) -> Result<(), Box> { // 1) fetch page info!("Fetching page: {}", page_url); let html = fetch_page(page_url).await?; let info = parse_binutils(&html)?; info!("Parsed info: {:?}", info); // 2) build config let cfg = BinutilsConfig::new(lfs_root, target, info.clone()); // 3) ensure source base dir exists let src_base = cfg.source_base_dir(); if !src_base.exists() { info!("Creating source base dir: {:?}", src_base); tokio::fs::create_dir_all(&src_base).await?; } // 4) find extracted source directory (binutils-*) let mut source_dir: Option = None; if let Ok(mut rd) = tokio::fs::read_dir(&src_base).await { while let Some(entry) = rd.next_entry().await? { let ft = entry.file_type().await?; if ft.is_dir() { let name = entry.file_name().to_string_lossy().to_string(); if name.to_lowercase().contains("binutils") { source_dir = Some(entry.path()); break; } } } } // 5) if not found, attempt to download & extract if source_dir.is_none() { if let Some(dl) = &cfg.info.download_url { info!("No extracted source found; will download {}", dl); // download file into src_base let client = Client::new(); let resp = client.get(dl).send().await?; if !resp.status().is_success() { return Err(format!("Download failed: {}", resp.status()).into()); } // pick a filename from URL let url_path = url::Url::parse(dl)?; let filename = url_path .path_segments() .and_then(|seg| seg.last()) .and_then(|s| { if !s.is_empty() { Some(s.to_string()) } else { None } }) .ok_or("Cannot determine filename from URL")?; let outpath = src_base.join(&filename); info!("Saving archive to {:?}", outpath); let bytes = resp.bytes().await?; tokio::fs::write(&outpath, &bytes).await?; // extract using tar (async spawn). Use absolute path to src_base info!("Extracting archive {:?}", outpath); let tar_path = outpath.clone(); let mut tar_cmd = Command::new("tar"); tar_cmd.arg("-xf").arg(&tar_path).arg("-C").arg(&src_base); let status = tar_cmd.status().await?; if !status.success() { return Err("tar extraction failed".into()); } // look for extracted dir again if let Ok(mut rd) = tokio::fs::read_dir(&src_base).await { while let Some(entry) = rd.next_entry().await? { let ft = entry.file_type().await?; if ft.is_dir() { let name = entry.file_name().to_string_lossy().to_string(); if name.to_lowercase().contains("binutils") { source_dir = Some(entry.path()); break; } } } } } else { warn!("No download URL found on the page and no unpacked source present."); } } let source_dir = match source_dir { Some(p) => p, None => return Err("Could not locate or download/extract Binutils source".into()), }; info!("Using source dir: {:?}", source_dir); // 6) prepare build dir let build_dir = cfg.build_dir(); if !build_dir.exists() { info!("Creating build dir {:?}", build_dir); tokio::fs::create_dir_all(&build_dir).await?; } // 7) run configure: use absolute configure script path in source_dir let configure_path = source_dir.join("configure"); if !configure_path.exists() { return Err(format!("configure script not found at {:?}", configure_path).into()); } // If parser produced configure args tokens, use them; otherwise fallback to common flags let args = if !cfg.info.configure_args.is_empty() { cfg.info.configure_args.clone() } else { vec![ format!("--prefix={}", cfg.install_dir().display()), format!("--with-sysroot={}", cfg.lfs_root.display()), format!("--target={}", cfg.target), "--disable-nls".to_string(), "--disable-werror".to_string(), ] }; // replace $LFS and $LFS_TGT in args let args: Vec = args .into_iter() .map(|a| { a.replace("$LFS", &cfg.lfs_root.to_string_lossy()) .replace("$LFS_TGT", &cfg.target) }) .collect(); info!("Configuring with args: {:?}", args); // spawn configure let mut conf_cmd = Command::new(&configure_path); conf_cmd.current_dir(&build_dir); for a in &args { conf_cmd.arg(a); } conf_cmd.stdout(std::process::Stdio::inherit()); conf_cmd.stderr(std::process::Stdio::inherit()); let status = conf_cmd.status().await?; if !status.success() { return Err("configure step failed".into()); } info!("configure completed"); // 8) run build commands (make-like) if !cfg.info.build_cmds.is_empty() { for b in &cfg.info.build_cmds { // split into program + args let mut parts = shell_words::split(b).unwrap_or_else(|_| vec![b.clone()]); let prog = parts.remove(0); let mut cmd = Command::new(prog); if !parts.is_empty() { cmd.args(parts); } cmd.current_dir(&build_dir); cmd.stdout(std::process::Stdio::inherit()); cmd.stderr(std::process::Stdio::inherit()); let status = cmd.status().await?; if !status.success() { return Err(format!("build step failed: {:?}", b).into()); } } } else { // fallback to running `make` let mut m = Command::new("make"); m.current_dir(&build_dir); m.stdout(std::process::Stdio::inherit()); m.stderr(std::process::Stdio::inherit()); let status = m.status().await?; if !status.success() { return Err("make failed".into()); } } info!("build completed"); // 9) run install commands (make install) if !cfg.info.install_cmds.is_empty() { for inst in &cfg.info.install_cmds { let mut parts = shell_words::split(inst).unwrap_or_else(|_| vec![inst.clone()]); let prog = parts.remove(0); let mut cmd = Command::new(prog); if !parts.is_empty() { cmd.args(parts); } cmd.current_dir(&build_dir); cmd.stdout(std::process::Stdio::inherit()); cmd.stderr(std::process::Stdio::inherit()); let status = cmd.status().await?; if !status.success() { return Err(format!("install step failed: {:?}", inst).into()); } } } else { // fallback `make install` let mut mi = Command::new("make"); mi.arg("install"); mi.current_dir(&build_dir); mi.stdout(std::process::Stdio::inherit()); mi.stderr(std::process::Stdio::inherit()); let status = mi.status().await?; if !status.success() { return Err("make install failed".into()); } } info!("install completed. Binutils Pass 1 done."); Ok(()) }