1
0
Эх сурвалжийг харах

Feat: Android Dynamic Arch Support (#3215)

* feat: android multi-arch support

* revision: make krate immutable

* revision: minor tweaks

* revision: make pub crate

* revision: dedup arch access

* revision: minor tweaks
Miles Murgaw 7 сар өмнө
parent
commit
af53354b28

+ 7 - 1
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())

+ 5 - 4
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

+ 12 - 5
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.

+ 30 - 2
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(())
     }
 

+ 1 - 1
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...");
 

+ 1 - 1
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);

+ 4 - 4
packages/cli/src/cli/serve.rs

@@ -54,13 +54,13 @@ impl ServeArgs {
         Ok(StructuredOutput::Success)
     }
 
-    pub(crate) fn load_krate(&mut self) -> Result<DioxusCrate> {
+    pub(crate) async fn load_krate(&mut self) -> Result<DioxusCrate> {
         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(())
     }

+ 142 - 6
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<String>,
+    /// The architecture to build for.
+    #[clap(long, value_enum)]
+    pub(crate) arch: Option<Arch>,
 
-    /// 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<bool>,
@@ -50,3 +51,138 @@ pub(crate) struct TargetArgs {
     #[clap(long)]
     pub(crate) target: Option<String>,
 }
+
+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<Self> {
+        // Try auto detecting arch through adb.
+        static AUTO_ARCH: OnceCell<Option<Arch>> = 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<String> for Arch {
+    type Error = ();
+
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        match value.as_str() {
+            "armv7l" => Ok(Self::Arm),
+            "aarch64" => Ok(Self::Arm64),
+            "i386" => Ok(Self::X86),
+            "x86_64" => Ok(Self::X64),
+            _ => Err(()),
+        }
+    }
+}

+ 1 - 32
packages/cli/src/dioxus_crate.rs

@@ -557,7 +557,7 @@ impl DioxusCrate {
         krates
     }
 
-    fn android_ndk(&self) -> Option<PathBuf> {
+    pub(crate) fn android_ndk(&self) -> Option<PathBuf> {
         // "/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<Option<PathBuf>> = once_cell::sync::Lazy::new(|| {
             use std::env::var;
@@ -596,37 +596,6 @@ impl DioxusCrate {
         PATH.clone()
     }
 
-    pub(crate) fn android_linker(&self) -> Option<PathBuf> {
-        // "/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, '.');

+ 2 - 2
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