How to use exec() output in gradle - gradle

I am trying to implement a gradle task to dynamically create a buildsignature.properties file from a series of environment variable values and shell executions. I have it mostly working, but I can't seem to get the output of the shell commands. Here's my task...
task generateBuildSignature << {
ext.whoami = exec() {
executable = "whoami"
}
ext.hostname = exec() {
executable = "hostname"
}
ext.buildTag = System.env.BUILD_TAG ?: "dev"
ant.propertyfile(
file: "${buildDir}/buildsignature.properties",
comment: "This file is automatically generated - DO NOT EDIT!" ) {
entry( key: "version", value: "${project.version}" )
entry( key: "buildTimestamp", value: "${new Date().format('yyyy-MM-dd HH:mm:ss z')}" )
entry( key: "buildUser", value: "${ext.whoami}" )
entry( key: "buildSystem", value: "${ext.hostname}" )
entry( key: "buildTag", value: "$ext.buildTag" )
}
}
But the resulting properties field does not get the desired results for buildUser and buildSystem.
#This file is automatically generated - DO NOT EDIT!
#Mon, 18 Jun 2012 18:14:14 -0700
version=1.1.0
buildTimestamp=2012-06-18 18\:14\:14 PDT
buildUser=org.gradle.process.internal.DefaultExecHandle$ExecResultImpl#2e6a54f9
buildSystem=org.gradle.process.internal.DefaultExecHandle$ExecResultImpl#46f0bf3d
buildTag=dev
How do I get buildUser and buildSystem to match the output of the corresponding exec rather than some default ExecResultImpl toString? This really can't be that hard, can it?

This is my preferred syntax for getting the stdout from exec:
def stdout = new ByteArrayOutputStream()
exec{
commandLine "whoami"
standardOutput = stdout;
}
println "Output:\n$stdout";
Found here: http://gradle.1045684.n5.nabble.com/external-process-execution-td1431883.html
(Note that page has a typo though and mentions ByteArrayInputStream instead of ByteArrayOutputStream)

This post describes how to parse the output from an Exec invocation. Below you'll find two tasks that run your commands.
task setWhoamiProperty {
doLast {
new ByteArrayOutputStream().withStream { os ->
def result = exec {
executable = 'whoami'
standardOutput = os
}
ext.whoami = os.toString()
}
}
}
task setHostnameProperty {
doLast {
new ByteArrayOutputStream().withStream { os ->
def result = exec {
executable = 'hostname'
standardOutput = os
}
ext.hostname = os.toString()
}
}
}
task printBuildInfo {
dependsOn setWhoamiProperty, setHostnameProperty
doLast {
println whoami
println hostname
}
}
There's actually an easier way to get this information without having to invoke a shell command.
Currently logged in user: System.getProperty('user.name')
Hostname: InetAddress.getLocalHost().getHostName()

Using the kotlin-dsl:
import java.io.ByteArrayOutputStream
val outputText: String = ByteArrayOutputStream().use { outputStream ->
project.exec {
commandLine("whoami")
standardOutput = outputStream
}
outputStream.toString()
}

Groovy allows for a much simpler implementation in many cases. So if you are using Groovy-based build scripts you can simply do this:
def cmdOutput = "command line".execute().text

Paraphrased from the Gradle docs for Exec:
task execSomething {
doFirst {
exec {
workingDir '/some/dir'
commandLine '/some/command', 'arg'
...
//store the output instead of printing to the console:
standardOutput = new ByteArrayOutputStream()
//extension method execSomething.output() can be used to obtain the output:
ext.output = {
return standardOutput.toString()
}
}
}
}

kotlin-dsl variants
Groovy style
in buildSrc:
import org.codehaus.groovy.runtime.ProcessGroovyMethods
fun String.execute(): Process = ProcessGroovyMethods.execute(this)
fun Process.text(): String = ProcessGroovyMethods.getText(this)
build.gradle.kts:
"any command you want".execute().text().trim()
exec style
in buildSrc:
import org.gradle.api.Project
import org.gradle.process.ExecSpec
import java.io.ByteArrayOutputStream
fun Project.execWithOutput(spec: ExecSpec.() -> Unit) = ByteArrayOutputStream().use { outputStream ->
exec {
this.spec()
this.standardOutput = outputStream
}
outputStream.toString().trim()
}
build.gradle.kts:
val outputText = project.execWithOutput {
commandLine("whoami")
}
//variable project is actually optional
val outputText = execWithOutput {
commandLine("whoami")
}

Related

Fill array/args programmatically in Groovy/Gradle

I am trying to fix the args-argument for the executing programm for this Gradle task:
task generateResources(type: JavaExec) {
group = 'build'
description = 'Generate resource bundle from excel file'
mainClass.set('com.project.MainClass')
ext {
destDir = ''
sourceFile = ''
isDir = false
setInputDir = { input ->
xlxsTree = fileTree(input).matching { include '*.xls' }
xlxsTree.files.each {
args[1] = "$it" //how can I add all matching files???
}
}
setOutput = { output ->
outputs.dir(output)
destDir = output
args[0] = destDir
}
}
setInputDir("${project.projectDir}\\src\\main\\resources\\properties")
setOutput("${project.buildDir}\\resources\\generatedProperties")
logging.captureStandardOutput LogLevel.INFO
onlyIf {
!sourceFile.empty
}
classpath = project.configurations.resbundles
doFirst {
args args
jvmArgs '-Dfile.encoding=ISO-8859-15'
}
The args-argument in doLast{} should contain all file paths ending with .xls, e. g. ["C:\...\src\main\resources\properties\destdir", "C:\...\resources\generatedProperties\file1", "C:\...\resources\generatedProperties\file2"].
My approach is not working at all, I just tried to make it compile for at least one input file.

How extend task provided by plugin?

I have kotlin2js plugin with task compileKotlin2Js. I configure it like this:
val compileKotlin2Js: Kotlin2JsCompile by tasks
compileKotlin2Js.kotlinOptions {
main = "call"
outputFile = "${projectDir}/build/app.js"
}
Now I want to create similar task, but with other kotlinOptions. For example:
.kotlinOptions {
main = "noCall"
outputFile = "${projectDir}/build/lib.js"
}
How to do it?
UPDATE: I also tried to do some thing like this:
tasks.register<Kotlin2JsCompile>("myCompile2Js") {
kotlinOptions {
main = "noCall"
outputFile = "${projectDir}/build/lib.js"
}
}
But it produce error:
Execution failed for task ':myCompile2Js'.
> lateinit property destinationDirProvider has not been initialized
I also tried to specify destinationDir. Error disappear, but such task does not produce any build.
I haven't tested it, but I believe something like the following should do the trick:
tasks.register<Kotlin2JsCompile>("myCompile2Js") {
kotlinOptions {
main = "noCall"
outputFile = "${projectDir}/build/lib.js"
}
}
Or if you need a reference to the task later on:
val myCompile2Js by tasks.creating(Kotlin2JsCompile::class)
myCompile2Js.kotlinOptions {
main = "noCall"
outputFile = "${projectDir}/build/lib.js"
}

Custom gradle script for Detekt with multi-module project

I'm trying to create a custom gradle task that will run the different detekt profiles I have setup.
Here is my Detekt config:
detekt {
version = "1.0.0.RC6-4"
profile("main") {
input = "$projectDir/app/src/main/java"
output = "$projectDir/app/build/reports/detekt"
config = "$projectDir/config/detekt-config.yml"
}
profile("app") {
input = "$projectDir/app/src/main/java"
output = "$projectDir/app/build/reports/detekt"
}
profile("database") {
input = "$projectDir/database/src/main/java"
output = "$projectDir/database/build/reports/detekt"
}
profile("logging") {
input = "$projectDir/logging/src/main/java"
output = "$projectDir/logging/build/reports/detekt"
}
profile("network") {
input = "$projectDir/network/src/main/java"
output = "$projectDir/network/build/reports/detekt"
}
}
And here is what I'm trying for the custom gradle task:
task detektAll {
group = 'verification'
dependsOn 'detektCheck'
doLast {
println "\n##################################################" +
"\n# Detekt'ed all the things! Go you! #" +
"\n##################################################"
}
}
I need to add -Ddetekt.profile=app and the others for each profile.
How can I accomplish this?

Migrating Groovy gradle to Kotlin gradle (ext missing, or closure conversion, not sure)

I have some Git helper written in groovy:
git.gradle:
def gitHash() {
def res = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()
def diff = 'git diff'.execute([], project.rootDir).text.trim()
if (diff != null && diff.length() > 0) {
res += "-dirty"
}
return res
}
// method needs to be converted to closure, in order to be visible outside of script
ext.gitHash = { return gitHash();}
Now I converted whole stuff to Kotlin, looks like this now:
git.gradle.kts:
import java.io.IOException
import java.util.concurrent.TimeUnit
fun gitHash() : String {
var res = "git rev-parse --short HEAD".runCommand(project.rootDir)?.trim()
val diff = "git diff".runCommand(project.rootDir)?.trim()
if (diff != null && diff.isNotEmpty()) {
res += "-dirty"
}
return res!!
}
fun String.runCommand(workingDir: File): String? {
...
}
// method needs to be converted to closure, in order to be visible outside of script
//ext.gitHash = { return gitHash();} // <-- HERE'S THE PROBLEM
task("gitTask") { // <-- calling ./gradlew gitTask works
println(gitHash())
}
Main script includes these like this:
//apply from: 'git.gradle'
apply from: 'git.gradle.kts'
println gitHash() // works with Groovy, doesn't with Kotlin
Now, problem is, that main script can't recognize gitHash() method, most likely because I can't expose it via ext closure. Same as in Groovy script, this method seems to be private (or local) in that file.
As far I understand, ext closure is shorthand of 'project.extra' which I was trying to integrate. Also, it seems that typical Groovy closure have no equivalent in Kotlin. I stuck here, without any idea what else could I try. Any ideas welcome.
UPDATE
With:
var gitHash: Closure<Any?> by extra
gitHash = closureOf<String> { }
gitHash.delegate = { gitHash() }
I'm able to work with it in Groovy like:
println gitHash.invoke()
But it doesn't work with Kotlin script... because invoke() points to call() (https://github.com/gradle/gradle-script-kotlin/blob/master/src/main/kotlin/org/gradle/script/lang/kotlin/GroovyInteroperability.kt extension methods here). And while I'm trying to use it as closure gitHash() it leads to such error:
Parameter specified as non-null is null: method org.gradle.script.lang.kotlin.KotlinClosure.doCall, parameter it
It looks like I'm missing something...
In case you're still looking for a solution, here's what I'd suggest:
import java.io.ByteArrayOutputStream
inline
fun String.runCommand(workingDir: File): String {
val command = this
val stdout = ByteArrayOutputStream()
project.exec {
this.workingDir = workingDir
this.commandLine = command.split(" ")
this.standardOutput = stdout
}
return String(stdout.toByteArray()).trim()
}
task("gitHash") {
var res = "git rev-parse --short HEAD".runCommand(project.rootDir)
val diff = "git diff".runCommand(project.rootDir)
if (!diff.isNullOrBlank()) { res += "-dirty" }
println(res)
}
I've tested it with Gradle 4.6+
.. or perhaps the more idiomatic:
inline
fun String.runCommand(workingDir: File): String {
val stdout = ByteArrayOutputStream()
project.exec {
this.workingDir = workingDir
this.commandLine = this#runCommand.split(" ")
this.standardOutput = stdout
}
return String(stdout.toByteArray()).trim()
}
val commitHash by lazy { "git rev-parse --short HEAD".runCommand(project.rootDir) }
val workingCopyDiff by lazy { "git diff".runCommand(project.rootDir) }
val gitHash by tasks.creating {
println(commitHash + if (workingCopyDiff.isBlank()) "" else "-dirty")
}

Suppress Gradle's JavaExec output

I have gradle code below and I don't know how to avoid huge output generated by JavaExec task. I haven't found any option of JavaExec for it. If anyone knows better way of ignoring it, please share it.
def getStubOutput() {
return new FileOutputStream(new File("${buildDir}/temp"))
}
configure(project(':jradius:dictionary-min')) {
evaluationDependsOn(':jradius')
sourceSets {
main {
java {
srcDir "${projectDir}/target/dictionary-src"
}
}
}
dependencies {
compile project(':jradius:core')
}
task genSources(type: JavaExec) {
main = 'net.jradius.freeradius.RadiusDictionary'
classpath configurations.all
args = ["net.jradius.dictionary", "${projectDir}/../freeradius/dict-min", "${projectDir}/target/dictionary-src"]
maxHeapSize = "800m"
standardOutput = getStubOutput()
}
jar {
archiveName = "jradius-dictionary-min-1.1.5-SNAPSHOT.jar"
}
genSources.dependsOn ':jradius:cloneJradius'
compileJava.dependsOn genSources
}
I simply use a dummy OutputStream that does nothing in its write method:
def dummyOutputStream = new OutputStream() {
#Override
public void write(int b) {}
}
exec {
executable = name
standardOutput = dummyOutputStream
errorOutput = dummyOutputStream
ignoreExitValue = true
}
A great solution I came across is to modify the logging level of the task. If you set it to INFO, then it will squelch all the output of that task, unless gradle is run with --info.
Alternatively, you can set the level to LogLevel.QUIET, which will completely silence it.
task chatty(type: Exec) {
....
logging.captureStandardOutput LogLevel.INFO
}
As in the comment I thought that standardOutput can be set to null but the following piece of code (taken from: org.gradle.process.internal.AbstractExecHandleBuilder) shows that's not possible:
public AbstractExecHandleBuilder setStandardOutput(OutputStream outputStream) {
if (outputStream == null) {
throw new IllegalArgumentException("outputStream == null!");
}
this.standardOutput = outputStream;
return this;
}
What You can do is to redirect the output to temporary file (file will be deleted!) with this oneliner:
task genSources(type: JavaExec) {
main = 'net.jradius.freeradius.RadiusDictionary'
classpath configurations.all
args = ["net.jradius.dictionary", "${projectDir}/../freeradius/dict-min", "${projectDir}/target/dictionary-src"]
maxHeapSize = "800m"
standardOutput = { def f = File.createTempFile('aaa', 'bbb' ); f.deleteOnExit(); f.newOutputStream() }()
}
or if You'd like to save this output for further reading:
task genSources(type: JavaExec) {
main = 'net.jradius.freeradius.RadiusDictionary'
classpath configurations.all
args = ["net.jradius.dictionary", "${projectDir}/../freeradius/dict-min", "${projectDir}/target/dictionary-src"]
maxHeapSize = "800m"
standardOutput = new File(project.buildDir, 'radius.log').newOutputStream()
}
The last option is to add apache commons-io to script dependencies and set standardOutput to NullOutputStream. In can be done as follows:
import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'commons-io:commons-io:2.4'
}
}
task genSources(type: JavaExec) {
main = 'net.jradius.freeradius.RadiusDictionary'
classpath configurations.all
args = ["net.jradius.dictionary", "${projectDir}/../freeradius/dict-min", "${projectDir}/target/dictionary-src"]
maxHeapSize = "800m"
standardOutput = NULL_OUTPUT_STREAM
}
That's all that comes to my head.
This disables the standard output from a javaExec task:
task myCustomTask(type: javaExec) {
standardOutput = new ByteArrayOutputStream()
classpath = ...
main = ...
args ....
}

Resources