Slide 1

Slide 1 text

Groovy to Kotlin in build scripts Nelson Osacky

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

•Some Pros and Cons •Examples •How it works under the hood

Slide 4

Slide 4 text

Gradle Kotlin DSL v0.17.5

Slide 5

Slide 5 text

Type Safety

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

if (isCi) { foo }A

Slide 8

Slide 8 text

if (isCi) { foo }A

Slide 9

Slide 9 text

Auto Complete

Slide 10

Slide 10 text

• autocompletion gif

Slide 11

Slide 11 text

Slower builds

Slide 12

Slide 12 text

What is buildSrc?

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Less docs https://github.com/gradle/kotlin-dsl/tree/master/samples

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Must dig in source

Slide 21

Slide 21 text

Red lines in IDE

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

Missing features

Slide 24

Slide 24 text

import test.zen.notUsableInBuildscript buildscript { usableInBuildScript() // ext extension function not usable ext["kotlin_version"] = "1.2.50" // imported function is not usable notUsableInBuildscript() }

Slide 25

Slide 25 text

Pros • Type Safety • Auto Complete • Kotlin Cons • Slower builds • Less examples • Pre-release state • Source digging

Slide 26

Slide 26 text

Example Migrations

Slide 27

Slide 27 text

build.gradle -> build.gradle.kts

Slide 28

Slide 28 text

settings.gradle.kts

Slide 29

Slide 29 text

include ':app' include ':data' include ':service'

Slide 30

Slide 30 text

include( ":app", ":data", ":service" )

Slide 31

Slide 31 text

Kotlin Extension Functions and Properties

Slide 32

Slide 32 text

Kotlin provides the ability to extend a class with new functionality without having to inherit from the class. This is done via special declarations called extensions. 
 
 Kotlin supports extension functions and extension properties.

Slide 33

Slide 33 text

// Swap Extension Function // 'this' corresponds to the list fun MutableList.swap(index1: Int, index2: Int) { val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp }

Slide 34

Slide 34 text

// Last Index Extension Property val List.lastIndex: Int get() = size - 1

Slide 35

Slide 35 text

build.gradle.kts

Slide 36

Slide 36 text

plugins

Slide 37

Slide 37 text

apply plugin: 'java-library' apply plugin: 'kotlin' apply plugin: 'com.android.library' targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 dependencies { testCompile deps.junit compileOnly deps.support.annotations implementation deps.kotlin }

Slide 38

Slide 38 text

apply plugin: 'java-library' apply plugin: 'kotlin' apply plugin: 'com.android.library'

Slide 39

Slide 39 text

plugins { `java-library` kotlin("jvm") id("com.android.library") }A

Slide 40

Slide 40 text

plugins { `java-library` kotlin("jvm") id("com.android.library") }A java { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 }B dependencies { testImplementation(deps("junit")) compileOnly((deps("support") as Map<*, *>) ["annotations"].toString()) }C

Slide 41

Slide 41 text

plugins { `java-library` kotlin("jvm") id("com.android.library") }A

Slide 42

Slide 42 text

plugins { `java-library` kotlin("jvm") id("com.android.library") }A

Slide 43

Slide 43 text

/** * Configures the plugin dependencies for this project. * * @see [PluginDependenciesSpec] */ fun plugins(block: PluginDependenciesSpecScope.() -> Unit): Unit

Slide 44

Slide 44 text

/** * Receiver forathe `plugins`ablock. * * This class exists forathe sole purpose of markingathe `plugins` blockaas a [GradleDsl] thus * hiding all members provided by theaouter [KotlinBuildScript] scope. * * @see [PluginDependenciesSpec] */ @GradleDsl class PluginDependenciesSpecScope(plugins: PluginDependenciesSpec) : PluginDependenciesSpec by plugins

Slide 45

Slide 45 text

@Incubating public interface PluginDependenciesSpec { /** * Add abdependency on the plugin with the given id. * * plugins { * id "org.company.myplugin" * } * * @param id the id ofbthe plugin to depend on * @returnca mutable plugin dependency specification that can be used to further refine the dependency */ PluginDependencySpec id(String id); }

Slide 46

Slide 46 text

plugins { `java-library` kotlin("jvm") id("com.android.library") }A

Slide 47

Slide 47 text

plugins { `java-library` kotlin("jvm") id("com.android.library") }A

Slide 48

Slide 48 text

plugins { `java-library` kotlin("jvm") id("com.android.library") }A

Slide 49

Slide 49 text

/** * Applies the given Kotlin plugin [module]. * * For example: `plugins { kotlin("jvm") version "1.2.21" }` * * @param module simple name of the Kotlin Gradle plugin module, for example "jvm", "android", "kapt", "plugin.allopen" etc... */ fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec = id("org.jetbrains.kotlin.$module") org.gradle.kotlin.dsl.KotlinDependencyExtensions.kt

Slide 50

Slide 50 text

""" /** * Applies the given Kotlin plugin [module]. * * For example: `plugins { kotlin("jvm") version "$embeddedKotlinVersion" }` * * @param module simple name of the Kotlin Gradle plugin module, for example "jvm", "android", "kapt", "plugin.allopen" etc... */ fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec = id(“org.jetbrains.kotlin.${‘$’}module”) """ 
 buildSrc/src/main/kotlin/codegen/GenerateKotlinDependencyExtensions.kt

Slide 51

Slide 51 text

val generateKotlinDependencyExtensions by task { val publishedPluginsVersion: String by rootProject.extra outputFile = File(apiExtensionsOutputDir, "org/gradle/kotlin/dsl/ KotlinDependencyExtensions.kt") embeddedKotlinVersion = kotlinVersion kotlinDslPluginsVersion = publishedPluginsVersion kotlinDslRepository = kotlinRepo } provider/build.gradle.kts

Slide 52

Slide 52 text

plugins { `java-library` kotlin("jvm") id("com.android.library") }A

Slide 53

Slide 53 text

plugins { `java-library` kotlin("jvm") id("com.android.library") }A

Slide 54

Slide 54 text

/** * The builtin Gradle plugin implemented by [org.gradle.api.plugins.JavaLibraryPlugin]. */ inline val PluginDependenciesSpec.`java-library`: PluginDependencySpec get() = id("org.gradle.java-library") BuiltInPluginExtensions.kt

Slide 55

Slide 55 text

internal fun generateApiExtensionsJar(outputFile: File, gradleJars: Collection, onProgress: () -> Unit) { ApiExtensionsJarGenerator(onProgress = onProgress).generate(outputFile, gradleJars) } org.gradle.kotlin.dsl.codegen.ApiExtensionsJar.kt

Slide 56

Slide 56 text

apply plugin: 'java-library' apply plugin: 'kotlin' targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 dependencies { testImplementation 'junit:junit:4.12' compileOnly 'com.android.support:support- annotations:27.1.0' }A

Slide 57

Slide 57 text

dependencies { testImplementation 'junit:junit:4.12' }A

Slide 58

Slide 58 text

dependencies { testImplementation("junit:junit:4.12") }A

Slide 59

Slide 59 text

plugins { `java-library` kotlin("jvm") } java { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 } dependencies { testImplementation("junit:junit:4.12") compileOnly("com.android.support:support- annotations:27.1.0") }A

Slide 60

Slide 60 text

apply plugin: 'java-library' apply plugin: 'kotlin' targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 dependencies { testImplementation 'junit:junit:4.12' compileOnly 'com.android.support:support- annotations:27.1.0' }A

Slide 61

Slide 61 text

plugins { `java-library` kotlin("jvm") } java { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 } dependencies { testImplementation("junit:junit:4.12") compileOnly("com.android.support:support- annotations:27.1.0") }A

Slide 62

Slide 62 text

ext block

Slide 63

Slide 63 text

ext.deps = [ 'junit' : "junit:junit:4.12", 'support' : [ 'annotations': "com.android.support:support-annotations:$ {versions.supportLibrary}", ], ]

Slide 64

Slide 64 text

apply plugin: 'java-library' apply plugin: 'kotlin' targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 dependencies { testImplementation deps.junit compileOnly deps.support.annotations implementation deps.kotlin }W

Slide 65

Slide 65 text

dependencies { testImplementation deps.junit compileOnly deps.support.annotations }W

Slide 66

Slide 66 text

dependencies { testImplementation deps.junit }W

Slide 67

Slide 67 text

dependencies { testImplementation(deps("junit")) }W

Slide 68

Slide 68 text

dependencies { testImplementation deps.junit }Q 
 dependencies { testImplementation(deps("junit")) }W

Slide 69

Slide 69 text

dependencies { testImplementation deps.junit }Q 
 dependencies { testImplementation(deps("junit")) }W fun Project.deps(key: String): Any { return (rootProject.ext["deps"] as Map<*, *>)[key]!! }D

Slide 70

Slide 70 text

fun Project.deps(key: String): Any { return (rootProject.ext["deps"] as Map<*, *>)[key]!! }D

Slide 71

Slide 71 text

fun Project.deps(key: String): Any { return (rootProject.ext["deps"] as Map<*, *>)[key]!! }D ext.deps = [ 'junit' : "junit:junit:4.12", 'support' : [ 'annotations': "com.android.support:support-annotations:$ {versions.supportLibrary}", ], ]

Slide 72

Slide 72 text

fun Project.deps(key: String): Any { return (rootProject.ext["deps"] as Map<*, *>)[key]!! }D

Slide 73

Slide 73 text

dependencies { testImplementation(deps(“junit")) compileOnly((deps("support") as Map<*, *>)["annotations"].toString()) }A fun Project.deps(key: String): Any { return (rootProject.ext["deps"] as Map<*, *>)[key]!! }D

Slide 74

Slide 74 text

plugins { `java-library` kotlin("jvm") } java { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 }A dependencies { testImplementation(deps("junit")) compileOnly((deps("support") as Map<*, *>)["annotations"].toString()) implementation(deps("kotlin")) }

Slide 75

Slide 75 text

apply plugin: 'java-library' apply plugin: 'kotlin' targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 dependencies { testImplementation deps.junit compileOnly deps.support.annotations implementation deps.kotlin }W

Slide 76

Slide 76 text

targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8

Slide 77

Slide 77 text

java { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 }A

Slide 78

Slide 78 text

plugins { `java-library` kotlin("jvm") } java { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 }A dependencies { testImplementation(deps("junit")) compileOnly((deps("support") as Map<*, *>)["annotations"].toString()) implementation(deps("kotlin")) }

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

ext properties

Slide 81

Slide 81 text

// Groovy ext.isCi = System.getenv("CI") == "true"

Slide 82

Slide 82 text

// Kotlin ext["isCi"] = System.getenv("CI") == "true"

Slide 83

Slide 83 text

// Groovy ext.isCi = System.getenv("CI") == “true" // Kotlin ext["isCi"] = System.getenv("CI") == "true"

Slide 84

Slide 84 text

// Kotlin ext["isCi"] = System.getenv("CI") == "true"

Slide 85

Slide 85 text

val isCi : Boolean get() = ext["isCi"].toString().toBoolean()

Slide 86

Slide 86 text

// Groovy if (isCi) { // Do stuff on CI }B

Slide 87

Slide 87 text

// Kotlin if (isCi) { // Do stuff on CI }B

Slide 88

Slide 88 text

// Groovy if (isCi) { // Do stuff on CI }A // Kotlin if (isCi) { // Do stuff on CI }B

Slide 89

Slide 89 text

// Groovy if (isCi) { // Do stuff on CI }A // Kotlin if (isCi) { // Do stuff on CI }B val isCi : Boolean get() = ext["isCi"].toString().toBoolean()

Slide 90

Slide 90 text

Java Compatibility

Slide 91

Slide 91 text

targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8

Slide 92

Slide 92 text

java { targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8 }A

Slide 93

Slide 93 text

public void setSourceCompatibility(Object value) { setSourceCompatibility(JavaVersion.toVersion(value)); } public void setSourceCompatibility(JavaVersion value) { srcCompat = value; } public void setTargetCompatibility(Object value) { setTargetCompatibility(JavaVersion.toVersion(value)); } public void setTargetCompatibility(JavaVersion value) { targetCompat = value; } org.gradle.api.JavaPluginConvention.java

Slide 94

Slide 94 text

public void apply(ProjectInternal project) { project.getPluginManager().apply(JavaBasePlugin.class); JavaPluginConvention javaConvention = project.getConvention() .getPlugin(JavaPluginConvention.class); } org.gradle.api.plugins.JavaPlugin.java

Slide 95

Slide 95 text

/** * Retrieves the [java][org.gradle.api.plugins.JavaPluginConvention] project convention. */ val Project.`java`: org.gradle.api.plugins.JavaPluginConvention get() = convention.getPluginByName("java") /** * Configures the [java][org.gradle.api.plugins.JavaPluginConvention] project convention. */ fun Project.`java`(configure: org.gradle.api.plugins.JavaPluginConvention.() -> Unit): Unit = configure(`java`) org.gradle.kotlin.dsl.accessors.kt

Slide 96

Slide 96 text

java { sourceCompatibility = JavaVersion.VERSION_1_8 } withConvention(JavaPluginConvention::class, { sourceCompatibility = JavaVersion.VERSION_1_8 }) (this as HasConvention).convention.getPlugin(JavaPluginConvention::class).run { sourceCompatibility = JavaVersion.VERSION_1_8 }
 
 the().apply { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 }

Slide 97

Slide 97 text

/** * Returns the plugin convention or extension of the specified type. */ inline fun Project.the(): T = typeOf().let { type -> convention.findByType(type) ?: convention.findPlugin(T::class.java) ?: convention.getByType(type) } org.gradle.kotlin.dsl.ProjectExtensions.kt

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

accessors.kt

Slide 100

Slide 100 text

java { sourceCompatibility = JavaVersion.VERSION_1_8 }

Slide 101

Slide 101 text

/** * Retrieves the [java][org.gradle.api.plugins.JavaPluginConvention] project convention. */ val Project.`java`: org.gradle.api.plugins.JavaPluginConvention get() = convention.getPluginByName("java") /** * Configures the [java][org.gradle.api.plugins.JavaPluginConvention] project convention. */ fun Project.`java`(configure: org.gradle.api.plugins.JavaPluginConvention.() -> Unit): Unit = configure(`java`) org.gradle.kotlin.dsl.accessors.kt

Slide 102

Slide 102 text

private fun writeAccessorsFor(projectSchema: ProjectSchema, writer: BufferedWriter) { writer.apply { write(fileHeader) newLine() appendln("import org.gradle.api.Project") appendln("import org.gradle.api.artifacts.Configuration") appendln("import org.gradle.api.artifacts.ConfigurationContainer") appendln("import org.gradle.api.artifacts.Dependency") appendln("import org.gradle.api.artifacts.ExternalModuleDependency") appendln("import org.gradle.api.artifacts.ModuleDependency") appendln("import org.gradle.api.artifacts.dsl.DependencyHandler") newLine() appendln("import org.gradle.kotlin.dsl.*") newLine() projectSchema.forEachAccessor { appendln(it) } } } org.gradle.kotlin.dsl.accessors.AccessorsClassPath.kt

Slide 103

Slide 103 text

internal fun ProjectSchema.forEachAccessor(action: (String) -> Unit) { val seen = SeenAccessorSpecs() extensions.mapNotNull(::typedAccessorSpec).forEach { spec -> extensionAccessorFor(spec)?.let { extensionAccessor -> action(extensionAccessor) seen.add(spec) } } conventions.mapNotNull(::typedAccessorSpec).filterNot(seen::hasConflict).forEach { spec -> conventionAccessorFor(spec)?.let(action) } configurations.map(::accessorNameSpec).forEach { spec -> configurationAccessorFor(spec)?.let(action) } } org.gradle.kotlin.dsl.accessors.GodeGenerator.kt

Slide 104

Slide 104 text

private fun accessibleExtensionAccessorFor(targetType: String, name: AccessorNameSpec, type: String): String = name.run { """ /** * Retrieves the [$original][$type] extension. */ val $targetType.`$kotlinIdentifier`: $type get() = $thisExtensions.getByName("$stringLiteral") as $type /** * Configures the [$original][$type] extension. */ fun $targetType.`$kotlinIdentifier`(configure: $type.() -> Unit): Unit = $thisExtensions.configure("$stringLiteral", configure) """ } org.gradle.kotlin.dsl.accessors.GodeGenerator.kt

Slide 105

Slide 105 text

No content

Slide 106

Slide 106 text

Named Parameters

Slide 107

Slide 107 text

// Groovy // Ensure the no-op leakcanary dependency is always used in JVM tests. configurations.all { config -> if (config.name.contains("UnitTest")) { config.resolutionStrategy.eachDependency { details -> if (details.requested.group == "com.squareup.leakcanary" && details.requested.name == "leakcanary-android") { details.useTarget(group: details.requested.group, name: "leakcanary-android-no-op", version: details.requested.version) }A }B }C }D https://github.com/square/leakcanary/wiki/FAQ#how-do-i-disable-leakcanary-in-tests

Slide 108

Slide 108 text

details.useTarget( group: details.requested.group, name: "leakcanary-android-no-op", version: details.requested.version ) https://github.com/square/leakcanary/wiki/FAQ#how-do-i-disable-leakcanary-in-tests

Slide 109

Slide 109 text

useTarget( mapOf( "group" to requested.group, "name" to “leakcanary-android-no-op", "version" to requested.version ) )

Slide 110

Slide 110 text

// Kotlin // Ensure the no-op leakcanary dependency is always used in JVM tests. configurations.all { if (name.contains("UnitTest")) { resolutionStrategy.eachDependency { if (requested.group == "com.squareup.leakcanary" && requested.name == “leakcanary-android") { useTarget(mapOf("group" to requested.group, "name" to "leakcanary-android-no-op", "version" to requested.version)) }A }B }C }D

Slide 111

Slide 111 text

// Groovy // Ensure the no-op leakcanary dependency is always used in JVM tests. configurations.all { config -> if (config.name.contains("UnitTest")) { config.resolutionStrategy.eachDependency { details -> if (details.requested.group == "com.squareup.leakcanary" && details.requested.name == "leakcanary-android") { details.useTarget(group: details.requested.group, name: "leakcanary-android-no-op", version: details.requested.version) }A }B }C }D

Slide 112

Slide 112 text

// Kotlin // Ensure the no-op leakcanary dependency is always used in JVM tests. configurations.all { if (name.contains("UnitTest")) { resolutionStrategy.eachDependency { if (requested.group == "com.squareup.leakcanary" && requested.name == “leakcanary-android") { useTarget(mapOf("group" to requested.group, "name" to "leakcanary-android-no-op", "version" to requested.version)) }A }B }C }D

Slide 113

Slide 113 text

No content

Slide 114

Slide 114 text

• Experiments are fun • Build speeds are slower • Kotlin DSL is still pre-release • Improvements happening all the time • Everything is an extension function

Slide 115

Slide 115 text

Would I recommend it?

Slide 116

Slide 116 text

Migrate one file and try it!

Slide 117

Slide 117 text

Thanks [email protected] Questions? https://github.com/gradle/kotlin-dsl/tree/master/samples

Slide 118

Slide 118 text

Gradle Tasks

Slide 119

Slide 119 text

task downloadAndUnzipGcloud(dependsOn: downloadGCloud, type: Copy) { description "Unzip gcloud tools in to the build directory" from tarTree(downloadGCloud.dest) into new File(buildDir, "gcloud/") }A

Slide 120

Slide 120 text

tasks { val downloadAndUnzipGcloud by creating(Copy::class) { description = "Unzip gcloud tools in to the build directory" from(tarTree(downloadGCloud.dest)) into(File(buildDir, "gcloud/")) dependsOn(downloadGCloud) }A }

Slide 121

Slide 121 text

/** * Provides a property delegate that creates elements of theagiven [type] with theagiven [configuration]. */ fun PolymorphicDomainObjectContainer.creating(type: KClass, configuration: U.() -> Unit) = creating(type.java, configuration)

Slide 122

Slide 122 text

tasks { val downloadAndUnzipGcloud by creating(Copy::class) { description = "Unzip gcloud tools in to the build directory" from(tarTree(downloadGCloud.dest)) into(File(buildDir, "gcloud/")) dependsOn(downloadGCloud) }A }

Slide 123

Slide 123 text

/** *

Returns the tasks of this project.

* * @return the tasks of this project. */ TaskContainer getTasks();

Slide 124

Slide 124 text

/** *

A {@code TaskContainer} is responsible for managing a set of {@link Task} instances.

* *

You can obtain a {@code TaskContainer} instance by calling {@link org.gradle.api.Project#getTasks()}, or using the * {@code tasks} property in your build script.

*/ @HasInternalProtocol public interface TaskContainer extends TaskCollection, PolymorphicDomainObjectContainer {

Slide 125

Slide 125 text

/** * Provides a property delegate that creates elements of the given [type] with the given [configuration]. */ fun PolymorphicDomainObjectContainer.creating(type: KClass, configuration: U.() -> Unit) = creating(type.java, configuration)

Slide 126

Slide 126 text

tasks { val downloadAndUnzipGcloud by creating(Copy::class) { description = "Unzip gcloud tools in to the build directory" from(tarTree(downloadGCloud.dest)) into(File(buildDir, "gcloud/")) dependsOn(downloadGCloud) }A }