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 <lucas@tauri.app>
This commit is contained in:
Bipin Pandey
2025-10-14 23:46:54 +05:45
committed by GitHub
parent 684791efa6
commit 3b4fac2017
9 changed files with 112 additions and 58 deletions

View File

@@ -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.

View File

@@ -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());

View File

@@ -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(())
}

View File

@@ -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

View File

@@ -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<BuiltApplica
let mut env = env(options.ci)?;
configure_cargo(&mut env, &config)?;
generate_tauri_properties(
&config,
tauri_config.lock().unwrap().as_ref().unwrap(),
false,
)?;
crate::build::setup(&interface, &mut build_options, tauri_config.clone(), true)?;
let installed_targets =

View File

@@ -16,8 +16,8 @@ use crate::{
},
interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions},
mobile::{
use_network_address_for_dev_url, write_options, CliOptions, DevChild, DevHost, DevProcess,
TargetDevice,
android::generate_tauri_properties, use_network_address_for_dev_url, write_options, CliOptions,
DevChild, DevHost, DevProcess, TargetDevice,
},
ConfigValue, Error, Result,
};
@@ -271,6 +271,8 @@ fn run_dev(
configure_cargo(&mut env, config)?;
generate_tauri_properties(config, tauri_config.lock().unwrap().as_ref().unwrap(), true)?;
let installed_targets =
crate::interface::rust::installation::installed_targets().unwrap_or_default();

View File

@@ -18,6 +18,7 @@ use cargo_mobile2::{
util::prompt,
};
use clap::{Parser, Subcommand};
use semver::Version;
use std::{
env::set_var,
fs::{create_dir, create_dir_all, read_dir, write},
@@ -620,3 +621,66 @@ fn configure_cargo(env: &mut Env, config: &AndroidConfig) -> 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::<u32>().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(())
}

View File

@@ -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

View File

@@ -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<u32>,
/// 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,
}
}
}