use anyhow::{Context, Result};
use std::path::Path;
use std::process::Command;
pub fn handle_build_command(release: bool, verbose: bool) -> Result<()> {
verify_cargo_project()?;
run_cargo_build(release, verbose)?;
print_build_success_message(release);
Ok(())
}
fn verify_cargo_project() -> Result<()> {
let cargo_toml = Path::new("Cargo.toml");
if !cargo_toml.exists() {
anyhow::bail!(
"Cargo.toml not found. Run this command from a Cargo project directory.\n\
Hint: Use `ruchy new <name>` to create a new Ruchy project."
);
}
Ok(())
}
fn run_cargo_build(release: bool, verbose: bool) -> Result<()> {
let mut cmd = Command::new("cargo");
cmd.arg("build");
if release {
cmd.arg("--release");
}
if verbose {
cmd.arg("--verbose");
let mode = if release { "release" } else { "debug" };
println!("Running: cargo build --{mode}");
}
let output = cmd
.output()
.context("Failed to run cargo build - ensure cargo is installed")?;
if !output.stdout.is_empty() {
let stdout = String::from_utf8_lossy(&output.stdout);
print!("{stdout}");
}
if !output.stderr.is_empty() {
let stderr = String::from_utf8_lossy(&output.stderr);
eprint!("{stderr}");
}
if !output.status.success() {
anyhow::bail!("cargo build failed");
}
Ok(())
}
fn print_build_success_message(release: bool) {
let mode = if release { "release" } else { "debug" };
println!("Build complete ({mode} mode)");
println!("Run `cargo run` to execute the program");
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use tempfile::TempDir;
fn create_test_project() -> (TempDir, String) {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let project_name = "test_project";
let project_path = temp_dir.path().join(project_name);
std::process::Command::new("cargo")
.arg("new")
.arg(project_name)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run cargo new");
(
temp_dir,
project_path
.to_str()
.expect("Project path should be valid UTF-8")
.to_string(),
)
}
#[test]
fn test_verify_cargo_project_missing_cargo_toml() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let _original_dir = env::current_dir().expect("Failed to get current dir");
env::set_current_dir(temp_dir.path()).expect("Failed to change dir");
let result = verify_cargo_project();
env::set_current_dir(_original_dir).expect("Failed to restore dir");
assert!(result.is_err(), "Should fail when Cargo.toml doesn't exist");
assert!(
result
.unwrap_err()
.to_string()
.contains("Cargo.toml not found"),
"Error message should mention Cargo.toml"
);
}
#[test]
fn test_verify_cargo_project_success() {
let (_temp_dir, project_path) = create_test_project();
let _original_dir = env::current_dir().expect("Failed to get current dir");
env::set_current_dir(&project_path).expect("Failed to change dir");
let result = verify_cargo_project();
env::set_current_dir(_original_dir).expect("Failed to restore dir");
assert!(result.is_ok(), "Should succeed when Cargo.toml exists");
}
#[test]
fn test_print_build_success_message_debug() {
print_build_success_message(false);
}
#[test]
fn test_print_build_success_message_release() {
print_build_success_message(true);
}
#[test]
fn test_verify_cargo_project_idempotent() {
let (_temp_dir, project_path) = create_test_project();
let _original_dir = env::current_dir().expect("Failed to get current dir");
env::set_current_dir(&project_path).expect("Failed to change dir");
let result1 = verify_cargo_project();
let result2 = verify_cargo_project();
env::set_current_dir(_original_dir).expect("Failed to restore dir");
assert_eq!(
result1.is_ok(),
result2.is_ok(),
"Multiple calls should have same result"
);
}
#[test]
fn test_print_build_success_message_formats() {
print_build_success_message(true);
print_build_success_message(false);
}
#[test]
fn test_verify_cargo_project_cwd() {
let result = verify_cargo_project();
let _ = result;
}
#[test]
fn test_run_cargo_build_no_project() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let _original_dir = env::current_dir().expect("Failed to get current dir");
env::set_current_dir(temp_dir.path()).expect("Failed to change dir");
std::fs::write(
temp_dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)
.expect("Failed to write Cargo.toml");
std::fs::create_dir(temp_dir.path().join("src")).ok();
std::fs::write(temp_dir.path().join("src/main.rs"), "fn main() {}")
.expect("Failed to write main.rs");
let result = run_cargo_build(false, false);
env::set_current_dir(_original_dir).expect("Failed to restore dir");
assert!(result.is_ok());
}
#[test]
fn test_run_cargo_build_verbose() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let _original_dir = env::current_dir().expect("Failed to get current dir");
env::set_current_dir(temp_dir.path()).expect("Failed to change dir");
std::fs::write(
temp_dir.path().join("Cargo.toml"),
"[package]\nname = \"test2\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)
.expect("Failed to write Cargo.toml");
std::fs::create_dir(temp_dir.path().join("src")).ok();
std::fs::write(
temp_dir.path().join("src/main.rs"),
"fn main() { println!(\"Hello\"); }",
)
.expect("Failed to write main.rs");
let result = run_cargo_build(false, true); env::set_current_dir(_original_dir).expect("Failed to restore dir");
assert!(result.is_ok());
}
#[test]
fn test_handle_build_command_no_cargo() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let _original_dir = env::current_dir().expect("Failed to get current dir");
env::set_current_dir(temp_dir.path()).expect("Failed to change dir");
let result = handle_build_command(false, false);
env::set_current_dir(_original_dir).expect("Failed to restore dir");
assert!(result.is_err());
}
#[test]
fn test_handle_build_command_release_mode() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let _original_dir = env::current_dir().expect("Failed to get current dir");
env::set_current_dir(temp_dir.path()).expect("Failed to change dir");
let _ = handle_build_command(true, false);
env::set_current_dir(_original_dir).expect("Failed to restore dir");
}
#[test]
fn test_handle_build_command_all_options() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let _original_dir = env::current_dir().expect("Failed to get current dir");
env::set_current_dir(temp_dir.path()).expect("Failed to change dir");
let _ = handle_build_command(true, true);
env::set_current_dir(_original_dir).expect("Failed to restore dir");
}
}