Skip to content

Commit c37fd82

Browse files
committed
refactor: Extract shared build logic into reusable Gradle convention extensions
- Move repetitive build script logic from `app/android`, `app/web`, and `core/data/db-sqldelight` into a centralized `ProjectExtensions.kt` within the `build-logic` module. - Implement `forceAndroidXDependencyVersions()` to standardize lifecycle and savedstate dependency resolution across Android modules. - Implement `configureWasmJsChromeForKarmaTests()` and `configureWebSqlite3mcWasmResources()` to encapsulate WebAssembly test environment setup and SQLite3MC binary management. - Implement `excludeSqliteJdbcFromNonTestConfigurations()` to handle SQLDelight driver substitutions consistently. - Apply the `gradle.convention` plugin to `app/android` and `app/web` modules to leverage the new helpers. - Update `build-logic` documentation and AI agent guidelines to encourage the use of thin Gradle scripts and centralized project extensions. - Add `gradle-download-task` as a dependency for the convention module to support automated resource fetching.
1 parent f8953f8 commit c37fd82

7 files changed

Lines changed: 126 additions & 84 deletions

File tree

app/android/build.gradle.kts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
@file:Suppress("UnstableApiUsage")
22

3+
import com.softartdev.notedelight.forceAndroidXDependencyVersions
34
import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension
45
import com.google.firebase.crashlytics.buildtools.gradle.tasks.UploadMappingFileTask
56
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
67
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
78

89
plugins {
10+
alias(libs.plugins.gradle.convention)
911
alias(libs.plugins.android.application)
1012
alias(libs.plugins.compose)
1113
alias(libs.plugins.compose.compiler)
@@ -119,18 +121,4 @@ tasks.withType<UploadMappingFileTask> {
119121
dependsOn("processDebugGoogleServices")
120122
}
121123

122-
configurations.all {
123-
resolutionStrategy {
124-
sequenceOf(
125-
"common", "common-java8", "runtime", "runtime-ktx", "runtime-compose", "viewmodel", "viewmodel-ktx", "viewmodel-compose", "viewmodel-savedstate", "livedata", "livedata-core", "livedata-core-ktx", "process"
126-
).forEach { depName: String ->
127-
force("androidx.lifecycle:lifecycle-$depName:${libs.versions.androidxLifecycle.get()}")
128-
}
129-
force("androidx.savedstate:savedstate:1.4.0")
130-
force("androidx.savedstate:savedstate-ktx:1.4.0")
131-
force("androidx.savedstate:savedstate-compose:1.4.0")
132-
133-
force("androidx.concurrent:concurrent-futures:1.2.0")
134-
force("com.google.errorprone:error_prone_annotations:2.30.0")
135-
}
136-
}
124+
project.forceAndroidXDependencyVersions()

app/web/build.gradle.kts

Lines changed: 5 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
@file:OptIn(ExperimentalWasmDsl::class)
22

3-
import de.undercouch.gradle.tasks.download.Download
3+
import com.softartdev.notedelight.configureWasmJsChromeForKarmaTests
4+
import com.softartdev.notedelight.configureWebSqlite3mcWasmResources
45
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
5-
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
66
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
77

88
plugins {
9+
alias(libs.plugins.gradle.convention)
910
alias(libs.plugins.kotlin.multiplatform)
1011
alias(libs.plugins.compose)
1112
alias(libs.plugins.compose.compiler)
@@ -60,62 +61,5 @@ kotlin {
6061
}
6162
}
6263

63-
// Chrome binary for Karma tests.
64-
// Auto-detects Chrome on macOS/Linux/Windows when CHROME_BIN is not set.
65-
// NOTE: WasmGC module compilation in Chrome is very slow for large modules (~30 MB test WASM).
66-
// The main() function is guarded with isKarmaTestRunner() check to prevent the production app
67-
// from launching during tests (see main.kt).
68-
val defaultChromePaths: List<String> = listOf(
69-
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", // macOS
70-
"/usr/bin/google-chrome-stable", // Linux (stable)
71-
"/usr/bin/google-chrome", // Linux
72-
"/usr/bin/chromium-browser", // Linux (Chromium)
73-
"/usr/bin/chromium", // Linux (Chromium alt)
74-
"C:/Program Files/Google/Chrome/Application/chrome.exe", // Windows
75-
"C:/Program Files (x86)/Google/Chrome/Application/chrome.exe", // Windows x86
76-
)
77-
val chromeBinary: String? = providers.environmentVariable("CHROME_BIN").orNull
78-
?: defaultChromePaths.firstOrNull { file(it).exists() }
79-
80-
tasks.named<KotlinJsTest>("wasmJsBrowserTest").configure {
81-
enabled = chromeBinary != null
82-
if (chromeBinary != null) {
83-
environment("CHROME_BIN", chromeBinary)
84-
}
85-
}
86-
87-
// SQLite3MultipleCiphers WASM build with encryption support
88-
// See https://github.com/utelle/SQLite3MultipleCiphers/releases for the latest version
89-
val sqlite3mcVersion = "2.2.7"
90-
val sqliteVersion = "3.51.2"
91-
val sqliteWasmVersion = "3510200"
92-
val sqlite3mcZip = "sqlite3mc-$sqlite3mcVersion-sqlite-$sqliteVersion-wasm.zip"
93-
val sqliteDownload: Provider<Download> = tasks.register("sqliteDownload", Download::class.java) {
94-
src("https://github.com/utelle/SQLite3MultipleCiphers/releases/download/v$sqlite3mcVersion/$sqlite3mcZip")
95-
dest(layout.buildDirectory.dir("tmp"))
96-
onlyIfModified(true)
97-
}
98-
val sqliteUnzip: TaskProvider<Copy> = tasks.register("sqliteUnzip", Copy::class.java) {
99-
dependsOn(sqliteDownload)
100-
from(zipTree(layout.buildDirectory.dir("tmp/$sqlite3mcZip"))) {
101-
include("sqlite3mc-wasm-$sqliteWasmVersion/jswasm/**")
102-
exclude("**/*worker1*") // We use our own worker
103-
eachFile {
104-
relativePath = RelativePath(true, *relativePath.segments.drop(2).toTypedArray())
105-
}
106-
}
107-
into(layout.buildDirectory.dir("sqlite"))
108-
includeEmptyDirs = false
109-
}
110-
// Hook the unzip task into the wasmJs resource processing
111-
tasks.named("wasmJsProcessResources").configure {
112-
dependsOn(sqliteUnzip)
113-
}
114-
tasks.named<Copy>("wasmJsTestProcessResources").configure {
115-
dependsOn(sqliteUnzip)
116-
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
117-
}
118-
119-
tasks.named<Sync>("wasmJsBrowserDistribution").configure {
120-
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
121-
}
64+
project.configureWasmJsChromeForKarmaTests()
65+
project.configureWebSqlite3mcWasmResources()

build-logic/README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ build-logic (Gradle Convention Plugins)
2121
│ │ └── main/
2222
│ │ └── kotlin/
2323
│ │ ├── GradleConventionPlugin.kt
24-
│ │ └── # Other convention plugins
24+
│ │ └── com/softartdev/notedelight/ProjectExtensions.kt
2525
│ ├── build.gradle.kts
2626
│ └── settings.gradle.kts (not present, uses root settings)
2727
├── gradle.properties
@@ -70,6 +70,29 @@ gradlePlugin {
7070
}
7171
```
7272

73+
### Shared Build Script Helpers
74+
75+
`convention/src/main/kotlin/com/softartdev/notedelight/ProjectExtensions.kt` contains reusable extension functions used by module `build.gradle.kts` files to keep scripts small and focused.
76+
77+
Current extracted helpers include:
78+
- `disableIosReleaseTasks()` for iOS pod release link task disabling
79+
- `excludeSqliteJdbcFromNonTestConfigurations()` for SQLDelight/JVM driver substitution
80+
- `configureWasmJsChromeForKarmaTests()` for web Karma Chrome auto-detection (`CHROME_BIN`)
81+
- `configureWebSqlite3mcWasmResources()` for SQLite3MultipleCiphers WASM download/unzip task wiring
82+
- `forceAndroidXDependencyVersions()` for Android dependency resolution forcing
83+
84+
Typical module usage:
85+
86+
```kotlin
87+
import com.softartdev.notedelight.forceAndroidXDependencyVersions
88+
89+
plugins {
90+
alias(libs.plugins.gradle.convention)
91+
}
92+
93+
project.forceAndroidXDependencyVersions()
94+
```
95+
7396
## Usage in Modules
7497

7598
Apply convention plugins in module `build.gradle.kts`:
@@ -411,4 +434,3 @@ Potential convention plugins to add:
411434
- [Gradle Best Practices](https://docs.gradle.org/current/userguide/authoring_maintainable_build_scripts.html)
412435
- [Kotlin DSL](https://docs.gradle.org/current/userguide/kotlin_dsl.html)
413436
- [Convention Plugins](https://docs.gradle.org/current/samples/sample_convention_plugins.html)
414-

build-logic/convention/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ tasks.withType<KotlinCompile>().configureEach {
1717

1818
dependencies {
1919
compileOnly(kotlin("gradle-plugin", version = libs.versions.kotlin.get()))
20+
compileOnly("de.undercouch:gradle-download-task:${libs.versions.download.get()}")
2021
}
2122

2223
tasks {

build-logic/convention/src/main/kotlin/com/softartdev/notedelight/ProjectExtensions.kt

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package com.softartdev.notedelight
22

3+
import de.undercouch.gradle.tasks.download.Download
34
import org.gradle.api.Project
45
import org.gradle.api.artifacts.VersionCatalog
56
import org.gradle.api.artifacts.VersionCatalogsExtension
7+
import org.gradle.api.file.DuplicatesStrategy
8+
import org.gradle.api.file.RelativePath
9+
import org.gradle.api.tasks.Copy
10+
import org.gradle.api.tasks.Sync
611
import org.gradle.kotlin.dsl.getByType
12+
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
713

814
val Project.libs
915
get(): VersionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")
@@ -17,3 +23,87 @@ fun Project.disableIosReleaseTasks() {
1723
enabled = false
1824
}
1925
}
26+
27+
fun Project.excludeSqliteJdbcFromNonTestConfigurations() {
28+
configurations
29+
.matching { !it.name.contains("test", ignoreCase = true) }
30+
.configureEach {
31+
exclude(mapOf("group" to "org.xerial", "module" to "sqlite-jdbc"))
32+
}
33+
}
34+
35+
fun Project.configureWasmJsChromeForKarmaTests(defaultChromePaths: List<String> = DEFAULT_CHROME_PATHS) {
36+
val chromeBinary: String? = providers.environmentVariable("CHROME_BIN").orNull
37+
?: defaultChromePaths.firstOrNull { file(it).exists() }
38+
39+
tasks.named("wasmJsBrowserTest", KotlinJsTest::class.java).configure {
40+
enabled = chromeBinary != null
41+
chromeBinary?.let { environment("CHROME_BIN", it) }
42+
}
43+
}
44+
45+
fun Project.configureWebSqlite3mcWasmResources(
46+
sqlite3mcVersion: String = SQLITE3MC_VERSION,
47+
sqliteVersion: String = SQLITE_VERSION,
48+
sqliteWasmVersion: String = SQLITE_WASM_VERSION,
49+
) {
50+
val sqlite3mcZip = "sqlite3mc-$sqlite3mcVersion-sqlite-$sqliteVersion-wasm.zip"
51+
val sqliteDownload = tasks.register("sqliteDownload", Download::class.java) {
52+
src("https://github.com/utelle/SQLite3MultipleCiphers/releases/download/v$sqlite3mcVersion/$sqlite3mcZip")
53+
dest(layout.buildDirectory.dir("tmp"))
54+
onlyIfModified(true)
55+
}
56+
val sqliteUnzip = tasks.register("sqliteUnzip", Copy::class.java) {
57+
dependsOn(sqliteDownload)
58+
from(zipTree(layout.buildDirectory.file("tmp/$sqlite3mcZip"))) {
59+
include("sqlite3mc-wasm-$sqliteWasmVersion/jswasm/**")
60+
exclude("**/*worker1*")
61+
eachFile {
62+
relativePath = RelativePath(true, *relativePath.segments.drop(2).toTypedArray())
63+
}
64+
}
65+
into(layout.buildDirectory.dir("sqlite"))
66+
includeEmptyDirs = false
67+
}
68+
tasks.named("wasmJsProcessResources").configure {
69+
dependsOn(sqliteUnzip)
70+
}
71+
tasks.named("wasmJsTestProcessResources", Copy::class.java).configure {
72+
dependsOn(sqliteUnzip)
73+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
74+
}
75+
tasks.named("wasmJsBrowserDistribution", Sync::class.java).configure {
76+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
77+
}
78+
}
79+
80+
fun Project.forceAndroidXDependencyVersions() {
81+
val lifecycleVersion: String = libs.findVersion("androidxLifecycle").get().requiredVersion
82+
val lifecycleModules: List<String> = listOf("common", "common-java8", "runtime", "runtime-ktx", "runtime-compose", "viewmodel", "viewmodel-ktx", "viewmodel-compose", "viewmodel-savedstate", "livedata", "livedata-core", "livedata-core-ktx", "process")
83+
configurations.all {
84+
resolutionStrategy {
85+
lifecycleModules.forEach { depName: String ->
86+
force("androidx.lifecycle:lifecycle-$depName:$lifecycleVersion")
87+
}
88+
force("androidx.savedstate:savedstate:1.4.0")
89+
force("androidx.savedstate:savedstate-ktx:1.4.0")
90+
force("androidx.savedstate:savedstate-compose:1.4.0")
91+
force("androidx.concurrent:concurrent-futures:1.2.0")
92+
force("com.google.errorprone:error_prone_annotations:2.30.0")
93+
}
94+
}
95+
}
96+
97+
private const val SQLITE3MC_VERSION: String = "2.2.7"
98+
private const val SQLITE_VERSION: String = "3.51.2"
99+
private const val SQLITE_WASM_VERSION: String = "3510200"
100+
101+
private val DEFAULT_CHROME_PATHS: List<String> = listOf(
102+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
103+
"/usr/bin/google-chrome-stable",
104+
"/usr/bin/google-chrome",
105+
"/usr/bin/chromium-browser",
106+
"/usr/bin/chromium",
107+
"C:/Program Files/Google/Chrome/Application/chrome.exe",
108+
"C:/Program Files (x86)/Google/Chrome/Application/chrome.exe",
109+
)

core/data/db-sqldelight/build.gradle.kts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@file:OptIn(ExperimentalWasmDsl::class)
22

3+
import com.softartdev.notedelight.excludeSqliteJdbcFromNonTestConfigurations
34
import org.gradle.internal.os.OperatingSystem
45
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
56
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
@@ -12,12 +13,7 @@ plugins {
1213
alias(libs.plugins.kotlin.cocoapods)
1314
}
1415

15-
// Replace standard sqlite-jdbc with sqlite-jdbc-crypt for encryption support
16-
// Using Willena's fork (io.github.willena:sqlite-jdbc) which provides SQLCipher support
17-
// via SQLite3 Multiple Ciphers
18-
configurations.matching { !it.name.contains("test", ignoreCase = true) }.all {
19-
exclude(group = "org.xerial", module = "sqlite-jdbc")
20-
}
16+
project.excludeSqliteJdbcFromNonTestConfigurations()
2117

2218
kotlin {
2319
jvmToolchain(libs.versions.jdk.get().toInt())

docs/AI_AGENT_GUIDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ class YourViewModelTest {
229229
3. **Refactor incrementally**: Small, safe changes
230230
4. **Run tests frequently**: After each change
231231
5. **Verify all platforms**: Test on Android, Desktop, etc.
232+
6. **Keep Gradle scripts thin**: Move repeated/non-trivial `build.gradle.kts` logic into `build-logic/convention/src/main/kotlin/com/softartdev/notedelight/ProjectExtensions.kt` and call it from modules
232233

233234
## Pattern Recognition
234235

0 commit comments

Comments
 (0)