Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How can I make typesafe accessors available for new project properties contributed by a Gradle plugin?

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.

>Solution :

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

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading