1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
use std::{env, path::{Path, PathBuf}};
use self::find_java::find_java_home;
mod find_android_sdk;
mod find_java;
pub const ANDROID_HOME: &str = "ANDROID_HOME";
pub const ANDROID_SDK_ROOT: &str = "ANDROID_SDK_ROOT";
pub const ANDROID_BUILD_TOOLS_VERSION: &str = "ANDROID_BUILD_TOOLS_VERSION";
pub const ANDROID_PLATFORM: &str = "ANDROID_PLATFORM";
pub const ANDROID_SDK_VERSION: &str = "ANDROID_SDK_VERSION";
pub const ANDROID_API_LEVEL: &str = "ANDROID_API_LEVEL";
pub const ANDROID_SDK_EXTENSION: &str = "ANDROID_SDK_EXTENSION";
pub const ANDROID_D8_JAR: &str = "ANDROID_D8_JAR";
pub const ANDROID_JAR: &str = "ANDROID_JAR";
pub const JAVA_HOME: &str = "JAVA_HOME";
/// An extension trait for checking if a path exists.
pub trait PathExt {
fn path_if_exists(self) -> Option<Self> where Self: Sized;
}
impl<P: AsRef<Path>> PathExt for P {
fn path_if_exists(self) -> Option<P> {
if self.as_ref().as_os_str().is_empty() {
return None;
}
self.as_ref().exists().then_some(self)
}
}
/// Returns the path to the Android SDK directory.
///
/// The path is determined by an ordered set of attempts:
/// * The `ANDROID_HOME` environment variable, if it is set and if the directory exists.
/// * The `ANDROID_SDK_HOME` environment variable, if it is set and if the directory exists.
/// * The default installation location for the Android SDK, if it exists.
/// * On Windows, this is `%LOCALAPPDATA%\Android\Sdk`.
/// * On macOS, this is `~/Library/Android/sdk`.
/// * On Linux, this is `~/Android/Sdk`.
#[doc(alias("ANDROID_HOME", "ANDROID_SDK_ROOT", "home", "sdk", "root"))]
pub fn android_sdk() -> Option<PathBuf> {
env::var(ANDROID_HOME).ok()
.and_then(PathExt::path_if_exists)
.or_else(|| env::var(ANDROID_SDK_ROOT).ok()
.and_then(PathExt::path_if_exists)
)
.map(PathBuf::from)
.or_else(|| find_android_sdk::find_android_sdk().and_then(PathExt::path_if_exists))
}
/// Returns the path to the `android.jar` file for the given API level.
///
/// If the `ANDROID_JAR` environment variable is set and points to a file that exists,
/// that path is returned.
///
/// Otherwise, `platform_string` is used to find the `android.jar` file from the
/// `platforms` subdirectory in the Android SDK root directory.
///
/// If `platform_string` is `None`, the value of the following environment variables
/// are used to calculate the platform string:
/// * `ANDROID_PLATFORM`
/// * `ANDROID_API_LEVEL`
/// * `ANDROID_SDK_VERSION`
/// * `ANDROID_SDK_EXTENSION`
pub fn android_jar(platform_string: Option<&str>) -> Option<PathBuf> {
env::var(ANDROID_JAR).ok()
.and_then(PathExt::path_if_exists)
.map(PathBuf::from)
.or_else(|| android_sdk()
.and_then(|sdk| sdk
.join("platforms")
.join(platform_string.map(ToString::to_string)
.unwrap_or_else(|| env_android_platform_api_level()
.expect("either ANDROID_JAR or [ANDROID_PLATFORM, ANDROID_API_LEVEL, ANDROID_SDK_VERSION] must be set")
)
)
.join("android.jar")
.path_if_exists()
)
)
}
/// Returns the path to the `d8.jar` file for the given build tools version.
///
/// If the `ANDROID_D8_JAR` environment variable is set and points to a file that exists,
/// that path is returned.
/// If `build_tools_version`is `None`, the value of the `ANDROID_BUILD_TOOLS_VERSION` environment variable is used
/// to find the `d8.jar` file from the Android SDK root directory.
pub fn android_d8_jar(build_tools_version: Option<&str>) -> Option<PathBuf> {
env::var(ANDROID_D8_JAR).ok()
.and_then(PathExt::path_if_exists)
.map(PathBuf::from)
.or_else(|| android_sdk()
.and_then(|sdk| sdk
.join("build-tools")
.join(build_tools_version.map(ToString::to_string)
.unwrap_or_else(|| env::var(ANDROID_BUILD_TOOLS_VERSION)
.expect("either ANDROID_D8_JAR or ANDROID_BUILD_TOOLS_VERSION must be set")
)
)
.join("lib")
.join("d8.jar")
.path_if_exists()
)
)
}
/// Returns the platform version string (aka API level, SDK version) being targeted for compilation.
///
/// This deals with environment variables `ANDROID_PLATFORM`, `ANDROID_API_LEVEL`, and `ANDROID_SDK_VERSION`,
/// as well as the optional `ANDROID_SDK_EXTENSION`.
fn env_android_platform_api_level() -> Option<String> {
let mut base = env::var(ANDROID_PLATFORM).ok()
.or_else(|| env::var(ANDROID_API_LEVEL).ok())
.or_else(|| env::var(ANDROID_SDK_VERSION).ok())?;
if base.is_empty() {
return None;
}
if !base.starts_with("android-") {
base = format!("android-{}", base);
}
if base.contains("-ext") {
return Some(base);
}
if let Some(raw_ext) = env::var(ANDROID_SDK_EXTENSION).ok() {
let ext_num = raw_ext
.trim_start_matches("-")
.trim_start_matches("ext");
if !ext_num.is_empty() {
base = format!("{}-ext{}", base, ext_num);
}
}
Some(base)
}
/// Returns the path to the `java` executable by looking for `$JAVA_HOME/bin/java`.
pub fn java() -> Option<PathBuf> {
java_home().and_then(|jh| jh
.join("bin")
.join("java")
.path_if_exists()
)
}
/// Returns the path to the `javac` compiler by looking for `$JAVA_HOME/bin/javac`.
pub fn javac() -> Option<PathBuf> {
java_home().and_then(|jh| jh
.join("bin")
.join("javac")
.path_if_exists()
)
}
/// Returns the JAVA_HOME path by attempting to discover it.
///
/// First, if the `$JAVA_HOME` environment variable is set and points to a directory that exists,
/// that path is returned.
/// Otherwise, a series of common installation locations is used,
/// based on the current platform (macOS, Linux, Windows).
pub fn java_home() -> Option<PathBuf> {
env::var(JAVA_HOME).ok()
.and_then(PathExt::path_if_exists)
.map(PathBuf::from)
.or_else(find_java_home)
}