Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlinify your Gradle

Kotlinify your Gradle

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

Alexander Gherschon

April 23, 2018
Tweet

More Decks by Alexander Gherschon

Other Decks in Programming

Transcript

  1. KOTLINIFY YOUR GRADLE
    Android Makers Paris 2018
    Alexander Gherschon

    View full-size slide

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

    View full-size slide

  3. AGENDA
    • Gradle Plugins
    • Gradle Kotlin-DSL

    View full-size slide

  4. GRADLE PLUGINS

    View full-size slide

  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…

    View full-size slide

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

    View full-size slide

  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)

    View full-size slide

  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

    View full-size slide

  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'

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  15. CREATING A GRADLE PLUGIN

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. DEPLOYING LOCALLY - PUBLISH TASK

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  41. CURRENT RESULT

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  47. CURRENT RESULT

    View full-size slide

  48. 3 PHASES OF GRADLE
    INITIALIZATION CONFIGURATION EXECUTION

    View full-size slide

  49. 3 PHASES OF GRADLE
    INITIALIZATION CONFIGURATION EXECUTION

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  62. CURRENT RESULT

    View full-size slide

  63. RUNNING TASK - VARIANT DEBUG

    View full-size slide

  64. RUNNING TASK - VARIANT RELEASE

    View full-size slide

  65. WHAT ABOUT PARAMETERS?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  76. RUNNING TASK - VARIANT DEBUG

    View full-size slide

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

    View full-size slide

  78. GRADLE KOTLIN-DSL

    View full-size slide

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

    View full-size slide

  80. 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)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  83. 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

    View full-size slide

  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

    View full-size slide

  85. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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
    }
    }
    Lambda with Receiver
    =
    Lambda, with inside “this”

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  98. 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'

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  107. 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)
    }
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  118. 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!

    View full-size slide

  119. • 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

    View full-size slide

  120. THANK YOU!
    Questions?
    @GALEX

    View full-size slide