diff --git a/README.md b/README.md index 388be0930..ff343d43b 100644 --- a/README.md +++ b/README.md @@ -92,10 +92,11 @@ set PATH=%PATH%;c:\msys64\usr\bin The build produces three artifacts: -- The main .aar file, which contains the backend Kotlin code, web assets, and -Anki backend code compiled for Android. -- An Android wrapper .aar in `rsdroid-android/build/outputs/aar`. Its published -Maven metadata declares an API dependency on the main backend artifact. +- The backend API .aar in `rsdroid/build/outputs/aar`, 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. - A .jar that contains the backend code compiled for the host platform, for use with Robolectric unit tests. @@ -156,6 +157,10 @@ in the AnkiDroid repo, and add the following line: local_backend=true ``` +The local dependency configuration in AnkiDroid must include both +`rsdroid/build/outputs/aar/rsdroid-release.aar` 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 diff --git a/build_rust/src/main.rs b/build_rust/src/main.rs index 056917257..e00e335e5 100644 --- a/build_rust/src/main.rs +++ b/build_rust/src/main.rs @@ -13,7 +13,7 @@ use std::process::Command; use streaming_iterator::StreamingIterator; use tree_sitter::{Parser, Query, QueryCursor}; -const ANDROID_OUT_DIR: &str = "rsdroid/build/generated/jniLibs"; +const ANDROID_OUT_DIR: &str = "rsdroid-android/build/generated/jniLibs"; const ROBOLECTRIC_OUT_DIR: &str = "rsdroid-testing/build/generated/jniLibs"; fn main() -> Result<()> { @@ -55,7 +55,7 @@ fn run_gradle() -> Result<()> { fn build_web_artifacts() -> Result<()> { println!("*** Building desktop web components"); - let artifacts_dir = Path::new("rsdroid/build/generated/anki_artifacts/backend"); + let artifacts_dir = Path::new("rsdroid-android/build/generated/anki_artifacts/backend"); let mut cmd = if cfg!(windows) { let mut cmd = Command::new("cmd"); cmd.args(["/c", "tools\\ninja.bat"]); diff --git a/check-droid.bat b/check-droid.bat index b49f9db41..de3cfdd50 100755 --- a/check-droid.bat +++ b/check-droid.bat @@ -1,4 +1,4 @@ -if not exist rsdroid\build\outputs\aar\rsdroid-release.aar ( +if not exist rsdroid-android\build\outputs\aar\rsdroid-android-release.aar ( echo "Run ./build.bat first" exit 1 ) diff --git a/check-droid.sh b/check-droid.sh index 8a5431096..9d4f2034d 100755 --- a/check-droid.sh +++ b/check-droid.sh @@ -2,7 +2,7 @@ set -e -test -f rsdroid/build/outputs/aar/rsdroid-release.aar || ( +test -f rsdroid-android/build/outputs/aar/rsdroid-android-release.aar || ( echo "Run ./build.sh first" exit 1 ) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index d0cbb68a8..7b1cde754 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -11,9 +11,9 @@ This repo is comprised of three main components: - `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-android` is the Android entry point, published as `anki-backend-android`. It is to be - referenced by `Anki-Android` and package the Android native libraries (`librsdroid.so` for each - supported architecture) and bundle web assets as Android assets. It exposes `rsdroid` through an `api` dependency. +- `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 + supported architecture), and bundles web assets as Android assets. It exposes `rsdroid` through an `api` dependency. Other folders: diff --git a/gradle.properties b/gradle.properties index 7b2edb687..a1a841c08 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.74-anki26.09.3 +VERSION_NAME=0.1.75-anki26.09.3 POM_INCEPTION_YEAR=2020 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73ff0a76b..2970fa52d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ espresso = "3.7.0" androidGradlePlugin = "9.4.1" gradleMavenPublishPlugin = "0.37.0" junit = "6.1.3" +junit4 = "4.13.2" # https://github.com/JetBrains/kotlin/releases/ kotlin = '2.4.21' # https://github.com/Kotlin/kotlinx.serialization/releases @@ -55,6 +56,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestJunit" } androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } jakewharton-timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } +junit4 = { module = "junit:junit", version.ref = "junit4" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } diff --git a/rsdroid-android/README.md b/rsdroid-android/README.md index 303278690..307541cce 100644 --- a/rsdroid-android/README.md +++ b/rsdroid-android/README.md @@ -4,10 +4,10 @@ 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. -## Planned contents +## Contents -After the split, `anki-backend-android` will contain: +`anki-backend-android` contains: - Android native libraries (`librsdroid.so` for each supported architecture). - Web assets packaged as Android assets. -- Android SQLite adapters \ No newline at end of file +- Android SQLite adapters diff --git a/rsdroid-android/build.gradle b/rsdroid-android/build.gradle index 4d6c1b1fa..bc0d35e9a 100644 --- a/rsdroid-android/build.gradle +++ b/rsdroid-android/build.gradle @@ -1,12 +1,20 @@ +import com.android.build.gradle.tasks.BundleAar + +import java.util.zip.ZipFile + plugins { alias(libs.plugins.android.library) id 'signing' alias(libs.plugins.gradle.maven.publish.plugin) } +// The Rust build generates the core sources and this module's native libraries and assets. +tasks.named('preBuild').configure { dependsOn(':rsdroid:buildRust') } + android { namespace = 'net.ankiweb.rsdroid.android' compileSdk = libs.versions.compileSdk.get().toInteger() + ndkVersion = libs.versions.ndk.get() compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -33,6 +41,29 @@ android { lint { targetSdk = libs.versions.targetSdk.get().toInteger() } + + sourceSets { + main { + jniLibs.srcDirs 'build/generated/jniLibs' + assets { + srcDirs "build/generated/anki_artifacts" + } + } + } +} + +afterEvaluate { + // only the publishable AARs: AGP 9's bundleLocalLintAar tasks omit jniLibs + tasks.withType(BundleAar).matching { it.name ==~ /bundle(Debug|Release)Aar/ }.configureEach { bundleAarTask -> + bundleAarTask.doLast { + def aarFile = bundleAarTask.outputs.files.singleFile + def zipFile = new ZipFile(aarFile) + int libCount = zipFile.entries().toList().count { it.name.contains(".so") } + if (libCount == 0 || (System.getenv("ALL_ARCHS") == "1" && libCount != 4)) { + throw new GradleException("AAR appears to be missing shared library!") + } + } + } } dependencies { diff --git a/rsdroid/build.gradle b/rsdroid/build.gradle index bbd6cdecf..d2e30cc7c 100644 --- a/rsdroid/build.gradle +++ b/rsdroid/build.gradle @@ -1,7 +1,3 @@ -import com.android.build.gradle.tasks.BundleAar - -import java.util.zip.ZipFile - plugins { alias(libs.plugins.android.library) // required for aar generation to link to from AnkiDroid id 'signing' @@ -33,7 +29,6 @@ androidComponents { android { namespace = 'net.ankiweb.rsdroid' compileSdk = libs.versions.compileSdk.get().toInteger() - ndkVersion = libs.versions.ndk.get() compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -65,27 +60,9 @@ android { main { kotlin.srcDirs += "build/generated/source/backend" java.srcDirs += "build/generated/source/backend" - jniLibs.srcDirs 'build/generated/jniLibs' resources { srcDirs "src/main/resources" } - assets { - srcDirs "build/generated/anki_artifacts" - } - } - } -} - -afterEvaluate { - // only the publishable AARs: AGP 9's bundleLocalLintAar tasks omit jniLibs - tasks.withType(BundleAar).matching { it.name ==~ /bundle(Debug|Release)Aar/ }.configureEach { bundleAarTask -> - bundleAarTask.doLast { - def aarFile = bundleAarTask.outputs.files.singleFile - def zipFile = new ZipFile(aarFile) - int libCount = zipFile.entries().toList().count { it.name.contains(".so") } - if (libCount == 0 || (System.getenv("ALL_ARCHS") == "1" && libCount != 4)) { - throw new GradleException("AAR appears to be missing shared library!") - } } } } @@ -99,10 +76,7 @@ dependencies { implementation libs.androidx.annotation implementation libs.slf4j.api - testImplementation libs.junit.jupiter - testImplementation libs.robolectric - testImplementation libs.androidx.core - testImplementation libs.androidx.test.junit + testImplementation libs.junit4 testImplementation project(path: ':rsdroid-testing') } diff --git a/rsdroid/src/test/java/net/ankiweb/BackendCloseRaceTest.kt b/rsdroid/src/test/java/net/ankiweb/BackendCloseRaceTest.kt index 769eeab4e..1720e95d7 100644 --- a/rsdroid/src/test/java/net/ankiweb/BackendCloseRaceTest.kt +++ b/rsdroid/src/test/java/net/ankiweb/BackendCloseRaceTest.kt @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later package net.ankiweb -import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.long @@ -14,7 +13,6 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith import kotlin.concurrent.thread import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -27,7 +25,6 @@ import kotlin.time.Duration.Companion.minutes * * https://github.com/ankidroid/Anki-Android/issues/21455 */ -@RunWith(AndroidJUnit4::class) class BackendCloseRaceTest { @Before fun loadLibrary() { diff --git a/rsdroid/src/test/java/net/ankiweb/CollectionCreationJvmTest.kt b/rsdroid/src/test/java/net/ankiweb/CollectionCreationJvmTest.kt new file mode 100644 index 000000000..0ff1c171d --- /dev/null +++ b/rsdroid/src/test/java/net/ankiweb/CollectionCreationJvmTest.kt @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package net.ankiweb + +import net.ankiweb.rsdroid.BackendFactory.getBackend +import net.ankiweb.rsdroid.testing.RustBackendLoader.ensureSetup +import org.junit.Assert.assertEquals +import org.junit.Test + +/** Verifies collection creation through the backend API on a plain JVM. */ +class CollectionCreationJvmTest { + @Test + fun collectionOpensAndAnswersQueries() { + ensureSetup() + getBackend().use { backend -> + backend.openCollection(":memory:") + val response = backend.fullQueryProto("SELECT count(*) FROM col", emptyArray()) + assertEquals(1, response.rowCount) + val row = response.result.getRows(0) + assertEquals(1L, row.getFields(0).longValue) + } + } +} diff --git a/rsdroid/src/test/java/net/ankiweb/DbRequestBindingTest.kt b/rsdroid/src/test/java/net/ankiweb/DbRequestBindingTest.kt index 8fe5459c0..dbc8e3d26 100644 --- a/rsdroid/src/test/java/net/ankiweb/DbRequestBindingTest.kt +++ b/rsdroid/src/test/java/net/ankiweb/DbRequestBindingTest.kt @@ -1,16 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later package net.ankiweb -import androidx.test.ext.junit.runners.AndroidJUnit4 import anki.ankidroid.SqlValue.DataCase import net.ankiweb.rsdroid.BackendFactory.getBackend import net.ankiweb.rsdroid.testing.RustBackendLoader.ensureSetup import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.runner.RunWith -@RunWith(AndroidJUnit4::class) class DbRequestBindingTest { @Test fun bindArgumentsKeepTheirSqliteTypesAndValues() { diff --git a/rsdroid/src/test/java/net/ankiweb/FullQueryTest.kt b/rsdroid/src/test/java/net/ankiweb/FullQueryTest.kt index a1cd9df5d..1011e9b29 100644 --- a/rsdroid/src/test/java/net/ankiweb/FullQueryTest.kt +++ b/rsdroid/src/test/java/net/ankiweb/FullQueryTest.kt @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later package net.ankiweb -import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.double import kotlinx.serialization.json.int @@ -19,9 +18,7 @@ import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -@RunWith(AndroidJUnit4::class) class FullQueryTest { @Before fun loadLibrary() {