Closed
Description
Status Quo
Extension
public interface Extension {
Property<String> getDescription();
}
public interface OtherExtension {
Property<String> getDescription();
}
Groovy DSL
extension {
description = "My $project.description"
description = otherExtension.description
description = provider {
// compute stuff lazily
}
}
Kotlin DSL (build script or precompiled script)
extension {
description.set("My $project.description")
description.set(otherExtension.description)
description.set(provider {
// compute stuff lazily
})
}
Potential solutions
Option 1: Kotlin supports equals =
operator overloading
Implementation
operator fun <T> Property<T>.EQUALS(value: T): Unit = set(value)
operator fun <T> Property<T>.EQUALS(provider: Provider<T>): Unit = set(provider)
Kotlin DSL (build script or precompiled script)
extension {
description = "My $project.description"
description = otherExtension.description
description = provider {
// compute stuff lazily
}
}
Groovy DSL
Unchanged
Pros/Cons
- (+) Simple, just add two kotlin extensions
- (+) Groovy and Kotlin DSL look the same
- (-) Kotlin support not likely to happen
Option 2: Rename Property.set
/get
to setValue
/getValue
Just the for the plain value, do not rename set(Provider<T>)
.
Kotlin DSL (build script or precompiled script)
extension {
description.set("My $project.description") // DEPRECATED
description.value = "My $project.description"
description.set(otherExtension.description)
description.set(provider {
// compute stuff lazily
})
}
Groovy DSL
extension {
description = "My $project.description" // DEPRECATED
description.value = "My $project.description"
description = otherExtension.description
}
Pros/Cons
- (+) Simple, just add two methods, deprecate two old ones
- (+) Explicit difference between using the plain value or connecting to other properties
- (-/+) Will not look the same in Groovy and Kotlin DSL unless we remove sugar from Groovy DSL
- (-) Looks nice for eager assignment only
Alternative
Also rename set(Provider<T>)
to setValue(Provider<T>)
.
extension {
description.set("My $project.description") // DEPRECATED
description.value = "My $project.description"
description.setValue(otherExtension.description)
description.setValue(provider {
// compute stuff lazily
})
}
This makes it worse at first until Kotlin supports overloaded property setters gradle/kotlin-dsl-samples#380
BUT it becomes semantically wrong, value = provider
doesn't make much sense.
Option 3: Kotlin invoke operator overloading
Implementation
operator fun <T> Property<T>.invoke(value: T?): Unit = set(value)
operator fun <T> Property<T>.invoke(provider: Provider<T>): Unit = set(provider)
Kotlin DSL (build script or precompiled script)
extension {
description("foo")
description(otherExtension.description)
description(provider {
// compute stuff lazily
})
}
Pros/Cons
- (+) Simple, just add two kotlin extensions
- (-) Still not assignments
Activity
[-]Concise and statically-typed `Property` assignment[/-][+]Concise and statically-typed `Property<T>` assignment[/+]JLLeitschuh commentedon Sep 7, 2018
Option 3 might also collide with plugin author's extensions where they've defined
This might be an non-issue though.
jnizet commentedon Sep 7, 2018
And simply not exposing properties or type Property directly, but exposing plain old String/whatever properties delegating to private Property fields isn't an option, even for incubating classes?
eskatos commentedon Sep 7, 2018
@JLLeitschuh kotlin prefers original members over extensions
@jnizet types need to expose
Property<T>
JavaBean properties otherwise it means giving up on lazy configurationmkobit commentedon Sep 7, 2018
I wonder if setter overloading (KT-4075) or setter-only properties (KT-6519) would allow something like this to work? Seems like there might be a few missing Kotlin feautre requests (like how do you have setters for a
val
?), but maybe something that looks like this?Maybe accessor generation could generate these setters?
Just brainstorming if there are other options.
JLLeitschuh commentedon Sep 7, 2018
@mkobit That's a lot of overhead to put on plugin authors for ever field on a task they write.
StefMa commentedon Sep 9, 2018
To be honest I don't get the current "issue" here.
I mean - when a Kotlin developer take a look to the current javadoc they see that they have to call
set
and don't "assign".The only thing why something should changed here is that the Groovy DSL and the Kotlin DSL should look similar, right?
But none of your options can solve that.
From my point view nothing should be changed here. Kotlin is not Groovy (for good reasons). So the API is slightly different. No need to adjust Gradle/Kotlin here to be similar to Groovy.
Just my 2 cents 🙃
passsy commentedon Nov 2, 2018
I'm one of the developers running into this trap when I used the new
MavenPom
DSL usingProperty<T>
. I first implemented it in Groovy and then tried to use it in Kotlin in a different project and failed hard. It took me hours to find the.set(T)
. Using it is a dissatisfying experience.I don't see a problem here for Plugin authors. When I develop a DSL I use
var name: String? = null
and it can be used the same way from kotlin and groovy.The problem is that DSLs in gradle core uses the
Property
API which makes DSL inconsistent. Sometimes=
can be used (i.e. inandroid
DSL), sometimes.set(T)
is required.Except for
Option 1: Kotlin supports equals = operator overloading
which would restore a consistent DSL experienceOption 2
andOption 3
also break consistency.The
Property<T>
API was introduced because properties can now be lazily evaluated. That's a cool feature but often not required. I'm really unhappy that this changes how DSLs are used. As a plugin author, I'd much rather provide an alternative API for lazy properties.JLLeitschuh commentedon Nov 2, 2018
Actually, it is required to fix a lot of issues with task evaluation ordering that has plagued Gradle for a long time. The goal is to provide a mechanism where plugin authors do not have to utilize the
Project::afterEvaluate
method.eskatos commentedon Nov 6, 2018
The position of the @gradle/kotlin-dsl team is to advocate for Option 2 with minor changes.
We think that it's important to distinguish
Property<T>
plain value usage and provider wiring because the latter implies later side effects.For that reason and increased readability we suggest the following:
value: T?
property i.e.@Nullable T getValue()
/void setValue(@Nullable T)
@Nullable T getOrNull()
in favor of@Nullable T getValue()
void set(@Nullable T)
in favor ofvoid setValue(@Nullable T)
from(Provider<T>)
for wiringvoid set(Provider<T>)
in favor ofvoid from(Provider<T>)
Kotlin DSL
Groovy DSL
For trying it out
Put the following extensions in scope:
Pros/Cons
lacasseio commentedon Nov 6, 2018
I'm not sure I agree with removing the difference between
getOrNull()
andget()
as it adds some intention behind the querying of the property/provider.Could option 4 be a viable solution as well. I understand it doesn't feel property-ish in Java and Kotlin. I fear that option 2 will create 3 ways to understand the Provider API:
I think I would prefer to have the Groovy syntax sugar print a deprecation warning informing users how to properly use the
Property
object and pointing them to useset
until Gradle 6.0 or further (if we don't convert the entire code base to use provider API by then). This way, we would have a single way of setting values forProperty
in all language:What is your feeling on just removing (informing users) the Groovy syntax sugar and keeping everything as-is?
bamboo commentedon Nov 6, 2018
That's not what's in the proposal. We propose to deprecate
getOrNull()
in favor ofgetValue()
with the same signature and semantics.get()
remains unaffected by this proposal.bamboo commentedon Nov 6, 2018
As stated, we think that it's important to distinguish Property plain value usage and provider wiring because the latter implies later side effects.
68 remaining items