gradle-best-practices
gradle-best-practices copied to clipboard
Explain "Don't use Kotlin lambdas in your public API"
I've been reading your guide, pretty cool summary. I got hung up on this suggestion, could you please expand on it a bit more or link to some resources?
Don't use Kotlin lambdas in your public API
Why is that? What will break?
Gradle enhances the bytecode at runtime to provide a nicer DSL experience for users of your plugin.
That's great, but what if my users are using includeBuild(gradle/plugins), at that point my plugin will be a normal dependency and Gradle doesn't provide a nice DSL magic transformation. Any suggestion to support both?
Don't use Kotlin lambdas in your public API
I think this should go with
Don't use Groovy closures in your public API
The rationale is that one is pretty much unusable from the other language - also both from Java.
When you use Action<T>, it stays idiomatic in plugins implemented in plain Java, Groovy or Kotlin.
In the Groovy DSL (build scripts and precompiled scripts), there's some bytecode magic to also add Closure based overloads mostly for backwards compatibility.
In the Kotlin DSL (build scripts, precompiled scripts and .kt files in source sets with the kotlin-dsl applied), Action<T> is declared as a "lambda with receiver" equivalent to T.() -> Unit in order to remove the need to use it..
@TWiStErRob, if your users are using includeBuild(gradle/plugins) they should get what Gradle provides according to the following, otherwise it's a bug
@eskatos Ah, thanks, that's some proper magic there 🙇🏻♂️!!!
Sorry, I confused two of my projects (or in some places I didn't apply kotlin-dsl). I double-checked now and I see it actually working in practice:
- In one project I have
includeBuild(gradle/plugins)and ingradle/plugins/build.gradle.ktshaveimplementation(...)dependencies on Detekt Gradle Plugin, and then it defines plugins that configure Detekt reports. Thisfun reportsonly definesAction<T>overload, yet the usage compiles as if it wasT.()overload. This works fine as you described. - In another project (also defining plugins) I have a standard
java-gradle-plugin+kotlin(jvm)subproject that pulls in the same dependency and if I put the same code in there, I have to useit:
otherwise it doesn't compile. This is what I was referring to. So the answer here is that I need to applyproject.tasks.withType<Detekt>().configureEach { it.reports { it.html.required.set(true) // human it.txt.required.set(true) // console } }kotlin-dslto these modules to get the magic of@HasImplicitReceiver interface Action<T>.
It was always confusing not being able to use them the same way, thanks for clarifying! (Now I understand some IntellIJ errors I saw related to this when sync was broken! TIL)