kotlingradle

How to set an Input with the content of an OutputFile


With a task producing an OutputFile, for example:

abstract class TimeStampTask : DefaultTask() {

    @get: OutputFile
    abstract val output: RegularFileProperty 

    @TaskAction
    fun createFile() {
        val outputFile = output.get().asFile
        if (outputFile.exists()) {
            outputFile.delete()
        }
        outputFile.createNewFile()
        outputFile.appendText(Date().time.toString())
    }
}

I want to lazily set the value of an Input for another task with the content of the produced OutputFile, during configuration phase. For example in build.gradle.kts :

val timestampTask = project.tasks.register<TimeStampTask>("timestamp") {
    output.set(layout.buildDirectory.file(".timestamp"))
}

tasks.register<Zip>("packageDistribution") {
    archiveFileName = timestampTask.flatMap { task ->
        task.output.flatMap { project.provider { it.asFile.readText() } }
    }
    destinationDirectory = layout.buildDirectory.dir("dist")
    from(layout.buildDirectory.dir("toArchive"))
}

But during execution of the task, there's a java.io.FileNotFoundException: xxxx/.timestamp (The system cannot find the file specified)

Is it possible to set lazily during configuration phase? Or there is no other possibility than setting in a doFirst block?


Solution

  • You don’t need to fall back to a doFirst. You can keep it entirely lazy in the configuration phase, you just have to

    1. declare a dependency so that Gradle knows “packageDistribution” must run after “timestamp”,

    2. use the Provider-API properly (calling .set(...) on the Property, not assigning with =), and

    3. chain .flatMap{…}.map{…} (or just .map{…} on the task’s output) so that the file is only ever read at execution-time, after it’s been created.

    val timestampTask = tasks.register<TimeStampTask>("timestamp") {
      output.set(layout.buildDirectory.file(".timestamp"))
    }
    
    // derive a Provider<String> that will read the file later
    val timestampContents: Provider<String> = timestampTask
      // flatMap on a TaskProvider gives you a Task object at execution time
      .flatMap { ts ->               
        // ts.output is a RegularFileProperty; map its file to its contents
        ts.output.map { regFile ->
          regFile.asFile.readText()  // <-- this runs only when somebody actually resolves the Provider
        }
      }
    
    tasks.register<Zip>("packageDistribution") {
      // make sure we run timestamp before packaging
      dependsOn(timestampTask)        
    
      // set the archiveFileName to our lazy provider
      archiveFileName.set(timestampContents)
    
      destinationDirectory.set(layout.buildDirectory.dir("dist"))
      from(layout.buildDirectory.dir("toArchive"))
    }