From 90ed6aa99b896beb39b31908e2dc4fffa1796c35 Mon Sep 17 00:00:00 2001 From: SyedAhkam Date: Tue, 10 Oct 2023 03:59:50 +0530 Subject: [PATCH] feat: add `shell` command --- docs/src/commands/shell.md | 18 +- src/commands/mod.rs | 15 +- src/commands/shell.rs | 16 ++ src/lib.rs | 340 +++++++++++++++++++------------------ src/main.rs | 94 +++++----- 5 files changed, 261 insertions(+), 222 deletions(-) create mode 100644 src/commands/shell.rs diff --git a/docs/src/commands/shell.md b/docs/src/commands/shell.md index ce0a83e..4fe4d8d 100644 --- a/docs/src/commands/shell.md +++ b/docs/src/commands/shell.md @@ -1,3 +1,19 @@ # android shell -TODO \ No newline at end of file +```ignore +USAGE: + android shell + +OPTIONS: + -h, --help Print help information +``` + +## What it does + +It simply asks `adb` to attach a shell to your connected android device / emulator by executing `adb shell`. + +## Example + +```sh +$ android shell +``` \ No newline at end of file diff --git a/src/commands/mod.rs b/src/commands/mod.rs index e828985..72033ce 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,7 +1,8 @@ -pub mod build; -pub mod create; -pub mod devices; -pub mod install; -pub mod launch; -pub mod link; -pub mod run; +pub mod build; +pub mod create; +pub mod devices; +pub mod install; +pub mod launch; +pub mod link; +pub mod run; +pub mod shell; \ No newline at end of file diff --git a/src/commands/shell.rs b/src/commands/shell.rs new file mode 100644 index 0000000..b5823ad --- /dev/null +++ b/src/commands/shell.rs @@ -0,0 +1,16 @@ +use anyhow::{bail, Result}; +use clap::Parser; + +#[derive(Parser, Debug)] +pub struct Shell {} + +pub fn handle(_args: Shell) -> Result<()> { + // Try to run adb shell + let query_status = android_cli::attach_shell()?; + + if !query_status.success() { + bail!("failed to attach shell"); + } + + Ok(()) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e36f1f3..2fd740e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,168 +1,172 @@ -mod utils; - -use anyhow::{Context, Result}; -use guidon::{GitOptions, Guidon, TryNew}; -use itertools::Itertools; -use serde::{Deserialize, Serialize}; - -use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, - process::{Command, ExitStatus}, -}; - -use utils::{find_adb, find_gradle}; - -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const DEFAULT_MAIN_ACTIVITY: &str = "MainActivity"; - -const DEFAULT_TEMPLATE_REPO: &str = "https://github.com/SyedAhkam/android-cli-template"; -const TEMPLATE_REV: &str = "master"; - -const DOTFILE_COMMENT: &str = "// DO NOT MODIFY; Generated by Android CLI for internal usage.\n"; - -#[derive(Debug, Serialize, Deserialize)] -pub struct DotAndroid { - pub project_name: String, - pub package_id: String, - pub gen_at_version: String, - pub main_activity_name: String, -} - -pub fn copy_template(dest: &Path, vars: BTreeMap) -> Result<()> { - let git_options = GitOptions::builder() - .repo(DEFAULT_TEMPLATE_REPO) - .rev(TEMPLATE_REV) - .build() - .unwrap(); - - let mut guidon = Guidon::try_new(git_options)?; - - guidon.variables(vars); - guidon.apply_template(dest)?; - - Ok(()) -} - -pub fn create_local_properties_file(root: &Path, sdk_path: &str) -> Result<()> { - let sdk_path = Path::new(sdk_path); - let prop_file_path = PathBuf::new().join(root).join("local.properties"); - - // It is necessary to escape paths on windows - let content = if cfg!(windows) { - format!("sdk.dir={}", sdk_path.display()).replace("\\", "\\\\") - } else { - format!("sdk.dir={}", sdk_path.display()) - }; - - if sdk_path.exists() { - std::fs::write(prop_file_path, content).context("Unable to write local.properties file")?; - } else { - eprintln!("warning: did not create local.properties file because of invalid sdk path") - } - - Ok(()) -} - -pub fn invoke_gradle_command(cmd: &str) -> Result { - let gradle_path = find_gradle().context("ERROR: Gradle not found on system")?; - - let mut run = Command::new(gradle_path); - run.arg(cmd); - - println!( - "Invoking Gradle: {} {}", - &run.get_program().to_string_lossy(), - &run.get_args().map(|arg| arg.to_string_lossy()).join(" ") - ); - - Ok(run.status()?) -} - -pub fn invoke_adb_command(args: &[&str]) -> Result { - let adb_path = find_adb().context("ERROR: ADB not found on system")?; - - let mut run = Command::new(adb_path); - run.args(args); - - println!( - "Invoking ADB: {} {}", - &run.get_program().to_string_lossy(), - &run.get_args().map(|arg| arg.to_string_lossy()).join(" ") - ); - - Ok(run.status()?) -} - -pub fn create_dot_android( - dest: &Path, - project_name: String, - package_id: String, - main_activity_name: Option, -) -> Result<()> { - // Construct the structure - let dot_android = DotAndroid { - package_id, - project_name, - gen_at_version: VERSION.to_owned(), - main_activity_name: main_activity_name.unwrap_or(DEFAULT_MAIN_ACTIVITY.to_owned()), - }; - - // Serialize into Ron - let mut ron_contents = - ron::ser::to_string_pretty(&dot_android, ron::ser::PrettyConfig::default())?; - - // Add a comment at top - ron_contents.insert_str(0, DOTFILE_COMMENT); - - // Write to file - let path = PathBuf::from(dest).join(".android"); - std::fs::write(path, ron_contents).context("failed to write .android file")?; - - Ok(()) -} - -pub fn get_dot_android() -> Option { - if let Ok(contents) = std::fs::read_to_string(".android") { - return ron::from_str::(&contents).ok(); - }; - - None -} - -pub fn install_apk(is_release: bool) -> Result { - let output_dir = PathBuf::from("app/build/outputs/apk"); - - let apk_path = match is_release { - true => output_dir.join("release/app-release.apk"), - false => output_dir.join("debug/app-debug.apk"), - }; - - Ok(invoke_adb_command(&["install", apk_path.to_str().unwrap()]) - .context("failed to run adb command")?) -} - -pub fn trigger_build(is_release: bool) -> Result { - // Decide gradle subcommand to use - let cmd = match is_release { - true => "assembleRelease", - false => "assembleDebug", - }; - - Ok(invoke_gradle_command(cmd).context("failed to invoke gradle command")?) -} - -pub fn launch_activity(package_id: String, activity_name: String) -> Result { - Ok(invoke_adb_command(&[ - "shell", - "am", - "start", - "-n", - &format!("{}/.{}", package_id, activity_name), - ]) - .context("failed to invoke adb command")?) -} - -pub fn query_devices() -> Result { - Ok(invoke_adb_command(&["devices"]).context("failed to invoke adb command")?) -} +mod utils; + +use anyhow::{Context, Result}; +use guidon::{GitOptions, Guidon, TryNew}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; + +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + process::{Command, ExitStatus}, +}; + +use utils::{find_adb, find_gradle}; + +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const DEFAULT_MAIN_ACTIVITY: &str = "MainActivity"; + +const DEFAULT_TEMPLATE_REPO: &str = "https://github.com/SyedAhkam/android-cli-template"; +const TEMPLATE_REV: &str = "master"; + +const DOTFILE_COMMENT: &str = "// DO NOT MODIFY; Generated by Android CLI for internal usage.\n"; + +#[derive(Debug, Serialize, Deserialize)] +pub struct DotAndroid { + pub project_name: String, + pub package_id: String, + pub gen_at_version: String, + pub main_activity_name: String, +} + +pub fn copy_template(dest: &Path, vars: BTreeMap) -> Result<()> { + let git_options = GitOptions::builder() + .repo(DEFAULT_TEMPLATE_REPO) + .rev(TEMPLATE_REV) + .build() + .unwrap(); + + let mut guidon = Guidon::try_new(git_options)?; + + guidon.variables(vars); + guidon.apply_template(dest)?; + + Ok(()) +} + +pub fn create_local_properties_file(root: &Path, sdk_path: &str) -> Result<()> { + let sdk_path = Path::new(sdk_path); + let prop_file_path = PathBuf::new().join(root).join("local.properties"); + + // It is necessary to escape paths on windows + let content = if cfg!(windows) { + format!("sdk.dir={}", sdk_path.display()).replace("\\", "\\\\") + } else { + format!("sdk.dir={}", sdk_path.display()) + }; + + if sdk_path.exists() { + std::fs::write(prop_file_path, content).context("Unable to write local.properties file")?; + } else { + eprintln!("warning: did not create local.properties file because of invalid sdk path") + } + + Ok(()) +} + +pub fn invoke_gradle_command(cmd: &str) -> Result { + let gradle_path = find_gradle().context("ERROR: Gradle not found on system")?; + + let mut run = Command::new(gradle_path); + run.arg(cmd); + + println!( + "Invoking Gradle: {} {}", + &run.get_program().to_string_lossy(), + &run.get_args().map(|arg| arg.to_string_lossy()).join(" ") + ); + + Ok(run.status()?) +} + +pub fn invoke_adb_command(args: &[&str]) -> Result { + let adb_path = find_adb().context("ERROR: ADB not found on system")?; + + let mut run = Command::new(adb_path); + run.args(args); + + println!( + "Invoking ADB: {} {}", + &run.get_program().to_string_lossy(), + &run.get_args().map(|arg| arg.to_string_lossy()).join(" ") + ); + + Ok(run.status()?) +} + +pub fn create_dot_android( + dest: &Path, + project_name: String, + package_id: String, + main_activity_name: Option, +) -> Result<()> { + // Construct the structure + let dot_android = DotAndroid { + package_id, + project_name, + gen_at_version: VERSION.to_owned(), + main_activity_name: main_activity_name.unwrap_or(DEFAULT_MAIN_ACTIVITY.to_owned()), + }; + + // Serialize into Ron + let mut ron_contents = + ron::ser::to_string_pretty(&dot_android, ron::ser::PrettyConfig::default())?; + + // Add a comment at top + ron_contents.insert_str(0, DOTFILE_COMMENT); + + // Write to file + let path = PathBuf::from(dest).join(".android"); + std::fs::write(path, ron_contents).context("failed to write .android file")?; + + Ok(()) +} + +pub fn get_dot_android() -> Option { + if let Ok(contents) = std::fs::read_to_string(".android") { + return ron::from_str::(&contents).ok(); + }; + + None +} + +pub fn install_apk(is_release: bool) -> Result { + let output_dir = PathBuf::from("app/build/outputs/apk"); + + let apk_path = match is_release { + true => output_dir.join("release/app-release.apk"), + false => output_dir.join("debug/app-debug.apk"), + }; + + Ok(invoke_adb_command(&["install", apk_path.to_str().unwrap()]) + .context("failed to run adb command")?) +} + +pub fn trigger_build(is_release: bool) -> Result { + // Decide gradle subcommand to use + let cmd = match is_release { + true => "assembleRelease", + false => "assembleDebug", + }; + + Ok(invoke_gradle_command(cmd).context("failed to invoke gradle command")?) +} + +pub fn launch_activity(package_id: String, activity_name: String) -> Result { + Ok(invoke_adb_command(&[ + "shell", + "am", + "start", + "-n", + &format!("{}/.{}", package_id, activity_name), + ]) + .context("failed to invoke adb command")?) +} + +pub fn query_devices() -> Result { + Ok(invoke_adb_command(&["devices"]).context("failed to invoke adb command")?) +} + +pub fn attach_shell() -> Result { + Ok(invoke_adb_command(&["shell"]).context("failed to invoke adb command")?) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f02aeec..2106c4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,46 +1,48 @@ -use clap::{Parser, Subcommand}; - -mod commands; -mod utils; - -#[derive(Parser, Debug)] -#[clap(name = "Android CLI")] -#[clap(author, version, about)] -#[clap(author = "Syed Ahkam ")] -#[clap(arg_required_else_help = true)] -struct Cli { - #[clap(subcommand)] - command: SubCommand, -} - -#[derive(Subcommand, Debug)] -enum SubCommand { - Create(commands::create::Create), - Build(commands::build::Build), - Install(commands::install::Install), - Run(commands::run::Run), - Launch(commands::launch::Launch), - Devices(commands::devices::Devices), - Link(commands::link::Link), -} - -fn main() { - // Initialize the logger with the log level info - env_logger::init(); - - let args = Cli::parse(); - - let result = match args.command { - SubCommand::Create(args) => commands::create::handle(args), - SubCommand::Build(args) => commands::build::handle(args), - SubCommand::Install(args) => commands::install::handle(args), - SubCommand::Run(args) => commands::run::handle(args), - SubCommand::Launch(args) => commands::launch::handle(args), - SubCommand::Devices(args) => commands::devices::handle(args), - SubCommand::Link(args) => commands::link::handle(args), - }; - - if result.is_err() { - eprintln!("ERROR: {:?}", result.unwrap_err()); - } -} +use clap::{Parser, Subcommand}; + +mod commands; +mod utils; + +#[derive(Parser, Debug)] +#[clap(name = "Android CLI")] +#[clap(author, version, about)] +#[clap(author = "Syed Ahkam ")] +#[clap(arg_required_else_help = true)] +struct Cli { + #[clap(subcommand)] + command: SubCommand, +} + +#[derive(Subcommand, Debug)] +enum SubCommand { + Create(commands::create::Create), + Build(commands::build::Build), + Install(commands::install::Install), + Run(commands::run::Run), + Launch(commands::launch::Launch), + Devices(commands::devices::Devices), + Link(commands::link::Link), + Shell(commands::shell::Shell) +} + +fn main() { + // Initialize the logger with the log level info + env_logger::init(); + + let args = Cli::parse(); + + let result = match args.command { + SubCommand::Create(args) => commands::create::handle(args), + SubCommand::Build(args) => commands::build::handle(args), + SubCommand::Install(args) => commands::install::handle(args), + SubCommand::Run(args) => commands::run::handle(args), + SubCommand::Launch(args) => commands::launch::handle(args), + SubCommand::Devices(args) => commands::devices::handle(args), + SubCommand::Link(args) => commands::link::handle(args), + SubCommand::Shell(args) => commands::shell::handle(args) + }; + + if result.is_err() { + eprintln!("ERROR: {:?}", result.unwrap_err()); + } +}