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

Rock The Gradle MobileEra

Rock The Gradle MobileEra

More often than none, an Android project's build.gradle files grow larger and more complex with time. This talk will look at the underappreciated buildSrc folder, which allows you to create plugins directly from your project, but also can help you make your build.gradle files cleaner and more readable.

Xavier Gouchet

November 08, 2019
Tweet

More Decks by Xavier Gouchet

Other Decks in Programming

Transcript

  1. Xavier F. Gouchet, Senior Software Engineer
    @xgouchet
    Rock the Gradle,
    Rule the world

    View Slide

  2. View Slide

  3. INTRODUCTION
    0
    A (brief) Gradle introduction

    View Slide

  4. WHAT IS GRADLE ?
    General Purpose
    Language Agnostic
    Feature Agnostic
    Dependency
    Management System
    High performance
    Build Management
    Task Dependency Graph

    View Slide

  5. THE GRADLE ALGORITHM
    INIT
    Launches the JVM
    Analyse the working directory
    Creates the Project object(s)
    Compiles the buildSrc module(s)
    CONFIG EXEC
    Executes all the build.gradle scripts
    Creates & configures tasks
    Resolve dependencies
    Execute the relevant tasks

    View Slide

  6. BUILDSRC
    1
    “It’s a kind of Magic”
    — Queen

    View Slide

  7. ┬ MyProject
    ├┬ app/
    │├── src/
    │└── build.gradle
    ├┬ buildSrc/
    │├── src/
    │├── build.gradle
    │└── settings.gradle
    ├─ build.gradle
    └─ settings.gradle
    Project Structure

    View Slide

  8. ┬ MyProject
    ├┬ app/
    │├── src/
    │└── build.gradle
    ├┬ buildSrc/
    │├── src/
    │├── build.gradle
    │└── settings.gradle
    ├─ build.gradle
    └─ settings.gradle
    Project Structure

    View Slide

  9. ┬ MyProject
    ├┬ app/
    │├── src/
    │└── build.gradle
    ├┬ buildSrc/
    │├── src/
    │├── build.gradle
    │└── settings.gradle
    ├─ build.gradle
    └─ settings.gradle
    Project Structure

    View Slide

  10. ┬ MyProject
    ├┬ app/
    │├── src/
    │└── build.gradle
    ├┬ buildSrc/
    │├── src/
    │├── build.gradle
    │└── settings.gradle
    ├─ build.gradle
    └─ settings.gradle
    Project Structure

    View Slide

  11. Works like any module in your project
    Compiled and tested before any gradle task
    Groovy, Java, Kotlin, …
    Any public class / method becomes available in gradle
    scripts
    How does it work?

    View Slide

  12. Better dependency management
    Helper classes / methods
    Plugin configuration
    Custom plugin
    (Locally versioned with the project)
    What can we use it for?

    View Slide


  13. “A single change in buildSrc
    causes the whole project to
    become out-of-date.”

    View Slide

  14. SCRIPTS
    2
    “Don’t repeat yourself”
    — Uncle Bob Martin

    View Slide

  15. DEPENDENCIES
    Write Once
    Use Everywhere
    https://unsplash.com/@eliabevces

    View Slide

  16. dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:…"
    implementation "androidx.appcompat:appcompat:…"
    implementation "androidx.core:core-ktx:…"
    implementation "androidx.annotation:annotation:…"
    }
    Dependencies
    app/build.gradle

    View Slide

  17. object Dependencies {
    object Versions {
    const val Kotlin = "1.3.50"
    const val AndroidX = "1.0.0"
    }
    // …
    }
    Dependencies
    buildSrc/src/main/kotlin/Dependencies.kt

    View Slide

  18. object Dependencies {
    // …
    object Libraries {
    const val Kotlin =
    "org.jetbrains.kotlin:kotlin-stdlib:${Versions.Kotlin}"
    @JvmField val AndroidX = arrayOf(
    "androidx.appcompat:appcompat:${Versions.AndroidX}",
    "androidx.core:core-ktx:${Versions.AndroidX}",
    "androidx.annotation:annotation:${Versions.AndroidX}"
    )
    }
    }
    Dependencies
    buildSrc/src/main/kotlin/Dependencies.kt

    View Slide

  19. object Dependencies {
    // …
    object Libraries {
    const val Kotlin =
    "org.jetbrains.kotlin:kotlin-stdlib:${Versions.Kotlin}"
    @JvmField val AndroidX = arrayOf(
    "androidx.appcompat:appcompat:${Versions.AndroidX}",
    "androidx.core:core-ktx:${Versions.AndroidX}",
    "androidx.annotation:annotation:${Versions.AndroidX}"
    )
    }
    }
    Dependencies
    buildSrc/src/main/kotlin/Dependencies.kt

    View Slide

  20. object Dependencies {
    // …
    object Libraries {
    const val Kotlin =
    "org.jetbrains.kotlin:kotlin-stdlib:${Versions.Kotlin}"
    @JvmField val AndroidX = arrayOf(
    "androidx.appcompat:appcompat:${Versions.AndroidX}",
    "androidx.core:core-ktx:${Versions.AndroidX}",
    "androidx.annotation:annotation:${Versions.AndroidX}"
    )
    }
    }
    Dependencies
    buildSrc/src/main/kotlin/Dependencies.kt

    View Slide

  21. import Dependencies
    dependencies {
    implementation Dependencies.Libraries.Kotlin
    implementation Dependencies.Libraries.AndroidX
    }
    Dependencies
    app/build.gradle

    View Slide

  22. CONFIGURATIONS
    Hide the bulk
    https://unsplash.com/@adigold1

    View Slide

  23. detekt {
    toolVersion = "1.0.1"
    input = files("$projectDir/src/main/java")
    config = files("$project.rootDir/config/detekt.yml")
    reports.xml {
    enabled = true
    destination = file("build/reports/detekt.xml")
    }
    }
    check.dependsOn("detekt")
    Plugin Configuration
    app/build.gradle

    View Slide

  24. fun Project.detektConfig() {
    val ext = extensions.findByType(DetektExtension::class)
    ext.version = "1.0.1"
    ext.input = files("$projectDir/src/main/kotlin")
    // …
    tasks.named("check") {
    dependsOn("detekt")
    }
    }
    Plugin Configuration
    buildSrc/src/main/kotlin/DetektConfig.kt

    View Slide

  25. fun Project.detektConfig() {
    val ext = extensions.findByType(DetektExtension::class)
    ext.version = "1.0.1"
    ext.input = files("$projectDir/src/main/kotlin")
    // …
    tasks.named("check") {
    dependsOn("detekt")
    }
    }
    Plugin Configuration
    buildSrc/src/main/kotlin/DetektConfig.kt

    View Slide

  26. fun Project.detektConfig() {
    val ext = extensions.findByType(DetektExtension::class)
    ext.version = "1.0.1"
    ext.input = files("$projectDir/src/main/kotlin")
    // …
    tasks.named("check") {
    dependsOn("detekt")
    }
    }
    Plugin Configuration
    buildSrc/src/main/kotlin/DetektConfig.kt

    View Slide

  27. fun Project.detektConfig() {
    val ext = extensions.findByType(DetektExtension::class)
    ext.version = "1.0.1"
    ext.input = files("$projectDir/src/main/kotlin")
    // …
    tasks.named("check") {
    dependsOn("detekt")
    }
    }
    Plugin Configuration
    buildSrc/src/main/kotlin/DetektConfig.kt

    View Slide

  28. fun Project.detektConfig() {
    val ext = extensions.findByType(DetektExtension::class)
    ext.version = "1.0.1"
    ext.input = files("$projectDir/src/main/kotlin")
    // …
    tasks.named("check") {
    dependsOn("detekt")
    }
    }
    Plugin Configuration
    buildSrc/src/main/kotlin/DetektConfig.kt

    View Slide

  29. import detektConfig
    detektConfig()
    Plugin Configuration
    app/build.gradle

    View Slide

  30. UTILITIES
    Expand your toolbox
    https://unsplash.com/@toddquackenbush

    View Slide

  31. android {
    defaultConfig {
    versionCode 31401
    versionName "3.14.1"
    }
    }
    Version Code & Version Name
    app/build.gradle

    View Slide

  32. data class Version(
    val major: Int,
    val minor: Int,
    val hotfix: Int
    ) {
    val name = "$major.$minor.$hotfix"
    val code = (major × 10000) + (minor × 100) + hotfix
    }
    Version Code & Version Name
    buildSrc/src/main/kotlin/Version.kt

    View Slide

  33. data class Version(
    val major: Int,
    val minor: Int,
    val hotfix: Int
    ) {
    val name = "$major.$minor.$hotfix"
    val code = (major × 10000) + (minor × 100) + hotfix
    }
    Version Code & Version Name
    buildSrc/src/main/kotlin/Version.kt

    View Slide

  34. data class Version(
    val major: Int,
    val minor: Int,
    val hotfix: Int
    ) {
    val name = "$major.$minor.$hotfix"
    val code = (major × 10000) + (minor × 100) + hotfix
    }
    Version Code & Version Name
    buildSrc/src/main/kotlin/Version.kt

    View Slide

  35. object App {
    val Version = Version(3, 14, 1)
    }
    Version Code & Version Name
    buildSrc/src/main/kotlin/App.kt

    View Slide

  36. import App
    android {
    defaultConfig {
    versionCode App.Version.code
    versionName App.Version.name
    }
    }
    Version Code & Version Name
    app/build.gradle

    View Slide

  37. PLUGINS
    3
    Now you’re thinking with Plugins

    View Slide

  38. Variants
    Graph
    What a Plugin can do…
    Tasks Extensions
    Per-module configurations
    Configuration
    Project
    Task
    Dependencies

    View Slide

  39. A concrete example
    Sharing localisation strings between platforms

    View Slide

  40. TASK
    The Core Feature
    https://unsplash.com/@wanxi

    View Slide

  41. A Custom Task
    open class GetStringsTask : DefaultTask() {
    var languages : Array = arrayOf()
    var root = ""
    var baseUrl = "http://127.0.0.1"
    @TaskAction fun performTask() {
    for (l in languages) {
    Downloader.download("$baseUrl/$l/strings.xml",
    "$root/res/values-$l/strings.xml")
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsTask.kt

    View Slide

  42. A Custom Task
    open class GetStringsTask : DefaultTask() {
    var languages : Array = arrayOf()
    var root = ""
    var baseUrl = "http://127.0.0.1"
    @TaskAction fun performTask() {
    for (l in languages) {
    Downloader.download("$baseUrl/$l/strings.xml",
    "$root/res/values-$l/strings.xml")
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsTask.kt

    View Slide

  43. A Custom Task
    open class GetStringsTask : DefaultTask() {
    var languages : Array = arrayOf()
    var root = ""
    var baseUrl = "http://127.0.0.1"
    @TaskAction fun performTask() {
    for (l in languages) {
    Downloader.download("$baseUrl/$l/strings.xml",
    "$root/res/values-$l/strings.xml")
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsTask.kt

    View Slide

  44. A Custom Task
    open class GetStringsTask : DefaultTask() {
    var languages : Array = arrayOf()
    var root = ""
    var baseUrl = "http://127.0.0.1"
    @TaskAction fun performTask() {
    for (l in languages) {
    Downloader.download("$baseUrl/$l/strings.xml",
    "$root/res/values-$l/strings.xml")
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsTask.kt

    View Slide

  45. A Custom Task
    open class GetStringsTask : DefaultTask() {
    var languages : Array = arrayOf()
    var root = ""
    var baseUrl = "http://127.0.0.1"
    @TaskAction fun performTask() {
    for (l in languages) {
    Downloader.download("$baseUrl/$l/strings.xml",
    "$root/res/values-$l/strings.xml")
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsTask.kt

    View Slide

  46. A Custom Task
    import GetStringsTask
    task downloadStrings(type: GetStringsTask) {
    languages = ["en", "fr"]
    root = "$projectDir/src/main"
    baseUrl = "https://example.org/locales"
    }
    app/build.gradle

    View Slide

  47. EXTENSION
    Adding some
    flexibility
    https://unsplash.com/@glenncarstenspeters

    View Slide

  48. A Custom Extension
    open class GetStringsExt(
    var languages: Array = arrayOf(),
    var baseUrl: String = "http://127.0.0.1"
    )
    buildSrc/src/main/kotlin/GetStringsExt.kt

    View Slide

  49. A Custom Extension
    open class GetStringsTask : DefaultTask() {
    var root = ""
    var extension = GetStringsExt()
    @TaskAction fun performTask() {
    for (l in extension.languages) {
    val url = "${extension.baseUrl}/$l/strings.xml"
    val path = "$root/res/values-$l/strings.xml"
    Downloader.download(url, path)
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsTask.kt

    View Slide

  50. A Custom Extension
    open class GetStringsTask : DefaultTask() {
    var root = ""
    var extension = GetStringsExt()
    @TaskAction fun performTask() {
    for (l in extension.languages) {
    val url = "${extension.baseUrl}/$l/strings.xml"
    val path = "$root/res/values-$l/strings.xml"
    Downloader.download(url, path)
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsTask.kt

    View Slide

  51. A Custom Extension
    open class GetStringsTask : DefaultTask() {
    var root = ""
    var extension = GetStringsExt()
    @TaskAction fun performTask() {
    for (l in extension.languages) {
    val url = "${extension.baseUrl}/$l/strings.xml"
    val path = "$root/res/values-$l/strings.xml"
    Downloader.download(url, path)
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsTask.kt

    View Slide

  52. A Custom Extension
    open class GetStringsTask : DefaultTask() {
    var root = ""
    var extension = GetStringsExt()
    @TaskAction fun performTask() {
    for (l in extension.languages) {
    val url = "${extension.baseUrl}/$l/strings.xml"
    val path = "$root/res/values-$l/strings.xml"
    Downloader.download(url, path)
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsTask.kt

    View Slide

  53. A Custom Extension… and a Plugin
    class GetStringsPlugin : Plugin {
    override fun apply(project: Project) {
    val ext = project.extensions
    .create("getStrings", GetStringsExt::class.java)
    val task = project.tasks
    .create("getStrings", GetStringsTask::class.java)
    task.apply {
    root = "${project.projectDir.path}/src/main"
    extension = ext
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  54. A Custom Extension… and a Plugin
    class GetStringsPlugin : Plugin {
    override fun apply(project: Project) {
    val ext = project.extensions
    .create("getStrings", GetStringsExt::class.java)
    val task = project.tasks
    .create("getStrings", GetStringsTask::class.java)
    task.apply {
    root = "${project.projectDir.path}/src/main"
    extension = ext
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  55. A Custom Extension… and a Plugin
    class GetStringsPlugin : Plugin {
    override fun apply(project: Project) {
    val ext = project.extensions
    .create("getStrings", GetStringsExt::class.java)
    val task = project.tasks
    .create("getStrings", GetStringsTask::class.java)
    task.apply {
    root = "${project.projectDir.path}/src/main"
    extension = ext
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  56. A Custom Extension… and a Plugin
    class GetStringsPlugin : Plugin {
    override fun apply(project: Project) {
    val ext = project.extensions
    .create("getStrings", GetStringsExt::class.java)
    val task = project.tasks
    .create("getStrings", GetStringsTask::class.java)
    task.apply {
    root = "${project.projectDir.path}/src/main"
    extension = ext
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  57. A Custom Extension… and a Plugin
    class GetStringsPlugin : Plugin {
    override fun apply(project: Project) {
    val ext = project.extensions
    .create("getStrings", GetStringsExt::class.java)
    val task = project.tasks
    .create("getStrings", GetStringsTask::class.java)
    task.apply {
    root = "${project.projectDir.path}/src/main"
    extension = ext
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  58. A Custom Extension… and a Plugin
    class GetStringsPlugin : Plugin {
    override fun apply(project: Project) {
    val ext = project.extensions
    .create("getStrings", GetStringsExt::class.java)
    val task = project.tasks
    .create("getStrings", GetStringsTask::class.java)
    task.apply {
    root = "${project.projectDir.path}/src/main"
    extension = ext
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  59. A Custom Extension… and a Plugin
    import GetStringsPlugin
    apply plugin: GetStringsPlugin
    getStrings {
    languages = ["en", "fr"]
    baseUrl = "https://example.org/locales"
    // no need to specify the root path anymore
    }
    app/build.gradle

    View Slide

  60. The extension makes it easy to configure
    The plugin can fill in properties automatically
    The plugin can generate tasks per variant
    The plugin can manipulate the graph
    Why use a Plugin+Extension

    View Slide

  61. GOING FURTHER
    4
    “Don't stop me now (cause I’m having a good time!)”
    — Queen

    View Slide

  62. TASK GRAPH
    Where the
    Magic happen
    https://unsplash.com/@bill_oxford

    View Slide

  63. Ordering Tasks
    class GetStringsPlugin : Plugin {
    override fun apply(project: Project) {
    // …
    project.afterEvaluate { p ->
    p.tasks
    .withType(GenerateResValues::class.java) {
    it.dependsOn(task)
    }
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  64. Ordering Tasks
    class GetStringsPlugin : Plugin {
    override fun apply(project: Project) {
    // …
    project.afterEvaluate { p ->
    p.tasks
    .withType(GenerateResValues::class.java) {
    it.dependsOn(task)
    }
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  65. Ordering Tasks
    class GetStringsPlugin : Plugin {
    override fun apply(project: Project) {
    // …
    project.afterEvaluate { p ->
    p.tasks
    .withType(GenerateResValues::class.java) {
    it.dependsOn(task)
    }
    }
    }
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  66. SIDENOTE ON TASK ORDER
    STRICT
    A.dependsOn(B)
    C.finalizedBy(B)
    LOOSE
    D.mustRunAfter(B)
    E.shouldRunAfter(B)

    View Slide

  67. SIDENOTE ON TASK ORDER
    LOOSE
    D.mustRunAfter(B)
    E.shouldRunAfter(B)
    $ gradle D
    > Task :app:D
    BUILD SUCCESSFUL in 4s
    1 task executed

    View Slide

  68. SIDENOTE ON TASK ORDER
    LOOSE
    D.mustRunAfter(B)
    E.shouldRunAfter(B)
    $ gradle D B
    > Task :app:B
    > Task :app:D
    BUILD SUCCESSFUL in 8s
    2 tasks executed

    View Slide

  69. SIDENOTE ON TASK ORDER
    LOOSE
    D.mustRunAfter(B)
    E.shouldRunAfter(B)
    $ gradle E
    > Task :app:E
    BUILD SUCCESSFUL in 15s
    1 task executed

    View Slide

  70. SIDENOTE ON TASK ORDER
    LOOSE
    D.mustRunAfter(B)
    E.shouldRunAfter(B)
    $ gradle E B
    > Task :app:B
    > Task :app:E
    BUILD SUCCESSFUL in 16s
    2 tasks executed

    View Slide

  71. SIDENOTE ON TASK ORDER
    LOOSE
    D.mustRunAfter(B)
    E.shouldRunAfter(B)
    B.dependsOn(A)
    A.dependsOn(E)
    $ gradle E B
    > Task :app:E
    > Task :app:A
    > Task :app:B
    BUILD SUCCESSFUL in 23s
    3 tasks executed

    View Slide

  72. PLUGIN INTEGRATION
    Help the developer
    use your Plugin
    https://unsplash.com/@momentsbygabriel

    View Slide

  73. Aliasing the Plugin
    apply plugin: "java-gradle-plugin"
    gradlePlugin {
    plugins {
    getStrings {
    id = "getStrings" // the alias
    implementationClass = "GetStringsPlugin"
    }
    }
    }
    buildSrc/build.gradle

    View Slide

  74. Aliasing the Plugin
    import GetStringsPlugin
    apply plugin: GetStringsPlugin
    app/build.gradle

    View Slide

  75. Aliasing the Plugin
    apply plugin: "getStrings"
    app/build.gradle

    View Slide

  76. Task’s group & description
    class GetStringsPlugin : Plugin {
    init {
    group = "mobileEra"
    description = "Downloads strings.xml from server."
    }
    // …
    }
    buildSrc/src/main/kotlin/GetStringsPlugin.kt

    View Slide

  77. Android tasks
    -------------
    androidDependencies - Displays the Android dependencies.
    sourceSets - Prints out all the source sets in the project.
    MobileEra tasks
    ---------------
    getStrings - Downloads strings.xml from a server.
    Verification tasks
    ------------------
    lint - Runs lint on all variants.
    Task’s group & description
    $ gradlew :app:tasks

    View Slide

  78. Android tasks
    -------------
    androidDependencies - Displays the Android dependencies.
    sourceSets - Prints out all the source sets in the project.
    MobileEra tasks
    ---------------
    getStrings - Downloads strings.xml from a server.
    Verification tasks
    ------------------
    lint - Runs lint on all variants.
    Task’s group & description
    $ gradlew :app:tasks

    View Slide

  79. GRADLE CACHE
    Avoid
    unnecessary work
    https://unsplash.com/@jasonrobertsphotography

    View Slide

  80. buildSrc/src/main/kotlin/GetStringsTask.kt
    Defining Inputs & Outputs
    open class GetStringsTask : DefaultTask() {
    @Input fun getLanguagesInputs(): List {
    return extension.languages.toList()
    }
    @Input fun getBaseUrlInput(): String {
    return extension.baseUrl
    }
    }

    View Slide

  81. buildSrc/src/main/kotlin/GetStringsTask.kt
    Defining Inputs & Outputs
    open class GetStringsTask : DefaultTask() {
    @OutputFiles
    fun getTaskOutputs(): List {
    return extension.languages.map { l ->
    File("$root/res/values-$l/strings.xml")
    }
    }
    }

    View Slide

  82. It will never
    update until we change
    the config ?

    View Slide

  83. buildSrc/src/main/kotlin/GetStringsTask.kt
    Defining Inputs & Outputs
    open class GetStringsTask : DefaultTask() {
    @Input
    fun getDateInput() : String {
    val formatter = DateTimeFormatter.ISO_DATE
    return LocalDateTime.now().format(formatter)
    }
    }

    View Slide

  84. INCREMENTAL TASKS
    Avoid
    unnecessary work
    https://unsplash.com/@pluyar

    View Slide

  85. buildSrc/src/main/kotlin/GetStringsTask.kt
    Incremental Task
    open class GetStringsTask : DefaultTask() {
    @TaskAction
    fun incrementAction(inputs: IncrementalTaskInputs) {
    if (inputs.isIncremental) {
    inputs.outOfDate { println("Out of date: $it") }
    inputs.removed { println("Removed: $it") }
    } else {
    // …
    }
    }
    }

    View Slide

  86. buildSrc/src/main/kotlin/GetStringsTask.kt
    Incremental Task
    open class GetStringsTask : DefaultTask() {
    @TaskAction
    fun incrementAction(inputs: IncrementalTaskInputs) {
    if (inputs.isIncremental) {
    inputs.outOfDate { println("Out of date: $it") }
    inputs.removed { println("Removed: $it") }
    } else {
    // …
    }
    }
    }

    View Slide

  87. buildSrc/src/main/kotlin/GetStringsTask.kt
    Incremental Task
    open class GetStringsTask : DefaultTask() {
    @TaskAction
    fun incrementAction(inputs: IncrementalTaskInputs) {
    if (inputs.isIncremental) {
    inputs.outOfDate { println("Out of date: $it") }
    inputs.removed { println("Removed: $it") }
    } else {
    // …
    }
    }
    }

    View Slide

  88. buildSrc/src/main/kotlin/GetStringsTask.kt
    Incremental Task
    open class GetStringsTask : DefaultTask() {
    @TaskAction
    fun incrementAction(inputs: IncrementalTaskInputs) {
    if (inputs.isIncremental) {
    inputs.outOfDate { println("Out of date: $it") }
    inputs.removed { println("Removed: $it") }
    } else {
    // …
    }
    }
    }

    View Slide

  89. buildSrc/src/main/kotlin/GetStringsTask.kt
    Incremental Task
    open class GetStringsTask : DefaultTask() {
    @TaskAction
    fun incrementAction(inputs: IncrementalTaskInputs) {
    if (inputs.isIncremental) {
    inputs.outOfDate { println("Out of date: $it") }
    inputs.removed { println("Removed: $it") }
    } else {
    // …
    }
    }
    }

    View Slide

  90. buildSrc/src/main/kotlin/GetStringsTask.kt
    Incremental Task
    open class GetStringsTask : DefaultTask() {
    @TaskAction
    fun incrementAction(inputs: IncrementalTaskInputs) {
    if (inputs.isIncremental) {
    inputs.outOfDate { println("Out of date: $it") }
    inputs.removed { println("Removed: $it") }
    } else {
    // …
    }
    }
    }

    View Slide

  91. TESTING YOUR PLUGIN
    #TestMatters
    Caspar Benson / Getty Images

    View Slide

  92. PLUGIN
    Only test the action of
    the Plugin itself
    INTEGRATION
    Dummy project to test
    real life scenarios
    Testing a Gradle Plugin

    buildSrc is tested on each build!
    TASK
    Delegate to other
    classes as much as
    possible

    View Slide

  93. CREATING A DSL
    Simplify the
    configuration
    https://unsplash.com/@ratushny

    View Slide

  94. app/build.gradle
    Creating a DSL
    getStrings {
    baseUrl = "https://example.org/locales"
    variants {
    debug { languages = ["en"] }
    free { languages = ["en", "fr"] }
    fullRelease { languages = ["en", "fr", "es"] }
    }
    }

    View Slide

  95. DSL can go as deep as you need

    View Slide

  96. GENERATING STUFF
    Delegate the
    tedious tasks
    https://unsplash.com/@lennykuhne

    View Slide

  97. Generating Files
    Android Manifest
    Source code (JavaPoet / KotlinPoet)
    Resources (Strings, Drawables, Layouts, NavGraph, …)
    Assets…

    View Slide


  98. “Make sure your generator task is
    executed at the right moment.”

    View Slide

  99. WRAPPING UP
    5
    You can enter here the subtitle if you need it

    View Slide

  100. buildSrc is always built and tested
    Take away
    A single change in buildSrc invalidates all tasks
    Publish your plugins

    View Slide

  101. Your build scripts are still code.
    Keep them as clean, maintainable
    and understandable
    as your production code.

    View Slide

  102. Get the slides at
    https://speakerdeck.com/xgouchet/rock-the-gradle-mobileera
    Check sample project at
    https://github.com/xgouchet/RockTheGradle/

    View Slide

  103. CREDITS: This presentation template was created by Slidesgo, including
    icons by Flaticon, and infographics & images by Freepik.
    Do you have any questions?
    @xgouchet
    @datadoghq
    THANK YOU!

    View Slide