use std::path::Path; use anyhow::{Result, Context}; use indicatif::{ProgressBar, ProgressStyle}; use std::fs::File; use std::io::Write; use colored::*; use super::proxy::ProxyConfig; const ANDROID_SDK_URL_LINUX: &str = "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip"; const ANDROID_SDK_URL_MAC: &str = "https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip"; const ANDROID_SDK_URL_WINDOWS: &str = "https://dl.google.com/android/repository/commandlinetools-win-11076708_latest.zip"; pub fn install(sdk_path: &Path, proxy: &ProxyConfig) -> Result<()> { // Check if SDK exists AND is complete if sdk_path.exists() { match verify(sdk_path) { Ok(_) => { println!("{} Android SDK already installed at: {}", "✓".green(), sdk_path.display() ); return Ok(()); } Err(_) => { println!("{} Android SDK found but incomplete, reinstalling...", "⚠".yellow() ); // Continue with installation } } } println!("{}", "Installing Android SDK...".bright_yellow()); let url = if cfg!(target_os = "windows") { ANDROID_SDK_URL_WINDOWS } else if cfg!(target_os = "macos") { ANDROID_SDK_URL_MAC } else { ANDROID_SDK_URL_LINUX }; // Download println!("Downloading from: {}", url); proxy.print_status(); let client = proxy.client()?; let response = client.get(url) .send() .map_err(|e| { super::proxy::print_offline_instructions(); anyhow::anyhow!("Failed to download Android SDK: {}", e) })?; let total_size = response.content_length().unwrap_or(0); let pb = ProgressBar::new(total_size); pb.set_style( ProgressStyle::default_bar() .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})") .unwrap() .progress_chars("#>-"), ); let temp_zip = sdk_path.parent().unwrap().join("android-sdk-temp.zip"); let mut file = File::create(&temp_zip)?; let content = response.bytes()?; pb.inc(content.len() as u64); file.write_all(&content)?; pb.finish_with_message("Download complete"); // Extract println!("Extracting..."); let file = File::open(&temp_zip)?; let mut archive = zip::ZipArchive::new(file)?; std::fs::create_dir_all(sdk_path)?; archive.extract(sdk_path) .context("Failed to extract Android SDK")?; // Cleanup std::fs::remove_file(&temp_zip)?; // The zip extracts to cmdline-tools/ but we need it in cmdline-tools/latest/ let extracted_tools = sdk_path.join("cmdline-tools"); let target_location = sdk_path.join("cmdline-tools").join("latest"); if extracted_tools.exists() && !target_location.exists() { println!("Reorganizing cmdline-tools directory structure..."); let temp_dir = sdk_path.join("cmdline-tools-temp"); std::fs::rename(&extracted_tools, &temp_dir) .context("Failed to rename cmdline-tools to temp directory")?; std::fs::create_dir_all(&target_location) .context("Failed to create cmdline-tools/latest directory")?; for entry in std::fs::read_dir(&temp_dir)? { let entry = entry?; let dest = target_location.join(entry.file_name()); std::fs::rename(entry.path(), dest) .with_context(|| format!("Failed to move {} to latest/", entry.file_name().to_string_lossy()))?; } std::fs::remove_dir_all(&temp_dir) .context("Failed to remove temporary directory")?; } // Install required packages install_packages(sdk_path)?; println!("{} Android SDK installed successfully", "✓".green()); Ok(()) } fn install_packages(sdk_path: &Path) -> Result<()> { println!("Installing Android SDK packages..."); let sdkmanager_path = sdk_path.join("cmdline-tools").join("latest").join("bin"); let sdkmanager = if cfg!(target_os = "windows") { sdkmanager_path.join("sdkmanager.bat") } else { sdkmanager_path.join("sdkmanager") }; if !sdkmanager.exists() { anyhow::bail!( "sdkmanager not found at expected location: {}\n\ Directory structure may be incorrect.", sdkmanager.display() ); } println!("Found sdkmanager at: {}", sdkmanager.display()); run_sdkmanager(&sdkmanager, sdk_path) } fn run_sdkmanager(sdkmanager: &Path, sdk_root: &Path) -> Result<()> { use std::process::{Command, Stdio}; use std::io::Write; println!("Accepting licenses..."); // Build command based on OS let mut cmd = if cfg!(target_os = "windows") { let mut c = Command::new("cmd"); c.arg("/c"); c.arg(sdkmanager); c } else { Command::new(sdkmanager) }; cmd.arg(format!("--sdk_root={}", sdk_root.display())) .arg("--licenses") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let mut child = cmd.spawn() .context("Failed to spawn sdkmanager for licenses")?; // Write 'y' responses to accept all licenses if let Some(mut stdin) = child.stdin.take() { // Create a string with many 'y' responses let responses = "y\n".repeat(20); stdin.write_all(responses.as_bytes()) .context("Failed to write license responses")?; // Explicitly drop stdin to close the pipe drop(stdin); } let output = child.wait_with_output() .context("Failed to wait for license acceptance")?; if !output.status.success() { eprintln!("License stderr: {}", String::from_utf8_lossy(&output.stderr)); eprintln!("License stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("{} License acceptance may have failed, continuing anyway...", "⚠".yellow()); } else { println!("{} Licenses accepted", "✓".green()); } println!("Installing SDK packages (this may take a few minutes)..."); // Build command for package installation let mut cmd = if cfg!(target_os = "windows") { let mut c = Command::new("cmd"); c.arg("/c"); c.arg(sdkmanager); c } else { Command::new(sdkmanager) }; let status = cmd .arg(format!("--sdk_root={}", sdk_root.display())) .arg("platform-tools") .arg("platforms;android-34") .arg("build-tools;34.0.0") .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .status() .context("Failed to run sdkmanager for package installation")?; if !status.success() { anyhow::bail!("Failed to install Android SDK packages"); } Ok(()) } pub fn verify(sdk_path: &Path) -> Result<()> { if !sdk_path.exists() { anyhow::bail!( "Android SDK not found at: {}\n\ Run 'weevil sdk install' to download it automatically,\n\ or install manually from: https://developer.android.com/studio#command-tools", sdk_path.display() ); } let platform_tools = sdk_path.join("platform-tools"); if !platform_tools.exists() { anyhow::bail!( "Android SDK incomplete: platform-tools not found\n\ Expected at: {}\n\ Run 'weevil sdk install' to complete the installation", platform_tools.display() ); } Ok(()) }