android_build/env_paths/
mod.rs

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