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

feat: allow android apps with any name, fix android + windows for aarch64 target (#3213)

* use llvm objcopy to rename syms
* use handlebars
* it works!
* remove linker code
* better docs internally about dev.dioxus.main
* use bundle identifier
* double check bundle identifier is proper
Jonathan Kelley 7 сар өмнө
parent
commit
8a2922c663

+ 3 - 0
.vscode/settings.json

@@ -3,6 +3,9 @@
   "[toml]": {
     "editor.formatOnSave": false
   },
+  "[handlebars]": {
+    "editor.formatOnSave": false
+  },
   // "rust-analyzer.check.workspace": true,
   // "rust-analyzer.check.workspace": false,
   // "rust-analyzer.check.features": "all",

+ 0 - 3
packages/cli/assets/android/MainActivity.kt

@@ -1,3 +0,0 @@
-package com.example.androidfinal
-
-class MainActivity : WryActivity()

+ 8 - 0
packages/cli/assets/android/MainActivity.kt.hbs

@@ -0,0 +1,8 @@
+package dev.dioxus.main;
+
+// need to re-export buildconfig down from the parent
+import {{application_id}}.BuildConfig;
+typealias BuildConfig = BuildConfig;
+
+
+class MainActivity : WryActivity()

+ 2 - 2
packages/cli/assets/android/gen/app/build.gradle.kts → packages/cli/assets/android/gen/app/build.gradle.kts.hbs

@@ -4,10 +4,10 @@ plugins {
 }
 
 android {
-    namespace="com.example.androidfinal"
+    namespace="{{ application_id }}"
     compileSdk = 33
     defaultConfig {
-        applicationId = "com.example.androidfinal"
+        applicationId = "{{ application_id }}"
         minSdk = 24
         targetSdk = 33
         versionCode = 1

+ 2 - 2
packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml → packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs

@@ -5,8 +5,8 @@
     <application android:hasCode="true" android:supportsRtl="true" android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name" android:theme="@style/AppTheme">
         <activity android:configChanges="orientation|keyboardHidden" android:exported="true"
-            android:label="@string/app_name" android:name="com.example.androidfinal.MainActivity">
-            <meta-data android:name="android.app.lib_name" android:value="androidfinal" />
+            android:label="@string/app_name" android:name="dev.dioxus.main.MainActivity">
+            <meta-data android:name="android.app.lib_name" android:value="dioxusmain" />
             <meta-data android:name="android.app.func_name" android:value="ANativeActivity_onCreate" />
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />

+ 0 - 3
packages/cli/assets/android/gen/app/src/main/res/values/strings.xml

@@ -1,3 +0,0 @@
-<resources>
-    <string name="app_name">Androidfinal</string>
-</resources>

+ 3 - 0
packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">{{app_name}}</string>
+</resources>

+ 15 - 9
packages/cli/src/build/bundle.rs

@@ -347,9 +347,8 @@ impl AppBundle {
             //
             // todo(jon): maybe just symlink this rather than copy it?
             Platform::Android => {
-                // https://github.com/rust-mobile/xbuild/blob/master/xbuild/template/lib.rs
-                // https://github.com/rust-mobile/xbuild/blob/master/apk/src/lib.rs#L19
-                std::fs::copy(&self.app.exe, self.main_exe())?;
+                self.copy_android_exe(&self.app.exe, &self.main_exe())
+                    .await?;
             }
 
             // These are all super simple, just copy the exe into the folder
@@ -703,10 +702,6 @@ impl AppBundle {
         Ok(())
     }
 
-    pub(crate) fn bundle_identifier(&self) -> String {
-        format!("com.dioxuslabs.{}", self.build.krate.executable_name())
-    }
-
     fn macos_plist_contents(&self) -> Result<String> {
         handlebars::Handlebars::new()
             .render_template(
@@ -715,7 +710,7 @@ impl AppBundle {
                     display_name: self.build.platform_exe_name(),
                     bundle_name: self.build.platform_exe_name(),
                     executable_name: self.build.platform_exe_name(),
-                    bundle_identifier: format!("com.dioxuslabs.{}", self.build.platform_exe_name()),
+                    bundle_identifier: self.build.krate.bundle_identifier(),
                 },
             )
             .map_err(|e| e.into())
@@ -729,7 +724,7 @@ impl AppBundle {
                     display_name: self.build.platform_exe_name(),
                     bundle_name: self.build.platform_exe_name(),
                     executable_name: self.build.platform_exe_name(),
-                    bundle_identifier: format!("com.dioxuslabs.{}", self.build.platform_exe_name()),
+                    bundle_identifier: self.build.krate.bundle_identifier(),
                 },
             )
             .map_err(|e| e.into())
@@ -774,4 +769,15 @@ impl AppBundle {
             .join("debug")
             .join("app-debug.apk")
     }
+
+    /// Copy the Android executable to the target directory, and rename the hardcoded com_hardcoded_dioxuslabs entries
+    /// to the user's app name.
+    async fn copy_android_exe(&self, source: &Path, destination: &Path) -> Result<()> {
+        // we might want to eventually use the objcopy logic to handle this
+        //
+        // https://github.com/rust-mobile/xbuild/blob/master/xbuild/template/lib.rs
+        // https://github.com/rust-mobile/xbuild/blob/master/apk/src/lib.rs#L19
+        std::fs::copy(source, destination)?;
+        Ok(())
+    }
 }

+ 57 - 23
packages/cli/src/build/request.rs

@@ -307,6 +307,7 @@ impl BuildRequest {
     pub(crate) fn android_rust_flags(&self) -> String {
         let mut rust_flags = std::env::var("RUSTFLAGS").unwrap_or_default();
 
+        // todo(jon): maybe we can make the symbol aliasing logic here instead of using llvm-objcopy
         if self.build.platform() == Platform::Android {
             let cur_exe = std::env::current_exe().unwrap();
             rust_flags.push_str(format!(" -Clinker={}", cur_exe.display()).as_str());
@@ -456,19 +457,13 @@ impl BuildRequest {
         let mut env_vars = vec![];
 
         if self.build.platform() == Platform::Android {
-            let app = self.root_dir().join("app");
-            let app_main = app.join("src").join("main");
-            let app_kotlin = app_main.join("kotlin");
-            let app_kotlin_out = app_kotlin.join("com").join("example").join("androidfinal");
-
-            env_vars.push((
-                "WRY_ANDROID_PACKAGE",
-                "com.example.androidfinal".to_string(),
-            ));
-            env_vars.push(("WRY_ANDROID_LIBRARY", "androidfinal".to_string()));
+            env_vars.push(("WRY_ANDROID_PACKAGE", "dev.dioxus.main".to_string()));
+            env_vars.push(("WRY_ANDROID_LIBRARY", "dioxusmain".to_string()));
             env_vars.push((
                 "WRY_ANDROID_KOTLIN_FILES_OUT_DIR",
-                app_kotlin_out.display().to_string(),
+                self.wry_android_kotlin_files_out_dir()
+                    .display()
+                    .to_string(),
             ));
 
             env_vars.push(("RUSTFLAGS", self.android_rust_flags()))
@@ -619,8 +614,10 @@ impl BuildRequest {
             Platform::Liveview => self.krate.executable_name().to_string(),
             Platform::Windows => format!("{}.exe", self.krate.executable_name()),
 
-            // from the apk spec, the root exe will actually be a shared library
-            Platform::Android => format!("lib{}.so", self.krate.executable_name()),
+            // from the apk spec, the root exe is a shared library
+            // we include the user's rust code as a shared library with a fixed namespacea
+            Platform::Android => "libdioxusmain.so".to_string(),
+
             Platform::Web => unimplemented!("there's no main exe on web"), // this will be wrong, I think, but not important?
 
             // todo: maybe this should be called AppRun?
@@ -643,7 +640,7 @@ impl BuildRequest {
         let app_kotlin = app_main.join("kotlin");
         let app_jnilibs = app_main.join("jniLibs");
         let app_assets = app_main.join("assets");
-        let app_kotlin_out = app_kotlin.join("com").join("example").join("androidfinal");
+        let app_kotlin_out = self.wry_android_kotlin_files_out_dir();
         create_dir_all(&app)?;
         create_dir_all(&app_main)?;
         create_dir_all(&app_kotlin)?;
@@ -657,6 +654,18 @@ impl BuildRequest {
         tracing::debug!("Initialized app/src/assets: {:?}", app_assets);
         tracing::debug!("Initialized app/src/kotlin/main: {:?}", app_kotlin_out);
 
+        // handlerbars
+        let hbs = handlebars::Handlebars::new();
+        #[derive(serde::Serialize)]
+        struct HbsTypes {
+            application_id: String,
+            app_name: String,
+        }
+        let hbs_data = HbsTypes {
+            application_id: self.krate.full_mobile_app_name(),
+            app_name: self.krate.mobile_app_name(),
+        };
+
         // Top-level gradle config
         write(
             root.join("build.gradle.kts"),
@@ -692,7 +701,10 @@ impl BuildRequest {
         // Now the app directory
         write(
             app.join("build.gradle.kts"),
-            include_bytes!("../../assets/android/gen/app/build.gradle.kts"),
+            hbs.render_template(
+                include_str!("../../assets/android/gen/app/build.gradle.kts.hbs"),
+                &hbs_data,
+            )?,
         )?;
         write(
             app.join("proguard-rules.pro"),
@@ -700,18 +712,20 @@ impl BuildRequest {
         )?;
         write(
             app.join("src").join("main").join("AndroidManifest.xml"),
-            include_bytes!("../../assets/android/gen/app/src/main/AndroidManifest.xml"),
+            hbs.render_template(
+                include_str!("../../assets/android/gen/app/src/main/AndroidManifest.xml.hbs"),
+                &hbs_data,
+            )?,
         )?;
 
         // Write the main activity manually since tao dropped support for it
         write(
-            app_main
-                .join("kotlin")
-                .join("com")
-                .join("example")
-                .join("androidfinal")
+            self.wry_android_kotlin_files_out_dir()
                 .join("MainActivity.kt"),
-            include_bytes!("../../assets/android/MainActivity.kt"),
+            hbs.render_template(
+                include_str!("../../assets/android/MainActivity.kt.hbs"),
+                &hbs_data,
+            )?,
         )?;
 
         // Write the res folder
@@ -720,7 +734,10 @@ impl BuildRequest {
         create_dir_all(res.join("values"))?;
         write(
             res.join("values").join("strings.xml"),
-            include_bytes!("../../assets/android/gen/app/src/main/res/values/strings.xml"),
+            hbs.render_template(
+                include_str!("../../assets/android/gen/app/src/main/res/values/strings.xml.hbs"),
+                &hbs_data,
+            )?,
         )?;
         write(
             res.join("values").join("colors.xml"),
@@ -790,4 +807,21 @@ impl BuildRequest {
 
         Ok(())
     }
+
+    pub(crate) fn wry_android_kotlin_files_out_dir(&self) -> PathBuf {
+        let mut kotlin_dir = self
+            .root_dir()
+            .join("app")
+            .join("src")
+            .join("main")
+            .join("kotlin");
+
+        for segment in "dev.dioxus.main".split('.') {
+            kotlin_dir = kotlin_dir.join(segment);
+        }
+
+        tracing::debug!("app_kotlin_out: {:?}", kotlin_dir);
+
+        kotlin_dir
+    }
 }

+ 34 - 3
packages/cli/src/dioxus_crate.rs

@@ -606,23 +606,54 @@ impl DioxusCrate {
                 return toolchain_dir
                     .join("darwin-x86_64")
                     .join("bin")
-                    .join("clang");
+                    .join("aarch64-linux-android24-clang");
             }
 
             if cfg!(target_os = "linux") {
-                return toolchain_dir.join("linux-x86_64").join("bin").join("clang");
+                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("clang.exe");
+                    .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, '.');
+        let sub = split
+            .next()
+            .expect("Identifier to have at least 3 periods like `com.example.app`");
+        let tld = split
+            .next()
+            .expect("Identifier to have at least 3 periods like `com.example.app`");
+        format!("{}.{}", sub, tld)
+    }
+
+    pub(crate) fn mobile_app_name(&self) -> String {
+        self.executable_name().to_string()
+    }
+
+    pub(crate) fn full_mobile_app_name(&self) -> String {
+        format!("{}.{}", self.mobile_org(), self.mobile_app_name())
+    }
+
+    pub(crate) fn bundle_identifier(&self) -> String {
+        if let Some(identifier) = self.config.bundle.identifier.clone() {
+            return identifier.clone();
+        }
+
+        format!("com.example.{}", self.executable_name())
+    }
 }
 
 impl std::fmt::Debug for DioxusCrate {

+ 37 - 24
packages/cli/src/serve/handle.rs

@@ -21,6 +21,8 @@ use tokio::{
 /// We might want to bring in websockets here too, so we know the exact channels the app is using to
 /// communicate with the devserver. Currently that's a broadcast-type system, so this struct isn't super
 /// duper useful.
+///
+/// todo: restructure this such that "open" is a running task instead of blocking the main thread
 pub(crate) struct AppHandle {
     pub(crate) app: AppBundle,
 
@@ -266,7 +268,7 @@ impl AppHandle {
             .arg("launch")
             .arg("--console")
             .arg("booted")
-            .arg(self.app.bundle_identifier())
+            .arg(self.app.build.krate.bundle_identifier())
             .envs(ios_envs)
             .stderr(Stdio::piped())
             .stdout(Stdio::piped())
@@ -451,30 +453,41 @@ impl AppHandle {
         unimplemented!("dioxus-cli doesn't support ios devices yet.")
     }
 
-    async fn open_android_sim(&self, envs: Vec<(&str, String)>) -> Result<()> {
-        // Install
-        // adb install -r app-debug.apk
-        let _output = Command::new("adb")
-            .arg("install")
-            .arg("-r")
-            .arg(self.app.apk_path())
-            .stderr(Stdio::piped())
-            .stdout(Stdio::piped())
-            .output()
-            .await?;
+    async fn open_android_sim(&self, envs: Vec<(&'static str, String)>) -> Result<()> {
+        let apk_path = self.app.apk_path();
+        let full_mobile_app_name = self.app.build.krate.full_mobile_app_name();
+
+        // Start backgrounded since .open() is called while in the arm of the top-level match
+        tokio::task::spawn(async move {
+            // Install
+            // adb install -r app-debug.apk
+            let _output = Command::new("adb")
+                .arg("install")
+                .arg("-r")
+                .arg(apk_path)
+                .stderr(Stdio::piped())
+                .stdout(Stdio::piped())
+                .output()
+                .await?;
 
-        // adb shell am start -n com.example.androidfinal/com.example.androidfinal.MainActivity
-        let _output = Command::new("adb")
-            .arg("shell")
-            .arg("am")
-            .arg("start")
-            .arg("-n")
-            .arg("com.example.androidfinal/com.example.androidfinal.MainActivity")
-            .envs(envs)
-            .stderr(Stdio::piped())
-            .stdout(Stdio::piped())
-            .output()
-            .await?;
+            // eventually, use the user's MainAcitivty, not our MainAcitivty
+            // adb shell am start -n dev.dioxus.main/dev.dioxus.main.MainActivity
+            let activity_name = format!("{}/dev.dioxus.main.MainActivity", full_mobile_app_name,);
+
+            let _output = Command::new("adb")
+                .arg("shell")
+                .arg("am")
+                .arg("start")
+                .arg("-n")
+                .arg(activity_name)
+                .envs(envs)
+                .stderr(Stdio::piped())
+                .stdout(Stdio::piped())
+                .output()
+                .await?;
+
+            Result::<()>::Ok(())
+        });
 
         Ok(())
     }

+ 1 - 1
packages/cli/src/serve/runner.rs

@@ -275,7 +275,7 @@ impl AppRunner {
                     .arg("simctl")
                     .arg("get_app_container")
                     .arg("booted")
-                    .arg(runner.app.bundle_identifier())
+                    .arg(runner.app.build.krate.bundle_identifier())
                     .output()
                     .await;
 

+ 11 - 9
packages/mobile/src/lib.rs

@@ -47,19 +47,21 @@ pub fn root() {
     dioxus_desktop::launch::launch(app, vec![], Default::default());
 }
 
+/// Expose the `Java_dev_dioxus_main_WryActivity_create` function to the JNI layer.
+/// We hardcode these to have a single trampoline for host Java code to call into.
+///
+/// This saves us from having to plumb the top-level package name all the way down into
+/// this file. This is better for modularity (ie just call dioxus' main to run the app) as
+/// well as cache thrashing since this crate doesn't rely on external env vars.
+///
+/// The CLI is expecting to find `dev.dioxus.main` in the final library. If you find a need to
+/// change this, you'll need to change the CLI as well.
 #[cfg(target_os = "android")]
 #[no_mangle]
 #[inline(never)]
 pub extern "C" fn start_app() {
-    tao::android_binding!(
-        com_example,
-        androidfinal,
-        WryActivity,
-        wry::android_setup,
-        root,
-        tao
-    );
-    wry::android_binding!(com_example, androidfinal, wry);
+    tao::android_binding!(dev_dioxus, main, WryActivity, wry::android_setup, root, tao);
+    wry::android_binding!(dev_dioxus, main, wry);
 }
 
 /// Call our `main` function to initialize the rust runtime and set the launch binding trampoline