#!/bin/bash # FTC Project Generator - Shared Library Functions # Copyright (c) 2026 Nexus Workshops LLC # Licensed under MIT License # Get the directory where this script lives get_script_dir() { echo "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" } # Get generator version get_generator_version() { local script_dir="$(get_script_dir)" local version_file="$script_dir/../VERSION" if [ -f "$version_file" ]; then cat "$version_file" | tr -d '\n\r' else echo "unknown" fi } # Process template file, replacing placeholders process_template() { local input="$1" local output="$2" sed -e "s|{{PROJECT_NAME}}|${PROJECT_NAME}|g" \ -e "s|{{SDK_DIR}}|${FTC_SDK_DIR}|g" \ -e "s|{{FTC_VERSION}}|${FTC_VERSION}|g" \ -e "s|{{GENERATOR_VERSION}}|${GENERATOR_VERSION}|g" \ "$input" > "$output" } # Copy template file copy_template() { local src="$1" local dest="$2" cp "$src" "$dest" } # Create project structure create_project_structure() { local project_dir="$1" mkdir -p "$project_dir" cd "$project_dir" mkdir -p src/main/java/robot/subsystems mkdir -p src/main/java/robot/hardware mkdir -p src/main/java/robot/opmodes mkdir -p src/test/java/robot/subsystems mkdir -p src/test/java/robot/hardware mkdir -p gradle/wrapper } # Install all project templates install_templates() { local project_dir="$1" local template_dir="$2" cd "$project_dir" copy_template "$template_dir/Pose2d.java" "src/main/java/robot/Pose2d.java" copy_template "$template_dir/Drive.java" "src/main/java/robot/subsystems/Drive.java" copy_template "$template_dir/MecanumDrive.java" "src/main/java/robot/hardware/MecanumDrive.java" copy_template "$template_dir/TeleOp.java" "src/main/java/robot/opmodes/TeleOp.java" copy_template "$template_dir/DriveTest.java" "src/test/java/robot/subsystems/DriveTest.java" copy_template "$template_dir/build.gradle.kts" "build.gradle.kts" process_template "$template_dir/settings.gradle.kts.template" "settings.gradle.kts" process_template "$template_dir/gitignore.template" ".gitignore" process_template "$template_dir/README.md.template" "README.md" copy_template "$template_dir/build.sh" "build.sh" chmod +x "build.sh" create_deploy_script "$project_dir" echo "${GENERATOR_VERSION}" > ".ftc-generator-version" } # Create deploy script create_deploy_script() { local project_dir="$1" cat > "$project_dir/deploy-to-robot.sh" <<'ENDSCRIPT' #!/bin/bash set -e CONTROL_HUB_IP="${CONTROL_HUB_IP:-192.168.43.1}" CONTROL_HUB_PORT="5555" while [[ $# -gt 0 ]]; do case $1 in -h|--help) echo "════════════════════════════════════════════════════════════════" echo " Deploy FTC Project to Control Hub" echo "════════════════════════════════════════════════════════════════" echo "" echo "Usage: $0 [options]" echo "" echo "Options:" echo " --usb Force USB connection" echo " --wifi Force WiFi Direct connection" echo " -i, --ip Custom Control Hub IP" echo " -h, --help Show this help" echo "" echo "Examples:" echo " $0 # Auto-detect connection" echo " $0 --usb # Use USB only" echo " $0 --wifi # Use WiFi Direct" echo " $0 -i 192.168.1.5 # Custom IP" echo "" exit 0 ;; -i|--ip) CONTROL_HUB_IP="$2"; shift 2 ;; --usb) FORCE_USB=true; shift ;; --wifi) FORCE_WIFI=true; shift ;; *) shift ;; esac done echo "════════════════════════════════════════════════════════════════" echo " FTC Project Deployment" echo "════════════════════════════════════════════════════════════════" echo "" # Step 1: Deploy code to SDK echo "Step 1: Deploying code to SDK TeamCode..." if ! ./gradlew deployToSDK; then echo "" echo "Error: Failed to deploy code" echo "Make sure you're in the project directory" exit 1 fi echo "✓ Code deployed" echo "" # Step 2: Build APK echo "Step 2: Building APK..." SDK_DIR="${HOME}/ftc-sdk" if [ ! -d "$SDK_DIR" ]; then echo "" echo "════════════════════════════════════════════════════════════════" echo " Error: FTC SDK Not Found" echo "════════════════════════════════════════════════════════════════" echo "" echo "The FTC SDK should be at: $SDK_DIR" echo "" echo "This should have been set up when you created the project." echo "" echo "To fix:" echo " git clone --depth 1 --branch v10.1.1 \\" echo " https://github.com/FIRST-Tech-Challenge/FtcRobotController.git \\" echo " ~/ftc-sdk" echo "" exit 1 fi cd "$SDK_DIR" || exit 1 # Check for Android SDK configuration if [ ! -f "local.properties" ] && [ -z "$ANDROID_HOME" ]; then echo "" echo "Android SDK not found. Attempting auto-setup..." # Try to find common Android SDK locations FOUND_SDK="" for loc in "$HOME/Android/Sdk" "$HOME/.android-sdk" "$HOME/Library/Android/sdk"; do if [ -d "$loc" ] && [ -d "$loc/platforms" ]; then FOUND_SDK="$loc" break fi done if [ -n "$FOUND_SDK" ]; then echo "✓ Found Android SDK at $FOUND_SDK" echo "sdk.dir=$FOUND_SDK" > local.properties export ANDROID_HOME="$FOUND_SDK" else echo "" echo "════════════════════════════════════════════════════════════════" echo " Error: Android SDK Not Configured" echo "════════════════════════════════════════════════════════════════" echo "" echo "The FTC SDK needs the Android SDK to build APKs." echo "" echo "FIX OPTION 1: Run setup automatically" echo " Create a new project with ftc-new-project (it will set up Android SDK)" echo "" echo "FIX OPTION 2: Set ANDROID_HOME manually" echo " export ANDROID_HOME=/path/to/android/sdk" echo " echo 'export ANDROID_HOME=/path/to/android/sdk' >> ~/.bashrc" echo "" echo "FIX OPTION 3: Create local.properties" echo " echo 'sdk.dir=/path/to/android/sdk' > ~/ftc-sdk/local.properties" echo "" echo "════════════════════════════════════════════════════════════════" echo " How to Install Android SDK" echo "════════════════════════════════════════════════════════════════" echo "" echo "1. Download Android Studio:" echo " https://developer.android.com/studio" echo "" echo "2. Install and open Android Studio" echo "" echo "3. Go to: Tools → SDK Manager" echo "" echo "4. Note the 'Android SDK Location' path shown" echo "" echo "5. Use that path in FIX OPTION 2 or 3 above" echo "" echo "Typical Android SDK locations:" echo " • Linux: ~/Android/Sdk" echo " • macOS: ~/Library/Android/sdk" echo " • Windows: C:\\Users\\YourName\\AppData\\Local\\Android\\Sdk" echo "" echo "════════════════════════════════════════════════════════════════" echo "" exit 1 fi fi # Build the APK (assembleDebug creates the actual APK file) # Suppress Java version warnings by setting gradle.properties if [ ! -f "gradle.properties" ] || ! grep -q "android.javaCompile.suppressSourceTargetDeprecationWarning" gradle.properties 2>/dev/null; then echo "" >> gradle.properties echo "# Suppress Java version deprecation warnings" >> gradle.properties echo "android.javaCompile.suppressSourceTargetDeprecationWarning=true" >> gradle.properties fi if ! ./gradlew assembleDebug; then echo "" echo "Error: APK build failed" echo "Check the error messages above for details" exit 1 fi # Find the APK - path varies by FTC SDK version APK_PATH="" for possible_path in \ "$SDK_DIR/TeamCode/build/outputs/apk/debug/TeamCode-debug.apk" \ "$SDK_DIR/FtcRobotController/build/outputs/apk/debug/FtcRobotController-debug.apk" \ "$SDK_DIR/build/outputs/apk/debug/FtcRobotController-debug.apk" \ "$SDK_DIR/FtcRobotController/build/outputs/apk/FtcRobotController-debug.apk"; do if [ -f "$possible_path" ]; then APK_PATH="$possible_path" break fi done # If still not found, search for it if [ -z "$APK_PATH" ]; then APK_PATH=$(find "$SDK_DIR" -name "*-debug.apk" -type f 2>/dev/null | head -1) fi if [ -z "$APK_PATH" ] || [ ! -f "$APK_PATH" ]; then echo "" echo "Error: APK not found after build" echo "" echo "Build succeeded but APK location is unexpected." echo "Searched in:" echo " • $SDK_DIR/TeamCode/build/outputs/apk/debug/" echo " • $SDK_DIR/FtcRobotController/build/outputs/apk/debug/" echo "" echo "To find it manually:" echo " find ~/ftc-sdk -name '*.apk' -type f" echo "" exit 1 fi echo "✓ APK built at: $(basename "$APK_PATH")" echo "" # Step 3: Install to Control Hub echo "Step 3: Installing to Control Hub..." if ! command -v adb &> /dev/null; then echo "" echo "════════════════════════════════════════════════════════════════" echo " Error: adb Command Not Found" echo "════════════════════════════════════════════════════════════════" echo "" echo "The 'adb' tool is needed to install APKs to the Control Hub." echo "" echo "Install:" echo " • Ubuntu/Debian: sudo apt install android-tools-adb" echo " • Arch Linux: sudo pacman -S android-tools" echo " • macOS: brew install android-platform-tools" echo "" echo "Or download Android Platform Tools:" echo " https://developer.android.com/studio/releases/platform-tools" echo "" exit 1 fi echo "Checking for Control Hub connection..." INSTALLED=false # Try USB first if [ "$FORCE_WIFI" != "true" ]; then USB_COUNT=$(adb devices 2>/dev/null | grep -v "List" | grep -E "device$|unauthorized$" | wc -l) if [ "$USB_COUNT" -gt 0 ]; then # Check if authorized if adb devices 2>/dev/null | grep -q "unauthorized"; then echo "" echo "════════════════════════════════════════════════════════════════" echo " USB Device Found but Unauthorized" echo "════════════════════════════════════════════════════════════════" echo "" echo "A Control Hub is connected via USB but not authorized." echo "" echo "To fix:" echo " 1. Check the Control Hub screen" echo " 2. Allow USB debugging when prompted" echo " 3. Run this script again" echo "" exit 1 fi echo "✓ Control Hub connected via USB" if adb install -r "$APK_PATH" 2>&1; then INSTALLED=true fi elif [ "$FORCE_USB" = "true" ]; then echo "" echo "════════════════════════════════════════════════════════════════" echo " No USB Device Found" echo "════════════════════════════════════════════════════════════════" echo "" echo "No Control Hub detected via USB (--usb specified)." echo "" echo "Make sure:" echo " • Control Hub is powered on" echo " • USB cable is connected" echo " • USB debugging is enabled" echo "" echo "Check connection: adb devices" echo "" exit 1 fi fi # Try WiFi if USB didn't work if [ "$INSTALLED" = "false" ] && [ "$FORCE_USB" != "true" ]; then echo "No USB connection. Trying WiFi at $CONTROL_HUB_IP:$CONTROL_HUB_PORT..." # Try to connect with timeout adb connect "$CONTROL_HUB_IP:$CONTROL_HUB_PORT" &>/dev/null & ADB_CONNECT_PID=$! # Wait up to 5 seconds for connection for i in {1..10}; do if ! kill -0 $ADB_CONNECT_PID 2>/dev/null; then break fi sleep 0.5 done # Kill if still running kill $ADB_CONNECT_PID 2>/dev/null || true # Check if connected if adb devices 2>/dev/null | grep -q "$CONTROL_HUB_IP"; then echo "✓ Connected via WiFi" if adb install -r "$APK_PATH" 2>&1; then INSTALLED=true fi else echo "✗ Could not connect to Control Hub" fi fi if [ "$INSTALLED" = "false" ]; then echo "" echo "════════════════════════════════════════════════════════════════" echo " No Control Hub Found" echo "════════════════════════════════════════════════════════════════" echo "" echo "Could not detect a Control Hub via USB or WiFi." echo "" echo "APK was built successfully at:" echo " $APK_PATH" echo "" echo "To install later:" echo "" echo "Option 1: USB Connection" echo " 1. Connect Control Hub via USB" echo " 2. Run: adb install -r \"$APK_PATH\"" echo "" echo "Option 2: WiFi Connection" echo " 1. Connect to Control Hub WiFi network" echo " 2. Run: adb connect 192.168.43.1:5555" echo " 3. Run: adb install -r \"$APK_PATH\"" echo "" echo "Option 3: Run this script again when connected" echo " ./deploy-to-robot.sh" echo "" echo "Option 4: Custom IP" echo " ./deploy-to-robot.sh -i YOUR_IP" echo "" echo "════════════════════════════════════════════════════════════════" echo "" exit 0 fi echo "" echo "════════════════════════════════════════════════════════════════" echo " ✓ Deployment Complete!" echo "════════════════════════════════════════════════════════════════" echo "" echo "On Driver Station:" echo " 1. Go to OpModes menu" echo " 2. Select TeleOp → 'Main TeleOp'" echo " 3. Press INIT, then START" echo "" echo "Your code is now running on the robot! 🤖" echo "" ENDSCRIPT chmod +x "$project_dir/deploy-to-robot.sh" } # Setup Gradle wrapper setup_gradle_wrapper() { local project_dir="$1" cd "$project_dir" # Create gradle wrapper properties mkdir -p gradle/wrapper cat > gradle/wrapper/gradle-wrapper.properties < /dev/null; then echo "Error: gradle command not found" echo "Gradle must be installed to generate wrapper scripts" echo "" echo "Install:" echo " Ubuntu/Debian: sudo apt install gradle" echo " macOS: brew install gradle" return 1 fi echo "Generating Gradle wrapper..." # Try with system gradle first if gradle wrapper --gradle-version 8.9 --no-daemon 2>/dev/null; then echo "✓ Gradle wrapper created" else echo "" echo "System Gradle failed. Using fallback method..." # Download wrapper jar directly local wrapper_jar_url="https://raw.githubusercontent.com/gradle/gradle/v8.9.0/gradle/wrapper/gradle-wrapper.jar" if command -v curl &> /dev/null; then curl -sL "$wrapper_jar_url" -o gradle/wrapper/gradle-wrapper.jar 2>/dev/null elif command -v wget &> /dev/null; then wget -q "$wrapper_jar_url" -O gradle/wrapper/gradle-wrapper.jar 2>/dev/null else echo "Error: Need curl or wget to download wrapper jar" return 1 fi if [ ! -f "gradle/wrapper/gradle-wrapper.jar" ]; then echo "Error: Failed to download gradle-wrapper.jar" return 1 fi # Create proper gradlew script cat > gradlew <<'GRADLEW_END' #!/bin/sh APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_BASE_NAME=${0##*/} # Determine the Java command if [ -n "$JAVA_HOME" ] ; then JAVACMD=$JAVA_HOME/bin/java else JAVACMD=java fi # Check if Java is available if ! "$JAVACMD" -version > /dev/null 2>&1; then echo "ERROR: JAVA_HOME is not set and no 'java' command could be found" exit 1 fi # Execute Gradle exec "$JAVACMD" -Xmx64m -Xms64m -Dorg.gradle.appname="$APP_BASE_NAME" -classpath "$(dirname "$0")/gradle/wrapper/gradle-wrapper.jar" org.gradle.wrapper.GradleWrapperMain "$@" GRADLEW_END chmod +x gradlew # Create Windows batch file cat > gradlew.bat <<'GRADLEWBAT_END' @if "%DEBUG%" == "" @echo off setlocal enabledelayedexpansion set DIRNAME=%~dp0 set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% set CLASSPATH=%APP_HOME%gradle\wrapper\gradle-wrapper.jar if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe goto execute :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%\bin\java.exe :execute "%JAVA_EXE%" -Xmx64m -Xms64m -Dorg.gradle.appname="%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end GRADLEWBAT_END echo "✓ Gradle wrapper created (via fallback)" fi # Verify wrapper was created if [ ! -f "gradlew" ]; then echo "Error: gradlew script not created" return 1 fi } # Check if project is a generator project is_generator_project() { local project_dir="$1" [ -f "$project_dir/.ftc-generator-version" ] } # Get project generator version get_project_generator_version() { local project_dir="$1" if [ -f "$project_dir/.ftc-generator-version" ]; then cat "$project_dir/.ftc-generator-version" | tr -d '\n\r' else echo "unknown" fi } # Upgrade project upgrade_project() { local project_dir="$1" local template_dir="$2" local old_version=$(get_project_generator_version "$project_dir") echo "Upgrading project from $old_version to ${GENERATOR_VERSION}..." cd "$project_dir" copy_template "$template_dir/build.gradle.kts" "build.gradle.kts" process_template "$template_dir/settings.gradle.kts.template" "settings.gradle.kts" process_template "$template_dir/gitignore.template" ".gitignore" copy_template "$template_dir/build.sh" "build.sh" chmod +x "build.sh" create_deploy_script "$project_dir" echo "${GENERATOR_VERSION}" > ".ftc-generator-version" echo "✓ Upgrade complete" } # Initialize git repo init_git_repo() { local project_dir="$1" cd "$project_dir" if [ ! -d ".git" ]; then git init > /dev/null 2>&1 git add . > /dev/null 2>&1 git commit -m "Initial commit from FTC Project Generator v${GENERATOR_VERSION}" > /dev/null 2>&1 echo "✓ Git repository initialized" fi } # Check prerequisites check_prerequisites() { local missing=() command -v git &> /dev/null || missing+=("git") command -v java &> /dev/null || missing+=("java") command -v gradle &> /dev/null || missing+=("gradle") if [ "${#missing[@]}" -gt 0 ]; then echo "Error: Missing tools: ${missing[*]}" echo "Install: sudo apt install ${missing[*]}" return 1 fi echo "✓ All prerequisites satisfied" return 0 } # Setup Android SDK automatically setup_android_sdk() { local android_sdk_dir="${ANDROID_SDK_DIR:-$HOME/.android-sdk}" # Check if already configured if [ -n "$ANDROID_HOME" ] && [ -d "$ANDROID_HOME" ]; then echo "✓ Android SDK already configured at $ANDROID_HOME" return 0 fi if [ -d "$android_sdk_dir" ] && [ -f "$android_sdk_dir/cmdline-tools/latest/bin/sdkmanager" ]; then echo "✓ Android SDK found at $android_sdk_dir" export ANDROID_HOME="$android_sdk_dir" return 0 fi echo "" echo ">>> Setting up Android SDK..." echo "This is needed to build APKs for the Control Hub." echo "Download size: ~150MB" echo "" # Detect OS local os_type="" case "$(uname -s)" in Linux*) os_type="linux" ;; Darwin*) os_type="mac" ;; *) echo "Unsupported OS for auto-setup" return 1 ;; esac # Download command-line tools local tools_url="" if [ "$os_type" = "linux" ]; then tools_url="https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" else tools_url="https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip" fi echo "Downloading Android SDK command-line tools..." local temp_zip=$(mktemp -d)/cmdline-tools.zip if command -v curl &> /dev/null; then curl -L "$tools_url" -o "$temp_zip" 2>/dev/null || { echo "Error: Failed to download Android SDK tools" return 1 } elif command -v wget &> /dev/null; then wget -q "$tools_url" -O "$temp_zip" || { echo "Error: Failed to download Android SDK tools" return 1 } else echo "Error: Need curl or wget to download" return 1 fi # Extract echo "Installing to $android_sdk_dir..." mkdir -p "$android_sdk_dir/cmdline-tools" if ! command -v unzip &> /dev/null; then echo "Error: unzip not found. Install with: sudo apt install unzip" return 1 fi unzip -q "$temp_zip" -d "$android_sdk_dir/cmdline-tools" mv "$android_sdk_dir/cmdline-tools/cmdline-tools" "$android_sdk_dir/cmdline-tools/latest" rm -rf "$(dirname "$temp_zip")" export ANDROID_HOME="$android_sdk_dir" export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin" # Accept licenses and install essentials echo "Installing Android SDK components..." yes | sdkmanager --licenses > /dev/null 2>&1 || true sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" > /dev/null 2>&1 # Add to shell RC files for persistence local shell_rc="" if [ -n "$BASH_VERSION" ]; then shell_rc="$HOME/.bashrc" elif [ -n "$ZSH_VERSION" ]; then shell_rc="$HOME/.zshrc" fi if [ -n "$shell_rc" ] && [ -f "$shell_rc" ]; then if ! grep -q "ANDROID_HOME.*android-sdk" "$shell_rc"; then echo "" >> "$shell_rc" echo "# Android SDK (added by FTC Project Generator)" >> "$shell_rc" echo "export ANDROID_HOME=\"$android_sdk_dir\"" >> "$shell_rc" echo "export PATH=\"\$PATH:\$ANDROID_HOME/cmdline-tools/latest/bin:\$ANDROID_HOME/platform-tools\"" >> "$shell_rc" echo "Note: Added ANDROID_HOME to $shell_rc" fi fi # Create local.properties in FTC SDK if [ -n "$FTC_SDK_DIR" ] && [ -d "$FTC_SDK_DIR" ]; then echo "sdk.dir=$android_sdk_dir" > "$FTC_SDK_DIR/local.properties" fi echo "✓ Android SDK configured at $android_sdk_dir" echo "" }