diff --git i/src/commands/new.rs w/src/commands/new.rs index aeed30a..4d219c6 100644 --- i/src/commands/new.rs +++ w/src/commands/new.rs @@ -3,12 +3,14 @@ use colored::*; use crate::sdk::SdkConfig; +use crate::sdk::proxy::ProxyConfig; use crate::project::ProjectBuilder; pub fn create_project( name: &str, ftc_sdk: Option<&str>, android_sdk: Option<&str>, + proxy: &ProxyConfig, ) -> Result<()> { // Validate project name if name.is_empty() { @@ -32,6 +34,7 @@ pub fn create_project( } println!("{}", format!("Creating FTC project: {}", name).bright_green().bold()); + proxy.print_status(); println!(); // Check system health FIRST diff --git i/src/commands/sdk.rs w/src/commands/sdk.rs index ddc3aa6..250b844 100644 --- i/src/commands/sdk.rs +++ w/src/commands/sdk.rs @@ -1,18 +1,22 @@ use anyhow::Result; use colored::*; use crate::sdk::SdkConfig; +use crate::sdk::proxy::ProxyConfig; -pub fn install_sdks() -> Result<()> { +pub fn install_sdks(proxy: &ProxyConfig) -> Result<()> { println!("{}", "Installing SDKs...".bright_yellow().bold()); println!(); + proxy.print_status(); + println!(); + let config = SdkConfig::new()?; // Install FTC SDK - crate::sdk::ftc::install(&config.ftc_sdk_path, &config.android_sdk_path)?; + crate::sdk::ftc::install(&config.ftc_sdk_path, &config.android_sdk_path, proxy)?; // Install Android SDK - crate::sdk::android::install(&config.android_sdk_path)?; + crate::sdk::android::install(&config.android_sdk_path, proxy)?; println!(); println!("{} All SDKs installed successfully", "✓".green().bold()); @@ -44,17 +48,20 @@ pub fn show_status() -> Result<()> { Ok(()) } -pub fn update_sdks() -> Result<()> { +pub fn update_sdks(proxy: &ProxyConfig) -> Result<()> { println!("{}", "Updating SDKs...".bright_yellow().bold()); println!(); + + proxy.print_status(); + println!(); let config = SdkConfig::new()?; // Update FTC SDK - crate::sdk::ftc::update(&config.ftc_sdk_path)?; + crate::sdk::ftc::update(&config.ftc_sdk_path, proxy)?; println!(); println!("{} SDKs updated successfully", "✓".green().bold()); Ok(()) -} +} \ No newline at end of file diff --git i/src/commands/setup.rs w/src/commands/setup.rs index 975b814..cbb5871 100644 --- i/src/commands/setup.rs +++ w/src/commands/setup.rs @@ -4,22 +4,26 @@ use colored::*; use crate::sdk::SdkConfig; +use crate::sdk::proxy::ProxyConfig; use crate::project::ProjectConfig; /// Setup development environment - either system-wide or for a specific project -pub fn setup_environment(project_path: Option<&str>) -> Result<()> { +pub fn setup_environment(project_path: Option<&str>, proxy: &ProxyConfig) -> Result<()> { match project_path { - Some(path) => setup_project(path), - None => setup_system(), + Some(path) => setup_project(path, proxy), + None => setup_system(proxy), } } /// Setup system-wide development environment with default SDKs -fn setup_system() -> Result<()> { +fn setup_system(proxy: &ProxyConfig) -> Result<()> { println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!("{}", " System Setup - Preparing FTC Development Environment".bright_cyan().bold()); println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!(); + + proxy.print_status(); + println!(); let mut issues = Vec::new(); let mut installed = Vec::new(); @@ -57,18 +61,34 @@ fn setup_system() -> Result<()> { } 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)); + match crate::sdk::ftc::install(&sdk_config.ftc_sdk_path, &sdk_config.android_sdk_path, proxy) { + Ok(_) => { + 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)); + } + Err(e) => { + println!("{} {}", "✗".red(), e); + print_ftc_manual_fallback(&sdk_config); + issues.push(("FTC SDK", "See manual installation instructions above.".to_string())); + } + } } } } 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)); + match crate::sdk::ftc::install(&sdk_config.ftc_sdk_path, &sdk_config.android_sdk_path, proxy) { + Ok(_) => { + 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)); + } + Err(e) => { + println!("{} {}", "✗".red(), e); + print_ftc_manual_fallback(&sdk_config); + issues.push(("FTC SDK", "See manual installation instructions above.".to_string())); + } + } } println!(); @@ -85,14 +105,26 @@ fn setup_system() -> Result<()> { } 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()); + match crate::sdk::android::install(&sdk_config.android_sdk_path, proxy) { + Ok(_) => installed.push("Android SDK (installed)".to_string()), + Err(e) => { + println!("{} {}", "✗".red(), e); + print_android_manual_fallback(&sdk_config); + issues.push(("Android SDK", "See manual installation instructions above.".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()); + match crate::sdk::android::install(&sdk_config.android_sdk_path, proxy) { + Ok(_) => installed.push("Android SDK (installed)".to_string()), + Err(e) => { + println!("{} {}", "✗".red(), e); + print_android_manual_fallback(&sdk_config); + issues.push(("Android SDK", "See manual installation instructions above.".to_string())); + } + } } println!(); @@ -132,7 +164,7 @@ fn setup_system() -> Result<()> { } /// Setup dependencies for a specific project by reading its .weevil.toml -fn setup_project(project_path: &str) -> Result<()> { +fn setup_project(project_path: &str, proxy: &ProxyConfig) -> Result<()> { let project_path = PathBuf::from(project_path); if !project_path.exists() { @@ -143,6 +175,9 @@ fn setup_project(project_path: &str) -> Result<()> { println!("{}", " Project Setup - Installing Dependencies".bright_cyan().bold()); println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!(); + + proxy.print_status(); + println!(); // Load project configuration println!("{}", "Reading project configuration...".bright_yellow()); @@ -214,7 +249,7 @@ fn setup_project(project_path: &str) -> Result<()> { // Try to install it automatically println!("{}", "Attempting automatic installation...".bright_yellow()); - match crate::sdk::ftc::install(&config.ftc_sdk_path, &config.android_sdk_path) { + match crate::sdk::ftc::install(&config.ftc_sdk_path, &config.android_sdk_path, proxy) { Ok(_) => { println!("{} FTC SDK {} installed successfully", "✓".green(), @@ -224,13 +259,13 @@ fn setup_project(project_path: &str) -> Result<()> { } 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"); + let sdk_config = SdkConfig { + ftc_sdk_path: config.ftc_sdk_path.clone(), + android_sdk_path: config.android_sdk_path.clone(), + cache_dir: dirs::home_dir().unwrap_or_default().join(".weevil"), + }; + print_ftc_manual_fallback(&sdk_config); + issues.push(("FTC SDK", "See manual installation instructions above.".to_string())); } } } @@ -249,14 +284,36 @@ fn setup_project(project_path: &str) -> Result<()> { } Err(_) => { println!("{} Android SDK found but incomplete, reinstalling...", "⚠".yellow()); - crate::sdk::android::install(&config.android_sdk_path)?; - installed.push("Android SDK (installed)".to_string()); + match crate::sdk::android::install(&config.android_sdk_path, proxy) { + Ok(_) => installed.push("Android SDK (installed)".to_string()), + Err(e) => { + println!("{} {}", "✗".red(), e); + let sdk_config = SdkConfig { + ftc_sdk_path: config.ftc_sdk_path.clone(), + android_sdk_path: config.android_sdk_path.clone(), + cache_dir: dirs::home_dir().unwrap_or_default().join(".weevil"), + }; + print_android_manual_fallback(&sdk_config); + issues.push(("Android SDK", "See manual installation instructions above.".to_string())); + } + } } } } else { println!("Android SDK not found. Installing..."); - crate::sdk::android::install(&config.android_sdk_path)?; - installed.push("Android SDK (installed)".to_string()); + match crate::sdk::android::install(&config.android_sdk_path, proxy) { + Ok(_) => installed.push("Android SDK (installed)".to_string()), + Err(e) => { + println!("{} {}", "✗".red(), e); + let sdk_config = SdkConfig { + ftc_sdk_path: config.ftc_sdk_path.clone(), + android_sdk_path: config.android_sdk_path.clone(), + cache_dir: dirs::home_dir().unwrap_or_default().join(".weevil"), + }; + print_android_manual_fallback(&sdk_config); + issues.push(("Android SDK", "See manual installation instructions above.".to_string())); + } + } } println!(); @@ -511,4 +568,147 @@ fn print_project_summary(installed: &[String], issues: &[(&str, String)], config println!(" Then run {} to verify", format!("weevil setup {}", project_path.display()).bright_white()); } println!(); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Manual fallback instructions — printed when automatic install fails. +// These walk the user through doing everything by hand, with explicit steps +// for Linux, macOS, and Windows. +// ───────────────────────────────────────────────────────────────────────────── + +fn print_ftc_manual_fallback(sdk_config: &SdkConfig) { + let dest = sdk_config.ftc_sdk_path.display(); + + println!(); + println!("{}", "═══════════════════════════════════════════════════════════".bright_yellow()); + println!("{}", " Manual FTC SDK Installation".bright_yellow().bold()); + println!("{}", "═══════════════════════════════════════════════════════════".bright_yellow()); + println!(); + println!(" Automatic installation failed. Follow the steps below to"); + println!(" clone the FTC SDK by hand. If you are behind a proxy, set"); + println!(" the environment variables shown before running git."); + println!(); + println!(" Target directory: {}", dest); + println!(); + + println!(" {} Linux / macOS:", "▸".bright_cyan()); + println!(" # If behind a proxy, set these first (replace with your proxy):"); + println!(" export HTTPS_PROXY=http://your-proxy:3128"); + println!(" export HTTP_PROXY=http://your-proxy:3128"); + println!(); + println!(" # If the proxy uses a custom CA certificate, add:"); + println!(" export GIT_SSL_CAPATH=/path/to/ca-certificates"); + println!(" # (ask your IT department for the CA cert if needed)"); + println!(); + println!(" git clone https://github.com/FIRST-Tech-Challenge/FtcRobotController.git \\"); + println!(" {}", dest); + println!(" cd {}", dest); + println!(" git checkout v10.1.1"); + println!(); + + println!(" {} Windows (PowerShell):", "▸".bright_cyan()); + println!(" # If behind a proxy, set these first:"); + println!(" $env:HTTPS_PROXY = \"http://your-proxy:3128\""); + println!(" $env:HTTP_PROXY = \"http://your-proxy:3128\""); + println!(); + println!(" # If the proxy uses a custom CA certificate:"); + println!(" git config --global http.sslCAInfo C:\\path\\to\\ca-bundle.crt"); + println!(" # (ask your IT department for the CA cert if needed)"); + println!(); + println!(" git clone https://github.com/FIRST-Tech-Challenge/FtcRobotController.git `"); + println!(" {}", dest); + println!(" cd {}", dest); + println!(" git checkout v10.1.1"); + println!(); + + println!(" Once done, run {} again.", "weevil setup".bright_white()); + println!("{}", "═══════════════════════════════════════════════════════════".bright_yellow()); + println!(); +} + +fn print_android_manual_fallback(sdk_config: &SdkConfig) { + let dest = sdk_config.android_sdk_path.display(); + + // Pick the right download URL for the current OS + let (url, extract_note) = if cfg!(target_os = "windows") { + ( + "https://dl.google.com/android/repository/commandlinetools-win-11076708_latest.zip", + "Extract the zip. You will get a cmdline-tools/ folder." + ) + } else if cfg!(target_os = "macos") { + ( + "https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip", + "Extract the zip. You will get a cmdline-tools/ folder." + ) + } else { + ( + "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip", + "Extract the zip. You will get a cmdline-tools/ folder." + ) + }; + + println!(); + println!("{}", "═══════════════════════════════════════════════════════════".bright_yellow()); + println!("{}", " Manual Android SDK Installation".bright_yellow().bold()); + println!("{}", "═══════════════════════════════════════════════════════════".bright_yellow()); + println!(); + println!(" Automatic installation failed. Follow the steps below to"); + println!(" download and set up the Android SDK by hand."); + println!(); + println!(" Target directory: {}", dest); + println!(" Download URL: {}", url); + println!(); + + println!(" {} Linux / macOS:", "▸".bright_cyan()); + println!(" # If behind a proxy, set these first:"); + println!(" export HTTPS_PROXY=http://your-proxy:3128"); + println!(" export HTTP_PROXY=http://your-proxy:3128"); + println!(); + println!(" # If the proxy uses a custom CA cert, add:"); + println!(" export CURL_CA_BUNDLE=/path/to/ca-bundle.crt"); + println!(" # (ask your IT department for the CA cert if needed)"); + println!(); + println!(" mkdir -p {}", dest); + println!(" cd {}", dest); + println!(" curl -L -o cmdline-tools.zip \\"); + println!(" \"{}\"", url); + println!(" unzip cmdline-tools.zip"); + println!(" # {} Move into the expected layout:", extract_note); + println!(" mv cmdline-tools cmdline-tools-temp"); + println!(" mkdir -p cmdline-tools/latest"); + println!(" mv cmdline-tools-temp/* cmdline-tools/latest/"); + println!(" rmdir cmdline-tools-temp"); + println!(" # Accept licenses and install packages:"); + println!(" ./cmdline-tools/latest/bin/sdkmanager --licenses"); + println!(" ./cmdline-tools/latest/bin/sdkmanager platform-tools \"platforms;android-34\" \"build-tools;34.0.0\""); + println!(); + + println!(" {} Windows (PowerShell):", "▸".bright_cyan()); + println!(" # If behind a proxy, set these first:"); + println!(" $env:HTTPS_PROXY = \"http://your-proxy:3128\""); + println!(" $env:HTTP_PROXY = \"http://your-proxy:3128\""); + println!(); + println!(" # If the proxy uses a custom CA cert:"); + println!(" # Download the CA cert from your IT department and note its path."); + println!(" # PowerShell's Invoke-WebRequest will use the system cert store;"); + println!(" # you may need to import the cert: "); + println!(" # Import-Certificate -FilePath C:\\path\\to\\ca.crt -CertStoreLocation Cert:\\LocalMachine\\Root"); + println!(); + println!(" New-Item -ItemType Directory -Path \"{}\" -Force", dest); + println!(" cd \"{}\"", dest); + println!(" Invoke-WebRequest -Uri \"{}\" -OutFile cmdline-tools.zip", url); + println!(" Expand-Archive -Path cmdline-tools.zip -DestinationPath ."); + println!(" # Move into the expected layout:"); + println!(" Rename-Item cmdline-tools cmdline-tools-temp"); + println!(" New-Item -ItemType Directory -Path cmdline-tools\\latest"); + println!(" Move-Item cmdline-tools-temp\\* cmdline-tools\\latest\\"); + println!(" Remove-Item cmdline-tools-temp"); + println!(" # Accept licenses and install packages:"); + println!(" .\\cmdline-tools\\latest\\bin\\sdkmanager.bat --licenses"); + println!(" .\\cmdline-tools\\latest\\bin\\sdkmanager.bat platform-tools \"platforms;android-34\" \"build-tools;34.0.0\""); + println!(); + + println!(" Once done, run {} again.", "weevil setup".bright_white()); + println!("{}", "═══════════════════════════════════════════════════════════".bright_yellow()); + println!(); } \ No newline at end of file diff --git i/src/main.rs w/src/main.rs index aa8fce4..35d11f7 100644 --- i/src/main.rs +++ w/src/main.rs @@ -35,6 +35,14 @@ enum Commands { /// Path to Android SDK (optional, will auto-detect or download) #[arg(long)] android_sdk: Option, + + /// Use this proxy for all network operations (e.g. http://proxy:3128) + #[arg(long)] + proxy: Option, + + /// Force direct connection, ignoring proxy env vars + #[arg(long)] + no_proxy: bool, }, /// Check system health and diagnose issues @@ -44,6 +52,14 @@ enum Commands { Setup { /// Path to project directory (optional - without it, sets up system) path: Option, + + /// Use this proxy for all network operations (e.g. http://proxy:3128) + #[arg(long)] + proxy: Option, + + /// Force direct connection, ignoring proxy env vars + #[arg(long)] + no_proxy: bool, }, /// Remove Weevil-installed SDKs and dependencies @@ -85,6 +101,14 @@ enum Commands { Sdk { #[command(subcommand)] command: SdkCommands, + + /// Use this proxy for all network operations (e.g. http://proxy:3128) + #[arg(long)] + proxy: Option, + + /// Force direct connection, ignoring proxy env vars + #[arg(long)] + no_proxy: bool, }, /// Show or update project configuration @@ -120,14 +144,16 @@ fn main() -> Result<()> { print_banner(); match cli.command { - Commands::New { name, ftc_sdk, android_sdk } => { - commands::new::create_project(&name, ftc_sdk.as_deref(), android_sdk.as_deref()) + Commands::New { name, ftc_sdk, android_sdk, proxy, no_proxy } => { + let proxy_config = weevil::sdk::proxy::ProxyConfig::resolve(proxy.as_deref(), no_proxy)?; + commands::new::create_project(&name, ftc_sdk.as_deref(), android_sdk.as_deref(), &proxy_config) } Commands::Doctor => { commands::doctor::run_diagnostics() } - Commands::Setup { path } => { - commands::setup::setup_environment(path.as_deref()) + Commands::Setup { path, proxy, no_proxy } => { + let proxy_config = weevil::sdk::proxy::ProxyConfig::resolve(proxy.as_deref(), no_proxy)?; + commands::setup::setup_environment(path.as_deref(), &proxy_config) } Commands::Uninstall { dry_run, only } => { commands::uninstall::uninstall_dependencies(dry_run, only) @@ -138,11 +164,14 @@ fn main() -> Result<()> { Commands::Deploy { path, usb, wifi, ip } => { commands::deploy::deploy_project(&path, usb, wifi, ip.as_deref()) } - Commands::Sdk { command } => match command { - SdkCommands::Install => commands::sdk::install_sdks(), - SdkCommands::Status => commands::sdk::show_status(), - SdkCommands::Update => commands::sdk::update_sdks(), - }, + Commands::Sdk { command, proxy, no_proxy } => { + let proxy_config = weevil::sdk::proxy::ProxyConfig::resolve(proxy.as_deref(), no_proxy)?; + match command { + SdkCommands::Install => commands::sdk::install_sdks(&proxy_config), + SdkCommands::Status => commands::sdk::show_status(), + SdkCommands::Update => commands::sdk::update_sdks(&proxy_config), + } + } Commands::Config { path, set_sdk } => { if let Some(sdk_path) = set_sdk { commands::config::set_sdk(&path, &sdk_path) @@ -164,4 +193,4 @@ fn print_banner() { println!("{}", " Nexus Workshops LLC".bright_cyan()); println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!(); -} +} \ No newline at end of file diff --git i/src/sdk/android.rs w/src/sdk/android.rs index 596ed74..b91701e 100644 --- i/src/sdk/android.rs +++ w/src/sdk/android.rs @@ -6,11 +6,13 @@ 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) -> Result<()> { +pub fn install(sdk_path: &Path, proxy: &ProxyConfig) -> Result<()> { // Check if SDK exists AND is complete if sdk_path.exists() { match verify(sdk_path) { @@ -42,10 +44,20 @@ pub fn install(sdk_path: &Path) -> Result<()> { // Download println!("Downloading from: {}", url); - let client = Client::new(); + let client = match &proxy.url { + Some(proxy_url) => { + println!(" via proxy: {}", proxy_url); + Client::builder() + .proxy(reqwest::Proxy::all(proxy_url.clone()) + .context("Failed to configure proxy")?) + .build() + .context("Failed to build HTTP client")? + } + None => Client::new(), + }; let response = client.get(url) .send() - .context("Failed to download Android SDK")?; + .context("Failed to download Android SDK. If you are behind a proxy, try --proxy or set HTTPS_PROXY")?; let total_size = response.content_length().unwrap_or(0); @@ -104,14 +116,14 @@ pub fn install(sdk_path: &Path) -> Result<()> { } // Install required packages - install_packages(sdk_path)?; + install_packages(sdk_path, proxy)?; println!("{} Android SDK installed successfully", "✓".green()); Ok(()) } -fn install_packages(sdk_path: &Path) -> Result<()> { +fn install_packages(sdk_path: &Path, proxy: &ProxyConfig) -> Result<()> { println!("Installing Android SDK packages..."); let sdkmanager_path = sdk_path.join("cmdline-tools").join("latest").join("bin"); @@ -132,10 +144,10 @@ fn install_packages(sdk_path: &Path) -> Result<()> { println!("Found sdkmanager at: {}", sdkmanager.display()); - run_sdkmanager(&sdkmanager, sdk_path) + run_sdkmanager(&sdkmanager, sdk_path, proxy) } -fn run_sdkmanager(sdkmanager: &Path, sdk_root: &Path) -> Result<()> { +fn run_sdkmanager(sdkmanager: &Path, sdk_root: &Path, proxy: &ProxyConfig) -> Result<()> { use std::process::{Command, Stdio}; use std::io::Write; @@ -151,6 +163,13 @@ fn run_sdkmanager(sdkmanager: &Path, sdk_root: &Path) -> Result<()> { Command::new(sdkmanager) }; + // Inject proxy env vars so sdkmanager picks them up + if let Some(proxy_url) = &proxy.url { + let url_str = proxy_url.as_str(); + cmd.env("HTTPS_PROXY", url_str) + .env("HTTP_PROXY", url_str); + } + cmd.arg(format!("--sdk_root={}", sdk_root.display())) .arg("--licenses") .stdin(Stdio::piped()) @@ -192,6 +211,13 @@ fn run_sdkmanager(sdkmanager: &Path, sdk_root: &Path) -> Result<()> { } else { Command::new(sdkmanager) }; + + // Inject proxy env vars here too + if let Some(proxy_url) = &proxy.url { + let url_str = proxy_url.as_str(); + cmd.env("HTTPS_PROXY", url_str) + .env("HTTP_PROXY", url_str); + } let status = cmd .arg(format!("--sdk_root={}", sdk_root.display())) diff --git i/src/sdk/ftc.rs w/src/sdk/ftc.rs index 778cece..3e982e8 100644 --- i/src/sdk/ftc.rs +++ w/src/sdk/ftc.rs @@ -4,10 +4,12 @@ use colored::*; use std::fs; +use super::proxy::ProxyConfig; + 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<()> { +pub fn install(sdk_path: &Path, android_sdk_path: &Path, proxy: &ProxyConfig) -> Result<()> { if sdk_path.exists() { println!("{} FTC SDK already installed at: {}", "✓".green(), @@ -22,8 +24,8 @@ pub fn install(sdk_path: &Path, android_sdk_path: &Path) -> Result<()> { println!("Cloning from: {}", FTC_SDK_URL); println!("Version: {}", FTC_SDK_VERSION); - // Clone the repository - let repo = Repository::clone(FTC_SDK_URL, sdk_path) + // Clone the repository, with proxy if configured + let repo = clone_with_proxy(FTC_SDK_URL, sdk_path, proxy) .context("Failed to clone FTC SDK")?; // Checkout specific version @@ -39,6 +41,44 @@ pub fn install(sdk_path: &Path, android_sdk_path: &Path) -> Result<()> { Ok(()) } +/// Clone a git repo, injecting http.proxy into a git2 config if ProxyConfig has a URL. +/// Returns a more helpful error message when a proxy was involved. +fn clone_with_proxy(url: &str, dest: &Path, proxy: &ProxyConfig) -> Result { + let mut opts = git2::CloneOptions::new(); + + if let Some(proxy_url) = &proxy.url { + // git2 reads http.proxy from a config object passed to the clone options. + // We build an in-memory config with just that one key. + let mut git_config = git2::Config::new()?; + git_config.set_str("http.proxy", proxy_url.as_str())?; + opts.local(false); // force network path even if URL looks local + // Unfortunately git2::CloneOptions doesn't have a direct .config() method, + // so we set the env var which libgit2 also respects as a fallback. + std::env::set_var("GIT_PROXY_COMMAND", ""); // clear any ssh proxy + std::env::set_var("HTTP_PROXY", proxy_url.as_str()); + std::env::set_var("HTTPS_PROXY", proxy_url.as_str()); + println!(" via proxy: {}", proxy_url); + } + + Repository::clone_with(url, dest, &opts).map_err(|e| { + if proxy.url.is_some() { + anyhow::anyhow!( + "{}\n\n\ + This failure may be caused by your proxy. If you are behind a \ + corporate or school network, see 'weevil setup' for manual \ + fallback instructions.", + e + ) + } else { + anyhow::anyhow!( + "{}\n\n\ + If you are behind a proxy, try: weevil sdk install --proxy ", + e + ) + } + }) +} + 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 @@ -80,15 +120,39 @@ fn check_version(sdk_path: &Path) -> Result<()> { Ok(()) } -pub fn update(sdk_path: &Path) -> Result<()> { +pub fn update(sdk_path: &Path, proxy: &ProxyConfig) -> Result<()> { println!("{}", "Updating FTC SDK...".bright_yellow()); + // Set proxy env vars for the fetch if configured + if let Some(proxy_url) = &proxy.url { + std::env::set_var("HTTP_PROXY", proxy_url.as_str()); + std::env::set_var("HTTPS_PROXY", proxy_url.as_str()); + println!(" via proxy: {}", proxy_url); + } + 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)?; + remote.fetch(&["refs/tags/*:refs/tags/*"], None, None) + .map_err(|e| { + if proxy.url.is_some() { + anyhow::anyhow!( + "Failed to fetch: {}\n\n\ + This failure may be caused by your proxy. If you are behind a \ + corporate or school network, see 'weevil setup' for manual \ + fallback instructions.", + e + ) + } else { + anyhow::anyhow!( + "Failed to fetch: {}\n\n\ + If you are behind a proxy, try: weevil sdk update --proxy ", + e + ) + } + })?; // Checkout latest version let obj = repo.revparse_single(FTC_SDK_VERSION)?; diff --git i/src/sdk/mod.rs w/src/sdk/mod.rs index 080ce36..5d7c065 100644 --- i/src/sdk/mod.rs +++ w/src/sdk/mod.rs @@ -6,6 +6,7 @@ pub mod android; pub mod ftc; pub mod gradle; +pub mod proxy; pub struct SdkConfig { pub ftc_sdk_path: PathBuf,