How to configure AppEngine Gradle plugin using Kotlin DSL

In order to have kotlin-dsl generate static accessors before compile time for applied plugins you must use the plugins {} block and not the buildscript {} block. buildscript {} will still make the dependencies visible to the script classpath, but you will not get those.

As you noticed, the plugin's Maven coordinates that may be different than the plugin Id. You can handle this in the settings.gradle with the pluginManagement specification (an example for Android plugin is here. Here is how I handle that (and using war plugin for minimal application):

build.gradle,kts

plugins {
  id("com.google.cloud.tools.appengine") version "1.3.4"
  `war`
}

settings.gradle

pluginManagement {
  repositories {
    gradlePluginPortal()
    google()
  }
  resolutionStrategy {
    eachPlugin {
      if (requested.id.id == "com.google.cloud.tools.appengine") {
        useModule("com.google.cloud.tools:appengine-gradle-plugin:${requested.version}")
      }
    }
  }
}

Now, I have the plugin applied and kotlin-dsl will generate the accessors before script compilation.

Running ./gradlew kotlinDslAccessorsReport and perusing through it I see this in the output:

/**
 * Retrieves the [appengine][com.google.cloud.tools.gradle.appengine.core.AppEngineExtension] project extension.
 */
val Project.`appengine`: com.google.cloud.tools.gradle.appengine.core.AppEngineExtension get() =
  extensions.getByName("appengine") as com.google.cloud.tools.gradle.appengine.core.AppEngineExtension

/**
 * Configures the [appengine][com.google.cloud.tools.gradle.appengine.core.AppEngineExtension] project extension.
 */
fun Project.`appengine`(configure: com.google.cloud.tools.gradle.appengine.core.AppEngineExtension.() -> Unit): Unit =
    extensions.configure("appengine", configure)

Now, you can see that the appengine { ... } code block will work correctly in the top level. We just need to figure out what can go inside of it based on its type. Note that if we were using buildscript {} instead of plugins {}, you would have to either copy/paste these accessors yourself or do something like extensions.getByType(com.google.cloud.tools.gradle.appengine.core.AppEngineExtension::class) in your build script.

Doing some searching you can find the source code for AppEngineExtension on GitHub. Unfortunately, it does not have any methods or fields on it. It is basically used as an "extension holder" class, in that other extensions are added to it here and here (and probably other places). This means that we need to do some class cast tricks to be able to configure this object. The source code is IMO the only way to really figure out how to access these kinds of objects.

Below shows how we can configure the deploy extension, which is a DeployExtension and how we can configure the run extension, which is a RunExtension.

import com.google.cloud.tools.gradle.appengine.core.DeployExtension
import com.google.cloud.tools.gradle.appengine.standard.RunExtension

appengine {
  ((this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("run") as RunExtension).apply {
    port = 8080
  }
  ((this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("deploy") as DeployExtension).apply {
    stopPreviousVersion = true // default - stop the current version
    promote = true
  }
}

There are a few different ways to accomplish the above, but that is the approach I took. The plugin itself should offer friendlier methods for configuration until kotlin-dsl/457 is resolved, so I opened an issue


Type-safe approach using version 2.0 of the appengine gradle plugin:

import com.google.cloud.tools.gradle.appengine.standard.AppEngineStandardExtension

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath("com.google.cloud.tools:appengine-gradle-plugin:2.0.0-rc5")
    }
}

plugins {
    java
    war
    kotlin("jvm") version "..."
}

repositories {
    jcenter()
}
apply {
    plugin("com.google.cloud.tools.appengine")
}

configure<AppEngineStandardExtension> {
    deploy {
        projectId = "..."
        version = "..."
        stopPreviousVersion = true // etc
    }
}