It is good to have fast builds. They provide immediate feedback and generally cut a lot of slack. Towards this end, every build tool does some form of optimization. The most common optimization is not running tasks that are deemed to up-to-date.
As an example, consider a compile task that takes in java files and generates class files. It is deemed to be not up-to-date when
- There are no class files present in the working set
- The input files were modified
- The output files generated after compile are different than the output files generated by the previous run
The last point is somewhat non obvious. There could be a lot of reasons why the output of a compile can be different for the same input and one of them is the way we link libraries. But regardless, it might be difficult to see how rerunning a task regardless of whether the input has changed or not could possibly an optimization. If a build tool after executing a task deems that it is up-to-date, it need not run tasks that “depend upon” the current task. For example, we need not run the package task if there was no change to the binaries.
Historically, tools have used timestamps to determine whether the sources are up-to-date or not. This strategy is error prone and it could not possibly be extended to apply to the output files as they are regenerated every time you run the task. Hence gradle uses hashes of these files to determine whether they are up-to-date. This is similar to how git determines whether the content has changed.
There are is one small problem with this strategy. Gradle never considers tasks that do not generate any output to be up to date. To overcome this problem, gradle lets you define your own strategy to determine whether the task is up to date using the
upToDateWhen() method of
TaskOutputs. This has proven to be a very effective strategy that significantly reduces build times and avoid hard to debug errors.