Home > database >  How can I make typesafe accessors available for new project properties contributed by a Gradle plugi
How can I make typesafe accessors available for new project properties contributed by a Gradle plugi

Time:08-31

If I have a plugin which defines a new project property:

// Common plugin

var copyrightDate: String? by project.extra

And then try to access this property in a build script:

plugins {
    `lifecycle-base`
    id("acme.common")
}

copyrightDate = "2022"

I get the predictable result of:

e: path\to\build.gradle.kts:7:1: Unresolved reference: copyrightDate

A workaround as noted in the answer here is to explicitly declare the property again each time you are about to use it, like:

var copyrightDate: String? by project.extra

Since we have a number of projects using the same plugin, I'd rather have this happen automatically, like how applying the java plugin makes a java {} function available without having to declare it. This mechanism appears to be used by a number of plugins, including those not shipped with Gradle itself. But I'm yet to figure out how they're making it work.

How is this meant to be done?

  • Test project contains a unit test written for Gradle TestKit which tries to run a similar build script.

CodePudding user response:

Register an extension

The official way to do this is to register an extension.

First create an extension object. So long as it only has simple properties, it can be a Gradle managed type.

// src/main/kotlin/my/custom/plugin/MyCustomPluginSettings.kt
package my.custom.plugin

import org.gradle.api.provider.Property

interface MyCustomPluginSettings {
  val copyrightDate: Property<String>
}

(I've used a Property<> rather than a String (the benefits are listed here) but you could also use a plain String if you wanted.)

Next, in your plugin, create the extension, and set a default value for copyrightDate.

// src/main/kotlin/my/custom/plugin/MyCustomPlugin.kt
package my.custom.plugin

import org.gradle.api.*
import org.gradle.kotlin.dsl.*

abstract class MyCustomPlugin : Plugin<Project> {
  override fun apply(target: Project) {
    val myCustomPluginSettings = target.extensions.create<MyCustomPluginSettings>("myCustomPlugin")
    myCustomPluginSettings.copyrightDate.convention("2022")
  }
}

Note that I'm using the Gradle Kotlin DSL. Make sure to apply the kotlin-dsl plugin in your plugin's build.gradle.kts!

You can also apply your MyCustomPluginSettings in a buildSrc plugin in the same way - just use the contents of the apply(...) {} function in the .kts file.

Now when you apply your plugin, Gradle will automatically generate a Kotlin DSL accessor from the name you gave your extension.

// build.gradle.kts

plugins {
  id("my.custom.plugin")
}

println(myCustomPlugin.copyrightDate.get())

Non-extension method

If you define the property in a .kt file, then so long as that file is included with the plugin.

(If you're writing buildSrc plugins, then the .kt file can be anywhere in ./buildSrc/src/main/kotlin/...)

// src/main/kotlin/my/custom/plugin/constants.kt

package my.custom.plugin

import org.gradle.api.Project

var Project.copyrightDate: String?
  get() = extra["copyrightDate"] as String?
  set(value) {
    extra["copyrightDate"] = value
  }

Now in a build.gradle.kts uses can import this.

// build.gradle.kts

import my.custom.plugin.copyrightDate

plugins {
  id("my.custom.plugin")
}

println(copyrightDate)

Avoiding the import

You can avoid the import by either putting constants.kt in the source root, without a package, or in one of the default import packages, like org.gradle.kotlin.dsl.

// src/main/kotlin/my/custom/plugin/constants.kt

package org.gradle.kotlin.dsl

import org.gradle.api.Project

var Project.copyrightDate: String?
  get() = extra["copyrightDate"] as String?
  set(value) {
    extra["copyrightDate"] = value
  }

Because of the risk of clashing, I only recommend this for buildSrc plugins. But this might be handy if you want to include a helper extension for something more complicated, like a helper function for defining dependencies with a default version (like how there's a dependencies { kotlin("reflect") } helper function).

  • Related