Gradle has incremental build support to speed up our builds. This means Gradle checks input and output for a task and if something changed the task is executed, otherwise the task is skipped. In previous posts we learned how to add incremental build support to our tasks with annotations and inputs
and outputs
property of a task. When we have a task that has an output file for an input file, like with transformations, we can have a more efficient task using an incremental task action. With an incremental task action we have extra information on the files that are handled by the task. We can have different actions based on if an input file is out of date or removed. This way we can handle only the input files that have changed or removed with incremental builds, instead of all the input files.
To create an incremental task action we must have a task action method (annotated with @TaskAction
) that has a single argument of type IncrementalTaskInputs
. The IncrementalTaskInputs
class has the method outOfDate
and removed
. These methods take an action, that can be implemented with a closure, with an instance of InputFileDetails
as argument. We can get to the input file via this instance and use that for our task logic. When an input file is out of date, because the file contents has changed or the output file has been removed, the action we defined for the outOfDate
method is invoked. If the input file is removed the action for the method removed
is invoked.
In the following example we have a task HtmlConverter
with a task action that support incremental builds for input files. When an input file has changed it is processed, otherwise it is skipped. In the build file we create the task convert
that uses the HtmlConverter
task class:
// Create task to convert text to HTML. task convert(type: HtmlConverter) { sourceDir = file('src/docs/text') outputDir = file("${buildDir}/html") } import groovy.xml.MarkupBuilder import groovy.transform.CompileStatic import groovy.transform.CompileDynamic /** * Simple Gradle task that takes an text * input file and converts it to a HTML file. */ @CompileStatic class HtmlConverter extends DefaultTask { @InputDirectory @PathSensitive(PathSensitivity.RELATIVE) File sourceDir @OutputDirectory File outputDir /** * Task action that will check if a source file is out of date * or removed. If out of date the source file is converted * to HTML. If source file is removed the generated * HTML file is removed. * * @param inputs Used for incremental task action. */ @TaskAction void convert(IncrementalTaskInputs inputs) { // If the user for example used --rerun-tasks // this task is not incremental. Only // inputs.outOfDate is executed, so we must first // remove all output files. if (!inputs.incremental) { project.delete(outputDir.listFiles()) } // Input file has changed, so we convert it. inputs.outOfDate { InputFileDetails outOfDate -> convertFile(outOfDate.file) } // Input file is removed, so we remove the // output file that was created for the input file. inputs.removed { InputFileDetails removed -> removeOutputFile(removed.file) } } /** * Convert text file to HTML. * * @param file Text file to convert to HTML. */ private void convertFile(final File file) { logger.lifecycle 'Convert file {}', file.name final lines = file.readLines() final outputWriter = new FileWriter(new File(outputDir, outputFilename(file))) writeHtml(lines, outputWriter) } /** * Use first line as title for HTML, rest is body. * * @param lines Lines to transform to HTML. * @param writer Writer to write HTML to. */ @CompileDynamic private void writeHtml(final List lines, final Writer writer) { final html = new MarkupBuilder(writer) html.html { head { title lines[0] } body { lines[2..-1].each { line -> p line } } } } /** * Remove the output file thas was created for the * given input file. * * @param file Input file to remove output file for. */ private void removeOutputFile(final File file) { logger.lifecycle 'Remove HTML for file {}', file.name new File(outputDir, outputFilename(file)).delete() } /** * Determine HTML output filename based on base of input filename. * * @param file Used to create HTML output file name. */ private String outputFilename(final File file) { file.name[0..file.name.lastIndexOf('.')] + 'html' } }
In our project we have 3 source files in the directory src/docs/text
: sample1.txt
, sample2.txt
and hello.txt
. We run the convert
task for the first time and we see all input files are processed:
$ gradle convert :convert Convert file hello.txt Convert file sample1.txt Convert file sample2.txt BUILD SUCCESSFUL Total time: 0.948 secs
Next we change hello.txt
and when re-run the task we see only our changed file is processed. If we rename it after the change we can see the hello.html
is removed and the new file is processed:
$ echo "Gradle rocks" >> src/docs/text/hello.txt $ gradle convert :convert Convert file hello.txt BUILD SUCCESSFUL Total time: 0.793 secs $ mv src/docs/text/hello.txt src/docs/text/sample.txt $ gradle convert :convert Convert file sample.txt Remove HTML for file hello.txt BUILD SUCCESSFUL Total time: 0.76 secs $
Written with Gradle 3.5.