feat: add proxy support for SDK downloads (v1.1.0)

Add --proxy and --no-proxy global flags to control HTTP/HTTPS proxy
usage for all network operations (SDK installs, FTC SDK clone/fetch,
Android SDK download).

Proxy resolution priority:
  1. --no-proxy          → go direct, ignore everything
  2. --proxy <url>       → use the specified proxy
  3. HTTPS_PROXY / HTTP_PROXY env vars (auto-detected)
  4. Nothing             → go direct

Key implementation details:
- reqwest client is always built through ProxyConfig::client() rather
  than Client::new(), so --no-proxy actively suppresses env-var
  auto-detection instead of just being a no-op.
- git2/libgit2 has its own HTTP transport that doesn't use reqwest.
  GitProxyGuard is an RAII guard that temporarily sets/clears the
  HTTPS_PROXY env vars around clone and fetch operations, then restores
  the previous state on drop. This avoids mutating ~/.gitconfig.
- Gradle wrapper reads HTTPS_PROXY natively; no programmatic
  intervention needed.
- All network failure paths now print offline/air-gapped installation
  instructions automatically, covering manual SDK installs and Gradle
  distribution download.

Closes: v1.1.0 proxy support milestone
This commit is contained in:
Eric Ratliff
2026-02-01 09:44:53 -06:00
parent 5596f5bade
commit 54647a47b1
9 changed files with 1161 additions and 48 deletions

View File

@@ -4,18 +4,19 @@ use std::process::Command;
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());
@@ -57,7 +58,7 @@ 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)?;
crate::sdk::ftc::install(&sdk_config.ftc_sdk_path, &sdk_config.android_sdk_path, proxy)?;
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));
@@ -65,7 +66,7 @@ fn setup_system() -> Result<()> {
}
} else {
println!("FTC SDK not found. Installing...");
crate::sdk::ftc::install(&sdk_config.ftc_sdk_path, &sdk_config.android_sdk_path)?;
crate::sdk::ftc::install(&sdk_config.ftc_sdk_path, &sdk_config.android_sdk_path, proxy)?;
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));
@@ -85,13 +86,13 @@ fn setup_system() -> Result<()> {
}
Err(_) => {
println!("{} Android SDK found but incomplete, reinstalling...", "".yellow());
crate::sdk::android::install(&sdk_config.android_sdk_path)?;
crate::sdk::android::install(&sdk_config.android_sdk_path, proxy)?;
installed.push("Android SDK (installed)".to_string());
}
}
} else {
println!("Android SDK not found. Installing...");
crate::sdk::android::install(&sdk_config.android_sdk_path)?;
crate::sdk::android::install(&sdk_config.android_sdk_path, proxy)?;
installed.push("Android SDK (installed)".to_string());
}
println!();
@@ -132,7 +133,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() {
@@ -214,7 +215,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(),
@@ -249,13 +250,13 @@ fn setup_project(project_path: &str) -> Result<()> {
}
Err(_) => {
println!("{} Android SDK found but incomplete, reinstalling...", "".yellow());
crate::sdk::android::install(&config.android_sdk_path)?;
crate::sdk::android::install(&config.android_sdk_path, proxy)?;
installed.push("Android SDK (installed)".to_string());
}
}
} else {
println!("Android SDK not found. Installing...");
crate::sdk::android::install(&config.android_sdk_path)?;
crate::sdk::android::install(&config.android_sdk_path, proxy)?;
installed.push("Android SDK (installed)".to_string());
}
println!();