android_build/env_paths/
mod.rs1use 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";
18pub const JAVA_SOURCE_VERSION: &str = "JAVA_SOURCE_VERSION";
19pub const JAVA_TARGET_VERSION: &str = "JAVA_TARGET_VERSION";
20
21pub trait PathExt {
23 fn path_if_exists(self) -> Option<Self> where Self: Sized;
24}
25impl<P: AsRef<Path>> PathExt for P {
26 fn path_if_exists(self) -> Option<P> {
27 if self.as_ref().as_os_str().is_empty() {
28 return None;
29 }
30 match self.as_ref().try_exists() {
31 Ok(true) => Some(self),
32 _ => None,
33 }
34 }
35}
36
37
38#[doc(alias("ANDROID_HOME", "ANDROID_SDK_ROOT", "home", "sdk", "root"))]
48pub fn android_sdk() -> Option<PathBuf> {
49 env_var(ANDROID_HOME).ok()
50 .and_then(PathExt::path_if_exists)
51 .or_else(|| env_var(ANDROID_SDK_ROOT).ok()
52 .and_then(PathExt::path_if_exists)
53 )
54 .map(PathBuf::from)
55 .or_else(|| find_android_sdk::find_android_sdk().and_then(PathExt::path_if_exists))
56}
57
58pub fn android_jar(platform_string: Option<&str>) -> Option<PathBuf> {
71 env_var(ANDROID_JAR).ok()
72 .and_then(PathExt::path_if_exists)
73 .map(PathBuf::from)
74 .or_else(|| android_sdk()
75 .and_then(|sdk| {
76 let platforms = sdk.join("platforms");
77 platform_string.map(ToString::to_string)
78 .or_else(env_android_platform_api_level)
79 .or_else(|| {
80 let latest = find_latest_version(&platforms, "android.jar");
81 #[cfg(feature = "cargo")]
82 if let Some(ver) = latest.as_ref() {
83 println!("cargo::warning=ANDROID_PLATFORM environment variable \
84 is not set, using '{ver}'.");
85 }
86 latest
87 })
88 .map(|version| platforms.join(version))
89 })
90 .and_then(|path| path.join("android.jar").path_if_exists())
91 )
92}
93
94pub fn android_d8_jar(build_tools_version: Option<&str>) -> Option<PathBuf> {
105 env_var(ANDROID_D8_JAR).ok()
106 .and_then(PathExt::path_if_exists)
107 .map(PathBuf::from)
108 .or_else(|| android_sdk()
109 .and_then(|sdk| {
110 let build_tools = sdk.join("build-tools");
111 build_tools_version.map(ToString::to_string)
112 .or_else(|| env_var(ANDROID_BUILD_TOOLS_VERSION).ok())
113 .or_else(|| {
114 let latest = find_latest_version(&build_tools, Path::new("lib").join("d8.jar"));
115 #[cfg(feature = "cargo")]
116 if let Some(ver) = latest.as_ref() {
117 println!("cargo::warning=ANDROID_BUILD_TOOLS_VERSION environment variable \
118 is not set, using '{ver}'.");
119 }
120 latest
121 })
122 .map(|version| build_tools.join(version))
123 })
124 .and_then(|path| path.join("lib").join("d8.jar").path_if_exists())
125 )
126}
127
128fn env_android_platform_api_level() -> Option<String> {
132 let mut base = env_var(ANDROID_PLATFORM).ok()
133 .or_else(|| env_var(ANDROID_API_LEVEL).ok())
134 .or_else(|| env_var(ANDROID_SDK_VERSION).ok())?;
135
136 if base.is_empty() {
137 return None;
138 }
139
140 if !base.starts_with("android-") {
141 base = format!("android-{}", base);
142 }
143
144 if base.contains("-ext") {
145 return Some(base);
146 }
147
148 if let Ok(raw_ext) = env_var(ANDROID_SDK_EXTENSION) {
149 let ext_num = raw_ext
150 .trim_start_matches("-")
151 .trim_start_matches("ext");
152 if !ext_num.is_empty() {
153 base = format!("{}-ext{}", base, ext_num);
154 }
155 }
156
157 Some(base)
158}
159
160fn find_latest_version(base: impl AsRef<Path>, arg: impl AsRef<Path>) -> Option<String> {
166 std::fs::read_dir(base)
167 .ok()?
168 .filter_map(|entry| entry.ok())
169 .filter(|entry| entry.path().join(arg.as_ref()).exists())
170 .map(|entry| entry.file_name())
171 .max()
172 .and_then(|name| name.to_os_string().into_string().ok())
173}
174
175pub fn java() -> Option<PathBuf> {
177 java_home().and_then(|jh| jh
178 .join("bin")
179 .join("java")
180 .path_if_exists()
181 )
182}
183
184pub fn javac() -> Option<PathBuf> {
186 java_home().and_then(|jh| jh
187 .join("bin")
188 .join("javac")
189 .path_if_exists()
190 )
191}
192
193pub fn java_home() -> Option<PathBuf> {
200 env_var(JAVA_HOME).ok()
201 .and_then(PathExt::path_if_exists)
202 .map(PathBuf::from)
203 .or_else(find_java_home)
204}
205
206pub fn java_source_version() -> Option<u32> {
208 env_var(JAVA_SOURCE_VERSION).ok()?.parse().ok()
209}
210
211pub fn java_target_version() -> Option<u32> {
213 env_var(JAVA_TARGET_VERSION).ok()?.parse().ok()
214}
215
216pub fn check_javac_version(java_home: impl AsRef<Path>) -> std::io::Result<u32> {
218 let javac = java_home.as_ref().join("bin").join("javac");
219 let output = std::process::Command::new(&javac)
220 .arg("-version")
221 .output()
222 .map_err(|e| std::io::Error::other(
223 format!("Failed to execute javac -version: {:?}", e)
224 ))?;
225 if !output.status.success() {
226 return Err(std::io::Error::other(format!(
227 "Failed to get javac version: {}",
228 String::from_utf8_lossy(&output.stderr)
229 )));
230 }
231 let mut version_output = String::from_utf8_lossy(&output.stdout);
232 if version_output.is_empty() {
233 version_output = String::from_utf8_lossy(&output.stderr);
235 }
236 let version = parse_javac_version_output(&version_output);
237 if version > 0 {
238 Ok(version as u32)
239 } else {
240 Err(std::io::Error::other(
241 format!("Failed to parse javac version: '{version_output}'")
242 ))
243 }
244}
245
246fn parse_javac_version_output(version_output: &str) -> i32 {
248 let version = version_output
249 .split_whitespace()
250 .nth(1)
251 .and_then(|v| v.split('-').next())
252 .unwrap_or_default();
253 let mut java_ver: i32 = version.split('.').next().unwrap_or("0").parse().unwrap_or(0);
254 if java_ver == 1 {
255 java_ver = version.split('.').nth(1).unwrap_or("0").parse().unwrap_or(0);
257 }
258 java_ver
259}
260
261#[test]
262fn test_parse_javac_version() {
263 assert_eq!(parse_javac_version_output("javac 1.8.0_292"), 8);
264 assert_eq!(parse_javac_version_output("javac 17.0.13"), 17);
265 assert_eq!(parse_javac_version_output("javac 21.0.5"), 21);
266 assert_eq!(parse_javac_version_output("javac 24-ea"), 24);
267 assert_eq!(parse_javac_version_output("error"), 0);
268 assert_eq!(parse_javac_version_output("javac error"), 0);
269}
270
271fn env_var(var: &str) -> Result<String, env::VarError> {
273 #[cfg(feature = "cargo")]
274 println!("cargo:rerun-if-env-changed={}", var);
275 env::var(var)
276}