Skip to content

Commit

Permalink
feat: add shell command
Browse files Browse the repository at this point in the history
  • Loading branch information
SyedAhkam committed Oct 9, 2023
1 parent 894b9a6 commit 90ed6aa
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 222 deletions.
18 changes: 17 additions & 1 deletion docs/src/commands/shell.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# android shell

TODO
```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
```
15 changes: 8 additions & 7 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
16 changes: 16 additions & 0 deletions src/commands/shell.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
340 changes: 172 additions & 168 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<String, String>) -> 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<ExitStatus> {
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<ExitStatus> {
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<String>,
) -> 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<DotAndroid> {
if let Ok(contents) = std::fs::read_to_string(".android") {
return ron::from_str::<DotAndroid>(&contents).ok();
};

None
}

pub fn install_apk(is_release: bool) -> Result<ExitStatus> {
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<ExitStatus> {
// 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<ExitStatus> {
Ok(invoke_adb_command(&[
"shell",
"am",
"start",
"-n",
&format!("{}/.{}", package_id, activity_name),
])
.context("failed to invoke adb command")?)
}

pub fn query_devices() -> Result<ExitStatus> {
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<String, String>) -> 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<ExitStatus> {
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<ExitStatus> {
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<String>,
) -> 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<DotAndroid> {
if let Ok(contents) = std::fs::read_to_string(".android") {
return ron::from_str::<DotAndroid>(&contents).ok();
};

None
}

pub fn install_apk(is_release: bool) -> Result<ExitStatus> {
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<ExitStatus> {
// 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<ExitStatus> {
Ok(invoke_adb_command(&[
"shell",
"am",
"start",
"-n",
&format!("{}/.{}", package_id, activity_name),
])
.context("failed to invoke adb command")?)
}

pub fn query_devices() -> Result<ExitStatus> {
Ok(invoke_adb_command(&["devices"]).context("failed to invoke adb command")?)
}

pub fn attach_shell() -> Result<ExitStatus> {
Ok(invoke_adb_command(&["shell"]).context("failed to invoke adb command")?)
}
Loading

0 comments on commit 90ed6aa

Please sign in to comment.