10

I am trying to execute my Kotlin class using the command:

./gradlew -q run < src/main/kotlin/samples/input.txt

Here is my HelloWorld.kt class:

package samples

fun main(args: Array<String>) {

    println("Hello, world!")

    val lineRead = readLine()
    println(lineRead)
}

Here is my build.gradle.kts:

plugins {
    kotlin("jvm")
    application
}

application {
    mainClassName = "samples.HelloWorldKt"
}

dependencies {
    compile(kotlin("stdlib"))
}

repositories {
    jcenter()
}

The code executes, but the data contained inside the input.txt file is not displayed. Here is the output I get:

Hello, world!
null

I want to be able to execute the gradlew command above and the input.txt stream be redirected to stdio. I can easily do that in C++. Once I compile my .cpp file, I can run:

./my_code < input.txt

and it executes as expected.

How can I achieve the same thing with Kotlin and Gradle?

Update: Based on this answer, I've tried adding this to build.gradle.kts but it is not a valid syntax:

enter image description here

eskatos
  • 4,174
  • 2
  • 40
  • 42
  • @CodeConfident Yes, I am sure. My problem is similar to this question: https://discuss.gradle.org/t/why-doesnt-system-in-read-block-when-im-using-gradle/3308/2 – Bitcoin Cash - ADA enthusiast Aug 18 '17 at 03:03
  • Sorry... I deleted my comment already. When I re-read it it just didn't make sense any more. – charles-allen Aug 18 '17 at 03:07
  • That link looks like a reasonable answer. Have you tried adding `run { standardInput = System.in }` to your build.gradle.kts? Same suggestion is here: https://discuss.gradle.org/t/how-can-i-execute-a-java-application-that-asks-for-user-input/3264 – charles-allen Aug 18 '17 at 03:11
  • why `build.gradle.kts` and not just `build.gradle`? – Thufir Nov 05 '17 at 01:21

3 Answers3

21

AjahnCharles suggestion about run { standardInput = System.in } is correct, but to port it to kotlin-dsl you need a different syntax. run in this case is the task name and you configure existing task of application plugin. To configure existing task in kotlin-dsl you should use one of this ways:

val run by tasks.getting(JavaExec::class) {
    standardInput = System.`in`
}

or

val run: JavaExec by tasks
run.standardInput = System.`in`

The upcoming version of Gradle 4.3 should provide API for plugin writers to read user input.

The reason of difference between of Groovy and Kotlin in this case because Groovy uses dynamic types, but in Kotlin you must specify task type to have autocompletion and just to compile config script

gildor
  • 1,789
  • 14
  • 19
1

I finally settled on this (Gradle 7.1.1):

plugins {
    application
}

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

I don't know enough Kotlin yet to judge whether this is equivalent to https://stackoverflow.com/a/46662535/253921.

J. B. Rainsberger
  • 1,193
  • 9
  • 23
0

Almost, but this doesn't work :'(

In Theory

My understanding: < input.txt sets the standard input for the gradlew process, but by default this is not forwarded to your program.

You want to add this to your build.gradle.kts:

run {
    standardInput = System.`in`
}

Sources:
https://discuss.gradle.org/t/why-doesnt-system-in-read-block-when-im-using-gradle/3308/2
https://discuss.gradle.org/t/how-can-i-execute-a-java-application-that-asks-for-user-input/3264


In Practice

These build configs look about the same to me, yet Groovy works and Kotlin doesn't. I'm starting to think that the Gradle Kotlin DSL doesn't support the standardInput term yet :/

Gradle in Kotlin vs Gradle in Groovy

Here's my working Groovy version if that's any help:

apply plugin: 'kotlin'
apply plugin: 'application'

buildscript {
    ext.kotlin_version = '1.1.4'

    repositories {
        jcenter()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

repositories {
    jcenter()
}

dependencies {
    // api => exported to consumers (found on their compile classpath)
    // implementation => used internally (not exposed to consumers)
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

mainClassName = "samples.HelloWorldKt"

run {
    standardInput = System.in
}
charles-allen
  • 3,891
  • 2
  • 23
  • 35
  • 1
    I have tried that already. But the code does not compile. I've attached a screenshot to the question. – Bitcoin Cash - ADA enthusiast Aug 18 '17 at 03:16
  • I've (finally) recreated the problem. I made both a Kotlin build.gradle.kts and a standard build.gradle. The latter works provided the `run { ... }` is set. This makes me think the problem lies in importing the appropriate Java package into the Kotlin build script (I guess Groovy does this automatically). Nearly there... ^^ – charles-allen Aug 18 '17 at 05:27
  • @Tiago - I'm afraid despite having it working with Intellij, kotlin command line, and gradle-groovy, I ***cannot*** work out how to make it work for gradle-kotlin, though based on the screenshot above you can probably tell I'm having more trouble than you! I hope you find an answer; this seems quite a useful thing to be able to do. – charles-allen Aug 18 '17 at 08:10
  • Yeah, I've spent hours on this as well. But it is good to know the Groovy version works. I might stick to that solution then. Thank you a lot for helping me look into this! – Bitcoin Cash - ADA enthusiast Aug 18 '17 at 10:19
  • @Tiago - Honestly it was really good practice for me. Good to have a challenge to drive your learning ^^. I'd leave the question open to see if you get a better answer later. I'll be following it for sure! – charles-allen Aug 18 '17 at 10:30
  • 1
    `run {}` comes from the kotlin-stdlib and doesn't reference the `run` task. `tasks.getByName() { standardInput = System.in }` should do – eskatos Sep 24 '18 at 07:26
  • 2
    @eskatos's suggestion works for me with one minor adjustment: ```tasks.withType() { standardInput = System.`in` }``` – Bruce Hamilton Jul 16 '21 at 20:54
  • @BruceHamilton Can we not be more surgical than setting `standardInput` for _every_ `JavaExec` task in the build? This works, and I'm grateful, but I'd like to do even better. – J. B. Rainsberger Aug 14 '21 at 23:57
  • Aha! Even better! ``tasks.getByName("run", JavaExec::class) { standardInput = System.`in` }``. This way, we have fewer side-effects. – J. B. Rainsberger Aug 15 '21 at 00:19