waterui_cli/android/
toolchain.rs1use std::{env, path::PathBuf};
2
3use crate::{
4 toolchain::{Installation, Toolchain, cmake::Cmake},
5 utils::which,
6};
7
8pub type AndroidToolchain = (AndroidSdk, AndroidNdk, Cmake);
10
11#[derive(Debug, Clone, Default)]
13pub struct AndroidSdk;
14
15impl AndroidSdk {
16 #[must_use]
18 pub fn detect_path() -> Option<PathBuf> {
19 if let Ok(android_home) = env::var("ANDROID_HOME") {
21 let sdk_path = PathBuf::from(android_home);
22 if sdk_path.exists() {
23 return Some(sdk_path);
24 }
25 }
26
27 if cfg!(target_os = "macos") {
28 let home_sdk = PathBuf::from(env::var("HOME").ok()?).join("Library/Android/sdk");
29 if home_sdk.exists() {
30 return Some(home_sdk);
31 }
32 }
33
34 if cfg!(target_os = "linux") {
35 let home_sdk = PathBuf::from(env::var("HOME").ok()?).join("Android/Sdk");
36 if home_sdk.exists() {
37 return Some(home_sdk);
38 }
39 }
40
41 if cfg!(target_os = "windows") {
42 if let Ok(localappdata) = env::var("LOCALAPPDATA") {
43 let sdk_path = PathBuf::from(localappdata).join("Android/Sdk");
44 if sdk_path.exists() {
45 return Some(sdk_path);
46 }
47 }
48 }
49
50 None
51 }
52
53 #[must_use]
55 pub fn adb_path() -> Option<PathBuf> {
56 let sdk_path = Self::detect_path()?;
57 let adb = sdk_path
58 .join("platform-tools")
59 .join(if cfg!(target_os = "windows") {
60 "adb.exe"
61 } else {
62 "adb"
63 });
64 if adb.exists() { Some(adb) } else { None }
65 }
66
67 #[must_use]
69 pub fn emulator_path() -> Option<PathBuf> {
70 let sdk_path = Self::detect_path()?;
71 let emulator = sdk_path
72 .join("emulator")
73 .join(if cfg!(target_os = "windows") {
74 "emulator.exe"
75 } else {
76 "emulator"
77 });
78 if emulator.exists() {
79 Some(emulator)
80 } else {
81 None
82 }
83 }
84}
85
86#[derive(Debug, Clone, Default)]
88pub struct AndroidSdkInstallation;
89
90#[derive(Debug, Clone, Default)]
92pub struct AndroidNdk;
93
94#[derive(Debug)]
96pub struct Java;
97
98impl Java {
99 pub async fn detect_path() -> Option<PathBuf> {
101 if let Ok(home) = env::var("JAVA_HOME") {
103 let java_path = PathBuf::from(home).join("bin/java");
104 if java_path.exists() {
105 return Some(java_path);
106 }
107 }
108
109 if cfg!(target_os = "macos") {
111 const ANDROID_STUDIO_JBRS: &[&str] = &[
112 "/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java",
113 "/Applications/Android Studio Preview.app/Contents/jbr/Contents/Home/bin/java",
114 ];
115 for path in ANDROID_STUDIO_JBRS {
116 let java_path = PathBuf::from(path);
117 if java_path.exists() {
118 return Some(java_path);
119 }
120 }
121 }
122
123 which("java").await.ok()
125 }
126}
127
128impl AndroidNdk {
129 #[must_use]
131 pub fn detect_path() -> Option<PathBuf> {
132 if let Ok(ndk_root) = env::var("ANDROID_NDK_ROOT") {
134 let ndk_path = PathBuf::from(ndk_root);
135 if ndk_path.exists() {
136 return Some(ndk_path);
137 }
138 }
139
140 if let Ok(ndk_home) = env::var("ANDROID_NDK_HOME") {
142 let ndk_path = PathBuf::from(ndk_home);
143 if ndk_path.exists() {
144 return Some(ndk_path);
145 }
146 }
147
148 let sdk_path = AndroidSdk::detect_path()?;
150
151 let ndk_dir = sdk_path.join("ndk");
152 if ndk_dir.exists() {
153 if let Ok(entries) = std::fs::read_dir(&ndk_dir) {
155 let mut versions: Vec<PathBuf> = entries
156 .filter_map(std::result::Result::ok)
157 .map(|e| e.path())
158 .filter(|p| p.is_dir())
159 .collect();
160
161 versions.sort();
162 if let Some(latest) = versions.last() {
163 return Some(latest.clone());
164 }
165 }
166 }
167
168 None
169 }
170}
171
172#[derive(Debug, thiserror::Error)]
174pub enum FailToInstallAndroidSdk {}
175
176impl Toolchain for AndroidSdk {
177 type Installation = AndroidSdkInstallation;
178
179 async fn check(&self) -> Result<(), crate::toolchain::ToolchainError<Self::Installation>> {
180 use crate::toolchain::ToolchainError;
181
182 if Self::detect_path().is_none() {
183 return Err(ToolchainError::unfixable(
184 "Android SDK not found",
185 "Install Android Studio from https://developer.android.com/studio \
186 or set ANDROID_HOME environment variable to your SDK path.",
187 ));
188 }
189
190 if Self::adb_path().is_none() {
192 return Err(ToolchainError::unfixable(
193 "Android SDK platform-tools not found",
194 "Open Android Studio -> SDK Manager -> SDK Tools -> check 'Android SDK Platform-Tools'",
195 ));
196 }
197
198 Ok(())
199 }
200}
201
202impl Installation for AndroidSdkInstallation {
203 type Error = FailToInstallAndroidSdk;
204
205 async fn install(&self) -> Result<(), Self::Error> {
206 Ok(())
210 }
211}
212
213#[derive(Debug)]
215pub struct AndroidNdkInstallation;
216
217impl Toolchain for AndroidNdk {
218 type Installation = AndroidNdkInstallation;
219
220 async fn check(&self) -> Result<(), crate::toolchain::ToolchainError<Self::Installation>> {
221 use crate::toolchain::ToolchainError;
222
223 let ndk_path = Self::detect_path().ok_or_else(|| {
224 ToolchainError::unfixable(
225 "Android NDK not found",
226 "Open Android Studio -> SDK Manager -> SDK Tools -> check 'NDK (Side by side)' \
227 or set ANDROID_NDK_ROOT environment variable.",
228 )
229 })?;
230
231 let llvm_dir = ndk_path.join("toolchains/llvm/prebuilt");
233 if !llvm_dir.exists() {
234 return Err(ToolchainError::unfixable(
235 "Android NDK LLVM toolchain not found",
236 "The NDK installation appears to be incomplete. \
237 Try reinstalling the NDK via Android Studio SDK Manager.",
238 ));
239 }
240
241 Ok(())
242 }
243}
244
245impl Installation for AndroidNdkInstallation {
246 type Error = FailToInstallAndroidNdk;
247
248 async fn install(&self) -> Result<(), Self::Error> {
249 Ok(())
252 }
253}
254
255#[derive(Debug, thiserror::Error)]
257pub enum FailToInstallAndroidNdk {}