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");
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<()> {
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()),
};
let mut ron_contents =
ron::ser::to_string_pretty(&dot_android, ron::ser::PrettyConfig::default())?;
ron_contents.insert_str(0, DOTFILE_COMMENT);
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> {
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")?)
}