diff --git a/.vscode/settings.json b/.vscode/settings.json index a1b7ed88b..253e55c5f 100644 --- a/.vscode/settings.json +++ b/.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", diff --git a/packages/cli/assets/android/MainActivity.kt b/packages/cli/assets/android/MainActivity.kt deleted file mode 100644 index e45e16119..000000000 --- a/packages/cli/assets/android/MainActivity.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.example.androidfinal - -class MainActivity : WryActivity() diff --git a/packages/cli/assets/android/MainActivity.kt.hbs b/packages/cli/assets/android/MainActivity.kt.hbs new file mode 100644 index 000000000..15cc9e386 --- /dev/null +++ b/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() diff --git a/packages/cli/assets/android/gen/app/build.gradle.kts b/packages/cli/assets/android/gen/app/build.gradle.kts.hbs similarity index 93% rename from packages/cli/assets/android/gen/app/build.gradle.kts rename to packages/cli/assets/android/gen/app/build.gradle.kts.hbs index 62efdce70..74ac21707 100644 --- a/packages/cli/assets/android/gen/app/build.gradle.kts +++ b/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 diff --git a/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml b/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs similarity index 87% rename from packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml rename to packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs index be79c89cd..3164e10e9 100644 --- a/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml +++ b/packages/cli/assets/android/gen/app/src/main/AndroidManifest.xml.hbs @@ -5,8 +5,8 @@ - + android:label="@string/app_name" android:name="dev.dioxus.main.MainActivity"> + diff --git a/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml b/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml deleted file mode 100644 index 2439055d9..000000000 --- a/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Androidfinal - \ No newline at end of file diff --git a/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs b/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs new file mode 100644 index 000000000..9847bd9b6 --- /dev/null +++ b/packages/cli/assets/android/gen/app/src/main/res/values/strings.xml.hbs @@ -0,0 +1,3 @@ + + {{app_name}} + diff --git a/packages/cli/src/build/bundle.rs b/packages/cli/src/build/bundle.rs index 15011406a..28074551e 100644 --- a/packages/cli/src/build/bundle.rs +++ b/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 { 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(()) + } } diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index b9e7d06c8..b0d8a2e37 100644 --- a/packages/cli/src/build/request.rs +++ b/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 + } } diff --git a/packages/cli/src/dioxus_crate.rs b/packages/cli/src/dioxus_crate.rs index a0074f5cc..97377f43a 100644 --- a/packages/cli/src/dioxus_crate.rs +++ b/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 { diff --git a/packages/cli/src/serve/handle.rs b/packages/cli/src/serve/handle.rs index add387b8e..3a4f4eaa9 100644 --- a/packages/cli/src/serve/handle.rs +++ b/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(); - // 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?; + // 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?; + + // 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(()) } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index bb52f2686..33d1bee13 100644 --- a/packages/cli/src/serve/runner.rs +++ b/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; diff --git a/packages/mobile/src/lib.rs b/packages/mobile/src/lib.rs index 213183e73..f5cd224d0 100644 --- a/packages/mobile/src/lib.rs +++ b/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