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 super::hardening::{self, HardeningStatus};
use crate::converter::format;
use crate::utils::config::{self, BendisConfig};
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()?;
restore_root_override_for_workspace_update(&bendis_dir, &root_dir)?;
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 restore_root_override_for_workspace_update(bendis_dir: &Path, root_dir: &Path) -> Result<()> {
let source = bendis_dir.join(".bender.yml");
if !source.is_file() {
bail!("error: bendis_workspace/.bender.yml not found");
}
fs::copy(&source, root_dir.join(".bender.yml"))
.context("Failed to restore root .bender.yml from bendis_workspace")?;
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(())
}
#[cfg(test)]
mod tests {
use super::restore_root_override_for_workspace_update;
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn workspace_update_restores_root_override_before_running_bender() {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let root: PathBuf = std::env::temp_dir().join(format!("bendis-restore-{nonce}"));
let workspace = root.join("bendis_workspace");
fs::create_dir_all(&workspace).unwrap();
fs::write(
root.join(".bender.yml"),
"overrides:\n core: { path: '../aegisrtl/core' }\n",
)
.unwrap();
fs::write(
workspace.join(".bender.yml"),
"overrides:\n core: { git: 'ssh://ihep/core.git', rev: abc }\n",
)
.unwrap();
restore_root_override_for_workspace_update(&workspace, &root).unwrap();
assert_eq!(
fs::read_to_string(root.join(".bender.yml")).unwrap(),
fs::read_to_string(workspace.join(".bender.yml")).unwrap()
);
fs::remove_dir_all(root).unwrap();
}
}