mirror of
https://github.com/chmouel/liseur.git
synced 2026-10-14 03:39:09 +00:00
290 lines
11 KiB
Kotlin
290 lines
11 KiB
Kotlin
import java.util.Properties
|
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
|
|
plugins {
|
|
alias(libs.plugins.android.application)
|
|
alias(libs.plugins.kotlin.compose)
|
|
alias(libs.plugins.ksp)
|
|
}
|
|
|
|
// Release signing is optional and kept out of git: if keystore.properties
|
|
// is absent (e.g. on a fresh clone or in CI), the release build type simply
|
|
// stays unsigned rather than failing the build.
|
|
val keystorePropertiesFile = rootProject.file("keystore.properties")
|
|
val keystoreProperties = Properties()
|
|
val releaseKeystoreFile =
|
|
if (keystorePropertiesFile.exists()) {
|
|
keystorePropertiesFile.inputStream().use(keystoreProperties::load)
|
|
val missingProperties =
|
|
listOf("storeFile", "storePassword", "keyAlias", "keyPassword")
|
|
.filter { keystoreProperties.getProperty(it).isNullOrBlank() }
|
|
if (missingProperties.isNotEmpty()) {
|
|
throw GradleException(
|
|
"Invalid keystore.properties: missing ${missingProperties.joinToString()}. " +
|
|
"See DEVELOPER.md#signing-a-release-build-optional."
|
|
)
|
|
}
|
|
|
|
rootProject.file(keystoreProperties.getProperty("storeFile")).also {
|
|
if (!it.isFile) {
|
|
throw GradleException(
|
|
"Release signing keystore does not exist: ${it.absolutePath}. " +
|
|
"Regenerate the local signing setup described in " +
|
|
"DEVELOPER.md#signing-a-release-build-optional."
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
null
|
|
}
|
|
|
|
android {
|
|
namespace = "com.chmouel.liseur"
|
|
compileSdk = 37
|
|
|
|
defaultConfig {
|
|
applicationId = "com.chmouel.liseur"
|
|
minSdk = 26
|
|
targetSdk = 37
|
|
versionCode = 52
|
|
versionName = "0.23.0"
|
|
}
|
|
|
|
signingConfigs {
|
|
if (releaseKeystoreFile != null) {
|
|
create("release") {
|
|
storeFile = releaseKeystoreFile
|
|
storePassword = keystoreProperties.getProperty("storePassword")
|
|
keyAlias = keystoreProperties.getProperty("keyAlias")
|
|
keyPassword = keystoreProperties.getProperty("keyPassword")
|
|
// v3 records the signing lineage, which is what makes
|
|
// rotating this key possible later without breaking updates.
|
|
enableV3Signing = true
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = true
|
|
isShrinkResources = true
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro"
|
|
)
|
|
if (releaseKeystoreFile != null) {
|
|
signingConfig = signingConfigs.getByName("release")
|
|
}
|
|
}
|
|
debug {
|
|
isMinifyEnabled = false
|
|
}
|
|
// A build that installs *beside* the real app rather than over
|
|
// it. The debug build carries the production applicationId, so
|
|
// pushing a work in progress to a phone replaces a library —
|
|
// reading positions, highlights, notes — that cannot be rebuilt.
|
|
// The suffix gives this one its own package name, and with it
|
|
// its own database, DataStore and SAF grant, so the installed
|
|
// app is never touched.
|
|
//
|
|
// Only the id and the label differ: it is the debug build in
|
|
// every other respect, debug-signed and unminified, so what is
|
|
// tested here behaves the way `make run` behaves on the
|
|
// emulator. `release` is deliberately not its base — F-Droid
|
|
// rebuilds that one byte for byte, and nothing here should be
|
|
// able to reach it.
|
|
create("dev") {
|
|
initWith(getByName("debug"))
|
|
applicationIdSuffix = ".dev"
|
|
versionNameSuffix = "-dev"
|
|
isMinifyEnabled = false
|
|
}
|
|
}
|
|
|
|
// Two builds of the same app. `foss` is what F-Droid rebuilds and what
|
|
// the GitHub release carries; `play` adds features that call a
|
|
// non-free network service (Gemini's read-aloud voices) and ships only
|
|
// on Google Play. Everything for those features lives in src/play, so
|
|
// the foss APK has neither the code nor the endpoint, and F-Droid has
|
|
// nothing to flag.
|
|
flavorDimensions += "distribution"
|
|
productFlavors {
|
|
create("foss") {
|
|
dimension = "distribution"
|
|
}
|
|
create("play") {
|
|
dimension = "distribution"
|
|
isDefault = true
|
|
proguardFiles("proguard-rules-play.pro")
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
// Required by the Readium toolkit.
|
|
isCoreLibraryDesugaringEnabled = true
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
buildConfig = true
|
|
}
|
|
|
|
// Lists the bundled UI languages (from the values-* folders) so Android
|
|
// 13+ offers Liseur in the system's per-app language settings. The
|
|
// default language is declared in res/resources.properties.
|
|
androidResources {
|
|
generateLocaleConfig = true
|
|
}
|
|
|
|
packaging {
|
|
resources {
|
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
|
}
|
|
jniLibs {
|
|
// The only native code in the app arrives prebuilt inside
|
|
// library AARs. Left alone it is already stripped; letting
|
|
// AGP strip it again ties the bytes to whichever NDK the
|
|
// build machine happens to have, which is exactly the kind
|
|
// of environment leak that breaks reproducible builds.
|
|
keepDebugSymbols += "**/*.so"
|
|
}
|
|
}
|
|
|
|
dependenciesInfo {
|
|
// AGP's dependency manifest is encrypted with a Google key, so
|
|
// nobody but Play can read it and F-Droid cannot reproduce it.
|
|
// Dropping it keeps the APK byte-identical across rebuilds,
|
|
// which is what lets F-Droid publish our own signed binary.
|
|
includeInApk = false
|
|
includeInBundle = false
|
|
}
|
|
|
|
testOptions {
|
|
unitTests {
|
|
// The calibre-web clients log with android.util.Log, which is a
|
|
// stub in android.jar; without this every call throws.
|
|
isReturnDefaultValues = true
|
|
isIncludeAndroidResources = true
|
|
all { test ->
|
|
// The suites are independent (each brings its own
|
|
// MockWebServer and in-memory Room), so run them across
|
|
// the cores instead of one after another.
|
|
test.maxParallelForks =
|
|
(Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// F-Droid's buildserver runs Gradle with auto-download of toolchains
|
|
// switched off, so asking for a toolchain it does not already have fails
|
|
// the build. Pinning the JVM target instead needs no toolchain lookup and
|
|
// matches the source/target compatibility set in compileOptions above, so
|
|
// the F-Droid recipe can build the app as-is rather than patching this
|
|
// file from a prebuild step.
|
|
kotlin {
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_17)
|
|
}
|
|
}
|
|
|
|
ksp {
|
|
arg("room.schemaLocation", "$projectDir/schemas")
|
|
}
|
|
|
|
// The migration tests read the exported schemas through the asset
|
|
// manager, which under Robolectric means the variant's merged assets.
|
|
// Debug only: release builds must not carry a hundred kilobytes of JSON
|
|
// describing databases nobody will ever open.
|
|
android.sourceSets.getByName("debug").assets.srcDir("$projectDir/schemas")
|
|
|
|
dependencies {
|
|
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
|
|
|
implementation(libs.androidx.core.ktx)
|
|
implementation(libs.androidx.core.splashscreen)
|
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
|
implementation(libs.androidx.lifecycle.process)
|
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
|
implementation(libs.androidx.activity.compose)
|
|
implementation(libs.androidx.navigation3.runtime)
|
|
implementation(libs.androidx.navigation3.ui)
|
|
implementation(libs.compose.material3.adaptive.navigation3)
|
|
implementation(libs.androidx.fragment.compose)
|
|
|
|
implementation(platform(libs.compose.bom))
|
|
implementation(libs.compose.ui)
|
|
implementation(libs.compose.ui.graphics)
|
|
implementation(libs.compose.ui.tooling.preview)
|
|
implementation(libs.compose.material3)
|
|
implementation(libs.compose.material.icons.extended)
|
|
|
|
debugImplementation(libs.compose.ui.tooling)
|
|
|
|
implementation(libs.reorderable)
|
|
implementation(libs.readium.shared)
|
|
implementation(libs.readium.streamer)
|
|
implementation(libs.readium.navigator)
|
|
|
|
// Read aloud. Readium's TTS navigator walks the publication sentence
|
|
// by sentence and hands each one to our engine, which gets the voice
|
|
// from a speech service; media3-session keeps it playing with the
|
|
// screen off. Both are FOSS and in both builds; only the Gemini
|
|
// service is Play's.
|
|
implementation(libs.readium.navigator.media.tts)
|
|
implementation(libs.media3.session)
|
|
|
|
// Already inside readium-navigator, which uses it to lift footnotes out
|
|
// of a chapter. Declared here because the reader parses notes of its own
|
|
// that Readium does not recognise, and a transitive dependency is not a
|
|
// promise.
|
|
implementation(libs.jsoup)
|
|
|
|
// Play build only. ML Kit translates on the phone once a language
|
|
// model is downloaded, but the SDK is not free software and reports
|
|
// usage metrics to Google, so F-Droid and GitHub builds never carry
|
|
// it. See DEVELOPER.md, F-Droid readiness.
|
|
"playImplementation"(libs.mlkit.translate)
|
|
|
|
implementation(libs.room.runtime)
|
|
implementation(libs.room.ktx)
|
|
ksp(libs.room.compiler)
|
|
|
|
implementation(libs.okhttp)
|
|
implementation(libs.okhttp.sse)
|
|
implementation(libs.androidx.work.runtime.ktx)
|
|
implementation(libs.glance.appwidget)
|
|
implementation(libs.glance.material3)
|
|
|
|
implementation(libs.coil.compose)
|
|
implementation(libs.coil.network.okhttp)
|
|
// The platform cannot decode SVG, so a book whose cover is one has a
|
|
// blank tile on the shelf however well it reads. Covers are
|
|
// rasterized once at import, so this is only ever reached there.
|
|
implementation(libs.androidsvg)
|
|
implementation(libs.androidx.documentfile)
|
|
implementation(libs.androidx.datastore.preferences)
|
|
|
|
testImplementation(libs.junit)
|
|
// The app uses the platform's org.json; unit tests need a real implementation
|
|
// of it because android.jar's is a stub. Test-only, never shipped.
|
|
testImplementation(libs.json)
|
|
// Lets the calibre-web clients be tested against a real socket, which is
|
|
// the only way to cover redirects, failed logins and refused deletes.
|
|
testImplementation(libs.mockwebserver)
|
|
testImplementation(libs.okhttp.tls)
|
|
// Gives the sync coordinator's tests a clock they control, which is
|
|
// the only way to pin down what happens when two requests overlap.
|
|
testImplementation(libs.coroutines.test)
|
|
// Runs the real Room database on the JVM, so the reading-position
|
|
// state machine is tested by the same SQL that ships. CI runs unit
|
|
// tests only, so an androidTest source set would never be exercised.
|
|
testImplementation(libs.robolectric)
|
|
testImplementation(libs.androidx.test.core)
|
|
// Replays every exported schema against the next migration, so an
|
|
// upgrade cannot quietly drop a column full of reading positions.
|
|
testImplementation(libs.room.testing)
|
|
}
|