Kotlinify your Gradle

Kotlinify your Gradle

Presentation given at Android Makers Paris 2018 https://androidmakers.fr/schedule/#session-45

B11fe994ffca4aaa3da014530981ece3?s=128

Alexander Gherschon

April 23, 2018
Tweet

Transcript

  1. KOTLINIFY YOUR GRADLE Android Makers Paris 2018 Alexander Gherschon

  2. WHO AM I? • Android Developer @ Houzz • Organiser

    and Speaker @ KotlinTLV.co.il • Love to build tools!
  3. AGENDA • Gradle Plugins • Gradle Kotlin-DSL

  4. GRADLE PLUGINS

  5. WHAT IS GRADLE • Build system (JVM based) • Defines

    Tasks inside a Project • A Plugin is set of Tasks gathered for a purpose • Java, Kotlin, Android, iOS, .Net…
  6. INTERESTING EXISTING PLUGINS • Android Gradle Plugin • Kotlin Gradle

    Plugin • Gradle Android Ribbonizer plugin • Gradle Play Publisher • etc…
  7. BUILDING A GRADLE PLUGIN • Print all “// TODO” lines

    in our project • Find all source files • Find all lines starting with “// TODO” • Android projects (for each build variant)
  8. SOME CONTEXT ANDROID-TODOS-GRADLE-PLUGIN https://github.com/galex/android-todos-gradle-plugin ANDROID-TODOS-TEST-APP https://github.com/galex/android-todos-test-app

  9. USING GRADLE PLUGINS buildscript { ext.kotlin_version = '1.2.21' repositories {

    google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' classpath “org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version” } } apply plugin: 'com.android.application' apply plugin: 'kotlin-android'
  10. buildscript { ext.kv = '1.2.21' repositories { google() jcenter() }

    dependencies { classpath 'com.android.tools.build:gradle:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kv" } } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' USING GRADLE PLUGINS
  11. buildscript { ext.kv = '1.2.21' repositories { google() jcenter() }

    dependencies { classpath 'com.android.tools.build:gradle:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kv" } } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' USING GRADLE PLUGINS
  12. buildscript { ext.kv = '1.2.21' repositories { google() jcenter() }

    dependencies { classpath 'com.android.tools.build:gradle:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kv" } } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' USING GRADLE PLUGINS
  13. WHERE DO THEY LIVE Repo mavenCentral() jcenter() google() Repo mavenLocal()

    buildSrc
  14. GRADLE API INTERFACE<PROJECT> •getDependencies() •getTasks() •… INTERFACE<TASK> •doFirst() •doLast() •…

    INTERFACE<REPOSITORY> •mavenCentral() •…
  15. CREATING A GRADLE PLUGIN

  16. UPDATING GRADLE distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https://services.gradle.org/distributions/gradle-4.6-all.zip ./gradle/wrapper/gradle-wrapper.properties

  17. ADDING DEPENDENCIES ./build.gradle repositories { jcenter() google() } dependencies {

    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile group: 'junit', name: 'junit', version: '4.12' compile gradleApi() compileOnly 'com.android.tools.build:gradle:3.0.1' }
  18. ADDING DEPENDENCIES ./build.gradle repositories { jcenter() google() } dependencies {

    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile group: 'junit', name: 'junit', version: '4.12' compile gradleApi() compileOnly 'com.android.tools.build:gradle:3.0.1' }
  19. ADDING DEPENDENCIES ./build.gradle repositories { jcenter() google() } dependencies {

    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile group: 'junit', name: 'junit', version: '4.12' compile gradleApi() compileOnly 'com.android.tools.build:gradle:3.0.1' }
  20. ADDING DEPENDENCIES ./build.gradle repositories { jcenter() google() } dependencies {

    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile group: 'junit', name: 'junit', version: '4.12' compile gradleApi() compileOnly 'com.android.tools.build:gradle:3.0.1' }
  21. NAMING OUR PLUGIN ./build.gradle ext { pluginGroupId = 'il.co.galex.tools.build' pluginArtifactId

    = 'todos' pluginVersion = '0.0.1' }
  22. DEPLOYING LOCALLY ./build.gradle apply plugin: 'maven-publish' publishing { publications {

    TodosPublication(MavenPublication) { groupId = pluginGroupId artifactId = pluginArtifactId version = pluginVersion from components.java } } }
  23. DEPLOYING LOCALLY - PUBLISH TASK

  24. DEPENDING & APPLYING ./build.gradle buildscript { repositories { mavenLocal() }

    dependencies { classpath 'com.android.tools.build:gradle:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "il.co.galex.tools.build:todos:0.0.1" } } apply plugin: 'il.co.galex.todos' ./app/build.gradle
  25. DEPENDING & APPLYING ./build.gradle buildscript { repositories { mavenLocal() }

    dependencies { classpath 'com.android.tools.build:gradle:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "il.co.galex.tools.build:todos:0.0.1" } } apply plugin: 'il.co.galex.todos' ./app/build.gradle
  26. APPLYING OUR PLUGIN ./app/build.gradle apply plugin: 'il.co.galex.todos'

  27. GRADLE PLUGIN ENTRY POINT apply plugin: 'il.co.galex.todos' ./src/main/resources/ il.co.galex.todos.properties META-INF/gradle-plugins/

    MATCH!
  28. GRADLE PLUGIN ENTRY POINT ./src/…/META-INF/gradle-plugins/il.co.galex.todos.properties implementation-class=il.co.galex.tools.todos.plugin.TodosPlugin /src/main/kotlin/…/il.co.galex.tools.todos.plugin.TodosPlugin class TodosPlugin: Plugin<Project>

    { override fun apply(project: Project) { TODO("We will implement here our plugin!") } }
  29. RESULT!

  30. IMPLEMENTING OUR PLUGIN class TodosPlugin: Plugin<Project> { override fun apply(project:

    Project?) { TODO("We will implement here our plugin!") } }
  31. IMPLEMENTING OUR PLUGIN class TodosPlugin: Plugin<Project> { override fun apply(project:

    Project) { } }
  32. IMPLEMENTING OUR PLUGIN override fun apply(project: Project) { val hasApp

    = project.plugins.hasPlugin(AppPlugin::class.java) val hasLib = project.plugins.hasPlugin(LibraryPlugin::class.java) if (!hasApp && !hasLib) { throw IllegalStateException(“'some warning here”) } (…) }
  33. DEPENDENCIES (REMINDER) ./build.gradle repositories { jcenter() google() } dependencies {

    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile group: 'junit', name: 'junit', version: '4.12' compile gradleApi() compileOnly 'com.android.tools.build:gradle:3.0.1' }
  34. DEPENDENCIES (REMINDER) ./build.gradle repositories { jcenter() google() } dependencies {

    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testCompile group: 'junit', name: 'junit', version: '4.12' compile gradleApi() compileOnly 'com.android.tools.build:gradle:3.0.1' }
  35. IMPLEMENTING OUR PLUGIN override fun apply(project: Project) { val hasApp

    = project.plugins.hasPlugin(AppPlugin::class.java) val hasLib = project.plugins.hasPlugin(LibraryPlugin::class.java) if (!hasApp && !hasLib) { throw IllegalStateException(“'some warning here”) } (…) }
  36. IMPLEMENTING OUR PLUGIN override fun apply(project: Project) { val hasApp

    = project.plugins.hasPlugin(AppPlugin::class.java) val hasLib = project.plugins.hasPlugin(LibraryPlugin::class.java) if (!hasApp && !hasLib) { throw IllegalStateException(“'some warning here”) } (…) }
  37. IMPLEMENTING OUR PLUGIN override fun apply(project: Project) { val hasApp

    = project.plugins.hasPlugin(AppPlugin::class.java) val hasLib = project.plugins.hasPlugin(LibraryPlugin::class.java) if (!hasApp && !hasLib) { throw IllegalStateException(“'some warning here”) } (…) }
  38. IMPLEMENTING OUR PLUGIN override fun apply(project: Project) { (…) val

    variants: DomainObjectSet<out BaseVariant> = if (hasApp) { project.extensions.findByType(AppExtension::class.java)!!.applicationVariants } else { project.extensions.findByType(LibraryExtension::class.java)!!.libraryVariants } (…) }
  39. IMPLEMENTING OUR PLUGIN override fun apply(project: Project) { (…) val

    variants: DomainObjectSet<out BaseVariant> = if (hasApp) { project.extensions.findByType(AppExtension::class.java)!!.applicationVariants } else { project.extensions.findByType(LibraryExtension::class.java)!!.libraryVariants } (…) }
  40. IMPLEMENTING OUR PLUGIN override fun apply(project: Project) { val variants:

    DomainObjectSet<out BaseVariant> = (…) variants.all { variant: BaseVariant -> variant.sourceSets.flatMap { it.javaDirectories } .forEach { println(it) } } }
  41. IMPLEMENTING OUR PLUGIN override fun apply(project: Project) { val variants:

    DomainObjectSet<out BaseVariant> = (…) variants.all { variant: BaseVariant -> variant.sourceSets.flatMap { it.javaDirectories } .forEach { println(it) } } }
  42. CURRENT RESULT

  43. IMPLEMENTING OUR PLUGIN variants.all { variant: BaseVariant -> val folders

    = variant.sourceSets.flatMap { it.javaDirectories } val taskName = "print${variant.name.capitalize()}Todos" val task = project.tasks.create(taskName, TodosTask::class.java) { it.folders = folders } task.group = "Todos" task.description = "Find all todos in the project for variant ${variant.name}" }
  44. IMPLEMENTING OUR PLUGIN variants.all { variant: BaseVariant -> val folders

    = variant.sourceSets.flatMap { it.javaDirectories } val taskName = "print${variant.name.capitalize()}Todos" val task = project.tasks.create(taskName, TodosTask::class.java) { it.folders = folders } task.group = "Todos" task.description = "Find all todos in the project for variant ${variant.name}" }
  45. IMPLEMENTING OUR PLUGIN variants.all { variant: BaseVariant -> val folders

    = variant.sourceSets.flatMap { it.javaDirectories } val taskName = "print${variant.name.capitalize()}Todos" val task = project.tasks.create(taskName, TodosTask::class.java) { it.folders = folders } task.group = "Todos" task.description = "Find all todos in the project for variant ${variant.name}" }
  46. IMPLEMENTING OUR PLUGIN variants.all { variant: BaseVariant -> val folders

    = variant.sourceSets.flatMap { it.javaDirectories } val taskName = "print${variant.name.capitalize()}Todos" val task = project.tasks.create(taskName, TodosTask::class.java) { it.folders = folders } task.group = "Todos" task.description = "Find all todos in the project for variant ${variant.name}" }
  47. IMPLEMENTING OUR PLUGIN variants.all { variant: BaseVariant -> val folders

    = variant.sourceSets.flatMap { it.javaDirectories } val taskName = "print${variant.name.capitalize()}Todos" val task = project.tasks.create(taskName, TodosTask::class.java) { it.folders = folders } task.group = "Todos" task.description = "Find all todos in the project for variant ${variant.name}" }
  48. CURRENT RESULT

  49. 3 PHASES OF GRADLE INITIALIZATION CONFIGURATION EXECUTION

  50. 3 PHASES OF GRADLE INITIALIZATION CONFIGURATION EXECUTION

  51. IMPLEMENTING OUR PLUGIN variants.all { variant: BaseVariant -> val folders

    = variant.sourceSets.flatMap { it.javaDirectories } val taskName = "print${variant.name.capitalize()}Todos" val task = project.tasks.create(taskName, TodosTask::class.java) { it.folders = folders } task.group = "Todos" task.description = "Find all todos in the project for variant ${variant.name}" }
  52. IMPLEMENTING OUR PLUGIN variants.all { variant: BaseVariant -> val folders

    = variant.sourceSets.flatMap { it.javaDirectories } val taskName = "print${variant.name.capitalize()}Todos" val task = project.tasks.create(taskName, TodosTask::class.java) { it.folders = folders } task.group = "Todos" task.description = "Find all todos in the project for variant ${variant.name}" }
  53. THE TODOS TASK open class TodosTask : DefaultTask() { var

    folders: List<File>? = null }
  54. THE TODOS TASK open class TodosTask : DefaultTask() { @TaskAction

    fun run() { folders?.forEach { folder -> folder.walk().filter { it.isFile }.forEach { file -> file.readLines().forEachIndexed { index, line -> if (line.contains("// TODO")) { println("$file:${index + 1} - ${line.trim()}") } } } } } }
  55. THE TODOS TASK open class TodosTask : DefaultTask() { @TaskAction

    fun run() { folders?.forEach { folder -> folder.walk().filter { it.isFile }.forEach { file -> file.readLines().forEachIndexed { index, line -> if (line.contains("// TODO")) { println("$file:${index + 1} - ${line.trim()}") } } } } } }
  56. THE TODOS TASK open class TodosTask : DefaultTask() { @TaskAction

    fun run() { folders?.forEach { folder -> folder.walk().filter { it.isFile }.forEach { file -> file.readLines().forEachIndexed { index, line -> if (line.contains("// TODO")) { println("$file:${index + 1} - ${line.trim()}") } } } } } }
  57. THE TODOS TASK open class TodosTask : DefaultTask() { @TaskAction

    fun run() { folders?.forEach { folder -> folder.walk().filter { it.isFile }.forEach { file -> file.readLines().forEachIndexed { index, line -> if (line.contains("// TODO")) { println("$file:${index + 1} - ${line.trim()}") } } } } } }
  58. THE TODOS TASK open class TodosTask : DefaultTask() { @TaskAction

    fun run() { folders?.forEach { folder -> folder.walk().filter { it.isFile }.forEach { file -> file.readLines().forEachIndexed { index, line -> if (line.contains("// TODO")) { println("$file:${index + 1} - ${line.trim()}") } } } } } }
  59. THE TODOS TASK open class TodosTask : DefaultTask() { @TaskAction

    fun run() { folders?.forEach { folder -> folder.walk().filter { it.isFile }.forEach { file -> file.readLines().forEachIndexed { index, line -> if (line.contains("// TODO")) { println("$file:${index + 1} - ${line.trim()}") } } } } } }
  60. THE TODOS TASK open class TodosTask : DefaultTask() { @TaskAction

    fun run() { folders?.forEach { folder -> folder.walk().filter { it.isFile }.forEach { file -> file.readLines().forEachIndexed { index, line -> if (line.contains("// TODO")) { println("$file:${index + 1} - ${line.trim()}") } } } } } }
  61. THE TODOS TASK open class TodosTask : DefaultTask() { @TaskAction

    fun run() { folders?.forEach { folder -> folder.walk().filter { it.isFile }.forEach { file -> file.readLines().forEachIndexed { index, line -> if (line.contains("// TODO")) { println("$file:${index + 1} - ${line.trim()}") } } } } } }
  62. THE TODOS TASK open class TodosTask : DefaultTask() { @TaskAction

    fun run() { folders?.forEach { folder -> folder.walk().filter { it.isFile }.forEach { file -> file.readLines().forEachIndexed { index, line -> if (line.contains("// TODO")) { println("$file:${index + 1} - ${line.trim()}") } } } } } }
  63. CURRENT RESULT

  64. RUNNING TASK - VARIANT DEBUG

  65. RUNNING TASK - VARIANT RELEASE

  66. WHAT ABOUT PARAMETERS?

  67. USING AN EXTENSION ./build.gradle todos { keywords = ['TODO', 'WTH']

    }
  68. WRITING OUR EXTENSION open class TodosExtension { var keywords: List<String>?

    = null set(value) { when { value == null -> throw IllegalArgumentException("...") value.isEmpty() -> throw IllegalArgumentException("...") value.find { it == "" } != null -> throw IllegalArgumentException("...") else -> field = value } } }
  69. WRITING OUR EXTENSION open class TodosExtension { var keywords: List<String>?

    = null set(value) { when { value == null -> throw IllegalArgumentException("...") value.isEmpty() -> throw IllegalArgumentException("...") value.find { it == "" } != null -> throw IllegalArgumentException("...") else -> field = value } } }
  70. WRITING OUR EXTENSION open class TodosExtension { var keywords: List<String>?

    = null set(value) { when { value == null -> throw IllegalArgumentException("...") value.isEmpty() -> throw IllegalArgumentException("...") value.find { it == "" } != null -> throw IllegalArgumentException("...") else -> field = value } } }
  71. USING OUR EXTENSION IN OUR PLUGIN class TodosPlugin : Plugin<Project>

    { override fun apply(project: Project) { project.extensions.create("todos", TodosExtension::class.java) } }
  72. USING OUR EXTENSION IN OUR PLUGIN class TodosPlugin : Plugin<Project>

    { override fun apply(project: Project) { project.extensions.create("todos", TodosExtension::class.java) } }
  73. USING OUR EXTENSION IN OUR TASK val extension: TodosExtension? =

    project.extensions.findByType(TodosExtension::class.java) val keywords = extension?.keywords ?: listOf(“TODO") file.readLines().forEachIndexed { index, line -> if (line.trim().startsWith("//") && keywords.intersect(line.split(" ")).isNotEmpty()) { println("$file:${index + 1} - ${line.trim()}") } }
  74. USING OUR EXTENSION IN OUR TASK val extension: TodosExtension? =

    project.extensions.findByType(TodosExtension::class.java) val keywords = extension?.keywords ?: listOf(“TODO") file.readLines().forEachIndexed { index, line -> if (line.trim().startsWith("//") && keywords.intersect(line.split(" ")).isNotEmpty()) { println("$file:${index + 1} - ${line.trim()}") } }
  75. USING OUR EXTENSION IN OUR TASK val extension: TodosExtension? =

    project.extensions.findByType(TodosExtension::class.java) val keywords = extension?.keywords ?: listOf(“TODO") file.readLines().forEachIndexed { index, line -> if (line.trim().startsWith("//") && keywords.intersect(line.split(" ")).isNotEmpty()) { println("$file:${index + 1} - ${line.trim()}") } }
  76. DEFINING OUR EXTENSION ./build.gradle todos { keywords = ['TODO', 'WTF']

    }
  77. RUNNING TASK - VARIANT DEBUG

  78. • Built a Gradle Plugin • Created a task per

    Android Build Variant • Used an extension for as plugin parameters RECAP
  79. GRADLE KOTLIN-DSL

  80. WHAT IS A DSL • Domain Specific Language • General

    purpose Programming Language
  81. SIMPLE DSL EXAMPLE enum class Color { BLUE, RED, GREEN

    } data class Wheels(var number: Int? = null, var color: Color? = null) data class Vehicle(var wheels: Wheels? = null, var engine: String? = null)
  82. SIMPLE DSL EXAMPLE // imperative way val carImperative = Vehicle(Wheels(2,

    Color.RED), "Meh") println(carImperative) // Vehicle(wheels=Wheels(number=2, color=RED), engine=Meh)
  83. SIMPLE DSL EXAMPLE // declarative way val carDSL = vehicle

    { engine = "V8" wheels { number = 4 color = Color.BLUE } }
  84. SIMPLE DSL EXAMPLE private fun vehicle(block: Vehicle.() -> Unit) =

    Vehicle().apply(block) // declarative way val carDSL = vehicle { engine = "V8" wheels { number = 4 color = Color.BLUE } } SIMPLE DSL EXAMPLE
  85. SIMPLE DSL EXAMPLE private fun vehicle(block: Vehicle.() -> Unit) =

    Vehicle().apply(block) // declarative way val carDSL = vehicle { engine = "V8" wheels { number = 4 color = Color.BLUE } } SIMPLE DSL EXAMPLE
  86. SIMPLE DSL EXAMPLE // declarative way val carDSL = vehicle

    { this.engine = "V8" wheels { number = 4 color = Color.BLUE } } private fun vehicle(block: Vehicle.() -> Unit) = Vehicle().apply(block) SIMPLE DSL EXAMPLE
  87. THE APPLY FUNCTION private fun vehicle(block: Vehicle.() -> Unit) =

    Vehicle().apply(block) private fun vehicle(block: Vehicle.() -> Unit): Vehicle { val vehicle = Vehicle() vehicle.block() return vehicle }
  88. APPLY FUNCTION private fun vehicle(block: Vehicle.() -> Unit) = Vehicle().apply(block)

    private fun vehicle(block: Vehicle.() -> Unit): Vehicle { val vehicle = Vehicle() vehicle.block() return vehicle }
  89. SIMPLE DSL EXAMPLE // declarative way val carDSL = vehicle

    { engine = "V8" wheels { number = 4 color = Color.BLUE } }
  90. SIMPLE DSL EXAMPLE // declarative way val carDSL = vehicle

    { this.engine = "V8" this.wheels { this.number = 4 this.color = Color.BLUE } }
  91. SIMPLE DSL EXAMPLE private fun Vehicle.wheels(block: Wheels.() -> Unit) {

    this.wheels = Wheels().apply(block) } // declarative way val carDSL = vehicle { this.engine = "V8" this.wheels { this.number = 4 this.color = Color.BLUE } }
  92. SIMPLE DSL EXAMPLE private fun Vehicle.wheels(block: Wheels.() -> Unit) {

    this.wheels = Wheels().apply(block) } // declarative way val carDSL = vehicle { this.engine = "V8" this.wheels { this.number = 4 this.color = Color.BLUE } }
  93. SIMPLE DSL EXAMPLE private fun Vehicle.wheels(block: Wheels.() -> Unit) {

    this.wheels = Wheels().apply(block) } // declarative way val carDSL = vehicle { this.engine = "V8" this.wheels { this.number = 4 this.color = Color.BLUE } }
  94. SIMPLE DSL EXAMPLE private fun Vehicle.wheels((block: Wheels.() -> Unit) {

    this.wheels = Wheels().apply(block) } // declarative way val carDSL = vehicle { this.engine = "V8" this.wheels { this.number = 4 this.color = Color.BLUE } } Lambda with Receiver = Lambda, with inside “this”
  95. GRADLE API INTERFACE<PROJECT> •getDependencies() •getTasks() •… INTERFACE<TASK> •doFirst() •doLast() •…

    INTERFACE<REPOSITORY> •mavenCentral() •…
  96. WHAT IS THE KOTLIN-DSL • Gradle API • Set of

    Extensions functions and lambdas with receiver • IntelliJ ‘extension’ which understands .kts build files
  97. PS: CONVERT TO K IN TWO GIT ‘PHASES’ 1.Rename file

    then commit 2.Modify its content then commit and push
  98. PS: CONVERT TO K IN TWO GIT ‘PHASES’ 1.build.gradle ->

    build.gradle.kts + commit 2.Modify build.gradle.kts + commit + push
  99. CONVERTING OUR PLUGIN - 1 buildscript { ext.kotlin_version = '1.2.31'

    repositories { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } apply plugin: 'java' apply plugin: 'kotlin' apply plugin: 'maven-publish'
  100. CONVERTING OUR PLUGIN - 1’ plugins { kotlin("jvm") version "1.2.40"

    `java-gradle-plugin` `maven-publish` }
  101. CONVERTING OUR PLUGIN - 2 compileKotlin { kotlinOptions.jvmTarget = "1.8"

    }
  102. CONVERTING OUR PLUGIN - 2' tasks { withType<KotlinCompile> { kotlinOptions.jvmTarget

    = "1.8" } }
  103. CONVERTING OUR PLUGIN - 3 task sourceJar(type: Jar) { from

    sourceSets.main.allJava } task javadocJar(type: Jar, dependsOn: javadoc) { from javadoc.destinationDir }
  104. CONVERTING OUR PLUGIN - 3’ val sourceJar = task<Jar>("sourceJar") {

    from(java.sourceSets["main"].allSource) classifier = "sources" } val javadocJar: Jar = task<Jar>("javadocJar") { dependsOn("javadoc") from(java.docsDir) classifier = "javadoc" }
  105. CONVERTING OUR PLUGIN - 4 ext { pluginGroupId = 'il.co.galex.tools.build'

    pluginArtifactId = 'todos' pluginVersion = '0.0.2' }
  106. CONVERTING OUR PLUGIN - 4’ ext { set("pluginGroupId", "il.co.galex.tools.build") set("pluginArtifactId",

    "todos") set("pluginVersion", "0.0.5") }
  107. CONVERTING OUR PLUGIN - 5 publishing { publications { TodosPublication(MavenPublication)

    { groupId = pluginGroupId artifactId = pluginArtifactId version = pluginVersion from components.java artifact sourceJar { classifier "sources" } artifact javadocJar { classifier = 'javadoc' } } } }
  108. CONVERTING OUR PLUGIN - 5’ publishing { publications { create<MavenPublication>("TodosPublication")

    { groupId = ext["pluginGroupId"] as String? artifactId = ext["pluginArtifactId"] as String? version = ext["pluginVersion"] as String? from(components["java"]) artifact(sourceJar) artifact(javadocJar) } } }
  109. CONVERTING OUR ANDROID APP - 1 android { compileSdkVersion 26

    defaultConfig { applicationId "il.co.galex.todosgradleplugintest" minSdkVersion 15 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } (...) }
  110. CONVERTING OUR ANDROID APP - 1’ android { compileSdkVersion(26) defaultConfig

    { applicationId = "il.co.galex.todosgradleplugintest" minSdkVersion(15) targetSdkVersion(26) versionCode = 1 versionName = "1.0" testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" } (...) }
  111. CONVERTING OUR ANDROID APP - 2 signingConfigs { debug {

    storeFile file("debug.keystore") } } buildTypes { debug { signingConfig signingConfigs.debug minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
  112. CONVERTING OUR ANDROID APP - 2’ signingConfigs { getByName("debug") {

    storeFile = file("debug.keystore") } } buildTypes { getByName("debug") { signingConfig = signingConfigs.getByName("debug") isMinifyEnabled = false proguardFile(getDefaultProguardFile ("proguard-android.txt")) proguardFile("proguard-rules.pro") } }
  113. CONVERTING OUR ANDROID APP - 3 sourceSets { main {

    java.srcDirs += 'src/main/kotlin' } debug { java.srcDirs += 'src/debug/kotlin' } release { java.srcDirs += 'src/release/kotlin' } }
  114. CONVERTING OUR ANDROID APP - 3’ sourceSets { getByName("main") {

    this.java.srcDir("src/main/kotlin") } getByName("debug") { this.java.srcDir("src/debug/kotlin") } getByName("release") { this.java.srcDir("src/release/kotlin") } }
  115. CONVERTING OUR ANDROID APP - 4 dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

    implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' }
  116. CONVERTING OUR ANDROID APP - 4’ dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") implementation("com.android.support:appcompat-v7:26.1.0")

    implementation("com.android.support.constraint:constraint-layout:1.0.2") testImplementation("junit:junit:4.12") androidTestImplementation("com.android.support.test:runner:1.0.1") androidTestImplementation("com.android.support.test.espresso:espresso-core:3.0.1") }
  117. CONVERTING OUR ANDROID APP - 5 todos { keywords =

    [“TODO", “WTH"] }
  118. CONVERTING OUR ANDROID APP - 5 todos { keywords =

    listOf("TODO", "WTH") }
  119. RECAP • Converted from Groovy to Kotlin • Not that

    easy to switch • Probably because it’s quite early… v0.1.6.3 • But it’s Kotlin, it’s fun!
  120. • Todos Gradle Plugin https://github.com/galex/android-todos-gradle-plugin • Android Test App https://github.com/galex/android-todos-test-app

    • Some good blogposts on the Kotlin-DSL • https://antonioleiva.com/kotlin-dsl-gradle/ • https://kotlinexpertise.com/gradlekotlindsl/ • Books • Kotlin in Action https://www.manning.com/books/kotlin-in-action • Gradle in Action https://www.manning.com/books/gradle-in-action LINKS
  121. THANK YOU! Questions? @GALEX