feat: Weevil v1.0.0-beta1 - FTC Project Generator

Cross-platform tool for generating clean, testable FTC robot projects
without editing the SDK installation.

Features:
- Standalone project generation with proper separation from SDK
- Per-project SDK configuration via .weevil.toml
- Local unit testing support (no robot required)
- Cross-platform build/deploy scripts (Linux/macOS/Windows)
- Project upgrade system preserving user code
- Configuration management commands
- Comprehensive test suite (11 passing tests)
- Zero-warning builds

Architecture:
- Pure Rust implementation with embedded Gradle wrapper
- Projects use deployToSDK task to copy code to FTC SDK TeamCode
- Git-ready projects with automatic initialization
- USB and WiFi deployment with auto-detection

Commands:
- weevil new <name> - Create new project
- weevil upgrade <path> - Update project infrastructure
- weevil config <path> - View/modify project configuration
- weevil sdk status/install/update - Manage SDKs

Addresses the core problem: FTC's SDK structure forces students to
edit framework internals instead of separating concerns like industry
standard practices. Weevil enables proper software engineering workflows
for robotics education.
This commit is contained in:
Eric Ratliff
2026-01-24 15:20:18 -06:00
commit 70a1acc2a1
35 changed files with 3558 additions and 0 deletions

142
src/sdk/android.rs Normal file
View File

@@ -0,0 +1,142 @@
use std::path::Path;
use anyhow::{Result, Context};
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::blocking::Client;
use std::fs::File;
use std::io::Write;
use colored::*;
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";
pub fn install(sdk_path: &Path) -> Result<()> {
if sdk_path.exists() {
println!("{} Android SDK already installed at: {}",
"".green(),
sdk_path.display()
);
return Ok(());
}
println!("{}", "Installing Android SDK...".bright_yellow());
let url = if cfg!(target_os = "macos") {
ANDROID_SDK_URL_MAC
} else {
ANDROID_SDK_URL_LINUX
};
// Download
println!("Downloading from: {}", url);
let client = Client::new();
let response = client.get(url)
.send()
.context("Failed to download Android SDK")?;
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)?;
// Cleanup
std::fs::remove_file(&temp_zip)?;
// 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 = sdk_path
.join("cmdline-tools/bin/sdkmanager");
if !sdkmanager.exists() {
// Try alternate location
let alt = sdk_path.join("cmdline-tools/latest/bin/sdkmanager");
if alt.exists() {
return run_sdkmanager(&alt, sdk_path);
}
// Need to move cmdline-tools to correct location
let from = sdk_path.join("cmdline-tools");
let to = sdk_path.join("cmdline-tools/latest");
if from.exists() {
std::fs::create_dir_all(sdk_path.join("cmdline-tools"))?;
std::fs::rename(&from, &to)?;
return run_sdkmanager(&to.join("bin/sdkmanager"), sdk_path);
}
}
run_sdkmanager(&sdkmanager, sdk_path)
}
fn run_sdkmanager(sdkmanager: &Path, sdk_root: &Path) -> Result<()> {
use std::process::Command;
use std::io::Write;
// Accept licenses
let mut yes_cmd = Command::new("yes");
let yes_output = yes_cmd.output()?;
let mut cmd = Command::new(sdkmanager);
cmd.arg("--sdk_root")
.arg(sdk_root)
.arg("--licenses")
.stdin(std::process::Stdio::piped())
.spawn()?
.stdin
.as_mut()
.unwrap()
.write_all(&yes_output.stdout)?;
// Install packages
Command::new(sdkmanager)
.arg("--sdk_root")
.arg(sdk_root)
.arg("platform-tools")
.arg("platforms;android-34")
.arg("build-tools;34.0.0")
.status()?;
Ok(())
}
pub fn verify(sdk_path: &Path) -> Result<()> {
if !sdk_path.exists() {
anyhow::bail!("Android SDK not found at: {}", sdk_path.display());
}
let platform_tools = sdk_path.join("platform-tools");
if !platform_tools.exists() {
anyhow::bail!("Android SDK incomplete: platform-tools not found");
}
Ok(())
}