1

I am new to Gradle. I frequently see these distinct syntaxes for setting a value:

configBlock {
    foo("foo")
    bar = "bar"
    baz.set("baz")
}

I wonder whether there is an underlying convention when to use which syntax. It seems that foo() is used when there are varargs, in order to get a slightly shorter syntax and potentially prevent removal of existing list elements:

// Instead of this...
foo = listOf("foo 1", "foo 2")
// use this:
foo("foo 1", "foo 2") // Possibly only adding values.

Though I have also seen qux.set(listOf("qux 1", "qux 2")).

For bar = "bar" vs. baz.set("baz") however, I am clueless.

Note that of course I am aware that for an existing DSL, I cannot choose which one to use, so the question mainly applies for authoring a plugin.

Marco Eckstein
  • 4,448
  • 4
  • 37
  • 48

1 Answers1

1

To summarise your question: There are two ways of capturing user input:

  • A normal property var foo: String
  • Using a property, var foo: Property<String>

What's the difference? And why are there different ways of setting these values?

tl;dr:

Avoiding work

Gradle uses tasks to perform work, and Gradle likes avoiding work.

To avoid work, there are two aspects to consider:

  1. Gradle must be able to check if inputs or outputs have changed to perform up-to-date checks.

  2. To avoid unnecessary work, inputs and outputs should only be evaluated on demand, using Lazy Configuration.

Up-to-date checks

Gradle checks if tasks are up-to-date by storing checksums of all inputs and outputs task properties. All properties must therefore be serialisable.

This is usually not relevant, because Gradle can serialise most properties. All primitives are serialisable (in your example, foo, bar, and baz are all strings), and if not, it can implement Java Serializable.

The Provider API

Note that up-to-date checks are only used for tasks. There are no up-to-date checks on Gradle extensions. So why shouldn't an extension use a normal property?

interface MyCoolPluginExtension {
  var version: String // what's wrong with a simple variable property?
}

A simple property might work for a lot of cases, but there are some edge cases:

  • What if computing the value of version is, in some way, expensive or slow? For example, if it's contained within a file that's downloaded from the internet.
  • What if version is dependent on another property that isn't known yet?

While computing the actual value of version could be performed immediately, this could introduce unnecessary work if the value of version isn't needed for the tasks that Gradle need to run. It could also introduce expensive work in the configuration phase - something to be avoided!

Using a Property solves these problems:

interface MyCoolPluginExtension {
  val version: Property<String> // use the Provider API for better work-avoidance
}

It does this in two ways

  1. The value will be lazily evaluated. Ideally, this evaluation only happens during the execution of a task - so if the task that needs the property never runs, the work is avoided.
  2. Properties can be chained together. This means that a property can lazily react to another property - again, avoiding work unless its necessary.

Best of both worlds?

Using the Gradle Provider API can sometimes be awkward to use. But we can create helper functions to make it more convenient!

When it comes to up-to-date checks, Gradle will only consider task properties that are annotated as inputs or outputs, and will ignore those annotated with @Internal.

This means you could expose some helper functions to users - so long as the actual task property is correctly annotated as an input or output.

For example, you could mark the actual Property<String> as internal, and only expose a var to plugin users:

import org.gradle.api.provider.Property

abstract class MyCoolPluginExtension {
  internal abstract val versionProp: Property<String>

  var version: String?
    get() = versionProp.orNull
    set(value) {
      versionProp.set(value)
    }
}

(This is a pretty silly example - in real life just use the Property setter, version.set("1.2.3")!)

// build.gradle.kts
plugins {
  id("my.cool.plugin")
}

myCoolPlugin {
  version = "1.2.3"
}
aSemy
  • 5,485
  • 2
  • 25
  • 51