Advertisement
IPerov

Untitled

Apr 9th, 2024
579
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 9.05 KB | None | 0 0
  1. this article I’d like to describe how you can get rid of boilerplate code in your build.gradle files in multimodule project with the help of the Convention plugins.
  2.  
  3. TL;DR — with the help of convention plugins you can refactor your Gradle file from this state (71 lines):
  4.  
  5. plugins {
  6.     alias(libs.plugins.kotlinMultiplatform)
  7.     alias(libs.plugins.androidLibrary)
  8.     id("maven-publish")
  9. }
  10.  
  11. group = "com.mikhailovskii.kmp"
  12. version = System.getenv("LIBRARY_VERSION") ?: libs.versions.pluginVersion.get()
  13.  
  14. kotlin {
  15.     androidTarget {
  16.         compilations.all { publishLibraryVariants("release", "debug") }
  17.     }
  18.  
  19.     listOf(
  20.         iosX64(),
  21.         iosArm64(),
  22.         iosSimulatorArm64()
  23.     ).forEach {
  24.         it.binaries.framework {
  25.             baseName = "in-app-review-kmp-rustore"
  26.             isStatic = true
  27.         }
  28.     }
  29.  
  30.     jvmToolchain(17)
  31.  
  32.     sourceSets {
  33.         androidMain.dependencies {
  34.             implementation(libs.rustore.review)
  35.         }
  36.         commonMain.dependencies {
  37.             api(projects.inAppReviewKmp)
  38.         }
  39.     }
  40. }
  41.  
  42. android {
  43.     namespace = "com.mikhailovskii.inappreview"
  44.     compileSdk = 34
  45.     defaultConfig {
  46.         minSdk = 26
  47.     }
  48. }
  49.  
  50. publishing {
  51.     publications {
  52.         matching {
  53.             return@matching it.name in listOf("iosArm64", "iosX64", "kotlinMultiplatform")
  54.         }.all {
  55.             tasks.withType<AbstractPublishToMaven>()
  56.                 .matching { it.publication == this@all }
  57.                 .configureEach { onlyIf { findProperty("isMainHost") == "true" } }
  58.         }
  59.     }
  60.     repositories {
  61.         maven {
  62.             url = uri("https://maven.pkg.github.com/SergeiMikhailovskii/kmp-app-review")
  63.             credentials {
  64.                 username = System.getenv("GITHUB_USER")
  65.                 password = System.getenv("GITHUB_API_KEY")
  66.             }
  67.         }
  68.     }
  69. }
  70.  
  71. tasks.register("buildAndPublish", DefaultTask::class) {
  72.     dependsOn("build")
  73.     dependsOn("publish")
  74.     tasks.findByPath("publish")?.mustRunAfter("build")
  75. }
  76.  
  77.  
  78. To this (15 lines):
  79.  
  80. plugins {
  81.     id("com.mikhailovskii.kmp.module")
  82.     id("maven-publish")
  83. }
  84.  
  85. kotlin {
  86.     sourceSets {
  87.         androidMain.dependencies {
  88.             implementation(libs.rustore.review)
  89.         }
  90.         commonMain.dependencies {
  91.             api(projects.inAppReviewKmp)
  92.         }
  93.     }
  94. }
  95.  
  96. Interested? Before diving into the refactoring let me discuss the initial setup I have. I have a KMP library that can launch the in-app review in various Android and iOS stores. Since some stores implementations require additional SDKs I decided to make them as separate modules. So, the library contains multiple modules that have the same structure:
  97.  
  98. Artifact group + version for building and publishing the module
  99. Targets setup
  100. Java setup
  101. Dependencies setup
  102. Android setup
  103. Publishing setup
  104. Wrapper task for building and publishing
  105. Everything except the 4th point is the same across the modules, so we can extract it into the Convention Plugin. Probably, it’s a default Gradle Plugin that contains some “conventional setup” for some part of the logic in the .gradle file.
  106.  
  107. To start the implementation we need to create the module for the plugin. Gradle documentation advises us to implement composite-build for this case, so let’s do it. Declare a folder and name it in any way you want (it’s a common practice to name it build-logic). To make this project a subproject we need to add settings.gradle.kts file where the subproject’s structure will be described. Here’s the way how it looks like:
  108.  
  109. dependencyResolutionManagement {
  110.     @Suppress("UnstableApiUsage")
  111.     repositories {
  112.         google()
  113.         mavenCentral()
  114.     }
  115.     versionCatalogs {
  116.         create("libs") {
  117.             from(files("../gradle/libs.versions.toml"))
  118.         }
  119.     }
  120. }
  121.  
  122. rootProject.name = "build-logic"
  123. include(":convention")
  124.  
  125.  
  126. There are repositories declared in this file that contain the dependencies used inside the subproject, specified path to the version catalog and included the module (convention) where the plugin is located.
  127.  
  128. Don’t forget to add the subproject into the main project — put the line includeBuild(“build-logic”) inside the pluginManagement block inside the root’s settings.gradle.kts.
  129.  
  130. Now let’s move to the module with the plugin. Here’s it’s build.gradle file:
  131.  
  132. plugins {
  133.     `kotlin-dsl`
  134. }
  135.  
  136. gradlePlugin {
  137.     plugins {
  138.         register("com.mikhailovskii.kmp.KMPModuleConventionPlugin") {
  139.             id = "com.mikhailovskii.kmp.module"
  140.             implementationClass = "KMPModuleConventionPlugin"
  141.         }
  142.     }
  143. }
  144.  
  145. dependencies {
  146.     compileOnly(libs.android.gradle)
  147.     compileOnly(libs.kotlin.gradle.plugin)
  148. }
  149.  
  150.  
  151. Plugin kotlin-dsl is applied to this module as it contains an extension to register the convention plugin we are going to implement. Then we register the convention plugin (specify it’s name, id, implementation class) and apply dependencies we need for the development of plugin. Since the plugin is used only during the compile time we declare the dependencies as compileOnly.
  152.  
  153. The implementation of the plugin is the following:
  154.  
  155. class KMPModuleConventionPlugin : Plugin<Project> {
  156.     override fun apply(target: Project) {
  157.         with(target) {
  158.             with(pluginManager) {
  159.                 apply(extensions.getPluginId("androidLibrary"))
  160.                 apply(extensions.getPluginId("kotlinMultiplatform"))
  161.                 apply("maven-publish")
  162.             }
  163.             group = "com.mikhailovskii.kmp"
  164.             version = System.getenv("LIBRARY_VERSION") ?: extensions.getVersion("pluginVersion")
  165.             extensions.configure<KotlinMultiplatformExtension> {
  166.                 androidTarget { publishLibraryVariants("release", "debug") }
  167.                 iosX64()
  168.                 iosArm64()
  169.                 iosSimulatorArm64()
  170.                 applyDefaultHierarchyTemplate()
  171.                 jvmToolchain(17)
  172.             }
  173.             extensions.configure<LibraryExtension> {
  174.                 namespace = "com.mikhailovskii.inappreview"
  175.                 compileSdk = 34
  176.                 defaultConfig {
  177.                     minSdk = 21
  178.                 }
  179.                 compileOptions {
  180.                     sourceCompatibility = JavaVersion.VERSION_17
  181.                     targetCompatibility = JavaVersion.VERSION_17
  182.                 }
  183.             }
  184.             extensions.configure<PublishingExtension> {
  185.                 publications {
  186.                     matching {
  187.                         return@matching it.name in listOf(
  188.                             "iosArm64",
  189.                             "iosX64",
  190.                             "kotlinMultiplatform"
  191.                         )
  192.                     }.all {
  193.                         tasks.withType<AbstractPublishToMaven>()
  194.                             .matching { it.publication == this@all }
  195.                             .configureEach { onlyIf { findProperty("isMainHost") == "true" } }
  196.                     }
  197.                 }
  198.                 repositories {
  199.                     maven {
  200.                         url = uri("https://maven.pkg.github.com/SergeiMikhailovskii/kmp-app-review")
  201.                         credentials {
  202.                             username = System.getenv("GITHUB_USER")
  203.                             password = System.getenv("GITHUB_API_KEY")
  204.                         }
  205.                     }
  206.                 }
  207.             }
  208.             tasks.register("buildAndPublish", DefaultTask::class) {
  209.                 dependsOn("build")
  210.                 dependsOn("publish")
  211.                 tasks.findByPath("publish")?.mustRunAfter("build")
  212.             }
  213.         }
  214.     }
  215. }
  216.  
  217.  
  218. The new plugin should be a subclass of the Project. And inside the body of the apply method we add all the logic we need. So, as you can see here we apply default plugins, setup targets, Java and Android, publishing and even create a new task.
  219.  
  220. I want to highlight that it’s more convenient to create extensions to get the pluginId/version you need from the version catalog. This is the way how these extensions look like:
  221.  
  222. internal fun ExtensionContainer.getPluginId(alias: String): String =
  223.     getByType<VersionCatalogsExtension>().named("libs").findPlugin(alias).get().get().pluginId
  224.  
  225. internal fun ExtensionContainer.getVersion(alias: String): String =
  226.     getByType<VersionCatalogsExtension>().named("libs").findVersion(alias).get().toString()
  227.  
  228. After everything is done we just add the plugin into the modules we need and cleanup the duplicated code from the build.gradle file since this logic is encapsulated inside the plugin:
  229.  
  230.  
  231. plugins {
  232.     id("com.mikhailovskii.kmp.module")
  233.     id("maven-publish")
  234. }
  235.  
  236. kotlin {
  237.     sourceSets {
  238.         androidMain.dependencies {
  239.             implementation(libs.rustore.review)
  240.         }
  241.         commonMain.dependencies {
  242.             api(projects.inAppReviewKmp)
  243.         }
  244.     }
  245. }
  246.  
  247.  
  248. The full implementation can be found inside the project.
  249.  
  250. Thank you for reading! Feel free to ask questions and leave the feedback in comments or Linkedin.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement