1use std::path::{Path, PathBuf};
5
6use eyre::{Result, bail};
7
8use crate::{
9 android::{
10 AndroidBuildTools, AndroidNdk, AndroidPlatformTools, AndroidRustTargets, AndroidSdk,
11 AndroidSdkPlatforms, Java, Kotlin,
12 platform::{ALL_ABIS, AndroidAbi},
13 },
14 apple::toolchain::{AppleSdk, Xcode},
15 gtk4::toolchain::Gtk4Toolchain,
16 toolchain::{
17 Host, Installation, Toolchain, ToolchainError,
18 cmake::Cmake,
19 doctor::{CheckStatus, doctor, ids},
20 web::web_toolchain,
21 windows_arm64_llvm::WindowsArm64LlvmToolchain,
22 },
23};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26enum AndroidCheckScope {
27 BuildOrPackage,
28 Run,
29}
30
31fn toolchain_check_message<I: Installation>(component: &str, error: &ToolchainError<I>) -> String {
32 match error {
33 ToolchainError::Fixable(_) => format!(
34 "{component} toolchain check failed: missing dependencies can be fixed automatically with `water doctor --fix`."
35 ),
36 ToolchainError::Unfixable(unfixable) => {
37 format!("{component} toolchain check failed: {unfixable}")
38 }
39 }
40}
41
42fn android_doctor_item_in_scope(id: &str, scope: AndroidCheckScope) -> bool {
43 match id {
44 ids::ANDROID_SDK
45 | ids::ANDROID_SDK_PLATFORMS
46 | ids::ANDROID_BUILD_TOOLS
47 | ids::ANDROID_NDK
48 | ids::ANDROID_RUST_TARGETS
49 | ids::CMAKE
50 | ids::JAVA
51 | ids::KOTLIN => true,
52 ids::ANDROID_PLATFORM_TOOLS => scope == AndroidCheckScope::Run,
53 _ => false,
54 }
55}
56
57fn format_path_or_missing(label: &str, path: Option<&Path>) -> String {
58 path.map_or_else(
59 || format!("- {label}: <not detected>"),
60 |path| format!("- {label}: {}", path.display()),
61 )
62}
63
64async fn android_detection_summary(host: &Host) -> String {
65 let sdk_root = AndroidSdk::detect_path(host);
66 let d8_jar = AndroidSdk::d8_jar_path(host);
67 let ndk_root = AndroidNdk::detect_path(host);
68 let java_bin: Option<PathBuf> = Java::detect_path(host).await;
69 let java_home: Option<PathBuf> = Java::detect_home(host).await;
70
71 [
72 "Detected Android/JDK configuration:".to_string(),
73 format_path_or_missing("Android SDK root", sdk_root.as_deref()),
74 format_path_or_missing("Android build-tools d8.jar", d8_jar.as_deref()),
75 format_path_or_missing("Android NDK root", ndk_root.as_deref()),
76 format_path_or_missing("Java executable", java_bin.as_deref()),
77 format_path_or_missing("JAVA_HOME", java_home.as_deref()),
78 ]
79 .join("\n")
80}
81
82fn format_doctor_missing_item(
83 name: &'static str,
84 message: Option<String>,
85 is_fixable: bool,
86) -> String {
87 let mode = if is_fixable { "fixable" } else { "manual" };
88 message.map_or_else(
89 || format!("- {name} [{mode}]"),
90 |message| format!("- {name} [{mode}]: {message}"),
91 )
92}
93
94async fn android_doctor_summary(host: &Host, scope: AndroidCheckScope) -> String {
95 let mut lines = vec!["Relevant doctor diagnostics:".to_string()];
96
97 for item in doctor(host).await {
98 if item.status != CheckStatus::Missing || !android_doctor_item_in_scope(item.id, scope) {
99 continue;
100 }
101 let is_fixable = item.is_fixable();
102 let message = item.message;
103 lines.push(format_doctor_missing_item(item.name, message, is_fixable));
104 }
105
106 if lines.len() == 1 {
107 lines.push("- No additional Android diagnostics were reported by doctor.".to_string());
108 }
109
110 lines.join("\n")
111}
112
113async fn android_failure_message<I: Installation>(
114 host: &Host,
115 component: &str,
116 error: &ToolchainError<I>,
117 scope: AndroidCheckScope,
118) -> String {
119 [
120 toolchain_check_message(component, error),
121 android_detection_summary(host).await,
122 android_doctor_summary(host, scope).await,
123 "Next steps: run `water doctor` for full diagnostics, then `water doctor --fix` to auto-install fixable dependencies.".to_string(),
124 ]
125 .join("\n")
126}
127
128pub async fn check_apple(host: &Host, sdk: AppleSdk) -> Result<()> {
133 let xcode = Xcode;
134 if let Err(e) = xcode.check(host).await {
135 bail!("{}", toolchain_check_message("Xcode", &e));
136 }
137 if let Err(e) = sdk.check(host).await {
138 bail!("{}", toolchain_check_message(&sdk.to_string(), &e));
139 }
140 Ok(())
141}
142
143pub async fn check_android_build_or_package(host: &Host) -> Result<()> {
148 check_android_build_or_package_for_abis(host, ALL_ABIS).await
149}
150
151pub async fn check_android_build_or_package_for_abis(
156 host: &Host,
157 required_abis: &[AndroidAbi],
158) -> Result<()> {
159 let sdk = AndroidSdk;
160 if let Err(e) = sdk.check(host).await {
161 bail!(
162 "{}",
163 android_failure_message(host, "Android SDK", &e, AndroidCheckScope::BuildOrPackage)
164 .await
165 );
166 }
167 let platforms = AndroidSdkPlatforms;
168 if let Err(e) = platforms.check(host).await {
169 bail!(
170 "{}",
171 android_failure_message(
172 host,
173 "Android SDK Platforms",
174 &e,
175 AndroidCheckScope::BuildOrPackage
176 )
177 .await
178 );
179 }
180 let build_tools = AndroidBuildTools;
181 if let Err(e) = build_tools.check(host).await {
182 bail!(
183 "{}",
184 android_failure_message(
185 host,
186 "Android SDK Build-Tools (d8)",
187 &e,
188 AndroidCheckScope::BuildOrPackage
189 )
190 .await
191 );
192 }
193 let ndk = AndroidNdk;
194 if let Err(e) = ndk.check(host).await {
195 bail!(
196 "{}",
197 android_failure_message(host, "Android NDK", &e, AndroidCheckScope::BuildOrPackage)
198 .await
199 );
200 }
201 let cmake = Cmake::default();
202 if let Err(e) = cmake.check(host).await {
203 bail!(
204 "{}",
205 android_failure_message(host, "Host CMake", &e, AndroidCheckScope::BuildOrPackage)
206 .await
207 );
208 }
209 let java = Java;
210 if let Err(e) = java.check(host).await {
211 bail!(
212 "{}",
213 android_failure_message(host, "Java", &e, AndroidCheckScope::BuildOrPackage).await
214 );
215 }
216 let rust_targets = AndroidRustTargets::for_abis(required_abis);
217 if let Err(e) = rust_targets.check(host).await {
218 bail!(
219 "{}",
220 android_failure_message(
221 host,
222 "Android Rust Targets",
223 &e,
224 AndroidCheckScope::BuildOrPackage
225 )
226 .await
227 );
228 }
229 let kotlin = Kotlin;
230 if let Err(e) = kotlin.check(host).await {
231 bail!(
232 "{}",
233 android_failure_message(host, "Kotlin", &e, AndroidCheckScope::BuildOrPackage).await
234 );
235 }
236 Ok(())
237}
238
239pub async fn check_android_run(host: &Host) -> Result<()> {
244 check_android_build_or_package(host).await?;
245 let platform_tools = AndroidPlatformTools;
246 if let Err(e) = platform_tools.check(host).await {
247 bail!(
248 "{}",
249 android_failure_message(host, "Android Platform-Tools", &e, AndroidCheckScope::Run)
250 .await
251 );
252 }
253 Ok(())
254}
255
256pub async fn check_gtk4(host: &Host) -> Result<()> {
261 let toolchain = Gtk4Toolchain;
262 if let Err(e) = toolchain.check(host).await {
263 bail!("{}", toolchain_check_message("GTK4", &e));
264 }
265 Ok(())
266}
267
268pub async fn check_winui(host: &Host) -> Result<()> {
273 let toolchain = crate::winui::toolchain::WinUiToolchain;
274 if let Err(e) = toolchain.check(host).await {
275 bail!("{}", toolchain_check_message("WinUI", &e));
276 }
277 Ok(())
278}
279
280pub async fn check_hydrolysis(host: &Host) -> Result<()> {
285 let llvm = WindowsArm64LlvmToolchain;
286 if let Err(e) = llvm.check(host).await {
287 bail!(
288 "{}",
289 toolchain_check_message("Windows ARM64 LLVM toolchain", &e)
290 );
291 }
292 Ok(())
293}
294
295pub async fn check_web(host: &Host) -> Result<()> {
300 if let Err(error) = web_toolchain().check(host).await {
301 bail!(
302 "Web toolchain check failed: {error}. Run `water doctor --fix` to install fixable components."
303 );
304 }
305 Ok(())
306}