use std::path::Path; use anyhow::{Result, Context}; use git2::Repository; use colored::*; use std::fs; const FTC_SDK_URL: &str = "https://github.com/FIRST-Tech-Challenge/FtcRobotController.git"; const FTC_SDK_VERSION: &str = "v10.1.1"; pub fn install(sdk_path: &Path, android_sdk_path: &Path) -> Result<()> { if sdk_path.exists() { println!("{} FTC SDK already installed at: {}", "✓".green(), sdk_path.display() ); // Make sure local.properties exists even if SDK was already installed create_local_properties(sdk_path, android_sdk_path)?; return check_version(sdk_path); } println!("{}", "Installing FTC SDK...".bright_yellow()); println!("Cloning from: {}", FTC_SDK_URL); println!("Version: {}", FTC_SDK_VERSION); // Clone the repository let repo = Repository::clone(FTC_SDK_URL, sdk_path) .context("Failed to clone FTC SDK")?; // Checkout specific version let obj = repo.revparse_single(FTC_SDK_VERSION)?; repo.checkout_tree(&obj, None)?; repo.set_head_detached(obj.id())?; // Create local.properties with Android SDK path create_local_properties(sdk_path, android_sdk_path)?; println!("{} FTC SDK installed successfully", "✓".green()); Ok(()) } fn create_local_properties(sdk_path: &Path, android_sdk_path: &Path) -> Result<()> { // Convert path to use forward slashes (works on both Windows and Unix) let android_sdk_str = android_sdk_path .display() .to_string() .replace("\\", "/"); let local_properties = format!("sdk.dir={}\n", android_sdk_str); let properties_path = sdk_path.join("local.properties"); fs::write(&properties_path, local_properties) .context("Failed to create local.properties")?; println!("{} Created local.properties with Android SDK path", "✓".green()); Ok(()) } fn check_version(sdk_path: &Path) -> Result<()> { let repo = Repository::open(sdk_path)?; // Get current HEAD let head = repo.head()?; let commit = head.peel_to_commit()?; // Try to find a tag for this commit let tag_names = repo.tag_names(None)?; let tags: Vec<_> = tag_names .iter() .filter_map(|t| t) .collect(); println!("Current version: {}", tags.first() .map(|t| t.to_string()) .unwrap_or_else(|| format!("{}", commit.id())) ); Ok(()) } pub fn update(sdk_path: &Path) -> Result<()> { println!("{}", "Updating FTC SDK...".bright_yellow()); let repo = Repository::open(sdk_path) .context("FTC SDK not found or not a git repository")?; // Fetch latest let mut remote = repo.find_remote("origin")?; remote.fetch(&["refs/tags/*:refs/tags/*"], None, None)?; // Checkout latest version let obj = repo.revparse_single(FTC_SDK_VERSION)?; repo.checkout_tree(&obj, None)?; repo.set_head_detached(obj.id())?; println!("{} FTC SDK updated to {}", "✓".green(), FTC_SDK_VERSION); Ok(()) } pub fn verify(sdk_path: &Path) -> Result<()> { if !sdk_path.exists() { anyhow::bail!("FTC SDK not found at: {}", sdk_path.display()); } // Check for essential directories let team_code = sdk_path.join("TeamCode"); let ftc_robot_controller = sdk_path.join("FtcRobotController"); if !team_code.exists() || !ftc_robot_controller.exists() { anyhow::bail!("FTC SDK incomplete: missing essential directories"); } Ok(()) } pub fn get_version(sdk_path: &Path) -> Result { let repo = Repository::open(sdk_path)?; let head = repo.head()?; let commit = head.peel_to_commit()?; // Try to find a tag let tags = repo.tag_names(None)?; for tag in tags.iter().flatten() { let tag_ref = repo.find_reference(&format!("refs/tags/{}", tag))?; if let Ok(tag_commit) = tag_ref.peel_to_commit() { if tag_commit.id() == commit.id() { return Ok(tag.to_string()); } } } Ok(format!("commit-{}", &commit.id().to_string()[..8])) }