How to run a command line command with Kotlin DSL in Gradle 6.1.1? - kotlin

I am trying to run the code block below, after reading multiple posts on the topic and the Gradle manual. I run the below and get the following error: execCommand == null!
Any ideas on what I am doing wrong with the below code block?
open class BuildDataClassFromAvro : org.gradle.api.tasks.Exec() {
#TaskAction
fun build() {
println("Building data classes.....")
commandLine("date")
}
}
tasks.register<BuildDataClassFromAvro>("buildFromAvro") {
description = "Do stuff"
}

To define a Gradle task that runs a command-line using the Gradle Kotlin DSL do something like this in your build file:
task<Exec>("buildFromAvro") {
commandLine("echo", "test")
}
In the example above the commandLine will simply run echo, outputting the value test. So replace that with whatever you want to actually do.
You can then run that with gradle buildFromAvro
More info here: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html

If adding to an existing task:
exec {
commandLine("echo", "hi")
}

Another approach is to use the Java ProcessBuilder API:
tasks.create("MyTask") {
val command = "echo Hello"
doLast {
val process = ProcessBuilder()
.command(command.split(" "))
.directory(rootProject.projectDir)
.redirectOutput(Redirect.INHERIT)
.redirectError(Redirect.INHERIT)
.start()
.waitFor(60, TimeUnit.SECONDS)
val result = process.inputStream.bufferedReader().readText()
println(result) // Prints Hello
}
}

Related

Calling PCEnhancerTask from Kotlin in Gradle

I need to call the OpenJPA PCEnhancerTask class from Kotlin instead of Groovy. The following code works just fine (based on a previous solution documented here):
def openJPAClosure = {
def entityFiles = sourceSets.main.output.classesDirs.asFileTree.matching {
include 'com/company/persist/*Entity.class'
}
println "Enhancing with OpenJPA:"
entityFiles.getFiles().each {
println it
}
ant.taskdef(
name : 'openjpac',
classpath : sourceSets.main.runtimeClasspath.asPath,
classname : 'org.apache.openjpa.ant.PCEnhancerTask'
)
ant.openjpac(
classpath: sourceSets.main.runtimeClasspath.asPath,
addDefaultConstructor: false,
enforcePropertyRestrictions: true) {
entityFiles.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
}
}
I was looking at the documentation on how to call Ant tasks from Gradle but I could not translate all the necessary steps using the GroovyBuilder. So instead I tough of calling the PCEnhancer directly:
fun openJPAEnrich() {
val entityFiles = sourceSets.main.get().output.classesDirs.asFileTree.matching {
include("com/company/persist/*Entity.class")
}
println("Enhancing with OpenJPA, the following files...")
entityFiles.getFiles().forEach() {
println(it)
}
org.apache.openjpa.ant.PCEnhancerTask.main(asList(entityFiles))
}
But it complains about not being able to find org.apache.openjpa in the classpath (but is it listed as a compilation dependency)
My questions are:
What is the correct way to translate the original Groovy construct to Kotlin using groovyBuilder
If is not possible, how you can correctly call PCEnhancer from Kotlin in Gradle?
So I ended making it work with a custom JavaExec Gradle task:
tasks.create<JavaExec>("openJPAEnrich") {
val entityFiles = sourceSets.main.get().output.classesDirs.asFileTree.matching {
include("com/company/persist/*Entity.class")
}
println("Enhancing with OpenJPA, the following files...")
entityFiles.files.forEach() {
println(it)
}
classpath = sourceSets.main.get().runtimeClasspath
main = "org.apache.openjpa.enhance.PCEnhancer"
args(listOf("-enforcePropertyRestrictions", "true", "-addDefaultConstructor", "false"))
entityFiles.forEach { classFile -> args?.add(classFile.toString())}
}
I was tempted to build my own custom Gradle task but for this felt overkill.
Thanks.
--Jose

How to add task dependencies in Gradle?

I'm trying to write a task named stage like this:
plugins {
base
}
val clean = "clean"
val build = "build"
tasks.register("stage") {
dependsOn(clean, build)
}
tasks[build].dependsOn(clean)
The problem is that when I run ./gradlew stage it doesn't run clean, nor build. This is a multiproject build and I have 3 subprojects with Kotlin code.
How do I get Gradle to run ./gradlew clean build whenever I type ./gradlew stage? This is clearly not a solution, and I don't see what I'm doing wrong.
I also tried this, but it didn't work either. It runs the tasks I want but at the end it runs clean and I end up with no build folder:
tasks.register("stage") {
subprojects.forEach { project ->
val clean = project.tasks.first { it.name.contains("clean") }
val build = project.tasks.first { it.name.contains("build") }
build.dependsOn(clean)
dependsOn(build)
}
}
I think one of the issues in your code is running the command:
build.dependsOn(clean)
Instead try:
tasks{
register("stage"){
dependsOn(clean, build)
}
}
Remember that tasks{} is a container and by scoping the code inside we have access to the other tasks as well.
You likely need to define the ordering using mustRunAfter
tasks.register("stage") {
subprojects.forEach { project ->
val clean = project.tasks.first { it.name.contains("clean") }
val build = project.tasks.first { it.name.contains("build") }
build.dependsOn(clean)
build.mustRunAfter(clean)
dependsOn(build)
}
}
From the docs of Task Dependencies and Task Ordering:
Dependencies to a task are controlled using Task.dependsOn(java.lang.Object[]) or Task.setDependsOn(java.lang.Iterable), and Task.mustRunAfter(java.lang.Object[]), Task.setMustRunAfter(java.lang.Iterable), Task.shouldRunAfter(java.lang.Object[]) and Task.setShouldRunAfter(java.lang.Iterable) are used to specify ordering between tasks.
This documentation in Gradle about ordering tasks can explain tasks ordering, and why you need a combination of dependsOn and mustRunAfter for your use case:
Note that “B.mustRunAfter(A)” or “B.shouldRunAfter(A)” does not imply any execution dependency between the tasks:
It is possible to execute tasks A and B independently. The ordering rule only has an effect when both tasks are scheduled for execution.
its much simpler
plugins {
base
}
allprojects {
task stage() {
dependsOn(clean, build)
}
}
so when you run stage - it should do clean and build

Using Kotlin/Native, how can I execute an external OS executable or $PATH command and interact with its stdin, stdout, and stderr streams?

I'm working on a proof of concept for a command line tool written in Kotlin/Native, as it seems to be an ideal language and runtime for cross-platform binaries.
This command line tool needs to regularly interact with operating system executables, commands and/or shell functions in the os user $PATH or shell environment. However, I don't see any examples in kotlin-native samples or documentation anywhere that might indicate how to:
execute an operating system executable or $PATH command
obtain the execution's return code (integer)
ensure the executed process's stdin, stdout and stderr file descriptor streams can be represented as OutputStream and InputStreams respectively
In JVM-land, we'd use java.lang.ProcessBuilder for all of this, but that's apparently not available in Kotlin/Native.
I found the cinterop/posix platform.posix.system function, but that doesn't give you access to the process's streams.
In my web research, I found a really nice C tutorial that indicates the only clean way to do this is with fork and dup2 and such, but it's not clear to me if or how that would translate to Kotlin/Native code.
I did some play around with kotlin native and succeded to do a posix exec command for jvm, mac, unix and (untested) windows...
https://github.com/hoffipublic/minimal_kotlin_multiplatform
target common
fun String.executeCommand(
redirectStderr: Boolean = true
): String? = MppProcess.executeCommand(this, redirectStderr)
interface IMppProcess {
fun executeCommand(
command: String,
redirectStderr: Boolean = true
): String?
}
expect object MppProcess : IMppProcess {
override fun executeCommand(
command: String,
redirectStderr: Boolean
): String?
}
target jvm
actual object MppProcess : IMppProcess {
actual override fun executeCommand(
command: String,
redirectStderr: Boolean
): String? {
return runCatching {
ProcessBuilder(command.split(Regex("(?<!(\"|').{0,255}) | (?!.*\\1.*)")))
//.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.apply { if (redirectStderr) this.redirectError(ProcessBuilder.Redirect.PIPE) }
.start().apply { waitFor(60L, TimeUnit.SECONDS) }
.inputStream.bufferedReader().readText()
}.onFailure { it.printStackTrace() }.getOrNull()
}
}
target mac/unix/windows
import kotlinx.cinterop.refTo
import kotlinx.cinterop.toKString
import platform.posix.fgets
import platform.posix.pclose
import platform.posix.popen
actual object MppProcess : IMppProcess {
actual override fun executeCommand(
command: String,
redirectStderr: Boolean
): String? {
val commandToExecute = if (redirectStderr) "$command 2>&1" else command
val fp = popen(commandToExecute, "r") ?: error("Failed to run command: $command")
val stdout = buildString {
val buffer = ByteArray(4096)
while (true) {
val input = fgets(buffer.refTo(0), buffer.size, fp) ?: break
append(input.toKString())
}
}
val status = pclose(fp)
if (status != 0) {
error("Command `$command` failed with status $status${if (redirectStderr) ": $stdout" else ""}")
}
return stdout
}
}
only did execute ls by now, but it works.

readLine() doesn't wait for user input in Kotlin/Native

Here is a simple script
fun main() {
print("ready> ")
val input = readLine()
println("User input: $input")
}
When I run this program with gradle runReleaseExecutableMacos I expect that I'll see a ready> prompt and will have a possibility to type some chars. But this program finishes immediately with User input: null as a result.
Am I missing something?
To achieve the behavior you desire, you can run the executable file produced by the Gradle. It will have an extension *.kexe.
Also, you can extend your build.gradle file with additional parameter. you got to add something like this:
macosX64("macos") {
binaries {
executable {
runTask.standardInput = System.in
}
}
}

How to pass command line argument to Gradle Kotlin DSL

Here's an example from Groovy that represents exactly what I would like to achieve:
Command line:
./gradlew jib -PmyArg=hello
build.gradle.kts
task myTask {
doFirst {
println myArg
... do what you want
}
}
Source of this example is here - option 3.
How can I read pass and read myArg value in Kotlin DSL ?
After some time found an answer:
build.gradle.kts
val myArg: String by project // Command line argument is always a part of project
task("myTask") {
doFirst {
if (project.hasProperty("myArg")) {
println(myArg)
}
}
}
Command line:
gradle myTask -PmyArg=foo
Output:
$ gradle myTask -PmyArg=foo
> Task :myTask
foo
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
Related links:
How to pass arguments from command line to gradle
How to pass parameters or arguments into a gradle task
Gradle task check if property is defined
I retrieved the argument for my task like this (build.gradle.kts with Kotlin DSL):
tasks.create("myCustomTask") {
doLast {
val myArg = properties["myArgName"]
// OR a more verbose form:
val myArg = project.properties["myArgName"]
}
}
./gradlew myCustomTask -PmyArgName=hello