Capture Exec output using Kotlin DSL

Please refer to the following Ant snippet when reading the rest of the issue description.

    <target name="git.log" depends="init">
        <exec executable="bash" failonerror="true">
            <arg line="${util.dir}/build/git-log2json.sh" />
            <redirector output="${build.app.java.dir}/commit.json" />
        </exec>
    </target>

The above ant target executes a shell script and writes the output of the script to a file. This file is then bundled as part of a war.

How does one achieve the same effect in Gradle when using Kotlin DSL? I’m guessing that an Exec task would be the way to go. However I’m not able to find any examples. FWIW I’m already using the built-in war plugin to create the war file.

Something like the following groovy (please convert to kotlin)

task gitLog {
   ext.outfile = file("$buildDir/commit.json")
   outputs.file outfile
   doLast {
      outfile.withOutputStream { out ->
         project.exec {
            commandLine "bash ${utilDir}/build/git-log2json.sh".split(' ')
            standardOutput = out
            ignoreExitValue = false // don't need this line, it's the default 
         }
      } 
   } 
} 

See ExecSpec

This is where I’m stuck :cry:
I’ve been looking for examples without much luck.

Tried the following. I’m able to see the output on the command line. Not sure how to redirect to the file though.

tasks.register("gitLog") {

    val logFile = file("$buildDir/resources/main/commit.json")

    doLast {
        project.exec {
            commandLine("./util/build/git-log2json.sh").standardOutput.to(logFile)
        }
    }
}

Got it to work. Not sure if this is idiomatic. Feel free to correct me.

tasks.register("gitLog") {

    val logFile = file("$buildDir/resources/main/commit.json")

    doLast {

        logFile.outputStream().use { outputStream ->
            project.exec {
                commandLine("./util/build/git-log2json.sh")
                standardOutput = outputStream
            }
        }
    }
}

I get the feeling that you are writing to the same output directory as the “processResources” task. Each task should use a separate output directory so that up-to-date checks /task skipping can work correctly.

Something like:

task gitLog {
   ext.logDir = file("$buildDir/gitLog")
   outputs.dir logDir
   doLast {
      file("$logDir/commit.json").withOutputStream {...}
   }
} 
processResources {
   from gitLog // adds the folder and also adds a task dependency 
} 

This makes sense. I’ll make these changes. Thanks.

I’ve modified processResources and it is working fine. (How) can I add multiple from clauses to processResources? Some of these have differing into clauses. Is there an idiom for this?

To give more context, I want to add some custom information to the war file produced later.

The add folder works; however I have to explicitly declare the task dependency. Am I missing something? Kotlin DSL given below:

val gitLogDir = "$buildDir/gitLog"

tasks.processResources {
    dependsOn(":gitLog")

    // Add git log to project resources
    from(gitLogDir)
}

can I add multiple from clauses to processResources ? Some of these have differing into clauses. Is there an idiom for this

You can use Copy.with(CopySpec). Eg:

processResources {
   with(copySpec {
      from 'a' 
      into 'b' 
   }) 
   with(copySpec {
      from 'c' 
      into 'd' 
   }) 
} 

The add folder works; however I have to explicitly declare the task dependency. Am I missing something

Yes, you are. My example uses from(Task) whereas yours from(String). Gradle will automatically add task dependencies for FileCollection backed by a task. When using string or File you’ll need to explicitly add the Task dependency

Thanks. How does one do that when using the Kotlin DSL? I’ve declared my task so:

tasks.register("gitLog") {  
    // ... 
}

tasks.processResources {
    from(???)
}

I did the following instead, and it seems to work. Does this look terribly non-idiomatic?

tasks.processResources {

    into("b") {
        from("a")
    }
    
    into("d") {
        from("c")
    }
}

D’uh. Figured it out.

val gitLog = tasks.register("gitLog") {  
    // ... 
}

Does this look terribly non-idiomatic?

I prefer my source directories to contain the subdirectories. So “a” has a “b” subdirectory and “c” has a “d” subdirectory. Then I can just do

processResources {
   from 'a' 
   from 'c' 
} 

It simplifies things and also makes it more future proof