Benedikt Ritter - Senior Software Engineer at Gradle Inc.
😺 @britter
Gradle Introduction
What powers the Gradle Kotlin DSL
Pre-compiled Script Plugins
Tipps for authoring Plugins
You define a model of your software project through a Java/Kotlin API
Entry point: org.gradle.api.Project
build.gradle.kts
// empty file
settings.gradle.kts
rootProject.name = "kotlin-everywhere"
build.gradle.kts
tasks.register("t1") {
inputs.dir("...")
ouptuts.file("...")
doLast {
// do some work
}
}
settings.gradle.kts
rootProject.name = "kotlin-everywhere"
buildSrc/src/main/kotlin/MyProject.kt
fun org.gradle.api.Project.configureMyProject() {
tasks.register("t1") {
inputs.dir("...")
ouptuts.file("...")
doLast {
// do some work
}
}
}
build.gradle.kts
configureMyProject()
build.gradle.kts
project(":user-data").plugins.apply("java")
project(":account-data").plugins.apply("java")
project(":services").plugins.apply("org.jetbrains.kotlin.jvm")
project(":desktop-app").plugins.apply("org.jetbrains.kotlin.jvm")
project(":android-app").plugins.apply("org.jetbrains.kotlin.android")
project(":android-app").plugins.apply("com.android.application")
Gradle provides a dynamic model that
… can be configured by build authors
… can be extended by plugin authors
The Kotlin DSL provides
… a statically typed facade on top of that model
… support for authoring builds
… the complete Gradle feature set
Delegated Properties
Extension functions
Implicit receivers
Delegated properties are syntactic sugar for accessing values of a certain type.
Values can be accessed using the by
keyword if the containing object implements:
fun getValue(thisRef: Any?, property: KProperty<*>): T
This works for example for Maps:
val map = mapOf("name" to "Benedikt")
val name: String by map
println(name) // prints "Benedikt"
build.gradle.kts
val myTask = tasks.register<Copy>("myTask") {
// configure copy
}
vs.
build.gradle.kts
val myTask: Copy by tasks.registering {
// configure copy
}
Works with all DomainObjectContainers
build.gradle.kts
val integTest: SourceSet by sourceSets.creating {
compileClasspath = files(configurations.compileClasspath)
runtimeClasspath = files(configurations.runtimeClasspath)
}
val optionalDeps: Configuration by configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
}
Extend any class with additional functionality:
StringExtensions.kt
fun String.capitalize() {
// go through this string and make first characters upper case
}
SomeClass.kt
import capitalize
fun someMethod(str: String)
str.capitalize()
}
build.gradle.kts
plugins {
java
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks.test {
useJUnitPlatform()
}
build.gradle.kts
plugins {
java
}
java { // where is this coming from?
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks.test { // where is test coming from?
useJUnitPlatform()
}
Somewhere inside the Kotlin DSL implementation (simplified)
build.gradle.kts
val org.gradle.plugin.use.PluginDependenciesSpec.`java`
fun org.gradle.api.Project.java(configure: Action<JavaPluginExtension>)
val org.gradle.api.TaskContainer.`test`: TaskProvider<Test>
DSL extensions for plugins that are added to the build are generated on the fly!
The receiver of a call is the object the call will executed against.
class Guitar {
fun playTune() { }
fun playSong(): {
playTune() // implicit receiver
this.playTune() // explicit receiver
}
}
Let’s take a step back and think about build scripts again…
build.gradle.kts
tasks.test {
useJUnitPlatform()
}
Where is tasks
coming from?
Who is the receiver of the useJUnitPlatform()
call?
Project is set as an implicit receiver
All types from the Gradle API are implicitly imported
Configuration lambdas have an implicit receiver of the type that is configured
build.gradle.kts
project.tasks.test { test ->
test.useJUnitPlatform()
}
subproject-a/build.gradle.kts
plugins {
kotlin("jvm")
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
What happens when we introduce subproject-b
?
buildSrc/src/main/kotlin/KotlinConvetions.kt
import org.gradle.api.Plugin
import org.gradle.api.Project
open class KotlinConventions: Plugin<Project> {
override fun apply(project: Project) {
project.plugins.apply("org.jetbrains.kotlin.jvm:1.3.50")
project.dependencies.add("implementation",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50")
project.tasks.withType(KotlinCompile::class) {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
}
}
Very different developer experience from writing build scripts…
buildSrc/src/main/kotlin/kotlin-conventions.kts
plugins {
kotlin("jvm")
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
subproject-a/build.gradle.kts
plugins {
`kotlin-conventions`
}
Let’s write a plugin that collects some names and then greets all those people.
buildSrc/src/main/kotlin/GreetingPlugin.kt
data class Who(project: Project) {
val who = project.property<String>
}
val allWhos = mutableListOf<Who>()
fun greet(configure: Who.() -> Unit) {
val who = Who(project)
who.configure()
allWhos.add(who.who.get())
}
project.tasks.add(GreetingTask::class.java, "greetings")
build.gradle.kts
plugins {
GreetingPlugin
}
greet {
who.set("Hello World")
}
build.gradle
plugins {
id 'GreetingPlugin'
}
// Doesn't work, type Who.() -> Unit is not accessible
greet {
who = "Hello World"
}
Strongly type your plugin API even in Groovy
Your plugin might be used from either Groovy or Kotlin (or even Java!)
Don’t use language specific types
Avoid groovy.lang.Closure
Avoid Kotlin lambdas
Use the built-in org.gradle.api.Action
type
buildSrc/src/main/kotlin/GreetingPlugin.kt
data class Who(project: Project) {
val who = project.property<String>
}
val allWhos = mutableListOf<Who>()
fun greet(action: Action<Who>) {
val who = Who(project)
action.execute(who)
allWhos.add(who.who.get())
}
project.tasks.add(GreetingTask::class.java, "greetings")
With Gradle you model your project through a Java/Kotlin API
The Kotlin DSL provides a staticly types facade to the underlying dynamic model
Pre-compiled script script plugin are a conventient way to structure your build
As a plugin author always use strong types but don’t expose language specific ones
😺 @britter