Gradle Exec first task output to second task commandLine

buildship

(raaone7) #1

Task 1: would call say a.bat, output of a.bat is “hostname”

Task 2: Should call hostname (output from task 1), output would be the host name

when i try to do “gradlew task2 -x test” on below build.gradle contents , it doesn’t work.

task task1(type: Exec) {
    commandLine = "ipconfig"
    println "task1 calling ${commandLine}"
	standardOutput = new ByteArrayOutputStream()
	doLast {
		ext.loginCommand = "hostname"
            // ext.loginCommand = standardOutput.toString() // this is where the command will return the hostname
		println "task1 command finished"
		println "loginCommand=${loginCommand}"
		println "********************"
	}
}

task task2(type: Exec, dependsOn: task1) {
	doFirst {
   		println "task1 command output is -- ${task1.loginCommand}"
  	}
	// This doesn't work, i want "hostname" from task1
	commandLine = task1.loginCommand
	
	// This works, if i set the hostname as command
	//commandLine = "hostname"
}

Have already posted it on SO with no luck:-
http://stackoverflow.com/questions/39675427/gradle-exec-task-output-to-another-task


Assigning exec output to a closure not working
(Raleigh) #2

This has to do with the gradle build life cycle. Every invocation of gradle goes through three distinct phases: initialization, configuration, and execution. See https://docs.gradle.org/current/userguide/build_lifecycle.html

Because the doLast { ... } block in task1 isn’t evaluated until the execution phase the “loginCommand” property doesn’t exist during the configuration phase which is when task2 needs it to be defined.

If you move the ext.loginCommand = "hostname" line to be outside the doLast it will work as you want.


(raaone7) #3

I tried it didn’t work. I will try to rephrase the situation.

I am looking for output of task 1 via command line, to be used as an commandLine value in task 2

Say task1 calls a.bat and returns hostname
task2 would call hostname

Hope that makes sense.

Have updated the task1 above. Have put it as a comment.


(raaone7) #4

Good explanation Raleigh. I tried this. This seem to get 90% far… Not sure, if i am missing something.

task task1(type: Exec) {
    commandLine = "a.bat"
    println "task1 calling ${commandLine}"
	standardOutput = new ByteArrayOutputStream()
	doLast {
		ext.loginCommand = standardOutput.toString()
		println "task1 command finished"
		println "********************"
	}
}

task task2(dependsOn: task1) {
	doFirst {
   		println "inside task2 - task1 command output is -- ${task1.loginCommand}"
   		exec {
   			executable = task1.loginCommand
   		}
  	}
}

**a.bat is below**
@echo off
echo hostname



Output :-

task1 calling [a.bat]
:task1
task1 command finished
********************
:task2
inside task2 - task1 command output is -- hostname

:task2 FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':task2'.
> A problem occurred starting process 'command 'hostname
  ''

(raaone7) #5

This seem to have resolved the issue. YaY.

task task1(type: Exec) {
    commandLine = "a.bat"
    println "task1 calling ${commandLine}"
	standardOutput = new ByteArrayOutputStream()
	doLast {
		ext.loginCommand = standardOutput.toString()
		println "task1 command finished"
		println "********************"
	}
}

task task2(dependsOn: task1) {
	doFirst {
   		println "inside task2 - task1 command output is -- ${task1.loginCommand}"
   		exec {
   			commandLine = task1.loginCommand.trim() 
   		}
  	}
}

(raaone7) #6

Now next is how to how to ignore the escape sequences if, task one returns something like this.

For ex :- the below is returned by task1 script (a.bat),

"docker login -u test login -p AQECAHhLyjz7PJEPmYbqH6A6f/wWNzyz5BKa37BnBPsAyuF1KQAABGIwggReBgkqhkiG9w0BBwagggRPMIIESwIBADCCBEQGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMtxi+TnIBzphpKTVaAo/1PQ= -e none https://test.me"

and task 2 need to trigger the above command


(raaone7) #7

Got bit far using the below, but failing for some reason.

if i put i harcode in a variable

def command1 = docker login -u test -p AQECAHhLyjz7PJEPmYbqH6A6f/wWNzyz5BKa37BnBPsAyu= -e none https://test.me

and do
commandLine = command1.trim().tokenize()

it works perfect

If i get the login command from task1

like task 1 output is below

docker login -u test -p AQECAHhLyjz7PJEPmYbqH6A6f/wWNzyz5BKa37BnBPsAyu= -e none https://test.me

commandLine = task1.loginCommand.trim().tokenize()

task 2 doesn’t seem to like it for some reason


(Raleigh) #8

Does task1 really need to be a gradle task?

If not then there are a couple of options you can try:

  1. define a method. Since gradle has full access to Groovy you can define a method like this:
def login(cmd) {
    println "Running command $cmd"
    def sout = new ByteArrayOutputStream()
    project.exec {
        commandLine = [cmd]
        standardOutput = sout
    }
    return sout.toString()
}

This takes an argument, which is the command to run and returns the stdout of the command.

  1. An alternative is to use a closure to run the command. In the example below the default cmd run is a.bat if nothing is passed.
project.ext.login2 = { cmd='a.bat' ->
    println "Running command $cmd, method 2"
    def sout = new ByteArrayOutputStream()
    project.exec {
        commandLine = [cmd]
        standardOutput = sout
    }
    return sout.toString()
}

To use either version simply invoke the method from your task like this:

task task2() {
    def cmd = login('a.bat')
    def cmd2 = login2()
    doFirst {
        println "inside task2 - login command output is -- ${cmd}"
        println "inside task2 - login2 command output is -- ${cmd2}"
        exec {
            executable = cmd.trim()
        }
    }
}

The trim() method is to chop off any superfluous whitespace from the command string.


(raaone7) #9

i tried to put ipconfig /a inside a.bat

Contents of a.bat

@echo off
echo ipconfig /all

Came up with error. Output below

C:\MyData\projects\rdms_microservices\listxls>gradlew task2 -x test
Running command a.bat
Running command a.bat, method 2
:task2
inside task2 - login command output is -- ipconfig /a

inside task2 - login2 command output is -- ipconfig /a

:task2 FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':task2'.
> A problem occurred starting process 'command 'ipconfig /a''

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 6.439 secs

Some reason it fails, if we pass the arguments.
if i put “echo hostname” inside a.bat, it works, but doesnt work if i put “echo ipconfig /a” inside a.bat

Error from stack trace

Caused by: org.gradle.process.internal.ExecException: A problem occurred starting process 'command 'ipconfig /a''
        at org.gradle.process.internal.DefaultExecHandle.setEndStateInfo(DefaultExecHandle.java:197)
        at org.gradle.process.internal.DefaultExecHandle.failed(DefaultExecHandle.java:327)
        at org.gradle.process.internal.ExecHandleRunner.run(ExecHandleRunner.java:86)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
        at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
Caused by: net.rubygrapefruit.platform.NativeException: Could not start 'ipconfig /a'
        at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:27)
        at net.rubygrapefruit.platform.internal.WindowsProcessLauncher.start(WindowsProcessLauncher.java:22)
        at net.rubygrapefruit.platform.internal.WrapperProcessLauncher.start(WrapperProcessLauncher.java:36)
        at org.gradle.process.internal.ExecHandleRunner.run(ExecHandleRunner.java:68)
        ... 2 more
Caused by: java.io.IOException: Cannot run program "ipconfig /a" (in directory "C:\MyData\projects\rdms_microservices\listxls"): CreateProcess error=2, The system cannot find the file specif
        at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25)
        ... 5 more
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
        ... 6 more


BUILD FAILED

Total time: 4.939 secs

(Raleigh) #10

You need to use commandLine or executable & args if your command take arguments.
see the DSL for more details: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html

This works for me. Note I used the /all switch as /a fails for me.

a.bat

@echo off
echo ipconfg /all
task task2() {
    def cmdline = login('a.bat').trim().tokenize(' ') as List

    doFirst {
        println "inside task2 - login command output is -- ${cmdline}"
        exec {
            commandLine = cmdline

        }
    }
}

(raaone7) #11

Thanks Raleigh. Fantastic !!! it worked as expected. You have been of great help.


(Yarochkin) #12

Please see code below for complete solution. Main points to use: doLast and exec inside doLast{}.

We are creating ByteArrayOutputStream() like in gradle docs https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html

task showId(type: Exec) {
    commandLine 'whoami'
    standardOutput = new ByteArrayOutputStream()
    ext.hash = {
        standardOutput.toString()
    }
}

task writeHash(dependsOn: 'showId') {
    def result = file("${buildDir}/hash.txt")
    outputs.file result

   doLast {
        if(!buildDir.exists()) {
            buildDir.mkdirs()
        }
// Here you are referencing to var as  tasks.{taskName}.{variableName} to read it. Because commandLine executes  in the 
// early LifeCycle we need to read var and execute inside doLast cycle

    def hash = tasks.showId.hash()
    println hash
    result.write(hash)
    exec {
    commandLine ('echo','hash: ', "$hash")    
    }
}

}