Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changes/auto-increment-android-version-code.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion crates/tauri-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
57 changes: 3 additions & 54 deletions crates/tauri-build/src/mobile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,21 @@
// 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");

Comment on lines +10 to 13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Tauri.properties generation regression 🐞 Bug ⛯ Reliability

• tauri-build no longer generates app/tauri.properties, but the Android template uses that file
  to set versionCode/versionName.
• If developers build the generated Android project directly via Gradle/Android Studio (without
  running tauri-cli Android build/dev first), Gradle will fall back to default versionCode=1 and
  versionName=1.0, and the values won’t be kept in sync with tauri.conf.json by tauri-build
  anymore.
Agent prompt
### Issue description
Android version metadata (`versionCode`/`versionName`) is read from `app/tauri.properties` in the Android template. This PR removes `tauri.properties` generation from `tauri-build` and only generates it in tauri-cli Android build/dev flows, which can break or change behavior for direct Gradle/Android Studio builds.

### Issue Context
- `build.gradle.kts` defaults to versionCode=1/versionName=1.0 if `tauri.properties` does not exist.
- After this change, `tauri-build` no longer produces `tauri.properties` at all.

### Fix Focus Areas
- crates/tauri-build/src/mobile.rs[10-62]
- crates/tauri-cli/templates/mobile/android/app/build.gradle.kts[11-28]
- crates/tauri-cli/src/mobile/android/build.rs[178-188]
- crates/tauri-cli/src/mobile/android/dev.rs[268-276]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

let mut gradle_settings =
"// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n".to_string();
let mut app_build_gradle = "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
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();
Expand Down Expand Up @@ -54,61 +49,15 @@ 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")?;

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(())
}
7 changes: 7 additions & 0 deletions crates/tauri-cli/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"default": {
"active": false,
"android": {
"autoIncrementVersionCode": false,
"minSdkVersion": 24
},
"createUpdaterArtifacts": false,
Expand Down Expand Up @@ -2282,6 +2283,7 @@
"android": {
"description": "Android configuration.",
"default": {
"autoIncrementVersionCode": false,
"minSdkVersion": 24
},
"allOf": [
Expand Down Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion crates/tauri-cli/src/mobile/android/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
)?;
Comment on lines +181 to +185

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. tauri_config double unwrap() 📘 Rule violation ⛯ Reliability

• New code calls tauri_config.lock().unwrap().as_ref().unwrap(), which can panic at runtime if the
  mutex is poisoned or the inner value is None.
• This violates the requirement to avoid panics/unwrap() for fallible operations and instead
  propagate errors via Result.
• A panic here would crash the CLI during Android build/dev flows rather than providing actionable
  error context.
Agent prompt
## Issue description
Android build/dev code uses `unwrap()` on `tauri_config.lock()` and on the inner `Option`, which can panic and crash the CLI.

## Issue Context
Per compliance requirements, fallible operations should not panic; they should return `Result` with meaningful context.

## Fix Focus Areas
- crates/tauri-cli/src/mobile/android/build.rs[181-185]
- crates/tauri-cli/src/mobile/android/dev.rs[274-274]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


crate::build::setup(&interface, &mut build_options, tauri_config.clone(), true)?;

let installed_targets =
Expand Down
6 changes: 4 additions & 2 deletions crates/tauri-cli/src/mobile/android/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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();

Expand Down
64 changes: 64 additions & 0 deletions crates/tauri-cli/src/mobile/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -620,3 +621,66 @@ fn configure_cargo(env: &mut Env, config: &AndroidConfig) -> Result<()> {

Ok(())
}

pub fn generate_tauri_properties(
config: &AndroidConfig,
tauri_config: &TauriConfig,
dev: bool,
) -> Result<()> {
Comment on lines +625 to +629

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. generate_tauri_properties undocumented 📘 Rule violation ✧ Quality

• A new public API pub fn generate_tauri_properties(...) was introduced without any ///
  documentation comments.
• This violates the requirement that public functions/methods include documentation explaining
  purpose, parameters, and behavior.
• Lack of docs makes the new behavior (including auto-increment semantics) harder to use correctly.
Agent prompt
## Issue description
The new public function `generate_tauri_properties` lacks required documentation comments.

## Issue Context
Public APIs must be documented so consumers understand behavior and configuration implications.

## Fix Focus Areas
- crates/tauri-cli/src/mobile/android/mod.rs[625-629]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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 {
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}"));
Comment on lines +635 to +646

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Auto-increment exceeds max 🐞 Bug ✓ Correctness

• autoIncrementVersionCode increments the previous versionCode read from tauri.properties but
  never validates/clamps to the documented maximum (2,100,000,000).
• Once a repo reaches versionCode=2100000000, the next build will write 2100000001 (still a
  valid u32) which violates the schema/Play Store constraint and can cause Gradle/Play publishing
  failures.
Agent prompt
### Issue description
`autoIncrementVersionCode` increments the last persisted `versionCode` from `tauri.properties`, but does not enforce the documented maximum of 2,100,000,000. This can write `versionCode` values that Gradle/Play Store reject.

### Issue Context
- Schema documents `versionCode` maximum: 2,100,000,000.
- Current logic uses `saturating_add(1)` which only prevents `u32` overflow (up to `u32::MAX`), not Android’s max.

### Fix Focus Areas
- crates/tauri-cli/src/mobile/android/mod.rs[635-646]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

} 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 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
);
}

if dev {
version_code = version_code.clamp(1, 2100000000);
}

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| app_tauri_properties_content != o)
.unwrap_or(true)
{
write(&app_tauri_properties_path, app_tauri_properties_content)
.context("failed to write tauri.properties")?;
}
}

Ok(())
}
7 changes: 7 additions & 0 deletions crates/tauri-schema-generator/schemas/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"default": {
"active": false,
"android": {
"autoIncrementVersionCode": false,
"minSdkVersion": 24
},
"createUpdaterArtifacts": false,
Expand Down Expand Up @@ -2282,6 +2283,7 @@
"android": {
"description": "Android configuration.",
"default": {
"autoIncrementVersionCode": false,
"minSdkVersion": 24
},
"allOf": [
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions crates/tauri-utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2929,13 +2929,24 @@ 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 {
fn default() -> Self {
Self {
min_sdk_version: default_min_sdk_version(),
version_code: None,
auto_increment_version_code: false,
}
}
}
Expand Down