From 94fd8667823aae6a35040e19985fdb2c2aefba6f Mon Sep 17 00:00:00 2001 From: Andrew de Waal Date: Tue, 3 Feb 2026 08:14:48 -0800 Subject: [PATCH 1/5] feat: Support Android release flavors (feat #504) We now allow the `run_with_application_id_suffix` command for Android. This allows for the running of different build variants (such as debug and release), as described in [these docs](https://developer.android.com/build/build-variants#build-types). This is related to the Tauri feature described [here](https://github.com/tauri-apps/tauri/issues/14777). --- .changes/android-build-variants.md | 11 +++++ src/android/cli.rs | 9 +++- src/android/device.rs | 75 +++++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 .changes/android-build-variants.md diff --git a/.changes/android-build-variants.md b/.changes/android-build-variants.md new file mode 100644 index 00000000..9e25aaf7 --- /dev/null +++ b/.changes/android-build-variants.md @@ -0,0 +1,11 @@ +--- +"cargo-mobile2": minor +--- + +# Android build variants + +In order to support running different build variants (such as debug and +release), we now support calling `Device::run_with_application_id_suffix`. + +This relates to the `applicationIdSuffix` as defined in Android development, +described [in these docs](https://developer.android.com/build/build-variants#build-types). diff --git a/src/android/cli.rs b/src/android/cli.rs index fb08b6b4..71625670 100644 --- a/src/android/cli.rs +++ b/src/android/cli.rs @@ -76,6 +76,11 @@ pub enum Command { help = "Specifies which activtiy to launch" )] activity: Option, + #[structopt( + long = "application-id-suffix", + help = "Optional suffix for the application ID (e.g. \".debug\")" + )] + application_id_suffix: Option, }, #[structopt(name = "st", about = "Displays a detailed stacktrace for a device")] Stacktrace, @@ -301,12 +306,13 @@ impl Exec for Input { filter: cli::Filter { filter }, reinstall_deps: cli::ReinstallDeps { reinstall_deps }, activity, + application_id_suffix, } => with_config(non_interactive, wrapper, |config, metadata, env| { let build_app_bundle = metadata.asset_packs().is_some(); ensure_init(config)?; device_prompt(env) .map_err(Error::DevicePromptFailed)? - .run( + .run_with_application_id_suffix( config, env, noise_level, @@ -320,6 +326,7 @@ impl Exec for Input { .unwrap_or(DEFAULT_ACTIVITY) .to_string() }), + application_id_suffix, ) .and_then(|h| { h.wait().map(|_| ()).map_err(|err| RunError::CommandFailed { diff --git a/src/android/device.rs b/src/android/device.rs index ada160db..9c80a34d 100644 --- a/src/android/device.rs +++ b/src/android/device.rs @@ -370,6 +370,12 @@ impl<'a> Device<'a> { Ok(()) } + /** + * This is the legacy function that doesn't support applicationIdSuffix, which + * is required for running different build variants (such as debug and release). + * + * It is kept for backwards compatibility. + */ #[allow(clippy::too_many_arguments)] pub fn run( &self, @@ -381,6 +387,32 @@ impl<'a> Device<'a> { build_app_bundle: bool, reinstall_deps: bool, activity: String, + ) -> Result { + return self.run_with_application_id_suffix( + config, + env, + noise_level, + profile, + filter_level, + build_app_bundle, + reinstall_deps, + activity, + None + ); + } + + #[allow(clippy::too_many_arguments)] + pub fn run_with_application_id_suffix( + &self, + config: &Config, + env: &Env, + noise_level: NoiseLevel, + profile: Profile, + filter_level: Option, + build_app_bundle: bool, + reinstall_deps: bool, + activity: String, + application_id_suffix: Option, ) -> Result { if build_app_bundle { bundletool::install(reinstall_deps).map_err(RunError::BundletoolInstallFailed)?; @@ -402,7 +434,8 @@ impl<'a> Device<'a> { self.install_apk(config, env, profile) .map_err(RunError::ApkInstallFailed)?; } - let activity = format!("{}/{}", config.app().identifier(), activity); + + let activity = Device::resolve_activity_name(config.app().identifier().to_string(), application_id_suffix, activity); let activity_ = activity.clone(); let cmd = self .adb(env) @@ -518,4 +551,44 @@ impl<'a> Device<'a> { } Ok(()) } + + /** + * The activity name is the fully qualified class name of the activity to launch. + * Here are the expected formats for the activity name: of the release and debug variants + * + * Release variants have 2 acceptable formats: + * * `com.example.app/.MainActivity` + * * `com.example.app/com.example.app.MainActivity` + * + * Debug variants have 1 acceptable format: + * * `com.example.app.debug/com.example.app.MainActivity` + */ + fn resolve_activity_name( + app_identifier: String, + application_id_suffix: Option, + activity: String, + ) -> String { + let identifier: String = match application_id_suffix { + Some(suffix) => format!("{}{}", app_identifier, suffix), + None => app_identifier, + }; + + return format!("{}/{}", identifier, activity); + } } + +#[cfg(test)] +mod test { + use rstest::rstest; + use super::*; + #[rstest( + app_identifier, application_id_suffix, activity, expected, + case("com.example.app", Some(".debug"), "com.example.app.MainActivity", "com.example.app.debug/com.example.app.MainActivity"), + case("com.example.app", None, ".MainActivity", "com.example.app/.MainActivity"), + case("com.example.app", None, "com.example.app.MainActivity", "com.example.app/com.example.app.MainActivity"), + )] + fn test_resolve_activity_name(app_identifier: String, application_id_suffix: Option<&str>, activity: String, expected: String) { + let activity = Device::resolve_activity_name(app_identifier, application_id_suffix.map(|s| s.to_string()), activity); + assert_eq!(activity, expected); + } +} \ No newline at end of file From 0bd0972e379dd253abe0baa2cd3c9c3e931efcbe Mon Sep 17 00:00:00 2001 From: Andrew de Waal Date: Mon, 2 Mar 2026 09:53:34 -0800 Subject: [PATCH 2/5] Fix linting errors --- src/android/device.rs | 81 ++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/src/android/device.rs b/src/android/device.rs index 9c80a34d..cb13ba73 100644 --- a/src/android/device.rs +++ b/src/android/device.rs @@ -373,7 +373,7 @@ impl<'a> Device<'a> { /** * This is the legacy function that doesn't support applicationIdSuffix, which * is required for running different build variants (such as debug and release). - * + * * It is kept for backwards compatibility. */ #[allow(clippy::too_many_arguments)] @@ -387,17 +387,17 @@ impl<'a> Device<'a> { build_app_bundle: bool, reinstall_deps: bool, activity: String, - ) -> Result { + ) -> Result { return self.run_with_application_id_suffix( - config, - env, - noise_level, - profile, - filter_level, - build_app_bundle, - reinstall_deps, - activity, - None + config, + env, + noise_level, + profile, + filter_level, + build_app_bundle, + reinstall_deps, + activity, + None, ); } @@ -435,7 +435,11 @@ impl<'a> Device<'a> { .map_err(RunError::ApkInstallFailed)?; } - let activity = Device::resolve_activity_name(config.app().identifier().to_string(), application_id_suffix, activity); + let activity = Device::resolve_activity_name( + config.app().identifier().to_string(), + application_id_suffix, + activity, + ); let activity_ = activity.clone(); let cmd = self .adb(env) @@ -555,12 +559,12 @@ impl<'a> Device<'a> { /** * The activity name is the fully qualified class name of the activity to launch. * Here are the expected formats for the activity name: of the release and debug variants - * - * Release variants have 2 acceptable formats: + * + * Release variants have 2 acceptable formats: * * `com.example.app/.MainActivity` * * `com.example.app/com.example.app.MainActivity` - * - * Debug variants have 1 acceptable format: + * + * Debug variants have 1 acceptable format: * * `com.example.app.debug/com.example.app.MainActivity` */ fn resolve_activity_name( @@ -572,23 +576,50 @@ impl<'a> Device<'a> { Some(suffix) => format!("{}{}", app_identifier, suffix), None => app_identifier, }; - + return format!("{}/{}", identifier, activity); } } #[cfg(test)] mod test { - use rstest::rstest; use super::*; + use rstest::rstest; #[rstest( - app_identifier, application_id_suffix, activity, expected, - case("com.example.app", Some(".debug"), "com.example.app.MainActivity", "com.example.app.debug/com.example.app.MainActivity"), - case("com.example.app", None, ".MainActivity", "com.example.app/.MainActivity"), - case("com.example.app", None, "com.example.app.MainActivity", "com.example.app/com.example.app.MainActivity"), + app_identifier, + application_id_suffix, + activity, + expected, + case( + "com.example.app", + Some(".debug"), + "com.example.app.MainActivity", + "com.example.app.debug/com.example.app.MainActivity" + ), + case( + "com.example.app", + None, + ".MainActivity", + "com.example.app/.MainActivity" + ), + case( + "com.example.app", + None, + "com.example.app.MainActivity", + "com.example.app/com.example.app.MainActivity" + ) )] - fn test_resolve_activity_name(app_identifier: String, application_id_suffix: Option<&str>, activity: String, expected: String) { - let activity = Device::resolve_activity_name(app_identifier, application_id_suffix.map(|s| s.to_string()), activity); + fn test_resolve_activity_name( + app_identifier: String, + application_id_suffix: Option<&str>, + activity: String, + expected: String, + ) { + let activity = Device::resolve_activity_name( + app_identifier, + application_id_suffix.map(|s| s.to_string()), + activity, + ); assert_eq!(activity, expected); } -} \ No newline at end of file +} From 436a2d1c8be5e0f7bc51d72ef08b29973eb6fb18 Mon Sep 17 00:00:00 2001 From: Andrew de Waal Date: Tue, 3 Mar 2026 06:01:19 -0800 Subject: [PATCH 3/5] Apply suggestion from @FabianLars Replace `minor` with `patch` Co-authored-by: Fabian-Lars <30730186+FabianLars@users.noreply.github.com> --- .changes/android-build-variants.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/android-build-variants.md b/.changes/android-build-variants.md index 9e25aaf7..5901fdcb 100644 --- a/.changes/android-build-variants.md +++ b/.changes/android-build-variants.md @@ -1,5 +1,5 @@ --- -"cargo-mobile2": minor +"cargo-mobile2": patch --- # Android build variants From 74b2977bcb8efb8e82388a1b597955f44587b50a Mon Sep 17 00:00:00 2001 From: Andrew de Waal Date: Tue, 3 Mar 2026 06:49:40 -0800 Subject: [PATCH 4/5] Inline logic for activity path resolution --- src/android/device.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/android/device.rs b/src/android/device.rs index cb13ba73..f19cbee4 100644 --- a/src/android/device.rs +++ b/src/android/device.rs @@ -560,6 +560,9 @@ impl<'a> Device<'a> { * The activity name is the fully qualified class name of the activity to launch. * Here are the expected formats for the activity name: of the release and debug variants * + * Even though the actual logic of this method is very simple, it is kept + * separate for clarity and for unit testing. + * * Release variants have 2 acceptable formats: * * `com.example.app/.MainActivity` * * `com.example.app/com.example.app.MainActivity` @@ -572,12 +575,10 @@ impl<'a> Device<'a> { application_id_suffix: Option, activity: String, ) -> String { - let identifier: String = match application_id_suffix { - Some(suffix) => format!("{}{}", app_identifier, suffix), - None => app_identifier, - }; - - return format!("{}/{}", identifier, activity); + return format!( + "{app_identifier}{}/{activity}", + application_id_suffix.unwrap_or_default() + ); } } From 24d3f7b20f6de5d4284ca7540b9d4fc0c43336a9 Mon Sep 17 00:00:00 2001 From: Fabian-Lars <30730186+FabianLars@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:13:57 +0100 Subject: [PATCH 5/5] shorten changefile --- .changes/android-build-variants.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.changes/android-build-variants.md b/.changes/android-build-variants.md index 5901fdcb..7f234c29 100644 --- a/.changes/android-build-variants.md +++ b/.changes/android-build-variants.md @@ -2,10 +2,5 @@ "cargo-mobile2": patch --- -# Android build variants - -In order to support running different build variants (such as debug and -release), we now support calling `Device::run_with_application_id_suffix`. - -This relates to the `applicationIdSuffix` as defined in Android development, +Added `Device::run_with_application_id_suffix` to support running different build variants. This relates to the `applicationIdSuffix` as defined in Android development, described [in these docs](https://developer.android.com/build/build-variants#build-types).