use anyhow::{bail, Context, Result};
use colored::Colorize;
use std::fs;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::process::{Command, Stdio};
use crate::converter::format;
use crate::utils::config::{self, BendisConfig};
use super::hardening::{self, HardeningStatus};
pub fn run(hard: bool) -> Result<()> {
if config::check_and_migrate_if_needed()? {
if !config::get_bendis_dir().exists() {
bail!(
"error: bendis_workspace directory not found\nrun `bendis init` to migrate or initialize the project"
);
}
}
println!("Updating dependencies...");
let cfg = BendisConfig::load()?;
let bendis_dir = config::get_bendis_dir();
let root_dir = config::get_root_dir();
if !bendis_dir.exists() {
bail!(
"error: bendis_workspace directory not found\nrun `bendis init` first to initialize the project"
);
}
let mut gitignore_warning = None;
if cfg.gitignore_check == 1 {
if let Err(e) = config::check_bendis_gitignore() {
gitignore_warning = Some(e.to_string());
}
}
println!(" Syncing hw/ and target/ directories to bendis_workspace/...");
config::copy_root_dirs_to_bendis_workspace()?;
println!(" Preparing cache files (it may take long)...");
run_bender_update_in_bendis(&bendis_dir, cfg.silent_mode == 1)?;
println!(" Converting URLs...");
let configs_changed = format::convert(&bendis_dir, &root_dir)?;
let needs_update = configs_changed
|| !root_dir.join("Bender.lock").exists()
|| !root_dir.join(".bender").exists();
println!(" Updating bendis_workspace/.gitignore...");
config::ensure_bendis_workspace_gitignore_entries()?;
println!(" Updating root .gitignore...");
config::ensure_root_gitignore_excludes_hw_target()?;
println!(" Checking for changes...");
if needs_update {
println!(" Detected changes, updating dependencies...");
cleanup_root_files(&root_dir, hard)?;
run_bender_update_in_root()?;
} else {
println!(" No changes detected, skipping bender update");
}
println!(" Cleaning up cache...");
cleanup_bendis_workspace_bender_dir(&bendis_dir, cfg.storage_saving_mode == 1)?;
verify_completion(&root_dir)?;
if let Some(warning_msg) = gitignore_warning {
eprintln!("\n{}", "warning:".yellow().bold());
eprintln!(" {}", warning_msg);
eprintln!(" Some files in bendis_workspace/ may be tracked by git unexpectedly.");
eprintln!(" Please review bendis_workspace/.gitignore and ensure it contains the required entries.");
eprintln!(" To disable this check, set 'gitignore_check = 0' in: {}",
"bendis config".cyan());
}
if hard {
println!("Preparing AegisRTL workspace...");
let result = hardening::run(&root_dir)?;
match result.status {
HardeningStatus::Completed => {
println!("Hardening completed. Changed RTL files: {}", result.changed_files.len());
}
HardeningStatus::Skipped => {
println!("Hardening skipped: aegisrtl/scripts/harden.sh was not found");
}
}
println!("Hardened RTL dependencies activated");
}
println!("Done");
Ok(())
}
fn run_bender_update_in_bendis(bendis_dir: &Path, silent: bool) -> Result<()> {
let mut cmd = Command::new("bender");
cmd.args(&["-d", "./bendis_workspace", "update"]);
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
let mut child = cmd.spawn()
.context("Failed to run bender. Is bender installed and in PATH?")?;
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();
let mut stdout_reader = BufReader::new(stdout);
let mut stderr_reader = BufReader::new(stderr);
let mut log_buffer = Vec::new();
let mut line = String::new();
while stdout_reader.read_line(&mut line).unwrap_or(0) > 0 {
if !silent {
print!("{}", line);
}
log_buffer.push(line.clone());
line.clear();
}
while stderr_reader.read_line(&mut line).unwrap_or(0) > 0 {
if !silent {
eprint!("{}", line);
}
log_buffer.push(line.clone());
line.clear();
}
let status = child.wait()
.context("Failed to wait for bender process")?;
if !status.success() {
eprintln!("\n{}", "Error: bender update failed in bendis_workspace/".red());
eprintln!("\nCommand output:");
for log_line in &log_buffer {
eprint!("{}", log_line);
}
let has_fetch_error = log_buffer.iter().any(|line| {
line.contains("Fetching Dependency") &&
line.contains("cannot satisfy requirement") &&
line.contains("may need fetch")
});
if has_fetch_error {
eprintln!("\n{}", "Possible solution:".yellow());
eprintln!(" 1. Delete {} directory", "bendis_workspace/.bender/".cyan());
eprintln!(" 2. Check your internet connection");
eprintln!(" 3. Run {} again", "bendis update".cyan());
}
bail!("error: bender update in bendis_workspace/ failed");
}
let lock_file = bendis_dir.join("Bender.lock");
if !lock_file.exists() {
bail!("error: failed to generate bendis_workspace/Bender.lock");
}
Ok(())
}
fn cleanup_root_files(root_dir: &Path, preserve_bender_cache: bool) -> Result<()> {
let old_lock = root_dir.join("Bender.lock");
if old_lock.exists() {
fs::remove_file(&old_lock)
.context("Failed to remove old Bender.lock")?;
}
let old_bender_dir = root_dir.join(".bender");
if old_bender_dir.exists() && !preserve_bender_cache {
fs::remove_dir_all(&old_bender_dir)
.context("Failed to remove old .bender/ directory")?;
}
Ok(())
}
fn run_bender_update_in_root() -> Result<()> {
let mut cmd = Command::new("bender");
cmd.arg("update");
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
let mut child = cmd.spawn()
.context("Failed to run bender in root directory")?;
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();
let mut stdout_reader = BufReader::new(stdout);
let mut stderr_reader = BufReader::new(stderr);
let mut log_buffer = Vec::new();
let mut line = String::new();
while stdout_reader.read_line(&mut line).unwrap_or(0) > 0 {
print!("{}", line);
log_buffer.push(line.clone());
line.clear();
}
while stderr_reader.read_line(&mut line).unwrap_or(0) > 0 {
eprint!("{}", line);
log_buffer.push(line.clone());
line.clear();
}
let status = child.wait()
.context("Failed to wait for bender process")?;
if !status.success() {
eprintln!("\n{}", "Error: bender update failed in root directory".red());
eprintln!("\nCommand output:");
for log_line in &log_buffer {
eprint!("{}", log_line);
}
let has_fetch_error = log_buffer.iter().any(|line| {
line.contains("Fetching Dependency") &&
line.contains("cannot satisfy requirement") &&
line.contains("may need fetch")
});
if has_fetch_error {
eprintln!("\n{}", "Possible solution:".yellow());
eprintln!(" 1. Delete {} directory", ".bender/".cyan());
eprintln!(" 2. Check your internet connection");
eprintln!(" 3. Run {} again", "bendis update".cyan());
}
bail!("error: bender update in root directory failed");
}
Ok(())
}
fn cleanup_bendis_workspace_bender_dir(bendis_dir: &Path, full_cleanup: bool) -> Result<()> {
if full_cleanup {
let bender_dir = bendis_dir.join(".bender");
if bender_dir.exists() {
fs::remove_dir_all(&bender_dir)
.context("Failed to remove bendis_workspace/.bender/")?;
}
} else {
}
Ok(())
}
fn verify_completion(root_dir: &Path) -> Result<()> {
let mut missing = Vec::new();
if !root_dir.join("Bender.yml").exists() {
missing.push("Bender.yml");
}
if !root_dir.join(".bender.yml").exists() {
missing.push(".bender.yml");
}
if !root_dir.join("Bender.lock").exists() {
missing.push("Bender.lock");
}
if !root_dir.join(".bender").exists() {
missing.push(".bender/");
}
if !missing.is_empty() {
bail!("error: verification failed, missing: {}", missing.join(", "));
}
Ok(())
}