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

Can Kotlin save me from my Groovy buildscripts? Droidcon Berlin 2018

Can Kotlin save me from my Groovy buildscripts? Droidcon Berlin 2018

This talk covers
* Migration examples from Groovy to Kotlin in buildscripts.
* Some advantages and disadvantages of Groovy vs Kotlin.
* How the Kotlin-DSL works under the hood.

This was presented on June 27th at Droidcon Berlin 2018.

Video: https://www.youtube.com/watch?v=Rwrja9WCTS0

Nelson Osacky

June 27, 2018
Tweet

More Decks by Nelson Osacky

Other Decks in Technology

Transcript

  1. Groovy to Kotlin in
    build scripts
    Nelson Osacky

    View Slide

  2. View Slide

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

    View Slide

  4. Gradle Kotlin DSL
    v0.17.5

    View Slide

  5. Type Safety

    View Slide

  6. View Slide

  7. if (isCi) {
    foo
    }A

    View Slide

  8. if (isCi) {
    foo
    }A

    View Slide

  9. Auto Complete

    View Slide

  10. • autocompletion gif

    View Slide

  11. Slower builds

    View Slide

  12. What is buildSrc?

    View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. View Slide

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

    View Slide

  19. View Slide

  20. Must dig in source

    View Slide

  21. Red lines in IDE

    View Slide

  22. View Slide

  23. Missing features

    View Slide

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

    View Slide

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

    View Slide

  26. Example
    Migrations

    View Slide

  27. build.gradle -> build.gradle.kts

    View Slide

  28. settings.gradle.kts

    View Slide

  29. include ':app'
    include ':data'
    include ':service'

    View Slide

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

    View Slide

  31. Kotlin Extension
    Functions and
    Properties

    View Slide

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

    View Slide

  33. // 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
    }

    View Slide

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

    View Slide

  35. build.gradle.kts

    View Slide

  36. plugins

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  44. /**
    * 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

    View Slide

  45. @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);
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  49. /**
    * 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

    View Slide

  50. """
    /**
    * 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  54. /**
    * 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  62. ext block

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  66. dependencies {
    testImplementation deps.junit
    }W

    View Slide

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

    View Slide

  68. dependencies {
    testImplementation deps.junit
    }Q

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

    View Slide

  69. dependencies {
    testImplementation deps.junit
    }Q

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

    View Slide

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

    View Slide

  71. 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}",
    ],
    ]

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  76. targetCompatibility = JavaVersion.VERSION_1_8
    sourceCompatibility = JavaVersion.VERSION_1_8

    View Slide

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

    View Slide

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

    View Slide

  79. View Slide

  80. ext properties

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  90. Java Compatibility

    View Slide

  91. targetCompatibility = JavaVersion.VERSION_1_8
    sourceCompatibility = JavaVersion.VERSION_1_8

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  95. /**
    * 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

    View Slide

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

    View Slide

  97. /**
    * 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

    View Slide

  98. View Slide

  99. accessors.kt

    View Slide

  100. java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    }

    View Slide

  101. /**
    * 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  105. View Slide

  106. Named Parameters

    View Slide

  107. // 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

    View Slide

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

    View Slide

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

    View Slide

  110. // 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

    View Slide

  111. // 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

    View Slide

  112. // 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

    View Slide

  113. View Slide

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

    View Slide

  115. Would I
    recommend it?

    View Slide

  116. Migrate one file
    and try it!

    View Slide

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

    View Slide

  118. Gradle Tasks

    View Slide

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

    View Slide

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

    View Slide

  121. /**
    * 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)

    View Slide

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

    View Slide

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

    View Slide

  124. /**
    * 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 {

    View Slide

  125. /**
    * 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)

    View Slide

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

    View Slide