feat: add Android Studio integration (v1.1.0)
Generate .idea/ run configurations for one-click build and deployment directly from Android Studio. Students can now open projects in the IDE they already know and hit the green play button to deploy to their robot. Run configurations generated: - Build: compiles APK without deploying (build.sh / build.bat) - Deploy (auto): auto-detects USB or WiFi connection - Deploy (USB): forces USB deployment (deploy.sh --usb) - Deploy (WiFi): forces WiFi deployment (deploy.sh --wifi) - Test: runs unit tests (./gradlew test) Both Unix (.sh) and Windows (.bat) variants are generated. Android Studio automatically hides the configurations whose script files don't exist, so only platform-appropriate configs appear in the Run dropdown. workspace.xml configures the project tree to hide internal directories (build/, .gradle/, gradle/) and expand src/ by default, giving students a clean view of just their code and the deployment scripts. Technical notes: - Uses ShConfigurationType (not the old ShellScript type) for Android Studio 2025.2+ compatibility - All paths use $PROJECT_DIR$ for portability - INTERPRETER_PATH is /bin/bash on Unix, cmd.exe on Windows - upgrade.rs regenerates all .idea/ files so run configs stay in sync with any future deploy.sh flag changes Requires Shell Script plugin (by JetBrains) to be installed in Android Studio. README.md updated with installation instructions. Files modified: - src/project/mod.rs: generate_idea_files() writes 5 XML files per platform - src/commands/upgrade.rs: add .idea/ files to safe_to_overwrite
This commit is contained in:
@@ -52,6 +52,19 @@ pub fn upgrade_project(path: &str) -> Result<()> {
|
|||||||
"gradle/wrapper/gradle-wrapper.properties",
|
"gradle/wrapper/gradle-wrapper.properties",
|
||||||
"gradle/wrapper/gradle-wrapper.jar",
|
"gradle/wrapper/gradle-wrapper.jar",
|
||||||
".gitignore",
|
".gitignore",
|
||||||
|
// Android Studio integration — regenerated so run configs stay in
|
||||||
|
// sync if deploy.sh flags or script names ever change.
|
||||||
|
".idea/workspace.xml",
|
||||||
|
".idea/runConfigurations/Build.xml",
|
||||||
|
".idea/runConfigurations/Build (Windows).xml",
|
||||||
|
".idea/runConfigurations/Deploy (auto).xml",
|
||||||
|
".idea/runConfigurations/Deploy (auto) (Windows).xml",
|
||||||
|
".idea/runConfigurations/Deploy (USB).xml",
|
||||||
|
".idea/runConfigurations/Deploy (USB) (Windows).xml",
|
||||||
|
".idea/runConfigurations/Deploy (WiFi).xml",
|
||||||
|
".idea/runConfigurations/Deploy (WiFi) (Windows).xml",
|
||||||
|
".idea/runConfigurations/Test.xml",
|
||||||
|
".idea/runConfigurations/Test (Windows).xml",
|
||||||
];
|
];
|
||||||
|
|
||||||
println!("{}", "Updating infrastructure files...".bright_yellow());
|
println!("{}", "Updating infrastructure files...".bright_yellow());
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ impl ProjectBuilder {
|
|||||||
"src/test/java/robot",
|
"src/test/java/robot",
|
||||||
"src/test/java/robot/subsystems",
|
"src/test/java/robot/subsystems",
|
||||||
"gradle/wrapper",
|
"gradle/wrapper",
|
||||||
|
".idea/runConfigurations",
|
||||||
];
|
];
|
||||||
|
|
||||||
for dir in dirs {
|
for dir in dirs {
|
||||||
@@ -416,6 +417,304 @@ class BasicTest {
|
|||||||
test_file
|
test_file
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Android Studio integration: .idea/ files
|
||||||
|
self.generate_idea_files(project_path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate .idea/ files for Android Studio integration.
|
||||||
|
///
|
||||||
|
/// The goal is for students to open the project in Android Studio and see
|
||||||
|
/// a clean file tree (just src/ and the scripts) with Run configurations
|
||||||
|
/// that invoke Weevil's shell scripts directly. All the internal plumbing
|
||||||
|
/// (sdk/, .gradle/, build/) is hidden from the IDE view.
|
||||||
|
///
|
||||||
|
/// Android Studio uses IntelliJ's run configuration XML format. The
|
||||||
|
/// ShellScript type invokes a script relative to the project root — exactly
|
||||||
|
/// what we want since deploy.sh and build.sh already live there.
|
||||||
|
fn generate_idea_files(&self, project_path: &Path) -> Result<()> {
|
||||||
|
// workspace.xml — controls the file-tree view and hides internals.
|
||||||
|
// We use a ProjectViewPane exclude pattern list rather than touching
|
||||||
|
// the module's source roots, so this works regardless of whether the
|
||||||
|
// student has opened the project before.
|
||||||
|
let workspace_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectViewManager">
|
||||||
|
<state>
|
||||||
|
<navigator currentProjector="ProjectFiles" hideEmptyMiddlePackages="true" sortByType="true">
|
||||||
|
<state>
|
||||||
|
<expand>
|
||||||
|
<file url="file://$PROJECT_DIR$/src" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java/robot" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/test" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/test/java" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/test/java/robot" />
|
||||||
|
</expand>
|
||||||
|
</state>
|
||||||
|
</navigator>
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
|
<component name="ExcludedFiles">
|
||||||
|
<file url="file://$PROJECT_DIR$/build" reason="Build output" />
|
||||||
|
<file url="file://$PROJECT_DIR$/.gradle" reason="Gradle cache" />
|
||||||
|
<file url="file://$PROJECT_DIR$/gradle" reason="Gradle wrapper internals" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
"#;
|
||||||
|
fs::write(project_path.join(".idea/workspace.xml"), workspace_xml)?;
|
||||||
|
|
||||||
|
// Run configurations. Each is a ShellScript type that invokes one of
|
||||||
|
// Weevil's scripts. Android Studio shows these in the Run dropdown
|
||||||
|
// at the top of the IDE — no configuration needed by the student.
|
||||||
|
//
|
||||||
|
// We generate both Unix (.sh, ./gradlew) and Windows (.bat, gradlew.bat)
|
||||||
|
// variants. Android Studio automatically hides configs whose script files
|
||||||
|
// don't exist, so only the platform-appropriate ones appear in the dropdown.
|
||||||
|
|
||||||
|
// Build (Unix) — just builds the APK without deploying
|
||||||
|
let build_unix_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Build" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/build.sh" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="/bin/bash" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Build.xml"),
|
||||||
|
build_unix_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Build (Windows) — same, but calls build.bat
|
||||||
|
let build_windows_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Build (Windows)" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/build.bat" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="cmd.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="/c" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Build (Windows).xml"),
|
||||||
|
build_windows_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Deploy (auto) — no flags, deploy.sh auto-detects USB vs WiFi
|
||||||
|
let deploy_auto_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Deploy (auto)" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/deploy.sh" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="/bin/bash" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Deploy (auto).xml"),
|
||||||
|
deploy_auto_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Deploy (auto) (Windows)
|
||||||
|
let deploy_auto_windows_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Deploy (auto) (Windows)" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/deploy.bat" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="cmd.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="/c" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Deploy (auto) (Windows).xml"),
|
||||||
|
deploy_auto_windows_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Deploy (USB) — forces USB connection
|
||||||
|
let deploy_usb_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Deploy (USB)" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/deploy.sh" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="--usb" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="/bin/bash" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Deploy (USB).xml"),
|
||||||
|
deploy_usb_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Deploy (USB) (Windows)
|
||||||
|
let deploy_usb_windows_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Deploy (USB) (Windows)" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/deploy.bat" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="cmd.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="/c" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Deploy (USB) (Windows).xml"),
|
||||||
|
deploy_usb_windows_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Deploy (WiFi) — forces WiFi connection to default 192.168.43.1
|
||||||
|
let deploy_wifi_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Deploy (WiFi)" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/deploy.sh" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="--wifi" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="/bin/bash" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Deploy (WiFi).xml"),
|
||||||
|
deploy_wifi_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Deploy (WiFi) (Windows)
|
||||||
|
let deploy_wifi_windows_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Deploy (WiFi) (Windows)" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/deploy.bat" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="cmd.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="/c" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Deploy (WiFi) (Windows).xml"),
|
||||||
|
deploy_wifi_windows_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Test — runs the unit test suite via Gradle
|
||||||
|
let test_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Test" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/gradlew" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="test" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="/bin/bash" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Test.xml"),
|
||||||
|
test_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Test (Windows)
|
||||||
|
let test_windows_xml = r#"<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration name="Test (Windows)" type="ShConfigurationType">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/gradlew.bat" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="test" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
|
||||||
|
<option name="INTERPRETER_PATH" value="cmd.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="/c" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="true" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
"#;
|
||||||
|
fs::write(
|
||||||
|
project_path.join(".idea/runConfigurations/Test (Windows).xml"),
|
||||||
|
test_windows_xml,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user