71
build-logic/convention/build.gradle.kts
Normal file
71
build-logic/convention/build.gradle.kts
Normal file
@@ -0,0 +1,71 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
group = "com.example.architecture.buildlogic"
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// The convention plugins apply these by id, so they only need them at compile time.
|
||||
compileOnly(libs.android.gradlePlugin)
|
||||
compileOnly(libs.kotlin.gradlePlugin)
|
||||
compileOnly(libs.compose.compiler.gradlePlugin)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
register("androidApplication") {
|
||||
id = "architecture.android.application"
|
||||
implementationClass = "com.example.architecture.convention.AndroidApplicationConventionPlugin"
|
||||
}
|
||||
register("androidLibrary") {
|
||||
id = "architecture.android.library"
|
||||
implementationClass = "com.example.architecture.convention.AndroidLibraryConventionPlugin"
|
||||
}
|
||||
register("androidFeature") {
|
||||
id = "architecture.android.feature"
|
||||
implementationClass = "com.example.architecture.convention.AndroidFeatureConventionPlugin"
|
||||
}
|
||||
register("androidFeatureViews") {
|
||||
id = "architecture.android.feature.views"
|
||||
implementationClass = "com.example.architecture.convention.AndroidFeatureViewsConventionPlugin"
|
||||
}
|
||||
register("domainModule") {
|
||||
id = "architecture.domain.module"
|
||||
implementationClass = "com.example.architecture.convention.DomainModuleConventionPlugin"
|
||||
}
|
||||
register("androidUnitTest") {
|
||||
id = "architecture.android.unit.test"
|
||||
implementationClass = "com.example.architecture.convention.AndroidUnitTestConventionPlugin"
|
||||
}
|
||||
register("compose") {
|
||||
id = "architecture.compose"
|
||||
implementationClass = "com.example.architecture.convention.ComposeConventionPlugin"
|
||||
}
|
||||
register("koin") {
|
||||
id = "architecture.koin"
|
||||
implementationClass = "com.example.architecture.convention.KoinConventionPlugin"
|
||||
}
|
||||
register("ktor") {
|
||||
id = "architecture.ktor"
|
||||
implementationClass = "com.example.architecture.convention.KtorConventionPlugin"
|
||||
}
|
||||
register("kotlinxSerialization") {
|
||||
id = "architecture.kotlinx.serialization"
|
||||
implementationClass = "com.example.architecture.convention.KotlinxSerializationConventionPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import org.gradle.api.JavaVersion
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
|
||||
/**
|
||||
* Configures the single `:app` module: applicationId, SDK levels, Java 17, and the release
|
||||
* build type. Compose is added separately by [ComposeConventionPlugin].
|
||||
*/
|
||||
class AndroidApplicationConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("com.android.application")
|
||||
|
||||
extensions.configure<ApplicationExtension> {
|
||||
namespace = "com.example.architecture"
|
||||
compileSdk = COMPILE_SDK
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.architecture"
|
||||
minSdk = MIN_SDK
|
||||
targetSdk = TARGET_SDK
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = false
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configureKotlinJvmToolchain()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* A Compose-based feature presentation module: Android library + Compose + Koin, plus the common
|
||||
* feature stack (lifecycle, type-safe navigation, coroutines, Coil). Used by every
|
||||
* `:feature:*:presentation-compose` and the MVVM `:feature:about:presentation`.
|
||||
*/
|
||||
class AndroidFeatureConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("architecture.android.library")
|
||||
pluginManager.apply("architecture.compose")
|
||||
pluginManager.apply("architecture.koin")
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findLibrary("androidx-core-ktx").get())
|
||||
add("implementation", libs.findBundle("lifecycle-compose").get())
|
||||
add("implementation", libs.findLibrary("androidx-navigation-compose").get())
|
||||
add("implementation", libs.findLibrary("kotlinx-coroutines-android").get())
|
||||
add("implementation", libs.findLibrary("koin-androidx-compose").get())
|
||||
add("implementation", libs.findLibrary("coil-compose").get())
|
||||
add("implementation", libs.findLibrary("coil-network-okhttp").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* A classic Views feature renderer: Android library + Koin, ViewBinding ON, Compose OFF.
|
||||
* Brings Fragment / RecyclerView / Material / AppCompat and Coil's ImageView loader so the
|
||||
* Views renderer can drive the same ViewModel as the Compose one.
|
||||
*/
|
||||
class AndroidFeatureViewsConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("architecture.android.library")
|
||||
pluginManager.apply("architecture.koin")
|
||||
|
||||
extensions.configure<LibraryExtension> {
|
||||
buildFeatures.viewBinding = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findLibrary("androidx-core-ktx").get())
|
||||
add("implementation", libs.findBundle("views").get())
|
||||
add("implementation", libs.findLibrary("androidx-lifecycle-runtime-ktx").get())
|
||||
add("implementation", libs.findLibrary("androidx-lifecycle-viewmodel-ktx").get())
|
||||
add("implementation", libs.findLibrary("kotlinx-coroutines-android").get())
|
||||
add("implementation", libs.findLibrary("coil-core").get())
|
||||
add("implementation", libs.findLibrary("coil-network-okhttp").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import org.gradle.api.JavaVersion
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
|
||||
/**
|
||||
* Base configuration shared by every Android library module. Each module still declares its own
|
||||
* `namespace` in its build file.
|
||||
*/
|
||||
class AndroidLibraryConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("com.android.library")
|
||||
|
||||
extensions.configure<LibraryExtension> {
|
||||
compileSdk = COMPILE_SDK
|
||||
|
||||
defaultConfig {
|
||||
minSdk = MIN_SDK
|
||||
// Used by instrumented (androidTest) tests, e.g. the Compose UI test in
|
||||
// :feature:characters:presentation-compose. Harmless for modules without androidTest.
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
configureKotlinJvmToolchain()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
|
||||
/**
|
||||
* Runs an Android library module's local unit tests (`src/test`) on the **JUnit 5 platform** with the
|
||||
* shared `unit-test` toolset (JUnit Jupiter, kotlinx-coroutines-test, Turbine, AssertK).
|
||||
*
|
||||
* Deliberately does NOT use the `de.mannodermaus.android-junit5` Gradle plugin: its 1.11.x line
|
||||
* targets AGP 8.x and we build on AGP 9.0. It isn't needed for *local* unit tests anyway -
|
||||
* `com.android.build.gradle.tasks.factory.AndroidUnitTest` extends Gradle's [Test] task, so calling
|
||||
* `useJUnitPlatform()` on it is enough (this mirrors `DomainModuleConventionPlugin`, which does the
|
||||
* same for pure-JVM modules).
|
||||
*/
|
||||
class AndroidUnitTestConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
dependencies {
|
||||
add("testImplementation", libs.findBundle("unit-test").get())
|
||||
add("testRuntimeOnly", libs.findLibrary("junit-jupiter-engine").get())
|
||||
// Gradle 9 dropped the bundled launcher; JUnit 5 won't start without it.
|
||||
add("testRuntimeOnly", libs.findLibrary("junit-platform-launcher").get())
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import com.android.build.api.dsl.LibraryExtension
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* Enables Jetpack Compose on an Android application or library module: applies the Compose
|
||||
* compiler plugin, turns on the `compose` build feature, and wires the BOM-aligned Compose deps.
|
||||
*
|
||||
* Order-independent: `withPlugin` enables the build feature whenever the Android plugin is applied,
|
||||
* regardless of whether this plugin runs before or after it.
|
||||
*/
|
||||
class ComposeConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("org.jetbrains.kotlin.plugin.compose")
|
||||
|
||||
pluginManager.withPlugin("com.android.library") {
|
||||
extensions.configure<LibraryExtension> { buildFeatures.compose = true }
|
||||
}
|
||||
pluginManager.withPlugin("com.android.application") {
|
||||
extensions.configure<ApplicationExtension> { buildFeatures.compose = true }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// `implementation` (not api): every Compose consumer applies this convention itself, so
|
||||
// Compose must NOT leak transitively - that keeps the UI-agnostic presentation module
|
||||
// (which depends on core:presentation) free of Compose.
|
||||
val bom = platform(libs.findLibrary("androidx-compose-bom").get())
|
||||
add("implementation", bom)
|
||||
add("androidTestImplementation", bom)
|
||||
add("implementation", libs.findBundle("compose").get())
|
||||
add("debugImplementation", libs.findLibrary("androidx-compose-ui-tooling").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
|
||||
/**
|
||||
* Pure-Kotlin (JVM) module for the domain layer: no Android dependencies. Adds Coroutines (for
|
||||
* `Flow`-returning repository interfaces) and runs unit tests on the JUnit 5 platform.
|
||||
*/
|
||||
class DomainModuleConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("org.jetbrains.kotlin.jvm")
|
||||
|
||||
configureKotlinJvmToolchain()
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findLibrary("kotlinx-coroutines-core").get())
|
||||
add("testImplementation", libs.findLibrary("junit-jupiter-api").get())
|
||||
add("testImplementation", libs.findLibrary("assertk").get())
|
||||
// Domain doesn't consume the `unit-test` bundle, so MockK and the coroutines
|
||||
// test artifact are added explicitly here.
|
||||
add("testImplementation", libs.findLibrary("mockk").get())
|
||||
add("testImplementation", libs.findLibrary("kotlinx-coroutines-test").get())
|
||||
add("testRuntimeOnly", libs.findLibrary("junit-jupiter-engine").get())
|
||||
// Gradle 9 dropped the bundled launcher; JUnit 5 won't start without it.
|
||||
add("testRuntimeOnly", libs.findLibrary("junit-platform-launcher").get())
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* Adds the Koin BOM + core/android dependencies. Compose-specific Koin (`koin-androidx-compose`)
|
||||
* is added only by [AndroidFeatureConventionPlugin] so UI-agnostic modules stay Compose-free.
|
||||
*/
|
||||
class KoinConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
dependencies {
|
||||
val bom = platform(libs.findLibrary("koin-bom").get())
|
||||
add("implementation", bom)
|
||||
add("implementation", libs.findBundle("koin").get())
|
||||
add("testImplementation", libs.findLibrary("koin-test").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* Applies the KotlinX Serialization compiler plugin + JSON runtime for modules that hold
|
||||
* `@Serializable` DTOs or navigation routes but do not need the full Ktor stack.
|
||||
*/
|
||||
class KotlinxSerializationConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("org.jetbrains.kotlin.plugin.serialization")
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findLibrary("kotlinx-serialization-json").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.dependencies
|
||||
|
||||
/**
|
||||
* Wires the Ktor client bundle (OkHttp engine, content negotiation, JSON, logging) and the
|
||||
* KotlinX Serialization runtime. Applies the serialization compiler plugin so `@Serializable`
|
||||
* DTOs compile.
|
||||
*/
|
||||
class KtorConventionPlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) = with(target) {
|
||||
pluginManager.apply("org.jetbrains.kotlin.plugin.serialization")
|
||||
|
||||
dependencies {
|
||||
add("implementation", libs.findBundle("ktor").get())
|
||||
add("implementation", libs.findLibrary("kotlinx-serialization-json").get())
|
||||
add("testImplementation", libs.findLibrary("ktor-client-mock").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.example.architecture.convention
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.VersionCatalog
|
||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||
|
||||
internal const val COMPILE_SDK = 36
|
||||
internal const val MIN_SDK = 24
|
||||
internal const val TARGET_SDK = 36
|
||||
internal const val JVM_TARGET = 17
|
||||
|
||||
/** Type-safe accessor for the shared `libs` version catalog from inside a convention plugin. */
|
||||
internal val Project.libs: VersionCatalog
|
||||
get() = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
|
||||
/**
|
||||
* Pins the Kotlin JVM toolchain. Works for both Android modules and pure-Kotlin (`jvm`) modules
|
||||
* because [KotlinProjectExtension] is the common supertype of both kotlin extensions.
|
||||
*/
|
||||
internal fun Project.configureKotlinJvmToolchain() {
|
||||
extensions.configure<KotlinProjectExtension> {
|
||||
jvmToolchain(JVM_TARGET)
|
||||
}
|
||||
}
|
||||
26
build-logic/settings.gradle.kts
Normal file
26
build-logic/settings.gradle.kts
Normal file
@@ -0,0 +1,26 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
versionCatalogs {
|
||||
// Reuse the single source of truth for versions.
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "build-logic"
|
||||
include(":convention")
|
||||
Reference in New Issue
Block a user