Slide 1

Slide 1 text

KOTLINIFY YOUR GRADLE Android Makers Paris 2018 Alexander Gherschon

Slide 2

Slide 2 text

WHO AM I? • Android Developer @ Houzz • Organiser and Speaker @ KotlinTLV.co.il • Love to build tools!

Slide 3

Slide 3 text

AGENDA • Gradle Plugins • Gradle Kotlin-DSL

Slide 4

Slide 4 text

GRADLE PLUGINS

Slide 5

Slide 5 text

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…

Slide 6

Slide 6 text

INTERESTING EXISTING PLUGINS • Android Gradle Plugin • Kotlin Gradle Plugin • Gradle Android Ribbonizer plugin • Gradle Play Publisher • etc…

Slide 7

Slide 7 text

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)

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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'

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

WHERE DO THEY LIVE Repo mavenCentral() jcenter() google() Repo mavenLocal() buildSrc

Slide 14

Slide 14 text

GRADLE API INTERFACE •getDependencies() •getTasks() •… INTERFACE •doFirst() •doLast() •… INTERFACE •mavenCentral() •…

Slide 15

Slide 15 text

CREATING A GRADLE PLUGIN

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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' }

Slide 18

Slide 18 text

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' }

Slide 19

Slide 19 text

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' }

Slide 20

Slide 20 text

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' }

Slide 21

Slide 21 text

NAMING OUR PLUGIN ./build.gradle ext { pluginGroupId = 'il.co.galex.tools.build' pluginArtifactId = 'todos' pluginVersion = '0.0.1' }

Slide 22

Slide 22 text

DEPLOYING LOCALLY ./build.gradle apply plugin: 'maven-publish' publishing { publications { TodosPublication(MavenPublication) { groupId = pluginGroupId artifactId = pluginArtifactId version = pluginVersion from components.java } } }

Slide 23

Slide 23 text

DEPLOYING LOCALLY - PUBLISH TASK

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

APPLYING OUR PLUGIN ./app/build.gradle apply plugin: 'il.co.galex.todos'

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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 { override fun apply(project: Project) { TODO("We will implement here our plugin!") } }

Slide 29

Slide 29 text

RESULT!

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

IMPLEMENTING OUR PLUGIN class TodosPlugin: Plugin { override fun apply(project: Project) { } }

Slide 32

Slide 32 text

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”) } (…) }

Slide 33

Slide 33 text

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' }

Slide 34

Slide 34 text

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' }

Slide 35

Slide 35 text

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”) } (…) }

Slide 36

Slide 36 text

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”) } (…) }

Slide 37

Slide 37 text

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”) } (…) }

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

CURRENT RESULT

Slide 43

Slide 43 text

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}" }

Slide 44

Slide 44 text

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}" }

Slide 45

Slide 45 text

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}" }

Slide 46

Slide 46 text

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}" }

Slide 47

Slide 47 text

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}" }

Slide 48

Slide 48 text

CURRENT RESULT

Slide 49

Slide 49 text

3 PHASES OF GRADLE INITIALIZATION CONFIGURATION EXECUTION

Slide 50

Slide 50 text

3 PHASES OF GRADLE INITIALIZATION CONFIGURATION EXECUTION

Slide 51

Slide 51 text

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}" }

Slide 52

Slide 52 text

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}" }

Slide 53

Slide 53 text

THE TODOS TASK open class TodosTask : DefaultTask() { var folders: List? = null }

Slide 54

Slide 54 text

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()}") } } } } } }

Slide 55

Slide 55 text

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()}") } } } } } }

Slide 56

Slide 56 text

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()}") } } } } } }

Slide 57

Slide 57 text

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()}") } } } } } }

Slide 58

Slide 58 text

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()}") } } } } } }

Slide 59

Slide 59 text

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()}") } } } } } }

Slide 60

Slide 60 text

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()}") } } } } } }

Slide 61

Slide 61 text

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()}") } } } } } }

Slide 62

Slide 62 text

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()}") } } } } } }

Slide 63

Slide 63 text

CURRENT RESULT

Slide 64

Slide 64 text

RUNNING TASK - VARIANT DEBUG

Slide 65

Slide 65 text

RUNNING TASK - VARIANT RELEASE

Slide 66

Slide 66 text

WHAT ABOUT PARAMETERS?

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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()}") } }

Slide 74

Slide 74 text

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()}") } }

Slide 75

Slide 75 text

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()}") } }

Slide 76

Slide 76 text

DEFINING OUR EXTENSION ./build.gradle todos { keywords = ['TODO', 'WTF'] }

Slide 77

Slide 77 text

RUNNING TASK - VARIANT DEBUG

Slide 78

Slide 78 text

• Built a Gradle Plugin • Created a task per Android Build Variant • Used an extension for as plugin parameters RECAP

Slide 79

Slide 79 text

GRADLE KOTLIN-DSL

Slide 80

Slide 80 text

WHAT IS A DSL • Domain Specific Language • General purpose Programming Language

Slide 81

Slide 81 text

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)

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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 }

Slide 88

Slide 88 text

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 }

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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 } }

Slide 92

Slide 92 text

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 } }

Slide 93

Slide 93 text

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 } }

Slide 94

Slide 94 text

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”

Slide 95

Slide 95 text

GRADLE API INTERFACE •getDependencies() •getTasks() •… INTERFACE •doFirst() •doLast() •… INTERFACE •mavenCentral() •…

Slide 96

Slide 96 text

WHAT IS THE KOTLIN-DSL • Gradle API • Set of Extensions functions and lambdas with receiver • IntelliJ ‘extension’ which understands .kts build files

Slide 97

Slide 97 text

PS: CONVERT TO K IN TWO GIT ‘PHASES’ 1.Rename file then commit 2.Modify its content then commit and push

Slide 98

Slide 98 text

PS: CONVERT TO K IN TWO GIT ‘PHASES’ 1.build.gradle -> build.gradle.kts + commit 2.Modify build.gradle.kts + commit + push

Slide 99

Slide 99 text

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'

Slide 100

Slide 100 text

CONVERTING OUR PLUGIN - 1’ plugins { kotlin("jvm") version "1.2.40" `java-gradle-plugin` `maven-publish` }

Slide 101

Slide 101 text

CONVERTING OUR PLUGIN - 2 compileKotlin { kotlinOptions.jvmTarget = "1.8" }

Slide 102

Slide 102 text

CONVERTING OUR PLUGIN - 2' tasks { withType { kotlinOptions.jvmTarget = "1.8" } }

Slide 103

Slide 103 text

CONVERTING OUR PLUGIN - 3 task sourceJar(type: Jar) { from sourceSets.main.allJava } task javadocJar(type: Jar, dependsOn: javadoc) { from javadoc.destinationDir }

Slide 104

Slide 104 text

CONVERTING OUR PLUGIN - 3’ val sourceJar = task("sourceJar") { from(java.sourceSets["main"].allSource) classifier = "sources" } val javadocJar: Jar = task("javadocJar") { dependsOn("javadoc") from(java.docsDir) classifier = "javadoc" }

Slide 105

Slide 105 text

CONVERTING OUR PLUGIN - 4 ext { pluginGroupId = 'il.co.galex.tools.build' pluginArtifactId = 'todos' pluginVersion = '0.0.2' }

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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' } } } }

Slide 108

Slide 108 text

CONVERTING OUR PLUGIN - 5’ publishing { publications { create("TodosPublication") { groupId = ext["pluginGroupId"] as String? artifactId = ext["pluginArtifactId"] as String? version = ext["pluginVersion"] as String? from(components["java"]) artifact(sourceJar) artifact(javadocJar) } } }

Slide 109

Slide 109 text

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" } (...) }

Slide 110

Slide 110 text

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" } (...) }

Slide 111

Slide 111 text

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' } }

Slide 112

Slide 112 text

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") } }

Slide 113

Slide 113 text

CONVERTING OUR ANDROID APP - 3 sourceSets { main { java.srcDirs += 'src/main/kotlin' } debug { java.srcDirs += 'src/debug/kotlin' } release { java.srcDirs += 'src/release/kotlin' } }

Slide 114

Slide 114 text

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") } }

Slide 115

Slide 115 text

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' }

Slide 116

Slide 116 text

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") }

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

CONVERTING OUR ANDROID APP - 5 todos { keywords = listOf("TODO", "WTH") }

Slide 119

Slide 119 text

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!

Slide 120

Slide 120 text

• 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

Slide 121

Slide 121 text

THANK YOU! Questions? @GALEX