From 3b4fac2017832d426dd07c5e24e26684eda57f7b Mon Sep 17 00:00:00 2001 From: Bipin Pandey <31367252+bipsBro@users.noreply.github.com> Date: Tue, 14 Oct 2025 23:46:54 +0545 Subject: [PATCH] feat(android): add auto_increment_version_code option for Android builds (#14194) * add new api (auto_increment_version_code) in android configuration * ensure increment is only ran once * skip on dev * update doc * change file --------- Co-authored-by: Lucas Nogueira --- .../auto-increment-android-version-code.md | 8 +++ crates/tauri-build/src/lib.rs | 2 +- crates/tauri-build/src/mobile.rs | 57 +---------------- crates/tauri-cli/config.schema.json | 7 ++ crates/tauri-cli/src/mobile/android/build.rs | 8 ++- crates/tauri-cli/src/mobile/android/dev.rs | 6 +- crates/tauri-cli/src/mobile/android/mod.rs | 64 +++++++++++++++++++ .../schemas/config.schema.json | 7 ++ crates/tauri-utils/src/config.rs | 11 ++++ 9 files changed, 112 insertions(+), 58 deletions(-) create mode 100644 .changes/auto-increment-android-version-code.md diff --git a/.changes/auto-increment-android-version-code.md b/.changes/auto-increment-android-version-code.md new file mode 100644 index 000000000..1703ae424 --- /dev/null +++ b/.changes/auto-increment-android-version-code.md @@ -0,0 +1,8 @@ +--- +"tauri-cli": minor:feat +"@tauri-apps/cli": minor:feat +"tauri-build": minor:feat +"tauri-utils": minor:feat +--- + +Add `tauri.conf.json > bundle > android > autoIncrementVersionCode` config option to automatically increment the Android version code. diff --git a/crates/tauri-build/src/lib.rs b/crates/tauri-build/src/lib.rs index 5e392d210..1cd9c7b78 100644 --- a/crates/tauri-build/src/lib.rs +++ b/crates/tauri-build/src/lib.rs @@ -499,7 +499,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> { println!("cargo:rustc-env=TAURI_ANDROID_PACKAGE_NAME_PREFIX={android_package_prefix}"); if let Some(project_dir) = env::var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) { - mobile::generate_gradle_files(project_dir, &config)?; + mobile::generate_gradle_files(project_dir)?; } cfg_alias("dev", is_dev()); diff --git a/crates/tauri-build/src/mobile.rs b/crates/tauri-build/src/mobile.rs index 836b0d935..9acef2e91 100644 --- a/crates/tauri-build/src/mobile.rs +++ b/crates/tauri-build/src/mobile.rs @@ -2,18 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use std::{fs::write, path::PathBuf}; +use std::path::PathBuf; use anyhow::{Context, Result}; -use semver::Version; -use tauri_utils::{config::Config, write_if_changed}; +use tauri_utils::write_if_changed; -use crate::is_dev; - -pub fn generate_gradle_files(project_dir: PathBuf, config: &Config) -> Result<()> { +pub fn generate_gradle_files(project_dir: PathBuf) -> Result<()> { let gradle_settings_path = project_dir.join("tauri.settings.gradle"); let app_build_gradle_path = project_dir.join("app").join("tauri.build.gradle.kts"); - let app_tauri_properties_path = project_dir.join("app").join("tauri.properties"); let mut gradle_settings = "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n".to_string(); @@ -21,7 +17,6 @@ pub fn generate_gradle_files(project_dir: PathBuf, config: &Config) -> Result<() val implementation by configurations dependencies {" .to_string(); - let mut app_tauri_properties = Vec::new(); for (env, value) in std::env::vars_os() { let env = env.to_string_lossy(); @@ -54,32 +49,6 @@ dependencies {" app_build_gradle.push_str("\n}"); - if let Some(version) = config.version.as_ref() { - app_tauri_properties.push(format!("tauri.android.versionName={version}")); - if let Some(version_code) = config.bundle.android.version_code.as_ref() { - app_tauri_properties.push(format!("tauri.android.versionCode={version_code}")); - } else if let Ok(version) = Version::parse(version) { - let mut version_code = version.major * 1000000 + version.minor * 1000 + version.patch; - - if is_dev() { - version_code = version_code.clamp(1, 2100000000); - } - - if version_code == 0 { - return Err(anyhow::anyhow!( - "You must change the `version` in `tauri.conf.json`. The default value `0.0.0` is not allowed for Android package and must be at least `0.0.1`." - )); - } else if version_code > 2100000000 { - return Err(anyhow::anyhow!( - "Invalid version code {}. Version code must be between 1 and 2100000000. You must change the `version` in `tauri.conf.json`.", - version_code - )); - } - - app_tauri_properties.push(format!("tauri.android.versionCode={version_code}")); - } - } - // Overwrite only if changed to not trigger rebuilds write_if_changed(&gradle_settings_path, gradle_settings) .context("failed to write tauri.settings.gradle")?; @@ -87,28 +56,8 @@ dependencies {" write_if_changed(&app_build_gradle_path, app_build_gradle) .context("failed to write tauri.build.gradle.kts")?; - if !app_tauri_properties.is_empty() { - let app_tauri_properties_content = format!( - "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n{}", - app_tauri_properties.join("\n") - ); - if std::fs::read_to_string(&app_tauri_properties_path) - .map(|o| o != app_tauri_properties_content) - .unwrap_or(true) - { - write(&app_tauri_properties_path, app_tauri_properties_content) - .context("failed to write tauri.properties")?; - } - } - println!("cargo:rerun-if-changed={}", gradle_settings_path.display()); println!("cargo:rerun-if-changed={}", app_build_gradle_path.display()); - if !app_tauri_properties.is_empty() { - println!( - "cargo:rerun-if-changed={}", - app_tauri_properties_path.display() - ); - } Ok(()) } diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index 6d480475c..0c25453f3 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -84,6 +84,7 @@ "default": { "active": false, "android": { + "autoIncrementVersionCode": false, "minSdkVersion": 24 }, "createUpdaterArtifacts": false, @@ -2282,6 +2283,7 @@ "android": { "description": "Android configuration.", "default": { + "autoIncrementVersionCode": false, "minSdkVersion": 24 }, "allOf": [ @@ -3826,6 +3828,11 @@ "format": "uint32", "maximum": 2100000000.0, "minimum": 1.0 + }, + "autoIncrementVersionCode": { + "description": "Whether to automatically increment the `versionCode` on each build.\n\n - If `true`, the generator will try to read the last `versionCode` from\n `tauri.properties` and increment it by 1 for every build.\n - If `false` or not set, it falls back to `version_code` or semver-derived logic.\n\n Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository.", + "default": false, + "type": "boolean" } }, "additionalProperties": false diff --git a/crates/tauri-cli/src/mobile/android/build.rs b/crates/tauri-cli/src/mobile/android/build.rs index a8f5ff2d2..86c4f22c5 100644 --- a/crates/tauri-cli/src/mobile/android/build.rs +++ b/crates/tauri-cli/src/mobile/android/build.rs @@ -15,7 +15,7 @@ use crate::{ flock, }, interface::{AppInterface, Interface, Options as InterfaceOptions}, - mobile::{write_options, CliOptions, TargetDevice}, + mobile::{android::generate_tauri_properties, write_options, CliOptions, TargetDevice}, ConfigValue, Error, Result, }; use clap::{ArgAction, Parser}; @@ -178,6 +178,12 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result Result<()> { Ok(()) } + +fn generate_tauri_properties( + config: &AndroidConfig, + tauri_config: &TauriConfig, + dev: bool, +) -> Result<()> { + let app_tauri_properties_path = config.project_dir().join("app").join("tauri.properties"); + + let mut app_tauri_properties = Vec::new(); + if let Some(version) = tauri_config.version.as_ref() { + app_tauri_properties.push(format!("tauri.android.versionName={version}")); + if tauri_config.bundle.android.auto_increment_version_code && !dev { + let last_version_code = std::fs::read_to_string(&app_tauri_properties_path) + .ok() + .and_then(|content| { + content + .lines() + .find(|line| line.starts_with("tauri.android.versionCode=")) + .and_then(|line| line.split('=').nth(1)) + .and_then(|s| s.trim().parse::().ok()) + }); + let new_version_code = last_version_code.map(|v| v.saturating_add(1)).unwrap_or(1); + app_tauri_properties.push(format!("tauri.android.versionCode={new_version_code}")); + } else if let Some(version_code) = tauri_config.bundle.android.version_code.as_ref() { + app_tauri_properties.push(format!("tauri.android.versionCode={version_code}")); + } else if let Ok(version) = Version::parse(version) { + let mut version_code = version.major * 1000000 + version.minor * 1000 + version.patch; + + if dev { + version_code = version_code.clamp(1, 2100000000); + } + + if version_code == 0 { + crate::error::bail!( + "You must change the `version` in `tauri.conf.json`. The default value `0.0.0` is not allowed for Android package and must be at least `0.0.1`." + ); + } else if version_code > 2100000000 { + crate::error::bail!( + "Invalid version code {}. Version code must be between 1 and 2100000000. You must change the `version` in `tauri.conf.json`.", + version_code + ); + } + + app_tauri_properties.push(format!("tauri.android.versionCode={version_code}")); + } + } + + if !app_tauri_properties.is_empty() { + let app_tauri_properties_content = format!( + "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n{}", + app_tauri_properties.join("\n") + ); + if std::fs::read_to_string(&app_tauri_properties_path) + .map(|o| o != app_tauri_properties_content) + .unwrap_or(true) + { + write(&app_tauri_properties_path, app_tauri_properties_content) + .context("failed to write tauri.properties")?; + } + } + + Ok(()) +} diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index 6d480475c..0c25453f3 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -84,6 +84,7 @@ "default": { "active": false, "android": { + "autoIncrementVersionCode": false, "minSdkVersion": 24 }, "createUpdaterArtifacts": false, @@ -2282,6 +2283,7 @@ "android": { "description": "Android configuration.", "default": { + "autoIncrementVersionCode": false, "minSdkVersion": 24 }, "allOf": [ @@ -3826,6 +3828,11 @@ "format": "uint32", "maximum": 2100000000.0, "minimum": 1.0 + }, + "autoIncrementVersionCode": { + "description": "Whether to automatically increment the `versionCode` on each build.\n\n - If `true`, the generator will try to read the last `versionCode` from\n `tauri.properties` and increment it by 1 for every build.\n - If `false` or not set, it falls back to `version_code` or semver-derived logic.\n\n Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository.", + "default": false, + "type": "boolean" } }, "additionalProperties": false diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index 89ec983c7..40d33292a 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -2929,6 +2929,16 @@ pub struct AndroidConfig { #[serde(alias = "version-code")] #[cfg_attr(feature = "schema", validate(range(min = 1, max = 2_100_000_000)))] pub version_code: Option, + + /// Whether to automatically increment the `versionCode` on each build. + /// + /// - If `true`, the generator will try to read the last `versionCode` from + /// `tauri.properties` and increment it by 1 for every build. + /// - If `false` or not set, it falls back to `version_code` or semver-derived logic. + /// + /// Note that to use this feature, you should remove `/tauri.properties` from `src-tauri/gen/android/app/.gitignore` so the current versionCode is committed to the repository. + #[serde(alias = "auto-increment-version-code", default)] + pub auto_increment_version_code: bool, } impl Default for AndroidConfig { @@ -2936,6 +2946,7 @@ impl Default for AndroidConfig { Self { min_sdk_version: default_min_sdk_version(), version_code: None, + auto_increment_version_code: false, } } }