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)?;
}
Comment on lines 501 to 503

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. Missing android version properties 🐞 Bug ✓ Correctness

tauri-build::try_build no longer generates app/tauri.properties, so Android builds that don’t
run tauri-cli’s generate_tauri_properties will ignore tauri.conf.json version/versionCode and
fall back to the Gradle template defaults (1/1.0). This silently produces wrong Android
versionCode/versionName in those build flows.
Agent Prompt
### Issue description
Android versionCode/versionName are sourced from `app/tauri.properties` in the Android Gradle template. This PR removed `tauri.properties` generation from `tauri-build`, making correct versioning dependent on tauri-cli calling `generate_tauri_properties`, which does not cover non-CLI build flows.

### Issue Context
`tauri_build::try_build` still parses the Tauri config and still runs during Cargo build scripts, but now only writes `tauri.settings.gradle` and `tauri.build.gradle.kts`. The Gradle template defaults to versionCode=1/versionName=1.0 when `tauri.properties` is missing.

### Fix Focus Areas
- crates/tauri-build/src/lib.rs[473-503]
- crates/tauri-build/src/mobile.rs[10-62]
- crates/tauri-cli/templates/mobile/android/app/build.gradle.kts[11-28]

### Implementation direction
- Reintroduce `tauri.properties` generation in `tauri-build` using the parsed `Config` (including the new `auto_increment_version_code` behavior), or
- Add a `tauri-build` helper (e.g., `mobile::generate_tauri_properties(project_dir, config, dev)`), called from `try_build()` when `TAURI_ANDROID_PROJECT_PATH` is present.
- Ensure behavior matches the documented limits and dev/release expectations.

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


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

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

2. tauri_config.lock() uses unwrap 📘 Rule violation ⛯ Reliability

The new Android build path uses unwrap() on a mutex lock and on an Option, which can panic and
terminate the process. This violates the requirement to avoid panicking for fallible operations and
to return/propagate errors via Result.
Agent Prompt
## Issue description
New code in Android build uses `tauri_config.lock().unwrap().as_ref().unwrap()`, which can panic (poisoned mutex / missing config). Compliance requires returning/propagating errors as `Result` instead of panicking.

## Issue Context
`generate_tauri_properties` already returns `Result<()>`, so callers can (and should) propagate lock/option access failures via `?` and meaningful errors rather than `unwrap()`.

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

ⓘ 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

1. generate_tauri_properties lacks docs 📘 Rule violation ✓ Correctness

A new pub fn generate_tauri_properties is introduced without Rust documentation comments. This
violates the requirement to document public APIs for usability and generated docs.
Agent Prompt
## Issue description
A new public function `generate_tauri_properties` was added without Rust doc comments (`///`). Public APIs must be documented.

## Issue Context
This function is `pub` and will appear in generated documentation; add docs describing what it generates, what inputs mean (`config`, `tauri_config`, `dev`), and key behavior (auto-increment logic, reading/writing `tauri.properties`).

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

4. Unbounded auto-increment versioncode 🐞 Bug ✓ Correctness

With autoIncrementVersionCode=true, generate_tauri_properties increments the previous
versionCode without enforcing the documented maximum (2,100,000,000). This can create an
out-of-policy versionCode (Play Store rejection) and can eventually exceed Kotlin/Gradle Int
parsing limits, breaking the build.
Agent Prompt
### Issue description
`autoIncrementVersionCode` increments versionCode without validating the new value against the documented maximum (2,100,000,000) and without considering Gradle/Kotlin `Int` parsing constraints.

### Issue Context
The value is written into `app/tauri.properties`, then the Android Gradle template reads it via `.toInt()`.

### Fix Focus Areas
- crates/tauri-cli/src/mobile/android/mod.rs[635-646]
- crates/tauri-utils/src/config.rs[2917-2931]
- crates/tauri-cli/templates/mobile/android/app/build.gradle.kts[21-28]

### Implementation direction
- Parse last_version_code as `u32`.
- If last_version_code >= 2_100_000_000, return an error explaining the limit and how to reset/override.
- Use `checked_add(1)` instead of `saturating_add(1)`.
- Optionally also reject values > `i32::MAX` (2_147_483_647) because Gradle uses `toInt()`.

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

Comment on lines +636 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

5. Auto-increment resets to 1 🐞 Bug ⛯ Reliability

If tauri.properties is missing/unreadable or the tauri.android.versionCode= line can’t be
parsed, autoIncrementVersionCode silently sets versionCode to 1. This can decrease the versionCode
relative to prior releases and block upgrades/publishing without any warning.
Agent Prompt
### Issue description
`autoIncrementVersionCode` silently falls back to versionCode=1 when `tauri.properties` is missing/unreadable/malformed. This is especially likely because the Android template ignores `tauri.properties` by default.

### Issue Context
Silent resets can cause non-monotonic versionCode and publishing/update failures.

### Fix Focus Areas
- crates/tauri-cli/src/mobile/android/mod.rs[636-646]
- crates/tauri-cli/templates/mobile/android/app/.gitignore[4-6]

### Implementation direction
- If `tauri.properties` is missing and auto-increment is enabled:
  - Either: bail with a clear error telling the user to commit/unignore the file or set `bundle.android.versionCode` explicitly.
  - Or: initialize from `bundle.android.version_code` if present, else from semver-derived versionCode, then write that value.
- If file exists but is unreadable/unparseable: return an error (don’t swallow with `.ok()`).
- Consider emitting a log message describing the chosen versionCode source.

ⓘ 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