fix: Complete Windows deployment pipeline

Fixes critical bugs in Windows APK deployment workflow including APK path
resolution, adb integration, and config file parsing.

Changes:
- Fix APK search to look for TeamCode-debug.apk instead of *app-debug.apk
- Strip both single and double quotes from batch file path parsing
- Add android_sdk_path to project configuration (.weevil.toml)
- Resolve adb.exe from Android SDK platform-tools directory
- Check adb install exit code and report deployment failures correctly
- Add migration support for old .weevil.toml files missing android_sdk_path
- Update all tests to use new ProjectConfig::new() signature

The deployment workflow now properly finds the generated APK, locates adb,
and reports success/failure accurately on Windows.
This commit is contained in:
Eric Ratliff
2026-01-25 18:53:16 -06:00
parent 6626ca83d1
commit 64826e2ce2
5 changed files with 67 additions and 17 deletions

View File

@@ -511,7 +511,7 @@ Built with frustration at unnecessarily complex robotics frameworks, and hope th
## Project Status ## Project Status
**Current Version:** 1.0.0-beta2 **Current Version:** 1.0.0-rc1
**What Works:** **What Works:**
- ✅ Project generation - ✅ Project generation
@@ -532,4 +532,7 @@ Built with frustration at unnecessarily complex robotics frameworks, and hope th
**Questions? Issues? Suggestions?** **Questions? Issues? Suggestions?**
Open an issue on NXGit or reach out to the FTC community. Let's make robot programming accessible for everyone! 🚀 📧 Email: [eric@nxws.dev](mailto:eric@nxws.dev)
🐛 Issues: Open an issue on the repository
Building better tools so you can build better robots. 🤖

View File

@@ -32,7 +32,7 @@ pub fn upgrade_project(path: &str) -> Result<()> {
let project_name = project_path.file_name() let project_name = project_path.file_name()
.and_then(|n| n.to_str()) .and_then(|n| n.to_str())
.unwrap_or("unknown"); .unwrap_or("unknown");
crate::project::ProjectConfig::new(project_name, sdk_config.ftc_sdk_path.clone())? crate::project::ProjectConfig::new(project_name, sdk_config.ftc_sdk_path.clone(), sdk_config.android_sdk_path.clone())?
}; };
println!("Current SDK: {}", project_config.ftc_sdk_path.display()); println!("Current SDK: {}", project_config.ftc_sdk_path.display());

View File

@@ -9,10 +9,16 @@ pub struct ProjectConfig {
pub weevil_version: String, pub weevil_version: String,
pub ftc_sdk_path: PathBuf, pub ftc_sdk_path: PathBuf,
pub ftc_sdk_version: String, pub ftc_sdk_version: String,
#[serde(default = "default_android_sdk_path")]
pub android_sdk_path: PathBuf,
}
fn default_android_sdk_path() -> PathBuf {
PathBuf::new()
} }
impl ProjectConfig { impl ProjectConfig {
pub fn new(project_name: &str, ftc_sdk_path: PathBuf) -> Result<Self> { pub fn new(project_name: &str, ftc_sdk_path: PathBuf, android_sdk_path: PathBuf) -> Result<Self> {
let ftc_sdk_version = crate::sdk::ftc::get_version(&ftc_sdk_path) let ftc_sdk_version = crate::sdk::ftc::get_version(&ftc_sdk_path)
.unwrap_or_else(|_| "unknown".to_string()); .unwrap_or_else(|_| "unknown".to_string());
@@ -21,6 +27,7 @@ impl ProjectConfig {
weevil_version: "1.0.0".to_string(), weevil_version: "1.0.0".to_string(),
ftc_sdk_path, ftc_sdk_path,
ftc_sdk_version, ftc_sdk_version,
android_sdk_path,
}) })
} }
@@ -34,9 +41,15 @@ impl ProjectConfig {
let contents = fs::read_to_string(&config_path) let contents = fs::read_to_string(&config_path)
.context("Failed to read .weevil.toml")?; .context("Failed to read .weevil.toml")?;
let config: ProjectConfig = toml::from_str(&contents) let mut config: ProjectConfig = toml::from_str(&contents)
.context("Failed to parse .weevil.toml")?; .context("Failed to parse .weevil.toml")?;
// Migrate old configs that don't have android_sdk_path
if config.android_sdk_path.as_os_str().is_empty() {
let sdk_config = crate::sdk::SdkConfig::new()?;
config.android_sdk_path = sdk_config.android_sdk_path;
}
Ok(config) Ok(config)
} }
@@ -77,6 +90,7 @@ impl ProjectConfig {
println!(); println!();
println!("{:.<20} {}", "FTC SDK Path", self.ftc_sdk_path.display().to_string().bright_white()); println!("{:.<20} {}", "FTC SDK Path", self.ftc_sdk_path.display().to_string().bright_white());
println!("{:.<20} {}", "FTC SDK Version", self.ftc_sdk_version.bright_white()); println!("{:.<20} {}", "FTC SDK Version", self.ftc_sdk_version.bright_white());
println!("{:.<20} {}", "Android SDK Path", self.android_sdk_path.display().to_string().bright_white());
println!(); println!();
} }
} }

View File

@@ -77,7 +77,7 @@ impl ProjectBuilder {
fn create_project_files(&self, project_path: &Path, sdk_config: &SdkConfig) -> Result<()> { fn create_project_files(&self, project_path: &Path, sdk_config: &SdkConfig) -> Result<()> {
// Create .weevil.toml config // Create .weevil.toml config
let project_config = ProjectConfig::new(&self.name, sdk_config.ftc_sdk_path.clone())?; let project_config = ProjectConfig::new(&self.name, sdk_config.ftc_sdk_path.clone(), sdk_config.android_sdk_path.clone())?;
project_config.save(project_path)?; project_config.save(project_path)?;
// README.md // README.md
@@ -334,34 +334,62 @@ echo "✓ Deployed!"
let deploy_bat = r#"@echo off let deploy_bat = r#"@echo off
setlocal enabledelayedexpansion setlocal enabledelayedexpansion
REM Read SDK path from config REM Read SDK paths from config
for /f "tokens=2 delims==" %%a in ('findstr /c:"ftc_sdk_path" .weevil.toml') do ( for /f "tokens=2 delims==" %%a in ('findstr /c:"ftc_sdk_path" .weevil.toml') do set SDK_DIR=%%a
set SDK_DIR=%%a for /f "tokens=2 delims==" %%a in ('findstr /c:"android_sdk_path" .weevil.toml') do set ANDROID_SDK=%%a
set SDK_DIR=!SDK_DIR:"=!
set SDK_DIR=!SDK_DIR: =! REM Strip all quotes (both single and double)
) set SDK_DIR=%SDK_DIR:"=%
set SDK_DIR=%SDK_DIR:'=%
set SDK_DIR=%SDK_DIR: =%
set ANDROID_SDK=%ANDROID_SDK:"=%
set ANDROID_SDK=%ANDROID_SDK:'=%
set ANDROID_SDK=%ANDROID_SDK: =%
if not defined SDK_DIR ( if not defined SDK_DIR (
echo Error: Could not read FTC SDK path from .weevil.toml echo Error: Could not read FTC SDK path from .weevil.toml
exit /b 1 exit /b 1
) )
if not defined ANDROID_SDK (
echo Error: Could not read Android SDK path from .weevil.toml
exit /b 1
)
REM Set ADB path
set ADB_PATH=%ANDROID_SDK%\platform-tools\adb.exe
echo Building APK... echo Building APK...
call gradlew.bat buildApk call gradlew.bat buildApk
echo. echo.
echo Deploying to Control Hub... echo Deploying to Control Hub...
REM Find APK REM Find APK - look for TeamCode-debug.apk
for /f "delims=" %%i in ('dir /s /b "%SDK_DIR%\*app-debug.apk" 2^>nul') do set APK=%%i for /f "delims=" %%i in ('dir /s /b "%SDK_DIR%\TeamCode-debug.apk" 2^>nul') do set APK=%%i
if not defined APK ( if not defined APK (
echo Error: APK not found echo Error: APK not found
exit /b 1 exit /b 1
) )
echo Found APK: %APK%
REM Check for adb
if not exist "%ADB_PATH%" (
echo Error: adb not found at %ADB_PATH%
echo Run: weevil sdk install
exit /b 1
)
echo Installing: %APK% echo Installing: %APK%
adb install -r "%APK%" "%ADB_PATH%" install -r "%APK%"
if errorlevel 1 (
echo.
echo Deployment failed!
exit /b 1
)
echo. echo.
echo Deployed! echo Deployed!

View File

@@ -13,6 +13,7 @@ use weevil::sdk::SdkConfig;
fn test_config_create_and_save() { fn test_config_create_and_save() {
let temp_dir = TempDir::new().unwrap(); let temp_dir = TempDir::new().unwrap();
let sdk_path = temp_dir.path().join("mock-sdk"); let sdk_path = temp_dir.path().join("mock-sdk");
let android_sdk_path = temp_dir.path().join("android-sdk");
// Create minimal SDK structure // Create minimal SDK structure
fs::create_dir_all(sdk_path.join("TeamCode/src/main/java")).unwrap(); fs::create_dir_all(sdk_path.join("TeamCode/src/main/java")).unwrap();
@@ -20,10 +21,11 @@ fn test_config_create_and_save() {
fs::write(sdk_path.join("build.gradle"), "// test").unwrap(); fs::write(sdk_path.join("build.gradle"), "// test").unwrap();
fs::write(sdk_path.join(".version"), "v10.1.1").unwrap(); fs::write(sdk_path.join(".version"), "v10.1.1").unwrap();
let config = ProjectConfig::new("test-robot", sdk_path.clone()).unwrap(); let config = ProjectConfig::new("test-robot", sdk_path.clone(), android_sdk_path.clone()).unwrap();
assert_eq!(config.project_name, "test-robot"); assert_eq!(config.project_name, "test-robot");
assert_eq!(config.ftc_sdk_path, sdk_path); assert_eq!(config.ftc_sdk_path, sdk_path);
assert_eq!(config.android_sdk_path, android_sdk_path);
assert_eq!(config.weevil_version, "1.0.0"); assert_eq!(config.weevil_version, "1.0.0");
// Save and reload // Save and reload
@@ -34,12 +36,14 @@ fn test_config_create_and_save() {
let loaded = ProjectConfig::load(&project_path).unwrap(); let loaded = ProjectConfig::load(&project_path).unwrap();
assert_eq!(loaded.project_name, config.project_name); assert_eq!(loaded.project_name, config.project_name);
assert_eq!(loaded.ftc_sdk_path, config.ftc_sdk_path); assert_eq!(loaded.ftc_sdk_path, config.ftc_sdk_path);
assert_eq!(loaded.android_sdk_path, config.android_sdk_path);
} }
#[test] #[test]
fn test_config_toml_format() { fn test_config_toml_format() {
let temp_dir = TempDir::new().unwrap(); let temp_dir = TempDir::new().unwrap();
let sdk_path = temp_dir.path().join("sdk"); let sdk_path = temp_dir.path().join("sdk");
let android_sdk_path = temp_dir.path().join("android-sdk");
// Create minimal SDK // Create minimal SDK
fs::create_dir_all(sdk_path.join("TeamCode/src/main/java")).unwrap(); fs::create_dir_all(sdk_path.join("TeamCode/src/main/java")).unwrap();
@@ -47,7 +51,7 @@ fn test_config_toml_format() {
fs::write(sdk_path.join("build.gradle"), "// test").unwrap(); fs::write(sdk_path.join("build.gradle"), "// test").unwrap();
fs::write(sdk_path.join(".version"), "v10.1.1").unwrap(); fs::write(sdk_path.join(".version"), "v10.1.1").unwrap();
let config = ProjectConfig::new("my-robot", sdk_path).unwrap(); let config = ProjectConfig::new("my-robot", sdk_path, android_sdk_path).unwrap();
let project_path = temp_dir.path().join("project"); let project_path = temp_dir.path().join("project");
fs::create_dir_all(&project_path).unwrap(); fs::create_dir_all(&project_path).unwrap();
@@ -59,6 +63,7 @@ fn test_config_toml_format() {
assert!(content.contains("weevil_version = \"1.0.0\"")); assert!(content.contains("weevil_version = \"1.0.0\""));
assert!(content.contains("ftc_sdk_path")); assert!(content.contains("ftc_sdk_path"));
assert!(content.contains("ftc_sdk_version")); assert!(content.contains("ftc_sdk_version"));
assert!(content.contains("android_sdk_path"));
} }
#[test] #[test]