Skip to content

Commit

Permalink
refactor(build): define convention plugins to factor out common build…
Browse files Browse the repository at this point in the history
… logic (#630)

* define convention plugins for common usages

- Kotlin Multiplatform library (with extensions)
- Compose Multiplatform
- Koin with annotations
- Sentry configuration
- testing business logic
- UI testing

* move notification icon in module where it's used

* remove accidental material import

* make reading Sentry DSN cross-platform (yay!)

* apply convention plugins to core modules

* apply convention plugins to domain modules

* apply convention plugins to feature modules
  • Loading branch information
AkesiSeli authored Dec 9, 2024
1 parent bd998f9 commit 29626ba
Show file tree
Hide file tree
Showing 76 changed files with 689 additions and 3,485 deletions.
58 changes: 58 additions & 0 deletions build-logic/convention/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
plugins {
`kotlin-dsl`
}

group = "com.livefast.eattrash.raccoonforfriendica.buildlogic"

repositories {
google()
mavenCentral()
gradlePluginPortal()
}

dependencies {
compileOnly(libs.gradle)
compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.compose.gradlePlugin)
compileOnly(libs.ksp.gradlePlugin)
implementation(libs.kotlinpoet)
}

gradlePlugin {
plugins {
register("sentryDsn") {
id = "com.livefast.eattrash.sentryDsn"
implementationClass = "plugins.SentryDsnPlugin"
}

register("composeMultiplatform") {
id = "com.livefast.eattrash.composeMultiplatform"
implementationClass = "plugins.ComposeMultiplatformPlugin"
}

register("koinWithKsp") {
id = "com.livefast.eattrash.koinWithKsp"
implementationClass = "plugins.KoinWithKspPlugin"
}

register("kotlinMultiplatform") {
id = "com.livefast.eattrash.kotlinMultiplatform"
implementationClass = "plugins.KotlinMultiplatformPlugin"
}

register("serializationPlugin") {
id = "com.livefast.eattrash.serialization"
implementationClass = "plugins.SerializationPlugin"
}

register("testPlugin") {
id = "com.livefast.eattrash.test"
implementationClass = "plugins.TestPlugin"
}

register("uiTestPlugin") {
id = "com.livefast.eattrash.uiTest"
implementationClass = "plugins.UiTestPlugin"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package extensions

import org.gradle.api.Project
import org.jetbrains.compose.ComposePlugin
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

internal fun Project.configureComposeMultiplatform(extension: KotlinMultiplatformExtension) =
extension.apply {
val composeDeps = extensions.getByType(ComposePlugin.Dependencies::class.java)
sourceSets.apply {
commonMain {
dependencies {
implementation(composeDeps.runtime)
implementation(composeDeps.foundation)
implementation(composeDeps.material3)
implementation(composeDeps.materialIconsExtended)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package extensions

import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import utils.dependency
import utils.libs

internal fun Project.configureKoinAnnotations(extension: KotlinMultiplatformExtension) =
extension.apply {
compilerOptions {
freeCompilerArgs.add("-Xexpect-actual-classes")
}

sourceSets.apply {
commonMain {
dependencies {
implementation(libs.findLibrary("koin-core").dependency)
api(libs.findLibrary("koin-annotations").dependency)
}

kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package extensions

import com.android.build.gradle.LibraryExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import utils.PACKAGE_PREFIX
import utils.libs
import utils.version

internal fun Project.configureKotlinAndroid(extension: LibraryExtension) =
extension.apply {
val moduleName = path.split(":").drop(1).joinToString(".")
namespace = if (moduleName.isNotEmpty()) "$PACKAGE_PREFIX.$moduleName" else PACKAGE_PREFIX

compileSdk = libs.findVersion("android-compileSdk").version
defaultConfig {
minSdk = libs.findVersion("android-minSdk").version
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package extensions

import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.kotlin.dsl.create
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

interface CustomKotlinMultiplatformExtension {
val additionalLinkerOptionForIos: Property<String>
}

internal fun Project.configureKotlinMultiplatform(extension: KotlinMultiplatformExtension) =
extension.apply {
applyDefaultHierarchyTemplate()
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8)
}
}

val moduleName = path.split(":").drop(1).joinToString(".")
val customExtension =
project.extensions
.create<CustomKotlinMultiplatformExtension>("customKotlinMultiplatformExtension")
val linkerOption = customExtension.additionalLinkerOptionForIos.getOrElse("")
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach {
it.binaries.framework {
baseName = moduleName
isStatic = true
if (linkerOption.isNotEmpty()) {
linkerOpts.add(linkerOption)
}
}
}
}
24 changes: 24 additions & 0 deletions build-logic/convention/src/main/kotlin/extensions/ConfigureTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package extensions

import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import utils.dependency
import utils.libs

internal fun Project.configureTest(extension: KotlinMultiplatformExtension) =
extension.apply {
sourceSets.apply {
commonTest {
dependencies {
implementation(kotlin("test"))
implementation(libs.findLibrary("kotlinx-coroutines-test").dependency)
implementation(libs.findLibrary("turbine").dependency)
}
}
androidUnitTest {
dependencies {
implementation(project(":core:testutils"))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package extensions

import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import org.jetbrains.compose.ComposePlugin
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
import utils.dependency
import utils.libs

internal fun Project.configureUiTest(extension: KotlinMultiplatformExtension) =
extension.apply {
val composeDeps = extensions.getByType(ComposePlugin.Dependencies::class.java)
sourceSets.apply {
androidUnitTest {
dependencies {
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(composeDeps.uiTest)
implementation(libs.findLibrary("compose-ui-test").dependency)
implementation(libs.findLibrary("robolectric").dependency)
implementation(project(":core:testutils"))
}
}
}
androidTarget {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test)
}

dependencies {
add("debugImplementation", libs.findLibrary("compose-ui-test-manifest").dependency)
}
}

internal fun Project.configureUiTestAndroid(extension: LibraryExtension) =
extension.apply {
defaultConfig {
testOptions.unitTests.isIncludeAndroidResources = true
testInstrumentationRunner = "org.robolectric.RobolectricTestRunner"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package plugins

import extensions.configureComposeMultiplatform
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import utils.libs
import utils.pluginId

class ComposeMultiplatformPlugin : Plugin<Project> {
override fun apply(target: Project): Unit =
with(target) {
with(pluginManager) {
apply(libs.findPlugin("jetbrains-compose").pluginId)
apply(libs.findPlugin("compose-compiler").pluginId)
}

extensions.configure(
KotlinMultiplatformExtension::class.java,
::configureComposeMultiplatform,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package plugins

import com.google.devtools.ksp.gradle.KspExtension
import extensions.configureKoinAnnotations
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import utils.dependency
import utils.libs
import utils.pluginId

class KoinWithKspPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply(libs.findPlugin("ksp").pluginId)
}

extensions.configure(
KotlinMultiplatformExtension::class.java,
::configureKoinAnnotations,
)

extensions.configure(KspExtension::class.java) {
arg("KOIN_DEFAULT_MODULE", "false")
}

dependencies.apply {
add("kspCommonMainMetadata", libs.findLibrary("koin-ksp").dependency)
add("kspAndroid", libs.findLibrary("koin-ksp").dependency)
add("kspIosX64", libs.findLibrary("koin-ksp").dependency)
add("kspIosArm64", libs.findLibrary("koin-ksp").dependency)
add("kspIosSimulatorArm64", libs.findLibrary("koin-ksp").dependency)
}

tasks.withType(KotlinCompilationTask::class.java).configureEach {
if (name != "kspCommonMainKotlinMetadata") {
dependsOn("kspCommonMainKotlinMetadata")
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package plugins

import com.android.build.gradle.LibraryExtension
import extensions.configureKotlinAndroid
import extensions.configureKotlinMultiplatform
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import utils.libs
import utils.pluginId

class KotlinMultiplatformPlugin : Plugin<Project> {
override fun apply(target: Project): Unit =
with(target) {
with(pluginManager) {
apply(libs.findPlugin("kotlin-multiplatform").pluginId)
apply(libs.findPlugin("android-library").pluginId)
}

extensions.configure(
KotlinMultiplatformExtension::class.java,
::configureKotlinMultiplatform,
)
extensions.configure(
LibraryExtension::class.java,
::configureKotlinAndroid,
)
}
}
31 changes: 31 additions & 0 deletions build-logic/convention/src/main/kotlin/plugins/SentryDsnPlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package plugins

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import tasks.GenerateSentryConfigTask

class SentryDsnPlugin : Plugin<Project> {
override fun apply(target: Project): Unit =
with(target) {
val codeGenerator =
project.tasks
.create("generateSentryConfig", GenerateSentryConfigTask::class.java)
.apply {
group = "generation"
description = "Generate Sentry configuration file"
}
extensions.configure<KotlinMultiplatformExtension> {
sourceSets.apply {
commonMain {
kotlin.srcDir("build/generated/custom")
}
}
}
tasks.withType(KotlinCompile::class.java).configureEach {
dependsOn(codeGenerator)
}
}
}
Loading

0 comments on commit 29626ba

Please sign in to comment.