Reading stdin and The Kotlin DSL for Gradle - The Code Whisperer

I couldn’t figure this out myself, nor even find the appropriate Stack Overflow article on my own, so I’m writing this. I hope you find it and it alleviates your annoyed state.


This is a companion discussion topic for the original entry at https://blog.thecodewhisperer.com/permalink/stdin-gradle-kotlin-dsl

Funny how the Gradle Kotlin DSL makes some things more consistent, but at the same time others less so.

Another alternative that I use and that is equivalent (as far as I know) is this:

tasks.run<JavaExec> {
    standardInput = System.`in`
}

This works because run comes pre-defined as an extension property on TaskContainer in the kotlin-dsl. If you jump to to the implementation you will find this:

val TaskContainer.`run`: TaskProvider<org.gradle.api.tasks.JavaExec>
    get() = named<org.gradle.api.tasks.JavaExec>("run")

We need the explicit <JavaExec> to get the invoke extension from Gradle to take precedence over the kotlin run scope-function. However since invoke operators are just syntactic sugar for just typing out invoke as a method call, we can also do this:

tasks.run.invoke {
    standardInput = System.`in`
}

And now it becomes interesting again, because how is invoke implemented? Let’s have a look:

operator fun <T> NamedDomainObjectProvider<T>.invoke(action: T.() -> Unit) =
    configure(action)

So the invoke method simply forwards to configure, which means we can also do this:

tasks.run.configure {
    standardInput = System.`in`
}

I am sticking with the first option I mentioned, but having many ways to do the same thing sure is confusing.

1 Like

Thank you! The last version seems to express the intent most directly, so I prefer it. What makes you prefer the first version?

It’s a close call for sure.

a.) this is closer to how I configure other parts, for example tests

tasks.test {
    useJUnitPlatform()
}

b.) I sometimes find myself helping the IDE/Kotlin compiler to figure out the right generic type. This happens for example when using some AssertJ matchers in Kotlin code. It’s logical for me, that this helps to clarify the type of this within the block.

But again, just personal preference.

Btw. I think tasks.getByName should be avoided in larger Gradle configurations, as I think it doesn’t allow configuration avoidance.

Thanks for the link. Evidently it suffices to replace getByName() with named(), so I’ll do that:

tasks.named("run", JavaExec::class) {
    standardInput = System.`in`
}

If there’s a more-idiomatic way to write that, I’ll do that. :slight_smile: