Compare commits
5 Commits
v1.0.0-rc2
...
78abe1d65c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78abe1d65c | ||
|
|
d8e3c54f3d | ||
|
|
df7ca091ec | ||
|
|
4e9575cc4f | ||
|
|
6b6ba058b7 |
17
CHANGELOG.md
Normal file
17
CHANGELOG.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## [1.0.0] - 2026-01-27
|
||||
|
||||
First stable release! 🎉
|
||||
|
||||
### Added
|
||||
- Complete Windows deployment support
|
||||
- Android SDK path in project configuration
|
||||
- Robust cross-platform build and deployment scripts
|
||||
- Project upgrade command with config migration
|
||||
- Comprehensive test suite
|
||||
|
||||
### Fixed
|
||||
- Windows APK discovery and deployment
|
||||
- Batch file path parsing (quote handling)
|
||||
- ADB integration and error reporting
|
||||
@@ -511,7 +511,7 @@ Built with frustration at unnecessarily complex robotics frameworks, and hope th
|
||||
|
||||
## Project Status
|
||||
|
||||
**Current Version:** 1.0.0-rc2
|
||||
**Current Version:** 1.0.0
|
||||
|
||||
**What Works:**
|
||||
- ✅ Project generation
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
This document outlines the planned feature development for Weevil across multiple versions. Features are subject to change based on user feedback, technical constraints, and market needs.
|
||||
|
||||
**Current Version:** 1.0.0-rc2
|
||||
**Next Release:** 1.1.0 (Target: TBD)
|
||||
|
||||
---
|
||||
|
||||
## Version 1.1.0 - Core Stability & Team Adoption
|
||||
|
||||
267
src/commands/doctor.rs
Normal file
267
src/commands/doctor.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use colored::*;
|
||||
|
||||
use crate::sdk::SdkConfig;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SystemHealth {
|
||||
pub java_ok: bool,
|
||||
pub java_version: Option<String>,
|
||||
pub ftc_sdk_ok: bool,
|
||||
pub ftc_sdk_version: Option<String>,
|
||||
pub android_sdk_ok: bool,
|
||||
pub adb_ok: bool,
|
||||
pub adb_version: Option<String>,
|
||||
pub gradle_ok: bool,
|
||||
pub gradle_version: Option<String>,
|
||||
}
|
||||
|
||||
impl SystemHealth {
|
||||
pub fn is_healthy(&self) -> bool {
|
||||
// Required: Java, FTC SDK, Android SDK
|
||||
// Optional: ADB in PATH (can be in Android SDK), Gradle (projects have wrapper)
|
||||
self.java_ok && self.ftc_sdk_ok && self.android_sdk_ok
|
||||
}
|
||||
}
|
||||
|
||||
/// Run system diagnostics and report health status
|
||||
pub fn run_diagnostics() -> Result<()> {
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!("{}", " 🩺 Weevil Doctor - System Diagnostics".bright_cyan().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!();
|
||||
|
||||
let health = check_system_health()?;
|
||||
print_diagnostics(&health);
|
||||
|
||||
println!();
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
|
||||
if health.is_healthy() {
|
||||
println!("{}", " ✓ System is healthy and ready for FTC development".bright_green().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!();
|
||||
println!("{}", "You can now:".bright_yellow().bold());
|
||||
println!(" - Create a new project: {}", "weevil new <project-name>".bright_cyan());
|
||||
println!(" - Setup a cloned project: {}", "weevil setup <project-path>".bright_cyan());
|
||||
} else {
|
||||
println!("{}", " ⚠ Issues found - setup required".bright_yellow().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!();
|
||||
println!("{}", "To fix issues, run:".bright_yellow().bold());
|
||||
println!(" {}", "weevil setup".bright_cyan());
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check system health and return a report
|
||||
pub fn check_system_health() -> Result<SystemHealth> {
|
||||
let sdk_config = SdkConfig::new()?;
|
||||
|
||||
// Check Java
|
||||
let (java_ok, java_version) = match check_java() {
|
||||
Ok(version) => (true, Some(version)),
|
||||
Err(_) => (false, None),
|
||||
};
|
||||
|
||||
// Check FTC SDK
|
||||
let (ftc_sdk_ok, ftc_sdk_version) = if sdk_config.ftc_sdk_path.exists() {
|
||||
match crate::sdk::ftc::verify(&sdk_config.ftc_sdk_path) {
|
||||
Ok(_) => {
|
||||
let version = crate::sdk::ftc::get_version(&sdk_config.ftc_sdk_path)
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
(true, Some(version))
|
||||
}
|
||||
Err(_) => (false, None),
|
||||
}
|
||||
} else {
|
||||
(false, None)
|
||||
};
|
||||
|
||||
// Check Android SDK
|
||||
let android_sdk_ok = if sdk_config.android_sdk_path.exists() {
|
||||
crate::sdk::android::verify(&sdk_config.android_sdk_path).is_ok()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Check ADB
|
||||
let (adb_ok, adb_version) = match check_adb(&sdk_config.android_sdk_path) {
|
||||
Ok(version) => (true, Some(version)),
|
||||
Err(_) => (false, None),
|
||||
};
|
||||
|
||||
// Check Gradle (optional)
|
||||
let (gradle_ok, gradle_version) = match check_gradle() {
|
||||
Ok(version) => (true, Some(version)),
|
||||
Err(_) => (false, None),
|
||||
};
|
||||
|
||||
Ok(SystemHealth {
|
||||
java_ok,
|
||||
java_version,
|
||||
ftc_sdk_ok,
|
||||
ftc_sdk_version,
|
||||
android_sdk_ok,
|
||||
adb_ok,
|
||||
adb_version,
|
||||
gradle_ok,
|
||||
gradle_version,
|
||||
})
|
||||
}
|
||||
|
||||
fn print_diagnostics(health: &SystemHealth) {
|
||||
let sdk_config = SdkConfig::new().unwrap();
|
||||
|
||||
println!("{}", "Required Components:".bright_yellow().bold());
|
||||
println!();
|
||||
|
||||
// Java
|
||||
if health.java_ok {
|
||||
println!(" {} Java JDK {}",
|
||||
"✓".green(),
|
||||
health.java_version.as_ref().unwrap()
|
||||
);
|
||||
} else {
|
||||
println!(" {} Java JDK {}",
|
||||
"✗".red(),
|
||||
"not found".red()
|
||||
);
|
||||
}
|
||||
|
||||
// FTC SDK
|
||||
if health.ftc_sdk_ok {
|
||||
println!(" {} FTC SDK {} at {}",
|
||||
"✓".green(),
|
||||
health.ftc_sdk_version.as_ref().unwrap(),
|
||||
sdk_config.ftc_sdk_path.display()
|
||||
);
|
||||
} else {
|
||||
println!(" {} FTC SDK {} (expected at {})",
|
||||
"✗".red(),
|
||||
"not found".red(),
|
||||
sdk_config.ftc_sdk_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// Android SDK
|
||||
if health.android_sdk_ok {
|
||||
println!(" {} Android SDK at {}",
|
||||
"✓".green(),
|
||||
sdk_config.android_sdk_path.display()
|
||||
);
|
||||
} else {
|
||||
println!(" {} Android SDK {} (expected at {})",
|
||||
"✗".red(),
|
||||
"not found".red(),
|
||||
sdk_config.android_sdk_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "Optional Components:".bright_yellow().bold());
|
||||
println!();
|
||||
|
||||
// ADB
|
||||
if health.adb_ok {
|
||||
println!(" {} ADB {}",
|
||||
"✓".green(),
|
||||
health.adb_version.as_ref().unwrap()
|
||||
);
|
||||
} else {
|
||||
println!(" {} ADB {}",
|
||||
"⚠".yellow(),
|
||||
"not in PATH (included in Android SDK)".yellow()
|
||||
);
|
||||
}
|
||||
|
||||
// Gradle
|
||||
if health.gradle_ok {
|
||||
println!(" {} Gradle {}",
|
||||
"✓".green(),
|
||||
health.gradle_version.as_ref().unwrap()
|
||||
);
|
||||
} else {
|
||||
println!(" {} Gradle {}",
|
||||
"⚠".yellow(),
|
||||
"not in PATH (projects include wrapper)".yellow()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_java() -> Result<String> {
|
||||
let output = Command::new("java")
|
||||
.arg("-version")
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
for line in stderr.lines() {
|
||||
if line.contains("version") {
|
||||
if let Some(version_str) = line.split('"').nth(1) {
|
||||
return Ok(version_str.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok("installed (version unknown)".to_string())
|
||||
}
|
||||
Err(_) => anyhow::bail!("Java JDK not found in PATH"),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_adb(android_sdk_path: &Path) -> Result<String> {
|
||||
// First try system PATH
|
||||
let output = Command::new("adb")
|
||||
.arg("version")
|
||||
.output();
|
||||
|
||||
if let Ok(out) = output {
|
||||
if out.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.starts_with("Android Debug Bridge version") {
|
||||
return Ok(line.replace("Android Debug Bridge version ", ""));
|
||||
}
|
||||
}
|
||||
return Ok("installed (version unknown)".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Try Android SDK location
|
||||
let adb_path = if cfg!(target_os = "windows") {
|
||||
android_sdk_path.join("platform-tools").join("adb.exe")
|
||||
} else {
|
||||
android_sdk_path.join("platform-tools").join("adb")
|
||||
};
|
||||
|
||||
if adb_path.exists() {
|
||||
anyhow::bail!("ADB found in Android SDK but not in PATH")
|
||||
} else {
|
||||
anyhow::bail!("ADB not found")
|
||||
}
|
||||
}
|
||||
|
||||
fn check_gradle() -> Result<String> {
|
||||
let output = Command::new("gradle")
|
||||
.arg("--version")
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.starts_with("Gradle") {
|
||||
return Ok(line.replace("Gradle ", ""));
|
||||
}
|
||||
}
|
||||
Ok("installed (version unknown)".to_string())
|
||||
}
|
||||
Err(_) => anyhow::bail!("Gradle not found in PATH"),
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,7 @@ pub mod new;
|
||||
pub mod upgrade;
|
||||
pub mod deploy;
|
||||
pub mod sdk;
|
||||
pub mod config;
|
||||
pub mod config;
|
||||
pub mod setup;
|
||||
pub mod doctor;
|
||||
pub mod uninstall;
|
||||
@@ -34,14 +34,47 @@ pub fn create_project(
|
||||
println!("{}", format!("Creating FTC project: {}", name).bright_green().bold());
|
||||
println!();
|
||||
|
||||
// Check system health FIRST
|
||||
println!("{}", "Checking system prerequisites...".bright_yellow());
|
||||
let health = crate::commands::doctor::check_system_health()?;
|
||||
|
||||
if !health.is_healthy() {
|
||||
println!();
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_red());
|
||||
println!("{}", " ✗ System Setup Required".bright_red().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_red());
|
||||
println!();
|
||||
println!("{}", "Missing required components:".bright_yellow().bold());
|
||||
|
||||
if !health.java_ok {
|
||||
println!(" {} Java JDK", "✗".red());
|
||||
}
|
||||
if !health.ftc_sdk_ok {
|
||||
println!(" {} FTC SDK", "✗".red());
|
||||
}
|
||||
if !health.android_sdk_ok {
|
||||
println!(" {} Android SDK", "✗".red());
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "Before creating a project, you need to set up your development environment.".bright_yellow());
|
||||
println!();
|
||||
println!("{}", "Run this command to install required components:".bright_yellow().bold());
|
||||
println!(" {}", "weevil setup".bright_cyan());
|
||||
println!();
|
||||
println!("{}", "Then try creating your project again:".bright_yellow().bold());
|
||||
println!(" {}", format!("weevil new {}", name).bright_cyan());
|
||||
println!();
|
||||
|
||||
bail!("System setup required");
|
||||
}
|
||||
|
||||
println!("{} All prerequisites met", "✓".green());
|
||||
println!();
|
||||
|
||||
// Setup or verify SDK configuration
|
||||
let sdk_config = SdkConfig::with_paths(ftc_sdk, android_sdk)?;
|
||||
|
||||
// Install SDKs if needed
|
||||
println!("{}", "Checking SDKs...".bright_yellow());
|
||||
ensure_sdks(&sdk_config)?;
|
||||
|
||||
println!();
|
||||
println!("{}", "Creating project structure...".bright_yellow());
|
||||
|
||||
// Build the project
|
||||
@@ -57,34 +90,12 @@ pub fn create_project(
|
||||
println!("Version: {}", crate::sdk::ftc::get_version(&sdk_config.ftc_sdk_path).unwrap_or_else(|_| "unknown".to_string()));
|
||||
println!();
|
||||
println!("{}", "Next steps:".bright_yellow().bold());
|
||||
println!(" 1. cd {}", name);
|
||||
println!(" 1. {}", format!("cd {}", name).bright_cyan());
|
||||
println!(" 2. Review README.md for project structure");
|
||||
println!(" 3. Start coding in src/main/java/robot/");
|
||||
println!(" 4. Run: ./gradlew test");
|
||||
println!(" 5. Deploy: weevil deploy {}", name);
|
||||
println!(" 4. Run tests: {}", "./gradlew test".bright_cyan());
|
||||
println!(" 5. Deploy to robot: {}", format!("weevil deploy {}", name).bright_cyan());
|
||||
println!();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_sdks(config: &SdkConfig) -> Result<()> {
|
||||
// Check FTC SDK
|
||||
if !config.ftc_sdk_path.exists() {
|
||||
println!("FTC SDK not found. Installing...");
|
||||
crate::sdk::ftc::install(&config.ftc_sdk_path, &config.android_sdk_path)?;
|
||||
} else {
|
||||
println!("{} FTC SDK found at: {}", "✓".green(), config.ftc_sdk_path.display());
|
||||
crate::sdk::ftc::verify(&config.ftc_sdk_path)?;
|
||||
}
|
||||
|
||||
// Check Android SDK
|
||||
if !config.android_sdk_path.exists() {
|
||||
println!("Android SDK not found. Installing...");
|
||||
crate::sdk::android::install(&config.android_sdk_path)?;
|
||||
} else {
|
||||
println!("{} Android SDK found at: {}", "✓".green(), config.android_sdk_path.display());
|
||||
crate::sdk::android::verify(&config.android_sdk_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
514
src/commands/setup.rs
Normal file
514
src/commands/setup.rs
Normal file
@@ -0,0 +1,514 @@
|
||||
use anyhow::{Result, Context, bail};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use colored::*;
|
||||
|
||||
use crate::sdk::SdkConfig;
|
||||
use crate::project::ProjectConfig;
|
||||
|
||||
/// Setup development environment - either system-wide or for a specific project
|
||||
pub fn setup_environment(project_path: Option<&str>) -> Result<()> {
|
||||
match project_path {
|
||||
Some(path) => setup_project(path),
|
||||
None => setup_system(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Setup system-wide development environment with default SDKs
|
||||
fn setup_system() -> Result<()> {
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!("{}", " System Setup - Preparing FTC Development Environment".bright_cyan().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!();
|
||||
|
||||
let mut issues = Vec::new();
|
||||
let mut installed = Vec::new();
|
||||
|
||||
// Check and install SDKs
|
||||
let sdk_config = SdkConfig::new()?;
|
||||
|
||||
// 1. Check Java
|
||||
println!("{}", "Checking Java JDK...".bright_yellow());
|
||||
match check_java() {
|
||||
Ok(version) => {
|
||||
println!("{} Java JDK {} found", "✓".green(), version);
|
||||
installed.push(format!("Java JDK {}", version));
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} {}", "✗".red(), e);
|
||||
issues.push(("Java JDK", get_java_install_instructions()));
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// 2. Check/Install FTC SDK
|
||||
println!("{}", "Checking FTC SDK...".bright_yellow());
|
||||
if sdk_config.ftc_sdk_path.exists() {
|
||||
match crate::sdk::ftc::verify(&sdk_config.ftc_sdk_path) {
|
||||
Ok(_) => {
|
||||
let version = crate::sdk::ftc::get_version(&sdk_config.ftc_sdk_path)
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
println!("{} FTC SDK {} found at: {}",
|
||||
"✓".green(),
|
||||
version,
|
||||
sdk_config.ftc_sdk_path.display()
|
||||
);
|
||||
installed.push(format!("FTC SDK {}", version));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
} 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));
|
||||
}
|
||||
println!();
|
||||
|
||||
// 3. Check/Install Android SDK
|
||||
println!("{}", "Checking Android SDK...".bright_yellow());
|
||||
if sdk_config.android_sdk_path.exists() {
|
||||
match crate::sdk::android::verify(&sdk_config.android_sdk_path) {
|
||||
Ok(_) => {
|
||||
println!("{} Android SDK found at: {}",
|
||||
"✓".green(),
|
||||
sdk_config.android_sdk_path.display()
|
||||
);
|
||||
installed.push("Android SDK".to_string());
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Android SDK not found. Installing...");
|
||||
crate::sdk::android::install(&sdk_config.android_sdk_path)?;
|
||||
installed.push("Android SDK (installed)".to_string());
|
||||
}
|
||||
println!();
|
||||
|
||||
// 4. Check ADB
|
||||
println!("{}", "Checking ADB (Android Debug Bridge)...".bright_yellow());
|
||||
match check_adb(&sdk_config.android_sdk_path) {
|
||||
Ok(version) => {
|
||||
println!("{} ADB {} found", "✓".green(), version);
|
||||
installed.push(format!("ADB {}", version));
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} {}", "⚠".yellow(), e);
|
||||
println!(" ADB is included in Android SDK platform-tools");
|
||||
println!(" Add to PATH: {}", sdk_config.android_sdk_path.join("platform-tools").display());
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// 5. Check Gradle
|
||||
println!("{}", "Checking Gradle...".bright_yellow());
|
||||
match check_gradle() {
|
||||
Ok(version) => {
|
||||
println!("{} Gradle {} found", "✓".green(), version);
|
||||
installed.push(format!("Gradle {}", version));
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} {}", "⚠".yellow(), e);
|
||||
println!(" Note: Weevil projects include Gradle wrapper, so this is optional");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// Print summary
|
||||
print_system_summary(&installed, &issues, &sdk_config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Setup dependencies for a specific project by reading its .weevil.toml
|
||||
fn setup_project(project_path: &str) -> Result<()> {
|
||||
let project_path = PathBuf::from(project_path);
|
||||
|
||||
if !project_path.exists() {
|
||||
bail!("Project directory not found: {}", project_path.display());
|
||||
}
|
||||
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!("{}", " Project Setup - Installing Dependencies".bright_cyan().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!();
|
||||
|
||||
// Load project configuration
|
||||
println!("{}", "Reading project configuration...".bright_yellow());
|
||||
let config = ProjectConfig::load(&project_path)
|
||||
.context("Failed to load .weevil.toml")?;
|
||||
|
||||
println!();
|
||||
println!("{}", "Project Configuration:".bright_yellow().bold());
|
||||
println!(" Project: {}", config.project_name.bright_white());
|
||||
println!(" FTC SDK: {} ({})",
|
||||
config.ftc_sdk_version.bright_white(),
|
||||
config.ftc_sdk_path.display()
|
||||
);
|
||||
println!(" Android SDK: {}", config.android_sdk_path.display());
|
||||
println!();
|
||||
|
||||
let mut installed = Vec::new();
|
||||
let mut issues = Vec::new();
|
||||
|
||||
// 1. Check Java
|
||||
println!("{}", "Checking Java JDK...".bright_yellow());
|
||||
match check_java() {
|
||||
Ok(version) => {
|
||||
println!("{} Java JDK {} found", "✓".green(), version);
|
||||
installed.push(format!("Java JDK {}", version));
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} {}", "✗".red(), e);
|
||||
issues.push(("Java JDK", get_java_install_instructions()));
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// 2. Check/Install project-specific FTC SDK
|
||||
println!("{}", format!("Checking FTC SDK {}...", config.ftc_sdk_version).bright_yellow());
|
||||
if config.ftc_sdk_path.exists() {
|
||||
match crate::sdk::ftc::verify(&config.ftc_sdk_path) {
|
||||
Ok(_) => {
|
||||
println!("{} FTC SDK {} found at: {}",
|
||||
"✓".green(),
|
||||
config.ftc_sdk_version,
|
||||
config.ftc_sdk_path.display()
|
||||
);
|
||||
installed.push(format!("FTC SDK {}", config.ftc_sdk_version));
|
||||
}
|
||||
Err(_) => {
|
||||
println!("{} FTC SDK path exists but is invalid", "✗".red());
|
||||
println!(" Expected at: {}", config.ftc_sdk_path.display());
|
||||
println!();
|
||||
println!("{}", "Solution:".bright_yellow().bold());
|
||||
println!(" The .weevil.toml specifies an FTC SDK location that doesn't exist or is incomplete.");
|
||||
println!(" You have two options:");
|
||||
println!();
|
||||
println!(" 1. Update the project to use a different SDK:");
|
||||
println!(" weevil config {} --set-sdk <path-to-sdk>", project_path.display());
|
||||
println!();
|
||||
println!(" 2. Install the SDK at the expected location:");
|
||||
println!(" # Clone FTC SDK to the expected path");
|
||||
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 verification failed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("{} FTC SDK not found at: {}", "✗".red(), config.ftc_sdk_path.display());
|
||||
println!();
|
||||
|
||||
// Try to install it automatically
|
||||
println!("{}", "Attempting automatic installation...".bright_yellow());
|
||||
match crate::sdk::ftc::install(&config.ftc_sdk_path, &config.android_sdk_path) {
|
||||
Ok(_) => {
|
||||
println!("{} FTC SDK {} installed successfully",
|
||||
"✓".green(),
|
||||
config.ftc_sdk_version
|
||||
);
|
||||
installed.push(format!("FTC SDK {} (installed)", config.ftc_sdk_version));
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// 3. Check/Install Android SDK
|
||||
println!("{}", "Checking Android SDK...".bright_yellow());
|
||||
if config.android_sdk_path.exists() {
|
||||
match crate::sdk::android::verify(&config.android_sdk_path) {
|
||||
Ok(_) => {
|
||||
println!("{} Android SDK found at: {}",
|
||||
"✓".green(),
|
||||
config.android_sdk_path.display()
|
||||
);
|
||||
installed.push("Android SDK".to_string());
|
||||
}
|
||||
Err(_) => {
|
||||
println!("{} Android SDK found but incomplete, reinstalling...", "⚠".yellow());
|
||||
crate::sdk::android::install(&config.android_sdk_path)?;
|
||||
installed.push("Android SDK (installed)".to_string());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Android SDK not found. Installing...");
|
||||
crate::sdk::android::install(&config.android_sdk_path)?;
|
||||
installed.push("Android SDK (installed)".to_string());
|
||||
}
|
||||
println!();
|
||||
|
||||
// 4. Check ADB
|
||||
println!("{}", "Checking ADB...".bright_yellow());
|
||||
match check_adb(&config.android_sdk_path) {
|
||||
Ok(version) => {
|
||||
println!("{} ADB {} found", "✓".green(), version);
|
||||
installed.push(format!("ADB {}", version));
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} {}", "⚠".yellow(), e);
|
||||
println!(" Add to PATH: {}", config.android_sdk_path.join("platform-tools").display());
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// 5. Check Gradle wrapper in project
|
||||
println!("{}", "Checking Gradle wrapper...".bright_yellow());
|
||||
let gradlew = if cfg!(target_os = "windows") {
|
||||
project_path.join("gradlew.bat")
|
||||
} else {
|
||||
project_path.join("gradlew")
|
||||
};
|
||||
|
||||
if gradlew.exists() {
|
||||
println!("{} Gradle wrapper found in project", "✓".green());
|
||||
installed.push("Gradle wrapper".to_string());
|
||||
} else {
|
||||
println!("{} Gradle wrapper not found in project", "⚠".yellow());
|
||||
println!(" Run 'weevil upgrade {}' to regenerate project files", project_path.display());
|
||||
}
|
||||
println!();
|
||||
|
||||
// Print summary
|
||||
print_project_summary(&installed, &issues, &config, &project_path);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_java() -> Result<String> {
|
||||
let output = Command::new("java")
|
||||
.arg("-version")
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
// Java version is typically in stderr, format: java version "11.0.x" or openjdk version "11.0.x"
|
||||
for line in stderr.lines() {
|
||||
if line.contains("version") {
|
||||
if let Some(version_str) = line.split('"').nth(1) {
|
||||
return Ok(version_str.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok("installed (version unknown)".to_string())
|
||||
}
|
||||
Err(_) => bail!("Java JDK not found in PATH"),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_adb(android_sdk_path: &Path) -> Result<String> {
|
||||
// First try system PATH
|
||||
let output = Command::new("adb")
|
||||
.arg("version")
|
||||
.output();
|
||||
|
||||
if let Ok(out) = output {
|
||||
if out.status.success() {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.starts_with("Android Debug Bridge version") {
|
||||
return Ok(line.replace("Android Debug Bridge version ", ""));
|
||||
}
|
||||
}
|
||||
return Ok("installed (version unknown)".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Try Android SDK location
|
||||
let adb_path = if cfg!(target_os = "windows") {
|
||||
android_sdk_path.join("platform-tools").join("adb.exe")
|
||||
} else {
|
||||
android_sdk_path.join("platform-tools").join("adb")
|
||||
};
|
||||
|
||||
if adb_path.exists() {
|
||||
bail!("ADB found in Android SDK but not in PATH")
|
||||
} else {
|
||||
bail!("ADB not found")
|
||||
}
|
||||
}
|
||||
|
||||
fn check_gradle() -> Result<String> {
|
||||
let output = Command::new("gradle")
|
||||
.arg("--version")
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.starts_with("Gradle") {
|
||||
return Ok(line.replace("Gradle ", ""));
|
||||
}
|
||||
}
|
||||
Ok("installed (version unknown)".to_string())
|
||||
}
|
||||
Err(_) => bail!("Gradle not found in PATH (optional)"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_java_install_instructions() -> String {
|
||||
if cfg!(target_os = "windows") {
|
||||
format!(
|
||||
"Java JDK is required but not found.\n\
|
||||
\n\
|
||||
To install Java 11 on Windows:\n\
|
||||
\n\
|
||||
1. Download from: {}\n\
|
||||
2. Run the installer\n\
|
||||
3. Add Java to your PATH (installer usually does this)\n\
|
||||
4. Run 'weevil setup' again to verify\n\
|
||||
\n\
|
||||
Verify installation: java -version",
|
||||
"https://adoptium.net/temurin/releases/?version=11".bright_white()
|
||||
)
|
||||
} else if cfg!(target_os = "macos") {
|
||||
format!(
|
||||
"Java JDK is required but not found.\n\
|
||||
\n\
|
||||
To install Java 11 on macOS:\n\
|
||||
\n\
|
||||
Using Homebrew (recommended):\n\
|
||||
{}\n\
|
||||
\n\
|
||||
Or download from: {}\n\
|
||||
\n\
|
||||
Verify installation: java -version",
|
||||
" brew install openjdk@11".bright_white(),
|
||||
"https://adoptium.net/temurin/releases/?version=11".bright_white()
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Java JDK is required but not found.\n\
|
||||
\n\
|
||||
To install Java 11 on Ubuntu/Debian:\n\
|
||||
{}\n\
|
||||
{}\n\
|
||||
\n\
|
||||
To install on Fedora/RHEL:\n\
|
||||
{}\n\
|
||||
\n\
|
||||
Verify installation: java -version",
|
||||
" sudo apt update".bright_white(),
|
||||
" sudo apt install openjdk-11-jdk".bright_white(),
|
||||
" sudo dnf install java-11-openjdk-devel".bright_white()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn print_system_summary(installed: &[String], issues: &[(&str, String)], sdk_config: &SdkConfig) {
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_green());
|
||||
println!("{}", " System Setup Summary".bright_green().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_green());
|
||||
println!();
|
||||
|
||||
if !installed.is_empty() {
|
||||
println!("{}", "Installed Components:".bright_green().bold());
|
||||
for component in installed {
|
||||
println!(" {} {}", "✓".green(), component);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
if !issues.is_empty() {
|
||||
println!("{}", "Manual Installation Required:".bright_yellow().bold());
|
||||
println!();
|
||||
for (name, instructions) in issues {
|
||||
println!("{} {}", "✗".red(), name.red().bold());
|
||||
println!();
|
||||
for line in instructions.lines() {
|
||||
println!(" {}", line);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
println!("{}", "SDK Locations:".bright_cyan().bold());
|
||||
println!(" FTC SDK: {}", sdk_config.ftc_sdk_path.display());
|
||||
println!(" Android SDK: {}", sdk_config.android_sdk_path.display());
|
||||
println!(" Cache: {}", sdk_config.cache_dir.display());
|
||||
println!();
|
||||
|
||||
if issues.is_empty() {
|
||||
println!("{}", "✓ System is ready for FTC development!".bright_green().bold());
|
||||
println!();
|
||||
println!("{}", "Next steps:".bright_yellow().bold());
|
||||
println!(" Create a new project: {}", "weevil new my-robot".bright_white());
|
||||
println!(" Clone existing project: {}", "git clone <repo> && cd <repo> && weevil setup .".bright_white());
|
||||
} else {
|
||||
println!("{}", "⚠ Please install the required components listed above".bright_yellow().bold());
|
||||
println!(" Then run {} to verify", "weevil setup".bright_white());
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn print_project_summary(installed: &[String], issues: &[(&str, String)], config: &ProjectConfig, project_path: &Path) {
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_green());
|
||||
println!("{}", " Project Setup Summary".bright_green().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_green());
|
||||
println!();
|
||||
|
||||
println!("{}", "Project Details:".bright_cyan().bold());
|
||||
println!(" Name: {}", config.project_name);
|
||||
println!(" Location: {}", project_path.display());
|
||||
println!(" FTC SDK: {} at {}", config.ftc_sdk_version, config.ftc_sdk_path.display());
|
||||
println!();
|
||||
|
||||
if !installed.is_empty() {
|
||||
println!("{}", "Installed Components:".bright_green().bold());
|
||||
for component in installed {
|
||||
println!(" {} {}", "✓".green(), component);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
if !issues.is_empty() {
|
||||
println!("{}", "Manual Installation Required:".bright_yellow().bold());
|
||||
println!();
|
||||
for (name, instructions) in issues {
|
||||
println!("{} {}", "✗".red(), name.red().bold());
|
||||
println!();
|
||||
for line in instructions.lines() {
|
||||
println!(" {}", line);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
if issues.is_empty() {
|
||||
println!("{}", "✓ Project is ready for development!".bright_green().bold());
|
||||
println!();
|
||||
println!("{}", "Next steps:".bright_yellow().bold());
|
||||
println!(" 1. Review the code: {}", format!("cd {}", project_path.display()).bright_white());
|
||||
println!(" 2. Run tests: {}", "./gradlew test".bright_white());
|
||||
println!(" 3. Build: {}", "./build.sh (or build.bat on Windows)".bright_white());
|
||||
println!(" 4. Deploy to robot: {}", format!("weevil deploy {}", project_path.display()).bright_white());
|
||||
} else {
|
||||
println!("{}", "⚠ Please install the required components listed above".bright_yellow().bold());
|
||||
println!(" Then run {} to verify", format!("weevil setup {}", project_path.display()).bright_white());
|
||||
}
|
||||
println!();
|
||||
}
|
||||
393
src/commands/uninstall.rs
Normal file
393
src/commands/uninstall.rs
Normal file
@@ -0,0 +1,393 @@
|
||||
use anyhow::Result;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use colored::*;
|
||||
|
||||
use crate::sdk::SdkConfig;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum RemoveTarget {
|
||||
FtcSdk(PathBuf, String), // path, version label
|
||||
AndroidSdk(PathBuf),
|
||||
}
|
||||
|
||||
impl RemoveTarget {
|
||||
fn label(&self) -> String {
|
||||
match self {
|
||||
RemoveTarget::FtcSdk(_, version) => format!("FTC SDK {}", version),
|
||||
RemoveTarget::AndroidSdk(_) => "Android SDK".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &PathBuf {
|
||||
match self {
|
||||
RemoveTarget::FtcSdk(path, _) => path,
|
||||
RemoveTarget::AndroidSdk(path) => path,
|
||||
}
|
||||
}
|
||||
|
||||
fn size(&self) -> u64 {
|
||||
dir_size(self.path())
|
||||
}
|
||||
}
|
||||
|
||||
/// Uninstall Weevil-managed dependencies
|
||||
///
|
||||
/// - No args: removes ~/.weevil entirely
|
||||
/// - --dry-run: shows what would be removed
|
||||
/// - --only N [N ...]: selective removal of specific components
|
||||
pub fn uninstall_dependencies(dry_run: bool, targets: Option<Vec<usize>>) -> Result<()> {
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!("{}", " 🗑️ Weevil Uninstall - Remove Dependencies".bright_cyan().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!();
|
||||
|
||||
let sdk_config = SdkConfig::new()?;
|
||||
|
||||
// No --only flag: full uninstall, just nuke .weevil
|
||||
if targets.is_none() {
|
||||
return full_uninstall(&sdk_config, dry_run);
|
||||
}
|
||||
|
||||
// --only flag: selective removal
|
||||
let all_targets = scan_targets(&sdk_config);
|
||||
|
||||
if all_targets.is_empty() {
|
||||
println!("{}", "No Weevil-managed components found.".bright_green());
|
||||
println!();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Show numbered list
|
||||
println!("{}", "Found Weevil-managed components:".bright_yellow().bold());
|
||||
println!();
|
||||
for (i, target) in all_targets.iter().enumerate() {
|
||||
println!(" {}. {} — {}",
|
||||
(i + 1).to_string().bright_cyan().bold(),
|
||||
target.label(),
|
||||
format!("{} at {}", format_size(target.size()), target.path().display()).dimmed()
|
||||
);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Resolve selected indices
|
||||
let indices = targets.unwrap();
|
||||
let mut selected = Vec::new();
|
||||
for idx in indices {
|
||||
if idx == 0 || idx > all_targets.len() {
|
||||
println!("{} Invalid selection: {}. Valid range is 1–{}",
|
||||
"✗".red(), idx, all_targets.len());
|
||||
return Ok(());
|
||||
}
|
||||
selected.push(all_targets[idx - 1].clone());
|
||||
}
|
||||
|
||||
if dry_run {
|
||||
print_dry_run(&selected);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
print_removal_list(&selected);
|
||||
|
||||
if !confirm()? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
execute_removal(&selected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Full uninstall — removes the entire .weevil directory
|
||||
fn full_uninstall(sdk_config: &SdkConfig, dry_run: bool) -> Result<()> {
|
||||
if !sdk_config.cache_dir.exists() {
|
||||
println!("{}", "No Weevil-managed components found.".bright_green());
|
||||
println!();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let size = dir_size(&sdk_config.cache_dir);
|
||||
|
||||
if dry_run {
|
||||
let all_targets = scan_targets(sdk_config);
|
||||
|
||||
println!("{}", "── Dry Run ─────────────────────────────────────────────────".bright_yellow().bold());
|
||||
println!();
|
||||
println!("{}", format!("Contents of {}:", sdk_config.cache_dir.display()).bright_yellow().bold());
|
||||
println!();
|
||||
for (i, target) in all_targets.iter().enumerate() {
|
||||
println!(" {}. {} — {}",
|
||||
(i + 1).to_string().bright_cyan().bold(),
|
||||
target.label(),
|
||||
format!("{} at {}", format_size(target.size()), target.path().display()).dimmed()
|
||||
);
|
||||
}
|
||||
|
||||
// Note any system-installed dependencies that Weevil doesn't manage
|
||||
let mut has_external = false;
|
||||
|
||||
if sdk_config.android_sdk_path.exists()
|
||||
&& !sdk_config.android_sdk_path.to_string_lossy().contains(".weevil") {
|
||||
if !has_external {
|
||||
println!();
|
||||
has_external = true;
|
||||
}
|
||||
println!(" {} Android SDK at {} — not managed by Weevil, will not be removed",
|
||||
"ⓘ".bright_cyan(),
|
||||
sdk_config.android_sdk_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
if let Ok(gradle_version) = check_gradle() {
|
||||
if !has_external {
|
||||
println!();
|
||||
}
|
||||
println!(" {} Gradle {} — not managed by Weevil, will not be removed",
|
||||
"ⓘ".bright_cyan(),
|
||||
gradle_version
|
||||
);
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", format!("Total: {} ({})", sdk_config.cache_dir.display(), format_size(size)).bright_yellow().bold());
|
||||
println!();
|
||||
println!("{}", "To remove everything:".bright_yellow().bold());
|
||||
println!(" {}", "weevil uninstall".bright_cyan());
|
||||
println!();
|
||||
println!("{}", "To remove specific items:".bright_yellow().bold());
|
||||
println!(" {}", "weevil uninstall --only 1 2".bright_cyan());
|
||||
println!();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("{}", "This will permanently remove:".bright_yellow().bold());
|
||||
println!();
|
||||
println!(" {} {} ({})", "✗".red(), sdk_config.cache_dir.display(), format_size(size));
|
||||
println!();
|
||||
println!("{}", "Everything Weevil installed will be gone.".bright_yellow());
|
||||
println!();
|
||||
|
||||
if !confirm()? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!();
|
||||
print!(" Removing {} ... ", sdk_config.cache_dir.display());
|
||||
match fs::remove_dir_all(&sdk_config.cache_dir) {
|
||||
Ok(_) => {
|
||||
println!("{}", "✓".green());
|
||||
println!();
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!("{}", " ✓ Uninstall Complete".bright_green().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!();
|
||||
println!("{}", "Weevil binary is still installed. To remove it, delete the weevil executable.".bright_yellow());
|
||||
println!();
|
||||
println!("{}", "To reinstall dependencies later:".bright_yellow().bold());
|
||||
println!(" {}", "weevil setup".bright_cyan());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} ({})", "✗".red(), e);
|
||||
println!();
|
||||
println!("{}", "You may need to manually remove this directory.".bright_yellow());
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_dry_run(selected: &[RemoveTarget]) {
|
||||
println!("{}", "── Dry Run ─────────────────────────────────────────────────".bright_yellow().bold());
|
||||
println!();
|
||||
println!("{}", "The following would be removed:".bright_yellow());
|
||||
println!();
|
||||
let mut total: u64 = 0;
|
||||
for target in selected {
|
||||
let size = target.size();
|
||||
total += size;
|
||||
println!(" {} {} ({})", "✗".red(), target.label(), format_size(size));
|
||||
println!(" {}", target.path().display().to_string().dimmed());
|
||||
}
|
||||
println!();
|
||||
println!("{}", format!("Total: {}", format_size(total)).bright_yellow().bold());
|
||||
println!();
|
||||
println!("{}", "Run without --dry-run to actually remove these components.".dimmed());
|
||||
println!();
|
||||
}
|
||||
|
||||
fn print_removal_list(selected: &[RemoveTarget]) {
|
||||
println!("{}", "The following will be removed:".bright_yellow().bold());
|
||||
println!();
|
||||
let mut total: u64 = 0;
|
||||
for target in selected {
|
||||
let size = target.size();
|
||||
total += size;
|
||||
println!(" {} {} ({})", "✗".red(), target.label(), format_size(size));
|
||||
println!(" {}", target.path().display().to_string().dimmed());
|
||||
}
|
||||
println!();
|
||||
println!("{}", format!("Total: {}", format_size(total)).bright_yellow().bold());
|
||||
println!();
|
||||
}
|
||||
|
||||
fn confirm() -> Result<bool> {
|
||||
print!("{}", "Are you sure you want to continue? (y/N): ".bright_yellow());
|
||||
io::stdout().flush()?;
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
let answer = input.trim().to_lowercase();
|
||||
|
||||
if answer != "y" && answer != "yes" {
|
||||
println!();
|
||||
println!("{}", "Uninstall cancelled.".bright_green());
|
||||
println!();
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn execute_removal(selected: &[RemoveTarget]) {
|
||||
println!();
|
||||
println!("{}", "Removing components...".bright_yellow());
|
||||
println!();
|
||||
|
||||
let mut removed = Vec::new();
|
||||
let mut failed = Vec::new();
|
||||
|
||||
for target in selected {
|
||||
print!(" Removing {}... ", target.label());
|
||||
match fs::remove_dir_all(target.path()) {
|
||||
Ok(_) => {
|
||||
println!("{}", "✓".green());
|
||||
removed.push(target.clone());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{} ({})", "✗".red(), e);
|
||||
failed.push((target.clone(), e.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
|
||||
if failed.is_empty() {
|
||||
println!("{}", " ✓ Uninstall Complete".bright_green().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!();
|
||||
println!("{}", "Removed:".bright_green().bold());
|
||||
for target in &removed {
|
||||
println!(" {} {}", "✓".green(), target.label());
|
||||
}
|
||||
} else {
|
||||
println!("{}", " ⚠ Uninstall Completed with Errors".bright_yellow().bold());
|
||||
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
|
||||
println!();
|
||||
if !removed.is_empty() {
|
||||
println!("{}", "Removed:".bright_green().bold());
|
||||
for target in &removed {
|
||||
println!(" {} {}", "✓".green(), target.label());
|
||||
}
|
||||
println!();
|
||||
}
|
||||
println!("{}", "Failed to remove:".bright_red().bold());
|
||||
for (target, error) in &failed {
|
||||
println!(" {} {}: {}", "✗".red(), target.label(), error);
|
||||
}
|
||||
println!();
|
||||
println!("{}", "You may need to manually remove these directories.".bright_yellow());
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "To reinstall dependencies later:".bright_yellow().bold());
|
||||
println!(" {}", "weevil setup".bright_cyan());
|
||||
println!();
|
||||
}
|
||||
|
||||
/// Scan the cache directory for individual removable components (used by --only)
|
||||
fn scan_targets(sdk_config: &SdkConfig) -> Vec<RemoveTarget> {
|
||||
let mut targets = Vec::new();
|
||||
|
||||
if sdk_config.cache_dir.exists() {
|
||||
if let Ok(entries) = fs::read_dir(&sdk_config.cache_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
let path = entry.path();
|
||||
|
||||
if !path.is_dir() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if name.starts_with("ftc-sdk") {
|
||||
let version = crate::sdk::ftc::get_version(&path)
|
||||
.unwrap_or_else(|_| {
|
||||
if name == "ftc-sdk" {
|
||||
"default".to_string()
|
||||
} else {
|
||||
name.trim_start_matches("ftc-sdk-").to_string()
|
||||
}
|
||||
});
|
||||
targets.push(RemoveTarget::FtcSdk(path, version));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Android SDK — only if Weevil installed it (lives inside .weevil)
|
||||
if sdk_config.android_sdk_path.exists()
|
||||
&& sdk_config.android_sdk_path.to_string_lossy().contains(".weevil") {
|
||||
targets.push(RemoveTarget::AndroidSdk(sdk_config.android_sdk_path.clone()));
|
||||
}
|
||||
|
||||
targets
|
||||
}
|
||||
|
||||
fn check_gradle() -> Result<String> {
|
||||
let output = std::process::Command::new("gradle")
|
||||
.arg("--version")
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.starts_with("Gradle") {
|
||||
return Ok(line.replace("Gradle ", ""));
|
||||
}
|
||||
}
|
||||
Ok("installed (version unknown)".to_string())
|
||||
}
|
||||
Err(_) => anyhow::bail!("Gradle not found"),
|
||||
}
|
||||
}
|
||||
|
||||
fn dir_size(path: &PathBuf) -> u64 {
|
||||
let mut size: u64 = 0;
|
||||
if let Ok(entries) = fs::read_dir(path) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
size += dir_size(&path);
|
||||
} else if let Ok(metadata) = path.metadata() {
|
||||
size += metadata.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
fn format_size(bytes: u64) -> String {
|
||||
if bytes >= 1_073_741_824 {
|
||||
format!("{:.1} GB", bytes as f64 / 1_073_741_824.0)
|
||||
} else if bytes >= 1_048_576 {
|
||||
format!("{:.1} MB", bytes as f64 / 1_048_576.0)
|
||||
} else if bytes >= 1_024 {
|
||||
format!("{:.1} KB", bytes as f64 / 1_024.0)
|
||||
} else {
|
||||
format!("{} B", bytes)
|
||||
}
|
||||
}
|
||||
29
src/main.rs
29
src/main.rs
@@ -33,6 +33,26 @@ enum Commands {
|
||||
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
|
||||
@@ -99,6 +119,15 @@ fn main() -> Result<()> {
|
||||
Commands::New { name, ftc_sdk, android_sdk } => {
|
||||
commands::new::create_project(&name, ftc_sdk.as_deref(), android_sdk.as_deref())
|
||||
}
|
||||
Commands::Doctor => {
|
||||
commands::doctor::run_diagnostics()
|
||||
}
|
||||
Commands::Setup { path } => {
|
||||
commands::setup::setup_environment(path.as_deref())
|
||||
}
|
||||
Commands::Uninstall { dry_run, only } => {
|
||||
commands::uninstall::uninstall_dependencies(dry_run, only)
|
||||
}
|
||||
Commands::Upgrade { path } => {
|
||||
commands::upgrade::upgrade_project(&path)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user