Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e2e585a
fix(android): update template to use gradle 9 (#15828)
Legend-Master Aug 10, 2026
448d39e
fix: menu related commands can panic (#15860)
Legend-Master Aug 11, 2026
de5e25b
Merge upstream tauri-apps/tauri dev into OlympusLedgerOrg/tauri dev
OlympusLedgerOrg Aug 11, 2026
90042ee
fix(android): keep RuntimeVisibleAnnotations/AnnotationDefault in con…
OlympusLedgerOrg Aug 11, 2026
df64d7d
fix: apply remaining CodeRabbit auto-fixes
OlympusLedgerOrg Aug 11, 2026
f53eed2
fix(ci): add missing changeset tag and restore gradlew exec bit
OlympusLedgerOrg Aug 11, 2026
baa5a36
Merge branch 'dev' into sync-upstream-dev-2026-08-11
OlympusLedgerOrg Aug 11, 2026
89b0323
fix(example): set AppCompat theme, fix clean task, remove conflicting…
OlympusLedgerOrg Aug 11, 2026
9ae6221
fix(example): fix README formatting, disable AGP 9 built-in Kotlin
OlympusLedgerOrg Aug 11, 2026
69d03df
fix(example): dontwarn missing errorprone annotation from androidx.test
OlympusLedgerOrg Aug 11, 2026
94aacf9
fix(example): wire proguard-rules.pro into testProguardFiles too
OlympusLedgerOrg Aug 11, 2026
d41113f
ci(android): bound r8 plugin discovery job with timeout-minutes
claude Aug 11, 2026
118dac9
ci(android): capture diagnostics for r8 plugin discovery hang
claude Aug 11, 2026
646087e
ci(android): print r8 plugin discovery diagnostics into the job log
claude Aug 11, 2026
25cece3
fix(example): keep androidx.tracing.Trace in r8 plugin discovery test…
claude Aug 12, 2026
db67836
fix(example): keep kotlin.jvm.internal.** in r8 plugin discovery test…
claude Aug 12, 2026
c07122e
fix(example): keep the whole kotlin stdlib in r8 plugin discovery tes…
claude Aug 12, 2026
2dd4b05
fix: apply CodeRabbit auto-fixes
coderabbitai[bot] Aug 12, 2026
a19ef86
fix(example): keep Jackson ObjectMapper stable across app/test APK R8…
claude Aug 12, 2026
217589b
fix(android): keep Invoke's constructor in consumer-rules.pro
claude Aug 12, 2026
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
5 changes: 5 additions & 0 deletions .changes/android-consumer-proguard-keepattributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
tauri: patch:bug
---
Comment thread
coderabbitai[bot] marked this conversation as resolved.

On Android, keep `RuntimeVisibleAnnotations` and `AnnotationDefault` in the `tauri-android` consumer ProGuard/R8 rules. Without them, R8 could strip the annotation metadata that reflective plugin discovery relies on (`TauriPlugin.permissions`, `@Command`/`@ActivityCallback`/`@PermissionCallback` method lookup) in a minified release build, even though the class and method `-keep` rules on their own left the plugin's classes and methods in place. Added `examples/android-r8-plugin-discovery`, an instrumented-test module that builds a minified "release" app depending only on the shipped consumer rules and asserts plugin discovery still works.
23 changes: 23 additions & 0 deletions .changes/android-consumer-proguard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
tauri: minor:deps
tauri-cli: minor:deps
"tauri-apps/cli": minor:deps
---

On Android, fix missing `consumer-rules.pro` file in the template.

**IMPORTANT**: For plugin authors, update your `build.gradle.kts` file to remove the

```kotlin
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
```

section and rename your `proguard-rules.pro` to `consumer-rules.pro` to match the `consumerProguardFiles("consumer-rules.pro")` in the template.
8 changes: 8 additions & 0 deletions .changes/android-gradle-9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
tauri: minor:deps
tauri-build: minor:deps
tauri-cli: minor:deps
"tauri-apps/cli": minor:deps
---

On Android, updated the template to use Gradle v9.6.1 (`com.android.tools.build:gradle` v9.3.1) and Kotlin v2.2. Use `tauri android init` to apply the change.
9 changes: 9 additions & 0 deletions .changes/menu-command-panic-on-wrong-input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
tauri: minor:bug
tauri-macros: minor:bug
---

Fix menu-related commands can panic if called with invalid menu types through `invoke` directly.
Comment thread
OlympusLedgerOrg marked this conversation as resolved.

- The internal `do_menu_item!` macro now returns `Err(crate::Error::UnexpectedMenuKind)` instead of `unreachable!()`
- Added a new error type `tauri::Error::UnexpectedMenuKind`
115 changes: 115 additions & 0 deletions .github/workflows/test-android-r8-plugin-discovery.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright 2019-2024 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT

# Regression test for crates/tauri/mobile/android/consumer-rules.pro: proves a
# minified ("release", isMinifyEnabled = true) app that only depends on the
# tauri-android library's own consumer ProGuard rules can still reflectively
# discover and invoke a @TauriPlugin's @Command / @ActivityCallback /
# @PermissionCallback methods and read TauriPlugin.permissions. See
# examples/android-r8-plugin-discovery/README.md.
#
# This must run on a real device/emulator: `testBuildType = "release"` means
# the instrumented test below installs and runs the actual R8-minified APK,
# which is the only way to observe R8 stripping RuntimeVisibleAnnotations /
# AnnotationDefault -- a JVM unit test never sees R8 output at all.
name: test android r8 plugin discovery

permissions:
contents: read

on:
pull_request:
paths:
- '.github/workflows/test-android-r8-plugin-discovery.yml'
- 'crates/tauri/mobile/android/consumer-rules.pro'
- 'crates/tauri/mobile/android/src/main/java/app/tauri/annotation/**'
- 'crates/tauri/mobile/android/src/main/java/app/tauri/plugin/**'
- 'crates/tauri/mobile/android/build.gradle.kts'
- 'examples/android-r8-plugin-discovery/**'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
# KVM-backed runner required by the emulator action.
runs-on: ubuntu-latest
# Bounds a hung/deadlocked instrumented test run (e.g. connectedReleaseAndroidTest
# never returning) to a fast failure instead of GitHub's 360-minute default.
timeout-minutes: 15

steps:
- uses: actions/checkout@v7
with:
persist-credentials: false

- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 25
cache: gradle

- name: setup Android SDK
uses: android-actions/setup-android@v3

- name: enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: run minified-release plugin discovery test
# Leaves headroom inside the job's 15-minute budget for the always()
# diagnostics upload below to run after this step is cancelled.
timeout-minutes: 12
uses: reactivecircus/android-emulator-runner@v2
with:
working-directory: examples/android-r8-plugin-discovery
api-level: 34
target: google_apis
arch: x86_64
disable-animations: true
# connectedAndroidTest prints nothing to the Gradle console between
# task start and completion by default, which previously made a
# hang indistinguishable from a slow run. --info surfaces adb
# install/instrumentation progress, and the background logcat
# capture records what the device/app were doing if it hangs again.
script: |
"$ANDROID_HOME/platform-tools/adb" logcat -c
"$ANDROID_HOME/platform-tools/adb" logcat -v threadtime > "$GITHUB_WORKSPACE/r8-plugin-discovery-logcat.txt" &
logcat_pid=$!
trap 'kill "$logcat_pid" 2>/dev/null; wait "$logcat_pid" 2>/dev/null || true' EXIT
./gradlew :app:connectedReleaseAndroidTest --stacktrace --info

# Artifact downloads require reaching Azure Blob Storage, which some CI
# log viewers can't do -- print the diagnostics into the job log itself
# (app-relevant logcat lines + any XML test results) so they're always
# readable from the run's own log output, even after a cancellation.
- name: print diagnostics
if: always()
run: |
echo "::group::logcat (com.example.r8plugindiscovery / runtime / instrumentation lines)"
grep -E 'com\.example\.r8plugindiscovery|AndroidRuntime|FATAL EXCEPTION|ActivityManager|TestRunner|ANR |Instrumentation' \
r8-plugin-discovery-logcat.txt 2>/dev/null | tail -n 400 || echo "(no logcat file captured)"
echo "::endgroup::"
echo "::group::androidTest XML results"
for d in examples/android-r8-plugin-discovery/app/build/outputs/androidTest-results \
examples/android-r8-plugin-discovery/app/build/reports/androidTests; do
[ -d "$d" ] && find "$d" -type f -exec sh -c 'echo "--- $1 ---"; cat "$1"' sh {} \;
done
echo "::endgroup::"

- name: upload diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: r8-plugin-discovery-diagnostics
if-no-files-found: ignore
retention-days: 5
path: |
r8-plugin-discovery-logcat.txt
examples/android-r8-plugin-discovery/app/build/reports/androidTests/connected/**
examples/android-r8-plugin-discovery/app/build/outputs/androidTest-results/connected/**
18 changes: 5 additions & 13 deletions .github/workflows/test-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ jobs:
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17
cache: 'gradle'
java-version: 25
cache: gradle

- name: Setup NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r25b
ndk-version: r29
local-cache: true

# TODO check after https://github.com/nttld/setup-ndk/issues/518 is fixed
Expand All @@ -88,16 +88,8 @@ jobs:

- uses: Swatinem/rust-cache@v2

- name: build CLI
run: cargo build --manifest-path ./crates/tauri-cli/Cargo.toml

- name: move CLI to cargo bin dir
if: matrix.platform != 'windows-latest'
run: mv ./target/debug/cargo-tauri $HOME/.cargo/bin

- name: move CLI to cargo bin dir
if: matrix.platform == 'windows-latest'
run: mv ./target/debug/cargo-tauri.exe $HOME/.cargo/bin
- name: build and install CLI
run: cargo install tauri-cli --path ./crates/tauri-cli/ --locked --debug

- run: pnpm i --frozen-lockfile

Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/tauri-build/src/mobile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ pub fn generate_gradle_files(project_dir: PathBuf) -> Result<()> {
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
val implementation = configurations.getByName(\"implementation\")
dependencies {
implementation(\"androidx.lifecycle:lifecycle-process:2.10.0\")"
.to_string();
Expand Down
17 changes: 10 additions & 7 deletions crates/tauri-cli/templates/mobile/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.util.Properties
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
id("com.android.application")
Expand Down Expand Up @@ -37,17 +37,20 @@ android {
isJniDebuggable = true
isMinifyEnabled = false
packaging {
{{~#each abi-list}}
{{#each abi-list}}
jniLibs.keepDebugSymbols.add("*/{{this}}/*.so")
{{/each}}
}
}
getByName("release") {
isMinifyEnabled = true
optimization {
enable = true
}
proguardFiles(
*fileTree(".") { include("**/*.pro") }
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
.toList().toTypedArray()
*fileTree(".") {
include("**/*.pro")
exclude("build/**")
}.files.toTypedArray()
)
}
}
Expand Down Expand Up @@ -85,4 +88,4 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
}

apply(from = "tauri.build.gradle.kts")
apply(from = file("tauri.build.gradle.kts"))
4 changes: 2 additions & 2 deletions crates/tauri-cli/templates/mobile/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.11.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
classpath("com.android.tools.build:gradle:9.3.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10")
{{~#each android-project-dependencies}}
classpath("{{this}}"){{/each}}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ repositories {

dependencies {
compileOnly(gradleApi())
implementation("com.android.tools.build:gradle:8.11.0")
implementation("com.android.tools.build:gradle:9.3.1")
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ import org.gradle.api.GradleException
import org.gradle.api.logging.LogLevel
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import javax.inject.Inject
import org.gradle.process.ExecOperations

abstract class BuildTask : DefaultTask() {
@get:Inject
abstract val execOperations: ExecOperations

open class BuildTask : DefaultTask() {
@Input
var rootDirRel: String? = null
@Input
var projectDir: String? = null
@Input
var target: String? = null
@Input
var release: Boolean? = null
Expand All @@ -27,7 +34,7 @@ open class BuildTask : DefaultTask() {
"$executable.cmd",
"$executable.bat",
)

var lastException: Exception = e
for (fallback in fallbacks) {
try {
Expand All @@ -50,13 +57,13 @@ open class BuildTask : DefaultTask() {
val release = release ?: throw GradleException("release cannot be null")
val args = listOf({{quote-and-join tauri-binary-args}});

project.exec {
workingDir(File(project.projectDir, rootDirRel))
execOperations.exec {
workingDir(File(projectDir, rootDirRel))
executable(executable)
args(args)
if (project.logger.isEnabled(LogLevel.DEBUG)) {
if (logger.isEnabled(LogLevel.DEBUG)) {
args("-vv")
} else if (project.logger.isEnabled(LogLevel.INFO)) {
} else if (logger.isEnabled(LogLevel.INFO)) {
args("-v")
}
if (release) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,14 @@ open class RustPlugin : Plugin<Project> {
val targetName = targetPair.value
val targetArch = archList[targetPair.index]
val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() }
val targetBuildTask = project.tasks.maybeCreate(
val targetBuildTask = project.tasks.register(
"rustBuild$targetArchCapitalized$profileCapitalized",
BuildTask::class.java
).apply {
) {
group = TASK_GROUP
description = "Build dynamic library in $profile mode for $targetArch"
rootDirRel = config.rootDirRel
projectDir = project.projectDir.path
target = targetName
release = profile == "release"
}
Expand Down
20 changes: 11 additions & 9 deletions crates/tauri-cli/templates/mobile/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.configuration-cache=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonFinalResIds=false

# Our old plugin template had a bug that caused missing `consumer-rules.pro` files
# so we have to disable it for now.
# See https://github.com/tauri-apps/tauri/pull/15828#issuecomment-5199937796
android.proguard.failOnMissingFiles=false

# Keeping Gradle 9 compatible with current plugins
# TODO: Remove this in v3
android.builtInKotlin=false
android.newDsl=false
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Tue May 10 19:22:52 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
Loading
Loading