use std::path::{Path, PathBuf};
use color_eyre::eyre::{Result, bail};
use waterui_cli::{
android::{
AndroidBuildTools, AndroidNdk, AndroidPlatformTools, AndroidRustTargets, AndroidSdk,
AndroidSdkPlatforms, Java, Kotlin,
platform::{ALL_ABIS, AndroidAbi},
},
apple::toolchain::{AppleSdk, Xcode},
gtk4::toolchain::Gtk4Toolchain,
toolchain::{
Installation, Toolchain, ToolchainError,
cmake::Cmake,
doctor::{CheckStatus, doctor},
web::WebToolchain,
windows_arm64_llvm::WindowsArm64LlvmToolchain,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AndroidCheckScope {
BuildOrPackage,
Run,
}
fn toolchain_check_message<I: Installation>(component: &str, error: &ToolchainError<I>) -> String {
match error {
ToolchainError::Fixable(_) => format!(
"{component} toolchain check failed: missing dependencies can be fixed automatically with `water doctor --fix`."
),
ToolchainError::Unfixable(unfixable) => {
format!("{component} toolchain check failed: {unfixable}")
}
}
}
fn android_doctor_item_in_scope(name: &str, scope: AndroidCheckScope) -> bool {
match name {
"Android SDK"
| "Android SDK Platforms"
| "Android SDK Build-Tools (d8)"
| "Android NDK"
| "Android Rust Targets"
| "Host CMake"
| "Java"
| "Kotlin" => true,
"Android Platform-Tools (adb)" => scope == AndroidCheckScope::Run,
_ => false,
}
}
fn format_path_or_missing(label: &str, path: Option<&Path>) -> String {
path.map_or_else(
|| format!("- {label}: <not detected>"),
|path| format!("- {label}: {}", path.display()),
)
}
async fn android_detection_summary() -> String {
let sdk_root = AndroidSdk::detect_path();
let d8_jar = AndroidSdk::d8_jar_path();
let ndk_root = AndroidNdk::detect_path();
let java_bin: Option<PathBuf> = Java::detect_path().await;
let java_home: Option<PathBuf> = Java::detect_home().await;
[
"Detected Android/JDK configuration:".to_string(),
format_path_or_missing("Android SDK root", sdk_root.as_deref()),
format_path_or_missing("Android build-tools d8.jar", d8_jar.as_deref()),
format_path_or_missing("Android NDK root", ndk_root.as_deref()),
format_path_or_missing("Java executable", java_bin.as_deref()),
format_path_or_missing("JAVA_HOME", java_home.as_deref()),
]
.join("\n")
}
fn format_doctor_missing_item(
name: &'static str,
message: Option<String>,
is_fixable: bool,
) -> String {
let mode = if is_fixable { "fixable" } else { "manual" };
message.map_or_else(
|| format!("- {name} [{mode}]"),
|message| format!("- {name} [{mode}]: {message}"),
)
}
async fn android_doctor_summary(scope: AndroidCheckScope) -> String {
let mut lines = vec!["Relevant doctor diagnostics:".to_string()];
for item in doctor().await {
if item.status != CheckStatus::Missing || !android_doctor_item_in_scope(item.name, scope) {
continue;
}
let is_fixable = item.is_fixable();
let message = item.message;
lines.push(format_doctor_missing_item(item.name, message, is_fixable));
}
if lines.len() == 1 {
lines.push("- No additional Android diagnostics were reported by doctor.".to_string());
}
lines.join("\n")
}
async fn android_failure_message<I: Installation>(
component: &str,
error: &ToolchainError<I>,
scope: AndroidCheckScope,
) -> String {
[
toolchain_check_message(component, error),
android_detection_summary().await,
android_doctor_summary(scope).await,
"Next steps: run `water doctor` for full diagnostics, then `water doctor --fix` to auto-install fixable dependencies.".to_string(),
]
.join("\n")
}
pub async fn check_apple(sdk: AppleSdk) -> Result<()> {
let xcode = Xcode;
if let Err(e) = xcode.check().await {
bail!("{}", toolchain_check_message("Xcode", &e));
}
if let Err(e) = sdk.check().await {
bail!("{}", toolchain_check_message(&sdk.to_string(), &e));
}
Ok(())
}
pub async fn check_android_build_or_package() -> Result<()> {
check_android_build_or_package_for_abis(ALL_ABIS).await
}
pub async fn check_android_build_or_package_for_abis(required_abis: &[AndroidAbi]) -> Result<()> {
let sdk = AndroidSdk;
if let Err(e) = sdk.check().await {
bail!(
"{}",
android_failure_message("Android SDK", &e, AndroidCheckScope::BuildOrPackage).await
);
}
let platforms = AndroidSdkPlatforms;
if let Err(e) = platforms.check().await {
bail!(
"{}",
android_failure_message(
"Android SDK Platforms",
&e,
AndroidCheckScope::BuildOrPackage
)
.await
);
}
let build_tools = AndroidBuildTools;
if let Err(e) = build_tools.check().await {
bail!(
"{}",
android_failure_message(
"Android SDK Build-Tools (d8)",
&e,
AndroidCheckScope::BuildOrPackage
)
.await
);
}
let ndk = AndroidNdk;
if let Err(e) = ndk.check().await {
bail!(
"{}",
android_failure_message("Android NDK", &e, AndroidCheckScope::BuildOrPackage).await
);
}
let cmake = Cmake::default();
if let Err(e) = cmake.check().await {
bail!(
"{}",
android_failure_message("Host CMake", &e, AndroidCheckScope::BuildOrPackage).await
);
}
let java = Java;
if let Err(e) = java.check().await {
bail!(
"{}",
android_failure_message("Java", &e, AndroidCheckScope::BuildOrPackage).await
);
}
let rust_targets = AndroidRustTargets::for_abis(required_abis);
if let Err(e) = rust_targets.check().await {
bail!(
"{}",
android_failure_message(
"Android Rust Targets",
&e,
AndroidCheckScope::BuildOrPackage
)
.await
);
}
let kotlin = Kotlin;
if let Err(e) = kotlin.check().await {
bail!(
"{}",
android_failure_message("Kotlin", &e, AndroidCheckScope::BuildOrPackage).await
);
}
Ok(())
}
pub async fn check_android_run() -> Result<()> {
check_android_build_or_package().await?;
let platform_tools = AndroidPlatformTools;
if let Err(e) = platform_tools.check().await {
bail!(
"{}",
android_failure_message("Android Platform-Tools", &e, AndroidCheckScope::Run).await
);
}
Ok(())
}
pub async fn check_gtk4() -> Result<()> {
let toolchain = Gtk4Toolchain;
if let Err(e) = toolchain.check().await {
bail!("{}", toolchain_check_message("GTK4", &e));
}
Ok(())
}
pub async fn check_hydrolysis() -> Result<()> {
let llvm = WindowsArm64LlvmToolchain;
if let Err(e) = llvm.check().await {
bail!(
"{}",
toolchain_check_message("Windows ARM64 LLVM toolchain", &e)
);
}
Ok(())
}
pub async fn check_web() -> Result<()> {
let toolchain: WebToolchain = Default::default();
if let Err(error) = toolchain.check().await {
bail!(
"Web toolchain check failed: {error}. Run `water doctor --fix` to install fixable components."
);
}
Ok(())
}