diff --git a/README.md b/README.md index 7e63d07..f42f27e 100644 --- a/README.md +++ b/README.md @@ -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 > `trigram` tokenizer, which platform SQLite often lacks. -> **Why `JAVA_HOME`?** Gradle/AGP reject very new JDKs. The script uses -> Android Studio's bundled JetBrains Runtime when it's JDK 21 or 17, but recent -> Studio releases bundle JDK 25 and Fedora 44 no longer packages 21, so you may -> need `sudo dnf install temurin-21-jdk` (from the Adoptium repo). +> **Why `JAVA_HOME`?** The Android build (Gradle 9.6, AGP 9) runs on JDK 17 +> through 25. The script prefers Android Studio's bundled JetBrains Runtime, +> then the newest system JDK in that range, and skips anything newer than 25 +> until it's been tested. ### Build and run diff --git a/scripts/android-env.sh b/scripts/android-env.sh index 9f67a12..00dd903 100755 --- a/scripts/android-env.sh +++ b/scripts/android-env.sh @@ -36,24 +36,22 @@ _mtgc_android_env() { return 1 fi - # --- JDK: Gradle/AGP reject very new JDKs. Studio's bundled JBR is preferred, - # but newer Studio releases bundle JBR 25, so check each candidate's version. + # --- JDK: Gradle 9.6 / AGP 9 run on 17 through 25 (25 is the newest tested). + # Prefer Studio's bundled JBR, then the newest system JDK in that range. for d in \ "$HOME/.local/share/JetBrains/Toolbox/apps/android-studio/jbr" \ "$HOME/.local/share/JetBrains/Toolbox/apps/android-studio"/*/jbr \ "$HOME/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* \ - /usr/lib/jvm/java-17-openjdk /usr/lib/jvm/java-17-openjdk-* /usr/lib/jvm/temurin-17-jdk* + $(ls -d /usr/lib/jvm/* 2>/dev/null | sort -rV) do [ -x "$d/bin/javac" ] || continue - case "$(sed -n 's/^JAVA_VERSION="\([0-9]*\).*/\1/p' "$d/release" 2>/dev/null)" in - 21 | 17) jdk="$d"; break ;; - esac + local v + v="$(sed -n 's/^JAVA_VERSION="\([0-9]*\).*/\1/p' "$d/release" 2>/dev/null)" + if [ -n "$v" ] && [ "$v" -ge 17 ] && [ "$v" -le 25 ]; then jdk="$d"; break; fi done 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: sudo dnf install temurin-21-jdk" >&2 + echo "android-env: no JDK 17–25 found (sudo dnf install java-25-openjdk-devel)" >&2 return 1 fi diff --git a/scripts/android-setup.sh b/scripts/android-setup.sh index 6ee279d..124aef7 100755 --- a/scripts/android-setup.sh +++ b/scripts/android-setup.sh @@ -66,22 +66,20 @@ fi # --- 2. A JDK the Android Gradle Plugin actually accepts ------------------- # -# Fedora's default JDK is newer than AGP/Gradle supports (a JDK 25 default fails -# the Gradle build outright), so pin JAVA_HOME to 21 (or 17) for Android work. +# Gradle 9.6 / AGP 9 run on JDK 17 through 25 (25 is the newest tested; an +# 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, -# but newer Studio releases bundle JBR 25, so every candidate's major version is -# checked (from its `release` file) rather than assumed. Note we test for -# bin/javac specifically — a JRE-only or leftover-empty /usr/lib/jvm directory -# must not count. +# Android Studio's bundled JetBrains Runtime is preferred, then the newest +# system JDK in range. Every candidate's major version is checked (from its +# `release` file) rather than assumed. Note we test for bin/javac specifically — +# a JRE-only or leftover-empty /usr/lib/jvm directory 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() { [ -x "$1/bin/javac" ] || return 1 - case "$(sed -n 's/^JAVA_VERSION="\([0-9]*\).*/\1/p' "$1/release" 2>/dev/null)" in - 21 | 17) return 0 ;; - *) return 1 ;; - esac + local v + v="$(sed -n 's/^JAVA_VERSION="\([0-9]*\).*/\1/p' "$1/release" 2>/dev/null)" + [ -n "$v" ] && [ "$v" -ge 17 ] && [ "$v" -le 25 ] } find_jdk() { @@ -90,14 +88,13 @@ find_jdk() { echo "$ANDROID_JAVA_HOME"; return 0 fi # 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 \ "$HOME/.local/share/JetBrains/Toolbox/apps/android-studio/jbr" \ "$HOME/.local/share/JetBrains/Toolbox/apps/android-studio"/*/jbr \ "$HOME/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* \ - /usr/lib/jvm/java-17-openjdk /usr/lib/jvm/java-17-openjdk-* /usr/lib/jvm/temurin-17-jdk* + $(ls -d /usr/lib/jvm/* 2>/dev/null | sort -rV) do jdk_ok "$d" && { echo "$d"; return 0; } done @@ -108,8 +105,8 @@ if JDK_PATH="$(find_jdk)"; then export JAVA_HOME="$JDK_PATH" info "Android JDK: $JAVA_HOME ($("$JAVA_HOME/bin/java" -version 2>&1 | head -1))" else - err "No JDK 21 or 17 found (Gradle rejects newer ones, including recent Android Studio's JBR 25)." - err "Fix: sudo dnf install temurin-21-jdk (Adoptium repo; Fedora 44 dropped java-21-openjdk)" + err "No JDK 17 through 25 found." + err "Fix: sudo dnf install java-25-openjdk-devel" err "or: ANDROID_JAVA_HOME=/path/to/jdk ./scripts/android-setup.sh" exit 1 fi @@ -262,7 +259,7 @@ Add these to your shell profile (or export them before building): export ANDROID_HOME="$ANDROID_HOME" 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: diff --git a/src-tauri/gen/android/app/build.gradle.kts b/src-tauri/gen/android/app/build.gradle.kts index f1bb66a..e1cc173 100644 --- a/src-tauri/gen/android/app/build.gradle.kts +++ b/src-tauri/gen/android/app/build.gradle.kts @@ -1,5 +1,6 @@ import java.io.FileInputStream import java.util.Properties +import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("com.android.application") @@ -60,18 +61,35 @@ android { signingConfigs.findByName("release")?.let { signingConfig = it } isMinifyEnabled = true proguardFiles( - *fileTree(".") { include("**/*.pro") } + *fileTree(".") { + include("**/*.pro") + exclude("build/**") + } .plus(getDefaultProguardFile("proguard-android-optimize.txt")) .toList().toTypedArray() ) } } - kotlinOptions { - jvmTarget = "1.8" + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } buildFeatures { 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 { diff --git a/src-tauri/gen/android/build.gradle.kts b/src-tauri/gen/android/build.gradle.kts index 607240b..e91063f 100644 --- a/src-tauri/gen/android/build.gradle.kts +++ b/src-tauri/gen/android/build.gradle.kts @@ -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.4.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10") } } diff --git a/src-tauri/gen/android/buildSrc/build.gradle.kts b/src-tauri/gen/android/buildSrc/build.gradle.kts index 5c55bba..4c1797e 100644 --- a/src-tauri/gen/android/buildSrc/build.gradle.kts +++ b/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -18,6 +18,6 @@ repositories { dependencies { compileOnly(gradleApi()) - implementation("com.android.tools.build:gradle:8.11.0") + implementation("com.android.tools.build:gradle:9.4.1") } diff --git a/src-tauri/gen/android/buildSrc/src/main/java/com/garrettmills/mtgc/kotlin/BuildTask.kt b/src-tauri/gen/android/buildSrc/src/main/java/com/garrettmills/mtgc/kotlin/BuildTask.kt index 2c3c5ba..e655c17 100644 --- a/src-tauri/gen/android/buildSrc/src/main/java/com/garrettmills/mtgc/kotlin/BuildTask.kt +++ b/src-tauri/gen/android/buildSrc/src/main/java/com/garrettmills/mtgc/kotlin/BuildTask.kt @@ -1,16 +1,28 @@ import java.io.File -import java.util.Properties +import javax.inject.Inject import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.logging.LogLevel import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional 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 var rootDirRel: String? = null @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 @Input var release: Boolean? = null @@ -51,27 +63,15 @@ open class BuildTask : DefaultTask() { val release = release ?: throw GradleException("release cannot be null") val args = listOf("tauri", "android", "android-studio-script"); - // `project.exec` 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 = project.rootProject.file("local.properties") - if (f.exists()) f.inputStream().use { load(it) } - } - - project.exec { - workingDir(File(project.projectDir, rootDirRel)) + execOperations.exec { + workingDir(File(projectDir, rootDirRel)) executable(executable) - localProps.getProperty("sdk.dir")?.let { environment("ANDROID_HOME", it) } - localProps.getProperty("ndk.dir")?.let { environment("NDK_HOME", it) } + sdkDir?.let { environment("ANDROID_HOME", it) } + ndkDir?.let { environment("NDK_HOME", it) } 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) { diff --git a/src-tauri/gen/android/buildSrc/src/main/java/com/garrettmills/mtgc/kotlin/RustPlugin.kt b/src-tauri/gen/android/buildSrc/src/main/java/com/garrettmills/mtgc/kotlin/RustPlugin.kt index 4aa7fca..bd478c8 100644 --- a/src-tauri/gen/android/buildSrc/src/main/java/com/garrettmills/mtgc/kotlin/RustPlugin.kt +++ b/src-tauri/gen/android/buildSrc/src/main/java/com/garrettmills/mtgc/kotlin/RustPlugin.kt @@ -1,4 +1,5 @@ import com.android.build.api.dsl.ApplicationExtension +import java.util.Properties import org.gradle.api.DefaultTask import org.gradle.api.Plugin import org.gradle.api.Project @@ -46,6 +47,18 @@ open class RustPlugin : Plugin { } } + // 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 { for (profile in listOf("debug", "release")) { val profileCapitalized = profile.replaceFirstChar { it.uppercase() } @@ -63,13 +76,16 @@ open class RustPlugin : Plugin { 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 + sdkDir = localProps.getProperty("sdk.dir") + ndkDir = localProps.getProperty("ndk.dir") target = targetName release = profile == "release" } diff --git a/src-tauri/gen/android/gradle.properties b/src-tauri/gen/android/gradle.properties index 2a7ec69..4e8594a 100644 --- a/src-tauri/gen/android/gradle.properties +++ b/src-tauri/gen/android/gradle.properties @@ -21,4 +21,10 @@ kotlin.code.style=official # 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 \ No newline at end of file +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 diff --git a/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties index c5f9a53..b2ee479 100644 --- a/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties +++ b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -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