Kotlin and Gradle - Reading from stdio - gradle

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:

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

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.

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 :/
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
}

Related

How to lazily get main sourceSets in Gradle

I need to configure sources jar for all of my subprojects. I have the following subprojects configuration defined:
subprojects {
apply(plugin = "java-library")
val sourcesJar = tasks.registering(Jar::class) {
archiveClassifier.set("sources")
from(the<SourceSetContainer>().named("main").get().allJava)
}
tasks.named("assemble") {
dependsOn(sourcesJar)
}
}
When I try to run ./gradlew tasks I receive an exception in my subproject saying that:
Extension of type 'SourceSetContainer' does not exist. Currently registered extension types: [ExtraPropertiesExtension]
My assumption is that the access to get() method of the extension causes problems but without that I cannot refer to the allJava sources that I need. So how to achieve desired result by using configuration avoidance API?
Running on Gradle 5.2.1 with Kotlin DSL.
I found working solution. The problem is that when you call the<T>() function it is taken from the Task type which is why it complains about absent extension. The solution is to call the<T>() function on project instance like this:
subprojects {
apply {
plugin<JavaLibraryPlugin>()
}
val sourcesJar by tasks.registering(Jar::class) {
archiveClassifier.set("sources")
from(
// take extension from project instance
project.the<SourceSetContainer>().named("main").get().allJava
)
}
}

How do I create distributions with different dependencies using Gradle application/distribution plugin?

I am building a small Kotlin project in IntelliJ, Idea, and trying to figure out how to create multiple tar/zip files with customizations for each OS I want to support.
It seems like the distribution plugin (which is included when you use the application plugin) is the right direction, but I can't seem to figure out how to get it to do what I want.
I have read the documentation on the plugin, which can be found here, but it's not really clear to me how to accomplish what I want to do.
Here is an example build.gradle that shows at least the idea of what I want to do, that is to have a base application setup and then have some minor tailoring for each of the 3 OSs.
For example each of the 3 OSs need a unique version of the SWT library. The macos version needs a specific JVM setting, and for the linux version, I need to tailor the startup script to add some environment variables.
Is this possible with the distribution plugin? If not can someone suggest a different solution?
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.61'
}
apply plugin: 'application'
mainClassName = "MainKt"
version '1.0-SNAPSHOT'
repositories {
// my common required repos
}
dependencies {
// my common dependencies
}
distributions {
macos {
contents { from 'src' }
applicationDefaultJvmArgs.push("-XstartOnFirstThread")
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:4.5.2"
}
}
linux {
contents { from 'src' }
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:4.5.2"
}
startScripts.doLast {
def lines = unixScript.text.readLines()
println lines.add(1, '# add some stuff')
println lines.add(2, '# add some more stuff')
unixScript.text = lines.join("\n")
}
}
windows {
contents { from 'src' }
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:4.5.2"
}
}
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Update
This is what I am doing now, but I would like to improve on this.
I have a variable
def deploy = false
if (!deploy) {
applicationDefaultJvmArgs.push("-XstartOnFirstThread")
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:4.5.2"
}
} else {
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:4.5.2"
}
startScripts.doLast {
def lines = unixScript.text.readLines()
println lines.add(1, 'export foo=foo')
println lines.add(2, 'export bar=bar')
}
}
Right now I develop on my mac and set deploy to false. When I want to generate the distribution for linux I set deploy to true. I could add more code and do the same thing for windows, but I would like to just generate all the code in one task, and have it in different tar/zip files.
Opal provided some very good suggestions in the question comments that I tried, but in the end, I was not able to really get what I wanted.
Here is my current workaround solution it does pretty much what I was hoping I could have done with the distribution plugin.
Basically I create an osType property and set it's "default" to the os I am developing on, in this case macos. Then I have all my common dependencies in one dependency closure and add in the the os specific stuff in a groovy switch statement. I then just coded up a short shell script that overrides the osType property to each of the os's I want to support and calls the gradle task to build each in turn.
Here is my build.gradle file.
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.61'
id 'application'
}
ext {
swtVersion = "4.5.2"
}
ext.osType = project.properties['osType'] ?: 'macos'
println "building for ${osType}"
mainClassName = "MainKt"
repositories {
mavenCentral()
maven { url "http://maven-eclipse.github.io/maven" }
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}
switch(osType) {
case 'macos':
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:${swtVersion}"
}
applicationDefaultJvmArgs.push("-XstartOnFirstThread")
break
case 'linux':
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:${swtVersion}"
}
startScripts.doLast {
def lines = unixScript.text.readLines()
println lines.add(1, 'export FOO=foo')
println lines.add(2, 'export BAR=bar')
unixScript.text = lines.join("\n")
}
break
case 'windows':
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:${swtVersion}"
}
}
version "${osType}-1.0-SNAPSHOT"
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
and here is my shell script (buildAll.sh) that I run from the terminal in Idea.
#!/bin/sh
./gradlew -PosType=macos distTar
./gradlew -PosType=linux distTar
./gradlew -PosType=windows distTar
./gradlew -PosType=windows distZip

How do I use the native JUnit 5 support in Gradle with the Kotlin DSL?

I want to use the built-in JUnit 5 with the Gradle Kotlin DSL, because during build I get this warning:
WARNING: The junit-platform-gradle-plugin is deprecated and will be discontinued in JUnit Platform 1.3.
Please use Gradle's native support for running tests on the JUnit Platform (requires Gradle 4.6 or higher):
https://junit.org/junit5/docs/current/user-guide/#running-tests-build-gradle
That links tells me to put
test {
useJUnitPlatform()
}
in my build.gradle, but what is the syntax for build.gradle.kts?
My current build file is
import org.gradle.api.plugins.ExtensionAware
import org.junit.platform.gradle.plugin.FiltersExtension
import org.junit.platform.gradle.plugin.EnginesExtension
import org.junit.platform.gradle.plugin.JUnitPlatformExtension
group = "com.example"
version = "0.0"
// JUnit 5
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath("org.junit.platform:junit-platform-gradle-plugin:1.2.0")
}
}
apply {
plugin("org.junit.platform.gradle.plugin")
}
// Kotlin configuration.
plugins {
val kotlinVersion = "1.2.41"
application
kotlin("jvm") version kotlinVersion
java // Required by at least JUnit.
// Plugin which checks for dependency updates with help/dependencyUpdates task.
id("com.github.ben-manes.versions") version "0.17.0"
// Plugin which can update Gradle dependencies, use help/useLatestVersions
id("se.patrikerdes.use-latest-versions") version "0.2.1"
}
application {
mainClassName = "com.example.HelloWorld"
}
dependencies {
compile(kotlin("stdlib"))
// To "prevent strange errors".
compile(kotlin("reflect"))
// Kotlin reflection.
compile(kotlin("test"))
compile(kotlin("test-junit"))
// JUnit 5
testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
testRuntime("org.junit.platform:junit-platform-console:1.2.0")
// Kotlintest
testCompile("io.kotlintest:kotlintest-core:3.1.0-RC2")
testCompile("io.kotlintest:kotlintest-assertions:3.1.0-RC2")
testCompile("io.kotlintest:kotlintest-runner-junit5:3.1.0-RC2")
}
repositories {
mavenCentral()
mavenLocal()
jcenter()
}
(The following is some blabla because this question 'contains mostly code').
I tried to find documentation on how to customize tasks in the Kotlin DSL, but I couldn't find any. In normal Groovy you can just write the name of the task and then change things in the block, but the Kotlin DSL doesn't recognise the task as such, unresolved reference.
Also, this question is related but asks for creating of new tasks, instead of customize existing tasks: How do I overwrite a task in gradle kotlin-dsl
Here is a solution for normal Gradle.
[Edit april 2019] As Pedro has found, three months after I asked this question Gradle actually created a user guide for the Kotlin DSL which can be visited at https://docs.gradle.org/current/userguide/kotlin_dsl.html
They also added a migration guide from Groovy to Kotlin at https://guides.gradle.org/migrating-build-logic-from-groovy-to-kotlin/
Answer:
The syntax you ask for is
tasks.test {
// Use the built-in JUnit support of Gradle.
useJUnitPlatform()
}
which I figured out from this example file from the Kotlin DSL GitHub, or you can use
tasks.withType<Test> {
useJUnitPlatform()
}
which is used in the this official userguide which was created a couple of months after this answer was written (thanks to Pedro's answer for noting this).
But in any case you actually are still using the buildscript block, which is a bit deprecated itself, use the new plugins DSL instead (docs). New build.gradle.kts becomes
group = "com.example"
version = "0.0"
plugins {
val kotlinVersion = "1.2.41"
application
kotlin("jvm") version kotlinVersion
java // Required by at least JUnit.
// Plugin which checks for dependency updates with help/dependencyUpdates task.
id("com.github.ben-manes.versions") version "0.17.0"
// Plugin which can update Gradle dependencies, use help/useLatestVersions
id("se.patrikerdes.use-latest-versions") version "0.2.1"
}
application {
mainClassName = "com.example.HelloWorld"
}
dependencies {
compile(kotlin("stdlib"))
// To "prevent strange errors".
compile(kotlin("reflect"))
// Kotlin reflection.
compile(kotlin("test"))
compile(kotlin("test-junit"))
// JUnit 5
testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
testRuntime("org.junit.platform:junit-platform-console:1.2.0")
// Kotlintest
testCompile("io.kotlintest:kotlintest-core:3.1.0-RC2")
testCompile("io.kotlintest:kotlintest-assertions:3.1.0-RC2")
testCompile("io.kotlintest:kotlintest-runner-junit5:3.1.0-RC2")
}
repositories {
mavenCentral()
mavenLocal()
jcenter()
}
tasks {
// Use the native JUnit support of Gradle.
"test"(Test::class) {
useJUnitPlatform()
}
}
(Since the Gradle Kotlin DSL has almost no documentation at all except a few (undocumented) example files on GitHub, I'm documenting a few common examples here.)
(Complete example project at GitHub, self-promotion...)
Adding on top of accepted answer, it is also possible to use typed task configuration like:
tasks.withType<Test> {
useJUnitPlatform()
}
Update:
Gradle docs for reference here. Specifically Example 19 which has:
tasks.withType<JavaCompile> {
options.isWarnings = true
// ...
}
this worked for me till now...
plugins {
kotlin("jvm") version "1.7.10"
}
group = "org.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
testImplementation("org.junit.jupiter:junit-jupiter:5.9.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.0")
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}

Building a Gradle Kotlin project with Java 9/10 and Gradle's Kotlin DSL

This is kind of a follow up to Building a Kotlin + Java 9 project with Gradle. In the linked post Gradle with Groovy is used. In my case Kotlin DSL is used.
Basically I have a gradle project with the following structure (only relevant content here):
src/
| main/
| | kotlin/
| | | com/example/testproject/
| | | | Main.kt
| | | module-info.java
build.gradle.kts
settings.gradle
Usually I would run gradle run on it, but that results in the following error:
module-info.java:3: error: module not found: kotlin.stdlib
requires kotlin.stdlib;
Now this is what my build file currently looks like
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
var kotlin_version: String by extra
kotlin_version = "1.2.41"
repositories {
mavenCentral()
}
dependencies {
classpath(kotlin("gradle-plugin", kotlin_version))
}
}
repositories {
mavenCentral()
}
plugins {
kotlin("jvm") version "1.2.41"
application
}
val kotlin_version: String by extra
dependencies {
implementation(kotlin("stdlib", kotlin_version))
implementation(kotlin("stdlib-jdk8", kotlin_version))
implementation(kotlin("runtime", kotlin_version))
implementation(kotlin("reflect", kotlin_version))
}
val group = "com.example"
application {
mainClassName = "$group.testproject.Main"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_10
targetCompatibility = sourceCompatibility
sourceSets {
"main" {
java.srcDirs("src/main/kotlin")
}
}
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
And this is my module-info.java:
module com.example.testproject {
// Kotlin compatibility
requires kotlin.stdlib;
exports com.example.testproject;
}
Question: How to get the solution provided in the linked post (or any other solution) running, so that a Kotlin project with Gradle's Kotlin DSL can be compiled using a Java 9/10 environment?
This is kind of a self-answer (I do not full understand that matter, so the explanations might not be correct). The conclusions I draw here are purely empiric and based on a conversion from Kotlin DSL to Gradle's Groovy and back.
The first problem I encountered was that I had two conflicting providers for the Kotlin functions in:
implementation(kotlin("stdlib", kotlin_version))
implementation(kotlin("runtime", kotlin_version))
I solved that by deciding to go with stdlib. All other dependencies did not conflict with each other.
The more severe problem was something different: The compileJava task did not find the correct classes (from the project) and modules (from the distribution). Therefore I needed to adapt the paths as in the following example:
val compileKotlin: KotlinCompile by tasks
val compileJava: JavaCompile by tasks
compileJava.destinationDir = compileKotlin.destinationDir
This basically compiles the Java classes within Kotlins compiled output and makes Java find the classes from the project.
The last problem could finally be solved by the following non-idiomatic piece of Kotlin Script:
tasks {
"compileJava" {
dependsOn(":compileKotlin")
if (JavaVersion.current() >= JavaVersion.VERSION_1_9) {
inputs.property("moduleName", ext["moduleName"])
doFirst {
compileJava.options.compilerArgs = listOf(
// include Gradle dependencies as modules
"--module-path", java.sourceSets["main"].compileClasspath.asPath,
)
java.sourceSets["main"].compileClasspath = files()
}
}
}
}
This basically lets the compileJava task use an empty classpath and sets module path as compiler option to the currently set compileClasspath of the main source set (the Kotlin source set which is also added as Java source set).

Use Gradle to generate Java classes from a Swagger definition into a standalone JAR

I am fairly new to both Gradle and Swagger code generator plugin for it (concretely the one that is linked from Swagger's website, i.e. https://github.com/thebignet/swagger-codegen-gradle-plugin), so I'm not sure whether my problem is with Gradle in general or with that particular plugin.
I've created a simple multi-module Spring Boot application (but the fact that I'm using Spring Boot or even Spring doesn't matter much). It's a console application; i.e. it doesn't start a webserver. In fact, it's actually a REST client consuming someone else's interface.
The application consists of four modules: spc-parent (which is just an envelope for the rest) containing spc-boot, spc-service, and spc-integration-model. Spc-boot contains just the starting point of the application, spc-service now contains a single Spring service, and spc-integration-model is meant to contain classes needed to consume the REST interface. The resulting structure will be much more complicated but I've tried to create a sort of a minimal example.
The problem lies within the spc-integration-model module. It consists of a single source file, petstore.json, and a build.gradle copied from https://github.com/thebignet/swagger-codegen-gradle-plugin (and only slightly modified). There are actually two problems (but they may have the same underlying cause).
When running gradle build (from spc-parent) for the very first time, it fails. Java sources are generated from petstore.json but they don't get compiled, which is why the service in spc-service doesn't see needed classes. However, running gradle build a second time fixes this (generated Java sources get compiled which makes it possible to compile spc-service, too).
The created JAR of spc-integration-model never contains anything besides Manifest.
My goal here is to persuade Gradle to compile the generated classes right away during the first build and also to put them into the JAR.
Now for some concrete Gradle tasks. The most interesting is spc-integration-model's build.gradle:
plugins {
id 'org.detoeuf.swagger-codegen' version '1.7.4'
id 'java'
}
apply plugin: 'org.detoeuf.swagger-codegen'
repositories {
mavenCentral()
jcenter()
}
swagger {
inputSpec = 'http://petstore.swagger.io/v2/swagger.json'
outputDir = file('build/swagger')
lang = 'java'
additionalProperties = [
'apiPackage' : 'ondra.spc.integration.client.api',
'dateLibrary' : 'java8',
'hideGenerationTimestamp': 'true',
'invokerPackage' : 'ondra.spc.integration.client',
'library' : 'resttemplate',
'modelNameSuffix' : 'Dto',
'modelPackage' : 'ondra.spc.integration.client.model'
]
importMappings = [
'Dog': 'io.swagger.petstore.client.model.Dog'
]
}
sourceSets {
swagger {
java {
srcDir file("${project.buildDir.path}/swagger/src/main/java")
}
}
}
classes.dependsOn('swagger')
ext {
spring_boot_version = springBootVersion
jackson_version = jacksonVersion
junit_version = jUnitVersion
swagger_annotations_version = swaggerAnnotationsVersion
swagger_codegen_version = swaggerCodegenVersion
}
dependencies {
swaggerCompile "org.springframework.boot:spring-boot-starter-web:$spring_boot_version"
swaggerCompile "io.swagger:swagger-annotations:$swagger_annotations_version"
compile sourceSets.swagger.output
compile "com.fasterxml.jackson.core:jackson-core:$jackson_version"
compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
compile "io.swagger:swagger-codegen:$swagger_codegen_version"
testCompile "junit:junit:$junit_version"
}
(Now that I'm re-reading my question I see that the local version of petstore.json is actually not used and an online version is used instead but let's leave that aside.)
The rest should be quite straightforward. spc-service:
dependencies {
compile "org.springframework:spring-context:$springVersion"
compile "org.springframework:spring-web:$springVersion"
compile project (":spc-integration-model")
}
spc-boot:
dependencies {
compile "org.springframework.boot:spring-boot:$springBootVersion"
compile "org.springframework.boot:spring-boot-autoconfigure:$springBootVersion"
compile "org.springframework:spring-web:$springVersion"
runtime "org.hibernate.validator:hibernate-validator:$hibernateVersion"
runtime "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
compile project (":spc-service")
testCompile("org.junit.jupiter:junit-jupiter-api:$jUnitVersion")
testRuntime("org.junit.jupiter:junit-jupiter-engine:$jUnitVersion")
}
test {
useJUnitPlatform()
}
spc-parent:
subprojects {
apply plugin: 'java'
group 'ondra'
version '1.0-SNAPSHOT'
buildscript {
ext {
hibernateVersion = '6.0.9.Final'
jacksonVersion = '2.9.4'
springBootVersion = '2.0.0.RELEASE'
springVersion = '5.0.5.RELEASE'
swaggerAnnotationsVersion = '1.5.16'
swaggerCodegenVersion = '2.2.3'
jUnitVersion = '5.1.1'
}
repositories {
mavenCentral()
}
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
}
And spc-parent's settings.gradle:
rootProject.name = 'spc-parent'
include 'spc-boot'
include 'spc-service'
include 'spc-integration-model'
I've also put the whole application into a single archive: https://drive.google.com/open?id=1cOYIcaxnhik548w0wEGswgD2g4udATdD

Resources