Is there a cunning way to add a property to an `Ex...
# community-support
r
Is there a cunning way to add a property to an
ExtensionContainer
? I've got a plugin where I want to decorate an existing plugin with one new setter, so that in a build file I can write:
Copy code
existingPlugin {
  myNewProperty = "some value"
}
More details in thread.
I have two workarounds so far: 1. Add an object which has the property as an extension:
Copy code
class MyExtension(
  private var currentPropertySetting: String,
) {
  var myNewProperty: String
    get() = currentPropertySetting
    set(prefix) {
      // do clever stuff here
      currentPropertySetting = prefix
    }
}
(existingPluginModel as ExtensionAware)
      .extensions
      .create("myExtension", MyExtension::class.java)
which allows this, which seems verbose:
Copy code
existingPlugin {
  myExtension {
    myNewProperty = "some value"
  }
}
2. Add a function as an extension:
Copy code
(existingPluginModel as ExtensionAware)
  .extensions
  .add(
    object : TypeOf<Function1<String, Unit>>() {},
    "setMyNewProperty",
  ) { newValue: String ->
    // do clever stuff here
  }
which allows:
Copy code
existingPlugin {
  setMyNewProperty("some value")
}
This is less verbose, but apart from losing the setter feel to the DSL it leads to compiler warnings over the generated code, because
TypeOf<kotlin.Function1<String, Unit>>() {}
uses reflection, so ends up with
kotlin.jvm.functions.Function1
rather than
kotlin.Function1
as the type specified in the generated code, which kotlinc then warns about because you are meant to use
kotlin.Function1
.
e
does
Copy code
(existingPluginModel as ExtensionAware)
    .extensions
    .add<Property<String>>(
        "myNewProperty",
        objects.property()
    )
work? (https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/add.html there's an
inline
extension with
reified T
which allows you to avoid
new TypeOf()
)
(or really anything with
@SupportsKotlinAssignmentOverloading
but
Property
already exists so why not)
r
Looks promising - how do I act on its value? Can I add a listener somehow to a
Property
?
I guess I can just decorate a real
Property
with my own implementation that acts on
set
Hmm - was looking good, but the build file does not compile:
Copy code
Line 6:   myNewProperty = "some value"
            ^ Val cannot be reassigned
I wonder why it thinks it's a val...
e
it is,
plugin.assignment
is supposed to turn
=
into
set
there
maybe it interacts with extensions in some weird way
r
(On the acting on it - is there a hook where a plugin can act after the entire build script has been evaluated, but without a task being called? That might be a better place to read the property value and do my work than inline on the call to set...)
e
you could make your own type
Copy code
@SupportsKotlinAssignmentOverloading
class MyProperty<T>(var value: T, private val onSet: (T) -> Unit) {
    fun set(value: T) {
        this.value = value
        onSet(value)
    }
r
Yup, that would work - but still getting the compile error. I guess it thinks I'm trying to set the property on the Extension Container rather than call set on the property.
e
is the consumer
.gradle.kts
file or
.kt
?
if it's the latter, you need to
Copy code
import org.gradle.kotlin.dsl.assign
because it's not in the default imports and the IDE won't suggest it
v
Imho your first "work-around" is the proper way to do it. This way it also is clear that it is an extension of your plugin and not a built-in part of that other plugin. Besides that, you can also use it like
Copy code
existingPlugin {
  myExtension.myNewProperty = "some value"
}
if that suits you better.
(Btw. afair "you could make your own type" will not work as shown, the assignment plugin does not call
set
, but
assign
)
e
ah that's right, it is
assign
but if it didn't work with
Property
then that won't work either
v
Probably
is there a hook where a plugin can act after the entire build script has been evaluated, but without a task being called? That might be a better place to read the property value and do my work
None you should use. That is exactly
afterEvaluate { ... }
which is highly discouraged, as the main earnings you get from using it are timing problems, ordering problems, and race conditions. Usually you should have `Property`s and similar types and wire those together, only evaluating at execution time. And if you need to do something at configuration time with the values, I usually recommend to have a function in the extension and do the code in the body of the function, eventually preventing that it is called multiple times if necessary. But also for that, I would use a proper extension object with the function and not the function directly as extension, as you most probably leave users of one DSL behind if you do it like that.
In a normal extension you can even with a function retain the "property-like setting", if you add one nesting layer. If the function in the extension is
fun foo(action: Action<Bar>)
and
Bar
has the `Property`s. Then you can set them like
Copy code
myExtension {
    foo {
        bar1 = "x"
        bar2 = "y"
    }
}
and do the configuration in
foo
after evaluating the
action
.
The
Property
trick btw. works, I just tried it. But it has the same problem as the
Function1
trick. It only works properly with Kotlin DSL. With Groovy DSL you get "There's an extension registered with name 'myNewProperty'. You should not reassign it via a property setter."
So there then the original situation is swapped around and you could use
=
in Kotlin DSL, but need to use
.set()
in Groovy DSL.
r
> The Property trick btw. works, I just tried it. Strange - I haven't actually tried it for real, but using
GradleRunner.create().withPluginClasspath().build()
in a test it doesn't. Perhaps I'm doing something wrong... Code here: https://github.com/Mahoney-gradle-plugins/lidalia-idea-ext/blob/rework/src/main/kotlin/LidaliaIdeaExtPlugin.kt Test code here: https://github.com/Mahoney-gradle-plugins/lidalia-idea-ext/blob/rework/src/functionalTest/kotlin/LidaliaIdeaExtPluginFunctionalTest.kt Test failure visible here: https://github.com/Mahoney-gradle-plugins/lidalia-idea-ext/actions/runs/11213200240/job/31165826776
v
That does not work, because as I said, it is not
set
that is used, but
assign
. Just for the Gradle
Property
types
assign
forwards to
set
. For
ConfigurableFileCollection
it for example forwards to
setFrom
.
But as I said, if you care for Groovy DSL compatibility, you shouldn't use it, as you there then need to call
.assign
manually instead of using
=
.