From fc098a0a8fdd8e2d2e7499fd21c187b0ca5c3b3a Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:37:53 +0000 Subject: [PATCH 1/5] build: share the Rust build task across modules `buildRust` is registered on the root project and used the modules. `processResources` now depends on it to ensure the native libraries exist before they are packaged. Assisted-by: GPT-6.1-Sol xhigh --- build.gradle.kts | 3 +++ rsdroid-android/build.gradle | 2 +- rsdroid-testing/build.gradle | 6 +++--- rsdroid/build.gradle | 3 +-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e80ab7d67..36d90a3ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,6 +11,9 @@ plugins { alias(libs.plugins.ktlint.gradle.plugin) apply false } +// One Rust pipeline generates native libraries and API sources for all modules. +apply(from = "build-rust.gradle") + allprojects { repositories { google() diff --git a/rsdroid-android/build.gradle b/rsdroid-android/build.gradle index bc0d35e9a..dfa4f9022 100644 --- a/rsdroid-android/build.gradle +++ b/rsdroid-android/build.gradle @@ -9,7 +9,7 @@ plugins { } // The Rust build generates the core sources and this module's native libraries and assets. -tasks.named('preBuild').configure { dependsOn(':rsdroid:buildRust') } +tasks.named('preBuild').configure { dependsOn(':buildRust') } android { namespace = 'net.ankiweb.rsdroid.android' diff --git a/rsdroid-testing/build.gradle b/rsdroid-testing/build.gradle index b2ba7fda0..ad4be2256 100644 --- a/rsdroid-testing/build.gradle +++ b/rsdroid-testing/build.gradle @@ -25,8 +25,6 @@ plugins { alias(libs.plugins.gradle.maven.publish.plugin) } -apply from: "$rootDir/build-rust.gradle" - dependencies { implementation libs.kotlin.stdlib.jdk8 testImplementation libs.junit.jupiter @@ -39,8 +37,10 @@ test { systemProperty 'rsdroidTesting.testClasspath', sourceSets.test.runtimeClasspath.asPath } +// The native libraries must exist before they are copied into the test support JAR. +tasks.named('processResources').configure { dependsOn(':buildRust') } + jar { - mustRunAfter("buildRust") sourceSets { main { resources { diff --git a/rsdroid/build.gradle b/rsdroid/build.gradle index d2e30cc7c..55872d631 100644 --- a/rsdroid/build.gradle +++ b/rsdroid/build.gradle @@ -4,8 +4,7 @@ plugins { alias(libs.plugins.gradle.maven.publish.plugin) } -apply from: "$rootDir/build-rust.gradle" -preBuild.dependsOn "buildRust" +tasks.named('preBuild').configure { dependsOn(':buildRust') } def gitHead = { File repo -> providers.exec { From e75edc0227cb513f87a1adc5fe21834bb571f892 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:50:46 +0000 Subject: [PATCH 2/5] build: prepare `BuildConfig` generation for `java-library` The `generateBuildConfig` task is now registered with an explicit output directory. Assisted-by: GPT-6.1-Sol xhigh --- rsdroid/build.gradle | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rsdroid/build.gradle b/rsdroid/build.gradle index 55872d631..075d42aea 100644 --- a/rsdroid/build.gradle +++ b/rsdroid/build.gradle @@ -12,15 +12,16 @@ def gitHead = { File repo -> }.standardOutput.asText.map { it.trim() } } -// Generate BuildConfig +def generateBuildConfig = tasks.register("generateBuildConfig", GenerateBuildConfigTask) { + ankiCommitHash = gitHead(new File(rootDir, "anki")) + backendGitCommitHash = gitHead(rootDir) + ankiDesktopVersion = providers.gradleProperty("VERSION_NAME").map { it.replaceFirst(/.*anki/, "") } + cargoLock = rootProject.file("Cargo.lock") + outputDir = layout.buildDirectory.dir("generated/source/buildConfig") +} + androidComponents { onVariants(selector().all()) { variant -> - def generateBuildConfig = tasks.register("generate${variant.name.capitalize()}BuildConfig", GenerateBuildConfigTask) { - ankiCommitHash = gitHead(new File(rootDir, "anki")) - backendGitCommitHash = gitHead(rootDir) - ankiDesktopVersion = providers.gradleProperty("VERSION_NAME").map { it.replaceFirst(/.*anki/, "") } - cargoLock = rootProject.file("Cargo.lock") - } variant.sources.kotlin.addGeneratedSourceDirectory(generateBuildConfig) { it.outputDir } } } From 77a8ef29791b5b0a0e33233398dfe181b46e724d Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:50:46 +0000 Subject: [PATCH 3/5] build!: convert :rsdroid to a `java-library` BREAKING CHANGE: anki-android-backend is now a JAR instead of an AAR. Android consumers continue to use anki-backend-android, which depends on the core JAR. Replaces the Android library with a `java-library` (Java 11). Wire Kotlin API + Protobuf sources into the compilation + JAR. BuildConfig from the previous commit is reused. Javadocs are published, but empty. Fixes 674 Assisted-by: GPT-6.1-Sol xhigh --- .github/workflows/build-quick.yml | 8 +-- .github/workflows/build-release.yml | 8 +-- README.md | 14 ++--- build_rust/src/main.rs | 7 ++- check-droid.bat | 2 +- check-droid.sh | 2 +- docs/ARCHITECTURE.md | 10 ++-- rsdroid-android/README.md | 3 + rsdroid/build.gradle | 75 ++++++++++--------------- rsdroid/consumer-rules.pro | 0 rsdroid/gradle.properties | 4 +- rsdroid/proguard-rules.pro | 21 ------- tools/install-prerequisites-tartelet.sh | 2 +- 13 files changed, 62 insertions(+), 94 deletions(-) delete mode 100644 rsdroid/consumer-rules.pro delete mode 100644 rsdroid/proguard-rules.pro diff --git a/.github/workflows/build-quick.yml b/.github/workflows/build-quick.yml index 9846a93ee..61921878f 100644 --- a/.github/workflows/build-quick.yml +++ b/.github/workflows/build-quick.yml @@ -261,7 +261,7 @@ jobs: run: ./check-rust.bat - name: Run tests (Unit) - run: ./gradlew test rsdroid:lint rsdroid-android:lint ktlintCheck --daemon + run: ./gradlew test rsdroid-android:lint ktlintCheck --daemon - name: Run tests (Emulator) uses: reactivecircus/android-emulator-runner@v2 @@ -279,13 +279,13 @@ jobs: sleep 5 ./gradlew rsdroid-instrumented:connectedCheck - - name: Upload rsdroid AAR as artifact + - name: Upload backend core JARs as artifact uses: actions/upload-artifact@v6 if: '!cancelled()' with: - name: rsdroid-aar-${{ matrix.os }} + name: rsdroid-core-${{ matrix.os }} if-no-files-found: error - path: rsdroid/build/outputs/aar + path: rsdroid/build/libs - name: Upload Android wrapper AAR as artifact uses: actions/upload-artifact@v6 diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index a794e8f42..bf3719588 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -168,12 +168,12 @@ jobs: retry_on: error command: ./build.sh - - name: Upload rsdroid AAR as artifact + - name: Upload backend core JARs as artifact uses: actions/upload-artifact@v6 with: - name: rsdroid-aar + name: rsdroid-core if-no-files-found: error - path: rsdroid/build/outputs/aar + path: rsdroid/build/libs - name: Upload Android wrapper AAR as artifact uses: actions/upload-artifact@v6 @@ -191,7 +191,7 @@ jobs: # following steps only run on workflow dispatch - - name: Publish backend and Android wrapper AARs to Maven + - name: Publish backend core JAR and Android wrapper AAR to Maven if: "${{ github.event.inputs.mavenPublish != '' && github.event_name == 'workflow_dispatch'}}" env: ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }} diff --git a/README.md b/README.md index ff343d43b..1dfcc0d70 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,8 @@ set PATH=%PATH%;c:\msys64\usr\bin The build produces three artifacts: -- The backend API .aar in `rsdroid/build/outputs/aar`, which contains the backend -Kotlin code and generated Protocol Buffer classes. +- The backend API .jar in `rsdroid/build/libs`, published as `anki-android-backend`, +which contains the backend Kotlin code and generated Protocol Buffer classes. - The Android .aar in `rsdroid-android/build/outputs/aar`, which contains the SQLite adapter, web assets, and Anki backend code compiled for Android. Its published Maven metadata declares an API dependency on the backend API artifact. @@ -158,13 +158,11 @@ local_backend=true ``` The local dependency configuration in AnkiDroid must include both -`rsdroid/build/outputs/aar/rsdroid-release.aar` and +`rsdroid/build/libs/rsdroid.jar` and `rsdroid-android/build/outputs/aar/rsdroid-android-release.aar`. -Check `Anki-AndroidBackend/gradle.properties`'s `BACKEND_VERSION` and -`Anki-Android/build.gradle`'s `ext.ankidroid_backend_version`. Both variables -should have the same value. If it is not the case, you must edit Anki-Android's -one. +Ensure `VERSION_NAME` in this repository's `gradle.properties` matches +`ankiBackend` in AnkiDroid's `gradle/libs.versions.toml`. After making the change, you should be able to build and run the project on an x86_64 emulator/device (arm64 on M1 Macs), and run unit tests. @@ -216,7 +214,7 @@ the repository. 1. Head over to the main `Anki-Android` repository and update the `AnkiDroid/build.gradle` file there to adopt the new backend version once it shows up in -https://repo1.maven.org/maven2/io/github/david-allison/anki-android-backend/ +https://repo1.maven.org/maven2/io/github/david-allison/anki-backend-android/ ## test library copies diff --git a/build_rust/src/main.rs b/build_rust/src/main.rs index e00e335e5..5bed5b99d 100644 --- a/build_rust/src/main.rs +++ b/build_rust/src/main.rs @@ -47,7 +47,12 @@ fn run_gradle() -> Result<()> { Command::new("./gradlew") }; cmd.env("RUNNING_FROM_BUILD_SCRIPT", "1") - .args(["assembleRelease", "rsdroid-testing:build"]) + // assembleRelease covers Android modules; also build the JVM core JAR. + .args([ + "assembleRelease", + "rsdroid:assemble", + "rsdroid-testing:build", + ]) .ensure_success()?; } Ok(()) diff --git a/check-droid.bat b/check-droid.bat index de3cfdd50..c18ac80a4 100755 --- a/check-droid.bat +++ b/check-droid.bat @@ -3,4 +3,4 @@ if not exist rsdroid-android\build\outputs\aar\rsdroid-android-release.aar ( exit 1 ) -./gradlew rsdroid:lint rsdroid-android:lint rsdroid-instrumented:connectedCheck +./gradlew rsdroid-android:lint rsdroid-instrumented:connectedCheck diff --git a/check-droid.sh b/check-droid.sh index 9d4f2034d..d6168bea2 100755 --- a/check-droid.sh +++ b/check-droid.sh @@ -9,4 +9,4 @@ test -f rsdroid-android/build/outputs/aar/rsdroid-android-release.aar || ( . ./set-android-ndk-home.sh -./gradlew rsdroid:lint rsdroid-android:lint rsdroid-instrumented:connectedCheck +./gradlew rsdroid-android:lint rsdroid-instrumented:connectedCheck diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 7b1cde754..228c484e2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -8,11 +8,11 @@ This repo is comprised of three main components: of only three different functions: `openBackend()`, `closeBackend()`, and `runMethodRaw()`. When one of these functions is called by Java code, `rslib-bridge` takes care of converting the Java objects to a native Rust representation, and then passes the call on to Anki's Rust backend, which gets included in `rslib-bridge` via the `anki[/rslib]` submodule. -- `rsdroid` is a Kotlin library that provides a friendly interface to the backend code. The bulk of its code is automatically - generated from the service definitions in `anki/proto/anki`. It also exposes the Rust database functionality - through `SQLHandler`. +- `rsdroid` is a Kotlin/JVM library, published as `anki-android-backend`, that provides a friendly interface to the backend code. + Its JAR targets Java 11. The bulk of its code is automatically generated from the service definitions in `anki/proto/anki`. + It also exposes the Rust database functionality through `SQLHandler`. - `rsdroid-android` is the Android entry point, published as `anki-backend-android`. It is - referenced by `Anki-Android`, packages the Android native libraries (`librsdroid.so` for each + referenced by `Anki-Android`, packages the Android native libraries (`librsdroid.so` for each supported architecture), and bundles web assets as Android assets. It exposes `rsdroid` through an `api` dependency. Other folders: @@ -21,7 +21,7 @@ Other folders: `/tools/` Scripts to generate the backend interface and translations -`rsdroid-testing` - Builds a testing library targetting desktop machines, for use with Robolectric +`rsdroid-testing` - Builds native libraries for desktop machines, for use in JVM tests and Robolectric `rsdroid-instrumented` - Android Instrumented Test diff --git a/rsdroid-android/README.md b/rsdroid-android/README.md index 307541cce..791235fab 100644 --- a/rsdroid-android/README.md +++ b/rsdroid-android/README.md @@ -4,6 +4,9 @@ Android entry point for the Anki backend. This Android library exposes [`:rsdroid`](../rsdroid) through an `api` dependency, so consumers can use the backend API through this module. + +Maven consumers should depend on ths via `io.github.david-allison:anki-backend-android`. + ## Contents `anki-backend-android` contains: diff --git a/rsdroid/build.gradle b/rsdroid/build.gradle index 075d42aea..2a213d57d 100644 --- a/rsdroid/build.gradle +++ b/rsdroid/build.gradle @@ -1,11 +1,14 @@ +import com.vanniktech.maven.publish.KotlinJvm +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { - alias(libs.plugins.android.library) // required for aar generation to link to from AnkiDroid + id 'java-library' + alias(libs.plugins.kotlin.jvm) id 'signing' alias(libs.plugins.gradle.maven.publish.plugin) } -tasks.named('preBuild').configure { dependsOn(':buildRust') } - def gitHead = { File repo -> providers.exec { commandLine "git", "-C", repo.absolutePath, "rev-parse", "HEAD" @@ -20,67 +23,46 @@ def generateBuildConfig = tasks.register("generateBuildConfig", GenerateBuildCon outputDir = layout.buildDirectory.dir("generated/source/buildConfig") } -androidComponents { - onVariants(selector().all()) { variant -> - variant.sources.kotlin.addGeneratedSourceDirectory(generateBuildConfig) { it.outputDir } - } +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } -android { - namespace = 'net.ankiweb.rsdroid' - compileSdk = libs.versions.compileSdk.get().toInteger() - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - defaultConfig { - minSdk = libs.versions.minSdk.get().toInteger() - - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled = false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } +tasks.withType(KotlinCompile).configureEach { + compilerOptions { + jvmTarget = JvmTarget.JVM_11 } +} - testOptions { - targetSdk = libs.versions.targetSdk.get().toInteger() +sourceSets { + main { + kotlin.srcDir("build/generated/source/backend") + kotlin.srcDir(generateBuildConfig.flatMap { it.outputDir }) + // protoc emits Java messages alongside the generated Kotlin API. + java.srcDir("build/generated/source/backend") } +} - lint { - targetSdk = libs.versions.targetSdk.get().toInteger() - } +tasks.named('compileKotlin').configure { dependsOn(':buildRust', 'installGitHook') } +tasks.named('compileJava').configure { dependsOn(':buildRust') } +tasks.matching { it.name == 'sourcesJar' }.configureEach { dependsOn(':buildRust') } - sourceSets { - main { - kotlin.srcDirs += "build/generated/source/backend" - java.srcDirs += "build/generated/source/backend" - resources { - srcDirs "src/main/resources" - } - } - } +test { + useJUnit() } dependencies { - // Protobuf is part of the ABI, so include it as a compile/api dependency. + // These types and annotations appear in the public API. api libs.protobuf.kotlin.lite - // JsonArray is returned by the public fullQuery API. api libs.kotlinx.serialization.json + api libs.androidx.annotation - implementation libs.androidx.annotation implementation libs.slf4j.api testImplementation libs.junit4 testImplementation project(path: ':rsdroid-testing') } - // Install Git pre-commit hook for format-rust tasks.register('installGitHook', Copy) { from new File(rootProject.rootDir, 'pre-commit') @@ -92,9 +74,10 @@ tasks.register('installGitHook', Copy) { } } // to run manually: `./gradlew installGitHook` -tasks.named('preBuild').configure { dependsOn('installGitHook') } mavenPublishing { + // Publish sources and an empty Javadoc JAR for the Kotlin/generated API. + configure(new KotlinJvm()) // Use https://central.sonatype.com/account with david-allison's GitHub login, not Google // the host should match rsdroid-testing publishToMavenCentral(true) // "true" means publish+release automatically+unattended diff --git a/rsdroid/consumer-rules.pro b/rsdroid/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/rsdroid/gradle.properties b/rsdroid/gradle.properties index 403409e42..76b1a1faf 100644 --- a/rsdroid/gradle.properties +++ b/rsdroid/gradle.properties @@ -1,5 +1,5 @@ POM_ARTIFACT_ID=anki-android-backend -POM_PACKAGING=aar +POM_PACKAGING=jar POM_NAME=Anki Android Backend -POM_DESCRIPTION=JNI bridge between AnkiDroid and Anki Desktop\'s Rust code \ No newline at end of file +POM_DESCRIPTION=JVM API for Anki Desktop's Rust backend; Android integration is provided by anki-backend-android diff --git a/rsdroid/proguard-rules.pro b/rsdroid/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/rsdroid/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/tools/install-prerequisites-tartelet.sh b/tools/install-prerequisites-tartelet.sh index f70b3de41..ffb602d33 100755 --- a/tools/install-prerequisites-tartelet.sh +++ b/tools/install-prerequisites-tartelet.sh @@ -46,7 +46,7 @@ cd .. # from build-quick.yml cargo run -p build_rust ./check-rust.sh -./gradlew test rsdroid:lint +./gradlew test rsdroid-android:lint # from build-release.yml export ALL_ARCHS=1 From ab789b1304fe17a41831163d0744f851d1a034ed Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:50:46 +0000 Subject: [PATCH 4/5] build!: rename the `anki-android-backend` to `anki-backend-core` BREAKING CHANGE: JVM consumers must replace `anki-android-backend` with `anki-backend-core`. Android consumers may still use `anki-backend-android`. Assisted-by: GPT-6.1-Sol xhigh --- README.md | 2 +- docs/ARCHITECTURE.md | 2 +- rsdroid/gradle.properties | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1dfcc0d70..c6ae309cc 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ set PATH=%PATH%;c:\msys64\usr\bin The build produces three artifacts: -- The backend API .jar in `rsdroid/build/libs`, published as `anki-android-backend`, +- The backend API .jar in `rsdroid/build/libs`, published as `anki-backend-core`, which contains the backend Kotlin code and generated Protocol Buffer classes. - The Android .aar in `rsdroid-android/build/outputs/aar`, which contains the SQLite adapter, web assets, and Anki backend code compiled for Android. Its published diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 228c484e2..44af9760f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -8,7 +8,7 @@ This repo is comprised of three main components: of only three different functions: `openBackend()`, `closeBackend()`, and `runMethodRaw()`. When one of these functions is called by Java code, `rslib-bridge` takes care of converting the Java objects to a native Rust representation, and then passes the call on to Anki's Rust backend, which gets included in `rslib-bridge` via the `anki[/rslib]` submodule. -- `rsdroid` is a Kotlin/JVM library, published as `anki-android-backend`, that provides a friendly interface to the backend code. +- `rsdroid` is a Kotlin/JVM library, published as `anki-backend-core`, that provides a friendly interface to the backend code. Its JAR targets Java 11. The bulk of its code is automatically generated from the service definitions in `anki/proto/anki`. It also exposes the Rust database functionality through `SQLHandler`. - `rsdroid-android` is the Android entry point, published as `anki-backend-android`. It is diff --git a/rsdroid/gradle.properties b/rsdroid/gradle.properties index 76b1a1faf..f7dca096a 100644 --- a/rsdroid/gradle.properties +++ b/rsdroid/gradle.properties @@ -1,5 +1,5 @@ -POM_ARTIFACT_ID=anki-android-backend +POM_ARTIFACT_ID=anki-backend-core POM_PACKAGING=jar -POM_NAME=Anki Android Backend +POM_NAME=Anki Backend (Core) POM_DESCRIPTION=JVM API for Anki Desktop's Rust backend; Android integration is provided by anki-backend-android From ec7055ca74398086a81c0c1703508c0446d8945d Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:53:50 +0000 Subject: [PATCH 5/5] build: bump version to 0.1.76-anki26.09.3 First release of the anki-backend-core `java-library`. BREAKING CHANGE: * `anki-backend-core` replaces `anki-android-backend` as the backend API artifact, and is now a `java-library`. * AnkiDroid needs to update references in `BackendDependencies.kt` No impact to Android applications depending on `anki-backend-android` Assisted-by: GPT-6.1-Sol xhigh --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a1a841c08..8c8131a45 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ android.useAndroidX=true android.enableJetifier=false GROUP=io.github.david-allison -VERSION_NAME=0.1.75-anki26.09.3 +VERSION_NAME=0.1.76-anki26.09.3 POM_INCEPTION_YEAR=2020