Skip to content

Commit 90ed6aa

Browse files
committed
feat: add shell command
1 parent 894b9a6 commit 90ed6aa

5 files changed

Lines changed: 261 additions & 222 deletions

File tree

docs/src/commands/shell.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
11
# android shell
22

3-
TODO
3+
```ignore
4+
USAGE:
5+
android shell
6+
7+
OPTIONS:
8+
-h, --help Print help information
9+
```
10+
11+
## What it does
12+
13+
It simply asks `adb` to attach a shell to your connected android device / emulator by executing `adb shell`.
14+
15+
## Example
16+
17+
```sh
18+
$ android shell
19+
```

src/commands/mod.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
pub mod build;
2-
pub mod create;
3-
pub mod devices;
4-
pub mod install;
5-
pub mod launch;
6-
pub mod link;
7-
pub mod run;
1+
pub mod build;
2+
pub mod create;
3+
pub mod devices;
4+
pub mod install;
5+
pub mod launch;
6+
pub mod link;
7+
pub mod run;
8+
pub mod shell;

src/commands/shell.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use anyhow::{bail, Result};
2+
use clap::Parser;
3+
4+
#[derive(Parser, Debug)]
5+
pub struct Shell {}
6+
7+
pub fn handle(_args: Shell) -> Result<()> {
8+
// Try to run adb shell
9+
let query_status = android_cli::attach_shell()?;
10+
11+
if !query_status.success() {
12+
bail!("failed to attach shell");
13+
}
14+
15+
Ok(())
16+
}

src/lib.rs

Lines changed: 172 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1,168 +1,172 @@
1-
mod utils;
2-
3-
use anyhow::{Context, Result};
4-
use guidon::{GitOptions, Guidon, TryNew};
5-
use itertools::Itertools;
6-
use serde::{Deserialize, Serialize};
7-
8-
use std::{
9-
collections::BTreeMap,
10-
path::{Path, PathBuf},
11-
process::{Command, ExitStatus},
12-
};
13-
14-
use utils::{find_adb, find_gradle};
15-
16-
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
17-
pub const DEFAULT_MAIN_ACTIVITY: &str = "MainActivity";
18-
19-
const DEFAULT_TEMPLATE_REPO: &str = "https://github.com/SyedAhkam/android-cli-template";
20-
const TEMPLATE_REV: &str = "master";
21-
22-
const DOTFILE_COMMENT: &str = "// DO NOT MODIFY; Generated by Android CLI for internal usage.\n";
23-
24-
#[derive(Debug, Serialize, Deserialize)]
25-
pub struct DotAndroid {
26-
pub project_name: String,
27-
pub package_id: String,
28-
pub gen_at_version: String,
29-
pub main_activity_name: String,
30-
}
31-
32-
pub fn copy_template(dest: &Path, vars: BTreeMap<String, String>) -> Result<()> {
33-
let git_options = GitOptions::builder()
34-
.repo(DEFAULT_TEMPLATE_REPO)
35-
.rev(TEMPLATE_REV)
36-
.build()
37-
.unwrap();
38-
39-
let mut guidon = Guidon::try_new(git_options)?;
40-
41-
guidon.variables(vars);
42-
guidon.apply_template(dest)?;
43-
44-
Ok(())
45-
}
46-
47-
pub fn create_local_properties_file(root: &Path, sdk_path: &str) -> Result<()> {
48-
let sdk_path = Path::new(sdk_path);
49-
let prop_file_path = PathBuf::new().join(root).join("local.properties");
50-
51-
// It is necessary to escape paths on windows
52-
let content = if cfg!(windows) {
53-
format!("sdk.dir={}", sdk_path.display()).replace("\\", "\\\\")
54-
} else {
55-
format!("sdk.dir={}", sdk_path.display())
56-
};
57-
58-
if sdk_path.exists() {
59-
std::fs::write(prop_file_path, content).context("Unable to write local.properties file")?;
60-
} else {
61-
eprintln!("warning: did not create local.properties file because of invalid sdk path")
62-
}
63-
64-
Ok(())
65-
}
66-
67-
pub fn invoke_gradle_command(cmd: &str) -> Result<ExitStatus> {
68-
let gradle_path = find_gradle().context("ERROR: Gradle not found on system")?;
69-
70-
let mut run = Command::new(gradle_path);
71-
run.arg(cmd);
72-
73-
println!(
74-
"Invoking Gradle: {} {}",
75-
&run.get_program().to_string_lossy(),
76-
&run.get_args().map(|arg| arg.to_string_lossy()).join(" ")
77-
);
78-
79-
Ok(run.status()?)
80-
}
81-
82-
pub fn invoke_adb_command(args: &[&str]) -> Result<ExitStatus> {
83-
let adb_path = find_adb().context("ERROR: ADB not found on system")?;
84-
85-
let mut run = Command::new(adb_path);
86-
run.args(args);
87-
88-
println!(
89-
"Invoking ADB: {} {}",
90-
&run.get_program().to_string_lossy(),
91-
&run.get_args().map(|arg| arg.to_string_lossy()).join(" ")
92-
);
93-
94-
Ok(run.status()?)
95-
}
96-
97-
pub fn create_dot_android(
98-
dest: &Path,
99-
project_name: String,
100-
package_id: String,
101-
main_activity_name: Option<String>,
102-
) -> Result<()> {
103-
// Construct the structure
104-
let dot_android = DotAndroid {
105-
package_id,
106-
project_name,
107-
gen_at_version: VERSION.to_owned(),
108-
main_activity_name: main_activity_name.unwrap_or(DEFAULT_MAIN_ACTIVITY.to_owned()),
109-
};
110-
111-
// Serialize into Ron
112-
let mut ron_contents =
113-
ron::ser::to_string_pretty(&dot_android, ron::ser::PrettyConfig::default())?;
114-
115-
// Add a comment at top
116-
ron_contents.insert_str(0, DOTFILE_COMMENT);
117-
118-
// Write to file
119-
let path = PathBuf::from(dest).join(".android");
120-
std::fs::write(path, ron_contents).context("failed to write .android file")?;
121-
122-
Ok(())
123-
}
124-
125-
pub fn get_dot_android() -> Option<DotAndroid> {
126-
if let Ok(contents) = std::fs::read_to_string(".android") {
127-
return ron::from_str::<DotAndroid>(&contents).ok();
128-
};
129-
130-
None
131-
}
132-
133-
pub fn install_apk(is_release: bool) -> Result<ExitStatus> {
134-
let output_dir = PathBuf::from("app/build/outputs/apk");
135-
136-
let apk_path = match is_release {
137-
true => output_dir.join("release/app-release.apk"),
138-
false => output_dir.join("debug/app-debug.apk"),
139-
};
140-
141-
Ok(invoke_adb_command(&["install", apk_path.to_str().unwrap()])
142-
.context("failed to run adb command")?)
143-
}
144-
145-
pub fn trigger_build(is_release: bool) -> Result<ExitStatus> {
146-
// Decide gradle subcommand to use
147-
let cmd = match is_release {
148-
true => "assembleRelease",
149-
false => "assembleDebug",
150-
};
151-
152-
Ok(invoke_gradle_command(cmd).context("failed to invoke gradle command")?)
153-
}
154-
155-
pub fn launch_activity(package_id: String, activity_name: String) -> Result<ExitStatus> {
156-
Ok(invoke_adb_command(&[
157-
"shell",
158-
"am",
159-
"start",
160-
"-n",
161-
&format!("{}/.{}", package_id, activity_name),
162-
])
163-
.context("failed to invoke adb command")?)
164-
}
165-
166-
pub fn query_devices() -> Result<ExitStatus> {
167-
Ok(invoke_adb_command(&["devices"]).context("failed to invoke adb command")?)
168-
}
1+
mod utils;
2+
3+
use anyhow::{Context, Result};
4+
use guidon::{GitOptions, Guidon, TryNew};
5+
use itertools::Itertools;
6+
use serde::{Deserialize, Serialize};
7+
8+
use std::{
9+
collections::BTreeMap,
10+
path::{Path, PathBuf},
11+
process::{Command, ExitStatus},
12+
};
13+
14+
use utils::{find_adb, find_gradle};
15+
16+
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
17+
pub const DEFAULT_MAIN_ACTIVITY: &str = "MainActivity";
18+
19+
const DEFAULT_TEMPLATE_REPO: &str = "https://github.com/SyedAhkam/android-cli-template";
20+
const TEMPLATE_REV: &str = "master";
21+
22+
const DOTFILE_COMMENT: &str = "// DO NOT MODIFY; Generated by Android CLI for internal usage.\n";
23+
24+
#[derive(Debug, Serialize, Deserialize)]
25+
pub struct DotAndroid {
26+
pub project_name: String,
27+
pub package_id: String,
28+
pub gen_at_version: String,
29+
pub main_activity_name: String,
30+
}
31+
32+
pub fn copy_template(dest: &Path, vars: BTreeMap<String, String>) -> Result<()> {
33+
let git_options = GitOptions::builder()
34+
.repo(DEFAULT_TEMPLATE_REPO)
35+
.rev(TEMPLATE_REV)
36+
.build()
37+
.unwrap();
38+
39+
let mut guidon = Guidon::try_new(git_options)?;
40+
41+
guidon.variables(vars);
42+
guidon.apply_template(dest)?;
43+
44+
Ok(())
45+
}
46+
47+
pub fn create_local_properties_file(root: &Path, sdk_path: &str) -> Result<()> {
48+
let sdk_path = Path::new(sdk_path);
49+
let prop_file_path = PathBuf::new().join(root).join("local.properties");
50+
51+
// It is necessary to escape paths on windows
52+
let content = if cfg!(windows) {
53+
format!("sdk.dir={}", sdk_path.display()).replace("\\", "\\\\")
54+
} else {
55+
format!("sdk.dir={}", sdk_path.display())
56+
};
57+
58+
if sdk_path.exists() {
59+
std::fs::write(prop_file_path, content).context("Unable to write local.properties file")?;
60+
} else {
61+
eprintln!("warning: did not create local.properties file because of invalid sdk path")
62+
}
63+
64+
Ok(())
65+
}
66+
67+
pub fn invoke_gradle_command(cmd: &str) -> Result<ExitStatus> {
68+
let gradle_path = find_gradle().context("ERROR: Gradle not found on system")?;
69+
70+
let mut run = Command::new(gradle_path);
71+
run.arg(cmd);
72+
73+
println!(
74+
"Invoking Gradle: {} {}",
75+
&run.get_program().to_string_lossy(),
76+
&run.get_args().map(|arg| arg.to_string_lossy()).join(" ")
77+
);
78+
79+
Ok(run.status()?)
80+
}
81+
82+
pub fn invoke_adb_command(args: &[&str]) -> Result<ExitStatus> {
83+
let adb_path = find_adb().context("ERROR: ADB not found on system")?;
84+
85+
let mut run = Command::new(adb_path);
86+
run.args(args);
87+
88+
println!(
89+
"Invoking ADB: {} {}",
90+
&run.get_program().to_string_lossy(),
91+
&run.get_args().map(|arg| arg.to_string_lossy()).join(" ")
92+
);
93+
94+
Ok(run.status()?)
95+
}
96+
97+
pub fn create_dot_android(
98+
dest: &Path,
99+
project_name: String,
100+
package_id: String,
101+
main_activity_name: Option<String>,
102+
) -> Result<()> {
103+
// Construct the structure
104+
let dot_android = DotAndroid {
105+
package_id,
106+
project_name,
107+
gen_at_version: VERSION.to_owned(),
108+
main_activity_name: main_activity_name.unwrap_or(DEFAULT_MAIN_ACTIVITY.to_owned()),
109+
};
110+
111+
// Serialize into Ron
112+
let mut ron_contents =
113+
ron::ser::to_string_pretty(&dot_android, ron::ser::PrettyConfig::default())?;
114+
115+
// Add a comment at top
116+
ron_contents.insert_str(0, DOTFILE_COMMENT);
117+
118+
// Write to file
119+
let path = PathBuf::from(dest).join(".android");
120+
std::fs::write(path, ron_contents).context("failed to write .android file")?;
121+
122+
Ok(())
123+
}
124+
125+
pub fn get_dot_android() -> Option<DotAndroid> {
126+
if let Ok(contents) = std::fs::read_to_string(".android") {
127+
return ron::from_str::<DotAndroid>(&contents).ok();
128+
};
129+
130+
None
131+
}
132+
133+
pub fn install_apk(is_release: bool) -> Result<ExitStatus> {
134+
let output_dir = PathBuf::from("app/build/outputs/apk");
135+
136+
let apk_path = match is_release {
137+
true => output_dir.join("release/app-release.apk"),
138+
false => output_dir.join("debug/app-debug.apk"),
139+
};
140+
141+
Ok(invoke_adb_command(&["install", apk_path.to_str().unwrap()])
142+
.context("failed to run adb command")?)
143+
}
144+
145+
pub fn trigger_build(is_release: bool) -> Result<ExitStatus> {
146+
// Decide gradle subcommand to use
147+
let cmd = match is_release {
148+
true => "assembleRelease",
149+
false => "assembleDebug",
150+
};
151+
152+
Ok(invoke_gradle_command(cmd).context("failed to invoke gradle command")?)
153+
}
154+
155+
pub fn launch_activity(package_id: String, activity_name: String) -> Result<ExitStatus> {
156+
Ok(invoke_adb_command(&[
157+
"shell",
158+
"am",
159+
"start",
160+
"-n",
161+
&format!("{}/.{}", package_id, activity_name),
162+
])
163+
.context("failed to invoke adb command")?)
164+
}
165+
166+
pub fn query_devices() -> Result<ExitStatus> {
167+
Ok(invoke_adb_command(&["devices"]).context("failed to invoke adb command")?)
168+
}
169+
170+
pub fn attach_shell() -> Result<ExitStatus> {
171+
Ok(invoke_adb_command(&["shell"]).context("failed to invoke adb command")?)
172+
}

0 commit comments

Comments
 (0)