Android: build on JDK 25 with Gradle 9.6.1, AGP 9.4.1, Kotlin 2.2.10
Android Studio now bundles JBR 25, which Gradle 8.14's embedded Kotlin can't parse. Follow Tauri's current template: Gradle 9.6.1, Kotlin 2.2.10, the builtInKotlin/newDsl/failOnMissingFiles compatibility flags, the compilerOptions DSL, and ExecOperations in BuildTask (project.exec is gone in Gradle 9). AGP goes to 9.4.1 rather than the template's 9.3.1. Release lint vital is turned off: AGP 9.3/9.4's lint crashes analysing the .gradle.kts scripts (findFirCompiledSymbol), failing every release build. The env scripts now accept JDK 17 through 25, preferring Studio's JBR.
This commit is contained in:
@@ -474,10 +474,10 @@ prints the three environment variables to export (`ANDROID_HOME`, `NDK_HOME`,
|
|||||||
> Android's system libsqlite3 either — the card schema needs FTS5 with the
|
> Android's system libsqlite3 either — the card schema needs FTS5 with the
|
||||||
> `trigram` tokenizer, which platform SQLite often lacks.
|
> `trigram` tokenizer, which platform SQLite often lacks.
|
||||||
|
|
||||||
> **Why `JAVA_HOME`?** Gradle/AGP reject very new JDKs. The script uses
|
> **Why `JAVA_HOME`?** The Android build (Gradle 9.6, AGP 9) runs on JDK 17
|
||||||
> Android Studio's bundled JetBrains Runtime when it's JDK 21 or 17, but recent
|
> through 25. The script prefers Android Studio's bundled JetBrains Runtime,
|
||||||
> Studio releases bundle JDK 25 and Fedora 44 no longer packages 21, so you may
|
> then the newest system JDK in that range, and skips anything newer than 25
|
||||||
> need `sudo dnf install temurin-21-jdk` (from the Adoptium repo).
|
> until it's been tested.
|
||||||
|
|
||||||
### Build and run
|
### Build and run
|
||||||
|
|
||||||
|
|||||||
@@ -36,24 +36,22 @@ _mtgc_android_env() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- JDK: Gradle/AGP reject very new JDKs. Studio's bundled JBR is preferred,
|
# --- JDK: Gradle 9.6 / AGP 9 run on 17 through 25 (25 is the newest tested).
|
||||||
# but newer Studio releases bundle JBR 25, so check each candidate's version.
|
# Prefer Studio's bundled JBR, then the newest system JDK in that range.
|
||||||
for d in \
|
for d in \
|
||||||
"$HOME/.local/share/JetBrains/Toolbox/apps/android-studio/jbr" \
|
"$HOME/.local/share/JetBrains/Toolbox/apps/android-studio/jbr" \
|
||||||
"$HOME/.local/share/JetBrains/Toolbox/apps/android-studio"/*/jbr \
|
"$HOME/.local/share/JetBrains/Toolbox/apps/android-studio"/*/jbr \
|
||||||
"$HOME/android-studio/jbr" \
|
"$HOME/android-studio/jbr" \
|
||||||
/opt/android-studio/jbr \
|
/opt/android-studio/jbr \
|
||||||
/usr/lib/jvm/java-21-openjdk /usr/lib/jvm/java-21-openjdk-* /usr/lib/jvm/temurin-21-jdk* \
|
$(ls -d /usr/lib/jvm/* 2>/dev/null | sort -rV)
|
||||||
/usr/lib/jvm/java-17-openjdk /usr/lib/jvm/java-17-openjdk-* /usr/lib/jvm/temurin-17-jdk*
|
|
||||||
do
|
do
|
||||||
[ -x "$d/bin/javac" ] || continue
|
[ -x "$d/bin/javac" ] || continue
|
||||||
case "$(sed -n 's/^JAVA_VERSION="\([0-9]*\).*/\1/p' "$d/release" 2>/dev/null)" in
|
local v
|
||||||
21 | 17) jdk="$d"; break ;;
|
v="$(sed -n 's/^JAVA_VERSION="\([0-9]*\).*/\1/p' "$d/release" 2>/dev/null)"
|
||||||
esac
|
if [ -n "$v" ] && [ "$v" -ge 17 ] && [ "$v" -le 25 ]; then jdk="$d"; break; fi
|
||||||
done
|
done
|
||||||
if [ -z "$jdk" ]; then
|
if [ -z "$jdk" ]; then
|
||||||
echo "android-env: no JDK 21/17 found (Gradle rejects newer ones, incl. Studio's JBR 25)" >&2
|
echo "android-env: no JDK 17–25 found (sudo dnf install java-25-openjdk-devel)" >&2
|
||||||
echo "android-env: sudo dnf install temurin-21-jdk" >&2
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -66,22 +66,20 @@ fi
|
|||||||
|
|
||||||
# --- 2. A JDK the Android Gradle Plugin actually accepts -------------------
|
# --- 2. A JDK the Android Gradle Plugin actually accepts -------------------
|
||||||
#
|
#
|
||||||
# Fedora's default JDK is newer than AGP/Gradle supports (a JDK 25 default fails
|
# Gradle 9.6 / AGP 9 run on JDK 17 through 25 (25 is the newest tested; an
|
||||||
# the Gradle build outright), so pin JAVA_HOME to 21 (or 17) for Android work.
|
# older Gradle choked on 25, so an untested newer JDK is skipped, not trusted).
|
||||||
#
|
#
|
||||||
# Android Studio's bundled JetBrains Runtime is preferred when it's 21 or 17,
|
# Android Studio's bundled JetBrains Runtime is preferred, then the newest
|
||||||
# but newer Studio releases bundle JBR 25, so every candidate's major version is
|
# system JDK in range. Every candidate's major version is checked (from its
|
||||||
# checked (from its `release` file) rather than assumed. Note we test for
|
# `release` file) rather than assumed. Note we test for bin/javac specifically —
|
||||||
# bin/javac specifically — a JRE-only or leftover-empty /usr/lib/jvm directory
|
# a JRE-only or leftover-empty /usr/lib/jvm directory must not count.
|
||||||
# must not count.
|
|
||||||
|
|
||||||
# Major version of the JDK at $1 if it's one Gradle accepts (21 or 17), else fail.
|
# Succeeds if $1 is a JDK of a version Gradle accepts (17 through 25).
|
||||||
jdk_ok() {
|
jdk_ok() {
|
||||||
[ -x "$1/bin/javac" ] || return 1
|
[ -x "$1/bin/javac" ] || return 1
|
||||||
case "$(sed -n 's/^JAVA_VERSION="\([0-9]*\).*/\1/p' "$1/release" 2>/dev/null)" in
|
local v
|
||||||
21 | 17) return 0 ;;
|
v="$(sed -n 's/^JAVA_VERSION="\([0-9]*\).*/\1/p' "$1/release" 2>/dev/null)"
|
||||||
*) return 1 ;;
|
[ -n "$v" ] && [ "$v" -ge 17 ] && [ "$v" -le 25 ]
|
||||||
esac
|
|
||||||
}
|
}
|
||||||
|
|
||||||
find_jdk() {
|
find_jdk() {
|
||||||
@@ -90,14 +88,13 @@ find_jdk() {
|
|||||||
echo "$ANDROID_JAVA_HOME"; return 0
|
echo "$ANDROID_JAVA_HOME"; return 0
|
||||||
fi
|
fi
|
||||||
# Android Studio's bundled JBR (Toolbox or standalone install locations),
|
# Android Studio's bundled JBR (Toolbox or standalone install locations),
|
||||||
# then a system JDK (Fedora or Adoptium packages) of an accepted version.
|
# then the newest system JDK (Fedora or Adoptium packages) in range.
|
||||||
for d in \
|
for d in \
|
||||||
"$HOME/.local/share/JetBrains/Toolbox/apps/android-studio/jbr" \
|
"$HOME/.local/share/JetBrains/Toolbox/apps/android-studio/jbr" \
|
||||||
"$HOME/.local/share/JetBrains/Toolbox/apps/android-studio"/*/jbr \
|
"$HOME/.local/share/JetBrains/Toolbox/apps/android-studio"/*/jbr \
|
||||||
"$HOME/android-studio/jbr" \
|
"$HOME/android-studio/jbr" \
|
||||||
/opt/android-studio/jbr \
|
/opt/android-studio/jbr \
|
||||||
/usr/lib/jvm/java-21-openjdk /usr/lib/jvm/java-21-openjdk-* /usr/lib/jvm/temurin-21-jdk* \
|
$(ls -d /usr/lib/jvm/* 2>/dev/null | sort -rV)
|
||||||
/usr/lib/jvm/java-17-openjdk /usr/lib/jvm/java-17-openjdk-* /usr/lib/jvm/temurin-17-jdk*
|
|
||||||
do
|
do
|
||||||
jdk_ok "$d" && { echo "$d"; return 0; }
|
jdk_ok "$d" && { echo "$d"; return 0; }
|
||||||
done
|
done
|
||||||
@@ -108,8 +105,8 @@ if JDK_PATH="$(find_jdk)"; then
|
|||||||
export JAVA_HOME="$JDK_PATH"
|
export JAVA_HOME="$JDK_PATH"
|
||||||
info "Android JDK: $JAVA_HOME ($("$JAVA_HOME/bin/java" -version 2>&1 | head -1))"
|
info "Android JDK: $JAVA_HOME ($("$JAVA_HOME/bin/java" -version 2>&1 | head -1))"
|
||||||
else
|
else
|
||||||
err "No JDK 21 or 17 found (Gradle rejects newer ones, including recent Android Studio's JBR 25)."
|
err "No JDK 17 through 25 found."
|
||||||
err "Fix: sudo dnf install temurin-21-jdk (Adoptium repo; Fedora 44 dropped java-21-openjdk)"
|
err "Fix: sudo dnf install java-25-openjdk-devel"
|
||||||
err "or: ANDROID_JAVA_HOME=/path/to/jdk ./scripts/android-setup.sh"
|
err "or: ANDROID_JAVA_HOME=/path/to/jdk ./scripts/android-setup.sh"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -262,7 +259,7 @@ Add these to your shell profile (or export them before building):
|
|||||||
|
|
||||||
export ANDROID_HOME="$ANDROID_HOME"
|
export ANDROID_HOME="$ANDROID_HOME"
|
||||||
export NDK_HOME="$NDK_DIR"
|
export NDK_HOME="$NDK_DIR"
|
||||||
export JAVA_HOME="$JAVA_HOME" # Gradle rejects the newer system default
|
export JAVA_HOME="$JAVA_HOME" # a JDK 17–25 that Gradle accepts
|
||||||
|
|
||||||
Then, from the project root:
|
Then, from the project root:
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
@@ -60,18 +61,35 @@ android {
|
|||||||
signingConfigs.findByName("release")?.let { signingConfig = it }
|
signingConfigs.findByName("release")?.let { signingConfig = it }
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
*fileTree(".") { include("**/*.pro") }
|
*fileTree(".") {
|
||||||
|
include("**/*.pro")
|
||||||
|
exclude("build/**")
|
||||||
|
}
|
||||||
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
|
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||||
.toList().toTypedArray()
|
.toList().toTypedArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
compileOptions {
|
||||||
jvmTarget = "1.8"
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
}
|
}
|
||||||
|
// AGP 9.3/9.4's lint crashes analysing the .gradle.kts scripts
|
||||||
|
// ("findFirCompiledSymbol only works on compiled declarations"), which
|
||||||
|
// fails every release build. Skip the release-time lint pass until a
|
||||||
|
// newer AGP fixes it.
|
||||||
|
lint {
|
||||||
|
checkReleaseBuilds = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget = JvmTarget.JVM_1_8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rust {
|
rust {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.11.0")
|
classpath("com.android.tools.build:gradle:9.4.1")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(gradleApi())
|
compileOnly(gradleApi())
|
||||||
implementation("com.android.tools.build:gradle:8.11.0")
|
implementation("com.android.tools.build:gradle:9.4.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,28 @@
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Properties
|
import javax.inject.Inject
|
||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
import org.apache.tools.ant.taskdefs.condition.Os
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
import org.gradle.api.logging.LogLevel
|
import org.gradle.api.logging.LogLevel
|
||||||
import org.gradle.api.tasks.Input
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.Optional
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.process.ExecOperations
|
||||||
|
|
||||||
|
abstract class BuildTask : DefaultTask() {
|
||||||
|
@get:Inject
|
||||||
|
abstract val execOperations: ExecOperations
|
||||||
|
|
||||||
open class BuildTask : DefaultTask() {
|
|
||||||
@Input
|
@Input
|
||||||
var rootDirRel: String? = null
|
var rootDirRel: String? = null
|
||||||
@Input
|
@Input
|
||||||
|
var projectDir: String? = null
|
||||||
|
// From local.properties; see RustPlugin.
|
||||||
|
@Input @Optional
|
||||||
|
var sdkDir: String? = null
|
||||||
|
@Input @Optional
|
||||||
|
var ndkDir: String? = null
|
||||||
|
@Input
|
||||||
var target: String? = null
|
var target: String? = null
|
||||||
@Input
|
@Input
|
||||||
var release: Boolean? = null
|
var release: Boolean? = null
|
||||||
@@ -51,27 +63,15 @@ open class BuildTask : DefaultTask() {
|
|||||||
val release = release ?: throw GradleException("release cannot be null")
|
val release = release ?: throw GradleException("release cannot be null")
|
||||||
val args = listOf("tauri", "android", "android-studio-script");
|
val args = listOf("tauri", "android", "android-studio-script");
|
||||||
|
|
||||||
// `project.exec` inherits whatever environment the Gradle daemon has —
|
execOperations.exec {
|
||||||
// which is whatever launched Android Studio, not necessarily a shell
|
workingDir(File(projectDir, rootDirRel))
|
||||||
// where ANDROID_HOME/NDK_HOME were exported. Without NDK_HOME, cargo's
|
|
||||||
// build script for `ring` (and rusqlite's bundled SQLite) can't find
|
|
||||||
// the NDK's clang and rustBuild*Debug/Release fails outright. Read
|
|
||||||
// local.properties instead — the same file AGP itself reads sdk.dir
|
|
||||||
// from — so this works regardless of how Studio was started.
|
|
||||||
val localProps = Properties().apply {
|
|
||||||
val f = project.rootProject.file("local.properties")
|
|
||||||
if (f.exists()) f.inputStream().use { load(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
project.exec {
|
|
||||||
workingDir(File(project.projectDir, rootDirRel))
|
|
||||||
executable(executable)
|
executable(executable)
|
||||||
localProps.getProperty("sdk.dir")?.let { environment("ANDROID_HOME", it) }
|
sdkDir?.let { environment("ANDROID_HOME", it) }
|
||||||
localProps.getProperty("ndk.dir")?.let { environment("NDK_HOME", it) }
|
ndkDir?.let { environment("NDK_HOME", it) }
|
||||||
args(args)
|
args(args)
|
||||||
if (project.logger.isEnabled(LogLevel.DEBUG)) {
|
if (logger.isEnabled(LogLevel.DEBUG)) {
|
||||||
args("-vv")
|
args("-vv")
|
||||||
} else if (project.logger.isEnabled(LogLevel.INFO)) {
|
} else if (logger.isEnabled(LogLevel.INFO)) {
|
||||||
args("-v")
|
args("-v")
|
||||||
}
|
}
|
||||||
if (release) {
|
if (release) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import com.android.build.api.dsl.ApplicationExtension
|
import com.android.build.api.dsl.ApplicationExtension
|
||||||
|
import java.util.Properties
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
@@ -46,6 +47,18 @@ open class RustPlugin : Plugin<Project> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The cargo build inherits whatever environment the Gradle daemon has —
|
||||||
|
// which is whatever launched Android Studio, not necessarily a shell
|
||||||
|
// where ANDROID_HOME/NDK_HOME were exported. Without NDK_HOME, cargo's
|
||||||
|
// build script for `ring` (and rusqlite's bundled SQLite) can't find
|
||||||
|
// the NDK's clang and rustBuild*Debug/Release fails outright. Read
|
||||||
|
// local.properties instead — the same file AGP itself reads sdk.dir
|
||||||
|
// from — so this works regardless of how Studio was started.
|
||||||
|
val localProps = Properties().apply {
|
||||||
|
val f = rootProject.file("local.properties")
|
||||||
|
if (f.exists()) f.inputStream().use { load(it) }
|
||||||
|
}
|
||||||
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
for (profile in listOf("debug", "release")) {
|
for (profile in listOf("debug", "release")) {
|
||||||
val profileCapitalized = profile.replaceFirstChar { it.uppercase() }
|
val profileCapitalized = profile.replaceFirstChar { it.uppercase() }
|
||||||
@@ -63,13 +76,16 @@ open class RustPlugin : Plugin<Project> {
|
|||||||
val targetName = targetPair.value
|
val targetName = targetPair.value
|
||||||
val targetArch = archList[targetPair.index]
|
val targetArch = archList[targetPair.index]
|
||||||
val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() }
|
val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() }
|
||||||
val targetBuildTask = project.tasks.maybeCreate(
|
val targetBuildTask = project.tasks.register(
|
||||||
"rustBuild$targetArchCapitalized$profileCapitalized",
|
"rustBuild$targetArchCapitalized$profileCapitalized",
|
||||||
BuildTask::class.java
|
BuildTask::class.java
|
||||||
).apply {
|
) {
|
||||||
group = TASK_GROUP
|
group = TASK_GROUP
|
||||||
description = "Build dynamic library in $profile mode for $targetArch"
|
description = "Build dynamic library in $profile mode for $targetArch"
|
||||||
rootDirRel = config.rootDirRel
|
rootDirRel = config.rootDirRel
|
||||||
|
projectDir = project.projectDir.path
|
||||||
|
sdkDir = localProps.getProperty("sdk.dir")
|
||||||
|
ndkDir = localProps.getProperty("ndk.dir")
|
||||||
target = targetName
|
target = targetName
|
||||||
release = profile == "release"
|
release = profile == "release"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,3 +22,9 @@ kotlin.code.style=official
|
|||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
|
|
||||||
|
# From Tauri's Gradle 9 template: plugin crates ship consumer-rules.pro
|
||||||
|
# references that don't exist, and still use the pre-AGP-9 Kotlin setup.
|
||||||
|
android.proguard.failOnMissingFiles=false
|
||||||
|
android.builtInKotlin=false
|
||||||
|
android.newDsl=false
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#Tue May 10 19:22:52 CST 2022
|
#Tue May 10 19:22:52 CST 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
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
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
Reference in New Issue
Block a user