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