Suppose I have an extension interface like this:
interface MyExtension {
abstract val container: ExtensiblePolymorphicDomainObjectContainer<MyBaseClass>
}
When I try to create an instance of this extension via this:
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create<MyExtension>("myExtension")
}
}
It seems that Gradle (at least as of version 7.6.1) will have trouble applying a MyPlugin
due to being unable to create the extension, in turn due to being able to implement the container
property properly.
Is it a limitation of Gradle or the Kotlin DSL requiring a workaround (eg. making MyExtension
an abstract class and injecting an ObjectFactory
specifically), or am I doing something wrong?
Gradle can only create specific types of properties on managed types
These are
Property<T>
RegularFileProperty
DirectoryProperty
ListProperty<T>
SetProperty<T>
MapProperty<K, V>
ConfigurableFileCollection
ConfigurableFileTree
DomainObjectSet<T>
NamedDomainObjectContainer<T>
In Gradle v8.2, ExtensiblePolymorphicDomainObjectContainer<T>
will be supported. Until then they must be created manually using ObjectFactory
.
One way of doing this is by injecting the ObjectFactory into the constructor:
import javax.inject.Inject
import org.gradle.api.*
imports org.gradle.kotlin.dsl.*
abstract class MyExtension @Inject constructor(
private val objects: ObjectFactory,
) {
val container: ExtensiblePolymorphicDomainObjectContainer<MyBaseClass> =
objects.polymorphicDomainObjectContainer()
}
NamedDomainObjectContainer
sNote that even though Gradle will automatically create NamedDomainObjectContainer
properties it's usually a good idea to create one manually and add it as an extension.
abstract class MyExtension @Inject constructor(
private val objects: ObjectFactory,
) : ExtensionAware {
// *all* types that Gradle instantiates are automatically ExtensionAware,
// but adding the interface explicitly helps with IDE hints
val namedContainer: NamedDomainObjectContainer<MyBaseClass> =
objects.domainObjectContainer(MyBaseClass::class).also {
extensions.add("namedContainer", it)
}
}
This is verbose and weird - so why bother?
By adding namedContainer
as an extension, Gradle will automatically generate some nice type-safe Kotlin DSL accessors for any element that is added.
So, if in your plugin you add an element.
abstract class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create<MyExtension>("myExtension")
extension.namedContainer.register("anElement") { ... }
}
}
In any build script, there will be an easy way to access the element in a type-safe and IDE friendly way
// build.gradle.kts
plugins {
id("my.plugin")
}
myExtension {
namedContainer.anElement { // auto generated accessor
}
}