diff --git a/packages/cli/src/build/bundle.rs b/packages/cli/src/build/bundle.rs index 258659e4b..15aae2b56 100644 --- a/packages/cli/src/build/bundle.rs +++ b/packages/cli/src/build/bundle.rs @@ -745,7 +745,13 @@ impl AppBundle { )?; } - let output = Command::new("./gradlew") + let gradle_exec_name = match cfg!(windows) { + true => "gradlew.bat", + false => "gradlew", + }; + let gradle_exec = self.build.root_dir().join(gradle_exec_name); + + let output = Command::new(gradle_exec) .arg("assembleDebug") .current_dir(self.build.root_dir()) .stderr(std::process::Stdio::piped()) diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index b0d8a2e37..d2ee32269 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -114,10 +114,11 @@ impl BuildRequest { // We don't want to overwrite the user's .cargo/config.toml since that gets committed to git // and we want everyone's install to be the same. if self.build.platform() == Platform::Android { - let linker = self + let ndk = self .krate - .android_linker() + .android_ndk() .context("Could not autodetect android linker")?; + let linker = self.build.target_args.arch().android_linker(&ndk); tracing::trace!("Using android linker: {linker:?}"); @@ -252,7 +253,7 @@ impl BuildRequest { Some(true) => Some("aarch64-apple-ios"), _ => Some("aarch64-apple-ios-sim"), }, - Platform::Android => Some("aarch64-linux-android"), + Platform::Android => Some(self.build.target_args.arch().android_target_triplet()), Platform::Server => None, // we're assuming we're building for the native platform for now... if you're cross-compiling // the targets here might be different @@ -536,7 +537,7 @@ impl BuildRequest { .join("src") .join("main") .join("jniLibs") - .join("arm64-v8a"), + .join(self.build.target_args.arch().android_jnilib()), // these are all the same, I think? Platform::Windows diff --git a/packages/cli/src/build/verify.rs b/packages/cli/src/build/verify.rs index 3fa876183..f00eaa374 100644 --- a/packages/cli/src/build/verify.rs +++ b/packages/cli/src/build/verify.rs @@ -134,13 +134,20 @@ impl BuildRequest { /// will do its best to fill in the missing bits by exploring the sdk structure /// IE will attempt to use the Java installed from android studio if possible. pub(crate) async fn verify_android_tooling(&self, _rustup: RustupShow) -> Result<()> { - if self.krate.android_linker().is_none() { - return Err(anyhow::anyhow!( - "Android linker not found. Please set the ANDROID_NDK_HOME environment variable to the root of your NDK installation." - ).into()); + let result = self + .krate + .android_ndk() + .map(|ndk| self.build.target_args.arch().android_linker(&ndk)); + + if let Some(path) = result { + if path.exists() { + return Ok(()); + } } - Ok(()) + Err(anyhow::anyhow!( + "Android linker not found. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation." + ).into()) } /// Ensure the right dependencies are installed for linux apps. diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index afefec35f..ed2f9d48e 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -63,7 +63,7 @@ impl BuildArgs { let krate = DioxusCrate::new(&self.target_args).context("Failed to load Dioxus workspace")?; - self.resolve(&krate)?; + self.resolve(&krate).await?; let bundle = Builder::start(&krate, self.clone())?.finish().await?; @@ -79,7 +79,7 @@ impl BuildArgs { /// /// IE if they've specified "fullstack" as a feature on `dioxus`, then we want to build the /// fullstack variant even if they omitted the `--fullstack` flag. - pub(crate) fn resolve(&mut self, krate: &DioxusCrate) -> Result<()> { + pub(crate) async fn resolve(&mut self, krate: &DioxusCrate) -> Result<()> { let default_platform = krate.default_platform(); let auto_platform = krate.autodetect_platform(); @@ -145,6 +145,34 @@ impl BuildArgs { } } + // Determine arch if android + if self.platform == Some(Platform::Android) && self.target_args.arch.is_none() { + tracing::debug!("No android arch provided, attempting to auto detect."); + + let arch = Arch::autodetect().await; + + // Some extra logs + let arch = match arch { + Some(a) => { + tracing::info!( + "Autodetected `{}` Android arch.", + a.android_target_triplet() + ); + a.to_owned() + } + None => { + let a = Arch::default(); + tracing::info!( + "Could not detect Android arch, defaulting to `{}`", + a.android_target_triplet() + ); + a + } + }; + + self.target_args.arch = Some(arch); + } + Ok(()) } diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index 575be5998..2db93e6b5 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -46,7 +46,7 @@ impl Bundle { // We always use `release` mode for bundling self.build_arguments.release = true; - self.build_arguments.resolve(&krate)?; + self.build_arguments.resolve(&krate).await?; tracing::info!("Building app..."); diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index dcac3374c..7e6537343 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -14,7 +14,7 @@ impl RunArgs { let krate = DioxusCrate::new(&self.build_args.target_args) .context("Failed to load Dioxus workspace")?; - self.build_args.resolve(&krate)?; + self.build_args.resolve(&krate).await?; tracing::trace!("Building crate krate data: {:#?}", krate); tracing::trace!("Build args: {:#?}", self.build_args); diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index abd644fc2..0fb66c682 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -54,13 +54,13 @@ impl ServeArgs { Ok(StructuredOutput::Success) } - pub(crate) fn load_krate(&mut self) -> Result { + pub(crate) async fn load_krate(&mut self) -> Result { let krate = DioxusCrate::new(&self.build_arguments.target_args)?; - self.resolve(&krate)?; + self.resolve(&krate).await?; Ok(krate) } - pub(crate) fn resolve(&mut self, krate: &DioxusCrate) -> Result<()> { + pub(crate) async fn resolve(&mut self, krate: &DioxusCrate) -> Result<()> { // Enable hot reload. if self.hot_reload.is_none() { self.hot_reload = Some(krate.settings.always_hot_reload.unwrap_or(true)); @@ -82,7 +82,7 @@ impl ServeArgs { } // Resolve the build arguments - self.build_arguments.resolve(krate)?; + self.build_arguments.resolve(krate).await?; Ok(()) } diff --git a/packages/cli/src/cli/target.rs b/packages/cli/src/cli/target.rs index a4ab3123e..3a9052b27 100644 --- a/packages/cli/src/cli/target.rs +++ b/packages/cli/src/cli/target.rs @@ -1,4 +1,7 @@ use super::*; +use once_cell::sync::OnceCell; +use std::path::Path; +use tokio::process::Command; /// Information about the target to build #[derive(Clone, Debug, Default, Deserialize, Parser)] @@ -35,13 +38,11 @@ pub(crate) struct TargetArgs { #[clap(long)] pub(crate) no_default_features: bool, - /// The architecture to build for [default: "native"] - /// - /// Can either be `arm | arm64 | x86 | x86_64 | native` - #[clap(long)] - pub(crate) arch: Option, + /// The architecture to build for. + #[clap(long, value_enum)] + pub(crate) arch: Option, - /// Are we building for a device or just the simulator + /// Are we building for a device or just the simulator. /// If device is false, then we'll build for the simulator #[clap(long)] pub(crate) device: Option, @@ -50,3 +51,138 @@ pub(crate) struct TargetArgs { #[clap(long)] pub(crate) target: Option, } + +impl TargetArgs { + pub(crate) fn arch(&self) -> Arch { + self.arch.unwrap_or_default() + } +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Deserialize, clap::ValueEnum)] +#[non_exhaustive] +pub(crate) enum Arch { + // Android: armv7l, armv7-linux-androideabi + Arm, + // Android: aarch64, aarch64-linux-android + #[default] + Arm64, + // Android: i386, i686-linux-android + X86, + // Android: x86_64, x86_64-linux-android + X64, +} + +impl Arch { + pub(crate) async fn autodetect() -> Option { + // Try auto detecting arch through adb. + static AUTO_ARCH: OnceCell> = OnceCell::new(); + + match AUTO_ARCH.get() { + Some(a) => *a, + None => { + // TODO: Wire this up with --device flag. (add `-s serial`` flag before `shell` arg) + let output = Command::new("adb") + .arg("shell") + .arg("uname") + .arg("-m") + .output() + .await; + + let out = match output { + Ok(o) => o, + Err(e) => { + tracing::debug!("ADB command failed: {:?}", e); + return None; + } + }; + + // Parse ADB output + let Ok(out) = String::from_utf8(out.stdout) else { + tracing::debug!("ADB returned unexpected data."); + return None; + }; + let trimmed = out.trim().to_string(); + tracing::trace!("ADB Returned: `{trimmed:?}`"); + + // Set the cell + let arch = Arch::try_from(trimmed).ok(); + AUTO_ARCH + .set(arch) + .expect("the cell should have been checked empty by the match condition"); + + arch + } + } + } + + pub(crate) fn android_target_triplet(&self) -> &'static str { + match self { + Arch::Arm => "armv7-linux-androideabi", + Arch::Arm64 => "aarch64-linux-android", + Arch::X86 => "i686-linux-android", + Arch::X64 => "x86_64-linux-android", + } + } + + pub(crate) fn android_jnilib(&self) -> &'static str { + match self { + Arch::Arm => "armeabi-v7a", + Arch::Arm64 => "arm64-v8a", + Arch::X86 => "x86", + Arch::X64 => "x86_64", + } + } + + pub(crate) fn android_clang_triplet(&self) -> &'static str { + match self { + Self::Arm => "armv7a-linux-androideabi", + _ => self.android_target_triplet(), + } + } + + pub(crate) fn android_linker(&self, ndk: &Path) -> PathBuf { + // "/Users/jonkelley/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang" + + let toolchain_dir = ndk.join("toolchains").join("llvm").join("prebuilt"); + let triplet = self.android_clang_triplet(); + let clang_exec = format!("{}24-clang", triplet); + + if cfg!(target_os = "macos") { + // for whatever reason, even on aarch64 macos, the linker is under darwin-x86_64 + return toolchain_dir + .join("darwin-x86_64") + .join("bin") + .join(clang_exec); + } + + if cfg!(target_os = "linux") { + return toolchain_dir + .join("linux-x86_64") + .join("bin") + .join(clang_exec); + } + + if cfg!(target_os = "windows") { + return toolchain_dir + .join("windows-x86_64") + .join("bin") + .join(format!("{}.cmd", clang_exec)); + } + + unimplemented!("Unsupported target os for android toolchain auodetection") + } +} + +impl TryFrom for Arch { + type Error = (); + + fn try_from(value: String) -> Result { + match value.as_str() { + "armv7l" => Ok(Self::Arm), + "aarch64" => Ok(Self::Arm64), + "i386" => Ok(Self::X86), + "x86_64" => Ok(Self::X64), + _ => Err(()), + } + } +} diff --git a/packages/cli/src/dioxus_crate.rs b/packages/cli/src/dioxus_crate.rs index 97377f43a..ea7fca5f2 100644 --- a/packages/cli/src/dioxus_crate.rs +++ b/packages/cli/src/dioxus_crate.rs @@ -557,7 +557,7 @@ impl DioxusCrate { krates } - fn android_ndk(&self) -> Option { + pub(crate) fn android_ndk(&self) -> Option { // "/Users/jonkelley/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang" static PATH: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(|| { use std::env::var; @@ -596,37 +596,6 @@ impl DioxusCrate { PATH.clone() } - pub(crate) fn android_linker(&self) -> Option { - // "/Users/jonkelley/Library/Android/sdk/ndk/25.2.9519653/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang" - self.android_ndk().map(|ndk| { - let toolchain_dir = ndk.join("toolchains").join("llvm").join("prebuilt"); - - if cfg!(target_os = "macos") { - // for whatever reason, even on aarch64 macos, the linker is under darwin-x86_64 - return toolchain_dir - .join("darwin-x86_64") - .join("bin") - .join("aarch64-linux-android24-clang"); - } - - if cfg!(target_os = "linux") { - return toolchain_dir - .join("linux-x86_64") - .join("bin") - .join("aarch64-linux-android24-clang"); - } - - if cfg!(target_os = "windows") { - return toolchain_dir - .join("windows-x86_64") - .join("bin") - .join("aarch64-linux-android24-clang.cmd"); - } - - unimplemented!("Unsupported target os for android toolchain auodetection") - }) - } - pub(crate) fn mobile_org(&self) -> String { let identifier = self.bundle_identifier(); let mut split = identifier.splitn(3, '.'); diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 430e64e91..3dbd6d66d 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -42,7 +42,7 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> { let mut tracer = TraceController::redirect(); // Load the krate and resolve the server args against it - this might log so do it after we turn on the tracer first - let krate = args.load_krate()?; + let krate = args.load_krate().await?; // Note that starting the builder will queue up a build immediately let mut builder = Builder::start(&krate, args.build_args())?; @@ -158,7 +158,7 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> { screen.push_cargo_log(message); } BuildUpdate::BuildFailed { err } => { - tracing::error!("Build failed: {}", err); + tracing::error!("Build failed: {:?}", err); } BuildUpdate::BuildReady { bundle } => { let handle = runner