From 2df3107b5512e084f17194cd1e8d5af8cbad8ec3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 15 Jan 2026 09:34:20 -0600 Subject: [PATCH 1/2] autodetect android location --- packages/cli/src/build/tools.rs | 59 +++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/build/tools.rs b/packages/cli/src/build/tools.rs index d0638014ce..a9bf0a28e0 100644 --- a/packages/cli/src/build/tools.rs +++ b/packages/cli/src/build/tools.rs @@ -21,9 +21,16 @@ pub(crate) struct AndroidTools { pub fn get_android_tools() -> Option> { // We check for SDK first since users might install Android Studio and then install the SDK // After that they might install the NDK, so the SDK drives the source of truth. + // + // Priority order: + // 1. ANDROID_SDK_ROOT env var (official) + // 2. ANDROID_SDK env var (legacy) + // 3. ANDROID_HOME env var (common convention) + // 4. Default platform-specific installation path let sdk = var_or_debug("ANDROID_SDK_ROOT") .or_else(|| var_or_debug("ANDROID_SDK")) - .or_else(|| var_or_debug("ANDROID_HOME")); + .or_else(|| var_or_debug("ANDROID_HOME")) + .or_else(default_android_sdk_path); // Check the ndk. We look for users's overrides first and then look into the SDK. // Sometimes users set only the NDK (especially if they're somewhat advanced) so we need to look for it manually @@ -100,9 +107,23 @@ pub fn get_android_tools() -> Option> { } } - // todo(jon): how do we detect java home on linux? #[cfg(target_os = "linux")] { + // Check Android Studio bundled JDK first (common installation paths) + if let Some(home) = dirs::home_dir() { + let studio_jbr = home.join("android-studio/jbr"); + if studio_jbr.exists() { + return Some(studio_jbr); + } + } + + // Also check /opt which is another common install location + let opt_jbr = PathBuf::from("/opt/android-studio/jbr"); + if opt_jbr.exists() { + return Some(opt_jbr); + } + + // Fallback to system OpenJDK let jbr_home = PathBuf::from("/usr/lib/jvm/java-11-openjdk-amd64"); if jbr_home.exists() { return Some(jbr_home); @@ -357,3 +378,37 @@ fn var_or_debug(name: &str) -> Option { .ok() .map(PathBuf::from) } + +/// Returns the default Android SDK installation path for the current platform. +/// +/// This checks the standard installation locations for Android SDK: +/// - macOS: `~/Library/Android/sdk` +/// - Windows: `%LOCALAPPDATA%\Android\Sdk` +/// - Linux: `~/Android/Sdk` +fn default_android_sdk_path() -> Option { + #[cfg(target_os = "macos")] + { + let sdk = dirs::home_dir()?.join("Library/Android/sdk"); + if sdk.exists() { + return Some(sdk); + } + } + + #[cfg(target_os = "windows")] + { + let sdk = dirs::data_local_dir()?.join("Android").join("Sdk"); + if sdk.exists() { + return Some(sdk); + } + } + + #[cfg(target_os = "linux")] + { + let sdk = dirs::home_dir()?.join("Android/Sdk"); + if sdk.exists() { + return Some(sdk); + } + } + + None +} From 1fa966c29f558dfdf941758e520094d92535ad3e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 3 Apr 2026 14:20:48 -0500 Subject: [PATCH 2/2] pull out android resolve logic --- packages/cli/src/build/tools.rs | 157 +++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 32 deletions(-) diff --git a/packages/cli/src/build/tools.rs b/packages/cli/src/build/tools.rs index a9bf0a28e0..18afe74a4e 100644 --- a/packages/cli/src/build/tools.rs +++ b/packages/cli/src/build/tools.rs @@ -1,7 +1,10 @@ use crate::Result; use anyhow::Context; use itertools::Itertools; -use std::{path::PathBuf, sync::Arc}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; use target_lexicon::{ Aarch64Architecture, Architecture, ArmArchitecture, Triple, X86_32Architecture, }; @@ -19,38 +22,13 @@ pub(crate) struct AndroidTools { } pub fn get_android_tools() -> Option> { - // We check for SDK first since users might install Android Studio and then install the SDK - // After that they might install the NDK, so the SDK drives the source of truth. - // - // Priority order: - // 1. ANDROID_SDK_ROOT env var (official) - // 2. ANDROID_SDK env var (legacy) - // 3. ANDROID_HOME env var (common convention) - // 4. Default platform-specific installation path - let sdk = var_or_debug("ANDROID_SDK_ROOT") - .or_else(|| var_or_debug("ANDROID_SDK")) - .or_else(|| var_or_debug("ANDROID_HOME")) - .or_else(default_android_sdk_path); + let sdk_from_env = android_sdk_from_env(); + let ndk_from_env = android_ndk_from_env(); - // Check the ndk. We look for users's overrides first and then look into the SDK. - // Sometimes users set only the NDK (especially if they're somewhat advanced) so we need to look for it manually - // - // Might look like this, typically under "sdk": - // "/Users/jonkelley/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang" - let ndk = var_or_debug("NDK_HOME") - .or_else(|| var_or_debug("ANDROID_NDK_HOME")) - .or_else(|| { - // Look for the most recent NDK in the event the user has installed multiple NDK - // Eventually we might need to drive this from Dioxus.toml - let sdk = sdk.as_ref()?; - let ndk_dir = sdk.join("ndk").read_dir().ok()?; - ndk_dir - .flatten() - .map(|dir| (dir.file_name(), dir.path())) - .sorted() - .next_back() - .map(|(_, path)| path.to_path_buf()) - })?; + // When the user explicitly points at an NDK but not an SDK, keep the SDK paired with that NDK + // instead of overriding it with a platform default installation. + let (sdk, ndk) = + resolve_android_sdk_and_ndk(sdk_from_env, ndk_from_env, default_android_sdk_path())?; // Look for ADB in the SDK. If it's not there we'll use `adb` from the PATH let adb = sdk @@ -115,6 +93,11 @@ pub fn get_android_tools() -> Option> { if studio_jbr.exists() { return Some(studio_jbr); } + + let studio_jre = home.join("android-studio/jre"); + if studio_jre.exists() { + return Some(studio_jre); + } } // Also check /opt which is another common install location @@ -123,6 +106,11 @@ pub fn get_android_tools() -> Option> { return Some(opt_jbr); } + let opt_jre = PathBuf::from("/opt/android-studio/jre"); + if opt_jre.exists() { + return Some(opt_jre); + } + // Fallback to system OpenJDK let jbr_home = PathBuf::from("/usr/lib/jvm/java-11-openjdk-amd64"); if jbr_home.exists() { @@ -379,6 +367,54 @@ fn var_or_debug(name: &str) -> Option { .map(PathBuf::from) } +fn android_sdk_from_env() -> Option { + var_or_debug("ANDROID_SDK_ROOT") + .or_else(|| var_or_debug("ANDROID_SDK")) + .or_else(|| var_or_debug("ANDROID_HOME")) +} + +fn android_ndk_from_env() -> Option { + var_or_debug("NDK_HOME").or_else(|| var_or_debug("ANDROID_NDK_HOME")) +} + +fn sdk_from_ndk_path(ndk: &Path) -> Option { + Some(ndk.parent()?.parent()?.to_path_buf()) +} + +fn latest_ndk_in_sdk(sdk: &Path) -> Option { + // Look for the most recent NDK in the event the user has installed multiple NDKs. + sdk.join("ndk") + .read_dir() + .ok()? + .flatten() + .map(|dir| (dir.file_name(), dir.path())) + .sorted() + .next_back() + .map(|(_, path)| path) +} + +fn resolve_android_sdk_and_ndk( + sdk_from_env: Option, + ndk_from_env: Option, + default_sdk: Option, +) -> Option<(Option, PathBuf)> { + let sdk = sdk_from_env.clone().or_else(|| { + if ndk_from_env.is_some() { + None + } else { + default_sdk + } + }); + + let ndk = ndk_from_env + .clone() + .or_else(|| sdk.as_deref().and_then(latest_ndk_in_sdk))?; + + let sdk = sdk.or_else(|| sdk_from_ndk_path(&ndk)); + + Some((sdk, ndk)) +} + /// Returns the default Android SDK installation path for the current platform. /// /// This checks the standard installation locations for Android SDK: @@ -412,3 +448,60 @@ fn default_android_sdk_path() -> Option { None } + +#[cfg(test)] +mod tests { + use super::resolve_android_sdk_and_ndk; + use std::{fs, path::PathBuf}; + + fn create_ndk(sdk: &PathBuf, version: &str) -> PathBuf { + let ndk = sdk.join("ndk").join(version); + fs::create_dir_all(&ndk).unwrap(); + ndk + } + + #[test] + fn explicit_ndk_keeps_matching_sdk() { + let temp = tempfile::tempdir().unwrap(); + let default_sdk = temp.path().join("default-sdk"); + let custom_sdk = temp.path().join("custom-sdk"); + let custom_ndk = create_ndk(&custom_sdk, "26.3.11579264"); + create_ndk(&default_sdk, "27.0.12077973"); + + let (sdk, ndk) = + resolve_android_sdk_and_ndk(None, Some(custom_ndk.clone()), Some(default_sdk)).unwrap(); + + assert_eq!(sdk, Some(custom_sdk)); + assert_eq!(ndk, custom_ndk); + } + + #[test] + fn default_sdk_is_used_when_no_overrides_exist() { + let temp = tempfile::tempdir().unwrap(); + let default_sdk = temp.path().join("default-sdk"); + let older_ndk = create_ndk(&default_sdk, "25.2.9519653"); + let newer_ndk = create_ndk(&default_sdk, "27.0.12077973"); + + let (sdk, ndk) = + resolve_android_sdk_and_ndk(None, None, Some(default_sdk.clone())).unwrap(); + + assert_eq!(sdk, Some(default_sdk)); + assert_eq!(ndk, newer_ndk); + assert_ne!(ndk, older_ndk); + } + + #[test] + fn explicit_sdk_wins_over_default_sdk() { + let temp = tempfile::tempdir().unwrap(); + let default_sdk = temp.path().join("default-sdk"); + let env_sdk = temp.path().join("env-sdk"); + create_ndk(&default_sdk, "27.0.12077973"); + let env_ndk = create_ndk(&env_sdk, "26.1.10909125"); + + let (sdk, ndk) = + resolve_android_sdk_and_ndk(Some(env_sdk.clone()), None, Some(default_sdk)).unwrap(); + + assert_eq!(sdk, Some(env_sdk)); + assert_eq!(ndk, env_ndk); + } +}