1mod utils;
2
3use anyhow::{Context, Result};
4use guidon::{GitOptions, Guidon, TryNew};
5use itertools::Itertools;
6use serde::{Deserialize, Serialize};
7
8use std::{
9 collections::BTreeMap,
10 path::{Path, PathBuf},
11 process::{Command, ExitStatus},
12};
13
14use utils::{find_adb, find_gradle};
15
16pub const VERSION: &str = env!("CARGO_PKG_VERSION");
17pub const DEFAULT_MAIN_ACTIVITY: &str = "MainActivity";
18
19const DEFAULT_TEMPLATE_REPO: &str = "https://github.com/SyedAhkam/android-cli-template";
20const TEMPLATE_REV: &str = "master";
21
22const DOTFILE_COMMENT: &str = "// DO NOT MODIFY; Generated by Android CLI for internal usage.\n";
23
24#[derive(Debug, Serialize, Deserialize)]
25pub 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
32pub 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
47pub 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 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
67pub 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
82pub 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
97pub fn create_dot_android(
98 dest: &Path,
99 project_name: String,
100 package_id: String,
101 main_activity_name: Option<String>,
102) -> Result<()> {
103 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 let mut ron_contents =
113 ron::ser::to_string_pretty(&dot_android, ron::ser::PrettyConfig::default())?;
114
115 ron_contents.insert_str(0, DOTFILE_COMMENT);
117
118 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
125pub 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
133pub 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
145pub fn trigger_build(is_release: bool) -> Result<ExitStatus> {
146 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
155pub 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
166pub fn query_devices() -> Result<ExitStatus> {
167 Ok(invoke_adb_command(&["devices"]).context("failed to invoke adb command")?)
168}
169
170pub fn attach_shell() -> Result<ExitStatus> {
171 Ok(invoke_adb_command(&["shell"]).context("failed to invoke adb command")?)
172}