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