From f83936cbe8d7d75708649632bda2bb28d4721c3b Mon Sep 17 00:00:00 2001 From: Eric Ratliff Date: Thu, 29 Jan 2026 22:01:35 -0600 Subject: [PATCH] Included new setup command. It has not been tested yet --- src/commands/mod.rs | 3 +- src/commands/setup.rs | 514 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 9 + 3 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 src/commands/setup.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 94262b8..623c03f 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,4 +2,5 @@ pub mod new; pub mod upgrade; pub mod deploy; pub mod sdk; -pub mod config; \ No newline at end of file +pub mod config; +pub mod setup; \ No newline at end of file diff --git a/src/commands/setup.rs b/src/commands/setup.rs new file mode 100644 index 0000000..975b814 --- /dev/null +++ b/src/commands/setup.rs @@ -0,0 +1,514 @@ +use anyhow::{Result, Context, bail}; +use std::path::{Path, PathBuf}; +use std::process::Command; +use colored::*; + +use crate::sdk::SdkConfig; +use crate::project::ProjectConfig; + +/// Setup development environment - either system-wide or for a specific project +pub fn setup_environment(project_path: Option<&str>) -> Result<()> { + match project_path { + Some(path) => setup_project(path), + None => setup_system(), + } +} + +/// Setup system-wide development environment with default SDKs +fn setup_system() -> Result<()> { + println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); + println!("{}", " System Setup - Preparing FTC Development Environment".bright_cyan().bold()); + println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); + println!(); + + let mut issues = Vec::new(); + let mut installed = Vec::new(); + + // Check and install SDKs + let sdk_config = SdkConfig::new()?; + + // 1. Check Java + println!("{}", "Checking Java JDK...".bright_yellow()); + match check_java() { + Ok(version) => { + println!("{} Java JDK {} found", "✓".green(), version); + installed.push(format!("Java JDK {}", version)); + } + Err(e) => { + println!("{} {}", "✗".red(), e); + issues.push(("Java JDK", get_java_install_instructions())); + } + } + println!(); + + // 2. Check/Install FTC SDK + println!("{}", "Checking FTC SDK...".bright_yellow()); + if sdk_config.ftc_sdk_path.exists() { + match crate::sdk::ftc::verify(&sdk_config.ftc_sdk_path) { + Ok(_) => { + let version = crate::sdk::ftc::get_version(&sdk_config.ftc_sdk_path) + .unwrap_or_else(|_| "unknown".to_string()); + println!("{} FTC SDK {} found at: {}", + "✓".green(), + version, + sdk_config.ftc_sdk_path.display() + ); + installed.push(format!("FTC SDK {}", version)); + } + Err(_) => { + println!("{} FTC SDK found but incomplete, reinstalling...", "⚠".yellow()); + crate::sdk::ftc::install(&sdk_config.ftc_sdk_path, &sdk_config.android_sdk_path)?; + let version = crate::sdk::ftc::get_version(&sdk_config.ftc_sdk_path) + .unwrap_or_else(|_| "unknown".to_string()); + installed.push(format!("FTC SDK {} (installed)", version)); + } + } + } else { + println!("FTC SDK not found. Installing..."); + crate::sdk::ftc::install(&sdk_config.ftc_sdk_path, &sdk_config.android_sdk_path)?; + let version = crate::sdk::ftc::get_version(&sdk_config.ftc_sdk_path) + .unwrap_or_else(|_| "unknown".to_string()); + installed.push(format!("FTC SDK {} (installed)", version)); + } + println!(); + + // 3. Check/Install Android SDK + println!("{}", "Checking Android SDK...".bright_yellow()); + if sdk_config.android_sdk_path.exists() { + match crate::sdk::android::verify(&sdk_config.android_sdk_path) { + Ok(_) => { + println!("{} Android SDK found at: {}", + "✓".green(), + sdk_config.android_sdk_path.display() + ); + installed.push("Android SDK".to_string()); + } + Err(_) => { + println!("{} Android SDK found but incomplete, reinstalling...", "⚠".yellow()); + crate::sdk::android::install(&sdk_config.android_sdk_path)?; + installed.push("Android SDK (installed)".to_string()); + } + } + } else { + println!("Android SDK not found. Installing..."); + crate::sdk::android::install(&sdk_config.android_sdk_path)?; + installed.push("Android SDK (installed)".to_string()); + } + println!(); + + // 4. Check ADB + println!("{}", "Checking ADB (Android Debug Bridge)...".bright_yellow()); + match check_adb(&sdk_config.android_sdk_path) { + Ok(version) => { + println!("{} ADB {} found", "✓".green(), version); + installed.push(format!("ADB {}", version)); + } + Err(e) => { + println!("{} {}", "⚠".yellow(), e); + println!(" ADB is included in Android SDK platform-tools"); + println!(" Add to PATH: {}", sdk_config.android_sdk_path.join("platform-tools").display()); + } + } + println!(); + + // 5. Check Gradle + println!("{}", "Checking Gradle...".bright_yellow()); + match check_gradle() { + Ok(version) => { + println!("{} Gradle {} found", "✓".green(), version); + installed.push(format!("Gradle {}", version)); + } + Err(e) => { + println!("{} {}", "⚠".yellow(), e); + println!(" Note: Weevil projects include Gradle wrapper, so this is optional"); + } + } + println!(); + + // Print summary + print_system_summary(&installed, &issues, &sdk_config); + + Ok(()) +} + +/// Setup dependencies for a specific project by reading its .weevil.toml +fn setup_project(project_path: &str) -> Result<()> { + let project_path = PathBuf::from(project_path); + + if !project_path.exists() { + bail!("Project directory not found: {}", project_path.display()); + } + + println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); + println!("{}", " Project Setup - Installing Dependencies".bright_cyan().bold()); + println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); + println!(); + + // Load project configuration + println!("{}", "Reading project configuration...".bright_yellow()); + let config = ProjectConfig::load(&project_path) + .context("Failed to load .weevil.toml")?; + + println!(); + println!("{}", "Project Configuration:".bright_yellow().bold()); + println!(" Project: {}", config.project_name.bright_white()); + println!(" FTC SDK: {} ({})", + config.ftc_sdk_version.bright_white(), + config.ftc_sdk_path.display() + ); + println!(" Android SDK: {}", config.android_sdk_path.display()); + println!(); + + let mut installed = Vec::new(); + let mut issues = Vec::new(); + + // 1. Check Java + println!("{}", "Checking Java JDK...".bright_yellow()); + match check_java() { + Ok(version) => { + println!("{} Java JDK {} found", "✓".green(), version); + installed.push(format!("Java JDK {}", version)); + } + Err(e) => { + println!("{} {}", "✗".red(), e); + issues.push(("Java JDK", get_java_install_instructions())); + } + } + println!(); + + // 2. Check/Install project-specific FTC SDK + println!("{}", format!("Checking FTC SDK {}...", config.ftc_sdk_version).bright_yellow()); + if config.ftc_sdk_path.exists() { + match crate::sdk::ftc::verify(&config.ftc_sdk_path) { + Ok(_) => { + println!("{} FTC SDK {} found at: {}", + "✓".green(), + config.ftc_sdk_version, + config.ftc_sdk_path.display() + ); + installed.push(format!("FTC SDK {}", config.ftc_sdk_version)); + } + Err(_) => { + println!("{} FTC SDK path exists but is invalid", "✗".red()); + println!(" Expected at: {}", config.ftc_sdk_path.display()); + println!(); + println!("{}", "Solution:".bright_yellow().bold()); + println!(" The .weevil.toml specifies an FTC SDK location that doesn't exist or is incomplete."); + println!(" You have two options:"); + println!(); + println!(" 1. Update the project to use a different SDK:"); + println!(" weevil config {} --set-sdk ", project_path.display()); + println!(); + println!(" 2. Install the SDK at the expected location:"); + println!(" # Clone FTC SDK to the expected path"); + println!(" git clone https://github.com/FIRST-Tech-Challenge/FtcRobotController.git \\"); + println!(" {}", config.ftc_sdk_path.display()); + println!(" cd {}", config.ftc_sdk_path.display()); + println!(" git checkout {}", config.ftc_sdk_version); + bail!("FTC SDK verification failed"); + } + } + } else { + println!("{} FTC SDK not found at: {}", "✗".red(), config.ftc_sdk_path.display()); + println!(); + + // Try to install it automatically + println!("{}", "Attempting automatic installation...".bright_yellow()); + match crate::sdk::ftc::install(&config.ftc_sdk_path, &config.android_sdk_path) { + Ok(_) => { + println!("{} FTC SDK {} installed successfully", + "✓".green(), + config.ftc_sdk_version + ); + installed.push(format!("FTC SDK {} (installed)", config.ftc_sdk_version)); + } + Err(e) => { + println!("{} Automatic installation failed: {}", "✗".red(), e); + println!(); + println!("{}", "Manual Installation Required:".bright_yellow().bold()); + println!(" git clone https://github.com/FIRST-Tech-Challenge/FtcRobotController.git \\"); + println!(" {}", config.ftc_sdk_path.display()); + println!(" cd {}", config.ftc_sdk_path.display()); + println!(" git checkout {}", config.ftc_sdk_version); + bail!("FTC SDK installation failed"); + } + } + } + println!(); + + // 3. Check/Install Android SDK + println!("{}", "Checking Android SDK...".bright_yellow()); + if config.android_sdk_path.exists() { + match crate::sdk::android::verify(&config.android_sdk_path) { + Ok(_) => { + println!("{} Android SDK found at: {}", + "✓".green(), + config.android_sdk_path.display() + ); + installed.push("Android SDK".to_string()); + } + Err(_) => { + println!("{} Android SDK found but incomplete, reinstalling...", "⚠".yellow()); + crate::sdk::android::install(&config.android_sdk_path)?; + installed.push("Android SDK (installed)".to_string()); + } + } + } else { + println!("Android SDK not found. Installing..."); + crate::sdk::android::install(&config.android_sdk_path)?; + installed.push("Android SDK (installed)".to_string()); + } + println!(); + + // 4. Check ADB + println!("{}", "Checking ADB...".bright_yellow()); + match check_adb(&config.android_sdk_path) { + Ok(version) => { + println!("{} ADB {} found", "✓".green(), version); + installed.push(format!("ADB {}", version)); + } + Err(e) => { + println!("{} {}", "⚠".yellow(), e); + println!(" Add to PATH: {}", config.android_sdk_path.join("platform-tools").display()); + } + } + println!(); + + // 5. Check Gradle wrapper in project + println!("{}", "Checking Gradle wrapper...".bright_yellow()); + let gradlew = if cfg!(target_os = "windows") { + project_path.join("gradlew.bat") + } else { + project_path.join("gradlew") + }; + + if gradlew.exists() { + println!("{} Gradle wrapper found in project", "✓".green()); + installed.push("Gradle wrapper".to_string()); + } else { + println!("{} Gradle wrapper not found in project", "⚠".yellow()); + println!(" Run 'weevil upgrade {}' to regenerate project files", project_path.display()); + } + println!(); + + // Print summary + print_project_summary(&installed, &issues, &config, &project_path); + + Ok(()) +} + +fn check_java() -> Result { + let output = Command::new("java") + .arg("-version") + .output(); + + match output { + Ok(out) => { + let stderr = String::from_utf8_lossy(&out.stderr); + // Java version is typically in stderr, format: java version "11.0.x" or openjdk version "11.0.x" + for line in stderr.lines() { + if line.contains("version") { + if let Some(version_str) = line.split('"').nth(1) { + return Ok(version_str.to_string()); + } + } + } + Ok("installed (version unknown)".to_string()) + } + Err(_) => bail!("Java JDK not found in PATH"), + } +} + +fn check_adb(android_sdk_path: &Path) -> Result { + // First try system PATH + let output = Command::new("adb") + .arg("version") + .output(); + + if let Ok(out) = output { + if out.status.success() { + let stdout = String::from_utf8_lossy(&out.stdout); + for line in stdout.lines() { + if line.starts_with("Android Debug Bridge version") { + return Ok(line.replace("Android Debug Bridge version ", "")); + } + } + return Ok("installed (version unknown)".to_string()); + } + } + + // Try Android SDK location + let adb_path = if cfg!(target_os = "windows") { + android_sdk_path.join("platform-tools").join("adb.exe") + } else { + android_sdk_path.join("platform-tools").join("adb") + }; + + if adb_path.exists() { + bail!("ADB found in Android SDK but not in PATH") + } else { + bail!("ADB not found") + } +} + +fn check_gradle() -> Result { + let output = Command::new("gradle") + .arg("--version") + .output(); + + match output { + Ok(out) => { + let stdout = String::from_utf8_lossy(&out.stdout); + for line in stdout.lines() { + if line.starts_with("Gradle") { + return Ok(line.replace("Gradle ", "")); + } + } + Ok("installed (version unknown)".to_string()) + } + Err(_) => bail!("Gradle not found in PATH (optional)"), + } +} + +fn get_java_install_instructions() -> String { + if cfg!(target_os = "windows") { + format!( + "Java JDK is required but not found.\n\ + \n\ + To install Java 11 on Windows:\n\ + \n\ + 1. Download from: {}\n\ + 2. Run the installer\n\ + 3. Add Java to your PATH (installer usually does this)\n\ + 4. Run 'weevil setup' again to verify\n\ + \n\ + Verify installation: java -version", + "https://adoptium.net/temurin/releases/?version=11".bright_white() + ) + } else if cfg!(target_os = "macos") { + format!( + "Java JDK is required but not found.\n\ + \n\ + To install Java 11 on macOS:\n\ + \n\ + Using Homebrew (recommended):\n\ + {}\n\ + \n\ + Or download from: {}\n\ + \n\ + Verify installation: java -version", + " brew install openjdk@11".bright_white(), + "https://adoptium.net/temurin/releases/?version=11".bright_white() + ) + } else { + format!( + "Java JDK is required but not found.\n\ + \n\ + To install Java 11 on Ubuntu/Debian:\n\ + {}\n\ + {}\n\ + \n\ + To install on Fedora/RHEL:\n\ + {}\n\ + \n\ + Verify installation: java -version", + " sudo apt update".bright_white(), + " sudo apt install openjdk-11-jdk".bright_white(), + " sudo dnf install java-11-openjdk-devel".bright_white() + ) + } +} + +fn print_system_summary(installed: &[String], issues: &[(&str, String)], sdk_config: &SdkConfig) { + println!("{}", "═══════════════════════════════════════════════════════════".bright_green()); + println!("{}", " System Setup Summary".bright_green().bold()); + println!("{}", "═══════════════════════════════════════════════════════════".bright_green()); + println!(); + + if !installed.is_empty() { + println!("{}", "Installed Components:".bright_green().bold()); + for component in installed { + println!(" {} {}", "✓".green(), component); + } + println!(); + } + + if !issues.is_empty() { + println!("{}", "Manual Installation Required:".bright_yellow().bold()); + println!(); + for (name, instructions) in issues { + println!("{} {}", "✗".red(), name.red().bold()); + println!(); + for line in instructions.lines() { + println!(" {}", line); + } + println!(); + } + } + + println!("{}", "SDK Locations:".bright_cyan().bold()); + println!(" FTC SDK: {}", sdk_config.ftc_sdk_path.display()); + println!(" Android SDK: {}", sdk_config.android_sdk_path.display()); + println!(" Cache: {}", sdk_config.cache_dir.display()); + println!(); + + if issues.is_empty() { + println!("{}", "✓ System is ready for FTC development!".bright_green().bold()); + println!(); + println!("{}", "Next steps:".bright_yellow().bold()); + println!(" Create a new project: {}", "weevil new my-robot".bright_white()); + println!(" Clone existing project: {}", "git clone && cd && weevil setup .".bright_white()); + } else { + println!("{}", "⚠ Please install the required components listed above".bright_yellow().bold()); + println!(" Then run {} to verify", "weevil setup".bright_white()); + } + println!(); +} + +fn print_project_summary(installed: &[String], issues: &[(&str, String)], config: &ProjectConfig, project_path: &Path) { + println!("{}", "═══════════════════════════════════════════════════════════".bright_green()); + println!("{}", " Project Setup Summary".bright_green().bold()); + println!("{}", "═══════════════════════════════════════════════════════════".bright_green()); + println!(); + + println!("{}", "Project Details:".bright_cyan().bold()); + println!(" Name: {}", config.project_name); + println!(" Location: {}", project_path.display()); + println!(" FTC SDK: {} at {}", config.ftc_sdk_version, config.ftc_sdk_path.display()); + println!(); + + if !installed.is_empty() { + println!("{}", "Installed Components:".bright_green().bold()); + for component in installed { + println!(" {} {}", "✓".green(), component); + } + println!(); + } + + if !issues.is_empty() { + println!("{}", "Manual Installation Required:".bright_yellow().bold()); + println!(); + for (name, instructions) in issues { + println!("{} {}", "✗".red(), name.red().bold()); + println!(); + for line in instructions.lines() { + println!(" {}", line); + } + println!(); + } + } + + if issues.is_empty() { + println!("{}", "✓ Project is ready for development!".bright_green().bold()); + println!(); + println!("{}", "Next steps:".bright_yellow().bold()); + println!(" 1. Review the code: {}", format!("cd {}", project_path.display()).bright_white()); + println!(" 2. Run tests: {}", "./gradlew test".bright_white()); + println!(" 3. Build: {}", "./build.sh (or build.bat on Windows)".bright_white()); + println!(" 4. Deploy to robot: {}", format!("weevil deploy {}", project_path.display()).bright_white()); + } else { + println!("{}", "⚠ Please install the required components listed above".bright_yellow().bold()); + println!(" Then run {} to verify", format!("weevil setup {}", project_path.display()).bright_white()); + } + println!(); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 08e9dac..fe02881 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,12 @@ enum Commands { android_sdk: Option, }, + /// Setup development environment (system or project) + Setup { + /// Path to project directory (optional - without it, sets up system) + path: Option, + }, + /// Upgrade an existing project to the latest generator version Upgrade { /// Path to the project directory @@ -99,6 +105,9 @@ fn main() -> Result<()> { Commands::New { name, ftc_sdk, android_sdk } => { commands::new::create_project(&name, ftc_sdk.as_deref(), android_sdk.as_deref()) } + Commands::Setup { path } => { + commands::setup::setup_environment(path.as_deref()) + } Commands::Upgrade { path } => { commands::upgrade::upgrade_project(&path) }