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

Everything you didn't want to know about the Kotlin DSL

mbonnin
December 04, 2021

Everything you didn't want to know about the Kotlin DSL

mbonnin

December 04, 2021
Tweet

More Decks by mbonnin

Other Decks in Programming

Transcript

  1. Gradle - Kotlin
    Everything you didn’t want to know
    about the Gradle Kotlin APIs

    View Slide

  2. @MartinBonnin
    apollographql/apollo-android

    View Slide

  3. Disclaimer

    View Slide

  4. Once upon a time...

    View Slide

  5. GNU Make
    1988
    CC= gcc # gcc or g++
    CFLAGS=-g -Wall -DNORMALUNIX -DLINUX # -DUSEASM
    LDFLAGS=-L/usr/X11R6/lib
    LIBS=-lXext -lX11 -lnsl -lm
    # subdirectory for objects
    O=linux
    # not too sophisticated dependency
    OBJS= \
    $(O)/doomdef.o \
    $(O)/doomstat.o \
    all: $(O)/linuxxdoom
    clean:
    rm -f *.o *~ *.flc
    rm -f linux/*
    $(O)/linuxxdoom: $(OBJS) $(O)/i_main.o
    $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) $(O)/i_main.o \
    -o $(O)/linuxxdoom $(LIBS)
    $(O)/%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

    View Slide

  6. Maven
    2004

    xmlns="http://maven.apache.org/POM/4.0.0"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
    4.0.0
    com.apollographql.federation
    federation-parent
    0.7.1-SNAPSHOT
    pom



    com.graphql-java
    graphql-java
    ${graphql-java.version}







    org.apache.maven.plugins
    maven-surefire-plugin
    ${maven-surefire-plugin.version}





    com.diffplug.spotless
    spotless-maven-plugin




    View Slide

  7. Gradle
    2008
    plugins {
    id 'build-logic'
    id 'org.jetbrains.kotlin.jvm' version '1.5.31'
    }
    repositories {
    mavenCentral()
    }
    dependencies {
    implementation('com.squareup.okio:okio:3.0.0')
    }
    kotlin {
    sourceSets {
    main {
    languageSettings.optIn('kotlin.RequiresOptIn')
    }
    }
    }

    View Slide

  8. Kotlin DSL
    2018

    View Slide

  9. Differences

    View Slide

  10. plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.5.31'
    }
    dependencies {
    implementation(’com.squareup.okio:okio:3.0.0’)
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = '1.3'
    }
    }
    }

    View Slide

  11. plugins {
    id(”org.jetbrains.kotlin.jvm”).version(“1.5.31”)
    }
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = “1.3”
    }
    }
    }

    View Slide

  12. plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.5.31'
    }
    dependencies {
    implementation(’com.squareup.okio:okio:3.0.0’)
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = '1.3'
    }
    }
    }

    View Slide

  13. plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.5.31'
    }
    dependencies {
    implementation(’com.squareup.okio:okio:3.0.0’)
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = '1.3'
    }
    }
    }
    Dynamic
    Usages

    View Slide

  14. Groovy metaprogramming
    https://groovy-lang.org/metaprogramming.html
    class SomeGroovyClass {
    def invokeMethod(String name, Object args) {
    return "called invokeMethod $name $args"
    }
    def test() {
    return 'method exists'
    }
    }

    View Slide

  15. How does it work?
    With magic ✨

    View Slide

  16. How does it work?
    With magic
    autogenerated
    accessors✨

    View Slide

  17. plugins {
    id(”org.jetbrains.kotlin.jvm”).version(“1.5.31”)
    }
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = “1.3”
    }
    }
    }
    Configuration
    Extension
    Task

    View Slide

  18. The good news

    View Slide

  19. ● Project
    ● Plugins
    ● Tasks
    ● Extension
    ● SourceSets
    ● Configurations
    The Gradle Model
    https://github.com/autonomousapps/gradle-glossary

    View Slide

  20. plugins {
    id(”org.jetbrains.kotlin.jvm”).version(“1.5.31”)
    }
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = “1.3”
    }
    }
    }
    Extension

    View Slide

  21. Extension
    https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtensionAware.html

    View Slide

  22. Extension
    “Most plugins offer some configuration options for build scripts
    and other plugins to use to customize how the plugin works.
    Plugins do this using extension objects.”
    “An extension object is simply an object with Java Bean properties
    that represent the configuration.”
    https://docs.gradle.org/current/userguide/custom_plugins.html#sec:getting_input_from_the_build

    View Slide

  23. // When you're writing
    kotlin {
    explicitApi()
    }

    View Slide

  24. // When you're writing
    kotlin {
    explicitApi()
    }
    // What you're really doing is
    project.extensions.getByName("kotlin").apply {
    this as KotlinJvmProjectExtension
    explicitApi()
    }

    View Slide

  25. // When you're writing
    kotlin {
    explicitApi()
    }
    // What you're really doing is
    project.extensions.getByName("kotlin").apply {
    this as KotlinJvmProjectExtension
    explicitApi()
    }
    // Because Gradle generated an accessor:
    fun org.gradle.api.Project.`kotlin`(configure:
    Action): Unit
    =
    (this as
    org.gradle.api.plugins.ExtensionAware).extensions.configure("kotlin",
    configure)

    View Slide

  26. // You could also write
    configure {
    explicitApi()
    }
    // Or use 'the'
    the().apply {
    explicitApi()
    }
    // Even in imperative style, without lambda
    the().explicitApi()
    // It all works!

    View Slide

  27. Configuration
    https://github.com/gradle/gradle/blob/master/subprojects/dependency-management/src/main/java/or
    g/gradle/internal/component/external/descriptor/Configuration.java

    View Slide

  28. plugins {
    id(”org.jetbrains.kotlin.jvm”).version(“1.5.31”)
    }
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = “1.3”
    }
    }
    }
    Configuration

    View Slide

  29. Configuration
    https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html

    View Slide

  30. Configuration
    “A Configuration represents a group of artifacts and their
    dependencies”
    “Configuration is an instance of a FileCollection that contains all
    dependencies but not artifacts”
    “configurations have at least 3 different roles:
    - to declare dependencies
    - as a consumer, to resolve a set of dependencies to files
    - as a producer, to expose artifacts and their dependencies”
    https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.Configuration.html

    View Slide

  31. Configuration
    A list of dependencies
    Identified by “group:artifact:version”
    “implementation” is the name of a Configuration
    “api” too

    View Slide

  32. // When you’re writing
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }

    View Slide

  33. // When you’re writing
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }
    // What you’re really doing is
    dependencies {
    project.configurations
    .getByName("implementation")
    .dependencies
    .add(create("com.squareup.okio:okio:3.0.0"))
    }

    View Slide

  34. // When you’re writing
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }
    // What you’re really doing is
    dependencies {
    project.configurations
    .getByName("implementation")
    .dependencies
    .add(create("com.squareup.okio:okio:3.0.0"))
    }
    // Because Gradle generated an accessor for you!
    fun DependencyHandler.`implementation`(dependencyNotation: Any):
    Dependency? =
    add("implementation", dependencyNotation)

    View Slide

  35. // You could also write
    dependencies {
    "implementation"("com.squareup.okio:okio:3.0.0")
    }
    // Or that too
    dependencies {
    add("implementation", "com.squareup.okio:okio:3.0.0")
    }
    // It’s all the same...

    View Slide

  36. Task
    “A Task represents a single atomic piece of work for a build, such as
    compiling classes or generating javadoc.”
    https://docs.gradle.org/current/dsl/org.gradle.api.Task.html

    View Slide

  37. plugins {
    id(”org.jetbrains.kotlin.jvm”).version(“1.5.31”)
    }
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = “1.3”
    }
    }
    }
    Task

    View Slide

  38. // When you're writing
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = "1.3"
    }
    }
    }

    View Slide

  39. // When you're writing
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = "1.3"
    }
    }
    }
    // What you’re really doing is
    project.tasks.named("compileKotlin", KotlinCompile::class).configure {
    kotlinOptions {
    apiVersion = "1.3"
    }
    }

    View Slide

  40. // When you're writing
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = "1.3"
    }
    }
    }
    // What you’re really doing is
    project.tasks.named("compileKotlin", KotlinCompile::class).configure {
    kotlinOptions {
    apiVersion = "1.3"
    }
    }
    // Because Gradle generated an accessor for you
    val TaskContainer.`compileKotlin`:
    TaskProvider
    get() =
    named("compileKotlin")

    View Slide

  41. Gradle Containers
    ● project.configurations
    ● project.extensions
    ● project.tasks
    ● project.sourceSets
    ● NamedDomainObjectContainer

    View Slide

  42. When are accessors
    generated?

    View Slide

  43. plugins {
    id(”org.jetbrains.kotlin.jvm”).version(“1.5.31”)
    }
    // Accessors are generated here
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = “1.3”
    }
    }
    Magic
    happens here
    ✨ ✨

    View Slide

  44. class KotlinPlugin: Plugin {
    override fun apply(target: Project) {
    target.configurations.create("kotlinCompilerClasspath")
    target.extensions.create("kotlin",
    KotlinJvmProjectExtension::class.java)
    target.tasks.register("compileKotlin", CompileKotlinTask::class.java)
    }
    }
    abstract class CompileKotlinTask: DefaultTask() {
    //
    }
    abstract class KotlinJvmProjectExtension {
    //
    }
    Pseudo Kotlin Plugin Code

    View Slide

  45. Accessors generation
    https://github.com/gradle/gradle/blob/master/subprojects/kotlin-dsl-provider-plugins/src/main/k
    otlin/org/gradle/kotlin/dsl/provider/plugins/DefaultProjectSchemaProvider.kt

    View Slide

  46. plugins {
    id(”org.jetbrains.kotlin.jvm”).version(“1.5.31”)
    }
    // Accessors are generated here
    dependencies {
    implementation("com.squareup.okio:okio:3.0.0")
    }
    kotlin {
    explicitApi()
    }
    tasks {
    compileKotlin {
    kotlinOptions {
    apiVersion = “1.3”
    }
    }
    }
    Kotlin build script

    View Slide

  47. buildscript {
    dependencies {
    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31")
    }
    }
    apply(plugin = "org.jetbrains.kotlin.jvm")
    dependencies {
    add("implementation", "com.squareup.okio:okio:3.0.0")
    }
    configure {
    explicitApi()
    }
    tasks.named("compileKotlin", KotlinCompile::class) {
    kotlinOptions {
    apiVersion = "1.3"
    }
    }
    }
    Kotlin build script

    View Slide

  48. Kotlin DSL
    !=
    Kotlin build scripts

    View Slide

  49. Kotlin DSL
    ~=
    Kotlin build scripts
    +
    Generated Accessors
    +
    gradleKotlinDSL()
    +
    SamWithReceiver

    View Slide

  50. gradleKotlinDsl() dependency
    ● the()
    ● val commonMain by getting()
    ● val appleMain by creating()
    ● val myProperty: String by project
    ● closureOf {}
    ● ...

    View Slide

  51. SamWithReceiver
    val taskProvider = tasks.named("compileKotlin", KotlinCompile::class)
    taskProvider.configure {
    kotlinOptions {
    apiVersion = "1.3"
    }
    }

    View Slide

  52. SamWithReceiver
    val taskProvider = tasks.named("compileKotlin", KotlinCompile::class)
    taskProvider.configure {
    // How come the task is in 'this' and not 'it' ??
    kotlinOptions {
    apiVersion = "1.3"
    }
    }
    // configure takes an Action
    public interface Action {
    void execute(T t);
    }

    View Slide

  53. https://kotlinlang.org/docs/sam-with-receiver-plugin.html
    @HasImplicitReceiver
    public interface Action {
    /**
    * Performs this action against the given
    object.
    *
    * @param t The object to perform the action
    on.
    */
    void execute(T t);
    }

    View Slide

  54. Should I care?

    View Slide

  55. View Slide

  56. View Slide

  57. https://github.com/gradle/gradle/issues/15886#issuecomment-954833377

    View Slide

  58. “I can copy paste and
    it just works”
    “Yay, now I understand what’s
    going on!”
    “Well, maybe I can sacrifice type
    safety for build speed”

    View Slide

  59. apply plugin: 'com.android.library'
    apply plugin: 'kotlin-android'
    android {
    // lots of boilerplate
    }
    tasks.withType(KotlinCompile).configureEach {
    kotlinOptions {
    jvmTarget = JavaVersion.VERSION_11
    }
    }
    Convention plugins
    plugins {
    id 'com.squareup.android.lib'
    }
    https://developer.squareup.com/blog/herding-elephants/

    View Slide

  60. ● Kotlin DSL != Kotlin build scripts
    ● Use convention plugins for your Kotlin build
    logic:
    https://developer.squareup.com/blog/herding-elephants/
    Takeaways

    View Slide

  61. Merci!
    @MartinBonnin 🎄
    https://bit.ly/everything-you-didnt-want-to-know-about-gradle

    View Slide

  62. Resources
    ● https://docs.gradle.org/current/userguide/kotlin
    _dsl.html
    ● https://blog.mbonnin.net
    ● https://www.youtube.com/watch?v=XXoIzzcJr80
    ● https://github.com/bernaferrari/GradleKotlinCo
    nverter

    View Slide