Files
weevil/src/main.rs
Eric Ratliff 54647a47b1 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
2026-02-01 09:47:52 -06:00

186 lines
5.5 KiB
Rust

use clap::{Parser, Subcommand};
use colored::*;
use anyhow::Result;
use weevil::version::WEEVIL_VERSION;
// Import ProxyConfig through our own `mod sdk`, not through the `weevil`
// library crate. Both re-export the same source, but Rust treats
// `weevil::sdk::proxy::ProxyConfig` and `sdk::proxy::ProxyConfig` as
// distinct types when a binary and its lib are compiled together.
// The command modules already see the local-mod version, so main must match.
mod commands;
mod sdk;
mod project;
mod templates;
use sdk::proxy::ProxyConfig;
#[derive(Parser)]
#[command(name = "weevil")]
#[command(author = "Eric Ratliff <eric@nxlearn.net>")]
#[command(version = WEEVIL_VERSION)]
#[command(
about = "FTC robotics project generator - bores into complexity, emerges with clean code",
long_about = None
)]
struct Cli {
/// Use this HTTP/HTTPS proxy for all downloads
#[arg(long, value_name = "URL", global = true)]
proxy: Option<String>,
/// Skip proxy entirely — go direct even if HTTPS_PROXY is set
#[arg(long, global = true)]
no_proxy: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Create a new FTC robot project
New {
/// Name of the robot project
name: String,
/// Path to FTC SDK (optional, will auto-detect or download)
#[arg(long)]
ftc_sdk: Option<String>,
/// Path to Android SDK (optional, will auto-detect or download)
#[arg(long)]
android_sdk: Option<String>,
},
/// Check system health and diagnose issues
Doctor,
/// Setup development environment (system or project)
Setup {
/// Path to project directory (optional - without it, sets up system)
path: Option<String>,
},
/// Remove Weevil-installed SDKs and dependencies
Uninstall {
/// Show what would be removed without actually removing anything
#[arg(long)]
dry_run: bool,
/// Remove only specific items by number (use --dry-run first to see the list)
#[arg(long, value_name = "NUM", num_args = 1..)]
only: Option<Vec<usize>>,
},
/// Upgrade an existing project to the latest generator version
Upgrade {
/// Path to the project directory
path: String,
},
/// Build and deploy project to Control Hub
Deploy {
/// Path to the project directory
path: String,
/// Force USB connection
#[arg(long)]
usb: bool,
/// Force WiFi connection
#[arg(long)]
wifi: bool,
/// Custom IP address
#[arg(short, long)]
ip: Option<String>,
},
/// Manage SDKs (FTC and Android)
Sdk {
#[command(subcommand)]
command: SdkCommands,
},
/// Show or update project configuration
Config {
/// Path to the project directory
path: String,
/// Set FTC SDK path for this project
#[arg(long, value_name = "PATH")]
set_sdk: Option<String>,
},
}
#[derive(Subcommand)]
enum SdkCommands {
/// Install required SDKs
Install,
/// Show SDK status and locations
Status,
/// Update SDKs to latest versions
Update,
}
fn main() -> Result<()> {
// Enable colors on Windows
#[cfg(windows)]
colored::control::set_virtual_terminal(true).ok();
let cli = Cli::parse();
print_banner();
// Resolve proxy once at the top — every network-touching command uses it.
let proxy = ProxyConfig::resolve(cli.proxy.as_deref(), cli.no_proxy)?;
match cli.command {
Commands::New { name, ftc_sdk, android_sdk } => {
commands::new::create_project(&name, ftc_sdk.as_deref(), android_sdk.as_deref(), &proxy)
}
Commands::Doctor => {
commands::doctor::run_diagnostics()
}
Commands::Setup { path } => {
commands::setup::setup_environment(path.as_deref(), &proxy)
}
Commands::Uninstall { dry_run, only } => {
commands::uninstall::uninstall_dependencies(dry_run, only)
}
Commands::Upgrade { path } => {
commands::upgrade::upgrade_project(&path)
}
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(&proxy),
SdkCommands::Status => commands::sdk::show_status(),
SdkCommands::Update => commands::sdk::update_sdks(&proxy),
},
Commands::Config { path, set_sdk } => {
if let Some(sdk_path) = set_sdk {
commands::config::set_sdk(&path, &sdk_path)
} else {
commands::config::show_config(&path)
}
}
}
}
fn print_banner() {
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
println!(
"{}",
format!(" 🪲 Weevil - FTC Project Generator v{}", WEEVIL_VERSION)
.bright_cyan()
.bold()
);
println!("{}", " Nexus Workshops LLC".bright_cyan());
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
println!();
}