60

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?

mkobit
  • 43,979
  • 12
  • 156
  • 150
Bob Kuhar
  • 10,838
  • 11
  • 62
  • 115

6 Answers6

77

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)

mkobit
  • 43,979
  • 12
  • 156
  • 150
Taytay
  • 11,063
  • 5
  • 43
  • 49
  • gradle 3.3: this is not working. If I comment println nothing happens, if I uncomment it - there many new lines in output, it's strange but even no "Output" word – Tertium Mar 29 '17 at 11:33
61

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()

mkobit
  • 43,979
  • 12
  • 156
  • 150
Benjamin Muschko
  • 32,442
  • 9
  • 61
  • 82
  • Easy is better. I really didn't expect capturing the output of a shell command require hand parsing an output stream. No matter. Thanks for the guidance. – Bob Kuhar Jun 19 '12 at 15:32
  • 3
    Quick question, isn't your code missing `standardOutput = os` ?? – Nick Grealy Mar 21 '14 at 03:20
  • When we do this it seems to be asynchronous. It tries to output whoami and hostname before they exist. Ideas? –  Mar 25 '14 at 21:31
  • @ScottBeeson in the intervening years since this post my build.gradle now leverages the System.getProperty( 'user.name' ) and InetAddress.getLocalHost().getHostName() to achieve the goal of this post. I suspect this may overcome any race condition you are seeing. – Bob Kuhar Mar 26 '14 at 01:47
  • Will you be actually able to do `println whoami`? You are setting these properties on `ext` of tasks inside them, isn't it? Shouldn't it be `project.ext.hostname = os.toString()` or am I missing smth? – Taras Leskiv Jul 23 '15 at 13:08
  • @Taras Once defined as extra property, you don't have to be explicit anymore. Both works. – Benjamin Muschko Jul 23 '15 at 13:10
19

Using the kotlin-dsl:

import java.io.ByteArrayOutputStream

val outputText: String = ByteArrayOutputStream().use { outputStream ->
  project.exec {
    commandLine("whoami")
    standardOutput = outputStream
  }
  outputStream.toString()
}
mkobit
  • 43,979
  • 12
  • 156
  • 150
11

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
swpalmer
  • 3,890
  • 2
  • 23
  • 31
  • `def x = 'bash -c echo & echo $!'.execute().text` should this work? – Stefan Falk Jan 08 '21 at 16:18
  • 1
    @StefanFalk "bash -c ...." doesn't seem to have the output captured properly, no. For me `'echo hello'.execute().text` worked – swpalmer Jan 09 '21 at 17:14
  • This approach worked a lot better for me. The `args` parameter for the `exec` was not accepting an argument with a Windows based path. It just stopped at "C" and ignored the rest and I couldn't find a way to escape the path. The `execute()` call worked flawlessly though, even if it provides less control over execution. – omahena Jun 15 '23 at 11:08
6

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()
      }
    }
  }
}
mkobit
  • 43,979
  • 12
  • 156
  • 150
javabrett
  • 7,020
  • 4
  • 51
  • 73
  • 1
    This shows nothing in Android Studio if the task are not manually executed by double-clicking it in __Gradle projects__ window. – zwcloud Aug 10 '18 at 02:50
5

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")
}
aSemy
  • 5,485
  • 2
  • 25
  • 51
Lewik
  • 649
  • 1
  • 6
  • 19