From f179928c57b697f103743427e7adc2cdc0172ad7 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 18 Aug 2026 15:35:17 -0400 Subject: [PATCH] android: run ktlint and detekt from the image, not as Gradle plugins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First Android run failed at plugin resolution: Plugin [id: 'io.gitlab.arturbosch.detekt', version: '2.0.0-alpha.3'] was not found in any of the following sources That version is published to neither Maven Central nor the plugin portal — the latest detekt anywhere is 1.23.8. It was copied from Minstrel's catalog, where it presumably resolves from a cached artifact; copying a pin without checking it exists is what made it my problem. Rather than chase a working plugin version, the analyzers now run from the CLIs ci-rust-android already ships. That was the point of putting them in the image in step 3, and going through Gradle plugins would have meant a SECOND pinned version of each tool, resolved at build time, kept in lockstep with the image's by hand. One less resolution step, and step 3's decision finally earns its keep. Also replaces the source-ordering hack while here. Kotlin has to compile after the bindings are generated, and the usual `tasks.withType` cannot be written in this build at all — AGP 9's built-in Kotlin means that class is not on the buildscript classpath. Passing the TASK PROVIDERS to srcDir instead lets Gradle read their @OutputDirectory and infer the ordering itself, which is the idiomatic form and removes the dependsOn entirely. Good news from the failed run: the Gradle wrapper check passed, so Gradle 9.1.0 on the image's JDK 25 works — the toolchain decision from step 3 holds. Co-Authored-By: Claude Opus 5 (1M context) --- .forgejo/workflows/android.yml | 11 +++++++++-- android/app/build.gradle.kts | 31 ++++++++----------------------- android/build.gradle.kts | 4 ++-- android/gradle/libs.versions.toml | 12 ++++++------ 4 files changed, 25 insertions(+), 33 deletions(-) diff --git a/.forgejo/workflows/android.yml b/.forgejo/workflows/android.yml index ae97cbf..0e8e0b9 100644 --- a/.forgejo/workflows/android.yml +++ b/.forgejo/workflows/android.yml @@ -79,11 +79,18 @@ jobs: - name: Build the native library and bindings run: ./gradlew generateUniffiBindings + # The image's PINNED CLIs, not Gradle plugins. ci-rust-android carries both + # (M12 step 3) precisely so this lane needs no second image, and going + # through Gradle plugins would mean a second version of each tool resolved + # at build time and kept in lockstep with the image's by hand. + # + # Scoped to src/main: the generated uniffi bindings live under build/ and + # are not ours to style. - name: ktlint - run: ./gradlew ktlintCheck + run: ktlint "app/src/main/**/*.kt" - name: detekt - run: ./gradlew detekt + run: detekt --build-upon-default-config --config config/detekt.yml --input app/src/main/java - name: Unit tests # Host-JVM tests only. Anything touching the core needs an Android diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 95bf552..656f307 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -3,8 +3,6 @@ import javax.inject.Inject plugins { alias(libs.plugins.android.application) alias(libs.plugins.compose.compiler) - alias(libs.plugins.ktlint) - alias(libs.plugins.detekt) } // The Cargo workspace root — two levels up from android/app. @@ -192,8 +190,14 @@ android { sourceSets { getByName("main") { // The .so and the bindings are build outputs, not checked-in sources. - jniLibs.srcDir(jniLibsOut) - java.srcDir(bindingsOut) + // Passing the TASK PROVIDERS, not plain paths: Gradle reads each + // task's @OutputDirectory and infers the build ordering itself. A + // bare path would compile Kotlin before the bindings exist, and the + // usual fix — depending on KotlinCompile by type — cannot be written + // here, because AGP 9's built-in Kotlin means that class is not on + // the buildscript classpath. + jniLibs.srcDir(cargoNdkDebug) + java.srcDir(generateBindings) } } @@ -202,25 +206,6 @@ android { } } -// Kotlin compilation needs the generated bindings to exist first. -tasks.withType().configureEach { - dependsOn(generateBindings) -} -tasks.named("preBuild") { dependsOn(generateBindings) } - -// ktlint must not police generated code — it is uniffi's output, not ours, and -// there is no edit that would fix a complaint about it. -ktlint { - filter { - exclude { it.file.path.contains("generated") } - } -} - -detekt { - buildUponDefaultConfig = true - config.setFrom(files("$rootDir/config/detekt.yml")) -} - dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.activity.compose) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 558f6d5..9866537 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -4,6 +4,6 @@ plugins { // older plugin can't cast AGP 9's ApplicationExtension to the removed // BaseExtension. Same conclusion Minstrel reached on this toolchain pair. alias(libs.plugins.compose.compiler) apply false - alias(libs.plugins.ktlint) apply false - alias(libs.plugins.detekt) apply false + // ktlint/detekt are run from the CI image's pinned CLIs, not as Gradle + // plugins — see the note in gradle/libs.versions.toml. } diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 9456b01..fa16ef5 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -12,10 +12,12 @@ lifecycle = "2.8.7" activity-compose = "1.9.3" coroutines = "1.9.0" -# LOCKSTEP with ci-rust-android's versions.env. Bumping either side alone makes -# local and CI analysis disagree; Renovate cannot see the coupling. -ktlint-gradle = "12.1.1" -detekt = "2.0.0-alpha.3" +# ktlint and detekt are NOT Gradle plugins here. ci-rust-android already ships +# both as pinned CLIs (M12 step 3), and the CI lane invokes those directly. Adding +# the Gradle plugins would mean a SECOND pinned version of each tool, resolved at +# build time, that has to be kept in lockstep with the image's by hand — and the +# first attempt at it failed outright, because the detekt version Minstrel pins +# (2.0.0-alpha.3) is not published to Maven Central or the plugin portal at all. # JNA is not optional: uniffi's Kotlin bindings call into the .so through it. # The @aar classifier matters — the plain jar has no Android native payload and @@ -42,5 +44,3 @@ junit = { module = "junit:junit", version.ref = "junit" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-gradle" } -detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }