Gradle plugin builds in Kotlin


(Dimitar Dimitrov) #1

I’ve been kicking the tires of the new Kotlin DSL in one of my plugin projects, and I was quite thrilled to see the gradle-plugin sample

With its help I resolved most of the red underlines in my build, but I am a bit puzzled by some of the syntax (all examples below are using gradle-kotlin-dsl-4.1-20170629045256):

gradlePlugin {
    (plugins) {  // <== what is this?
        "myPlugin" { // EDIT: found that this is String.invoke() foreign function, defined local fin the outer scope class
            id = "my-plugin"
            implementationClass = "plugin.MyPlugin"
        }
    }
}

Perhaps it is my lack of Kotlin understanding, but why would (plugins) resolve to the closure-target method, while plugins without parentheses resolves to the project instance? Is this Kotlin or Gradle feature? Could you point to any relevant docs?

Then I found that if I want to refer to the domain-object I can’t use the property syntax, but have to do something like foobar = pluginBundle.plugins["myPlugin"].id - is that the only way? Is there a plan to support the property syntax?

Finally, why does this work:

buildScan {
    setLicenseAgreementUrl("https://gradle.com/terms-of-service")
    setLicenseAgree("yes")
}

But this doesn’t (fails compilation with Unresolved reference: xxx)?

buildScan {
    licenseAgreementUrl = "https://gradle.com/terms-of-service"
    licenseAgree = "yes"
}

Is it expected to work in the future?

Thanks in advance, and keep up the great work!


(René Groeschke) #2

this doesn’t work as to be able to use property syntax in kotlin, a setter AND a getter must be declared in the class. BuildScanExtension doesn’t provide getters here. That’s why you need to use the setters explicitly. This might change in the future.


(Dimitar Dimitrov) #3

Apologies for digging out an old topic, but I got 2 questions unanswered, and I am still curious and lazy (not a great combination, I know).

So, question #1 - what is this (plugins) { ... } construct, and why without the parens it would invoke project.plugins() and with parens it invokes GradlePluginExtension.plugins()? Is this somehow related to Kotlin’s @DslMarker?

gradlePlugin {
    (plugins) {             // <== what is this?
        "myPlugin" { 
            id = "my-plugin"
            implementationClass = "plugin.MyPlugin"
        }
    }
}

Question #2: if I want to refer to the domain-object I can’t use the property syntax, but have to do something like foobar = pluginBundle.plugins["myPlugin"].id

Is that the right way? Is there a plan to support the property syntax such as foobar = pluginBundle.plugins.myPlugin.id?


(Rodrigo B. de Oliveira) #4

Hi Dimitar,

The funky (plugins) { ... } construct is meant to force Kotlin to interpret plugins as a property access (to getPlugins()) followed by a lambda expression instead of an invocation of plugins(Action<...>).

That’s to take advantage of the special meaning the Kotlin DSL gives to applying a lambda expression to a Gradle collection value. It enables, among other things, the convenient syntax for creating and modifying elements via String prefixed blocks such as the “myPlugin” block in the snippet you posted. See the domain-objects sample in the kotlin-dsl repository for another example.

About your 2nd question, there’s no plan to support property syntax access to domain object collection elements but this can change once Kotlin evolves to support dynamic dispatching.

Kind regards,
Rodrigo


(Dimitar Dimitrov) #5

Thanks Rodrigo,

One final clarification about:

Could you point me to some documentation or elaborate a bit as to why plugins resolves to Project.getPlugins(), but (plugins) resolves to GradlePluginDevelopmentExtension.getPlugins()? What is the reason that putting an identifier inside parentheses changes its semantics?

As far as I understand, if we use the @DslMarker we should be able to control the default scope to be the extension.


(Rodrigo B. de Oliveira) #6

The (plugins) { } syntax is used to make plugins resolve to GradlePluginDevelopmentExtension.getPlugins() followed by a lambda expression instead of an invocation of GradlePluginDevelopmentExtension.plugins(Action) as it would be the case for the expression without the parentheses plugins { } since Action is a SAM interface.

In the end (plugins) { } gets interpreted as getPlugins().invoke({ }) where invoke is an extension method that executes the given configuration block in an augmented context.


(Dimitar Dimitrov) #7

Thank you, so we need to do this because we need to invoke the value of a property, which in Kotlin looks the same as calling a method with the same name. This explains what is actually happening and why the parens change the meaning.

Is there some intent in designing the DSL with this namespace overload, or is it a historical artifact?

BTW, calling GradlePluginDevelopmentExtension.plugins(Action) seems to be the same as invoking the property (please correct me if I’m wrong). At least according to IDEA what actually gets called without parens is KotlinBuildScript.plugins() which is the top-level plugins {...} block. This should be avoidable with @DslMarker


(Rodrigo B. de Oliveira) #8

They are not the same and that’s why we need the different syntax. As I said, (plugins) { } gets interpreted as getPlugins().invoke({ }) and invoke is an extension method (https://gradle.github.io/kotlin-dsl-docs/api/org.gradle.kotlin.dsl/invoke.html) that executes the given configuration block in an augmented context (https://gradle.github.io/kotlin-dsl-docs/api/org.gradle.kotlin.dsl/-named-domain-object-container-scope/index.html).


(Rodrigo B. de Oliveira) #9

It is a historical artifact.