How should task inputs be properly handled?


(Chris Doré) #1

I thought I understood how task inputs and outputs worked, but I just came across a case that broke my understanding.

Given these two tasks:

task a {
    ext {
        outDir = new File( buildDir, 'aout' )
        outFile = new File( buildDir, 'a.out' )
    }
    outputs.dir( outDir )
    outputs.file( outFile )
    doLast {
        outDir.mkdirs()
        new File( outDir, 'a.anotherOut' ).text = 'A'
        outFile.text = 'A'
    }
}
task b {
    ext {
        inThings = [tasks.a, file( 'another.in' )]
    }
    inputs.files( inThings )
    doLast {
        println inputs.files.join( '\n' )
    }
}

I expect the execution of task b to list the a.out, a.anotherOut, and another.in files. Instead the aout directory is listed in place of a.anotherOut. My expectation is based on the documentation of TaskInputs.files()/Project.files(), which states that a Task is converted to its outputs. Since TaskOutputs.dir() includes the files in the directory as part of the task’s outputs, I figured those child files would be part of b’s input files.

$ ./gradlew b
:a
:b
/home/cdore/sandbox/gradle-tests/gradle-taskoutputs-test/build/aout
/home/cdore/sandbox/gradle-tests/gradle-taskoutputs-test/build/a.out
/home/cdore/sandbox/gradle-tests/gradle-taskoutputs-test/another.in

Looking at how SourceTask.getSource() works, it would seem that b should be something like the following instead, which works:

task c {
    ext {
        inThings = [tasks.a, file( 'another.in' )]
    }
    inputs.files( project.files( inThings ).asFileTree )
    doLast {
        println inputs.files.join( '\n' )
    }
}

$ ./gradlew c
:a
:c
/home/cdore/sandbox/gradle-tests/gradle-taskoutputs-test/build/aout/a.anotherOut
/home/cdore/sandbox/gradle-tests/gradle-taskoutputs-test/build/a.out
/home/cdore/sandbox/gradle-tests/gradle-taskoutputs-test/another.in

Is there a more appropriate way to achieve the correct results, or is this “project.files(inputStuff).asFileTree” pattern something I should be using in my tasks and task classes going forward.

Thanks, Chris


(Stefan Oehme) #2

This is behaving as expected. The output of a is one file (a.out) and one directory (aout). By calling files(inThings) you are treating each as a single file. If you want it to be treated as a directory (making each child an input file), you’d have to call dir(...).


(Chris Doré) #3

OK, I think I see the critical piece of understanding that wasn’t apparent to me.

TaskInputs.files( Task ) != Converted to the task's output files (which I
                         erroneously interpreted as, "as recorded by the
                         up-to-date checking").

it is more like:

TaskInputs.files( Task ) == Converted to the task's _configured_ output files
                            and directories.

In other words, even though Gradle is recording/hashing a’s output files for up-to-date checking (including the ones in inside the output directory), that information has nothing to do with what is reported as a’s output files when configuring downstream tasks.
This makes sense I guess, since configuring b’s inputs happens during configuration, when Gradle doesn’t yet know anything about the actual files in a’s output directory.
I had it in my head that there was some magic going on when using a task as an input to another task, such that the files generated into the output directory at execution time would be fed directly into the consuming task’s inputs. Now I see that it’s just the configuration data that is being passed around.

Thanks for the trigger Stefan,
Chris


(Stefan Oehme) #4

You’re welcome! And your final explanation is even better than mine :slight_smile: